2019-03-14 07:09:45 +01:00
|
|
|
/*
|
|
|
|
* The MIT License (MIT)
|
2019-03-18 22:25:27 +01:00
|
|
|
*
|
2019-03-14 07:09:45 +01:00
|
|
|
* Copyright (c) 2012, 2013 <dhbaird@gmail.com>
|
2019-03-18 22:25:27 +01:00
|
|
|
*
|
2019-03-14 07:09:45 +01:00
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
2019-03-18 22:25:27 +01:00
|
|
|
*
|
2019-03-14 07:09:45 +01:00
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
2019-03-18 22:25:27 +01:00
|
|
|
*
|
2019-03-14 07:09:45 +01:00
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
/*
|
|
|
|
* IXWebSocketTransport.cpp
|
|
|
|
* Author: Benjamin Sergeant
|
2019-03-14 07:09:45 +01:00
|
|
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
2018-09-27 23:56:48 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
//
|
|
|
|
// Adapted from https://github.com/dhbaird/easywsclient
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "IXWebSocketTransport.h"
|
2019-01-03 05:07:54 +01:00
|
|
|
#include "IXWebSocketHandshake.h"
|
2018-11-10 03:23:49 +01:00
|
|
|
#include "IXWebSocketHttpHeaders.h"
|
2019-03-01 06:54:03 +01:00
|
|
|
#include "IXUrlParser.h"
|
|
|
|
#include "IXSocketFactory.h"
|
2018-09-27 23:56:48 +02:00
|
|
|
|
|
|
|
#include <string.h>
|
2018-10-09 06:42:45 +02:00
|
|
|
#include <stdlib.h>
|
2018-09-27 23:56:48 +02:00
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <vector>
|
|
|
|
#include <string>
|
|
|
|
#include <cstdarg>
|
|
|
|
#include <sstream>
|
2019-02-21 03:59:07 +01:00
|
|
|
#include <chrono>
|
|
|
|
#include <thread>
|
2018-09-27 23:56:48 +02:00
|
|
|
|
|
|
|
|
2019-04-26 01:21:36 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
int greatestCommonDivisor(int a, int b)
|
|
|
|
{
|
|
|
|
while (b != 0)
|
|
|
|
{
|
|
|
|
int t = b;
|
|
|
|
b = a % b;
|
|
|
|
a = t;
|
|
|
|
}
|
2019-04-18 18:24:16 +02:00
|
|
|
|
2019-04-26 01:21:36 +02:00
|
|
|
return a;
|
|
|
|
}
|
2019-04-18 18:24:16 +02:00
|
|
|
}
|
|
|
|
|
2018-11-10 03:23:49 +01:00
|
|
|
namespace ix
|
|
|
|
{
|
2019-04-18 18:24:16 +02:00
|
|
|
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
|
|
|
|
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
|
|
|
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
|
|
|
|
const bool WebSocketTransport::kDefaultEnablePong(true);
|
2019-05-09 18:21:05 +02:00
|
|
|
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200);
|
2019-02-21 03:59:07 +01:00
|
|
|
constexpr size_t WebSocketTransport::kChunkSize;
|
2019-05-09 18:21:05 +02:00
|
|
|
|
2019-04-19 18:16:25 +02:00
|
|
|
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
|
|
|
|
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
|
2019-04-23 13:31:55 +02:00
|
|
|
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
|
2019-05-09 18:21:05 +02:00
|
|
|
const uint16_t WebSocketTransport::kNoStatusCodeErrorCode(1005);
|
2019-04-18 19:02:31 +02:00
|
|
|
const std::string WebSocketTransport::kInternalErrorMessage("Internal error");
|
|
|
|
const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure");
|
2019-04-19 18:16:25 +02:00
|
|
|
const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout");
|
2019-04-23 13:31:55 +02:00
|
|
|
const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error");
|
2019-05-09 18:21:05 +02:00
|
|
|
const std::string WebSocketTransport::kNoStatusCodeErrorMessage("No status code");
|
2019-01-24 21:42:49 +01:00
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
WebSocketTransport::WebSocketTransport() :
|
2019-03-22 17:53:56 +01:00
|
|
|
_useMask(true),
|
2019-05-11 23:22:06 +02:00
|
|
|
_readyState(ReadyState::CLOSED),
|
2019-04-18 19:02:31 +02:00
|
|
|
_closeCode(kInternalErrorCode),
|
|
|
|
_closeReason(kInternalErrorMessage),
|
2018-12-15 01:28:11 +01:00
|
|
|
_closeWireSize(0),
|
2019-04-23 13:31:55 +02:00
|
|
|
_closeRemote(false),
|
2018-12-23 23:18:53 +01:00
|
|
|
_enablePerMessageDeflate(false),
|
2019-01-24 21:42:49 +01:00
|
|
|
_requestInitCancellation(false),
|
2019-05-09 18:21:05 +02:00
|
|
|
_closingTimePoint(std::chrono::steady_clock::now()),
|
2019-04-18 18:24:16 +02:00
|
|
|
_enablePong(kDefaultEnablePong),
|
|
|
|
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
|
|
|
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
|
|
|
|
_pingIntervalOrTimeoutGCDSecs(-1),
|
2019-05-09 18:21:05 +02:00
|
|
|
_nextGCDTimePoint(std::chrono::steady_clock::now()),
|
2019-04-18 18:24:16 +02:00
|
|
|
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
|
|
|
|
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-02-21 03:59:07 +01:00
|
|
|
_readbuf.resize(kChunkSize);
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
WebSocketTransport::~WebSocketTransport()
|
|
|
|
{
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2019-01-24 21:42:49 +01:00
|
|
|
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
2019-04-18 18:24:16 +02:00
|
|
|
bool enablePong,
|
2019-04-20 01:57:38 +02:00
|
|
|
int pingIntervalSecs,
|
|
|
|
int pingTimeoutSecs)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2018-11-10 03:23:49 +01:00
|
|
|
_perMessageDeflateOptions = perMessageDeflateOptions;
|
|
|
|
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
|
2019-04-18 18:24:16 +02:00
|
|
|
_enablePong = enablePong;
|
|
|
|
_pingIntervalSecs = pingIntervalSecs;
|
|
|
|
_pingTimeoutSecs = pingTimeoutSecs;
|
|
|
|
|
|
|
|
if (pingIntervalSecs > 0 && pingTimeoutSecs > 0)
|
2019-04-20 01:57:38 +02:00
|
|
|
{
|
|
|
|
_pingIntervalOrTimeoutGCDSecs = greatestCommonDivisor(pingIntervalSecs,
|
|
|
|
pingTimeoutSecs);
|
|
|
|
}
|
2019-04-18 18:24:16 +02:00
|
|
|
else if (_pingTimeoutSecs > 0)
|
2019-04-20 01:57:38 +02:00
|
|
|
{
|
2019-04-18 18:24:16 +02:00
|
|
|
_pingIntervalOrTimeoutGCDSecs = pingTimeoutSecs;
|
2019-04-20 01:57:38 +02:00
|
|
|
}
|
2019-04-18 18:24:16 +02:00
|
|
|
else
|
2019-04-20 01:57:38 +02:00
|
|
|
{
|
2019-04-18 18:24:16 +02:00
|
|
|
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
|
2019-04-20 01:57:38 +02:00
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2018-12-30 08:15:27 +01:00
|
|
|
// Client
|
2019-01-04 03:33:08 +01:00
|
|
|
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
|
|
|
|
int timeoutSecs)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
|
|
|
std::string protocol, host, path, query;
|
|
|
|
int port;
|
|
|
|
|
2019-05-06 23:45:02 +02:00
|
|
|
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2018-12-06 17:27:28 +01:00
|
|
|
return WebSocketInitResult(false, 0,
|
2018-12-31 21:43:47 +01:00
|
|
|
std::string("Could not parse URL ") + url);
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-03-01 06:54:03 +01:00
|
|
|
bool tls = protocol == "wss";
|
|
|
|
std::string errorMsg;
|
|
|
|
_socket = createSocket(tls, errorMsg);
|
|
|
|
|
|
|
|
if (!_socket)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-03-01 06:54:03 +01:00
|
|
|
return WebSocketInitResult(false, 0, errorMsg);
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-01-03 05:07:54 +01:00
|
|
|
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
|
|
|
_socket,
|
|
|
|
_perMessageDeflate,
|
|
|
|
_perMessageDeflateOptions,
|
|
|
|
_enablePerMessageDeflate);
|
2018-11-10 03:23:49 +01:00
|
|
|
|
2019-01-04 03:33:08 +01:00
|
|
|
auto result = webSocketHandshake.clientHandshake(url, host, path, port,
|
|
|
|
timeoutSecs);
|
2019-01-03 05:07:54 +01:00
|
|
|
if (result.success)
|
2018-11-07 23:54:44 +01:00
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::OPEN);
|
2018-11-07 23:54:44 +01:00
|
|
|
}
|
2019-01-03 05:07:54 +01:00
|
|
|
return result;
|
2019-01-03 01:08:32 +01:00
|
|
|
}
|
|
|
|
|
2018-12-30 08:15:27 +01:00
|
|
|
// Server
|
2019-01-04 03:33:08 +01:00
|
|
|
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
2018-12-30 06:53:33 +01:00
|
|
|
{
|
2019-03-22 23:33:04 +01:00
|
|
|
// Server should not mask the data it sends to the client
|
|
|
|
_useMask = false;
|
2019-03-22 17:53:56 +01:00
|
|
|
|
2019-03-15 02:37:38 +01:00
|
|
|
std::string errorMsg;
|
|
|
|
_socket = createSocket(fd, errorMsg);
|
|
|
|
|
|
|
|
if (!_socket)
|
|
|
|
{
|
|
|
|
return WebSocketInitResult(false, 0, errorMsg);
|
|
|
|
}
|
2018-12-30 06:53:33 +01:00
|
|
|
|
2019-01-03 05:07:54 +01:00
|
|
|
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
|
|
|
_socket,
|
|
|
|
_perMessageDeflate,
|
|
|
|
_perMessageDeflateOptions,
|
|
|
|
_enablePerMessageDeflate);
|
2018-12-30 08:15:27 +01:00
|
|
|
|
2019-01-04 03:33:08 +01:00
|
|
|
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
|
2019-01-03 05:07:54 +01:00
|
|
|
if (result.success)
|
2018-12-30 08:15:27 +01:00
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::OPEN);
|
2018-12-30 08:15:27 +01:00
|
|
|
}
|
2019-01-03 05:07:54 +01:00
|
|
|
return result;
|
2018-12-30 06:53:33 +01:00
|
|
|
}
|
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
WebSocketTransport::ReadyState WebSocketTransport::getReadyState() const
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
|
|
|
return _readyState;
|
|
|
|
}
|
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
void WebSocketTransport::setReadyState(ReadyState readyState)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2018-11-02 01:02:49 +01:00
|
|
|
// No state change, return
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState == readyState) return;
|
2018-11-02 01:02:49 +01:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
if (readyState == ReadyState::CLOSED)
|
2018-10-26 03:51:19 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
2019-04-23 13:31:55 +02:00
|
|
|
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
|
2019-04-18 19:02:31 +02:00
|
|
|
_closeCode = kInternalErrorCode;
|
|
|
|
_closeReason = kInternalErrorMessage;
|
2018-12-15 01:28:11 +01:00
|
|
|
_closeWireSize = 0;
|
2019-04-23 13:31:55 +02:00
|
|
|
_closeRemote = false;
|
2018-10-26 03:51:19 +02:00
|
|
|
}
|
2019-05-13 18:08:46 +02:00
|
|
|
else if (readyState == ReadyState::OPEN)
|
|
|
|
{
|
|
|
|
initTimePointsAndGCDAfterConnect();
|
|
|
|
}
|
2018-10-26 03:51:19 +02:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
_readyState = readyState;
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2018-10-26 03:51:19 +02:00
|
|
|
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-02-21 03:59:07 +01:00
|
|
|
_onCloseCallback = onCloseCallback;
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-05-13 18:08:46 +02:00
|
|
|
void WebSocketTransport::initTimePointsAndGCDAfterConnect()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
|
|
|
|
_lastSendPingTimePoint = std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
|
|
|
|
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
|
|
|
{
|
|
|
|
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-18 18:24:16 +02:00
|
|
|
// Only consider send PING time points for that computation.
|
|
|
|
bool WebSocketTransport::pingIntervalExceeded()
|
|
|
|
{
|
|
|
|
if (_pingIntervalSecs <= 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WebSocketTransport::pingTimeoutExceeded()
|
2019-01-26 01:11:39 +01:00
|
|
|
{
|
2019-04-18 18:24:16 +02:00
|
|
|
if (_pingTimeoutSecs <= 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
|
2019-01-26 01:11:39 +01:00
|
|
|
auto now = std::chrono::steady_clock::now();
|
2019-04-18 18:24:16 +02:00
|
|
|
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
|
2019-01-26 01:11:39 +01:00
|
|
|
}
|
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
bool WebSocketTransport::closingDelayExceeded()
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-05-09 18:21:05 +02:00
|
|
|
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs);
|
|
|
|
}
|
2019-04-19 01:42:44 +02:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
WebSocketTransport::PollResult WebSocketTransport::poll()
|
2019-05-09 18:21:05 +02:00
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState == ReadyState::OPEN)
|
2019-04-19 01:42:44 +02:00
|
|
|
{
|
2019-04-20 01:57:38 +02:00
|
|
|
// if (1) ping timeout is enabled and (2) duration since last received
|
|
|
|
// ping response (PONG) exceeds the maximum delay, then close the connection
|
2019-04-19 01:42:44 +02:00
|
|
|
if (pingTimeoutExceeded())
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-04-19 18:41:16 +02:00
|
|
|
close(kInternalErrorCode, kPingTimeoutMessage);
|
2019-04-19 01:42:44 +02:00
|
|
|
}
|
2019-04-20 01:57:38 +02:00
|
|
|
// If ping is enabled and no ping has been sent for a duration
|
2019-04-19 01:42:44 +02:00
|
|
|
// exceeding our ping interval, send a ping to the server.
|
|
|
|
else if (pingIntervalExceeded())
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
|
|
|
|
sendPing(ss.str());
|
|
|
|
}
|
|
|
|
}
|
2019-05-09 18:21:05 +02:00
|
|
|
|
|
|
|
// No timeout if state is not OPEN, otherwise computed
|
|
|
|
// pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set)
|
2019-05-11 23:22:06 +02:00
|
|
|
int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs;
|
2019-05-09 18:21:05 +02:00
|
|
|
|
|
|
|
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
|
|
|
{
|
|
|
|
// compute lasting delay to wait for next ping / timeout, if at least one set
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
if (now >= _nextGCDTimePoint)
|
|
|
|
{
|
|
|
|
_nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
|
|
|
|
|
|
|
lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-13 07:21:56 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20;
|
|
|
|
#endif
|
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
// poll the socket
|
|
|
|
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
|
2019-04-18 18:24:16 +02:00
|
|
|
|
2019-04-19 01:42:44 +02:00
|
|
|
// Make sure we send all the buffered data
|
|
|
|
// there can be a lot of it for large messages.
|
|
|
|
if (pollResult == PollResultType::SendRequest)
|
|
|
|
{
|
|
|
|
while (!isSendBufferEmpty() && !_requestInitCancellation)
|
|
|
|
{
|
|
|
|
// Wait with a 10ms timeout until the socket is ready to write.
|
|
|
|
// This way we are not busy looping
|
|
|
|
PollResultType result = _socket->isReadyToWrite(10);
|
|
|
|
|
|
|
|
if (result == PollResultType::Error)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-04-19 01:42:44 +02:00
|
|
|
_socket->close();
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::CLOSED);
|
2019-04-19 01:42:44 +02:00
|
|
|
break;
|
2019-03-14 07:09:45 +01:00
|
|
|
}
|
2019-04-19 01:42:44 +02:00
|
|
|
else if (result == PollResultType::ReadyForWrite)
|
2019-03-14 07:09:45 +01:00
|
|
|
{
|
2019-04-19 01:42:44 +02:00
|
|
|
sendOnSocket();
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
2019-04-19 01:42:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (pollResult == PollResultType::ReadyForRead)
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
|
|
|
|
2019-05-06 18:13:42 +02:00
|
|
|
if (ret < 0 && Socket::isWaitNeeded())
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-04-19 01:42:44 +02:00
|
|
|
break;
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
2019-04-19 01:42:44 +02:00
|
|
|
else if (ret <= 0)
|
2019-03-14 07:09:45 +01:00
|
|
|
{
|
2019-05-09 18:21:05 +02:00
|
|
|
// if there are received data pending to be processed, then delay the abnormal closure
|
|
|
|
// to after dispatch (other close code/reason could be read from the buffer)
|
|
|
|
|
2019-03-16 19:38:23 +01:00
|
|
|
_socket->close();
|
2019-05-09 18:21:05 +02:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
return PollResult::AbnormalClose;
|
2019-03-14 07:09:45 +01:00
|
|
|
}
|
2019-04-19 01:42:44 +02:00
|
|
|
else
|
2019-03-16 19:38:23 +01:00
|
|
|
{
|
2019-04-19 01:42:44 +02:00
|
|
|
_rxbuf.insert(_rxbuf.end(),
|
|
|
|
_readbuf.begin(),
|
|
|
|
_readbuf.begin() + ret);
|
2019-03-16 19:38:23 +01:00
|
|
|
}
|
2019-04-19 01:42:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (pollResult == PollResultType::Error)
|
|
|
|
{
|
|
|
|
_socket->close();
|
|
|
|
}
|
|
|
|
else if (pollResult == PollResultType::CloseRequest)
|
|
|
|
{
|
|
|
|
_socket->close();
|
|
|
|
}
|
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState == ReadyState::CLOSING && closingDelayExceeded())
|
2019-04-19 01:42:44 +02:00
|
|
|
{
|
2019-05-09 18:21:05 +02:00
|
|
|
_rxbuf.clear();
|
|
|
|
// close code and reason were set when calling close()
|
2019-04-19 01:42:44 +02:00
|
|
|
_socket->close();
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::CLOSED);
|
2019-04-19 01:42:44 +02:00
|
|
|
}
|
2019-05-09 18:21:05 +02:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
return PollResult::Succeeded;
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool WebSocketTransport::isSendBufferEmpty() const
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
|
|
|
return _txbuf.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebSocketTransport::appendToSendBuffer(const std::vector<uint8_t>& header,
|
|
|
|
std::string::const_iterator begin,
|
|
|
|
std::string::const_iterator end,
|
|
|
|
uint64_t message_size,
|
|
|
|
uint8_t masking_key[4])
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
|
|
|
|
|
|
|
_txbuf.insert(_txbuf.end(), header.begin(), header.end());
|
|
|
|
_txbuf.insert(_txbuf.end(), begin, end);
|
|
|
|
|
2019-03-22 17:53:56 +01:00
|
|
|
if (_useMask)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-03-22 17:53:56 +01:00
|
|
|
for (size_t i = 0; i != (size_t) message_size; ++i)
|
|
|
|
{
|
|
|
|
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
|
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-26 03:51:19 +02:00
|
|
|
void WebSocketTransport::unmaskReceiveBuffer(const wsheader_type& ws)
|
|
|
|
{
|
|
|
|
if (ws.mask)
|
|
|
|
{
|
|
|
|
for (size_t j = 0; j != ws.N; ++j)
|
|
|
|
{
|
|
|
|
_rxbuf[j+ws.header_size] ^= ws.masking_key[j&0x3];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
//
|
|
|
|
// http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol
|
|
|
|
//
|
|
|
|
// 0 1 2 3
|
|
|
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
|
|
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
|
|
|
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
|
|
|
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
|
|
|
// |N|V|V|V| |S| | (if payload len==126/127) |
|
|
|
|
// | |1|2|3| |K| | |
|
|
|
|
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
|
|
|
// | Extended payload length continued, if payload len == 127 |
|
|
|
|
// + - - - - - - - - - - - - - - - +-------------------------------+
|
|
|
|
// | |Masking-key, if MASK set to 1 |
|
|
|
|
// +-------------------------------+-------------------------------+
|
|
|
|
// | Masking-key (continued) | Payload Data |
|
|
|
|
// +-------------------------------- - - - - - - - - - - - - - - - +
|
|
|
|
// : Payload Data continued ... :
|
|
|
|
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
|
|
|
// | Payload Data continued ... |
|
|
|
|
// +---------------------------------------------------------------+
|
|
|
|
//
|
2019-05-11 23:22:06 +02:00
|
|
|
void WebSocketTransport::dispatch(WebSocketTransport::PollResult pollResult,
|
2019-05-09 18:21:05 +02:00
|
|
|
const OnMessageCallback& onMessageCallback)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-02-21 03:59:07 +01:00
|
|
|
while (true)
|
2018-10-26 03:51:19 +02:00
|
|
|
{
|
2018-09-27 23:56:48 +02:00
|
|
|
wsheader_type ws;
|
2019-05-09 18:21:05 +02:00
|
|
|
if (_rxbuf.size() < 2) break; /* Need at least 2 */
|
2018-09-27 23:56:48 +02:00
|
|
|
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
|
|
|
|
ws.fin = (data[0] & 0x80) == 0x80;
|
2018-11-07 20:45:17 +01:00
|
|
|
ws.rsv1 = (data[0] & 0x40) == 0x40;
|
2018-09-27 23:56:48 +02:00
|
|
|
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
|
|
|
|
ws.mask = (data[1] & 0x80) == 0x80;
|
|
|
|
ws.N0 = (data[1] & 0x7f);
|
|
|
|
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
2019-05-09 18:21:05 +02:00
|
|
|
if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
//
|
|
|
|
// Calculate payload length:
|
|
|
|
// 0-125 mean the payload is that long.
|
|
|
|
// 126 means that the following two bytes indicate the length,
|
|
|
|
// 127 means the next 8 bytes indicate the length.
|
|
|
|
//
|
|
|
|
int i = 0;
|
|
|
|
if (ws.N0 < 126)
|
|
|
|
{
|
|
|
|
ws.N = ws.N0;
|
|
|
|
i = 2;
|
|
|
|
}
|
|
|
|
else if (ws.N0 == 126)
|
|
|
|
{
|
|
|
|
ws.N = 0;
|
|
|
|
ws.N |= ((uint64_t) data[2]) << 8;
|
|
|
|
ws.N |= ((uint64_t) data[3]) << 0;
|
|
|
|
i = 4;
|
|
|
|
}
|
|
|
|
else if (ws.N0 == 127)
|
|
|
|
{
|
|
|
|
ws.N = 0;
|
|
|
|
ws.N |= ((uint64_t) data[2]) << 56;
|
|
|
|
ws.N |= ((uint64_t) data[3]) << 48;
|
|
|
|
ws.N |= ((uint64_t) data[4]) << 40;
|
|
|
|
ws.N |= ((uint64_t) data[5]) << 32;
|
|
|
|
ws.N |= ((uint64_t) data[6]) << 24;
|
|
|
|
ws.N |= ((uint64_t) data[7]) << 16;
|
|
|
|
ws.N |= ((uint64_t) data[8]) << 8;
|
|
|
|
ws.N |= ((uint64_t) data[9]) << 0;
|
|
|
|
i = 10;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// invalid payload length according to the spec. bail out
|
|
|
|
return;
|
|
|
|
}
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
if (ws.mask)
|
|
|
|
{
|
|
|
|
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
|
|
|
|
ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;
|
|
|
|
ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;
|
|
|
|
ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ws.masking_key[0] = 0;
|
|
|
|
ws.masking_key[1] = 0;
|
|
|
|
ws.masking_key[2] = 0;
|
|
|
|
ws.masking_key[3] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_rxbuf.size() < ws.header_size+ws.N)
|
|
|
|
{
|
|
|
|
return; /* Need: ws.header_size+ws.N - _rxbuf.size() */
|
|
|
|
}
|
|
|
|
|
|
|
|
// We got a whole message, now do something with it:
|
|
|
|
if (
|
2019-02-21 03:59:07 +01:00
|
|
|
ws.opcode == wsheader_type::TEXT_FRAME
|
2018-09-27 23:56:48 +02:00
|
|
|
|| ws.opcode == wsheader_type::BINARY_FRAME
|
|
|
|
|| ws.opcode == wsheader_type::CONTINUATION
|
|
|
|
) {
|
2018-10-26 03:51:19 +02:00
|
|
|
unmaskReceiveBuffer(ws);
|
2018-11-07 23:54:44 +01:00
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
//
|
|
|
|
// Usual case. Small unfragmented messages
|
|
|
|
//
|
|
|
|
if (ws.fin && _chunks.empty())
|
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
emitMessage(MessageKind::MSG,
|
2019-02-21 03:59:07 +01:00
|
|
|
std::string(_rxbuf.begin()+ws.header_size,
|
|
|
|
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
|
|
|
ws,
|
|
|
|
onMessageCallback);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// Add intermediary message to our chunk list.
|
|
|
|
// We use a chunk list instead of a big buffer because resizing
|
|
|
|
// large buffer can be very costly when we need to re-allocate
|
|
|
|
// the internal buffer which is slow and can let the internal OS
|
|
|
|
// receive buffer fill out.
|
|
|
|
//
|
|
|
|
_chunks.emplace_back(
|
|
|
|
std::vector<uint8_t>(_rxbuf.begin()+ws.header_size,
|
|
|
|
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
|
|
|
if (ws.fin)
|
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
|
2019-02-21 03:59:07 +01:00
|
|
|
_chunks.clear();
|
|
|
|
}
|
2019-03-11 19:12:43 +01:00
|
|
|
else
|
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
emitMessage(MessageKind::FRAGMENT, std::string(), ws, onMessageCallback);
|
2019-03-11 19:12:43 +01:00
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ws.opcode == wsheader_type::PING)
|
|
|
|
{
|
2018-10-26 03:51:19 +02:00
|
|
|
unmaskReceiveBuffer(ws);
|
2019-04-18 18:24:16 +02:00
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
std::string pingData(_rxbuf.begin()+ws.header_size,
|
|
|
|
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
|
|
|
|
2019-04-18 18:24:16 +02:00
|
|
|
if (_enablePong)
|
|
|
|
{
|
|
|
|
// Reply back right away
|
|
|
|
bool compress = false;
|
|
|
|
sendData(wsheader_type::PONG, pingData, compress);
|
|
|
|
}
|
2018-10-25 21:01:47 +02:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
emitMessage(MessageKind::PING, pingData, ws, onMessageCallback);
|
2018-10-25 21:01:47 +02:00
|
|
|
}
|
|
|
|
else if (ws.opcode == wsheader_type::PONG)
|
|
|
|
{
|
2018-10-26 03:51:19 +02:00
|
|
|
unmaskReceiveBuffer(ws);
|
2018-10-25 21:01:47 +02:00
|
|
|
std::string pongData(_rxbuf.begin()+ws.header_size,
|
|
|
|
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
|
|
|
|
2019-04-18 18:24:16 +02:00
|
|
|
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
|
|
|
|
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
emitMessage(MessageKind::PONG, pongData, ws, onMessageCallback);
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
2018-10-26 03:51:19 +02:00
|
|
|
else if (ws.opcode == wsheader_type::CLOSE)
|
|
|
|
{
|
2019-05-09 18:21:05 +02:00
|
|
|
std::string reason;
|
|
|
|
uint16_t code = 0;
|
|
|
|
|
2018-10-26 03:51:19 +02:00
|
|
|
unmaskReceiveBuffer(ws);
|
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
if (ws.N >= 2)
|
|
|
|
{
|
|
|
|
// Extract the close code first, available as the first 2 bytes
|
|
|
|
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
|
|
|
|
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
|
|
|
|
|
|
|
|
// Get the reason.
|
|
|
|
if (ws.N > 2)
|
|
|
|
{
|
|
|
|
reason.assign(_rxbuf.begin()+ws.header_size + 2,
|
|
|
|
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// no close code received
|
|
|
|
code = kNoStatusCodeErrorCode;
|
|
|
|
reason = kNoStatusCodeErrorMessage;
|
|
|
|
}
|
2018-10-26 03:51:19 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
// We receive a CLOSE frame from remote and are NOT the ones who triggered the close
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState != ReadyState::CLOSING)
|
2019-05-09 18:21:05 +02:00
|
|
|
{
|
|
|
|
// send back the CLOSE frame
|
|
|
|
sendCloseFrame(code, reason);
|
2019-05-01 20:08:36 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
2018-10-26 03:51:19 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
bool remote = true;
|
|
|
|
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// we got the CLOSE frame answer from our close, so we can close the connection if
|
|
|
|
// the code/reason are the same
|
|
|
|
bool identicalReason;
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
|
|
|
identicalReason = _closeCode == code && _closeReason == reason;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (identicalReason)
|
|
|
|
{
|
|
|
|
bool remote = false;
|
|
|
|
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 03:51:19 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-04-23 13:31:55 +02:00
|
|
|
// Unexpected frame type
|
|
|
|
|
|
|
|
close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size());
|
2018-10-26 03:51:19 +02:00
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
// Erase the message that has been processed from the input/read buffer
|
2018-09-27 23:56:48 +02:00
|
|
|
_rxbuf.erase(_rxbuf.begin(),
|
|
|
|
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
|
|
|
}
|
2019-05-09 18:21:05 +02:00
|
|
|
|
|
|
|
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
|
|
|
|
// the received and processed data then close the connection
|
2019-05-11 23:22:06 +02:00
|
|
|
if (pollResult == PollResult::AbnormalClose)
|
2019-05-09 18:21:05 +02:00
|
|
|
{
|
|
|
|
_rxbuf.clear();
|
|
|
|
|
|
|
|
// if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before)
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState == ReadyState::CLOSING)
|
2019-05-09 18:21:05 +02:00
|
|
|
{
|
|
|
|
_socket->close();
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::CLOSED);
|
2019-05-09 18:21:05 +02:00
|
|
|
}
|
|
|
|
// if we weren't closing, then close using abnormal close code and message
|
2019-05-11 23:22:06 +02:00
|
|
|
else if (_readyState != ReadyState::CLOSED)
|
2019-05-09 18:21:05 +02:00
|
|
|
{
|
|
|
|
closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false);
|
|
|
|
}
|
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
std::string WebSocketTransport::getMergedChunks() const
|
|
|
|
{
|
|
|
|
size_t length = 0;
|
|
|
|
for (auto&& chunk : _chunks)
|
|
|
|
{
|
|
|
|
length += chunk.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string msg;
|
|
|
|
msg.reserve(length);
|
|
|
|
|
|
|
|
for (auto&& chunk : _chunks)
|
|
|
|
{
|
|
|
|
std::string str(chunk.begin(), chunk.end());
|
|
|
|
msg += str;
|
|
|
|
}
|
|
|
|
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebSocketTransport::emitMessage(MessageKind messageKind,
|
2018-11-10 03:23:49 +01:00
|
|
|
const std::string& message,
|
|
|
|
const wsheader_type& ws,
|
|
|
|
const OnMessageCallback& onMessageCallback)
|
|
|
|
{
|
2018-11-14 02:46:05 +01:00
|
|
|
size_t wireSize = message.size();
|
2018-11-10 03:23:49 +01:00
|
|
|
|
2018-11-14 02:46:05 +01:00
|
|
|
// When the RSV1 bit is 1 it means the message is compressed
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != MessageKind::FRAGMENT)
|
2018-11-10 03:23:49 +01:00
|
|
|
{
|
2018-11-14 02:46:05 +01:00
|
|
|
std::string decompressedMessage;
|
2018-11-15 00:52:28 +01:00
|
|
|
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
|
2019-01-05 02:28:13 +01:00
|
|
|
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
|
2018-11-10 03:23:49 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-11-15 00:52:28 +01:00
|
|
|
onMessageCallback(message, wireSize, false, messageKind);
|
2018-11-10 03:23:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
unsigned WebSocketTransport::getRandomUnsigned()
|
|
|
|
{
|
|
|
|
auto now = std::chrono::system_clock::now();
|
2019-02-21 03:59:07 +01:00
|
|
|
auto seconds =
|
2018-09-27 23:56:48 +02:00
|
|
|
std::chrono::duration_cast<std::chrono::seconds>(
|
|
|
|
now.time_since_epoch()).count();
|
|
|
|
return static_cast<unsigned>(seconds);
|
|
|
|
}
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
WebSocketSendInfo WebSocketTransport::sendData(
|
|
|
|
wsheader_type::opcode_type type,
|
|
|
|
const std::string& message,
|
|
|
|
bool compress,
|
|
|
|
const OnProgressCallback& onProgressCallback)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState != ReadyState::OPEN)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2018-11-10 03:23:49 +01:00
|
|
|
return WebSocketSendInfo();
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t payloadSize = message.size();
|
|
|
|
size_t wireSize = message.size();
|
|
|
|
std::string compressedMessage;
|
2018-11-15 00:52:28 +01:00
|
|
|
bool compressionError = false;
|
2018-11-10 03:23:49 +01:00
|
|
|
|
|
|
|
std::string::const_iterator message_begin = message.begin();
|
|
|
|
std::string::const_iterator message_end = message.end();
|
|
|
|
|
2018-11-12 18:00:55 +01:00
|
|
|
if (compress)
|
2018-11-10 03:23:49 +01:00
|
|
|
{
|
2019-02-21 03:59:07 +01:00
|
|
|
if (!_perMessageDeflate.compress(message, compressedMessage))
|
|
|
|
{
|
|
|
|
bool success = false;
|
|
|
|
compressionError = true;
|
|
|
|
payloadSize = 0;
|
|
|
|
wireSize = 0;
|
|
|
|
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
|
|
|
}
|
|
|
|
compressionError = false;
|
2018-11-10 03:23:49 +01:00
|
|
|
wireSize = compressedMessage.size();
|
|
|
|
|
|
|
|
message_begin = compressedMessage.begin();
|
|
|
|
message_end = compressedMessage.end();
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
// Common case for most message. No fragmentation required.
|
|
|
|
if (wireSize < kChunkSize)
|
|
|
|
{
|
|
|
|
sendFragment(type, true, message_begin, message_end, compress);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// Large messages need to be fragmented
|
|
|
|
//
|
|
|
|
// Rules:
|
|
|
|
// First message needs to specify a proper type (BINARY or TEXT)
|
|
|
|
// Intermediary and last messages need to be of type CONTINUATION
|
|
|
|
// Last message must set the fin byte.
|
|
|
|
//
|
|
|
|
auto steps = wireSize / kChunkSize;
|
|
|
|
|
|
|
|
std::string::const_iterator begin = message_begin;
|
|
|
|
std::string::const_iterator end = message_end;
|
|
|
|
|
|
|
|
for (uint64_t i = 0 ; i < steps; ++i)
|
|
|
|
{
|
|
|
|
bool firstStep = i == 0;
|
|
|
|
bool lastStep = (i+1) == steps;
|
|
|
|
bool fin = lastStep;
|
|
|
|
|
|
|
|
end = begin + kChunkSize;
|
|
|
|
if (lastStep)
|
|
|
|
{
|
|
|
|
end = message_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto opcodeType = type;
|
|
|
|
if (!firstStep)
|
|
|
|
{
|
|
|
|
opcodeType = wsheader_type::CONTINUATION;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send message
|
|
|
|
sendFragment(opcodeType, fin, begin, end, compress);
|
|
|
|
|
2019-03-03 06:11:16 +01:00
|
|
|
if (onProgressCallback && !onProgressCallback((int)i, (int) steps))
|
2019-02-21 03:59:07 +01:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
begin += kChunkSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-14 21:47:03 +01:00
|
|
|
// Request to flush the send buffer on the background thread if it isn't empty
|
|
|
|
if (!isSendBufferEmpty())
|
|
|
|
{
|
|
|
|
_socket->wakeUpFromPoll(Socket::kSendRequest);
|
|
|
|
}
|
2019-03-12 06:16:55 +01:00
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
|
|
|
|
bool fin,
|
|
|
|
std::string::const_iterator message_begin,
|
|
|
|
std::string::const_iterator message_end,
|
|
|
|
bool compress)
|
|
|
|
{
|
2019-04-14 06:16:04 +02:00
|
|
|
uint64_t message_size = static_cast<uint64_t>(message_end - message_begin);
|
2018-11-10 03:23:49 +01:00
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
unsigned x = getRandomUnsigned();
|
|
|
|
uint8_t masking_key[4] = {};
|
|
|
|
masking_key[0] = (x >> 24);
|
|
|
|
masking_key[1] = (x >> 16) & 0xff;
|
|
|
|
masking_key[2] = (x >> 8) & 0xff;
|
|
|
|
masking_key[3] = (x) & 0xff;
|
|
|
|
|
|
|
|
std::vector<uint8_t> header;
|
|
|
|
header.assign(2 +
|
|
|
|
(message_size >= 126 ? 2 : 0) +
|
2019-03-22 23:33:04 +01:00
|
|
|
(message_size >= 65536 ? 6 : 0) +
|
|
|
|
(_useMask ? 4 : 0), 0);
|
2019-02-21 03:59:07 +01:00
|
|
|
header[0] = type;
|
|
|
|
|
|
|
|
// The fin bit indicate that this is the last fragment. Fin is French for end.
|
|
|
|
if (fin)
|
|
|
|
{
|
|
|
|
header[0] |= 0x80;
|
|
|
|
}
|
2018-11-07 23:54:44 +01:00
|
|
|
|
|
|
|
// This bit indicate that the frame is compressed
|
2018-11-12 18:00:55 +01:00
|
|
|
if (compress)
|
2018-11-07 23:54:44 +01:00
|
|
|
{
|
|
|
|
header[0] |= 0x40;
|
|
|
|
}
|
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
if (message_size < 126)
|
|
|
|
{
|
2019-03-22 17:53:56 +01:00
|
|
|
header[1] = (message_size & 0xff) | (_useMask ? 0x80 : 0);
|
2018-09-27 23:56:48 +02:00
|
|
|
|
2019-03-22 17:53:56 +01:00
|
|
|
if (_useMask)
|
|
|
|
{
|
|
|
|
header[2] = masking_key[0];
|
|
|
|
header[3] = masking_key[1];
|
|
|
|
header[4] = masking_key[2];
|
|
|
|
header[5] = masking_key[3];
|
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
2019-02-21 03:59:07 +01:00
|
|
|
else if (message_size < 65536)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-03-22 17:53:56 +01:00
|
|
|
header[1] = 126 | (_useMask ? 0x80 : 0);
|
2018-09-27 23:56:48 +02:00
|
|
|
header[2] = (message_size >> 8) & 0xff;
|
|
|
|
header[3] = (message_size >> 0) & 0xff;
|
|
|
|
|
2019-03-22 17:53:56 +01:00
|
|
|
if (_useMask)
|
|
|
|
{
|
|
|
|
header[4] = masking_key[0];
|
|
|
|
header[5] = masking_key[1];
|
|
|
|
header[6] = masking_key[2];
|
|
|
|
header[7] = masking_key[3];
|
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // TODO: run coverage testing here
|
2019-03-22 17:53:56 +01:00
|
|
|
header[1] = 127 | (_useMask ? 0x80 : 0);
|
2018-09-27 23:56:48 +02:00
|
|
|
header[2] = (message_size >> 56) & 0xff;
|
|
|
|
header[3] = (message_size >> 48) & 0xff;
|
|
|
|
header[4] = (message_size >> 40) & 0xff;
|
|
|
|
header[5] = (message_size >> 32) & 0xff;
|
|
|
|
header[6] = (message_size >> 24) & 0xff;
|
|
|
|
header[7] = (message_size >> 16) & 0xff;
|
|
|
|
header[8] = (message_size >> 8) & 0xff;
|
|
|
|
header[9] = (message_size >> 0) & 0xff;
|
|
|
|
|
2019-03-22 17:53:56 +01:00
|
|
|
if (_useMask)
|
|
|
|
{
|
|
|
|
header[10] = masking_key[0];
|
|
|
|
header[11] = masking_key[1];
|
|
|
|
header[12] = masking_key[2];
|
|
|
|
header[13] = masking_key[3];
|
|
|
|
}
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// _txbuf will keep growing until it can be transmitted over the socket:
|
|
|
|
appendToSendBuffer(header, message_begin, message_end,
|
|
|
|
message_size, masking_key);
|
|
|
|
|
|
|
|
// Now actually send this data
|
|
|
|
sendOnSocket();
|
|
|
|
}
|
|
|
|
|
2018-11-10 03:23:49 +01:00
|
|
|
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2018-11-14 02:46:05 +01:00
|
|
|
bool compress = false;
|
2019-04-18 18:24:16 +02:00
|
|
|
WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress);
|
|
|
|
|
2019-04-20 01:57:38 +02:00
|
|
|
if (info.success)
|
2019-04-18 18:24:16 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
|
|
|
|
_lastSendPingTimePoint = std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
return info;
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
WebSocketSendInfo WebSocketTransport::sendBinary(
|
|
|
|
const std::string& message,
|
|
|
|
const OnProgressCallback& onProgressCallback)
|
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-02-21 03:59:07 +01:00
|
|
|
return sendData(wsheader_type::BINARY_FRAME, message,
|
|
|
|
_enablePerMessageDeflate, onProgressCallback);
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-03-22 22:22:58 +01:00
|
|
|
WebSocketSendInfo WebSocketTransport::sendText(
|
|
|
|
const std::string& message,
|
|
|
|
const OnProgressCallback& onProgressCallback)
|
|
|
|
|
|
|
|
{
|
|
|
|
return sendData(wsheader_type::TEXT_FRAME, message,
|
|
|
|
_enablePerMessageDeflate, onProgressCallback);
|
|
|
|
}
|
|
|
|
|
2018-09-27 23:56:48 +02:00
|
|
|
void WebSocketTransport::sendOnSocket()
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
|
|
|
|
|
|
|
while (_txbuf.size())
|
|
|
|
{
|
2019-01-12 06:25:06 +01:00
|
|
|
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
|
2018-09-27 23:56:48 +02:00
|
|
|
|
2019-05-06 18:13:42 +02:00
|
|
|
if (ret < 0 && Socket::isWaitNeeded())
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2018-11-15 00:52:28 +01:00
|
|
|
else if (ret <= 0)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
|
|
|
_socket->close();
|
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::CLOSED);
|
2018-09-27 23:56:48 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-16 17:58:34 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
|
2018-09-27 23:56:48 +02:00
|
|
|
{
|
2019-05-09 18:21:05 +02:00
|
|
|
bool compress = false;
|
2018-12-15 01:28:11 +01:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
// if a status is set/was read
|
|
|
|
if (code != kNoStatusCodeErrorCode)
|
|
|
|
{
|
|
|
|
// See list of close events here:
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
|
|
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
|
2018-09-27 23:56:48 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
// copy reason after code
|
|
|
|
closure.append(reason);
|
2019-04-20 01:57:38 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
sendData(wsheader_type::CLOSE, closure, compress);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// no close code/reason set
|
|
|
|
sendData(wsheader_type::CLOSE, "", compress);
|
|
|
|
}
|
|
|
|
}
|
2019-04-18 19:02:31 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
|
|
|
{
|
|
|
|
_socket->close();
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
|
|
|
_closeCode = code;
|
|
|
|
_closeReason = reason;
|
|
|
|
_closeWireSize = closeWireSize;
|
|
|
|
_closeRemote = remote;
|
|
|
|
}
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::CLOSED);
|
2019-05-09 18:21:05 +02:00
|
|
|
}
|
2019-04-16 17:58:34 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
|
|
|
{
|
|
|
|
_requestInitCancellation = true;
|
2018-09-27 23:56:48 +02:00
|
|
|
|
2019-05-11 23:22:06 +02:00
|
|
|
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
|
2019-03-14 07:09:45 +01:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
sendCloseFrame(code, reason);
|
2019-04-16 17:58:34 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
|
|
|
_closeCode = code;
|
|
|
|
_closeReason = reason;
|
|
|
|
_closeWireSize = closeWireSize;
|
2019-04-23 13:31:55 +02:00
|
|
|
_closeRemote = remote;
|
2019-04-16 17:58:34 +02:00
|
|
|
}
|
2019-05-09 18:21:05 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
|
|
|
_closingTimePoint = std::chrono::steady_clock::now();
|
|
|
|
}
|
2019-05-11 23:22:06 +02:00
|
|
|
setReadyState(ReadyState::CLOSING);
|
2019-04-18 05:31:34 +02:00
|
|
|
|
2019-05-09 18:21:05 +02:00
|
|
|
// wake up the poll, but do not close yet
|
|
|
|
_socket->wakeUpFromPoll(Socket::kSendRequest);
|
2019-03-14 07:09:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t WebSocketTransport::bufferedAmount() const
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
|
|
|
return _txbuf.size();
|
2018-09-27 23:56:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace ix
|