Add audio websocket server that sends data to software oscilloscope browser client

pull/170/head
James Ball 2023-07-10 22:00:36 +01:00
rodzic 8db9f448f1
commit a103ac44b6
98 zmienionych plików z 12987 dodań i 8 usunięć

Wyświetl plik

@ -59,7 +59,7 @@ void OscirenderAudioProcessorEditor::resized() {
auto area = getLocalBounds();
area.removeFromLeft(3);
auto volumeArea = area.removeFromLeft(30);
volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), std::min(volumeArea.getHeight(), 300)));
volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), juce::jmin(volumeArea.getHeight(), 300)));
area.removeFromLeft(3);
auto sections = 2;
int index = audioProcessor.getCurrentFileIndex();

Wyświetl plik

@ -372,7 +372,7 @@ void OscirenderAudioProcessor::updateLengthIncrement() {
double traceMaxValue = traceMaxEnabled ? actualTraceMax : 1.0;
double traceMinValue = traceMinEnabled ? actualTraceMin : 0.0;
double proportionalLength = (traceMaxValue - traceMinValue) * frameLength;
lengthIncrement = std::max(proportionalLength / (currentSampleRate / frequency), MIN_LENGTH_INCREMENT);
lengthIncrement = juce::jmax(proportionalLength / (currentSampleRate / frequency), MIN_LENGTH_INCREMENT);
}
void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
@ -448,8 +448,8 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, j
y *= volume;
// clip
x = std::max(-threshold, std::min(threshold.load(), x));
y = std::max(-threshold, std::min(threshold.load(), y));
x = juce::jmax(-threshold, juce::jmin(threshold.load(), x));
y = juce::jmax(-threshold, juce::jmin(threshold.load(), y));
if (totalNumOutputChannels >= 2) {
channelData[0][sample] = x;
@ -460,8 +460,8 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, j
audioProducer.write(x, y);
actualTraceMax = std::max(actualTraceMin + MIN_TRACE, std::min(traceMax->getValue(), 1.0));
actualTraceMin = std::max(MIN_TRACE, std::min(traceMin->getValue(), actualTraceMax - MIN_TRACE));
actualTraceMax = juce::jmax(actualTraceMin + MIN_TRACE, juce::jmin(traceMax->getValue(), 1.0));
actualTraceMin = juce::jmax(MIN_TRACE, juce::jmin(traceMin->getValue(), actualTraceMax - MIN_TRACE));
if (!renderingSample) {
incrementShapeDrawing();
@ -484,8 +484,8 @@ void OscirenderAudioProcessor::incrementShapeDrawing() {
double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0;
// hard cap on how many times it can be over the length to
// prevent audio stuttering
frameDrawn += std::min(lengthIncrement, 20 * length);
shapeDrawn += std::min(lengthIncrement, 20 * length);
frameDrawn += juce::jmin(lengthIncrement, 20 * length);
shapeDrawn += juce::jmin(lengthIncrement, 20 * length);
// Need to skip all shapes that the lengthIncrement draws over.
// This is especially an issue when there are lots of small lines being

Wyświetl plik

@ -16,6 +16,7 @@
#include "audio/Effect.h"
#include <numbers>
#include "concurrency/BufferProducer.h"
#include "audio/AudioWebSocketServer.h"
//==============================================================================
/**
@ -229,6 +230,8 @@ private:
bool traceMaxEnabled = false;
bool traceMinEnabled = false;
AudioWebSocketServer softwareOscilloscopeServer{audioProducer};
void updateFrame();
void updateLengthIncrement();
void incrementShapeDrawing();

Wyświetl plik

@ -0,0 +1,69 @@
#include "AudioWebSocketServer.h"
AudioWebSocketServer::AudioWebSocketServer(BufferProducer& producer) : juce::Thread("AudioWebSocketServer"), producer(producer) {
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
// The ConnectionState object contains information about the connection,
// at this point only the client ip address and the port.
DBG("Remote ip: " << connectionState->getRemoteIp());
if (msg->type == ix::WebSocketMessageType::Open) {
DBG("New connection");
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
DBG("id: " << connectionState->getId());
// The uri the client did connect to.
DBG("Uri: " << msg->openInfo.uri);
DBG("Headers:");
for (auto it : msg->openInfo.headers) {
DBG("\t" << it.first << ": " << it.second);
}
}
});
ix::initNetSystem();
auto res = server.listen();
if (res.first) {
server.disablePerMessageDeflate();
server.start();
startThread();
}
// TODO: don't silently fail
}
AudioWebSocketServer::~AudioWebSocketServer() {
server.stop();
ix::uninitNetSystem();
producer.unregisterConsumer(consumer);
stopThread(1000);
}
void AudioWebSocketServer::run() {
producer.registerConsumer(consumer);
while (!threadShouldExit()) {
auto floatBuffer = consumer->startProcessing();
for (int i = 0; i < floatBuffer->size(); i++) {
short sample = floatBuffer->at(i) * 32767;
char b0 = sample & 0xff;
char b1 = (sample >> 8) & 0xff;
buffer[2 * i] = b0;
buffer[2 * i + 1] = b1;
}
for (auto&& client : server.getClients()) {
ix::IXWebSocketSendData data{buffer, sizeof(buffer)};
client->sendBinary(data);
}
consumer->finishedProcessing();
}
}

Wyświetl plik

@ -0,0 +1,18 @@
#pragma once
#include <JuceHeader.h>
#include "../ixwebsocket/IXWebSocketServer.h"
#include "../concurrency/BufferProducer.h"
class AudioWebSocketServer : juce::Thread {
public:
AudioWebSocketServer(BufferProducer& producer);
~AudioWebSocketServer();
void run() override;
private:
ix::WebSocketServer server{ 42988 };
BufferProducer& producer;
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(4096);
char buffer[4096 * 2 * 2];
};

Wyświetl plik

@ -0,0 +1,125 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* 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:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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.
*/
#include <cstdint>
#include <string>
namespace macaron {
class Base64 {
public:
static std::string Encode(const std::string data) {
static constexpr char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
}
#endif /* _MACARON_BASE64_H_ */

Wyświetl plik

@ -0,0 +1,61 @@
/*
* IXBench.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXBench.h"
#include <iostream>
namespace ix
{
Bench::Bench(const std::string& description)
: _description(description)
{
reset();
}
Bench::~Bench()
{
if (!_reported)
{
report();
}
}
void Bench::reset()
{
_start = std::chrono::high_resolution_clock::now();
_reported = false;
}
void Bench::report()
{
auto now = std::chrono::high_resolution_clock::now();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
_duration = microseconds.count();
std::cerr << _description << " completed in " << _duration << " us" << std::endl;
setReported();
}
void Bench::record()
{
auto now = std::chrono::high_resolution_clock::now();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
_duration = microseconds.count();
}
void Bench::setReported()
{
_reported = true;
}
uint64_t Bench::getDuration() const
{
return _duration;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,32 @@
/*
* IXBench.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <chrono>
#include <cstdint>
#include <string>
namespace ix
{
class Bench
{
public:
Bench(const std::string& description);
~Bench();
void reset();
void record();
void report();
void setReported();
uint64_t getDuration() const;
private:
std::string _description;
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
uint64_t _duration;
bool _reported;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,35 @@
/*
* IXCancellationRequest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCancellationRequest.h"
#include <cassert>
#include <chrono>
namespace ix
{
CancellationRequest makeCancellationRequestWithTimeout(
int secs, std::atomic<bool>& requestInitCancellation)
{
assert(secs > 0);
auto start = std::chrono::system_clock::now();
auto timeout = std::chrono::seconds(secs);
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool {
// Was an explicit cancellation requested ?
if (requestInitCancellation) return true;
auto now = std::chrono::system_clock::now();
if ((now - start) > timeout) return true;
// No cancellation request
return false;
};
return isCancellationRequested;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,18 @@
/*
* IXCancellationRequest.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
namespace ix
{
using CancellationRequest = std::function<bool()>;
CancellationRequest makeCancellationRequestWithTimeout(
int seconds, std::atomic<bool>& requestInitCancellation);
} // namespace ix

Wyświetl plik

@ -0,0 +1,73 @@
/*
* IXConnectionState.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXConnectionState.h"
namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState()
: _terminated(false)
{
computeId();
}
void ConnectionState::computeId()
{
_id = std::to_string(_globalId++);
}
const std::string& ConnectionState::getId() const
{
return _id;
}
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
{
return std::make_shared<ConnectionState>();
}
void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback)
{
_onSetTerminatedCallback = callback;
}
bool ConnectionState::isTerminated() const
{
return _terminated;
}
void ConnectionState::setTerminated()
{
_terminated = true;
if (_onSetTerminatedCallback)
{
_onSetTerminatedCallback();
}
}
const std::string& ConnectionState::getRemoteIp()
{
return _remoteIp;
}
int ConnectionState::getRemotePort()
{
return _remotePort;
}
void ConnectionState::setRemoteIp(const std::string& remoteIp)
{
_remoteIp = remoteIp;
}
void ConnectionState::setRemotePort(int remotePort)
{
_remotePort = remotePort;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,54 @@
/*
* IXConnectionState.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
namespace ix
{
using OnSetTerminatedCallback = std::function<void()>;
class ConnectionState
{
public:
ConnectionState();
virtual ~ConnectionState() = default;
virtual void computeId();
virtual const std::string& getId() const;
void setTerminated();
bool isTerminated() const;
const std::string& getRemoteIp();
int getRemotePort();
static std::shared_ptr<ConnectionState> createConnectionState();
private:
void setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback);
void setRemoteIp(const std::string& remoteIp);
void setRemotePort(int remotePort);
protected:
std::atomic<bool> _terminated;
std::string _id;
OnSetTerminatedCallback _onSetTerminatedCallback;
static std::atomic<uint64_t> _globalId;
std::string _remoteIp;
int _remotePort;
friend class SocketServer;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,191 @@
/*
* IXDNSLookup.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
//
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t
// which is different from all other platforms. We want the non unicode version.
// See https://github.com/microsoft/vcpkg/pull/11030
// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
//
#ifdef _UNICODE
#undef _UNICODE
#endif
#ifdef UNICODE
#undef UNICODE
#endif
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include <chrono>
#include <string.h>
#include <thread>
#include <utility>
// mingw build quirks
#if defined(_WIN32) && defined(__GNUC__)
#define AI_NUMERICSERV NI_NUMERICSERV
#define AI_ADDRCONFIG LUP_ADDRCONFIG
#endif
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 1; // ms
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait)
: _hostname(hostname)
, _port(port)
, _wait(wait)
, _res(nullptr)
, _done(false)
{
;
}
DNSLookup::AddrInfoPtr DNSLookup::getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::string sport = std::to_string(port);
struct addrinfo* res;
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res);
if (getaddrinfo_result)
{
errMsg = gai_strerror(getaddrinfo_result);
res = nullptr;
}
return AddrInfoPtr{ res, freeaddrinfo };
}
DNSLookup::AddrInfoPtr DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable)
{
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested);
}
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
// Maybe a cancellation request got in before the background thread terminated ?
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
return getAddrInfo(_hostname, _port, errMsg);
}
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
// Can only be called once, otherwise we would have to manage a pool
// of background thread which is overkill for our usage.
if (_done)
{
return nullptr; // programming error, create a second DNSLookup instance
// if you need a second lookup.
}
//
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
int port = _port;
std::string hostname(_hostname);
// We make the background thread doing the work a shared pointer
// instead of a member variable, because it can keep running when
// this object goes out of scope, in case of cancellation
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
t->detach();
while (!_done)
{
// Wait for 1 milliseconds, to see if the bg thread has terminated.
// We do not use a condition variable to wait, as destroying this one
// if the bg thread is alive can cause undefined behavior.
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
// Were we cancelled ?
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
}
// Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
errMsg = getErrMsg();
return getRes();
}
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
std::string hostname,
int port) // thread runner
{
// We don't want to read or write into members variables of an object that could be
// gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work.
std::string errMsg;
auto res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock())
{
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_done = true;
}
}
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
_errMsg = errMsg;
}
const std::string& DNSLookup::getErrMsg()
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
return _errMsg;
}
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = std::move(addr);
}
DNSLookup::AddrInfoPtr DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,67 @@
/*
* IXDNSLookup.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*
* Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo
* Does this in a background thread so that it can be cancelled, since
* getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile.
*/
#pragma once
#include "IXCancellationRequest.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <set>
#include <string>
struct addrinfo;
namespace ix
{
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
using AddrInfoPtr = std::shared_ptr<addrinfo>;
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default;
AddrInfoPtr resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable = true);
private:
AddrInfoPtr resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
AddrInfoPtr resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
AddrInfoPtr getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg);
void run(std::weak_ptr<DNSLookup> self, std::string hostname, int port); // thread runner
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
void setRes(AddrInfoPtr addr);
AddrInfoPtr getRes();
std::string _hostname;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
AddrInfoPtr _res;
std::mutex _resMutex;
std::string _errMsg;
std::mutex _errMsgMutex;
std::atomic<bool> _done;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,44 @@
/*
* IXExponentialBackoff.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXExponentialBackoff.h"
#include <cmath>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries)
{
// It's easy with a power function to go beyond 2^32, and then
// have unexpected results, so prepare for that
const uint32_t maxRetryCountWithoutOverflow = 26;
uint32_t waitTime = 0;
if (retryCount < maxRetryCountWithoutOverflow)
{
waitTime = std::pow(2, retryCount) * 100;
}
if (waitTime < minWaitBetweenReconnectionRetries)
{
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
if (retryCount >= maxRetryCountWithoutOverflow)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,16 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries);
} // namespace ix

Wyświetl plik

@ -0,0 +1,96 @@
/*
* IXGetFreePort.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
// Using inet_addr will trigger an error on uwp without this
// FIXME: use a different api
#ifdef _WIN32
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif
#endif
#include "IXGetFreePort.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include <random>
#include <string>
namespace ix
{
int getAnyFreePortRandom()
{
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
}
int getAnyFreePort()
{
socket_t sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return getAnyFreePortRandom();
}
int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
{
return getAnyFreePortRandom();
}
// Bind to port 0. This is the standard way to get a free port.
struct sockaddr_in server; // server address information
server.sin_family = AF_INET;
server.sin_port = htons(0);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
struct sockaddr_in sa; // server address information
socklen_t len = sizeof(sa);
if (getsockname(sockfd, (struct sockaddr*) &sa, &len) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
int port = ntohs(sa.sin_port);
Socket::closeSocket(sockfd);
return port;
}
int getFreePort()
{
while (true)
{
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom();
#else
int port = getAnyFreePort();
#endif
#else
int port = getAnyFreePort();
#endif
//
// Only port above 1024 can be used by non root users, but for some
// reason I got port 7 returned with macOS when binding on port 0...
//
if (port > 1024)
{
return port;
}
}
return -1;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,12 @@
/*
* IXGetFreePort.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
namespace ix
{
int getFreePort();
} // namespace ix

Wyświetl plik

@ -0,0 +1,120 @@
/*
* IXGzipCodec.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXGzipCodec.h"
#include "IXBench.h"
#include <array>
#include <string.h>
#ifdef IXWEBSOCKET_USE_ZLIB
#include <zlib.h>
#endif
namespace ix
{
std::string gzipCompress(const std::string& str)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return std::string();
#else
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
// deflateInit2 configure the file format: request gzip instead of deflate
const int windowBits = 15;
const int GZIP_ENCODING = 16;
deflateInit2(&zs,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
windowBits | GZIP_ENCODING,
8,
Z_DEFAULT_STRATEGY);
zs.next_in = (Bytef*) str.data();
zs.avail_in = (uInt) str.size(); // set the z_stream's input
int ret;
char outbuffer[32768];
std::string outstring;
// retrieve the compressed bytes blockwise
do
{
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
zs.avail_out = sizeof(outbuffer);
ret = deflate(&zs, Z_FINISH);
if (outstring.size() < zs.total_out)
{
// append the block to the output string
outstring.append(outbuffer, zs.total_out - outstring.size());
}
} while (ret == Z_OK);
deflateEnd(&zs);
return outstring;
#endif // IXWEBSOCKET_USE_ZLIB
}
#ifdef IXWEBSOCKET_USE_DEFLATE
static uint32_t loadDecompressedGzipSize(const uint8_t* p)
{
return ((uint32_t) p[0] << 0) | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) |
((uint32_t) p[3] << 24);
}
#endif
bool gzipDecompress(const std::string& in, std::string& out)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return false;
#else
z_stream inflateState;
memset(&inflateState, 0, sizeof(inflateState));
inflateState.zalloc = Z_NULL;
inflateState.zfree = Z_NULL;
inflateState.opaque = Z_NULL;
inflateState.avail_in = 0;
inflateState.next_in = Z_NULL;
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
{
return false;
}
inflateState.avail_in = (uInt) in.size();
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
const int kBufferSize = 1 << 14;
std::array<unsigned char, kBufferSize> compressBuffer;
do
{
inflateState.avail_out = (uInt) kBufferSize;
inflateState.next_out = &compressBuffer.front();
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
inflateEnd(&inflateState);
return false;
}
out.append(reinterpret_cast<char*>(&compressBuffer.front()),
kBufferSize - inflateState.avail_out);
} while (inflateState.avail_out == 0);
inflateEnd(&inflateState);
return true;
#endif // IXWEBSOCKET_USE_ZLIB
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,15 @@
/*
* IXGzipCodec.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
std::string gzipCompress(const std::string& str);
bool gzipDecompress(const std::string& in, std::string& out);
} // namespace ix

Wyświetl plik

@ -0,0 +1,213 @@
/*
* IXHttp.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttp.h"
#include "IXCancellationRequest.h"
#include "IXGzipCodec.h"
#include "IXSocket.h"
#include <sstream>
#include <vector>
namespace ix
{
std::string Http::trim(const std::string& str)
{
std::string out;
for (auto c : str)
{
if (c != ' ' && c != '\n' && c != '\r')
{
out += c;
}
}
return out;
}
std::pair<std::string, int> Http::parseStatusLine(const std::string& line)
{
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
std::string token;
std::stringstream tokenStream(line);
std::vector<std::string> tokens;
// Split by ' '
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string httpVersion;
if (tokens.size() >= 1)
{
httpVersion = trim(tokens[0]);
}
int statusCode = -1;
if (tokens.size() >= 2)
{
std::stringstream ss;
ss << trim(tokens[1]);
ss >> statusCode;
}
return std::make_pair(httpVersion, statusCode);
}
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(
const std::string& line)
{
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
std::string token;
std::stringstream tokenStream(line);
std::vector<std::string> tokens;
// Split by ' '
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string method;
if (tokens.size() >= 1)
{
method = trim(tokens[0]);
}
std::string requestUri;
if (tokens.size() >= 2)
{
requestUri = trim(tokens[1]);
}
std::string httpVersion;
if (tokens.size() >= 3)
{
httpVersion = trim(tokens[2]);
}
return std::make_tuple(method, requestUri, httpVersion);
}
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
std::unique_ptr<Socket>& socket, int timeoutSecs)
{
HttpRequestPtr httpRequest;
std::atomic<bool> requestInitCancellation(false);
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
// Read first line
auto lineResult = socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
}
// Parse request line (GET /foo HTTP/1.1\r\n)
auto requestLine = Http::parseRequestLine(line);
auto method = std::get<0>(requestLine);
auto uri = std::get<1>(requestLine);
auto httpVersion = std::get<2>(requestLine);
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(socket, isCancellationRequested);
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid)
{
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
}
std::string body;
if (headers.find("Content-Length") != headers.end())
{
int contentLength = 0;
try
{
contentLength = std::stoi(headers["Content-Length"]);
}
catch (const std::exception&)
{
return std::make_tuple(
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
}
if (contentLength < 0)
{
return std::make_tuple(
false, "Error: 'Content-Length' should be a positive integer", httpRequest);
}
auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested);
if (!res.first)
{
return std::make_tuple(
false, std::string("Error reading request: ") + res.second, httpRequest);
}
body = res.second;
}
// If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip")
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload;
if (!gzipDecompress(body, decompressedPayload))
{
return std::make_tuple(
false, std::string("Error during gzip decompression of the body"), httpRequest);
}
body = decompressedPayload;
#else
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
return std::make_tuple(false, errorMsg, httpRequest);
#endif
}
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers);
return std::make_tuple(true, "", httpRequest);
}
bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket)
{
// Write the response to the socket
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << response->statusCode;
ss << " ";
ss << response->description;
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
// Write headers
ss.str("");
ss << "Content-Length: " << response->body.size() << "\r\n";
for (auto&& it : response->headers)
{
ss << it.first << ": " << it.second << "\r\n";
}
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
return response->body.empty() ? true : socket->writeBytes(response->body, nullptr);
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,135 @@
/*
* IXHttp.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#include <atomic>
#include <cstdint>
#include <tuple>
#include <unordered_map>
namespace ix
{
enum class HttpErrorCode : int
{
Ok = 0,
CannotConnect = 1,
Timeout = 2,
Gzip = 3,
UrlMalformed = 4,
CannotCreateSocket = 5,
SendError = 6,
ReadError = 7,
CannotReadStatusLine = 8,
MissingStatus = 9,
HeaderParsingError = 10,
MissingLocation = 11,
TooManyRedirects = 12,
ChunkReadError = 13,
CannotReadBody = 14,
Cancelled = 15,
Invalid = 100
};
struct HttpResponse
{
int statusCode;
std::string description;
HttpErrorCode errorCode;
WebSocketHttpHeaders headers;
std::string body;
std::string errorMsg;
uint64_t uploadSize;
uint64_t downloadSize;
HttpResponse(int s = 0,
const std::string& des = std::string(),
const HttpErrorCode& c = HttpErrorCode::Ok,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& b = std::string(),
const std::string& e = std::string(),
uint64_t u = 0,
uint64_t d = 0)
: statusCode(s)
, description(des)
, errorCode(c)
, headers(h)
, body(b)
, errorMsg(e)
, uploadSize(u)
, downloadSize(d)
{
;
}
};
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
using HttpParameters = std::unordered_map<std::string, std::string>;
using HttpFormDataParameters = std::unordered_map<std::string, std::string>;
using Logger = std::function<void(const std::string&)>;
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
struct HttpRequestArgs
{
std::string url;
std::string verb;
WebSocketHttpHeaders extraHeaders;
std::string body;
std::string multipartBoundary;
int connectTimeout = 60;
int transferTimeout = 1800;
bool followRedirects = true;
int maxRedirects = 5;
bool verbose = false;
bool compress = true;
bool compressRequest = false;
Logger logger;
OnProgressCallback onProgressCallback;
OnChunkCallback onChunkCallback;
std::atomic<bool> cancel;
};
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
struct HttpRequest
{
std::string uri;
std::string method;
std::string version;
std::string body;
WebSocketHttpHeaders headers;
HttpRequest(const std::string& u,
const std::string& m,
const std::string& v,
const std::string& b,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
: uri(u)
, method(m)
, version(v)
, body(b)
, headers(h)
{
}
};
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class Http
{
public:
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
std::unique_ptr<Socket>& socket, int timeoutSecs);
static bool sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket);
static std::pair<std::string, int> parseStatusLine(const std::string& line);
static std::tuple<std::string, std::string, std::string> parseRequestLine(
const std::string& line);
static std::string trim(const std::string& str);
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,785 @@
/*
* IXHttpClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttpClient.h"
#include "IXGzipCodec.h"
#include "IXSocketFactory.h"
#include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h"
#include <assert.h>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <random>
#include <sstream>
#include <vector>
namespace ix
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
const std::string HttpClient::kPost = "POST";
const std::string HttpClient::kGet = "GET";
const std::string HttpClient::kHead = "HEAD";
const std::string HttpClient::kDelete = "DELETE";
const std::string HttpClient::kPut = "PUT";
const std::string HttpClient::kPatch = "PATCH";
HttpClient::HttpClient(bool async)
: _async(async)
, _stop(false)
, _forceBody(false)
{
if (!_async) return;
_thread = std::thread(&HttpClient::run, this);
}
HttpClient::~HttpClient()
{
if (!_thread.joinable()) return;
_stop = true;
_condition.notify_one();
_thread.join();
}
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
{
_tlsOptions = tlsOptions;
}
void HttpClient::setForceBody(bool value)
{
_forceBody = value;
}
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
{
auto request = std::make_shared<HttpRequestArgs>();
request->url = url;
request->verb = verb;
return request;
}
bool HttpClient::performRequest(HttpRequestArgsPtr args,
const OnResponseCallback& onResponseCallback)
{
assert(_async && "HttpClient needs its async parameter set to true "
"in order to call performRequest");
if (!_async) return false;
// Enqueue the task
{
// acquire lock
std::unique_lock<std::mutex> lock(_queueMutex);
// add the task
_queue.push(std::make_pair(args, onResponseCallback));
} // release lock
// wake up one thread
_condition.notify_one();
return true;
}
void HttpClient::run()
{
while (true)
{
HttpRequestArgsPtr args;
OnResponseCallback onResponseCallback;
{
std::unique_lock<std::mutex> lock(_queueMutex);
while (!_stop && _queue.empty())
{
_condition.wait(lock);
}
if (_stop) return;
auto p = _queue.front();
_queue.pop();
args = p.first;
onResponseCallback = p.second;
}
if (_stop) return;
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
onResponseCallback(response);
if (_stop) return;
}
}
HttpResponsePtr HttpClient::request(const std::string& url,
const std::string& verb,
const std::string& body,
HttpRequestArgsPtr args,
int redirects)
{
// We only have one socket connection, so we cannot
// make multiple requests concurrently.
std::lock_guard<std::recursive_mutex> lock(_mutex);
uint64_t uploadSize = 0;
uint64_t downloadSize = 0;
int code = 0;
WebSocketHttpHeaders headers;
std::string payload;
std::string description;
std::string protocol, host, path, query;
int port;
bool isProtocolDefaultPort;
if (!UrlParser::parse(url, protocol, host, path, query, port, isProtocolDefaultPort))
{
std::stringstream ss;
ss << "Cannot parse url: " << url;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::UrlMalformed,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
bool tls = protocol == "https";
std::string errorMsg;
_socket = createSocket(tls, -1, errorMsg, _tlsOptions);
if (!_socket)
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotCreateSocket,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
// Build request string
std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n";
ss << "Host: " << host;
if (!isProtocolDefaultPort)
{
ss << ":" << port;
}
ss << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress && !args->onChunkCallback)
{
ss << "Accept-Encoding: gzip"
<< "\r\n";
}
#endif
// Append extra headers
for (auto&& it : args->extraHeaders)
{
ss << it.first << ": " << it.second << "\r\n";
}
// Set a default Accept header if none is present
if (args->extraHeaders.find("Accept") == args->extraHeaders.end())
{
ss << "Accept: */*"
<< "\r\n";
}
// Set a default User agent if none is present
if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
// Set an origin header if missing
if (args->extraHeaders.find("Origin") == args->extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
{
// Set request compression header
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest)
{
ss << "Content-Encoding: gzip"
<< "\r\n";
}
#endif
ss << "Content-Length: " << body.size() << "\r\n";
// Set default Content-Type if unspecified
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
{
if (args->multipartBoundary.empty())
{
ss << "Content-Type: application/x-www-form-urlencoded"
<< "\r\n";
}
else
{
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
<< "\r\n";
}
}
ss << "\r\n";
ss << body;
}
else
{
ss << "\r\n";
}
std::string req(ss.str());
std::string errMsg;
// Make a cancellation object dealing with connection timeout
auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel);
auto isCancellationRequested = [&]() {
return cancelled() || _stop;
};
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotConnect;
std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Make a new cancellation object dealing with transfer timeout
cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel);
if (args->verbose)
{
std::stringstream ss;
ss << "Sending " << verb << " request "
<< "to " << host << ":" << port << std::endl
<< "request size: " << req.size() << " bytes" << std::endl
<< "=============" << std::endl
<< req << "=============" << std::endl
<< std::endl;
log(ss.str(), args);
}
if (!_socket->writeBytes(req, isCancellationRequested))
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::SendError;
std::string errorMsg("Cannot send request");
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uploadSize = req.size();
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine;
std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (args->verbose)
{
std::stringstream ss;
ss << "Status line " << line;
log(ss.str(), args);
}
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{
std::string errorMsg("Cannot parse response code from status line");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingStatus,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
headers = result.second;
if (!headersValid)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError;
std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
// Redirect ?
if ((code >= 301 && code <= 308) && args->followRedirects)
{
if (headers.find("Location") == headers.end())
{
std::string errorMsg("Missing location header for redirect");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingLocation,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (redirects >= args->maxRedirects)
{
std::stringstream ss;
ss << "Too many redirects: " << redirects;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::TooManyRedirects,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Recurse
std::string location = headers["Location"];
return request(location, verb, body, args, redirects + 1);
}
if (verb == "HEAD")
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
headers,
payload,
std::string(),
uploadSize,
downloadSize);
}
// Parse response:
if (headers.find("Content-Length") != headers.end())
{
ssize_t contentLength = -1;
ss.str("");
ss << headers["Content-Length"];
ss >> contentLength;
auto chunkResult = _socket->readBytes(contentLength,
args->onProgressCallback,
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (!args->onChunkCallback)
{
payload.reserve(contentLength);
payload += chunkResult.second;
}
}
else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked")
{
std::stringstream ss;
while (true)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second;
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uint64_t chunkSize;
ss.str("");
ss << std::hex << line;
ss >> chunkSize;
if (args->verbose)
{
std::stringstream oss;
oss << "Reading " << chunkSize << " bytes" << std::endl;
log(oss.str(), args);
}
// Read a chunk
auto chunkResult = _socket->readBytes((size_t) chunkSize,
args->onProgressCallback,
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (!args->onChunkCallback)
{
payload.reserve(payload.size() + (size_t) chunkSize);
payload += chunkResult.second;
}
// Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (chunkSize == 0) break;
}
}
else if (code == 204)
{
; // 204 is NoContent response code
}
else
{
std::string errorMsg("Cannot read http body");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotReadBody,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
downloadSize = payload.size();
// If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip")
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload;
if (!gzipDecompress(payload, decompressedPayload))
{
std::string errorMsg("Error decompressing payload");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload = decompressedPayload;
#else
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
#endif
}
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
headers,
payload,
std::string(),
uploadSize,
downloadSize);
}
HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kGet, std::string(), args);
}
HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kHead, std::string(), args);
}
HttpResponsePtr HttpClient::Delete(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kDelete, std::string(), args);
}
HttpResponsePtr HttpClient::request(const std::string& url,
const std::string& verb,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
std::string body;
if (httpFormDataParameters.empty())
{
body = serializeHttpParameters(httpParameters);
}
else
{
std::string multipartBoundary = generateMultipartBoundary();
args->multipartBoundary = multipartBoundary;
body = serializeHttpFormDataParameters(
multipartBoundary, httpFormDataParameters, httpParameters);
}
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest)
{
body = gzipCompress(body);
}
#endif
return request(url, verb, body, args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPost, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args)
{
return request(url, kPost, body, args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPut, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPut, body, args);
}
HttpResponsePtr HttpClient::patch(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPatch, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::patch(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPatch, body, args);
}
std::string HttpClient::urlEncode(const std::string& value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i)
{
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
{
std::stringstream ss;
size_t count = httpParameters.size();
size_t i = 0;
for (auto&& it : httpParameters)
{
ss << urlEncode(it.first) << "=" << urlEncode(it.second);
if (i++ < (count - 1))
{
ss << "&";
}
}
return ss.str();
}
std::string HttpClient::serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters)
{
//
// --AaB03x
// Content-Disposition: form-data; name="submit-name"
// Larry
// --AaB03x
// Content-Disposition: form-data; name="foo.txt"; filename="file1.txt"
// Content-Type: text/plain
// ... contents of file1.txt ...
// --AaB03x--
//
std::stringstream ss;
for (auto&& it : httpFormDataParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< " filename=\"" << it.first << "\""
<< "\r\n"
<< "Content-Type: application/octet-stream"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
for (auto&& it : httpParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
ss << "--" << multipartBoundary << "--\r\n";
return ss.str();
}
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
{
if (args->logger)
{
args->logger(msg);
}
}
std::string HttpClient::generateMultipartBoundary()
{
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
static std::random_device rd;
static std::mt19937 generator(rd());
std::shuffle(str.begin(), str.end(), generator);
return str;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,123 @@
/*
* IXHttpClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketHttpHeaders.h"
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
namespace ix
{
class HttpClient
{
public:
HttpClient(bool async = false);
~HttpClient();
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr Delete(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr post(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
HttpResponsePtr post(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr put(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
HttpResponsePtr put(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr patch(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
HttpResponsePtr patch(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr request(const std::string& url,
const std::string& verb,
const std::string& body,
HttpRequestArgsPtr args,
int redirects = 0);
HttpResponsePtr request(const std::string& url,
const std::string& verb,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
void setForceBody(bool value);
// Async API
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
const std::string& verb = HttpClient::kGet);
bool performRequest(HttpRequestArgsPtr request,
const OnResponseCallback& onResponseCallback);
// TLS
void setTLSOptions(const SocketTLSOptions& tlsOptions);
std::string serializeHttpParameters(const HttpParameters& httpParameters);
std::string serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters = HttpParameters());
std::string generateMultipartBoundary();
std::string urlEncode(const std::string& value);
const static std::string kPost;
const static std::string kGet;
const static std::string kHead;
const static std::string kDelete;
const static std::string kPut;
const static std::string kPatch;
private:
void log(const std::string& msg, HttpRequestArgsPtr args);
// Async API background thread runner
void run();
// Async API
bool _async;
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
mutable std::mutex _queueMutex;
std::condition_variable _condition;
std::atomic<bool> _stop;
std::thread _thread;
std::unique_ptr<Socket> _socket;
std::recursive_mutex _mutex; // to protect accessing the _socket (only one socket per
// client) the mutex needs to be recursive as this function
// might be called recursively to follow HTTP redirections
SocketTLSOptions _tlsOptions;
bool _forceBody;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,248 @@
/*
* IXHttpServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttpServer.h"
#include "IXGzipCodec.h"
#include "IXNetSystem.h"
#include "IXSocketConnect.h"
#include "IXUserAgent.h"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <sstream>
#include <vector>
namespace
{
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
{
std::vector<uint8_t> memblock;
std::ifstream file(path);
if (!file.is_open()) return std::make_pair(false, memblock);
file.seekg(0, file.end);
std::streamoff size = file.tellg();
file.seekg(0, file.beg);
memblock.resize((size_t) size);
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
return std::make_pair(true, memblock);
}
std::pair<bool, std::string> readAsString(const std::string& path)
{
auto res = load(path);
auto vec = res.second;
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
}
std::string response_head_file(const std::string& file_name){
if (std::string::npos != file_name.find(".html") || std::string::npos != file_name.find(".htm"))
return "text/html";
else if (std::string::npos != file_name.find(".css"))
return "text/css";
else if (std::string::npos != file_name.find(".js") || std::string::npos != file_name.find(".mjs"))
return "application/x-javascript";
else if (std::string::npos != file_name.find(".ico"))
return "image/x-icon";
else if (std::string::npos != file_name.find(".png"))
return "image/png";
else if (std::string::npos != file_name.find(".jpg") || std::string::npos != file_name.find(".jpeg"))
return "image/jpeg";
else if (std::string::npos != file_name.find(".gif"))
return "image/gif";
else if (std::string::npos != file_name.find(".svg"))
return "image/svg+xml";
else
return "application/octet-stream";
}
} // namespace
namespace ix
{
const int HttpServer::kDefaultTimeoutSecs(30);
HttpServer::HttpServer(int port,
const std::string& host,
int backlog,
size_t maxConnections,
int addressFamily,
int timeoutSecs,
int handshakeTimeoutSecs)
: WebSocketServer(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily)
, _timeoutSecs(timeoutSecs)
{
setDefaultConnectionCallback();
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
}
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
auto ret = Http::parseRequest(socket, _timeoutSecs);
// FIXME: handle errors in parseRequest
if (std::get<0>(ret))
{
auto request = std::get<2>(ret);
std::shared_ptr<ix::HttpResponse> response;
if (request->headers["Upgrade"] == "websocket")
{
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request);
}
else
{
auto response = _onConnectionCallback(request, connectionState);
if (!Http::sendResponse(response, socket))
{
logError("Cannot send response");
}
}
}
connectionState->setTerminated();
}
void HttpServer::setDefaultConnectionCallback()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
std::string uri(request->uri);
if (uri.empty() || uri == "/")
{
uri = "/index.html";
}
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
headers["Content-Type"] = response_head_file(uri);
std::string path("." + uri);
auto res = readAsString(path);
bool found = res.first;
if (!found)
{
return std::make_shared<HttpResponse>(
404, "Not Found", HttpErrorCode::Ok, WebSocketHttpHeaders(), std::string());
}
std::string content = res.second;
#ifdef IXWEBSOCKET_USE_ZLIB
std::string acceptEncoding = request->headers["Accept-encoding"];
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
{
content = gzipCompress(content);
headers["Content-Encoding"] = "gzip";
}
#endif
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri << " " << content.size();
logInfo(ss.str());
// FIXME: check extensions to set the content type
// headers["Content-Type"] = "application/octet-stream";
headers["Accept-Ranges"] = "none";
for (auto&& it : request->headers)
{
headers[it.first] = it.second;
}
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, content);
});
}
void HttpServer::makeRedirectServer(const std::string& redirectUrl)
{
//
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
//
setOnConnectionCallback(
[this, redirectUrl](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri;
logInfo(ss.str());
if (request->method == "POST")
{
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, std::string());
}
headers["Location"] = redirectUrl;
return std::make_shared<HttpResponse>(
301, "OK", HttpErrorCode::Ok, headers, std::string());
});
}
//
// Display the client parameter and body on the console
//
void HttpServer::makeDebugServer()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri;
logInfo(ss.str());
logInfo("== Headers == ");
for (auto&& it : request->headers)
{
std::ostringstream oss;
oss << it.first << ": " << it.second;
logInfo(oss.str());
}
logInfo("");
logInfo("== Body == ");
logInfo(request->body);
logInfo("");
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
});
}
int HttpServer::getTimeoutSecs()
{
return _timeoutSecs;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,57 @@
/*
* IXHttpServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXHttp.h"
#include "IXWebSocket.h"
#include "IXWebSocketServer.h"
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class HttpServer final : public WebSocketServer
{
public:
using OnConnectionCallback =
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
HttpServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily,
int timeoutSecs = HttpServer::kDefaultTimeoutSecs,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
void setOnConnectionCallback(const OnConnectionCallback& callback);
void makeRedirectServer(const std::string& redirectUrl);
void makeDebugServer();
int getTimeoutSecs();
private:
// Member variables
OnConnectionCallback _onConnectionCallback;
const static int kDefaultTimeoutSecs;
int _timeoutSecs;
// Methods
virtual void handleConnection(std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) final;
void setDefaultConnectionCallback();
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,461 @@
/*
* IXNetSystem.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXNetSystem.h"
#include <cstdint>
#include <cstdio>
#ifdef _WIN32
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 102
#endif
#ifndef ENOSPC
#define ENOSPC 28
#endif
#include <vector>
#endif
namespace ix
{
bool initNetSystem()
{
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
int err;
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
return err == 0;
#else
return true;
#endif
}
bool uninitNetSystem()
{
#ifdef _WIN32
int err = WSACleanup();
return err == 0;
#else
return true;
#endif
}
#ifdef _WIN32
struct WSAEvent
{
public:
WSAEvent(struct pollfd* fd)
: _fd(fd)
{
_event = WSACreateEvent();
}
WSAEvent(WSAEvent&& source) noexcept
{
_event = source._event;
source._event = WSA_INVALID_EVENT; // invalidate the event in the source
_fd = source._fd;
}
~WSAEvent()
{
if (_event != WSA_INVALID_EVENT)
{
// We must deselect the networkevents from the socket event. Otherwise the
// socket will report states that aren't there.
if (_fd != nullptr && _fd->fd != -1)
WSAEventSelect(_fd->fd, _event, 0);
WSACloseEvent(_event);
}
}
operator HANDLE()
{
return _event;
}
operator struct pollfd*()
{
return _fd;
}
private:
HANDLE _event;
struct pollfd* _fd;
};
#endif
//
// That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet
// (the curl folks have had problems with it).
//
// So we make it a select wrapper
//
// UPDATE: WSAPoll was fixed in Windows 10 Version 2004
//
// The optional "event" is set to nullptr if it wasn't signaled.
int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event)
{
#ifdef _WIN32
if (event && *event)
{
HANDLE interruptEvent = reinterpret_cast<HANDLE>(*event);
*event = nullptr; // the event wasn't signaled yet
if (nfds < 0 || nfds >= MAXIMUM_WAIT_OBJECTS - 1)
{
WSASetLastError(WSAEINVAL);
return SOCKET_ERROR;
}
std::vector<WSAEvent> socketEvents;
std::vector<HANDLE> handles;
// put the interrupt event as first element, making it highest priority
handles.push_back(interruptEvent);
// create the WSAEvents for the sockets
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (fd->fd >= 0)
{
// create WSAEvent and add it to the vectors
socketEvents.push_back(std::move(WSAEvent(fd)));
HANDLE handle = socketEvents.back();
if (handle == WSA_INVALID_EVENT)
{
WSASetLastError(WSAENOBUFS);
return SOCKET_ERROR;
}
handles.push_back(handle);
// mapping
long networkEvents = 0;
if (fd->events & (POLLIN )) networkEvents |= FD_READ | FD_ACCEPT;
if (fd->events & (POLLOUT /*| POLLWRNORM | POLLWRBAND*/)) networkEvents |= FD_WRITE | FD_CONNECT;
//if (fd->events & (POLLPRI | POLLRDBAND )) networkEvents |= FD_OOB;
if (WSAEventSelect(fd->fd, handle, networkEvents) != 0)
{
fd->revents = POLLNVAL;
socketEvents.pop_back();
handles.pop_back();
}
}
}
DWORD n = WSAWaitForMultipleEvents(handles.size(), handles.data(), FALSE, timeout != -1 ? static_cast<DWORD>(timeout) : WSA_INFINITE, FALSE);
if (n == WSA_WAIT_FAILED) return SOCKET_ERROR;
if (n == WSA_WAIT_TIMEOUT) return 0;
if (n == WSA_WAIT_EVENT_0)
{
// the interrupt event was signaled
*event = reinterpret_cast<void*>(interruptEvent);
return 1;
}
int handleIndex = n - WSA_WAIT_EVENT_0;
int socketIndex = handleIndex - 1;
WSANETWORKEVENTS netEvents;
int count = 0;
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
// all the signaled events must be processed.
while (socketIndex < socketEvents.size())
{
struct pollfd* fd = socketEvents[socketIndex];
memset(&netEvents, 0, sizeof(netEvents));
if (WSAEnumNetworkEvents(fd->fd, socketEvents[socketIndex], &netEvents) != 0)
{
fd->revents = POLLERR;
}
else if (netEvents.lNetworkEvents != 0)
{
// mapping
if (netEvents.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_OOB)) fd->revents |= POLLIN;
if (netEvents.lNetworkEvents & (FD_WRITE | FD_CONNECT )) fd->revents |= POLLOUT;
for (int i = 0; i < FD_MAX_EVENTS; ++i)
{
if (netEvents.iErrorCode[i] != 0)
{
fd->revents |= POLLERR;
break;
}
}
if (fd->revents != 0)
{
// only signaled sockets count
count++;
}
}
socketIndex++;
}
return count;
}
else
{
if (event && *event) *event = nullptr;
socket_t maxfd = 0;
fd_set readfds, writefds, errorfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
if (fd->fd > maxfd)
{
maxfd = fd->fd;
}
if ((fd->events & POLLIN))
{
FD_SET(fd->fd, &readfds);
}
if ((fd->events & POLLOUT))
{
FD_SET(fd->fd, &writefds);
}
if ((fd->events & POLLERR))
{
FD_SET(fd->fd, &errorfds);
}
}
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
if (ret < 0)
{
return ret;
}
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (FD_ISSET(fd->fd, &readfds))
{
fd->revents |= POLLIN;
}
if (FD_ISSET(fd->fd, &writefds))
{
fd->revents |= POLLOUT;
}
if (FD_ISSET(fd->fd, &errorfds))
{
fd->revents |= POLLERR;
}
}
return ret;
}
#else
if (event && *event) *event = nullptr;
//
// It was reported that on Android poll can fail and return -1 with
// errno == EINTR, which should be a temp error and should typically
// be handled by retrying in a loop.
// Maybe we need to put all syscall / C functions in
// a new IXSysCalls.cpp and wrap them all.
//
// The style from libuv is as such.
//
int ret = -1;
do
{
ret = ::poll(fds, nfds, timeout);
} while (ret == -1 && errno == EINTR);
return ret;
#endif
}
//
// mingw does not have inet_ntop, which were taken as is from the musl C library.
//
const char* inet_ntop(int af, const void* a0, char* s, socklen_t l)
{
#if defined(_WIN32) && defined(__GNUC__)
const unsigned char* a = (const unsigned char*) a0;
int i, j, max, best;
char buf[100];
switch (af)
{
case AF_INET:
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) return s;
break;
case AF_INET6:
if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12))
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%x:%x",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
256 * a[12] + a[13],
256 * a[14] + a[15]);
else
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%d.%d.%d.%d",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
a[12],
a[13],
a[14],
a[15]);
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
for (i = best = 0, max = 2; buf[i]; i++)
{
if (i && buf[i] != ':') continue;
j = strspn(buf + i, ":0");
if (j > max) best = i, max = j;
}
if (max > 3)
{
buf[best] = buf[best + 1] = ':';
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
}
if (strlen(buf) < l)
{
strcpy(s, buf);
return s;
}
break;
default: errno = EAFNOSUPPORT; return 0;
}
errno = ENOSPC;
return 0;
#else
return ::inet_ntop(af, a0, s, l);
#endif
}
#if defined(_WIN32) && defined(__GNUC__)
static int hexval(unsigned c)
{
if (c - '0' < 10) return c - '0';
c |= 32;
if (c - 'a' < 6) return c - 'a' + 10;
return -1;
}
#endif
//
// mingw does not have inet_pton, which were taken as is from the musl C library.
//
int inet_pton(int af, const char* s, void* a0)
{
#if defined(_WIN32) && defined(__GNUC__)
uint16_t ip[8];
unsigned char* a = (unsigned char*) a0;
int i, j, v, d, brk = -1, need_v4 = 0;
if (af == AF_INET)
{
for (i = 0; i < 4; i++)
{
for (v = j = 0; j < 3 && isdigit(s[j]); j++)
v = 10 * v + s[j] - '0';
if (j == 0 || (j > 1 && s[0] == '0') || v > 255) return 0;
a[i] = v;
if (s[j] == 0 && i == 3) return 1;
if (s[j] != '.') return 0;
s += j + 1;
}
return 0;
}
else if (af != AF_INET6)
{
errno = EAFNOSUPPORT;
return -1;
}
if (*s == ':' && *++s != ':') return 0;
for (i = 0;; i++)
{
if (s[0] == ':' && brk < 0)
{
brk = i;
ip[i & 7] = 0;
if (!*++s) break;
if (i == 7) return 0;
continue;
}
for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++)
v = 16 * v + d;
if (j == 0) return 0;
ip[i & 7] = v;
if (!s[j] && (brk >= 0 || i == 7)) break;
if (i == 7) return 0;
if (s[j] != ':')
{
if (s[j] != '.' || (i < 6 && brk < 0)) return 0;
need_v4 = 1;
i++;
break;
}
s += j + 1;
}
if (brk >= 0)
{
memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk));
for (j = 0; j < 7 - i; j++)
ip[brk + j] = 0;
}
for (j = 0; j < 8; j++)
{
*a++ = ip[j] >> 8;
*a++ = ip[j];
}
if (need_v4 && inet_pton(AF_INET, (const char*) s, a - 4) <= 0) return 0;
return 1;
#else
return ::inet_pton(af, s, a0);
#endif
}
// Convert network bytes to host bytes. Copied from the ASIO library
unsigned short network_to_host_short(unsigned short value)
{
#if defined(_WIN32)
unsigned char* value_p = reinterpret_cast<unsigned char*>(&value);
unsigned short result = (static_cast<unsigned short>(value_p[0]) << 8)
| static_cast<unsigned short>(value_p[1]);
return result;
#else // defined(_WIN32)
return ntohs(value);
#endif // defined(_WIN32)
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,93 @@
/*
* IXNetSystem.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
#include <cstdint>
#ifdef __FreeBSD__
#include <sys/types.h>
#endif
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <ws2tcpip.h>
#include <winsock2.h>
#include <basetsd.h>
#include <io.h>
#include <ws2def.h>
#include <cerrno>
#undef EWOULDBLOCK
#undef EAGAIN
#undef EINPROGRESS
#undef EBADF
#undef EINVAL
// map to WSA error codes
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EAGAIN WSATRY_AGAIN
#define EINPROGRESS WSAEINPROGRESS
#define EBADF WSAEBADF
#define EINVAL WSAEINVAL
// Define our own poll on Windows, as a wrapper on top of select
typedef unsigned long int nfds_t;
// pollfd is not defined by some versions of mingw64 since _WIN32_WINNT is too low
#if _WIN32_WINNT < 0x0600
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
#define POLLIN 0x001 /* There is data to read. */
#define POLLOUT 0x004 /* Writing now will not block. */
#define POLLERR 0x008 /* Error condition. */
#define POLLHUP 0x010 /* Hung up. */
#define POLLNVAL 0x020 /* Invalid polling request. */
#endif
#else
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#endif
namespace ix
{
#ifdef _WIN32
typedef SOCKET socket_t;
#else
typedef int socket_t;
#endif
bool initNetSystem();
bool uninitNetSystem();
int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
int inet_pton(int af, const char* src, void* dst);
unsigned short network_to_host_short(unsigned short value);
} // namespace ix

Wyświetl plik

@ -0,0 +1,16 @@
/*
* IXProgressCallback.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <functional>
#include <string>
namespace ix
{
using OnProgressCallback = std::function<bool(int current, int total)>;
using OnChunkCallback = std::function<void(const std::string&)>;
}

Wyświetl plik

@ -0,0 +1,53 @@
/*
* IXSelectInterrupt.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSelectInterrupt.h"
namespace ix
{
const uint64_t SelectInterrupt::kSendRequest = 1;
const uint64_t SelectInterrupt::kCloseRequest = 2;
SelectInterrupt::SelectInterrupt()
{
;
}
SelectInterrupt::~SelectInterrupt()
{
;
}
bool SelectInterrupt::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterrupt::notify(uint64_t /*value*/)
{
return true;
}
uint64_t SelectInterrupt::read()
{
return 0;
}
bool SelectInterrupt::clear()
{
return true;
}
int SelectInterrupt::getFd() const
{
return -1;
}
void* SelectInterrupt::getEvent() const
{
return nullptr;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,35 @@
/*
* IXSelectInterrupt.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <memory>
#include <string>
namespace ix
{
class SelectInterrupt
{
public:
SelectInterrupt();
virtual ~SelectInterrupt();
virtual bool init(std::string& errorMsg);
virtual bool notify(uint64_t value);
virtual bool clear();
virtual uint64_t read();
virtual int getFd() const;
virtual void* getEvent() const;
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
};
using SelectInterruptPtr = std::unique_ptr<SelectInterrupt>;
} // namespace ix

Wyświetl plik

@ -0,0 +1,85 @@
/*
* IXSelectInterruptEvent.cpp
*/
//
// On Windows we use a Windows Event to wake up ix::poll() (WSAWaitForMultipleEvents).
// And on any other platform that doesn't support pipe file descriptors we
// emulate the interrupt event by using a short timeout with ix::poll() and
// read from the SelectInterrupt. (see Socket::poll() "Emulation mode")
//
#include <algorithm>
#include "IXSelectInterruptEvent.h"
namespace ix
{
SelectInterruptEvent::SelectInterruptEvent()
{
#ifdef _WIN32
_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#endif
}
SelectInterruptEvent::~SelectInterruptEvent()
{
#ifdef _WIN32
CloseHandle(_event);
#endif
}
bool SelectInterruptEvent::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterruptEvent::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_valuesMutex);
// WebSocket implementation detail: We only need one of the values in the queue
if (std::find(_values.begin(), _values.end(), value) == _values.end())
_values.push_back(value);
#ifdef _WIN32
SetEvent(_event); // wake up
#endif
return true;
}
uint64_t SelectInterruptEvent::read()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
if (_values.size() > 0)
{
uint64_t value = _values.front();
_values.pop_front();
#ifdef _WIN32
// signal the event if there is still data in the queue
if (_values.size() == 0)
ResetEvent(_event);
#endif
return value;
}
return 0;
}
bool SelectInterruptEvent::clear()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
_values.clear();
#ifdef _WIN32
ResetEvent(_event);
#endif
return true;
}
void* SelectInterruptEvent::getEvent() const
{
#ifdef _WIN32
return reinterpret_cast<void*>(_event);
#else
return nullptr;
#endif
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,39 @@
/*
* IXSelectInterruptEvent.h
*/
#pragma once
#include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#include <string>
#include <deque>
#ifdef _WIN32
#include <windows.h>
#endif
namespace ix
{
class SelectInterruptEvent final : public SelectInterrupt
{
public:
SelectInterruptEvent();
virtual ~SelectInterruptEvent();
bool init(std::string& /*errorMsg*/) final;
bool notify(uint64_t value) final;
bool clear() final;
uint64_t read() final;
void* getEvent() const final;
private:
// contains every value only once, new values are inserted at the begin, nu
std::deque<uint64_t> _values;
std::mutex _valuesMutex;
#ifdef _WIN32
// Windows Event to wake up the socket poll
HANDLE _event;
#endif
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,26 @@
/*
* IXSelectInterruptFactory.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSelectInterruptFactory.h"
#include "IXUniquePtr.h"
#if _WIN32
#include "IXSelectInterruptEvent.h"
#else
#include "IXSelectInterruptPipe.h"
#endif
namespace ix
{
SelectInterruptPtr createSelectInterrupt()
{
#ifdef _WIN32
return ix::make_unique<SelectInterruptEvent>();
#else
return ix::make_unique<SelectInterruptPipe>();
#endif
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,16 @@
/*
* IXSelectInterruptFactory.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
namespace ix
{
class SelectInterrupt;
using SelectInterruptPtr = std::unique_ptr<SelectInterrupt>;
SelectInterruptPtr createSelectInterrupt();
} // namespace ix

Wyświetl plik

@ -0,0 +1,161 @@
/*
* IXSelectInterruptPipe.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
//
// On UNIX we use pipes to wake up select. There is no way to do that
// on Windows so this file is compiled out on Windows.
//
#ifndef _WIN32
#include "IXSelectInterruptPipe.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sstream>
#include <string.h> // for strerror
#include <unistd.h> // for write
namespace ix
{
// File descriptor at index 0 in _fildes is the read end of the pipe
// File descriptor at index 1 in _fildes is the write end of the pipe
const int SelectInterruptPipe::kPipeReadIndex = 0;
const int SelectInterruptPipe::kPipeWriteIndex = 1;
SelectInterruptPipe::SelectInterruptPipe()
{
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
}
SelectInterruptPipe::~SelectInterruptPipe()
{
::close(_fildes[kPipeReadIndex]);
::close(_fildes[kPipeWriteIndex]);
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
}
bool SelectInterruptPipe::init(std::string& errorMsg)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
// calling init twice is a programming error
assert(_fildes[kPipeReadIndex] == -1);
assert(_fildes[kPipeWriteIndex] == -1);
if (pipe(_fildes) < 0)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in pipe() call"
<< " : " << strerror(errno);
errorMsg = ss.str();
return false;
}
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
#ifdef F_SETNOSIGPIPE
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
#endif
return true;
}
bool SelectInterruptPipe::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeWriteIndex];
if (fd == -1) return false;
ssize_t ret = -1;
do
{
ret = ::write(fd, &value, sizeof(value));
} while (ret == -1 && errno == EINTR);
// we should write 8 bytes for an uint64_t
return ret == 8;
}
// TODO: return max uint64_t for errors ?
uint64_t SelectInterruptPipe::read()
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeReadIndex];
uint64_t value = 0;
ssize_t ret = -1;
do
{
ret = ::read(fd, &value, sizeof(value));
} while (ret == -1 && errno == EINTR);
return value;
}
bool SelectInterruptPipe::clear()
{
return true;
}
int SelectInterruptPipe::getFd() const
{
std::lock_guard<std::mutex> lock(_fildesMutex);
return _fildes[kPipeReadIndex];
}
} // namespace ix
#endif // !_WIN32

Wyświetl plik

@ -0,0 +1,41 @@
/*
* IXSelectInterruptPipe.h
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#include <stdint.h>
#include <string>
namespace ix
{
class SelectInterruptPipe final : public SelectInterrupt
{
public:
SelectInterruptPipe();
virtual ~SelectInterruptPipe();
bool init(std::string& errorMsg) final;
bool notify(uint64_t value) final;
bool clear() final;
uint64_t read() final;
int getFd() const final;
private:
// Store file descriptors used by the communication pipe. Communication
// happens between a control thread and a background thread, which is
// blocked on select.
int _fildes[2];
mutable std::mutex _fildesMutex;
// Used to identify the read/write idx
static const int kPipeReadIndex;
static const int kPipeWriteIndex;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,83 @@
/*
* IXSetThreadName.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXSetThreadName.h"
// unix systems
#if defined(__APPLE__) || defined(__linux__) || defined(BSD)
#include <pthread.h>
#endif
// freebsd needs this header as well
#if defined(BSD)
#include <pthread_np.h>
#endif
// Windows
#ifdef _WIN32
#include <windows.h>
#endif
namespace ix
{
#ifdef _WIN32
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push, 8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
void SetThreadName(DWORD dwThreadID, const char* threadName)
{
#ifndef __GNUC__
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = threadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{
RaiseException(
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
#endif
}
#endif
void setThreadName(const std::string& name)
{
#if defined(__APPLE__)
//
// Apple reserves 16 bytes for its thread names
// Notice that the Apple version of pthread_setname_np
// does not take a pthread_t argument
//
pthread_setname_np(name.substr(0, 63).c_str());
#elif defined(__linux__)
//
// Linux only reserves 16 bytes for its thread names
// See prctl and PR_SET_NAME property in
// http://man7.org/linux/man-pages/man2/prctl.2.html
//
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
#elif defined(_WIN32)
SetThreadName(-1, name.c_str());
#elif defined(BSD)
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
#else
// ... assert here ?
#endif
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,12 @@
/*
* IXSetThreadName.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
void setThreadName(const std::string& name);
}

Wyświetl plik

@ -0,0 +1,452 @@
/*
* IXSocket.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocket.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptFactory.h"
#include "IXSocketConnect.h"
#include <algorithm>
#include <array>
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <vector>
#ifdef min
#undef min
#endif
namespace ix
{
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
Socket::Socket(int fd)
: _sockfd(fd)
, _selectInterrupt(createSelectInterrupt())
{
;
}
Socket::~Socket()
{
close();
}
PollResultType Socket::poll(bool readyToRead,
int timeoutMs,
int sockfd,
const SelectInterruptPtr& selectInterrupt)
{
PollResultType pollResult = PollResultType::ReadyForRead;
//
// We used to use ::select to poll but on Android 9 we get large fds out of
// ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching
// to ::poll does fix that.
//
// However poll isn't as portable as select and has bugs on Windows, so we
// have a shim to fallback to select on those platforms. See
// https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
//
nfds_t nfds = 1;
struct pollfd fds[2];
memset(fds, 0, sizeof(fds));
fds[0].fd = sockfd;
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
// this is ignored by poll, but our select based poll wrapper on Windows needs it
fds[0].events |= POLLERR;
// File descriptor used to interrupt select when needed
int interruptFd = -1;
void* interruptEvent = nullptr;
if (selectInterrupt)
{
interruptFd = selectInterrupt->getFd();
interruptEvent = selectInterrupt->getEvent();
if (interruptFd != -1)
{
nfds = 2;
fds[1].fd = interruptFd;
fds[1].events = POLLIN;
}
else if (interruptEvent == nullptr)
{
// Emulation mode: SelectInterrupt neither supports file descriptors nor events
// Check the selectInterrupt for requests before doing the poll().
if (readSelectInterruptRequest(selectInterrupt, &pollResult))
{
return pollResult;
}
}
}
void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled
int ret = ix::poll(fds, nfds, timeoutMs, &event);
if (ret < 0)
{
pollResult = PollResultType::Error;
}
else if (ret == 0)
{
pollResult = PollResultType::Timeout;
if (selectInterrupt && interruptFd == -1 && interruptEvent == nullptr)
{
// Emulation mode: SelectInterrupt neither supports fd nor events
// Check the selectInterrupt for requests
readSelectInterruptRequest(selectInterrupt, &pollResult);
}
}
else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr))
{
// The InterruptEvent was signaled
readSelectInterruptRequest(selectInterrupt, &pollResult);
}
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN)
{
pollResult = PollResultType::ReadyForRead;
}
else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT)
{
pollResult = PollResultType::ReadyForWrite;
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (fds[0].revents & POLLERR)
{
pollResult = PollResultType::Error;
}
#else
int optval = -1;
socklen_t optlen = sizeof(optval);
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0)
{
pollResult = PollResultType::Error;
// set errno to optval so that external callers can have an
// appropriate error description when calling strerror
errno = optval;
}
#endif
}
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
fds[0].revents & POLLNVAL))
{
pollResult = PollResultType::Error;
}
return pollResult;
}
bool Socket::readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
PollResultType* pollResult)
{
uint64_t value = selectInterrupt->read();
if (value == SelectInterrupt::kSendRequest)
{
*pollResult = PollResultType::SendRequest;
return true;
}
else if (value == SelectInterrupt::kCloseRequest)
{
*pollResult = PollResultType::CloseRequest;
return true;
}
return false;
}
PollResultType Socket::isReadyToRead(int timeoutMs)
{
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = true;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
PollResultType Socket::isReadyToWrite(int timeoutMs)
{
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = false;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
// Wake up from poll/select by writing to the pipe which is watched by select
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
{
return _selectInterrupt->notify(wakeUpCode);
}
bool Socket::isWakeUpFromPollSupported()
{
return _selectInterrupt->getFd() != -1 || _selectInterrupt->getEvent() != nullptr;
}
bool Socket::accept(std::string& errMsg)
{
if (_sockfd == -1)
{
errMsg = "Socket is uninitialized";
return false;
}
return true;
}
bool Socket::connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (!_selectInterrupt->clear()) return false;
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
return _sockfd != -1;
}
void Socket::close()
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (_sockfd == -1) return;
closeSocket(_sockfd);
_sockfd = -1;
}
ssize_t Socket::send(char* buffer, size_t length)
{
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return ::send(_sockfd, buffer, length, flags);
}
ssize_t Socket::send(const std::string& buffer)
{
return send((char*) &buffer[0], buffer.size());
}
ssize_t Socket::recv(void* buffer, size_t length)
{
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return ::recv(_sockfd, (char*) buffer, length, flags);
}
int Socket::getErrno()
{
int err;
#ifdef _WIN32
err = WSAGetLastError();
#else
err = errno;
#endif
return err;
}
bool Socket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
}
void Socket::closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
bool Socket::init(std::string& errorMsg)
{
return _selectInterrupt->init(errorMsg);
}
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
int offset = 0;
int len = (int) str.size();
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send((char*) &str[offset], len);
// We wrote some bytes, as needed, all good.
if (ret > 0)
{
if (ret == len)
{
return true;
}
else
{
offset += ret;
len -= ret;
continue;
}
}
// There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded())
{
continue;
}
// There was an error during the write, abort
else
{
return false;
}
}
}
bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested)
{
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret;
ret = recv(buffer, 1);
// We read one byte, as needed, all good.
if (ret == 1)
{
return true;
}
// There is possibly something to be read, try again
else if (ret < 0 && Socket::isWaitNeeded())
{
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error)
{
return false;
}
}
// There was an error during the read, abort
else
{
return false;
}
}
}
std::pair<bool, std::string> Socket::readLine(
const CancellationRequest& isCancellationRequested)
{
char c;
std::string line;
line.reserve(64);
for (int i = 0; i < 2 || (line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
{
if (!readByte(&c, isCancellationRequested))
{
// Return what we were able to read
return std::make_pair(false, line);
}
line += c;
}
return std::make_pair(true, line);
}
std::pair<bool, std::string> Socket::readBytes(
size_t length,
const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested)
{
std::array<uint8_t, 1 << 14> readBuffer;
std::vector<uint8_t> output;
size_t bytesRead = 0;
while (bytesRead != length)
{
if (isCancellationRequested && isCancellationRequested())
{
const std::string errorMsg("Cancellation Requested");
return std::make_pair(false, errorMsg);
}
size_t size = std::min(readBuffer.size(), length - bytesRead);
ssize_t ret = recv((char*) &readBuffer[0], size);
if (ret > 0)
{
if (onChunkCallback)
{
std::string chunk(readBuffer.begin(), readBuffer.begin() + ret);
onChunkCallback(chunk);
}
else
{
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
}
bytesRead += ret;
}
else if (ret <= 0 && !Socket::isWaitNeeded())
{
const std::string errorMsg("Recv Error");
return std::make_pair(false, errorMsg);
}
if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length);
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error)
{
const std::string errorMsg("Poll Error");
return std::make_pair(false, errorMsg);
}
}
return std::make_pair(true, std::string(output.begin(), output.end()));
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,100 @@
/*
* IXSocket.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#ifdef _WIN32
#include <basetsd.h>
#ifdef _MSC_VER
typedef SSIZE_T ssize_t;
#endif
#endif
#include "IXCancellationRequest.h"
#include "IXProgressCallback.h"
#include "IXSelectInterrupt.h"
namespace ix
{
enum class PollResultType
{
ReadyForRead = 0,
ReadyForWrite = 1,
Timeout = 2,
Error = 3,
SendRequest = 4,
CloseRequest = 5
};
class Socket
{
public:
Socket(int fd = -1);
virtual ~Socket();
bool init(std::string& errorMsg);
// Functions to check whether there is activity on the socket
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint64_t wakeUpCode);
bool isWakeUpFromPollSupported();
PollResultType isReadyToWrite(int timeoutMs);
PollResultType isReadyToRead(int timeoutMs);
// Virtual methods
virtual bool accept(std::string& errMsg);
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested);
virtual void close();
virtual ssize_t send(char* buffer, size_t length);
ssize_t send(const std::string& buffer);
virtual ssize_t recv(void* buffer, size_t length);
// Blocking and cancellable versions, working with socket that can be set
// to non blocking mode. Used during HTTP upgrade.
bool readByte(void* buffer, const CancellationRequest& isCancellationRequested);
bool writeBytes(const std::string& str, const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readBytes(size_t length,
const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested);
static int getErrno();
static bool isWaitNeeded();
static void closeSocket(int fd);
static PollResultType poll(bool readyToRead,
int timeoutMs,
int sockfd,
const SelectInterruptPtr& selectInterrupt);
protected:
std::atomic<int> _sockfd;
std::mutex _socketMutex;
static bool readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
PollResultType* pollResult);
private:
static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout;
SelectInterruptPtr _selectInterrupt;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,315 @@
/*
* IXSocketAppleSSL.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*
* Adapted from Satori SDK Apple SSL code.
*/
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
#include "IXSocketAppleSSL.h"
#include "IXSocketConnect.h"
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define socketerrno errno
#include <Security/SecureTransport.h>
namespace ix
{
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _sslContext(nullptr)
, _tlsOptions(tlsOptions)
{
;
}
SocketAppleSSL::~SocketAppleSSL()
{
SocketAppleSSL::close();
}
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
{
std::string errMsg("Unknown SSL error.");
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
if (error)
{
CFStringRef message = CFErrorCopyDescription(error);
if (message)
{
char localBuffer[128];
Boolean success;
success = CFStringGetCString(message, localBuffer, 128, kCFStringEncodingUTF8);
if (success)
{
errMsg = localBuffer;
}
CFRelease(message);
}
CFRelease(error);
}
return errMsg;
}
OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection, void* data, size_t* len)
{
int fd = (int) (long) connection;
if (fd < 0) return errSSLInternal;
assert(data != nullptr);
assert(len != nullptr);
size_t requested_sz = *len;
ssize_t status = read(fd, data, requested_sz);
if (status > 0)
{
*len = (size_t) status;
if (requested_sz > *len)
{
return errSSLWouldBlock;
}
else
{
return noErr;
}
}
else if (status == 0)
{
*len = 0;
return errSSLClosedGraceful;
}
else
{
*len = 0;
switch (errno)
{
case ENOENT: return errSSLClosedGraceful;
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
case EINPROGRESS: return errSSLWouldBlock;
case ECONNRESET: return errSSLClosedAbort;
default: return errSecIO;
}
}
}
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
const void* data,
size_t* len)
{
int fd = (int) (long) connection;
if (fd < 0) return errSSLInternal;
assert(data != nullptr);
assert(len != nullptr);
size_t to_write_sz = *len;
ssize_t status = write(fd, data, to_write_sz);
if (status > 0)
{
*len = (size_t) status;
if (to_write_sz > *len)
{
return errSSLWouldBlock;
}
else
{
return noErr;
}
}
else if (status == 0)
{
*len = 0;
return errSSLClosedGraceful;
}
else
{
*len = 0;
switch (errno)
{
case ENOENT: return errSSLClosedGraceful;
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
case EINPROGRESS: return errSSLWouldBlock;
case ECONNRESET: return errSSLClosedAbort;
default: return errSecIO;
}
}
}
bool SocketAppleSSL::accept(std::string& errMsg)
{
errMsg = "TLS not supported yet in server mode with apple ssl backend";
return false;
}
OSStatus SocketAppleSSL::tlsHandShake(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
OSStatus status;
do
{
status = SSLHandshake(_sslContext);
// Interrupt the handshake
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
return errSSLInternal;
}
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
return status;
}
// No wait support
bool SocketAppleSSL::connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
OSStatus status;
{
std::lock_guard<std::mutex> lock(_mutex);
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
SSLSetIOFuncs(
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
if (!_tlsOptions.disable_hostname_validation)
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (_tlsOptions.isPeerVerifyDisabled())
{
Boolean option(1);
SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option);
status = tlsHandShake(errMsg, isCancellationRequested);
if (status == errSSLServerAuthCompleted)
{
// proceed with the handshake
status = tlsHandShake(errMsg, isCancellationRequested);
}
}
else
{
status = tlsHandShake(errMsg, isCancellationRequested);
}
}
if (status != noErr)
{
errMsg = getSSLErrorDescription(status);
close();
return false;
}
return true;
}
void SocketAppleSSL::close()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_sslContext == nullptr) return;
SSLClose(_sslContext);
CFRelease(_sslContext);
_sslContext = nullptr;
Socket::close();
}
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
{
OSStatus status = errSSLWouldBlock;
while (status == errSSLWouldBlock)
{
size_t processed = 0;
std::lock_guard<std::mutex> lock(_mutex);
status = SSLWrite(_sslContext, buf, nbyte, &processed);
if (processed > 0) return (ssize_t) processed;
// The connection was reset, inform the caller that this
// Socket should close
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
status == errSSLClosedAbort)
{
errno = ECONNRESET;
return -1;
}
if (status == errSSLWouldBlock)
{
errno = EWOULDBLOCK;
return -1;
}
}
return -1;
}
// No wait support
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
{
OSStatus status = errSSLWouldBlock;
while (status == errSSLWouldBlock)
{
size_t processed = 0;
std::lock_guard<std::mutex> lock(_mutex);
status = SSLRead(_sslContext, buf, nbyte, &processed);
if (processed > 0) return (ssize_t) processed;
// The connection was reset, inform the caller that this
// Socket should close
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
status == errSSLClosedAbort)
{
errno = ECONNRESET;
return -1;
}
if (status == errSSLWouldBlock)
{
errno = EWOULDBLOCK;
return -1;
}
}
return -1;
}
} // namespace ix
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT

Wyświetl plik

@ -0,0 +1,52 @@
/*
* IXSocketAppleSSL.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
#pragma once
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include <Security/SecureTransport.h>
#include <Security/Security.h>
#include <mutex>
namespace ix
{
class SocketAppleSSL final : public Socket
{
public:
SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
~SocketAppleSSL();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested) final;
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
static std::string getSSLErrorDescription(OSStatus status);
static OSStatus writeToSocket(SSLConnectionRef connection, const void* data, size_t* len);
static OSStatus readFromSocket(SSLConnectionRef connection, void* data, size_t* len);
OSStatus tlsHandShake(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
SSLContextRef _sslContext;
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
SocketTLSOptions _tlsOptions;
};
} // namespace ix
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT

Wyświetl plik

@ -0,0 +1,151 @@
/*
* IXSocketConnect.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketConnect.h"
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSocket.h"
#include "IXUniquePtr.h"
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
#ifdef ANDROID
#include <linux/in.h>
#include <linux/tcp.h>
#endif
#include "IXSelectInterruptFactory.h"
namespace ix
{
//
// This function can be cancelled every 50 ms
// This is important so that we don't block the main UI thread when shutting down a
// connection which is already trying to reconnect, and can be blocked waiting for
// ::connect to respond.
//
int SocketConnect::connectToAddress(const struct addrinfo* address,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
if (fd < 0)
{
errMsg = "Cannot create a socket";
return -1;
}
// Set the socket to non blocking mode, so that slow responses cannot
// block us for too long
SocketConnect::configure(fd);
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
if (res == -1 && !Socket::isWaitNeeded())
{
errMsg = strerror(Socket::getErrno());
Socket::closeSocket(fd);
return -1;
}
for (;;)
{
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
Socket::closeSocket(fd);
errMsg = "Cancelled";
return -1;
}
int timeoutMs = 10;
bool readyToRead = false;
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt();
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
if (pollResult == PollResultType::Timeout)
{
continue;
}
else if (pollResult == PollResultType::Error)
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
else if (pollResult == PollResultType::ReadyForWrite)
{
return fd;
}
else
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
}
}
int SocketConnect::connect(const std::string& hostname,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
//
// First do DNS resolution
//
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
auto res = dnsLookup->resolve(errMsg, isCancellationRequested);
if (res == nullptr)
{
return -1;
}
int sockfd = -1;
// iterate through the records to find a working peer
struct addrinfo* address;
for (address = res.get(); address != nullptr; address = address->ai_next)
{
//
// Second try to connect to the remote host
//
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
if (sockfd != -1)
{
break;
}
}
return sockfd;
}
// FIXME: configure is a terrible name
void SocketConnect::configure(int sockfd)
{
// 1. disable Nagle's algorithm
int flag = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
// 2. make socket non blocking
#ifdef _WIN32
unsigned long nonblocking = 1;
ioctlsocket(sockfd, FIONBIO, &nonblocking);
#else
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
#endif
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
#ifdef SO_NOSIGPIPE
int value = 1;
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value));
#endif
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,31 @@
/*
* IXSocketConnect.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXCancellationRequest.h"
#include <string>
struct addrinfo;
namespace ix
{
class SocketConnect
{
public:
static int connect(const std::string& hostname,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested);
static void configure(int sockfd);
private:
static int connectToAddress(const struct addrinfo* address,
std::string& errMsg,
const CancellationRequest& isCancellationRequested);
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,64 @@
/*
* IXSocketFactory.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketFactory.h"
#include "IXUniquePtr.h"
#ifdef IXWEBSOCKET_USE_TLS
#ifdef IXWEBSOCKET_USE_MBED_TLS
#include "IXSocketMbedTLS.h"
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
#include "IXSocketOpenSSL.h"
#elif __APPLE__
#include "IXSocketAppleSSL.h"
#endif
#else
#include "IXSocket.h"
#endif
namespace ix
{
std::unique_ptr<Socket> createSocket(bool tls,
int fd,
std::string& errorMsg,
const SocketTLSOptions& tlsOptions)
{
(void) tlsOptions;
errorMsg.clear();
std::unique_ptr<Socket> socket;
if (!tls)
{
socket = ix::make_unique<Socket>(fd);
}
else
{
#ifdef IXWEBSOCKET_USE_TLS
#if defined(IXWEBSOCKET_USE_MBED_TLS)
socket = ix::make_unique<SocketMbedTLS>(tlsOptions, fd);
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
socket = ix::make_unique<SocketOpenSSL>(tlsOptions, fd);
#elif defined(__APPLE__)
socket = ix::make_unique<SocketAppleSSL>(tlsOptions, fd);
#endif
#else
errorMsg = "TLS support is not enabled on this platform.";
return nullptr;
#endif
}
if (!socket->init(errorMsg))
{
socket.reset();
}
return socket;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,21 @@
/*
* IXSocketFactory.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocketTLSOptions.h"
#include <memory>
#include <string>
namespace ix
{
class Socket;
std::unique_ptr<Socket> createSocket(bool tls,
int fd,
std::string& errorMsg,
const SocketTLSOptions& tlsOptions);
} // namespace ix

Wyświetl plik

@ -0,0 +1,365 @@
/*
* IXSocketMbedTLS.cpp
* Author: Benjamin Sergeant, Max Weisel
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*
* Some code taken from
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
* and mini_client.c example from mbedtls
*/
#ifdef IXWEBSOCKET_USE_MBED_TLS
#include "IXSocketMbedTLS.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include <cstdint>
#include <string.h>
#ifdef _WIN32
// For manipulating the certificate store
#include <wincrypt.h>
#endif
namespace ix
{
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _tlsOptions(tlsOptions)
{
initMBedTLS();
}
SocketMbedTLS::~SocketMbedTLS()
{
SocketMbedTLS::close();
}
void SocketMbedTLS::initMBedTLS()
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_init(&_ssl);
mbedtls_ssl_config_init(&_conf);
mbedtls_ctr_drbg_init(&_ctr_drbg);
mbedtls_entropy_init(&_entropy);
mbedtls_x509_crt_init(&_cacert);
mbedtls_x509_crt_init(&_cert);
mbedtls_pk_init(&_pkey);
}
bool SocketMbedTLS::loadSystemCertificates(std::string& /* errorMsg */)
{
#ifdef _WIN32
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
if (!systemStore)
{
errorMsg = "CertOpenStore failed with ";
errorMsg += std::to_string(GetLastError());
return false;
}
PCCERT_CONTEXT certificateIterator = NULL;
int certificateCount = 0;
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
{
if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING)
{
int ret = mbedtls_x509_crt_parse(&_cacert,
certificateIterator->pbCertEncoded,
certificateIterator->cbCertEncoded);
if (ret == 0)
{
++certificateCount;
}
}
}
CertFreeCertificateContext(certificateIterator);
CertCloseStore(systemStore, 0);
if (certificateCount == 0)
{
errorMsg = "No certificates found";
return false;
}
return true;
#else
// On macOS we can query the system cert location from the keychain
// On Linux we could try to fetch some local files based on the distribution
// On Android we could use JNI to get to the system certs
return false;
#endif
}
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
{
initMBedTLS();
std::lock_guard<std::mutex> lock(_mutex);
const char* pers = "IXSocketMbedTLS";
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
mbedtls_entropy_func,
&_entropy,
(const unsigned char*) pers,
strlen(pers)) != 0)
{
errMsg = "Setting entropy seed failed";
return false;
}
if (mbedtls_ssl_config_defaults(&_conf,
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT) != 0)
{
errMsg = "Setting config default failed";
return false;
}
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
if (_tlsOptions.hasCertAndKey())
{
if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) < 0)
{
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
return false;
}
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0)
#else
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
#endif
{
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
return false;
}
if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0)
{
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
return false;
}
}
if (_tlsOptions.isPeerVerifyDisabled())
{
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
}
else
{
// FIXME: should we call mbedtls_ssl_conf_verify ?
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
if (_tlsOptions.isUsingSystemDefaults())
{
if (!loadSystemCertificates(errMsg))
{
return false;
}
}
else
{
if (_tlsOptions.isUsingInMemoryCAs())
{
const char* buffer = _tlsOptions.caFile.c_str();
size_t bufferSize =
_tlsOptions.caFile.size() + 1; // Needs to include null terminating
// character otherwise mbedtls will fail.
if (mbedtls_x509_crt_parse(
&_cacert, (const unsigned char*) buffer, bufferSize) < 0)
{
errMsg = "Cannot parse CA from memory.";
return false;
}
}
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
{
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
return false;
}
}
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
}
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
{
errMsg = "SSL setup failed";
return false;
}
if (!_tlsOptions.disable_hostname_validation)
{
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
{
errMsg = "SNI setup failed";
return false;
}
}
return true;
}
bool SocketMbedTLS::accept(std::string& errMsg)
{
bool isClient = false;
bool initialized = init(std::string(), isClient, errMsg);
if (!initialized)
{
close();
return false;
}
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
int res;
do
{
std::lock_guard<std::mutex> lock(_mutex);
res = mbedtls_ssl_handshake(&_ssl);
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
if (res != 0)
{
char buf[256];
mbedtls_strerror(res, buf, sizeof(buf));
errMsg = "error in handshake : ";
errMsg += buf;
if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
{
char verifyBuf[512];
uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl);
mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags);
errMsg += " : ";
errMsg += verifyBuf;
}
close();
return false;
}
return true;
}
bool SocketMbedTLS::connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
{
std::lock_guard<std::mutex> lock(_mutex);
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
}
bool isClient = true;
bool initialized = init(host, isClient, errMsg);
if (!initialized)
{
close();
return false;
}
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
int res;
do
{
{
std::lock_guard<std::mutex> lock(_mutex);
res = mbedtls_ssl_handshake(&_ssl);
}
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
close();
return false;
}
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
if (res != 0)
{
char buf[256];
mbedtls_strerror(res, buf, sizeof(buf));
errMsg = "error in handshake : ";
errMsg += buf;
close();
return false;
}
return true;
}
void SocketMbedTLS::close()
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_free(&_ssl);
mbedtls_ssl_config_free(&_conf);
mbedtls_ctr_drbg_free(&_ctr_drbg);
mbedtls_entropy_free(&_entropy);
mbedtls_x509_crt_free(&_cacert);
mbedtls_x509_crt_free(&_cert);
Socket::close();
}
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
if (res > 0)
{
return res;
}
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
}
}
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
{
while (true)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte);
if (res > 0)
{
return res;
}
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
}
return -1;
}
}
} // namespace ix
#endif // IXWEBSOCKET_USE_MBED_TLS

Wyświetl plik

@ -0,0 +1,60 @@
/*
* IXSocketMbedTLS.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#ifdef IXWEBSOCKET_USE_MBED_TLS
#pragma once
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/platform.h>
#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>
#include <mutex>
namespace ix
{
class SocketMbedTLS final : public Socket
{
public:
SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1);
~SocketMbedTLS();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested) final;
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
mbedtls_ssl_context _ssl;
mbedtls_ssl_config _conf;
mbedtls_entropy_context _entropy;
mbedtls_ctr_drbg_context _ctr_drbg;
mbedtls_x509_crt _cacert;
mbedtls_x509_crt _cert;
mbedtls_pk_context _pkey;
std::mutex _mutex;
SocketTLSOptions _tlsOptions;
bool init(const std::string& host, bool isClient, std::string& errMsg);
void initMBedTLS();
bool loadSystemCertificates(std::string& errMsg);
};
} // namespace ix
#endif // IXWEBSOCKET_USE_MBED_TLS

Wyświetl plik

@ -0,0 +1,861 @@
/*
* IXSocketOpenSSL.cpp
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*
* Adapted from Satori SDK OpenSSL code.
*/
#ifdef IXWEBSOCKET_USE_OPEN_SSL
#include "IXSocketOpenSSL.h"
#include "IXSocketConnect.h"
#include "IXUniquePtr.h"
#include <cassert>
#include <errno.h>
#include <vector>
#ifdef _WIN32
#include <shlwapi.h>
#else
#include <fnmatch.h>
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/x509v3.h>
#endif
#define socketerrno errno
#ifdef _WIN32
// For manipulating the certificate store
#include <wincrypt.h>
#endif
#ifdef _WIN32
namespace
{
bool loadWindowsSystemCertificates(SSL_CTX* ssl, std::string& errorMsg)
{
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
if (!systemStore)
{
errorMsg = "CertOpenStore failed with ";
errorMsg += std::to_string(GetLastError());
return false;
}
PCCERT_CONTEXT certificateIterator = NULL;
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
int certificateCount = 0;
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
{
X509* x509 = d2i_X509(NULL,
(const unsigned char**) &certificateIterator->pbCertEncoded,
certificateIterator->cbCertEncoded);
if (x509)
{
if (X509_STORE_add_cert(opensslStore, x509) == 1)
{
++certificateCount;
}
X509_free(x509);
}
}
CertFreeCertificateContext(certificateIterator);
CertCloseStore(systemStore, 0);
if (certificateCount == 0)
{
errorMsg = "No certificates found";
return false;
}
return true;
}
} // namespace
#endif
namespace ix
{
const std::string kDefaultCiphers =
"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA "
"ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 "
"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA "
"ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 "
"DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA "
"DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 AES128-SHA";
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
std::once_flag SocketOpenSSL::_openSSLInitFlag;
std::vector<std::unique_ptr<std::mutex>> openSSLMutexes;
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _ssl_connection(nullptr)
, _ssl_context(nullptr)
, _tlsOptions(tlsOptions)
{
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
}
SocketOpenSSL::~SocketOpenSSL()
{
SocketOpenSSL::close();
}
void SocketOpenSSL::openSSLInitialize()
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
#else
(void) OPENSSL_config(nullptr);
if (CRYPTO_get_locking_callback() == nullptr)
{
openSSLMutexes.clear();
for (int i = 0; i < CRYPTO_num_locks(); ++i)
{
openSSLMutexes.push_back(ix::make_unique<std::mutex>());
}
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
}
#endif
(void) OpenSSL_add_ssl_algorithms();
(void) SSL_load_error_strings();
_openSSLInitializationSuccessful = true;
}
void SocketOpenSSL::openSSLLockingCallback(int mode,
int type,
const char* /*file*/,
int /*line*/)
{
if (mode & CRYPTO_LOCK)
{
openSSLMutexes[type]->lock();
}
else
{
openSSLMutexes[type]->unlock();
}
}
std::string SocketOpenSSL::getSSLError(int ret)
{
unsigned long e;
int err = SSL_get_error(_ssl_connection, ret);
if (err == SSL_ERROR_WANT_CONNECT || err == SSL_ERROR_WANT_ACCEPT)
{
return "OpenSSL failed - connection failure";
}
else if (err == SSL_ERROR_WANT_X509_LOOKUP)
{
return "OpenSSL failed - x509 error";
}
else if (err == SSL_ERROR_SYSCALL)
{
e = ERR_get_error();
if (e > 0)
{
std::string errMsg("OpenSSL failed - ");
errMsg += ERR_error_string(e, nullptr);
return errMsg;
}
else if (e == 0 && ret == 0)
{
return "OpenSSL failed - received early EOF";
}
else
{
return "OpenSSL failed - underlying BIO reported an I/O error";
}
}
else if (err == SSL_ERROR_SSL)
{
e = ERR_get_error();
std::string errMsg("OpenSSL failed - ");
errMsg += ERR_error_string(e, nullptr);
return errMsg;
}
else if (err == SSL_ERROR_NONE)
{
return "OpenSSL failed - err none";
}
else if (err == SSL_ERROR_ZERO_RETURN)
{
return "OpenSSL failed - err zero return";
}
else
{
return "OpenSSL failed - unknown error";
}
}
SSL_CTX* SocketOpenSSL::openSSLCreateContext(std::string& errMsg)
{
const SSL_METHOD* method = SSLv23_client_method();
if (method == nullptr)
{
errMsg = "SSLv23_client_method failure";
return nullptr;
}
_ssl_method = method;
SSL_CTX* ctx = SSL_CTX_new(_ssl_method);
if (ctx)
{
SSL_CTX_set_mode(ctx,
SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
int options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE;
#ifdef SSL_OP_NO_TLSv1_3
// (partially?) work around hang in openssl 1.1.1b, by disabling TLS V1.3
// https://github.com/openssl/openssl/issues/7967
options |= SSL_OP_NO_TLSv1_3;
#endif
SSL_CTX_set_options(ctx, options);
}
return ctx;
}
bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots)
{
// Create certificate store
X509_STORE* certificate_store = SSL_CTX_get_cert_store(_ssl_context);
if (certificate_store == nullptr) return false;
// Configure to allow intermediate certs
X509_STORE_set_flags(certificate_store,
X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN);
// Create a new buffer and populate it with the roots
BIO* buffer = BIO_new_mem_buf((void*) roots.c_str(), static_cast<int>(roots.length()));
if (buffer == nullptr) return false;
// Read each root in the buffer and add to the certificate store
bool success = true;
size_t number_of_roots = 0;
while (true)
{
// Read the next root in the buffer
X509* root = PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void*) "");
if (root == nullptr)
{
// No more certs left in the buffer, we're done.
ERR_clear_error();
break;
}
// Try adding the root to the certificate store
ERR_clear_error();
if (!X509_STORE_add_cert(certificate_store, root))
{
// Failed to add. If the error is unrelated to the x509 lib or the cert already
// exists, we're safe to continue.
unsigned long error = ERR_get_error();
if (ERR_GET_LIB(error) != ERR_LIB_X509 ||
ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
{
// Failed. Clean up and bail.
success = false;
X509_free(root);
break;
}
}
// Clean up and loop
X509_free(root);
number_of_roots++;
}
// Clean up buffer
BIO_free(buffer);
// Make sure we loaded at least one certificate.
if (number_of_roots == 0) success = false;
return success;
}
/**
* Check whether a hostname matches a pattern
*/
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
{
#ifdef _WIN32
return PathMatchSpecA(host.c_str(), pattern);
#else
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
#endif
}
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
#if OPENSSL_VERSION_NUMBER < 0x10100000L
const std::string& hostname,
#else
const std::string& /* hostname */,
#endif
std::string& errMsg)
{
X509* server_cert = SSL_get_peer_certificate(ssl);
if (server_cert == nullptr)
{
errMsg = "OpenSSL failed - peer didn't present a X509 certificate.";
return false;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
// Check server name
bool hostname_verifies_ok = false;
STACK_OF(GENERAL_NAME)* san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i(
(X509*) server_cert, NID_subject_alt_name, NULL, NULL);
if (san_names)
{
for (int i = 0; i < sk_GENERAL_NAME_num(san_names); i++)
{
const GENERAL_NAME* sk_name = sk_GENERAL_NAME_value(san_names, i);
if (sk_name->type == GEN_DNS)
{
char* name = (char*) ASN1_STRING_data(sk_name->d.dNSName);
if ((size_t) ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) &&
checkHost(hostname, name))
{
hostname_verifies_ok = true;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
if (!hostname_verifies_ok)
{
int cn_pos = X509_NAME_get_index_by_NID(
X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
if (cn_pos >= 0)
{
X509_NAME_ENTRY* cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
if (cn_entry != nullptr)
{
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char* cn = (char*) ASN1_STRING_data(cn_asn1);
if ((size_t) ASN1_STRING_length(cn_asn1) == strlen(cn) &&
checkHost(hostname, cn))
{
hostname_verifies_ok = true;
}
}
}
}
if (!hostname_verifies_ok)
{
errMsg = "OpenSSL failed - certificate was issued for a different domain.";
return false;
}
#endif
X509_free(server_cert);
return true;
}
bool SocketOpenSSL::openSSLClientHandshake(const std::string& host,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
while (true)
{
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return false;
}
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
return false;
}
ERR_clear_error();
int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1)
{
if (_tlsOptions.disable_hostname_validation)
{
return true;
}
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
}
int reason = SSL_get_error(_ssl_connection, connect_result);
bool rc = false;
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
rc = true;
}
else
{
errMsg = getSSLError(connect_result);
rc = false;
}
if (!rc)
{
return false;
}
}
}
bool SocketOpenSSL::openSSLServerHandshake(std::string& errMsg)
{
while (true)
{
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return false;
}
ERR_clear_error();
int accept_result = SSL_accept(_ssl_connection);
if (accept_result == 1)
{
return true;
}
int reason = SSL_get_error(_ssl_connection, accept_result);
bool rc = false;
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
rc = true;
}
else
{
errMsg = getSSLError(accept_result);
rc = false;
}
if (!rc)
{
return false;
}
}
}
bool SocketOpenSSL::handleTLSOptions(std::string& errMsg)
{
ERR_clear_error();
if (_tlsOptions.hasCertAndKey())
{
if (SSL_CTX_use_certificate_chain_file(_ssl_context, _tlsOptions.certFile.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
_tlsOptions.certFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (SSL_CTX_use_PrivateKey_file(
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" + _tlsOptions.keyFile +
"\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (!SSL_CTX_check_private_key(_ssl_context))
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - cert/key mismatch(\"" + _tlsOptions.certFile + ", " +
_tlsOptions.keyFile + "\")";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
ERR_clear_error();
if (!_tlsOptions.isPeerVerifyDisabled())
{
if (_tlsOptions.isUsingSystemDefaults())
{
#ifdef _WIN32
if (!loadWindowsSystemCertificates(_ssl_context, errMsg))
{
return false;
}
#else
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
#endif
}
else
{
if (_tlsOptions.isUsingInMemoryCAs())
{
// Load from memory
openSSLAddCARootsFromString(_tlsOptions.caFile);
}
else
{
if (SSL_CTX_load_verify_locations(
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
}
}
SSL_CTX_set_verify(_ssl_context,
SSL_VERIFY_PEER,
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
SSL_CTX_set_verify_depth(_ssl_context, 4);
}
else
{
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
}
if (_tlsOptions.isUsingDefaultCiphers())
{
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + kDefaultCiphers +
"\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
}
else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + _tlsOptions.ciphers +
"\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
return true;
}
bool SocketOpenSSL::accept(std::string& errMsg)
{
bool handshakeSuccessful = false;
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_openSSLInitializationSuccessful)
{
errMsg = "OPENSSL_init_ssl failure";
return false;
}
if (_sockfd == -1)
{
return false;
}
{
const SSL_METHOD* method = SSLv23_server_method();
if (method == nullptr)
{
errMsg = "SSLv23_server_method failure";
_ssl_context = nullptr;
}
else
{
_ssl_method = method;
_ssl_context = SSL_CTX_new(_ssl_method);
if (_ssl_context)
{
SSL_CTX_set_mode(_ssl_context, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_mode(_ssl_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(_ssl_context,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
}
}
}
if (_ssl_context == nullptr)
{
return false;
}
ERR_clear_error();
if (_tlsOptions.hasCertAndKey())
{
if (SSL_CTX_use_certificate_chain_file(_ssl_context,
_tlsOptions.certFile.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
_tlsOptions.certFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (SSL_CTX_use_PrivateKey_file(
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" +
_tlsOptions.keyFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
ERR_clear_error();
if (!_tlsOptions.isPeerVerifyDisabled())
{
if (_tlsOptions.isUsingSystemDefaults())
{
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
else
{
if (_tlsOptions.isUsingInMemoryCAs())
{
// Load from memory
openSSLAddCARootsFromString(_tlsOptions.caFile);
}
else
{
const char* root_ca_file = _tlsOptions.caFile.c_str();
STACK_OF(X509_NAME) * rootCAs;
rootCAs = SSL_load_client_CA_file(root_ca_file);
if (rootCAs == NULL)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" +
_tlsOptions.caFile + "') failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else
{
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
if (SSL_CTX_load_verify_locations(
_ssl_context, root_ca_file, nullptr) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
}
}
SSL_CTX_set_verify(
_ssl_context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
SSL_CTX_set_verify_depth(_ssl_context, 4);
}
else
{
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
}
if (_tlsOptions.isUsingDefaultCiphers())
{
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1)
{
return false;
}
}
else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1)
{
return false;
}
_ssl_connection = SSL_new(_ssl_context);
if (_ssl_connection == nullptr)
{
errMsg = "OpenSSL failed to connect";
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
return false;
}
SSL_set_ecdh_auto(_ssl_connection, 1);
SSL_set_fd(_ssl_connection, _sockfd);
handshakeSuccessful = openSSLServerHandshake(errMsg);
}
if (!handshakeSuccessful)
{
close();
return false;
}
return true;
}
bool SocketOpenSSL::connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
bool handshakeSuccessful = false;
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_openSSLInitializationSuccessful)
{
errMsg = "OPENSSL_init_ssl failure";
return false;
}
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
_ssl_context = openSSLCreateContext(errMsg);
if (_ssl_context == nullptr)
{
return false;
}
if (!handleTLSOptions(errMsg))
{
return false;
}
_ssl_connection = SSL_new(_ssl_context);
if (_ssl_connection == nullptr)
{
errMsg = "OpenSSL failed to connect";
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
return false;
}
SSL_set_fd(_ssl_connection, _sockfd);
// SNI support
SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// Support for server name verification
// (The docs say that this should work from 1.0.2, and is the default from
// 1.1.0, but it does not. To be on the safe side, the manual test
// below is enabled for all versions prior to 1.1.0.)
if (!_tlsOptions.disable_hostname_validation)
{
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
}
#endif
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
}
if (!handshakeSuccessful)
{
close();
return false;
}
return true;
}
void SocketOpenSSL::close()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection != nullptr)
{
SSL_free(_ssl_connection);
_ssl_connection = nullptr;
}
if (_ssl_context != nullptr)
{
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
}
Socket::close();
}
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return 0;
}
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE)
{
return write_result;
}
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
}
}
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
{
while (true)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return 0;
}
ERR_clear_error();
ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
if (read_result > 0)
{
return read_result;
}
int reason = SSL_get_error(_ssl_connection, (int) read_result);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK;
}
return -1;
}
}
} // namespace ix
#endif // IXWEBSOCKET_USE_OPEN_SSL

Wyświetl plik

@ -0,0 +1,68 @@
/*
* IXSocketOpenSSL.h
* Author: Benjamin Sergeant, Matt DeBoer
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#ifdef IXWEBSOCKET_USE_OPEN_SSL
#pragma once
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include <mutex>
#include <openssl/bio.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/ssl.h>
namespace ix
{
class SocketOpenSSL final : public Socket
{
public:
SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
~SocketOpenSSL();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested) final;
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
void openSSLInitialize();
std::string getSSLError(int ret);
SSL_CTX* openSSLCreateContext(std::string& errMsg);
bool openSSLAddCARootsFromString(const std::string roots);
bool openSSLClientHandshake(const std::string& hostname,
std::string& errMsg,
const CancellationRequest& isCancellationRequested);
bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg);
bool checkHost(const std::string& host, const char* pattern);
bool handleTLSOptions(std::string& errMsg);
bool openSSLServerHandshake(std::string& errMsg);
// Required for OpenSSL < 1.1
static void openSSLLockingCallback(int mode, int type, const char* /*file*/, int /*line*/);
SSL* _ssl_connection;
SSL_CTX* _ssl_context;
const SSL_METHOD* _ssl_method;
SocketTLSOptions _tlsOptions;
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
static std::once_flag _openSSLInitFlag;
static std::atomic<bool> _openSSLInitializationSuccessful;
};
} // namespace ix
#endif // IXWEBSOCKET_USE_OPEN_SSL

Wyświetl plik

@ -0,0 +1,506 @@
/*
* IXSocketServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketServer.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptFactory.h"
#include "IXSetThreadName.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include "IXSocketFactory.h"
#include <assert.h>
#include <sstream>
#include <stdio.h>
#include <string.h>
namespace ix
{
const int SocketServer::kDefaultPort(8080);
const std::string SocketServer::kDefaultHost("127.0.0.1");
const int SocketServer::kDefaultTcpBacklog(5);
const size_t SocketServer::kDefaultMaxConnections(128);
const int SocketServer::kDefaultAddressFamily(AF_INET);
SocketServer::SocketServer(
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
: _port(port)
, _host(host)
, _backlog(backlog)
, _maxConnections(maxConnections)
, _addressFamily(addressFamily)
, _serverFd(-1)
, _stop(false)
, _stopGc(false)
, _connectionStateFactory(&ConnectionState::createConnectionState)
, _acceptSelectInterrupt(createSelectInterrupt())
{
}
SocketServer::~SocketServer()
{
stop();
}
void SocketServer::logError(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
fprintf(stderr, "%s\n", str.c_str());
}
void SocketServer::logInfo(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
fprintf(stdout, "%s\n", str.c_str());
}
std::pair<bool, std::string> SocketServer::listen()
{
std::string acceptSelectInterruptInitErrorMsg;
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg))
{
std::stringstream ss;
ss << "SocketServer::listen() error in SelectInterrupt::init: "
<< acceptSelectInterruptInitErrorMsg;
return std::make_pair(false, ss.str());
}
if (_addressFamily != AF_INET && _addressFamily != AF_INET6)
{
std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently "
"the only supported address families");
return std::make_pair(false, errMsg);
}
// Get a socket for accepting connections.
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno());
return std::make_pair(false, ss.str());
}
// Make that socket reusable. (allow restarting this server at will)
int enable = 1;
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
if (_addressFamily == AF_INET)
{
struct sockaddr_in server;
server.sin_family = _addressFamily;
server.sin_port = htons(_port);
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
}
else // AF_INET6
{
struct sockaddr_in6 server;
server.sin6_family = _addressFamily;
server.sin6_port = htons(_port);
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
}
//
// Listen for connections. Specify the tcp backlog.
//
if (::listen(_serverFd, _backlog) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling listen "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
return std::make_pair(true, "");
}
void SocketServer::start()
{
_stop = false;
if (!_thread.joinable())
{
_thread = std::thread(&SocketServer::run, this);
}
if (!_gcThread.joinable())
{
_gcThread = std::thread(&SocketServer::runGC, this);
}
}
void SocketServer::wait()
{
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
_conditionVariable.wait(lock);
}
void SocketServer::stopAcceptingConnections()
{
_stop = true;
}
void SocketServer::stop()
{
// Stop accepting connections, and close the 'accept' thread
if (_thread.joinable())
{
_stop = true;
// Wake up select
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest))
{
logError("SocketServer::stop: Cannot wake up from select");
}
_thread.join();
_stop = false;
}
// Join all threads and make sure that all connections are terminated
if (_gcThread.joinable())
{
_stopGc = true;
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one();
_gcThread.join();
_stopGc = false;
}
_conditionVariable.notify_one();
Socket::closeSocket(_serverFd);
}
void SocketServer::setConnectionStateFactory(
const ConnectionStateFactory& connectionStateFactory)
{
_connectionStateFactory = connectionStateFactory;
}
//
// join the threads for connections that have been closed
//
// When a connection is closed by a client, the connection state terminated
// field becomes true, and we can use that to know that we can join that thread
// and remove it from our _connectionsThreads data structure (a list).
//
void SocketServer::closeTerminatedThreads()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
auto it = _connectionsThreads.begin();
auto itEnd = _connectionsThreads.end();
while (it != itEnd)
{
auto& connectionState = it->first;
auto& thread = it->second;
if (!connectionState->isTerminated())
{
++it;
continue;
}
if (thread.joinable()) thread.join();
it = _connectionsThreads.erase(it);
}
}
void SocketServer::run()
{
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:ac:" + std::to_string(_port));
for (;;)
{
if (_stop) return;
// Use poll to check whether a new connection is in progress
int timeoutMs = -1;
#ifdef _WIN32
// select cannot be interrupted on Windows so we need to pass a small timeout
timeoutMs = 10;
#endif
bool readyToRead = true;
PollResultType pollResult =
Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt);
if (pollResult == PollResultType::Error)
{
std::stringstream ss;
ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno());
logError(ss.str());
continue;
}
if (pollResult != PollResultType::ReadyForRead)
{
continue;
}
// Accept a connection.
// FIXME: Is this working for ipv6 ?
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(client);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr*) &client, &addressLen)) < 0)
{
if (!Socket::isWaitNeeded())
{
// FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error accepting connection: " << err << ", "
<< strerror(err);
logError(ss.str());
}
continue;
}
if (getConnectedClientsCount() >= _maxConnections)
{
std::stringstream ss;
ss << "SocketServer::run() reached max connections = " << _maxConnections << ". "
<< "Not accepting connection";
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
// Retrieve connection info, the ip address of the remote peer/client)
std::string remoteIp;
int remotePort;
if (_addressFamily == AF_INET)
{
char remoteIp4[INET_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr)
{
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp4;
}
else // AF_INET6
{
char remoteIp6[INET6_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) ==
nullptr)
{
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp6;
}
std::shared_ptr<ConnectionState> connectionState;
if (_connectionStateFactory)
{
connectionState = _connectionStateFactory();
}
connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); });
connectionState->setRemoteIp(remoteIp);
connectionState->setRemotePort(remotePort);
if (_stop) return;
// create socket
std::string errorMsg;
bool tls = _socketTLSOptions.tls;
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
if (socket == nullptr)
{
logError("SocketServer::run() cannot create socket: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
}
// Set the socket to non blocking mode + other tweaks
SocketConnect::configure(clientFd);
if (!socket->accept(errorMsg))
{
logError("SocketServer::run() tls accept failed: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
}
// Launch the handleConnection work asynchronously in its own thread.
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
_connectionsThreads.push_back(std::make_pair(
connectionState,
std::thread(
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
}
}
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
void SocketServer::runGC()
{
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:gc:" + std::to_string(_port));
for (;;)
{
// Garbage collection to shutdown/join threads for closed connections.
closeTerminatedThreads();
// We quit this thread if all connections are closed and we received
// a stop request by setting _stopGc to true.
if (_stopGc && getConnectionsThreadsCount() == 0)
{
break;
}
// Unless we are stopping the server, wait for a connection
// to be terminated to run the threads GC, instead of busy waiting
// with a sleep
if (!_stopGc)
{
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
if(!_canContinueGC) {
_conditionVariableGC.wait(lock, [this]{ return _canContinueGC; });
}
_canContinueGC = false;
}
}
}
void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
_socketTLSOptions = socketTLSOptions;
}
void SocketServer::onSetTerminatedCallback()
{
// a connection got terminated, we can run the connection thread GC,
// so wake up the thread responsible for that
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one();
}
int SocketServer::getPort()
{
return _port;
}
std::string SocketServer::getHost()
{
return _host;
}
int SocketServer::getBacklog()
{
return _backlog;
}
std::size_t SocketServer::getMaxConnections()
{
return _maxConnections;
}
int SocketServer::getAddressFamily()
{
return _addressFamily;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,131 @@
/*
* IXSocketServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXConnectionState.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSocketTLSOptions.h"
#include <atomic>
#include <condition_variable>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class Socket;
class SocketServer
{
public:
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
// Each connection is handled by its own worker thread.
// We use a list as we only care about remove and append operations.
using ConnectionThreads =
std::list<std::pair<std::shared_ptr<ConnectionState>, std::thread>>;
SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~SocketServer();
virtual void stop();
// It is possible to override ConnectionState through inheritance
// this method allows user to change the factory by returning an object
// that inherits from ConnectionState but has its own methods.
void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
const static int kDefaultPort;
const static std::string kDefaultHost;
const static int kDefaultTcpBacklog;
const static size_t kDefaultMaxConnections;
const static int kDefaultAddressFamily;
void start();
std::pair<bool, std::string> listen();
void wait();
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
int getPort();
std::string getHost();
int getBacklog();
std::size_t getMaxConnections();
int getAddressFamily();
protected:
// Logging
void logError(const std::string& str);
void logInfo(const std::string& str);
void stopAcceptingConnections();
private:
// Member variables
int _port;
std::string _host;
int _backlog;
size_t _maxConnections;
int _addressFamily;
// socket for accepting connections
socket_t _serverFd;
std::atomic<bool> _stop;
std::mutex _logMutex;
// background thread to wait for incoming connections
std::thread _thread;
void run();
void onSetTerminatedCallback();
// background thread to cleanup (join) terminated threads
std::atomic<bool> _stopGc;
std::thread _gcThread;
void runGC();
// the list of (connectionState, threads) for each connections
ConnectionThreads _connectionsThreads;
std::mutex _connectionsThreadsMutex;
// used to have the main control thread for a server
// wait for a 'terminate' notification without busy polling
std::condition_variable _conditionVariable;
std::mutex _conditionVariableMutex;
// the factory to create ConnectionState objects
ConnectionStateFactory _connectionStateFactory;
virtual void handleConnection(std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined
void closeTerminatedThreads();
size_t getConnectionsThreadsCount();
SocketTLSOptions _socketTLSOptions;
// to wake up from select
SelectInterruptPtr _acceptSelectInterrupt;
// used by the gc thread, to know that a thread needs to be garbage collected
// as a connection
std::condition_variable _conditionVariableGC;
std::mutex _conditionVariableMutexGC;
bool _canContinueGC{ false };
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,93 @@
/*
* IXSocketTLSOptions.h
* Author: Matt DeBoer
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketTLSOptions.h"
#include <assert.h>
#include <fstream>
#include <sstream>
namespace ix
{
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
const char* kTLSCAFileDisableVerify = "NONE";
const char* kTLSCiphersUseDefault = "DEFAULT";
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
bool SocketTLSOptions::isValid() const
{
if (!_validated)
{
if (!certFile.empty() && !std::ifstream(certFile))
{
_errMsg = "certFile not found: " + certFile;
return false;
}
if (!keyFile.empty() && !std::ifstream(keyFile))
{
_errMsg = "keyFile not found: " + keyFile;
return false;
}
if (!caFile.empty() && caFile != kTLSCAFileDisableVerify &&
caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile))
{
_errMsg = "caFile not found: " + caFile;
return false;
}
if (certFile.empty() != keyFile.empty())
{
_errMsg = "certFile and keyFile must be both present, or both absent";
return false;
}
_validated = true;
}
return true;
}
bool SocketTLSOptions::hasCertAndKey() const
{
return !certFile.empty() && !keyFile.empty();
}
bool SocketTLSOptions::isUsingSystemDefaults() const
{
return caFile == kTLSCAFileUseSystemDefaults;
}
bool SocketTLSOptions::isUsingInMemoryCAs() const
{
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
}
bool SocketTLSOptions::isPeerVerifyDisabled() const
{
return caFile == kTLSCAFileDisableVerify;
}
bool SocketTLSOptions::isUsingDefaultCiphers() const
{
return ciphers.empty() || ciphers == kTLSCiphersUseDefault;
}
const std::string& SocketTLSOptions::getErrorMsg() const
{
return _errMsg;
}
std::string SocketTLSOptions::getDescription() const
{
std::stringstream ss;
ss << "TLS Options:" << std::endl;
ss << " certFile = " << certFile << std::endl;
ss << " keyFile = " << keyFile << std::endl;
ss << " caFile = " << caFile << std::endl;
ss << " ciphers = " << ciphers << std::endl;
ss << " tls = " << tls << std::endl;
return ss.str();
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,57 @@
/*
* IXSocketTLSOptions.h
* Author: Matt DeBoer
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
struct SocketTLSOptions
{
public:
// check validity of the object
bool isValid() const;
// the certificate presented to peers
std::string certFile;
// the key used for signing/encryption
std::string keyFile;
// the ca certificate (or certificate bundle) file containing
// certificates to be trusted by peers; use 'SYSTEM' to
// leverage the system defaults, use 'NONE' to disable peer verification
std::string caFile = "SYSTEM";
// list of ciphers (rsa, etc...)
std::string ciphers = "DEFAULT";
// whether tls is enabled, used for server code
bool tls = false;
// whether to skip validating the peer's hostname against the certificate presented
bool disable_hostname_validation = false;
bool hasCertAndKey() const;
bool isUsingSystemDefaults() const;
bool isUsingInMemoryCAs() const;
bool isPeerVerifyDisabled() const;
bool isUsingDefaultCiphers() const;
const std::string& getErrorMsg() const;
std::string getDescription() const;
private:
mutable std::string _errMsg;
mutable bool _validated = false;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,37 @@
/*
* IXStrCaseCompare.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone. All rights reserved.
*/
#include "IXStrCaseCompare.h"
#include <algorithm>
#include <locale>
namespace ix
{
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
const unsigned char& c2) const
{
#if defined(_WIN32) && !defined(__GNUC__)
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
#else
return std::tolower(c1) < std::tolower(c2);
#endif
}
bool CaseInsensitiveLess::cmp(const std::string& s1, const std::string& s2)
{
return std::lexicographical_compare(s1.begin(),
s1.end(), // source range
s2.begin(),
s2.end(), // dest range
NocaseCompare()); // comparison
}
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
{
return CaseInsensitiveLess::cmp(s1, s2);
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,25 @@
/*
* IXStrCaseCompare.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
struct CaseInsensitiveLess
{
// Case Insensitive compare_less binary function
struct NocaseCompare
{
bool operator()(const unsigned char& c1, const unsigned char& c2) const;
};
static bool cmp(const std::string& s1, const std::string& s2);
bool operator()(const std::string& s1, const std::string& s2) const;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,126 @@
/*
* IXUdpSocket.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXUdpSocket.h"
#include "IXNetSystem.h"
#include <cstring>
#include <sstream>
namespace ix
{
UdpSocket::UdpSocket(int fd)
: _sockfd(fd)
{
;
}
UdpSocket::~UdpSocket()
{
close();
}
void UdpSocket::close()
{
if (_sockfd == -1) return;
closeSocket(_sockfd);
_sockfd = -1;
}
int UdpSocket::getErrno()
{
int err;
#ifdef _WIN32
err = WSAGetLastError();
#else
err = errno;
#endif
return err;
}
bool UdpSocket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
}
void UdpSocket::closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
bool UdpSocket::init(const std::string& host, int port, std::string& errMsg)
{
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sockfd < 0)
{
errMsg = "Could not create socket";
return false;
}
#ifdef _WIN32
unsigned long nonblocking = 1;
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
#else
fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
#endif
memset(&_server, 0, sizeof(_server));
_server.sin_family = AF_INET;
_server.sin_port = htons(port);
// DNS resolution.
struct addrinfo hints, *result = nullptr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
int ret = getaddrinfo(host.c_str(), nullptr, &hints, &result);
if (ret != 0)
{
errMsg = strerror(UdpSocket::getErrno());
freeaddrinfo(result);
close();
return false;
}
struct sockaddr_in* host_addr = (struct sockaddr_in*) result->ai_addr;
memcpy(&_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr));
freeaddrinfo(result);
return true;
}
ssize_t UdpSocket::sendto(const std::string& buffer)
{
return (ssize_t)::sendto(
_sockfd, buffer.data(), buffer.size(), 0, (struct sockaddr*) &_server, sizeof(_server));
}
ssize_t UdpSocket::recvfrom(char* buffer, size_t length)
{
#ifdef _WIN32
int addressLen = (int) sizeof(_server);
#else
socklen_t addressLen = (socklen_t) sizeof(_server);
#endif
return (ssize_t)::recvfrom(
_sockfd, buffer, length, 0, (struct sockaddr*) &_server, &addressLen);
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,45 @@
/*
* IXUdpSocket.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <memory>
#include <string>
#ifdef _WIN32
#include <basetsd.h>
#ifdef _MSC_VER
typedef SSIZE_T ssize_t;
#endif
#endif
#include "IXNetSystem.h"
namespace ix
{
class UdpSocket
{
public:
UdpSocket(int fd = -1);
~UdpSocket();
// Virtual methods
bool init(const std::string& host, int port, std::string& errMsg);
ssize_t sendto(const std::string& buffer);
ssize_t recvfrom(char* buffer, size_t length);
void close();
static int getErrno();
static bool isWaitNeeded();
static void closeSocket(int fd);
private:
std::atomic<int> _sockfd;
struct sockaddr_in _server;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,18 @@
/*
* IXUniquePtr.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
namespace ix
{
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,409 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*
* IXUrlParser.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXUrlParser.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
namespace
{
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
LUrlParserError_InvalidSchemeName = 3,
LUrlParserError_NoDoubleSlash = 4,
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
std::string m_Port;
std::string m_Path;
std::string m_Query;
std::string m_Fragment;
std::string m_UserName;
std::string m_Password;
clParseURL()
: m_ErrorCode(LUrlParserError_Uninitialized)
{
}
/// return 'true' if the parsing was successful
bool IsValid() const
{
return m_ErrorCode == LUrlParserError_Ok;
}
/// helper to convert the port number to int, return 'true' if the port is valid (within the
/// 0..65535 range)
bool GetPort(int* OutPort) const;
/// parse the URL
static clParseURL ParseURL(const std::string& URL);
private:
explicit clParseURL(LUrlParserError ErrorCode)
: m_ErrorCode(ErrorCode)
{
}
};
static bool IsSchemeValid(const std::string& SchemeName)
{
for (auto c : SchemeName)
{
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
}
return true;
}
bool clParseURL::GetPort(int* OutPort) const
{
if (!IsValid())
{
return false;
}
int Port = atoi(m_Port.c_str());
if (Port <= 0 || Port > 65535)
{
return false;
}
if (OutPort)
{
*OutPort = Port;
}
return true;
}
// based on RFC 1738 and RFC 3986
clParseURL clParseURL::ParseURL(const std::string& URL)
{
clParseURL Result;
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as
*equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr(CurrentString, ':');
if (!LocalString)
{
return clParseURL(LUrlParserError_NoUrlCharacter);
}
// save the scheme name
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
if (!IsSchemeValid(Result.m_Scheme))
{
return clParseURL(LUrlParserError_InvalidSchemeName);
}
// scheme should be lowercase
std::transform(
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
// skip ':'
CurrentString = LocalString + 1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
while (*LocalString)
{
if (*LocalString == '@')
{
// user name and password are specified
bHasUserName = true;
break;
}
else if (*LocalString == '/')
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
if (bHasUserName)
{
// read user name
while (*LocalString && *LocalString != ':' && *LocalString != '@')
LocalString++;
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
// proceed with the current pointer
CurrentString = LocalString;
if (*CurrentString == ':')
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
while (*LocalString && *LocalString != '@')
LocalString++;
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
}
// skip '@'
if (*CurrentString != '@')
{
return clParseURL(LUrlParserError_NoAtSign);
}
CurrentString++;
}
bool bHasBracket = (*CurrentString == '[');
// go ahead, read the host name
LocalString = CurrentString;
while (*LocalString)
{
if (bHasBracket && *LocalString == ']')
{
// end of IPv6 address
LocalString++;
break;
}
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
{
// port number is specified
break;
}
LocalString++;
}
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
// is port number specified?
if (*CurrentString == ':')
{
CurrentString++;
// read port number
LocalString = CurrentString;
while (*LocalString && *LocalString != '/')
LocalString++;
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
}
// end of string
if (!*CurrentString)
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if (*CurrentString != '/')
{
return clParseURL(LUrlParserError_NoSlash);
}
CurrentString++;
// parse the path
LocalString = CurrentString;
while (*LocalString && *LocalString != '#' && *LocalString != '?')
LocalString++;
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
// check for query
if (*CurrentString == '?')
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
while (*LocalString && *LocalString != '#')
LocalString++;
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
}
// check for fragment
if (*CurrentString == '#')
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
while (*LocalString)
LocalString++;
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
}
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
int getProtocolPort(const std::string& protocol)
{
if (protocol == "ws" || protocol == "http")
{
return 80;
}
else if (protocol == "wss" || protocol == "https")
{
return 443;
}
return -1;
}
} // namespace
namespace ix
{
bool UrlParser::parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port)
{
bool isProtocolDefaultPort;
return parse(url, protocol, host, path, query, port, isProtocolDefaultPort);
}
bool UrlParser::parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool& isProtocolDefaultPort)
{
clParseURL res = clParseURL::ParseURL(url);
if (!res.IsValid())
{
return false;
}
protocol = res.m_Scheme;
host = res.m_Host;
path = res.m_Path;
query = res.m_Query;
const auto protocolPort = getProtocolPort(protocol);
if (!res.GetPort(&port))
{
port = protocolPort;
}
isProtocolDefaultPort = port == protocolPort;
if (path.empty())
{
path = "/";
}
else if (path[0] != '/')
{
path = '/' + path;
}
if (!query.empty())
{
path += "?";
path += query;
}
return true;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,31 @@
/*
* IXUrlParser.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
class UrlParser
{
public:
static bool parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port);
static bool parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool& isProtocolDefaultPort);
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,89 @@
/*
* IXUserAgent.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXUserAgent.h"
#include "IXWebSocketVersion.h"
#include <sstream>
#ifdef IXWEBSOCKET_USE_ZLIB
#include <zlib.h>
#endif
// Platform name
#if defined(_WIN32)
#define PLATFORM_NAME "windows" // Windows
#elif defined(_WIN64)
#define PLATFORM_NAME "windows" // Windows
#elif defined(__CYGWIN__) && !defined(_WIN32)
#define PLATFORM_NAME "windows" // Windows (Cygwin POSIX under Microsoft Window)
#elif defined(__ANDROID__)
#define PLATFORM_NAME "android" // Android (implies Linux, so it must come first)
#elif defined(__linux__)
#define PLATFORM_NAME "linux" // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and other
#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__)
#include <sys/param.h>
#if defined(BSD)
#define PLATFORM_NAME "bsd" // FreeBSD, NetBSD, OpenBSD, DragonFly BSD
#endif
#elif defined(__hpux)
#define PLATFORM_NAME "hp-ux" // HP-UX
#elif defined(_AIX)
#define PLATFORM_NAME "aix" // IBM AIX
#elif defined(__APPLE__) && defined(__MACH__) // Apple OSX and iOS (Darwin)
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
#define PLATFORM_NAME "ios" // Apple iOS
#elif TARGET_OS_IPHONE == 1
#define PLATFORM_NAME "ios" // Apple iOS
#elif TARGET_OS_MAC == 1
#define PLATFORM_NAME "macos" // Apple OSX
#endif
#elif defined(__sun) && defined(__SVR4)
#define PLATFORM_NAME "solaris" // Oracle Solaris, Open Indiana
#else
#define PLATFORM_NAME "unknown platform"
#endif
// SSL
#ifdef IXWEBSOCKET_USE_MBED_TLS
#include <mbedtls/version.h>
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
#include <openssl/opensslv.h>
#endif
namespace ix
{
std::string userAgent()
{
std::stringstream ss;
// IXWebSocket Version
ss << "ixwebsocket/" << IX_WEBSOCKET_VERSION;
// Platform
ss << " " << PLATFORM_NAME;
// TLS
#ifdef IXWEBSOCKET_USE_TLS
#ifdef IXWEBSOCKET_USE_MBED_TLS
ss << " ssl/mbedtls " << MBEDTLS_VERSION_STRING;
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
ss << " ssl/OpenSSL " << OPENSSL_VERSION_TEXT;
#elif __APPLE__
ss << " ssl/SecureTransport";
#endif
#else
ss << " nossl";
#endif
#ifdef IXWEBSOCKET_USE_ZLIB
// Zlib version
ss << " zlib " << ZLIB_VERSION;
#endif
return ss.str();
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,14 @@
/*
* IXUserAgent.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
std::string userAgent();
} // namespace ix

Wyświetl plik

@ -0,0 +1,178 @@
/*
* The following code is adapted from code originally written by Bjoern
* Hoehrmann <bjoern@hoehrmann.de>. See
* http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
*
* The original license:
*
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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.
*/
/*
* IXUtf8Validator.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*
* From websocketpp. Tiny modifications made for code style, function names etc...
*/
#pragma once
#include <cstdint>
#include <string>
namespace ix
{
/// State that represents a valid utf8 input sequence
static unsigned int const utf8_accept = 0;
/// State that represents an invalid utf8 input sequence
static unsigned int const utf8_reject = 1;
/// Lookup table for the UTF8 decode state machine
static uint8_t const utf8d[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1,
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
};
/// Decode the next byte of a UTF8 sequence
/**
* @param [out] state The decoder state to advance
* @param [out] codep The codepoint to fill in
* @param [in] byte The byte to input
* @return The ending state of the decode operation
*/
inline uint32_t decodeNextByte(uint32_t* state, uint32_t* codep, uint8_t byte)
{
uint32_t type = utf8d[byte];
*codep = (*state != utf8_accept) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte);
*state = utf8d[256 + *state * 16 + type];
return *state;
}
/// Provides streaming UTF8 validation functionality
class Utf8Validator
{
public:
/// Construct and initialize the validator
Utf8Validator()
: m_state(utf8_accept)
, m_codepoint(0)
{
}
/// Advance the state of the validator with the next input byte
/**
* @param byte The byte to advance the validation state with
* @return Whether or not the byte resulted in a validation error.
*/
bool consume(uint8_t byte)
{
if (decodeNextByte(&m_state, &m_codepoint, byte) == utf8_reject)
{
return false;
}
return true;
}
/// Advance Validator state with input from an iterator pair
/**
* @param begin Input iterator to the start of the input range
* @param end Input iterator to the end of the input range
* @return Whether or not decoding the bytes resulted in a validation error.
*/
template<typename iterator_type>
bool decode(iterator_type begin, iterator_type end)
{
for (iterator_type it = begin; it != end; ++it)
{
unsigned int result =
decodeNextByte(&m_state, &m_codepoint, static_cast<uint8_t>(*it));
if (result == utf8_reject)
{
return false;
}
}
return true;
}
/// Return whether the input sequence ended on a valid utf8 codepoint
/**
* @return Whether or not the input sequence ended on a valid codepoint.
*/
bool complete()
{
return m_state == utf8_accept;
}
/// Reset the Validator to decode another message
void reset()
{
m_state = utf8_accept;
m_codepoint = 0;
}
private:
uint32_t m_state;
uint32_t m_codepoint;
};
/// Validate a UTF8 string
/**
* convenience function that creates a Validator, validates a complete string
* and returns the result.
*/
inline bool validateUtf8(std::string const& s)
{
Utf8Validator v;
if (!v.decode(s.begin(), s.end()))
{
return false;
}
return v.complete();
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,76 @@
/*
* IXUuid.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
/**
* Generate a random uuid similar to the uuid python module
*
* >>> import uuid
* >>> uuid.uuid4().hex
* 'bec08155b37d4050a1f3c3fa0276bf12'
*
* Code adapted from https://github.com/r-lyeh-archived/sole
*/
#include "IXUuid.h"
#include <cstdint>
#include <iomanip>
#include <random>
#include <sstream>
#include <string>
namespace ix
{
class Uuid
{
public:
Uuid();
std::string toString() const;
private:
uint64_t _ab;
uint64_t _cd;
};
Uuid::Uuid()
{
static std::random_device rd;
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
_ab = dist(rd);
_cd = dist(rd);
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
}
std::string Uuid::toString() const
{
std::stringstream ss;
ss << std::hex << std::nouppercase << std::setfill('0');
uint32_t a = (_ab >> 32);
uint32_t b = (_ab & 0xFFFFFFFF);
uint32_t c = (_cd >> 32);
uint32_t d = (_cd & 0xFFFFFFFF);
ss << std::setw(8) << (a);
ss << std::setw(4) << (b >> 16);
ss << std::setw(4) << (b & 0xFFFF);
ss << std::setw(4) << (c >> 16);
ss << std::setw(4) << (c & 0xFFFF);
ss << std::setw(8) << d;
return ss.str();
}
std::string uuid4()
{
Uuid id;
return id.toString();
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,17 @@
/*
* IXUuid.h
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
/**
* Generate a random uuid
*/
std::string uuid4();
} // namespace ix

Wyświetl plik

@ -0,0 +1,630 @@
/*
* IXWebSocket.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocket.h"
#include "IXExponentialBackoff.h"
#include "IXSetThreadName.h"
#include "IXUniquePtr.h"
#include "IXUtf8Validator.h"
#include "IXWebSocketHandshake.h"
#include <cassert>
#include <cmath>
#include <cstdint>
namespace
{
const std::string emptyMsg;
} // namespace
namespace ix
{
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
const int WebSocket::kDefaultPingIntervalSecs(-1);
const bool WebSocket::kDefaultEnablePong(true);
const uint32_t WebSocket::kDefaultMaxWaitBetweenReconnectionRetries(10 * 1000); // 10s
const uint32_t WebSocket::kDefaultMinWaitBetweenReconnectionRetries(1); // 1 ms
WebSocket::WebSocket()
: _onMessageCallback(OnMessageCallback())
, _stop(false)
, _automaticReconnection(true)
, _maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries)
, _minWaitBetweenReconnectionRetries(kDefaultMinWaitBetweenReconnectionRetries)
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
, _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pingType(SendMessageKind::Ping)
{
_ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
{
_onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
emptyMsg,
wireSize,
WebSocketErrorInfo(),
WebSocketOpenInfo(),
WebSocketCloseInfo(code, reason, remote)));
});
}
WebSocket::~WebSocket()
{
stop();
_ws.setOnCloseCallback(nullptr);
}
void WebSocket::setUrl(const std::string& url)
{
std::lock_guard<std::mutex> lock(_configMutex);
_url = url;
}
void WebSocket::setHandshakeTimeout(int handshakeTimeoutSecs)
{
_handshakeTimeoutSecs = handshakeTimeoutSecs;
}
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
{
std::lock_guard<std::mutex> lock(_configMutex);
_extraHeaders = headers;
}
const std::string WebSocket::getUrl() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _url;
}
void WebSocket::setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{
std::lock_guard<std::mutex> lock(_configMutex);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
void WebSocket::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
std::lock_guard<std::mutex> lock(_configMutex);
_socketTLSOptions = socketTLSOptions;
}
const WebSocketPerMessageDeflateOptions WebSocket::getPerMessageDeflateOptions() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _perMessageDeflateOptions;
}
void WebSocket::setPingMessage(const std::string& sendMessage, SendMessageKind pingType)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingMessage = sendMessage;
_ws.setPingMessage(_pingMessage, pingType);
}
const std::string WebSocket::getPingMessage() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingMessage;
}
void WebSocket::setPingInterval(int pingIntervalSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = pingIntervalSecs;
}
int WebSocket::getPingInterval() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs;
}
void WebSocket::enablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = true;
}
void WebSocket::disablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = false;
}
void WebSocket::enablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
WebSocketPerMessageDeflateOptions perMessageDeflateOptions(true);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
void WebSocket::disablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
WebSocketPerMessageDeflateOptions perMessageDeflateOptions(false);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
void WebSocket::setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries;
}
void WebSocket::setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_minWaitBetweenReconnectionRetries = minWaitBetweenReconnectionRetries;
}
uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _maxWaitBetweenReconnectionRetries;
}
uint32_t WebSocket::getMinWaitBetweenReconnectionRetries() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _minWaitBetweenReconnectionRetries;
}
void WebSocket::start()
{
if (_thread.joinable()) return; // we've already been started
_thread = std::thread(&WebSocket::run, this);
}
void WebSocket::stop(uint16_t code, const std::string& reason)
{
close(code, reason);
if (_thread.joinable())
{
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
_sleepCondition.notify_one();
_thread.join();
_stop = false;
}
}
WebSocketInitResult WebSocket::connect(int timeoutSecs)
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(
_perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs);
}
WebSocketHttpHeaders headers(_extraHeaders);
std::string subProtocolsHeader;
auto subProtocols = getSubProtocols();
if (!subProtocols.empty())
{
//
// Sub Protocol strings are comma separated.
// Python code to do that is:
// >>> ','.join(['json', 'msgpack'])
// 'json,msgpack'
//
int i = 0;
for (auto subProtocol : subProtocols)
{
if (i++ != 0)
{
subProtocolsHeader += ",";
}
subProtocolsHeader += subProtocol;
}
headers["Sec-WebSocket-Protocol"] = subProtocolsHeader;
}
WebSocketInitResult status = _ws.connectToUrl(_url, headers, timeoutSecs);
if (!status.success)
{
return status;
}
_onMessageCallback(ix::make_unique<WebSocketMessage>(
WebSocketMessageType::Open,
emptyMsg,
0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers, status.protocol),
WebSocketCloseInfo()));
if (_pingIntervalSecs > 0)
{
// Send a heart beat right away
_ws.sendHeartBeat(_pingType);
}
return status;
}
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request)
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(
_perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs);
}
WebSocketInitResult status =
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate, request);
if (!status.success)
{
return status;
}
_onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
emptyMsg,
0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()));
if (_pingIntervalSecs > 0)
{
// Send a heart beat right away
_ws.sendHeartBeat(_pingType);
}
return status;
}
bool WebSocket::isConnected() const
{
return getReadyState() == ReadyState::Open;
}
bool WebSocket::isClosing() const
{
return getReadyState() == ReadyState::Closing;
}
void WebSocket::close(uint16_t code, const std::string& reason)
{
_ws.close(code, reason);
}
void WebSocket::checkConnection(bool firstConnectionAttempt)
{
using millis = std::chrono::duration<double, std::milli>;
uint32_t retries = 0;
millis duration(0);
// Try to connect perpertually
while (true)
{
if (isConnected() || isClosing() || _stop)
{
break;
}
if (!firstConnectionAttempt && !_automaticReconnection)
{
// Do not attempt to reconnect
break;
}
firstConnectionAttempt = false;
// Only sleep if we are retrying
if (duration.count() > 0)
{
std::unique_lock<std::mutex> lock(_sleepMutex);
_sleepCondition.wait_for(lock, duration);
}
if (_stop)
{
break;
}
// Try to connect synchronously
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
if (!status.success)
{
WebSocketErrorInfo connectErr;
if (_automaticReconnection)
{
duration =
millis(calculateRetryWaitMilliseconds(retries++,
_maxWaitBetweenReconnectionRetries,
_minWaitBetweenReconnectionRetries));
connectErr.wait_time = duration.count();
connectErr.retries = retries;
}
connectErr.reason = status.errorStr;
connectErr.http_status = status.http_status;
_onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
emptyMsg,
0,
connectErr,
WebSocketOpenInfo(),
WebSocketCloseInfo()));
}
}
}
void WebSocket::run()
{
setThreadName(getUrl());
bool firstConnectionAttempt = true;
while (true)
{
// 1. Make sure we are always connected
checkConnection(firstConnectionAttempt);
firstConnectionAttempt = false;
// if here we are closed then checkConnection was not able to connect
if (getReadyState() == ReadyState::Closed)
{
break;
}
// We can avoid to poll if we want to stop and are not closing
if (_stop && !isClosing()) break;
// 2. Poll to see if there's any new data available
WebSocketTransport::PollResult pollResult = _ws.poll();
// 3. Dispatch the incoming messages
_ws.dispatch(
pollResult,
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
WebSocketTransport::MessageKind messageKind)
{
WebSocketMessageType webSocketMessageType {WebSocketMessageType::Error};
switch (messageKind)
{
case WebSocketTransport::MessageKind::MSG_TEXT:
case WebSocketTransport::MessageKind::MSG_BINARY:
{
webSocketMessageType = WebSocketMessageType::Message;
}
break;
case WebSocketTransport::MessageKind::PING:
{
webSocketMessageType = WebSocketMessageType::Ping;
}
break;
case WebSocketTransport::MessageKind::PONG:
{
webSocketMessageType = WebSocketMessageType::Pong;
}
break;
case WebSocketTransport::MessageKind::FRAGMENT:
{
webSocketMessageType = WebSocketMessageType::Fragment;
}
break;
}
WebSocketErrorInfo webSocketErrorInfo;
webSocketErrorInfo.decompressionError = decompressionError;
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
_onMessageCallback(ix::make_unique<WebSocketMessage>(webSocketMessageType,
msg,
wireSize,
webSocketErrorInfo,
WebSocketOpenInfo(),
WebSocketCloseInfo(),
binary));
WebSocket::invokeTrafficTrackerCallback(wireSize, true);
});
}
}
void WebSocket::setOnMessageCallback(const OnMessageCallback& callback)
{
_onMessageCallback = callback;
}
bool WebSocket::isOnMessageCallbackRegistered() const
{
return _onMessageCallback != nullptr;
}
void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback)
{
_onTrafficTrackerCallback = callback;
}
void WebSocket::resetTrafficTrackerCallback()
{
setTrafficTrackerCallback(nullptr);
}
void WebSocket::invokeTrafficTrackerCallback(size_t size, bool incoming)
{
if (_onTrafficTrackerCallback)
{
_onTrafficTrackerCallback(size, incoming);
}
}
WebSocketSendInfo WebSocket::send(const std::string& data,
bool binary,
const OnProgressCallback& onProgressCallback)
{
return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const std::string& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const IXWebSocketSendData& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendUtf8Text(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendUtf8Text(const IXWebSocketSendData& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendText(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
if (!validateUtf8(text))
{
close(WebSocketCloseConstants::kInvalidFramePayloadData,
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
return false;
}
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::ping(const std::string& text, SendMessageKind pingType)
{
// Standard limit ping message size
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
return sendMessage(text, pingType);
}
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,
SendMessageKind sendMessageKind,
const OnProgressCallback& onProgressCallback)
{
if (!isConnected()) return WebSocketSendInfo(false);
//
// It is OK to read and write on the same socket in 2 different threads.
// https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid
//
// This makes it so that messages are sent right away, and we dont need
// a timeout while we poll to keep wake ups to a minimum (which helps
// with battery life), and use the system select call to notify us when
// incoming messages are arriving / there's data to be received.
//
std::lock_guard<std::mutex> lock(_writeMutex);
WebSocketSendInfo webSocketSendInfo;
switch (sendMessageKind)
{
case SendMessageKind::Text:
{
webSocketSendInfo = _ws.sendText(message, onProgressCallback);
}
break;
case SendMessageKind::Binary:
{
webSocketSendInfo = _ws.sendBinary(message, onProgressCallback);
}
break;
case SendMessageKind::Ping:
{
webSocketSendInfo = _ws.sendPing(message);
}
break;
}
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
return webSocketSendInfo;
}
ReadyState WebSocket::getReadyState() const
{
switch (_ws.getReadyState())
{
case ix::WebSocketTransport::ReadyState::OPEN: return ReadyState::Open;
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
case ix::WebSocketTransport::ReadyState::CLOSING: return ReadyState::Closing;
case ix::WebSocketTransport::ReadyState::CLOSED: return ReadyState::Closed;
default: return ReadyState::Closed;
}
}
std::string WebSocket::readyStateToString(ReadyState readyState)
{
switch (readyState)
{
case ReadyState::Open: return "OPEN";
case ReadyState::Connecting: return "CONNECTING";
case ReadyState::Closing: return "CLOSING";
case ReadyState::Closed: return "CLOSED";
default: return "UNKNOWN";
}
}
void WebSocket::enableAutomaticReconnection()
{
_automaticReconnection = true;
}
void WebSocket::disableAutomaticReconnection()
{
_automaticReconnection = false;
}
bool WebSocket::isAutomaticReconnectionEnabled() const
{
return _automaticReconnection;
}
size_t WebSocket::bufferedAmount() const
{
return _ws.bufferedAmount();
}
void WebSocket::addSubProtocol(const std::string& subProtocol)
{
std::lock_guard<std::mutex> lock(_configMutex);
_subProtocols.push_back(subProtocol);
}
const std::vector<std::string>& WebSocket::getSubProtocols()
{
std::lock_guard<std::mutex> lock(_configMutex);
return _subProtocols;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,187 @@
/*
* IXWebSocket.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*
* WebSocket RFC
* https://tools.ietf.org/html/rfc6455
*/
#pragma once
#include "IXProgressCallback.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketCloseConstants.h"
#include "IXWebSocketErrorInfo.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketMessage.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketTransport.h"
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
namespace ix
{
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
enum class ReadyState
{
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3
};
using OnMessageCallback = std::function<void(const WebSocketMessagePtr&)>;
using OnTrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
class WebSocket
{
public:
WebSocket();
~WebSocket();
void setUrl(const std::string& url);
// send extra headers in client handshake request
void setExtraHeaders(const WebSocketHttpHeaders& headers);
void setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
void setPingMessage(const std::string& sendMessage,
SendMessageKind pingType = SendMessageKind::Ping);
void setPingInterval(int pingIntervalSecs);
void enablePong();
void disablePong();
void enablePerMessageDeflate();
void disablePerMessageDeflate();
void addSubProtocol(const std::string& subProtocol);
void setHandshakeTimeout(int handshakeTimeoutSecs);
// Run asynchronously, by calling start and stop.
void start();
// stop is synchronous
void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
// Run in blocking mode, by connecting first manually, and then calling run.
WebSocketInitResult connect(int timeoutSecs);
void run();
// send is in text mode by default
WebSocketSendInfo send(const std::string& data,
bool binary = false,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const std::string& data,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const IXWebSocketSendData& data,
const OnProgressCallback& onProgressCallback = nullptr);
// does not check for valid UTF-8 characters. Caller must check that.
WebSocketSendInfo sendUtf8Text(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
// does not check for valid UTF-8 characters. Caller must check that.
WebSocketSendInfo sendUtf8Text(const IXWebSocketSendData& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text,SendMessageKind pingType = SendMessageKind::Ping);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
void setOnMessageCallback(const OnMessageCallback& callback);
bool isOnMessageCallbackRegistered() const;
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
static void resetTrafficTrackerCallback();
ReadyState getReadyState() const;
static std::string readyStateToString(ReadyState readyState);
const std::string getUrl() const;
const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
const std::string getPingMessage() const;
int getPingInterval() const;
size_t bufferedAmount() const;
void enableAutomaticReconnection();
void disableAutomaticReconnection();
bool isAutomaticReconnectionEnabled() const;
void setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries);
void setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries);
uint32_t getMaxWaitBetweenReconnectionRetries() const;
uint32_t getMinWaitBetweenReconnectionRetries() const;
const std::vector<std::string>& getSubProtocols();
private:
WebSocketSendInfo sendMessage(const IXWebSocketSendData& message,
SendMessageKind sendMessageKind,
const OnProgressCallback& callback = nullptr);
bool isConnected() const;
bool isClosing() const;
void checkConnection(bool firstConnectionAttempt);
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
// Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
WebSocketTransport _ws;
std::string _url;
WebSocketHttpHeaders _extraHeaders;
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
SocketTLSOptions _socketTLSOptions;
mutable std::mutex _configMutex; // protect all config variables access
OnMessageCallback _onMessageCallback;
static OnTrafficTrackerCallback _onTrafficTrackerCallback;
std::atomic<bool> _stop;
std::thread _thread;
std::mutex _writeMutex;
// Automatic reconnection
std::atomic<bool> _automaticReconnection;
static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries;
static const uint32_t kDefaultMinWaitBetweenReconnectionRetries;
uint32_t _maxWaitBetweenReconnectionRetries;
uint32_t _minWaitBetweenReconnectionRetries;
// Make the sleeping in the automatic reconnection cancellable
std::mutex _sleepMutex;
std::condition_variable _sleepCondition;
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;
// enable or disable PONG frame response to received PING frame
bool _enablePong;
static const bool kDefaultEnablePong;
// Optional ping and pong timeout
int _pingIntervalSecs;
int _pingTimeoutSecs;
std::string _pingMessage;
SendMessageKind _pingType;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
// Subprotocols
std::vector<std::string> _subProtocols;
friend class WebSocketServer;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,36 @@
/*
* IXWebSocketCloseConstants.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketCloseConstants.h"
namespace ix
{
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
const uint16_t WebSocketCloseConstants::kInvalidFramePayloadData(1007);
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure");
const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error");
const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure");
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
const std::string WebSocketCloseConstants::kProtocolErrorReservedBitUsed("Reserved bit used");
const std::string WebSocketCloseConstants::kProtocolErrorPingPayloadOversized(
"Ping reason control frame with payload length > 125 octets");
const std::string WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented(
"Control message fragmented");
const std::string WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence(
"Fragmentation: data message out of sequence");
const std::string WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence(
"Fragmentation: continuation opcode out of sequence");
const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage(
"Invalid frame payload data");
const std::string WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code");
} // namespace ix

Wyświetl plik

@ -0,0 +1,37 @@
/*
* IXWebSocketCloseConstants.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <string>
namespace ix
{
struct WebSocketCloseConstants
{
static const uint16_t kNormalClosureCode;
static const uint16_t kInternalErrorCode;
static const uint16_t kAbnormalCloseCode;
static const uint16_t kProtocolErrorCode;
static const uint16_t kNoStatusCodeErrorCode;
static const uint16_t kInvalidFramePayloadData;
static const std::string kNormalClosureMessage;
static const std::string kInternalErrorMessage;
static const std::string kAbnormalCloseMessage;
static const std::string kPingTimeoutMessage;
static const std::string kProtocolErrorMessage;
static const std::string kNoStatusCodeErrorMessage;
static const std::string kProtocolErrorReservedBitUsed;
static const std::string kProtocolErrorPingPayloadOversized;
static const std::string kProtocolErrorCodeControlMessageFragmented;
static const std::string kProtocolErrorCodeDataOpcodeOutOfSequence;
static const std::string kProtocolErrorCodeContinuationOpCodeOutOfSequence;
static const std::string kInvalidFramePayloadDataMessage;
static const std::string kInvalidCloseCodeMessage;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,28 @@
/*
* IXWebSocketCloseInfo.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <string>
namespace ix
{
struct WebSocketCloseInfo
{
uint16_t code;
std::string reason;
bool remote;
WebSocketCloseInfo(uint16_t c = 0, const std::string& r = std::string(), bool rem = false)
: code(c)
, reason(r)
, remote(rem)
{
;
}
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,22 @@
/*
* IXWebSocketErrorInfo.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <string>
namespace ix
{
struct WebSocketErrorInfo
{
uint32_t retries = 0;
double wait_time = 0;
int http_status = 0;
std::string reason;
bool decompressionError = false;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,392 @@
/*
* IXWebSocketHandshake.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketHandshake.h"
#include "IXBase64.h"
#include "IXHttp.h"
#include "IXSocketConnect.h"
#include "IXStrCaseCompare.h"
#include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXWebSocketHandshakeKeyGen.h"
#include <algorithm>
#include <iostream>
#include <random>
#include <sstream>
namespace ix
{
WebSocketHandshake::WebSocketHandshake(
std::atomic<bool>& requestInitCancellation,
std::unique_ptr<Socket>& socket,
WebSocketPerMessageDeflatePtr& perMessageDeflate,
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
std::atomic<bool>& enablePerMessageDeflate)
: _requestInitCancellation(requestInitCancellation)
, _socket(socket)
, _perMessageDeflate(perMessageDeflate)
, _perMessageDeflateOptions(perMessageDeflateOptions)
, _enablePerMessageDeflate(enablePerMessageDeflate)
{
}
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
{
return CaseInsensitiveLess::cmp(a, b) == 0;
}
std::string WebSocketHandshake::genRandomString(const int len)
{
std::string alphanum = "0123456789"
"ABCDEFGH"
"abcdefgh";
std::random_device r;
std::default_random_engine e1(r());
std::uniform_int_distribution<int> dist(0, (int) alphanum.size() - 1);
std::string s;
s.resize(len);
for (int i = 0; i < len; ++i)
{
int x = dist(e1);
s[i] = alphanum[x];
}
return s;
}
WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
{
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << code;
ss << " ";
ss << reason;
ss << "\r\n";
ss << "Server: " << userAgent() << "\r\n";
// Socket write can only be cancelled through a timeout here, not manually.
static std::atomic<bool> requestInitCancellation(false);
auto isCancellationRequested =
makeCancellationRequestWithTimeout(1, requestInitCancellation);
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(false, 500, "Timed out while sending error response");
}
return WebSocketInitResult(false, code, reason);
}
WebSocketInitResult WebSocketHandshake::clientHandshake(
const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& protocol,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs)
{
_requestInitCancellation = false;
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string errMsg;
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success)
{
std::stringstream ss;
ss << "Unable to connect to " << host << " on port " << port << ", error: " << errMsg;
return WebSocketInitResult(false, 0, ss.str());
}
// Generate a random 16 bytes string and base64 encode it.
//
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16));
std::stringstream ss;
ss << "GET " << path << " HTTP/1.1\r\n";
ss << "Host: " << host << ":" << port << "\r\n";
ss << "Upgrade: websocket\r\n";
ss << "Connection: Upgrade\r\n";
ss << "Sec-WebSocket-Version: 13\r\n";
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
// User-Agent can be customized by users
if (extraHeaders.find("User-Agent") == extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
// Set an origin header if missing
if (extraHeaders.find("Origin") == extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
for (auto& it : extraHeaders)
{
ss << it.first << ": " << it.second << "\r\n";
}
if (_enablePerMessageDeflate)
{
ss << _perMessageDeflateOptions.generateHeader();
}
ss << "\r\n";
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(
false, 0, std::string("Failed sending GET request to ") + url);
}
// Read HTTP status line
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
return WebSocketInitResult(
false, 0, std::string("Failed reading HTTP status line from ") + url);
}
// Validate status
auto statusLine = Http::parseStatusLine(line);
std::string httpVersion = statusLine.first;
int status = statusLine.second;
// HTTP/1.0 is too old.
if (httpVersion != "HTTP/1.1")
{
std::stringstream ss;
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
<< "Rejecting connection to " << url << ", status: " << status
<< ", HTTP Status line: " << line;
return WebSocketInitResult(false, status, ss.str());
}
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid)
{
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
}
// We want an 101 HTTP status for websocket, otherwise it could be
// a redirection (like 301)
if (status != 101)
{
std::stringstream ss;
ss << "Expecting status 101 (Switching Protocol), got " << status
<< " status connecting to " << url << ", HTTP Status line: " << line;
return WebSocketInitResult(false, status, ss.str(), headers, path);
}
// Check the presence of the connection field
if (headers.find("connection") == headers.end())
{
std::string errorMsg("Missing connection value");
return WebSocketInitResult(false, status, errorMsg);
}
// Check the value of the connection field
// Some websocket servers (Go/Gorilla?) send lowercase values for the
// connection header, so do a case insensitive comparison
//
// See https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a
//
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
{
std::stringstream ss;
ss << "Invalid connection value: " << headers["connection"];
return WebSocketInitResult(false, status, ss.str());
}
char output[29] = {};
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
if (std::string(output) != headers["sec-websocket-accept"])
{
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
return WebSocketInitResult(false, status, errorMsg);
}
if (_enablePerMessageDeflate)
{
// Parse the server response. Does it support deflate ?
std::string header = headers["sec-websocket-extensions"];
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
// If the server does not support that extension, disable it.
if (!webSocketPerMessageDeflateOptions.enabled())
{
_enablePerMessageDeflate = false;
}
// Otherwise try to initialize the deflate engine (zlib)
else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
{
return WebSocketInitResult(
false, 0, "Failed to initialize per message deflate engine");
}
}
return WebSocketInitResult(true, status, "", headers, path);
}
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request)
{
_requestInitCancellation = false;
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string method;
std::string uri;
std::string httpVersion;
if (request)
{
method = request->method;
uri = request->uri;
httpVersion = request->version;
}
else
{
// Read first line
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
return sendErrorResponse(400, "Error reading HTTP request line");
}
// Validate request line (GET /foo HTTP/1.1\r\n)
auto requestLine = Http::parseRequestLine(line);
method = std::get<0>(requestLine);
uri = std::get<1>(requestLine);
httpVersion = std::get<2>(requestLine);
}
if (method != "GET")
{
return sendErrorResponse(400, "Invalid HTTP method, need GET, got " + method);
}
if (httpVersion != "HTTP/1.1")
{
return sendErrorResponse(400,
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
}
WebSocketHttpHeaders headers;
if (request)
{
headers = request->headers;
}
else
{
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
headers = result.second;
if (!headersValid)
{
return sendErrorResponse(400, "Error parsing HTTP headers");
}
}
if (headers.find("sec-websocket-key") == headers.end())
{
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
}
if (headers.find("upgrade") == headers.end())
{
return sendErrorResponse(400, "Missing Upgrade header");
}
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket") &&
headers["Upgrade"] != "keep-alive, Upgrade") // special case for firefox
{
return sendErrorResponse(400,
"Invalid Upgrade header, "
"need WebSocket, got " +
headers["upgrade"]);
}
if (headers.find("sec-websocket-version") == headers.end())
{
return sendErrorResponse(400, "Missing Sec-WebSocket-Version value");
}
{
std::stringstream ss;
ss << headers["sec-websocket-version"];
int version;
ss >> version;
if (version != 13)
{
return sendErrorResponse(400,
"Invalid Sec-WebSocket-Version, "
"need 13, got " +
ss.str());
}
}
char output[29] = {};
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
std::stringstream ss;
ss << "HTTP/1.1 101 Switching Protocols\r\n";
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
ss << "Upgrade: websocket\r\n";
ss << "Connection: Upgrade\r\n";
ss << "Server: " << userAgent() << "\r\n";
// Parse the client headers. Does it support deflate ?
std::string header = headers["sec-websocket-extensions"];
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
// If the client has requested that extension,
if (webSocketPerMessageDeflateOptions.enabled() && enablePerMessageDeflate)
{
_enablePerMessageDeflate = true;
if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
{
return WebSocketInitResult(
false, 0, "Failed to initialize per message deflate engine");
}
ss << webSocketPerMessageDeflateOptions.generateHeader();
}
ss << "\r\n";
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(
false, 0, std::string("Failed sending response to remote end"));
}
return WebSocketInitResult(true, 200, "", headers, uri);
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,58 @@
/*
* IXWebSocketHandshake.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXCancellationRequest.h"
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketInitResult.h"
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <atomic>
#include <chrono>
#include <memory>
#include <string>
namespace ix
{
class WebSocketHandshake
{
public:
WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
std::unique_ptr<Socket>& _socket,
WebSocketPerMessageDeflatePtr& perMessageDeflate,
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
std::atomic<bool>& enablePerMessageDeflate);
WebSocketInitResult clientHandshake(const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& protocol,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
private:
std::string genRandomString(const int len);
// Parse HTTP headers
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
bool insensitiveStringCompare(const std::string& a, const std::string& b);
std::atomic<bool>& _requestInitCancellation;
std::unique_ptr<Socket>& _socket;
WebSocketPerMessageDeflatePtr& _perMessageDeflate;
WebSocketPerMessageDeflateOptions& _perMessageDeflateOptions;
std::atomic<bool>& _enablePerMessageDeflate;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,171 @@
// Copyright (c) 2016 Alex Hultman and contributors
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgement in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#pragma once
#include <cstddef>
#include <cstdint>
#include <string.h>
#include <string>
class WebSocketHandshakeKeyGen
{
template<int N, typename T>
struct static_for
{
void operator()(uint32_t* a, uint32_t* b)
{
static_for<N - 1, T>()(a, b);
T::template f<N - 1>(a, b);
}
};
template<typename T>
struct static_for<0, T>
{
void operator()(uint32_t* /*a*/, uint32_t* /*hash*/)
{
}
};
template<int state>
struct Sha1Loop
{
static inline uint32_t rol(uint32_t value, size_t bits)
{
return (value << bits) | (value >> (32 - bits));
}
static inline uint32_t blk(uint32_t b[16], size_t i)
{
return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
}
template<int i>
static inline void f(uint32_t* a, uint32_t* b)
{
switch (state)
{
case 1:
a[i % 5] +=
((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) +
b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
break;
case 2:
b[i] = blk(b, i);
a[(1 + i) % 5] +=
((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) +
b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
break;
case 3:
b[(i + 4) % 16] = blk(b, (i + 4) % 16);
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) +
b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
break;
case 4:
b[(i + 8) % 16] = blk(b, (i + 8) % 16);
a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) |
(a[(3 + i) % 5] & a[(2 + i) % 5])) +
b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
break;
case 5:
b[(i + 12) % 16] = blk(b, (i + 12) % 16);
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) +
b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
break;
case 6: b[i] += a[4 - i];
}
}
};
static inline void sha1(uint32_t hash[5], uint32_t b[16])
{
uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
static_for<16, Sha1Loop<1>>()(a, b);
static_for<4, Sha1Loop<2>>()(a, b);
static_for<20, Sha1Loop<3>>()(a, b);
static_for<20, Sha1Loop<4>>()(a, b);
static_for<20, Sha1Loop<5>>()(a, b);
static_for<5, Sha1Loop<6>>()(a, hash);
}
static inline void base64(unsigned char* src, char* dst)
{
const char* b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (int i = 0; i < 18; i += 3)
{
*dst++ = b64[(src[i] >> 2) & 63];
*dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
*dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
*dst++ = b64[src[i + 2] & 63];
}
*dst++ = b64[(src[18] >> 2) & 63];
*dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
*dst++ = b64[((src[19] & 15) << 2)];
*dst++ = '=';
}
public:
static inline void generate(const std::string& inputStr, char output[28])
{
char input[25] = {};
strncpy(input, inputStr.c_str(), 25 - 1);
input[25 - 1] = '\0';
uint32_t b_output[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
uint32_t b_input[16] = {0,
0,
0,
0,
0,
0,
0x32353845,
0x41464135,
0x2d453931,
0x342d3437,
0x44412d39,
0x3543412d,
0x43354142,
0x30444338,
0x35423131,
0x80000000};
for (int i = 0; i < 6; i++)
{
b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 |
(input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
}
sha1(b_output, b_input);
uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480};
sha1(b_output, last_b);
for (int i = 0; i < 5; i++)
{
uint32_t tmp = b_output[i];
char* bytes = (char*) &b_output[i];
bytes[3] = tmp & 0xff;
bytes[2] = (tmp >> 8) & 0xff;
bytes[1] = (tmp >> 16) & 0xff;
bytes[0] = (tmp >> 24) & 0xff;
}
base64((unsigned char*) b_output, output);
}
};

Wyświetl plik

@ -0,0 +1,74 @@
/*
* IXWebSocketHttpHeaders.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketHttpHeaders.h"
#include "IXSocket.h"
#include <algorithm>
#include <locale>
namespace ix
{
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::unique_ptr<Socket>& socket, const CancellationRequest& isCancellationRequested)
{
WebSocketHttpHeaders headers;
char line[1024];
int i;
while (true)
{
int colon = 0;
for (i = 0; i < 2 || (i < 1023 && line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
{
if (!socket->readByte(line + i, isCancellationRequested))
{
return std::make_pair(false, headers);
}
if (line[i] == ':' && colon == 0)
{
colon = i;
}
}
if (line[0] == '\r' && line[1] == '\n')
{
break;
}
// line is a single header entry. split by ':', and add it to our
// header map. ignore lines with no colon.
if (colon > 0)
{
line[i] = '\0';
std::string lineStr(line);
// colon is ':', usually colon+1 is ' ', and colon+2 is the start of the value.
// some webservers do not put a space after the colon character, so
// the start of the value might be farther than colon+2.
// The spec says that space after the : should be discarded.
// i is end of string (\0), i-colon is length of string minus key;
// subtract 1 for '\0', 1 for '\n', 1 for '\r',
// 1 for the ' ' after the ':', and total is -4
// since we use an std::string later on and don't account for '\0',
// plus the optional first space, total is -2
int start = colon + 1;
while (lineStr[start] == ' ')
{
start++;
}
std::string name(lineStr.substr(0, colon));
std::string value(lineStr.substr(start, lineStr.size() - start - 2));
headers[name] = value;
}
}
return std::make_pair(true, headers);
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,23 @@
/*
* IXWebSocketHttpHeaders.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXCancellationRequest.h"
#include "IXStrCaseCompare.h"
#include <map>
#include <memory>
#include <string>
namespace ix
{
class Socket;
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::unique_ptr<Socket>& socket, const CancellationRequest& isCancellationRequested);
} // namespace ix

Wyświetl plik

@ -0,0 +1,36 @@
/*
* IXWebSocketInitResult.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXWebSocketHttpHeaders.h"
namespace ix
{
struct WebSocketInitResult
{
bool success;
int http_status;
std::string errorStr;
WebSocketHttpHeaders headers;
std::string uri;
std::string protocol;
WebSocketInitResult(bool s = false,
int status = 0,
const std::string& e = std::string(),
WebSocketHttpHeaders h = WebSocketHttpHeaders(),
const std::string& u = std::string())
{
success = s;
http_status = status;
errorStr = e;
headers = h;
uri = u;
protocol = h["Sec-WebSocket-Protocol"];
}
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,60 @@
/*
* IXWebSocketMessage.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXWebSocketCloseInfo.h"
#include "IXWebSocketErrorInfo.h"
#include "IXWebSocketMessageType.h"
#include "IXWebSocketOpenInfo.h"
#include <memory>
#include <string>
namespace ix
{
struct WebSocketMessage
{
WebSocketMessageType type;
const std::string& str;
size_t wireSize;
WebSocketErrorInfo errorInfo;
WebSocketOpenInfo openInfo;
WebSocketCloseInfo closeInfo;
bool binary;
WebSocketMessage(WebSocketMessageType t,
const std::string& s,
size_t w,
WebSocketErrorInfo e,
WebSocketOpenInfo o,
WebSocketCloseInfo c,
bool b = false)
: type(t)
, str(s)
, wireSize(w)
, errorInfo(e)
, openInfo(o)
, closeInfo(c)
, binary(b)
{
;
}
/**
* @brief Deleted overload to prevent binding `str` to a temporary, which would cause
* undefined behavior since class members don't extend lifetime beyond the constructor call.
*/
WebSocketMessage(WebSocketMessageType t,
std::string&& s,
size_t w,
WebSocketErrorInfo e,
WebSocketOpenInfo o,
WebSocketCloseInfo c,
bool b = false) = delete;
};
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;
} // namespace ix

Wyświetl plik

@ -0,0 +1,21 @@
/*
* IXWebSocketMessageType.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
namespace ix
{
enum class WebSocketMessageType
{
Message = 0,
Open = 1,
Close = 2,
Error = 3,
Ping = 4,
Pong = 5,
Fragment = 6
};
}

Wyświetl plik

@ -0,0 +1,31 @@
/*
* IXWebSocketOpenInfo.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXWebSocketHttpHeaders.h"
#include <cstdint>
#include <string>
namespace ix
{
struct WebSocketOpenInfo
{
std::string uri;
WebSocketHttpHeaders headers;
std::string protocol;
WebSocketOpenInfo(const std::string& u = std::string(),
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& p = std::string())
: uri(u)
, headers(h)
, protocol(p)
{
;
}
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,98 @@
/*
* Copyright (c) 2015, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*
* Adapted from websocketpp/extensions/permessage_deflate/enabled.hpp
* (same license as MZ: https://opensource.org/licenses/BSD-3-Clause)
*
* - Reused zlib compression + decompression bits.
* - Refactored to have 2 class for compression and decompression, to allow multi-threading
* and make sure that _compressBuffer is not shared between threads.
* - Original code wasn't working for some reason, I had to add checks
* for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers
* would start accepting receiving/decoding compressed messages. Original code was probably
* modifying the passed in buffers before processing in enabled.hpp ?
* - Added more documentation.
*
* Per message Deflate RFC: https://tools.ietf.org/html/rfc7692
* Chrome websocket ->
* https://github.com/chromium/chromium/tree/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/net/websockets
*
*/
#include <cstdint>
#include "IXWebSocketPerMessageDeflate.h"
#include "IXUniquePtr.h"
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
namespace ix
{
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
: _compressor(ix::make_unique<WebSocketPerMessageDeflateCompressor>())
, _decompressor(ix::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
;
}
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
{
;
}
bool WebSocketPerMessageDeflate::init(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{
bool clientNoContextTakeover = perMessageDeflateOptions.getClientNoContextTakeover();
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
return _compressor->init(deflateBits, clientNoContextTakeover) &&
_decompressor->init(inflateBits, clientNoContextTakeover);
}
bool WebSocketPerMessageDeflate::compress(const IXWebSocketSendData& in, std::string& out)
{
return _compressor->compress(in, out);
}
bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out)
{
return _compressor->compress(in, out);
}
bool WebSocketPerMessageDeflate::decompress(const std::string& in, std::string& out)
{
return _decompressor->decompress(in, out);
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,64 @@
/*
* Copyright (c) 2015, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*
* Adapted from websocketpp/extensions/permessage_deflate/enabled.hpp
* (same license as MZ: https://opensource.org/licenses/BSD-3-Clause)
*/
#pragma once
#include <memory>
#include <string>
#include "IXWebSocketSendData.h"
namespace ix
{
class WebSocketPerMessageDeflateOptions;
class WebSocketPerMessageDeflateCompressor;
class WebSocketPerMessageDeflateDecompressor;
class WebSocketPerMessageDeflate
{
public:
WebSocketPerMessageDeflate();
~WebSocketPerMessageDeflate();
bool init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
bool compress(const IXWebSocketSendData& in, std::string& out);
bool compress(const std::string& in, std::string& out);
bool decompress(const std::string& in, std::string& out);
private:
std::unique_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
std::unique_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
};
using WebSocketPerMessageDeflatePtr = std::unique_ptr<WebSocketPerMessageDeflate>;
} // namespace ix

Wyświetl plik

@ -0,0 +1,252 @@
/*
* IXWebSocketPerMessageDeflateCodec.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <cassert>
#include <string.h>
namespace
{
// The passed in size (4) is important, without it the string litteral
// is treated as a char* and the null termination (\x00) makes it
// look like an empty string.
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
} // namespace
namespace ix
{
//
// Compressor
//
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
memset(&_deflateState, 0, sizeof(_deflateState));
_deflateState.zalloc = Z_NULL;
_deflateState.zfree = Z_NULL;
_deflateState.opaque = Z_NULL;
#endif
}
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
deflateEnd(&_deflateState);
#endif
}
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
bool clientNoContextTakeOver)
{
#ifdef IXWEBSOCKET_USE_ZLIB
int ret = deflateInit2(&_deflateState,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-1 * deflateBits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY);
if (ret != Z_OK) return false;
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
return true;
#else
return false;
#endif
}
template<typename T>
bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(const T& value)
{
if (kEmptyUncompressedBlock.size() > value.size()) return false;
auto N = value.size();
return value[N - 1] == kEmptyUncompressedBlock[3] &&
value[N - 2] == kEmptyUncompressedBlock[2] &&
value[N - 3] == kEmptyUncompressedBlock[1] &&
value[N - 4] == kEmptyUncompressedBlock[0];
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const IXWebSocketSendData& in,
std::string& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
std::vector<uint8_t>& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
std::string& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
std::vector<uint8_t>& out)
{
return compressData(in, out);
}
template<typename T, typename S>
bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
{
#ifdef IXWEBSOCKET_USE_ZLIB
//
// 7.2.1. Compression
//
// An endpoint uses the following algorithm to compress a message.
//
// 1. Compress all the octets of the payload of the message using
// DEFLATE.
//
// 2. If the resulting data does not end with an empty DEFLATE block
// with no compression (the "BTYPE" bits are set to 00), append an
// empty DEFLATE block with no compression to the tail end.
//
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
// After this step, the last octet of the compressed data contains
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
// set to 00.
//
size_t output;
// Clear output
out.clear();
if (in.empty())
{
// See issue #167
// The normal buffer size should be 6 but
// we remove the 4 octets from the tail (#4)
uint8_t buf[2] = {0x02, 0x00};
out.push_back(buf[0]);
out.push_back(buf[1]);
return true;
}
_deflateState.avail_in = (uInt) in.size();
_deflateState.next_in = (Bytef*) in.data();
do
{
// Output to local buffer
_deflateState.avail_out = (uInt) _compressBuffer.size();
_deflateState.next_out = &_compressBuffer.front();
deflate(&_deflateState, _flush);
output = _compressBuffer.size() - _deflateState.avail_out;
out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output);
} while (_deflateState.avail_out == 0);
if (endsWithEmptyUnCompressedBlock(out))
{
out.resize(out.size() - 4);
}
return true;
#else
return false;
#endif
}
//
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
memset(&_inflateState, 0, sizeof(_inflateState));
_inflateState.zalloc = Z_NULL;
_inflateState.zfree = Z_NULL;
_inflateState.opaque = Z_NULL;
_inflateState.avail_in = 0;
_inflateState.next_in = Z_NULL;
#endif
}
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
inflateEnd(&_inflateState);
#endif
}
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool clientNoContextTakeOver)
{
#ifdef IXWEBSOCKET_USE_ZLIB
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
if (ret != Z_OK) return false;
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
return true;
#else
return false;
#endif
}
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
{
#ifdef IXWEBSOCKET_USE_ZLIB
//
// 7.2.2. Decompression
//
// An endpoint uses the following algorithm to decompress a message.
//
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
// payload of the message.
//
// 2. Decompress the resulting data using DEFLATE.
//
std::string inFixed(in);
inFixed += kEmptyUncompressedBlock;
_inflateState.avail_in = (uInt) inFixed.size();
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
// Clear output
out.clear();
do
{
_inflateState.avail_out = (uInt) _compressBuffer.size();
_inflateState.next_out = &_compressBuffer.front();
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
return false; // zlib error
}
out.append(reinterpret_cast<char*>(&_compressBuffer.front()),
_compressBuffer.size() - _inflateState.avail_out);
} while (_inflateState.avail_out == 0);
return true;
#else
return false;
#endif
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,65 @@
/*
* IXWebSocketPerMessageDeflateCodec.h
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#ifdef IXWEBSOCKET_USE_ZLIB
#include "zlib.h"
#endif
#include <array>
#include <cstdint>
#include <string>
#include <vector>
#include "IXWebSocketSendData.h"
namespace ix
{
class WebSocketPerMessageDeflateCompressor
{
public:
WebSocketPerMessageDeflateCompressor();
~WebSocketPerMessageDeflateCompressor();
bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
bool compress(const IXWebSocketSendData& in, std::string& out);
bool compress(const std::string& in, std::string& out);
bool compress(const std::string& in, std::vector<uint8_t>& out);
bool compress(const std::vector<uint8_t>& in, std::string& out);
bool compress(const std::vector<uint8_t>& in, std::vector<uint8_t>& out);
private:
template<typename T, typename S>
bool compressData(const T& in, S& out);
template<typename T>
bool endsWithEmptyUnCompressedBlock(const T& value);
int _flush;
std::array<unsigned char, 1 << 14> _compressBuffer;
#ifdef IXWEBSOCKET_USE_ZLIB
z_stream _deflateState;
#endif
};
class WebSocketPerMessageDeflateDecompressor
{
public:
WebSocketPerMessageDeflateDecompressor();
~WebSocketPerMessageDeflateDecompressor();
bool init(uint8_t inflateBits, bool clientNoContextTakeOver);
bool decompress(const std::string& in, std::string& out);
private:
int _flush;
std::array<unsigned char, 1 << 14> _compressBuffer;
#ifdef IXWEBSOCKET_USE_ZLIB
z_stream _inflateState;
#endif
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,185 @@
/*
* IXWebSocketPerMessageDeflateOptions.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <algorithm>
#include <cctype>
#include <sstream>
namespace ix
{
/// Default values as defined in the RFC
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15;
static const uint8_t minServerMaxWindowBits = 8;
static const uint8_t maxServerMaxWindowBits = 15;
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15;
static const uint8_t minClientMaxWindowBits = 8;
static const uint8_t maxClientMaxWindowBits = 15;
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
bool enabled,
bool clientNoContextTakeover,
bool serverNoContextTakeover,
uint8_t clientMaxWindowBits,
uint8_t serverMaxWindowBits)
{
_enabled = enabled;
_clientNoContextTakeover = clientNoContextTakeover;
_serverNoContextTakeover = serverNoContextTakeover;
_clientMaxWindowBits = clientMaxWindowBits;
_serverMaxWindowBits = serverMaxWindowBits;
sanitizeClientMaxWindowBits();
}
//
// Four extension parameters are defined for "permessage-deflate" to
// help endpoints manage per-connection resource usage.
//
// - "server_no_context_takeover"
// - "client_no_context_takeover"
// - "server_max_window_bits"
// - "client_max_window_bits"
//
// Server response could look like that:
//
// Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover;
// server_no_context_takeover
//
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(std::string extension)
{
extension = removeSpaces(extension);
_enabled = false;
_clientNoContextTakeover = false;
_serverNoContextTakeover = false;
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
#ifdef IXWEBSOCKET_USE_ZLIB
// Split by ;
std::string token;
std::stringstream tokenStream(extension);
while (std::getline(tokenStream, token, ';'))
{
if (token == "permessage-deflate")
{
_enabled = true;
}
if (token == "server_no_context_takeover")
{
_serverNoContextTakeover = true;
}
if (token == "client_no_context_takeover")
{
_clientNoContextTakeover = true;
}
if (startsWith(token, "server_max_window_bits="))
{
uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
// Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values
_serverMaxWindowBits =
std::min(maxServerMaxWindowBits, std::max(x, minServerMaxWindowBits));
}
if (startsWith(token, "client_max_window_bits="))
{
uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
// Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values
_clientMaxWindowBits =
std::min(maxClientMaxWindowBits, std::max(x, minClientMaxWindowBits));
sanitizeClientMaxWindowBits();
}
}
#endif
}
void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits()
{
// zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9
// See https://bugs.chromium.org/p/chromium/issues/detail?id=691074
if (_clientMaxWindowBits == 8)
{
_clientMaxWindowBits = 9;
}
}
std::string WebSocketPerMessageDeflateOptions::generateHeader()
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::stringstream ss;
ss << "Sec-WebSocket-Extensions: permessage-deflate";
if (_clientNoContextTakeover) ss << "; client_no_context_takeover";
if (_serverNoContextTakeover) ss << "; server_no_context_takeover";
ss << "; server_max_window_bits=" << static_cast<int>(_serverMaxWindowBits);
ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits);
ss << "\r\n";
return ss.str();
#else
return std::string();
#endif
}
bool WebSocketPerMessageDeflateOptions::enabled() const
{
#ifdef IXWEBSOCKET_USE_ZLIB
return _enabled;
#else
return false;
#endif
}
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
{
return _clientNoContextTakeover;
}
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
{
return _serverNoContextTakeover;
}
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
{
return _clientMaxWindowBits;
}
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
{
return _serverMaxWindowBits;
}
bool WebSocketPerMessageDeflateOptions::startsWith(const std::string& str,
const std::string& start)
{
return str.compare(0, start.length(), start) == 0;
}
std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
{
std::string out(str);
out.erase(
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
out.end());
return out;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,48 @@
/*
* IXWebSocketPerMessageDeflateOptions.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <string>
namespace ix
{
class WebSocketPerMessageDeflateOptions
{
public:
WebSocketPerMessageDeflateOptions(
bool enabled = false,
bool clientNoContextTakeover = false,
bool serverNoContextTakeover = false,
uint8_t clientMaxWindowBits = kDefaultClientMaxWindowBits,
uint8_t serverMaxWindowBits = kDefaultServerMaxWindowBits);
WebSocketPerMessageDeflateOptions(std::string extension);
std::string generateHeader();
bool enabled() const;
bool getClientNoContextTakeover() const;
bool getServerNoContextTakeover() const;
uint8_t getServerMaxWindowBits() const;
uint8_t getClientMaxWindowBits() const;
static bool startsWith(const std::string& str, const std::string& start);
static std::string removeSpaces(const std::string& str);
static uint8_t const kDefaultClientMaxWindowBits;
static uint8_t const kDefaultServerMaxWindowBits;
private:
bool _enabled;
bool _clientNoContextTakeover;
bool _serverNoContextTakeover;
uint8_t _clientMaxWindowBits;
uint8_t _serverMaxWindowBits;
void sanitizeClientMaxWindowBits();
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,137 @@
/*
* IXWebSocketProxyServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketProxyServer.h"
#include "IXWebSocketServer.h"
#include <sstream>
namespace ix
{
class ProxyConnectionState : public ix::ConnectionState
{
public:
ProxyConnectionState()
: _connected(false)
{
}
ix::WebSocket& webSocket()
{
return _serverWebSocket;
}
bool isConnected()
{
return _connected;
}
void setConnected()
{
_connected = true;
}
private:
ix::WebSocket _serverWebSocket;
bool _connected;
};
int websocket_proxy_server_main(int port,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions,
const std::string& remoteUrl,
const RemoteUrlsMapping& remoteUrlsMapping,
bool /*verbose*/)
{
ix::WebSocketServer server(port, hostname);
server.setTLSOptions(tlsOptions);
auto factory = []() -> std::shared_ptr<ix::ConnectionState> {
return std::make_shared<ProxyConnectionState>();
};
server.setConnectionStateFactory(factory);
server.setOnConnectionCallback(
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState) {
auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
auto remoteIp = connectionState->getRemoteIp();
// Server connection
state->webSocket().setOnMessageCallback(
[webSocket, state, remoteIp](const WebSocketMessagePtr& msg) {
if (msg->type == ix::WebSocketMessageType::Close)
{
state->setTerminated();
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
auto ws = webSocket.lock();
if (ws)
{
ws->send(msg->str, msg->binary);
}
}
});
// Client connection
auto ws = webSocket.lock();
if (ws)
{
ws->setOnMessageCallback([state, remoteUrl, remoteUrlsMapping](
const WebSocketMessagePtr& msg) {
if (msg->type == ix::WebSocketMessageType::Open)
{
// Connect to the 'real' server
std::string url(remoteUrl);
// maybe we want a different url based on the mapping
std::string host = msg->openInfo.headers["Host"];
auto it = remoteUrlsMapping.find(host);
if (it != remoteUrlsMapping.end())
{
url = it->second;
}
// append the uri to form the full url
// (say ws://localhost:1234/foo/?bar=baz)
url += msg->openInfo.uri;
state->webSocket().setUrl(url);
state->webSocket().disableAutomaticReconnection();
state->webSocket().start();
// we should sleep here for a bit until we've established the
// connection with the remote server
while (state->webSocket().getReadyState() != ReadyState::Open)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason);
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
state->webSocket().send(msg->str, msg->binary);
}
});
}
});
auto res = server.listen();
if (!res.first)
{
return 1;
}
server.start();
server.wait();
return 0;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,24 @@
/*
* IXWebSocketProxyServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocketTLSOptions.h"
#include <cstdint>
#include <map>
#include <stddef.h>
#include <string>
namespace ix
{
using RemoteUrlsMapping = std::map<std::string, std::string>;
int websocket_proxy_server_main(int port,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions,
const std::string& remoteUrl,
const RemoteUrlsMapping& remoteUrlsMapping,
bool verbose);
} // namespace ix

Wyświetl plik

@ -0,0 +1,129 @@
/*
* IXWebSocketSendData.h
*
* WebSocket (Binary/Text) send data buffer
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <iterator>
namespace ix
{
/*
* IXWebSocketSendData implements a wrapper for std::string, std:vector<char/uint8_t> and char*.
* It removes the necessarity to copy the data or string into a std::string
*/
class IXWebSocketSendData {
public:
template<typename T>
struct IXWebSocketSendData_const_iterator
//: public std::iterator<std::forward_iterator_tag, T>
{
typedef IXWebSocketSendData_const_iterator<T> const_iterator;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = const value_type&;
pointer _ptr;
public:
IXWebSocketSendData_const_iterator() : _ptr(nullptr) {}
IXWebSocketSendData_const_iterator(pointer ptr) : _ptr(ptr) {}
~IXWebSocketSendData_const_iterator() {}
const_iterator operator++(int) { return const_iterator(_ptr++); }
const_iterator& operator++() { ++_ptr; return *this; }
reference operator* () const { return *_ptr; }
pointer operator->() const { return _ptr; }
const_iterator operator+ (const difference_type offset) const { return const_iterator(_ptr + offset); }
const_iterator operator- (const difference_type offset) const { return const_iterator(_ptr - offset); }
difference_type operator- (const const_iterator& rhs) const { return _ptr - rhs._ptr; }
bool operator==(const const_iterator& rhs) const { return _ptr == rhs._ptr; }
bool operator!=(const const_iterator& rhs) const { return _ptr != rhs._ptr; }
const_iterator& operator+=(const difference_type offset) { _ptr += offset; return *this; }
const_iterator& operator-=(const difference_type offset) { _ptr -= offset; return *this; }
};
using const_iterator = IXWebSocketSendData_const_iterator<char>;
/* The assigned std::string must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::string& str)
: _data(str.data())
, _size(str.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<char>& v)
: _data(v.data())
, _size(v.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<uint8_t>& v)
: _data(reinterpret_cast<const char*>(v.data()))
, _size(v.size())
{
}
/* The assigned memory must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const char* data, size_t size)
: _data(data)
, _size(data == nullptr ? 0 : size)
{
}
bool empty() const
{
return _data == nullptr || _size == 0;
}
const char* c_str() const
{
return _data;
}
const char* data() const
{
return _data;
}
size_t size() const
{
return _size;
}
inline const_iterator begin() const
{
return const_iterator(const_cast<char*>(_data));
}
inline const_iterator end() const
{
return const_iterator(const_cast<char*>(_data) + _size);
}
inline const_iterator cbegin() const
{
return begin();
}
inline const_iterator cend() const
{
return end();
}
private:
const char* _data;
const size_t _size;
};
}

Wyświetl plik

@ -0,0 +1,27 @@
/*
* IXWebSocketSendInfo.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
namespace ix
{
struct WebSocketSendInfo
{
bool success;
bool compressionError;
size_t payloadSize;
size_t wireSize;
WebSocketSendInfo(bool s = false, bool c = false, size_t p = 0, size_t w = 0)
: success(s)
, compressionError(c)
, payloadSize(p)
, wireSize(w)
{
;
}
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,237 @@
/*
* IXWebSocketServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketServer.h"
#include "IXNetSystem.h"
#include "IXSetThreadName.h"
#include "IXSocketConnect.h"
#include "IXWebSocket.h"
#include "IXWebSocketTransport.h"
#include <future>
#include <sstream>
#include <string.h>
namespace ix
{
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
const bool WebSocketServer::kDefaultEnablePong(true);
WebSocketServer::WebSocketServer(int port,
const std::string& host,
int backlog,
size_t maxConnections,
int handshakeTimeoutSecs,
int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
, _enablePong(kDefaultEnablePong)
, _enablePerMessageDeflate(true)
{
}
WebSocketServer::~WebSocketServer()
{
stop();
}
void WebSocketServer::stop()
{
stopAcceptingConnections();
auto clients = getClients();
for (auto client : clients)
{
client->close();
}
SocketServer::stop();
}
void WebSocketServer::enablePong()
{
_enablePong = true;
}
void WebSocketServer::disablePong()
{
_enablePong = false;
}
void WebSocketServer::disablePerMessageDeflate()
{
_enablePerMessageDeflate = false;
}
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
}
void WebSocketServer::setOnClientMessageCallback(const OnClientMessageCallback& callback)
{
_onClientMessageCallback = callback;
}
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
handleUpgrade(std::move(socket), connectionState);
connectionState->setTerminated();
}
void WebSocketServer::handleUpgrade(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState,
HttpRequestPtr request)
{
setThreadName("Srv:ws:" + connectionState->getId());
auto webSocket = std::make_shared<WebSocket>();
if (_onConnectionCallback)
{
_onConnectionCallback(webSocket, connectionState);
if (!webSocket->isOnMessageCallbackRegistered())
{
logError("WebSocketServer Application developer error: Server callback improperly "
"registered.");
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
connectionState->setTerminated();
return;
}
}
else if (_onClientMessageCallback)
{
WebSocket* webSocketRawPtr = webSocket.get();
webSocket->setOnMessageCallback(
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg)
{ _onClientMessageCallback(connectionState, *webSocketRawPtr, msg); });
}
else
{
logError(
"WebSocketServer Application developer error: No server callback is registerered.");
logError("Missing call to setOnConnectionCallback or setOnClientMessageCallback.");
connectionState->setTerminated();
return;
}
webSocket->disableAutomaticReconnection();
if (_enablePong)
{
webSocket->enablePong();
}
else
{
webSocket->disablePong();
}
// Add this client to our client set
{
std::lock_guard<std::mutex> lock(_clientsMutex);
_clients.insert(webSocket);
}
auto status = webSocket->connectToSocket(
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate, request);
if (status.success)
{
// Process incoming messages and execute callbacks
// until the connection is closed
webSocket->run();
}
else
{
std::stringstream ss;
ss << "WebSocketServer::handleConnection() HTTP status: " << status.http_status
<< " error: " << status.errorStr;
logError(ss.str());
}
webSocket->setOnMessageCallback(nullptr);
// Remove this client from our client set
{
std::lock_guard<std::mutex> lock(_clientsMutex);
if (_clients.erase(webSocket) != 1)
{
logError("Cannot delete client");
}
}
}
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
{
std::lock_guard<std::mutex> lock(_clientsMutex);
return _clients;
}
size_t WebSocketServer::getConnectedClientsCount()
{
std::lock_guard<std::mutex> lock(_clientsMutex);
return _clients.size();
}
//
// Classic servers
//
void WebSocketServer::makeBroadcastServer()
{
setOnClientMessageCallback(
[this](std::shared_ptr<ConnectionState> connectionState,
WebSocket& webSocket,
const WebSocketMessagePtr& msg)
{
auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Message)
{
for (auto&& client : getClients())
{
if (client.get() != &webSocket)
{
client->send(msg->str, msg->binary);
// Make sure the OS send buffer is flushed before moving on
do
{
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
}
}
}
});
}
bool WebSocketServer::listenAndStart()
{
auto res = listen();
if (!res.first)
{
return false;
}
start();
return true;
}
int WebSocketServer::getHandshakeTimeoutSecs()
{
return _handshakeTimeoutSecs;
}
bool WebSocketServer::isPongEnabled()
{
return _enablePong;
}
bool WebSocketServer::isPerMessageDeflateEnabled()
{
return _enablePerMessageDeflate;
}
} // namespace ix

Wyświetl plik

@ -0,0 +1,83 @@
/*
* IXWebSocketServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocketServer.h"
#include "IXWebSocket.h"
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class WebSocketServer : public SocketServer
{
public:
using OnConnectionCallback =
std::function<void(std::weak_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
using OnClientMessageCallback = std::function<void(
std::shared_ptr<ConnectionState>, WebSocket&, const WebSocketMessagePtr&)>;
WebSocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~WebSocketServer();
virtual void stop() final;
void enablePong();
void disablePong();
void disablePerMessageDeflate();
void setOnConnectionCallback(const OnConnectionCallback& callback);
void setOnClientMessageCallback(const OnClientMessageCallback& callback);
// Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients();
void makeBroadcastServer();
bool listenAndStart();
const static int kDefaultHandShakeTimeoutSecs;
int getHandshakeTimeoutSecs();
bool isPongEnabled();
bool isPerMessageDeflateEnabled();
private:
// Member variables
int _handshakeTimeoutSecs;
bool _enablePong;
bool _enablePerMessageDeflate;
OnConnectionCallback _onConnectionCallback;
OnClientMessageCallback _onClientMessageCallback;
std::mutex _clientsMutex;
std::set<std::shared_ptr<WebSocket>> _clients;
const static bool kDefaultEnablePong;
// Methods
virtual void handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState);
virtual size_t getConnectedClientsCount() final;
protected:
void handleUpgrade(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState,
HttpRequestPtr request = nullptr);
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,285 @@
/*
* IXWebSocketTransport.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
//
// Adapted from https://github.com/dhbaird/easywsclient
//
#include "IXCancellationRequest.h"
#include "IXProgressCallback.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketCloseConstants.h"
#include "IXWebSocketHandshake.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h"
#include <atomic>
#include <cstdint>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
namespace ix
{
class Socket;
enum class SendMessageKind
{
Text,
Binary,
Ping
};
class WebSocketTransport
{
public:
enum class ReadyState
{
CLOSING,
CLOSED,
CONNECTING,
OPEN
};
enum class MessageKind
{
MSG_TEXT,
MSG_BINARY,
PING,
PONG,
FRAGMENT
};
enum class PollResult
{
Succeeded,
AbnormalClose,
CannotFlushSendBuffer
};
using OnMessageCallback =
std::function<void(const std::string&, size_t, bool, MessageKind)>;
using OnCloseCallback = std::function<void(uint16_t, const std::string&, size_t, bool)>;
WebSocketTransport();
~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions,
bool enablePong,
int pingIntervalSecs);
// Client
WebSocketInitResult connectToUrl(const std::string& url,
const WebSocketHttpHeaders& headers,
int timeoutSecs);
// Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
PollResult poll();
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendText(const IXWebSocketSendData& message,
const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendPing(const IXWebSocketSendData& message);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage,
size_t closeWireSize = 0,
bool remote = false);
void closeSocket();
ReadyState getReadyState() const;
void setReadyState(ReadyState readyState);
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
size_t bufferedAmount() const;
// set ping heartbeat message
void setPingMessage(const std::string& message, SendMessageKind pingType);
// internal
// send any type of ping packet, not only 'ping' type
WebSocketSendInfo sendHeartBeat(SendMessageKind pingType);
private:
std::string _url;
struct wsheader_type
{
unsigned header_size;
bool fin;
bool rsv1;
bool rsv2;
bool rsv3;
bool mask;
enum opcode_type
{
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
uint8_t masking_key[4];
};
// Tells whether we should mask the data we send.
// client should mask but server should not
std::atomic<bool> _useMask;
// Tells whether we should flush the send buffer before
// saying that a send is complete. This is the mode for server code.
std::atomic<bool> _blockingSend;
// Buffer for reading from our socket. That buffer is never resized.
std::vector<uint8_t> _readbuf;
// Contains all messages that were fetched in the last socket read.
// This could be a mix of control messages (Close, Ping, etc...) and
// data messages. That buffer is resized
std::vector<uint8_t> _rxbuf;
// Contains all messages that are waiting to be sent
std::vector<uint8_t> _txbuf;
mutable std::mutex _txbufMutex;
// Hold fragments for multi-fragments messages in a list. We support receiving very large
// messages (tested messages up to 700M) and we cannot put them in a single
// buffer that is resized, as this operation can be slow when a buffer has its
// size increased 2 fold, while appending to a list has a fixed cost.
std::list<std::string> _chunks;
// Record the message kind (will be TEXT or BINARY) for a fragmented
// message, present in the first chunk, since the final chunk will be a
// CONTINUATION opcode and doesn't tell the full message kind
MessageKind _fragmentedMessageKind;
// Ditto for whether a message is compressed
bool _receivedMessageCompressed;
// Fragments are 32K long
static constexpr size_t kChunkSize = 1 << 15;
// Underlying TCP socket
std::unique_ptr<Socket> _socket;
std::mutex _socketMutex;
// Hold the state of the connection (OPEN, CLOSED, etc...)
std::atomic<ReadyState> _readyState;
OnCloseCallback _onCloseCallback;
std::string _closeReason;
mutable std::mutex _closeReasonMutex;
std::atomic<uint16_t> _closeCode;
std::atomic<size_t> _closeWireSize;
std::atomic<bool> _closeRemote;
// Data used for Per Message Deflate compression (with zlib)
WebSocketPerMessageDeflatePtr _perMessageDeflate;
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
std::atomic<bool> _enablePerMessageDeflate;
std::string _decompressedMessage;
std::string _compressedMessage;
// Used to control TLS connection behavior
SocketTLSOptions _socketTLSOptions;
// Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation;
mutable std::mutex _closingTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _closingTimePoint;
static const int kClosingMaximumWaitingDelayInMs;
// enable auto response to ping
std::atomic<bool> _enablePong;
static const bool kDefaultEnablePong;
// Optional ping and pong timeout
int _pingIntervalSecs;
std::atomic<bool> _pongReceived;
static const int kDefaultPingIntervalSecs;
bool _setCustomMessage;
std::string _kPingMessage;
SendMessageKind _pingType;
std::atomic<uint64_t> _pingCount;
// We record when ping are being sent so that we can know when to send the next one
mutable std::mutex _lastSendPingTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _lastSendPingTimePoint;
// If this function returns true, it is time to send a new ping
bool pingIntervalExceeded();
void initTimePointsAfterConnect();
// after calling close(), if no CLOSE frame answer is received back from the remote, we
// should close the connexion
bool closingDelayExceeded();
void sendCloseFrame(uint16_t code, const std::string& reason);
void closeSocketAndSwitchToClosedState(uint16_t code,
const std::string& reason,
size_t closeWireSize,
bool remote);
bool wakeUpFromPoll(uint64_t wakeUpCode);
bool flushSendBuffer();
bool sendOnSocket();
bool receiveFromSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
const IXWebSocketSendData& message,
bool compress,
const OnProgressCallback& onProgressCallback = nullptr);
template<class Iterator>
bool sendFragment(
wsheader_type::opcode_type type, bool fin, Iterator begin, Iterator end, bool compress);
void emitMessage(MessageKind messageKind,
const std::string& message,
bool compressedMessage,
const OnMessageCallback& onMessageCallback);
bool isSendBufferEmpty() const;
template<class Iterator>
void appendToSendBuffer(const std::vector<uint8_t>& header,
Iterator begin,
Iterator end,
uint64_t message_size,
uint8_t masking_key[4]);
unsigned getRandomUnsigned();
void unmaskReceiveBuffer(const wsheader_type& ws);
std::string getMergedChunks() const;
void setCloseReason(const std::string& reason);
const std::string& getCloseReason() const;
};
} // namespace ix

Wyświetl plik

@ -0,0 +1,9 @@
/*
* IXWebSocketVersion.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#define IX_WEBSOCKET_VERSION "11.4.4"

Wyświetl plik

@ -26,6 +26,165 @@
</GROUP>
</GROUP>
<GROUP id="{75439074-E50C-362F-1EDF-8B4BE9011259}" name="Source">
<GROUP id="{D0D95F57-3D9D-46D9-C126-25C3C7459AC5}" name="ixwebsocket">
<FILE id="pPOkoj" name="IXBase64.h" compile="0" resource="0" file="Source/ixwebsocket/IXBase64.h"/>
<FILE id="qVI3x2" name="IXBench.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXBench.cpp"/>
<FILE id="syb80Q" name="IXBench.h" compile="0" resource="0" file="Source/ixwebsocket/IXBench.h"/>
<FILE id="RpJzkB" name="IXCancellationRequest.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXCancellationRequest.cpp"/>
<FILE id="kdXAKI" name="IXCancellationRequest.h" compile="0" resource="0"
file="Source/ixwebsocket/IXCancellationRequest.h"/>
<FILE id="aamZdO" name="IXConnectionState.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXConnectionState.cpp"/>
<FILE id="BtXbWW" name="IXConnectionState.h" compile="0" resource="0"
file="Source/ixwebsocket/IXConnectionState.h"/>
<FILE id="RxeRZi" name="IXDNSLookup.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXDNSLookup.cpp"/>
<FILE id="Fz6v9l" name="IXDNSLookup.h" compile="0" resource="0" file="Source/ixwebsocket/IXDNSLookup.h"/>
<FILE id="Fhe5e2" name="IXExponentialBackoff.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXExponentialBackoff.cpp"/>
<FILE id="IaNSzp" name="IXExponentialBackoff.h" compile="0" resource="0"
file="Source/ixwebsocket/IXExponentialBackoff.h"/>
<FILE id="h7oBry" name="IXGetFreePort.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXGetFreePort.cpp"/>
<FILE id="Q6cGBn" name="IXGetFreePort.h" compile="0" resource="0" file="Source/ixwebsocket/IXGetFreePort.h"/>
<FILE id="pSqWe3" name="IXGzipCodec.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXGzipCodec.cpp"/>
<FILE id="t0Zcs0" name="IXGzipCodec.h" compile="0" resource="0" file="Source/ixwebsocket/IXGzipCodec.h"/>
<FILE id="ddCdsp" name="IXHttp.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXHttp.cpp"/>
<FILE id="WkOHup" name="IXHttp.h" compile="0" resource="0" file="Source/ixwebsocket/IXHttp.h"/>
<FILE id="aHrUkJ" name="IXHttpClient.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXHttpClient.cpp"/>
<FILE id="O3ifPl" name="IXHttpClient.h" compile="0" resource="0" file="Source/ixwebsocket/IXHttpClient.h"/>
<FILE id="eFGxxE" name="IXHttpServer.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXHttpServer.cpp"/>
<FILE id="IaxJJ6" name="IXHttpServer.h" compile="0" resource="0" file="Source/ixwebsocket/IXHttpServer.h"/>
<FILE id="o7gWn7" name="IXNetSystem.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXNetSystem.cpp"/>
<FILE id="R5LFhQ" name="IXNetSystem.h" compile="0" resource="0" file="Source/ixwebsocket/IXNetSystem.h"/>
<FILE id="zi85pc" name="IXProgressCallback.h" compile="0" resource="0"
file="Source/ixwebsocket/IXProgressCallback.h"/>
<FILE id="pa8Kbj" name="IXSelectInterrupt.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSelectInterrupt.cpp"/>
<FILE id="VZuUWe" name="IXSelectInterrupt.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSelectInterrupt.h"/>
<FILE id="j84Mkc" name="IXSelectInterruptEvent.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSelectInterruptEvent.cpp"/>
<FILE id="RUBIuY" name="IXSelectInterruptEvent.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSelectInterruptEvent.h"/>
<FILE id="HXwmGn" name="IXSelectInterruptFactory.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSelectInterruptFactory.cpp"/>
<FILE id="J7G90T" name="IXSelectInterruptFactory.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSelectInterruptFactory.h"/>
<FILE id="DjhB8v" name="IXSelectInterruptPipe.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSelectInterruptPipe.cpp"/>
<FILE id="cOe5ug" name="IXSelectInterruptPipe.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSelectInterruptPipe.h"/>
<FILE id="iYEr5X" name="IXSetThreadName.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSetThreadName.cpp"/>
<FILE id="lV9mJB" name="IXSetThreadName.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSetThreadName.h"/>
<FILE id="Z3cko8" name="IXSocket.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXSocket.cpp"/>
<FILE id="K89iuz" name="IXSocket.h" compile="0" resource="0" file="Source/ixwebsocket/IXSocket.h"/>
<FILE id="UW4XZp" name="IXSocketAppleSSL.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketAppleSSL.cpp"/>
<FILE id="goEr40" name="IXSocketAppleSSL.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketAppleSSL.h"/>
<FILE id="ZG0dAO" name="IXSocketConnect.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketConnect.cpp"/>
<FILE id="H8btLh" name="IXSocketConnect.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketConnect.h"/>
<FILE id="Q8y3eR" name="IXSocketFactory.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketFactory.cpp"/>
<FILE id="DgZ0NJ" name="IXSocketFactory.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketFactory.h"/>
<FILE id="vA7xBJ" name="IXSocketMbedTLS.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketMbedTLS.cpp"/>
<FILE id="NBsVY7" name="IXSocketMbedTLS.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketMbedTLS.h"/>
<FILE id="vffym1" name="IXSocketOpenSSL.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketOpenSSL.cpp"/>
<FILE id="rq1L43" name="IXSocketOpenSSL.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketOpenSSL.h"/>
<FILE id="DP4x6I" name="IXSocketServer.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketServer.cpp"/>
<FILE id="U62vhR" name="IXSocketServer.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketServer.h"/>
<FILE id="DK2PKa" name="IXSocketTLSOptions.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXSocketTLSOptions.cpp"/>
<FILE id="R2ahd8" name="IXSocketTLSOptions.h" compile="0" resource="0"
file="Source/ixwebsocket/IXSocketTLSOptions.h"/>
<FILE id="IrGHMo" name="IXStrCaseCompare.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXStrCaseCompare.cpp"/>
<FILE id="fmXfd5" name="IXStrCaseCompare.h" compile="0" resource="0"
file="Source/ixwebsocket/IXStrCaseCompare.h"/>
<FILE id="vrPgrU" name="IXUdpSocket.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXUdpSocket.cpp"/>
<FILE id="C4kXEa" name="IXUdpSocket.h" compile="0" resource="0" file="Source/ixwebsocket/IXUdpSocket.h"/>
<FILE id="ljkhdF" name="IXUniquePtr.h" compile="0" resource="0" file="Source/ixwebsocket/IXUniquePtr.h"/>
<FILE id="q3UHD4" name="IXUrlParser.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXUrlParser.cpp"/>
<FILE id="JbS05r" name="IXUrlParser.h" compile="0" resource="0" file="Source/ixwebsocket/IXUrlParser.h"/>
<FILE id="gCWgUe" name="IXUserAgent.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXUserAgent.cpp"/>
<FILE id="ZXs1Pm" name="IXUserAgent.h" compile="0" resource="0" file="Source/ixwebsocket/IXUserAgent.h"/>
<FILE id="GQ7boW" name="IXUtf8Validator.h" compile="0" resource="0"
file="Source/ixwebsocket/IXUtf8Validator.h"/>
<FILE id="rnALh0" name="IXUuid.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXUuid.cpp"/>
<FILE id="WcfqnP" name="IXUuid.h" compile="0" resource="0" file="Source/ixwebsocket/IXUuid.h"/>
<FILE id="P6VcmE" name="IXWebSocket.cpp" compile="1" resource="0" file="Source/ixwebsocket/IXWebSocket.cpp"/>
<FILE id="sf7P1K" name="IXWebSocket.h" compile="0" resource="0" file="Source/ixwebsocket/IXWebSocket.h"/>
<FILE id="npib4p" name="IXWebSocketCloseConstants.cpp" compile="1"
resource="0" file="Source/ixwebsocket/IXWebSocketCloseConstants.cpp"/>
<FILE id="vsumjK" name="IXWebSocketCloseConstants.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketCloseConstants.h"/>
<FILE id="T9bjry" name="IXWebSocketCloseInfo.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketCloseInfo.h"/>
<FILE id="LQoyiX" name="IXWebSocketErrorInfo.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketErrorInfo.h"/>
<FILE id="eL17m7" name="IXWebSocketHandshake.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXWebSocketHandshake.cpp"/>
<FILE id="ArSjWc" name="IXWebSocketHandshake.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketHandshake.h"/>
<FILE id="EI9MD8" name="IXWebSocketHandshakeKeyGen.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketHandshakeKeyGen.h"/>
<FILE id="jE9vTw" name="IXWebSocketHttpHeaders.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXWebSocketHttpHeaders.cpp"/>
<FILE id="h6BRFe" name="IXWebSocketHttpHeaders.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketHttpHeaders.h"/>
<FILE id="LBmQUe" name="IXWebSocketInitResult.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketInitResult.h"/>
<FILE id="FJf0OS" name="IXWebSocketMessage.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketMessage.h"/>
<FILE id="Zibknu" name="IXWebSocketMessageType.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketMessageType.h"/>
<FILE id="NkmVRB" name="IXWebSocketOpenInfo.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketOpenInfo.h"/>
<FILE id="ulTERZ" name="IXWebSocketPerMessageDeflate.cpp" compile="1"
resource="0" file="Source/ixwebsocket/IXWebSocketPerMessageDeflate.cpp"/>
<FILE id="k27DKW" name="IXWebSocketPerMessageDeflate.h" compile="0"
resource="0" file="Source/ixwebsocket/IXWebSocketPerMessageDeflate.h"/>
<FILE id="M8scBP" name="IXWebSocketPerMessageDeflateCodec.cpp" compile="1"
resource="0" file="Source/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp"/>
<FILE id="tBlaUn" name="IXWebSocketPerMessageDeflateCodec.h" compile="0"
resource="0" file="Source/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h"/>
<FILE id="rKyoIb" name="IXWebSocketPerMessageDeflateOptions.cpp" compile="1"
resource="0" file="Source/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp"/>
<FILE id="Ge5VH2" name="IXWebSocketPerMessageDeflateOptions.h" compile="0"
resource="0" file="Source/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h"/>
<FILE id="A8DdNw" name="IXWebSocketProxyServer.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXWebSocketProxyServer.cpp"/>
<FILE id="sUAP23" name="IXWebSocketProxyServer.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketProxyServer.h"/>
<FILE id="tYUG47" name="IXWebSocketSendData.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketSendData.h"/>
<FILE id="ndmbFI" name="IXWebSocketSendInfo.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketSendInfo.h"/>
<FILE id="IsH7aV" name="IXWebSocketServer.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXWebSocketServer.cpp"/>
<FILE id="m5MS1R" name="IXWebSocketServer.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketServer.h"/>
<FILE id="nb98Ow" name="IXWebSocketTransport.cpp" compile="1" resource="0"
file="Source/ixwebsocket/IXWebSocketTransport.cpp"/>
<FILE id="fFHF70" name="IXWebSocketTransport.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketTransport.h"/>
<FILE id="eAqAle" name="IXWebSocketVersion.h" compile="0" resource="0"
file="Source/ixwebsocket/IXWebSocketVersion.h"/>
</GROUP>
<GROUP id="{9F5970A9-8094-E7F3-7AC1-812AE5589B9F}" name="concurrency">
<FILE id="WQ2W15" name="BufferConsumer.h" compile="0" resource="0"
file="Source/concurrency/BufferConsumer.h"/>
@ -135,6 +294,10 @@
file="Source/components/VolumeComponent.h"/>
</GROUP>
<GROUP id="{85A33213-D880-BD92-70D8-1901DA6D23F0}" name="audio">
<FILE id="WDV6eI" name="AudioWebSocketServer.cpp" compile="1" resource="0"
file="Source/audio/AudioWebSocketServer.cpp"/>
<FILE id="RD41Jd" name="AudioWebSocketServer.h" compile="0" resource="0"
file="Source/audio/AudioWebSocketServer.h"/>
<FILE id="NWuowi" name="BitCrushEffect.cpp" compile="1" resource="0"
file="Source/audio/BitCrushEffect.cpp"/>
<FILE id="Bc8UeW" name="BitCrushEffect.h" compile="0" resource="0"