Remove ixWebSocket

This commit is contained in:
Kilian Hofmann 2022-09-04 00:50:49 +02:00
parent fbee417594
commit a820f7456a
123 changed files with 998 additions and 12947 deletions

View File

@ -18,7 +18,7 @@ BreakBeforeBraces: Linux
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CommentPragmas: "^ IWYU pragma:"
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
@ -27,7 +27,7 @@ DerivePointerBinding: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: true
IndentWidth: 4
IndentWidth: 2
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: true
@ -50,5 +50,5 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
Standard: c++14
SortIncludes: true
TabWidth: 4
TabWidth: 2
UseTab: Never

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
.idea/.name generated
View File

@ -1 +0,0 @@
GermanAirlinesVA_GAConnector

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

4
.idea/misc.xml generated
View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/GermanAirlinesVA-GAConnector.iml" filepath="$PROJECT_DIR$/.idea/GermanAirlinesVA-GAConnector.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,15 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/opt/llvm-mingw/bin/clang++",
"cStandard": "c17",
"intelliSenseMode": "windows-clang-x64"
}
],
"version": 4
}

34
.vscode/launch.json vendored
View File

@ -1,34 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "clang++-10 - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: clang++-10 build active file",
"miDebuggerPath": "/usr/bin/lldb-mi-10"
}
]
}

70
.vscode/settings.json vendored
View File

@ -1,67 +1,7 @@
{
"files.associations": {
"thread": "cpp",
"algorithm": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"shared_mutex": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"valarray": "cpp"
}
"cmake.generator": "Unix Makefiles",
"cmake.configureArgs": [
"-DIBM=ON"
],
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
}

28
.vscode/tasks.json vendored
View File

@ -1,28 +0,0 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: clang++-10 build active file",
"command": "/usr/bin/clang++-10",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}

View File

@ -12,9 +12,6 @@ set(PLUGIN_NAME GAConnector)
option(DEBUG "Debug symbols" OFF)
add_subdirectory(
ixwebsocket
)
add_subdirectory(
xplugin
)

View File

@ -12,36 +12,36 @@
namespace config
{
static inline std::map<std::string, std::string>
readConfig(const std::string &file)
{
std::ifstream config(file);
std::map<std::string, std::string> settings;
static inline std::map<std::string, std::string>
readConfig(const std::string &file)
{
std::ifstream config(file);
std::map<std::string, std::string> settings;
std::string line;
while (std::getline(config, line)) {
std::vector<std::string> fields = split(line, '=');
if (fields.size() >= 2) {
trim(fields[0]);
trim(fields[1]);
settings[fields[0]] = fields[1];
}
}
config.close();
return settings;
std::string line;
while (std::getline(config, line)) {
std::vector<std::string> fields = split(line, '=');
if (fields.size() >= 2) {
trim(fields[0]);
trim(fields[1]);
settings[fields[0]] = fields[1];
}
}
config.close();
return settings;
}
static inline void
writeConfig(const std::map<std::string, std::string> &config,
const std::string &file)
{
std::ofstream cfg(file);
for (const std::pair<const std::string, std::string> &entry : config) {
cfg << entry.first << '=' << entry.second << '\n';
}
cfg.close();
static inline void
writeConfig(const std::map<std::string, std::string> &config,
const std::string &file)
{
std::ofstream cfg(file);
for (const std::pair<const std::string, std::string> &entry : config) {
cfg << entry.first << '=' << entry.second << '\n';
}
cfg.close();
}
} // namespace config
#endif

View File

@ -21,36 +21,35 @@
*/
class Gate
{
private:
std::string designator;
double latitude;
double longitude;
std::vector<std::uint8_t> file;
private:
std::string designator;
double latitude;
double longitude;
std::vector<std::uint8_t> file;
public:
Gate(const std::string &designator, double latitude, double longitude)
{
public:
Gate(const std::string &designator, double latitude, double longitude)
{
this->designator = designator;
this->latitude = latitude;
this->longitude = longitude;
this->designator = designator;
this->latitude = latitude;
this->longitude = longitude;
file = std::vector<std::uint8_t>(18 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++; // Designator length
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr +=
this->designator.length() + 1; // Designator plus null termination
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += 8; // Latitude
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
file = std::vector<std::uint8_t>(18 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++; // Designator length
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1; // Designator plus null termination
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += 8; // Latitude
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif

View File

@ -11,47 +11,46 @@
*/
class PathSegment
{
private:
std::uint16_t altitude = 0;
std::uint16_t groundSpeed = 0;
double latitude = 0;
double longitude = 0;
std::vector<std::uint8_t> file;
private:
std::uint16_t altitude = 0;
std::uint16_t groundSpeed = 0;
double latitude = 0;
double longitude = 0;
std::vector<std::uint8_t> file;
public:
PathSegment() = default;
PathSegment(std::uint16_t altitude,
std::uint16_t groundSpeed,
double latitude,
double longitude)
{
this->altitude = altitude;
this->groundSpeed = groundSpeed;
this->latitude = latitude;
this->longitude = longitude;
public:
PathSegment() = default;
PathSegment(std::uint16_t altitude,
std::uint16_t groundSpeed,
double latitude,
double longitude)
{
this->altitude = altitude;
this->groundSpeed = groundSpeed;
this->latitude = latitude;
this->longitude = longitude;
file = std::vector<std::uint8_t>(20, 0);
std::uint8_t *bufPtr = file.data();
memcpy(bufPtr, &this->altitude, sizeof(this->altitude));
bufPtr += sizeof(this->altitude);
memcpy(bufPtr, &this->groundSpeed, sizeof(this->groundSpeed));
bufPtr += sizeof(this->groundSpeed);
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += sizeof(this->latitude);
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
file = std::vector<std::uint8_t>(20, 0);
std::uint8_t *bufPtr = file.data();
memcpy(bufPtr, &this->altitude, sizeof(this->altitude));
bufPtr += sizeof(this->altitude);
memcpy(bufPtr, &this->groundSpeed, sizeof(this->groundSpeed));
bufPtr += sizeof(this->groundSpeed);
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += sizeof(this->latitude);
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
friend bool operator==(const PathSegment &lhs, const PathSegment &rhs)
{
return lhs.altitude == rhs.altitude &&
lhs.groundSpeed == rhs.groundSpeed &&
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
}
friend bool operator!=(const PathSegment &lhs, const PathSegment &rhs)
{
return !(lhs == rhs);
}
friend bool operator==(const PathSegment &lhs, const PathSegment &rhs)
{
return lhs.altitude == rhs.altitude && lhs.groundSpeed == rhs.groundSpeed &&
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
}
friend bool operator!=(const PathSegment &lhs, const PathSegment &rhs)
{
return !(lhs == rhs);
}
};

View File

@ -6,19 +6,19 @@
class Path
{
private:
std::uint64_t count = 0;
std::vector<std::uint8_t> file;
private:
std::uint64_t count = 0;
std::vector<std::uint8_t> file;
public:
void addSegment(PathSegment segment)
{
file.resize(file.size() + segment.getBinaryLength());
std::uint8_t *bufPtr = file.data() + count * segment.getBinaryLength();
memcpy(bufPtr, segment.getBinaryData(), segment.getBinaryLength());
count++;
}
public:
void addSegment(PathSegment segment)
{
file.resize(file.size() + segment.getBinaryLength());
std::uint8_t *bufPtr = file.data() + count * segment.getBinaryLength();
memcpy(bufPtr, segment.getBinaryData(), segment.getBinaryLength());
count++;
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};

View File

@ -23,92 +23,91 @@
*/
class Runway
{
private:
std::string designator;
double latitudeStart;
double longitudeStart;
std::uint8_t width;
std::uint16_t length;
std::uint16_t trueHeading;
std::vector<std::uint8_t> file;
private:
std::string designator;
double latitudeStart;
double longitudeStart;
std::uint8_t width;
std::uint16_t length;
std::uint16_t trueHeading;
std::vector<std::uint8_t> file;
public:
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
double latitudeEnd,
double longitudeEnd,
double width)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = (std::uint8_t)std::round(util::to_feet(width));
double dist = util::distanceEarth(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd);
this->length = (std::uint16_t)std::round(util::to_feet(dist));
this->trueHeading =
(std::uint16_t)std::round(util::bearing(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd));
public:
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
double latitudeEnd,
double longitudeEnd,
double width)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = (std::uint8_t)std::round(util::to_feet(width));
double dist = util::distanceEarth(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd);
this->length = (std::uint16_t)std::round(util::to_feet(dist));
this->trueHeading = (std::uint16_t)std::round(util::bearing(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd));
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
std::uint8_t width,
std::uint16_t length,
std::uint16_t trueHeading)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = width;
this->length = length;
this->trueHeading = trueHeading;
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
std::uint8_t width,
std::uint16_t length,
std::uint16_t trueHeading)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = width;
this->length = length;
this->trueHeading = trueHeading;
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif

View File

@ -32,160 +32,152 @@
namespace simulatorDatabase
{
static inline void toFile(
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
const std::string &file)
{
std::uint8_t null = 0;
std::ofstream out(file, std::fstream::binary);
static inline void toFile(
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
const std::string &file)
{
std::uint8_t null = 0;
std::ofstream out(file, std::fstream::binary);
// File Header
std::uint8_t header[] = {'V', 'G', 'A', 'S', 0, CURRENT_VERSION};
out.write(reinterpret_cast<const char *>(header), 6);
// Num Airports
std::uint16_t numAirports = airports.size();
out.write(reinterpret_cast<const char *>(&numAirports),
sizeof(numAirports));
// Airport
for (const std::pair<const std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
&airport : airports) {
std::string icao = airport.first;
std::vector<Gate> gates = airport.second.first;
std::vector<Runway> runways = airport.second.second;
// ICAO
std::uint8_t icaoLength = icao.length();
out.write(reinterpret_cast<const char *>(&icaoLength),
sizeof(icaoLength));
out.write(icao.c_str(), icaoLength);
out.write(reinterpret_cast<const char *>(&null), sizeof(null));
// Gates
std::uint16_t numGates = gates.size();
out.write(reinterpret_cast<const char *>(&numGates),
sizeof(numGates));
for (Gate &gate : gates) {
out.write(reinterpret_cast<const char *>(gate.getBinaryData()),
(std::streamsize)gate.getBinaryLength());
}
// Runways
std::uint8_t numRunways = runways.size();
out.write(reinterpret_cast<const char *>(&numRunways),
sizeof(numRunways));
for (Runway &runway : runways) {
out.write(
reinterpret_cast<const char *>(runway.getBinaryData()),
(std::streamsize)runway.getBinaryLength());
}
}
out.close();
// File Header
std::uint8_t header[] = {'V', 'G', 'A', 'S', 0, CURRENT_VERSION};
out.write(reinterpret_cast<const char *>(header), 6);
// Num Airports
std::uint16_t numAirports = airports.size();
out.write(reinterpret_cast<const char *>(&numAirports),
sizeof(numAirports));
// Airport
for (const std::pair<const std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
&airport : airports) {
std::string icao = airport.first;
std::vector<Gate> gates = airport.second.first;
std::vector<Runway> runways = airport.second.second;
// ICAO
std::uint8_t icaoLength = icao.length();
out.write(reinterpret_cast<const char *>(&icaoLength),
sizeof(icaoLength));
out.write(icao.c_str(), icaoLength);
out.write(reinterpret_cast<const char *>(&null), sizeof(null));
// Gates
std::uint16_t numGates = gates.size();
out.write(reinterpret_cast<const char *>(&numGates), sizeof(numGates));
for (Gate &gate : gates) {
out.write(reinterpret_cast<const char *>(gate.getBinaryData()),
(std::streamsize)gate.getBinaryLength());
}
// Runways
std::uint8_t numRunways = runways.size();
out.write(reinterpret_cast<const char *>(&numRunways),
sizeof(numRunways));
for (Runway &runway : runways) {
out.write(reinterpret_cast<const char *>(runway.getBinaryData()),
(std::streamsize)runway.getBinaryLength());
}
}
out.close();
}
static inline std::map<std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
readVersion1(std::ifstream &in)
{
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
std::uint16_t numAirports;
in.read(reinterpret_cast<char *>(&numAirports), sizeof(numAirports));
for (int i = 0; i < numAirports; i++) {
// ICAO
std::uint8_t icaoLength;
in.read(reinterpret_cast<char *>(&icaoLength), sizeof(icaoLength));
char *icao = static_cast<char *>(calloc(icaoLength + 1, sizeof(char)));
in.read(icao, icaoLength + 1);
// Gates
std::uint16_t numGates;
in.read(reinterpret_cast<char *>(&numGates), sizeof(numGates));
for (int j = 0; j < numGates; j++) {
// ICAO
std::uint8_t designatorLength;
in.read(reinterpret_cast<char *>(&designatorLength),
sizeof(designatorLength));
char *designator =
static_cast<char *>(calloc(designatorLength + 1, sizeof(char)));
in.read(designator, designatorLength + 1);
// Latitude
double latitude;
in.read(reinterpret_cast<char *>(&latitude), sizeof(latitude));
// Latitude
double longitude;
in.read(reinterpret_cast<char *>(&longitude), sizeof(longitude));
airports[icao].first.emplace_back(designator, latitude, longitude);
}
// Runways
std::uint8_t numRunways;
in.read(reinterpret_cast<char *>(&numRunways), sizeof(numRunways));
for (int j = 0; j < numRunways; j++) {
// ICAO
std::uint8_t designatorLength;
in.read(reinterpret_cast<char *>(&designatorLength),
sizeof(designatorLength));
char *designator =
static_cast<char *>(calloc(designatorLength + 1, sizeof(char)));
in.read(designator, designatorLength + 1);
// Latitude
double latitude;
in.read(reinterpret_cast<char *>(&latitude), sizeof(latitude));
// Latitude
double longitude;
in.read(reinterpret_cast<char *>(&longitude), sizeof(longitude));
// Width
std::uint8_t width;
in.read(reinterpret_cast<char *>(&width), sizeof(width));
// Length
std::uint16_t length;
in.read(reinterpret_cast<char *>(&length), sizeof(length));
// True Heading
std::uint16_t trueHeading;
in.read(reinterpret_cast<char *>(&trueHeading), sizeof(trueHeading));
airports[icao].second.emplace_back(designator,
latitude,
longitude,
width,
length,
trueHeading);
}
}
static inline std::map<std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
readVersion1(std::ifstream &in)
{
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
in.close();
std::uint16_t numAirports;
in.read(reinterpret_cast<char *>(&numAirports), sizeof(numAirports));
return airports;
}
for (int i = 0; i < numAirports; i++) {
// ICAO
std::uint8_t icaoLength;
in.read(reinterpret_cast<char *>(&icaoLength), sizeof(icaoLength));
char *icao =
static_cast<char *>(calloc(icaoLength + 1, sizeof(char)));
in.read(icao, icaoLength + 1);
// Gates
std::uint16_t numGates;
in.read(reinterpret_cast<char *>(&numGates), sizeof(numGates));
for (int j = 0; j < numGates; j++) {
// ICAO
std::uint8_t designatorLength;
in.read(reinterpret_cast<char *>(&designatorLength),
sizeof(designatorLength));
char *designator = static_cast<char *>(
calloc(designatorLength + 1, sizeof(char)));
in.read(designator, designatorLength + 1);
// Latitude
double latitude;
in.read(reinterpret_cast<char *>(&latitude), sizeof(latitude));
// Latitude
double longitude;
in.read(reinterpret_cast<char *>(&longitude),
sizeof(longitude));
static inline std::map<std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
fromFile(const std::string &file)
{
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
std::ifstream in(file);
airports[icao].first.emplace_back(designator,
latitude,
longitude);
}
// Runways
std::uint8_t numRunways;
in.read(reinterpret_cast<char *>(&numRunways), sizeof(numRunways));
for (int j = 0; j < numRunways; j++) {
// ICAO
std::uint8_t designatorLength;
in.read(reinterpret_cast<char *>(&designatorLength),
sizeof(designatorLength));
char *designator = static_cast<char *>(
calloc(designatorLength + 1, sizeof(char)));
in.read(designator, designatorLength + 1);
// Latitude
double latitude;
in.read(reinterpret_cast<char *>(&latitude), sizeof(latitude));
// Latitude
double longitude;
in.read(reinterpret_cast<char *>(&longitude),
sizeof(longitude));
// Width
std::uint8_t width;
in.read(reinterpret_cast<char *>(&width), sizeof(width));
// Length
std::uint16_t length;
in.read(reinterpret_cast<char *>(&length), sizeof(length));
// True Heading
std::uint16_t trueHeading;
in.read(reinterpret_cast<char *>(&trueHeading),
sizeof(trueHeading));
airports[icao].second.emplace_back(designator,
latitude,
longitude,
width,
length,
trueHeading);
}
}
in.close();
return airports;
// File Header
char ident[5];
in.read(ident, 5);
if (strcmp(ident, "VGAS") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version;
in.read(reinterpret_cast<char *>(&version), 1);
static inline std::map<std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
fromFile(const std::string &file)
{
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
std::ifstream in(file);
// File Header
char ident[5];
in.read(ident, 5);
if (strcmp(ident, "VGAS") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version;
in.read(reinterpret_cast<char *>(&version), 1);
if (version == 1) {
return readVersion1(in);
}
return airports;
if (version == 1) {
return readVersion1(in);
}
return airports;
}
} // namespace simulatorDatabase
#endif

View File

@ -9,45 +9,45 @@
// trim from start (in place)
static inline void ltrim(std::string &s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s)
{
s.erase(std::find_if(s.rbegin(),
s.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
s.end());
s.erase(std::find_if(s.rbegin(),
s.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
s.end());
}
static inline std::string rtrim_copy(std::string s)
{
rtrim(s);
return s;
rtrim(s);
return s;
}
// trim from both ends (in place)
static inline void trim(std::string &s)
{
ltrim(s);
rtrim(s);
ltrim(s);
rtrim(s);
}
static inline std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> result;
std::stringstream ss(s);
std::string item;
std::vector<std::string> result;
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) {
result.push_back(item);
}
while (getline(ss, item, delim)) {
result.push_back(item);
}
return result;
return result;
}
#endif

View File

@ -39,264 +39,258 @@
namespace util
{
static inline double to_feet(double value) { return value * 3.280839895; }
static inline double to_feet(double value) { return value * 3.280839895; }
static inline double to_degrees(double value) { return value * 180 / M_PI; }
static inline double to_degrees(double value) { return value * 180 / M_PI; }
static inline double to_radians(double value) { return value * M_PI / 180; }
static inline double to_radians(double value) { return value * M_PI / 180; }
static inline double normalize(double value)
{
return fmod(value + 360, 360);
}
static inline double normalize(double value)
{
return fmod(value + 360, 360);
}
static inline double bearing(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double y = sin(to_radians(toLongitude) - to_radians(fromLongitude)) *
cos(to_radians(toLatitude));
double x = cos(to_radians(fromLatitude)) * sin(to_radians(toLatitude)) -
sin(to_radians(fromLatitude)) * cos(to_radians(toLatitude)) *
cos(to_radians(toLongitude) - to_radians(fromLongitude));
static inline double bearing(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double y = sin(to_radians(toLongitude) - to_radians(fromLongitude)) *
cos(to_radians(toLatitude));
double x = cos(to_radians(fromLatitude)) * sin(to_radians(toLatitude)) -
sin(to_radians(fromLatitude)) * cos(to_radians(toLatitude)) *
cos(to_radians(toLongitude) - to_radians(fromLongitude));
return normalize(to_degrees(atan2(y, x)));
}
return normalize(to_degrees(atan2(y, x)));
}
static inline double distanceEarth(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double lat1r, lon1r, lat2r, lon2r, u, v;
lat1r = to_radians(fromLatitude);
lon1r = to_radians(fromLongitude);
lat2r = to_radians(toLatitude);
lon2r = to_radians(toLongitude);
u = sin((lat2r - lat1r) / 2);
v = sin((lon2r - lon1r) / 2);
return 2.0 * EARTH_M *
asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
}
static inline double distanceEarth(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double lat1r, lon1r, lat2r, lon2r, u, v;
lat1r = to_radians(fromLatitude);
lon1r = to_radians(fromLongitude);
lat2r = to_radians(toLatitude);
lon2r = to_radians(toLongitude);
u = sin((lat2r - lat1r) / 2);
v = sin((lon2r - lon1r) / 2);
return 2.0 * EARTH_M * asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
}
template <typename T>
static inline std::vector<T>
select_T(const std::vector<T> &inVec,
std::function<bool(const T &)> predicate)
{
std::vector<T> result;
copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
return result;
}
template <typename T>
static inline std::vector<T>
select_T(const std::vector<T> &inVec,
std::function<bool(const T &)> predicate)
{
std::vector<T> result;
copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
return result;
}
#if defined APL || defined LIN
static unsigned long get_size_by_fd(int fd)
{
struct stat buf {
};
if (fstat(fd, &buf) < 0)
return 0;
return buf.st_size;
}
static unsigned long get_size_by_fd(int fd)
{
struct stat buf {
};
if (fstat(fd, &buf) < 0)
return 0;
return buf.st_size;
}
#endif
static void to_hex(const char *hash, char *buffer)
{
for (int i = 0; i < MD5LEN; i++) {
if (buffer != nullptr) {
sprintf(&buffer[2 * i], "%02x", hash[i] & 0xff);
}
}
static void to_hex(const char *hash, char *buffer)
{
for (int i = 0; i < MD5LEN; i++) {
if (buffer != nullptr) {
sprintf(&buffer[2 * i], "%02x", hash[i] & 0xff);
}
}
}
#ifdef IBM
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> toLog)
{
BOOL bResult = FALSE;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
HANDLE hFile;
BYTE rgbFile[BUFSIZE] = {0};
DWORD cbRead = 0;
BYTE rgbHash[MD5LEN] = {0};
DWORD cbHash = 0;
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> toLog)
{
BOOL bResult = FALSE;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
HANDLE hFile;
BYTE rgbFile[BUFSIZE] = {0};
DWORD cbRead = 0;
BYTE rgbHash[MD5LEN] = {0};
DWORD cbHash = 0;
// Logic to check usage goes here.
hFile = CreateFile(filepath,
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
// Logic to check usage goes here.
hFile = CreateFile(filepath,
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
// Get handle to the crypto provider
if (!CryptAcquireContext(&hProv,
nullptr,
nullptr,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
std::stringstream debug_msg;
debug_msg << "CryptAcquireContext returned with error "
<< GetLastError();
toLog(debug_msg.str());
// Get handle to the crypto provider
if (!CryptAcquireContext(&hProv,
nullptr,
nullptr,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
std::stringstream debug_msg;
debug_msg << "CryptAcquireContext returned with error " << GetLastError();
toLog(debug_msg.str());
CloseHandle(hFile);
return 1;
}
CloseHandle(hFile);
return 1;
}
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
std::stringstream debug_msg;
debug_msg << "CryptCreateHash returned with error "
<< GetLastError();
toLog(debug_msg.str());
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
std::stringstream debug_msg;
debug_msg << "CryptCreateHash returned with error " << GetLastError();
toLog(debug_msg.str());
CloseHandle(hFile);
CryptReleaseContext(hProv, 0);
return 1;
}
CloseHandle(hFile);
CryptReleaseContext(hProv, 0);
return 1;
}
while (
(bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, nullptr))) {
if (0 == cbRead) {
break;
}
while ((bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, nullptr))) {
if (0 == cbRead) {
break;
}
if (!CryptHashData(hHash, rgbFile, cbRead, 0)) {
std::stringstream debug_msg;
debug_msg << "CryptHashData returned with error "
<< GetLastError();
toLog(debug_msg.str());
if (!CryptHashData(hHash, rgbFile, cbRead, 0)) {
std::stringstream debug_msg;
debug_msg << "CryptHashData returned with error " << GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
}
if (!bResult) {
std::stringstream debug_msg;
debug_msg << "ReadFile returned with error " << GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
cbHash = MD5LEN;
if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) {
to_hex((char *)rgbHash, lastHash);
} else {
std::stringstream debug_msg;
debug_msg << "CryptGetHashParam returned with error "
<< GetLastError();
toLog(debug_msg.str());
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 0;
return 1;
}
}
if (!bResult) {
std::stringstream debug_msg;
debug_msg << "ReadFile returned with error " << GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
cbHash = MD5LEN;
if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) {
to_hex((char *)rgbHash, lastHash);
} else {
std::stringstream debug_msg;
debug_msg << "CryptGetHashParam returned with error " << GetLastError();
toLog(debug_msg.str());
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
CloseHandle(hFile);
return 0;
}
#endif
#ifdef APL
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> &toLog)
{
int file_descript;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> &toLog)
{
int file_descript;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
file_descript = open(filepath, O_RDONLY);
if (file_descript < 0)
return 1;
file_descript = open(filepath, O_RDONLY);
if (file_descript < 0)
return 1;
file_size = get_size_by_fd(file_descript);
file_size = get_size_by_fd(file_descript);
file_buffer =
(char *)mmap(0, file_size, PROT_READ, MAP_SHARED, file_descript, 0);
file_buffer =
(char *)mmap(0, file_size, PROT_READ, MAP_SHARED, file_descript, 0);
CC_MD5_CTX context;
CC_MD5_Init(&context);
CC_MD5_Update(&context, file_buffer, (CC_LONG)file_size);
CC_MD5_Final(result, &context);
CC_MD5_CTX context;
CC_MD5_Init(&context);
CC_MD5_Update(&context, file_buffer, (CC_LONG)file_size);
CC_MD5_Final(result, &context);
munmap(file_buffer, file_size);
close(file_descript);
munmap(file_buffer, file_size);
close(file_descript);
to_hex((char *)result, lastHash);
return 0;
}
to_hex((char *)result, lastHash);
return 0;
}
#pragma clang diagnostic pop
#endif
#ifdef LIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline int
generateMD5(const char *filepath,
char *buffer,
const std::function<void(const std::string)> &toLog)
{
int file_descriptor;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
static inline int
generateMD5(const char *filepath,
char *buffer,
const std::function<void(const std::string)> &toLog)
{
int file_descriptor;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
file_descriptor = open(filepath, O_RDONLY);
if (file_descriptor < 0)
return 1;
file_descriptor = open(filepath, O_RDONLY);
if (file_descriptor < 0)
return 1;
file_size = get_size_by_fd(file_descriptor);
if (file_size == 0)
return 1;
file_size = get_size_by_fd(file_descriptor);
if (file_size == 0)
return 1;
file_buffer = (char *)
mmap(nullptr, file_size, PROT_READ, MAP_SHARED, file_descriptor, 0);
file_buffer = (char *)
mmap(nullptr, file_size, PROT_READ, MAP_SHARED, file_descriptor, 0);
MD5((unsigned char *)file_buffer, file_size, result);
MD5((unsigned char *)file_buffer, file_size, result);
munmap(file_buffer, file_size);
close(file_descriptor);
munmap(file_buffer, file_size);
close(file_descriptor);
to_hex((char *)result, buffer);
return 0;
}
to_hex((char *)result, buffer);
return 0;
}
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline void setThreadName(const std::string &name)
{
static inline void setThreadName(const std::string &name)
{
#ifdef APL
//
// 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());
//
// 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());
#endif
#ifdef LIN
//
// 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());
//
// 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());
#endif
}
}
#pragma clang diagnostic pop
} // namespace util

View File

@ -2,7 +2,7 @@
shopt -s globstar
GLOBIGNORE='**/XPLM/**:XPLM/**:**/XPLM:**/ixwebsocket/**:ixwebsocket/**:**/ixwebsocket:**/nlohmann/**:nlohmann/**:**/nlohmann:**/XPSDK/**:XPSDK/**:**/XPSDK:**/build*/**:build*/**:**/build*'
GLOBIGNORE='**/XPLM/**:XPLM/**:**/XPLM:**/nlohmann/**:nlohmann/**:**/nlohmann:**/XPSDK/**:XPSDK/**:**/XPSDK:**/build*/**:build*/**:**/build*'
clang-format -verbose -i **/*.cpp
clang-format -verbose -i **/*.h

View File

@ -1,99 +0,0 @@
file(GLOB ixwebsocket CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/ixwebsocket/*.cpp)
add_library(ixwebsocket SHARED
${ixwebsocket}
)
target_include_directories(ixwebsocket PRIVATE
${CMAKE_SOURCE_DIR}/ixwebsocket/include
)
set_target_properties(ixwebsocket PROPERTIES
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/ixwebsocket/include
)
target_compile_options(ixwebsocket PRIVATE
-Wall
-Wextra
-pedantic
#-fvisibility=hidden
)
if(DEBUG)
target_compile_options(ixwebsocket PRIVATE
-g
)
target_link_options(ixwebsocket PRIVATE
-g
)
else()
target_compile_options(ixwebsocket PRIVATE
-O2
)
endif()
if(APPLE)
message("Building ixwebsocket for MacOSX Universal into ${PROJECT_BINARY_DIR}/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/Plugin/${PLUGIN_NAME}
BUILD_WITH_INSTALL_NAME_DIR TRUE
)
target_compile_options(ixwebsocket PRIVATE
#"SHELL:-arch i386"
"SHELL:-arch x86_64"
)
target_link_options(ixwebsocket PRIVATE
#"SHELL:-arch i386"
"SHELL:-arch x86_64"
)
target_link_libraries(ixwebsocket PRIVATE
"-framework Security"
)
elseif(UNIX)
message("Building ixwebsocket for Linux ${BIT} into ${PROJECT_BINARY_DIR}/Plugin/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/Plugin/${PLUGIN_NAME}/${BIT}
)
target_compile_options(ixwebsocket PRIVATE
-nodefaultlibs
)
if(BIT STREQUAL "32")
target_compile_options(ixwebsocket PRIVATE
-m32
)
target_link_options(ixwebsocket PRIVATE
-m32
)
endif()
target_link_libraries(ixwebsocket PRIVATE
crypto
pthread
)
elseif(WIN32)
message("Building ixwebsocket for Windows ${BIT} into ${PROJECT_BINARY_DIR}/Plugin/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/Plugin/${PLUGIN_NAME}/${BIT}
)
if(DEBUG)
target_compile_options(ixwebsocket PRIVATE
-gcodeview
)
target_link_options(ixwebsocket PRIVATE
-Wl,-pdb=
)
endif()
target_link_options(ixwebsocket PRIVATE
-static-libgcc
-static-libstdc++
)
target_link_libraries(ixwebsocket PRIVATE
ws2_32.lib
)
endif()
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)

View File

@ -1,56 +0,0 @@
/*
* 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

View File

@ -1,39 +0,0 @@
/*
* 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

View File

@ -1,54 +0,0 @@
/*
* 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

View File

@ -1,190 +0,0 @@
/*
* 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>
// 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)
{
;
}
struct addrinfo *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 res;
}
struct addrinfo *
DNSLookup::resolve(std::string &errMsg,
const CancellationRequest &isCancellationRequested,
bool cancellable)
{
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested);
}
void DNSLookup::release(struct addrinfo *addr) { freeaddrinfo(addr); }
struct addrinfo *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);
}
struct addrinfo *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;
struct addrinfo *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(struct addrinfo *addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
}
struct addrinfo *DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
} // namespace ix

View File

@ -1,30 +0,0 @@
/*
* 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)
{
uint32_t waitTime = (retryCount < 26) ? (std::pow(2, retryCount) * 100) : 0;
if (waitTime < minWaitBetweenReconnectionRetries) {
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries || waitTime == 0) {
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
}
} // namespace ix

View File

@ -1,95 +0,0 @@
/*
* 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

View File

@ -1,186 +0,0 @@
/*
* 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
#ifdef IXWEBSOCKET_USE_DEFLATE
#include <libdeflate.h>
#endif
namespace ix
{
std::string gzipCompress(const std::string &str)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return std::string();
#else
#ifdef IXWEBSOCKET_USE_DEFLATE
int compressionLevel = 6;
struct libdeflate_compressor *compressor;
compressor = libdeflate_alloc_compressor(compressionLevel);
const void *uncompressed_data = str.data();
size_t uncompressed_size = str.size();
void *compressed_data;
size_t actual_compressed_size;
size_t max_compressed_size;
max_compressed_size =
libdeflate_gzip_compress_bound(compressor, uncompressed_size);
compressed_data = malloc(max_compressed_size);
if (compressed_data == NULL) {
return std::string();
}
actual_compressed_size = libdeflate_gzip_compress(compressor,
uncompressed_data,
uncompressed_size,
compressed_data,
max_compressed_size);
libdeflate_free_compressor(compressor);
if (actual_compressed_size == 0) {
free(compressed_data);
return std::string();
}
std::string out;
out.assign(reinterpret_cast<char *>(compressed_data),
actual_compressed_size);
free(compressed_data);
return out;
#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_DEFLATE
#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
#ifdef IXWEBSOCKET_USE_DEFLATE
struct libdeflate_decompressor *decompressor;
decompressor = libdeflate_alloc_decompressor();
const void *compressed_data = in.data();
size_t compressed_size = in.size();
// Retrieve uncompressed size from the trailer of the gziped data
const uint8_t *ptr = reinterpret_cast<const uint8_t *>(&in.front());
auto uncompressed_size =
loadDecompressedGzipSize(&ptr[compressed_size - 4]);
// Use it to redimension our output buffer
out.resize(uncompressed_size);
libdeflate_result result = libdeflate_gzip_decompress(decompressor,
compressed_data,
compressed_size,
&out.front(),
uncompressed_size,
NULL);
libdeflate_free_decompressor(decompressor);
return result == LIBDEFLATE_SUCCESS;
#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_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB
}
} // namespace ix

View File

@ -1,208 +0,0 @@
/*
* 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, 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

View File

@ -1,724 +0,0 @@
/*
* 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 <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;
if (!UrlParser::parse(url, protocol, host, path, query, port)) {
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 << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress) {
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";
}
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 isCancellationRequested =
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
bool success =
_socket->connect(host, port, errMsg, isCancellationRequested);
if (!success) {
std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotConnect,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Make a new cancellation object dealing with transfer timeout
isCancellationRequested =
makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
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)) {
std::string errorMsg("Cannot send request");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::SendError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uploadSize = req.size();
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) {
std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(
code,
description,
HttpErrorCode::CannotReadStatusLine,
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) {
std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::HeaderParsingError,
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;
payload.reserve(contentLength);
auto chunkResult = _socket->readBytes(contentLength,
args->onProgressCallback,
isCancellationRequested);
if (!chunkResult.first) {
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload += chunkResult.second;
} else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked") {
std::stringstream ss;
while (true) {
lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second;
if (!lineResult.first) {
return std::make_shared<HttpResponse>(
code,
description,
HttpErrorCode::ChunkReadError,
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);
}
payload.reserve(payload.size() + (size_t)chunkSize);
// Read a chunk
auto chunkResult = _socket->readBytes((size_t)chunkSize,
args->onProgressCallback,
isCancellationRequested);
if (!chunkResult.first) {
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(
code,
description,
HttpErrorCode::ChunkReadError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload += chunkResult.second;
// Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first) {
return std::make_shared<HttpResponse>(
code,
description,
HttpErrorCode::ChunkReadError,
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

View File

@ -1,239 +0,0 @@
/*
* 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 <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()));
}
} // 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)
: SocketServer(port, host, backlog, maxConnections, addressFamily),
_connectedClientsCount(0), _timeoutSecs(timeoutSecs)
{
setDefaultConnectionCallback();
}
HttpServer::~HttpServer() { stop(); }
void HttpServer::stop()
{
stopAcceptingConnections();
// FIXME: cancelling / closing active clients ...
SocketServer::stop();
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback &callback)
{
_onConnectionCallback = callback;
}
void HttpServer::handleConnection(
std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
_connectedClientsCount++;
auto ret = Http::parseRequest(socket, _timeoutSecs);
// FIXME: handle errors in parseRequest
if (std::get<0>(ret)) {
auto response =
_onConnectionCallback(std::get<2>(ret), connectionState);
if (!Http::sendResponse(response, socket)) {
logError("Cannot send response");
}
}
connectionState->setTerminated();
_connectedClientsCount--;
}
size_t HttpServer::getConnectedClientsCount() { return _connectedClientsCount; }
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();
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

View File

@ -1,296 +0,0 @@
/*
* IXNetSystem.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXNetSystem.h"
#include <cstdint>
#include <cstdio>
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
}
//
// 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
//
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
#ifdef _WIN32
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
//
// 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

View File

@ -1,27 +0,0 @@
/*
* 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; }
} // namespace ix

View File

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

View File

@ -1,156 +0,0 @@
/*
* 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

View File

@ -1,81 +0,0 @@
/*
* 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

View File

@ -1,373 +0,0 @@
/*
* 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 <stdint.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)
{
//
// 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;
if (selectInterrupt) {
interruptFd = selectInterrupt->getFd();
if (interruptFd != -1) {
nfds = 2;
fds[1].fd = interruptFd;
fds[1].events = POLLIN;
}
}
int ret = ix::poll(fds, nfds, timeoutMs);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0) {
pollResult = PollResultType::Error;
} else if (ret == 0) {
pollResult = PollResultType::Timeout;
} else if (interruptFd != -1 && fds[1].revents & POLLIN) {
uint64_t value = selectInterrupt->read();
if (value == SelectInterrupt::kSendRequest) {
pollResult = PollResultType::SendRequest;
} else if (value == SelectInterrupt::kCloseRequest) {
pollResult = PollResultType::CloseRequest;
}
} 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;
}
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::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 CancellationRequest &isCancellationRequested)
{
std::array<uint8_t, 1 << 14> readBuffer;
std::vector<uint8_t> output;
while (output.size() != length) {
if (isCancellationRequested && isCancellationRequested()) {
const std::string errorMsg("Cancellation Requested");
return std::make_pair(false, errorMsg);
}
size_t size = std::min(readBuffer.size(), length - output.size());
ssize_t ret = recv((char *)&readBuffer[0], size);
if (ret > 0) {
output.insert(output.end(),
readBuffer.begin(),
readBuffer.begin() + ret);
} else if (ret <= 0 && !Socket::isWaitNeeded()) {
const std::string errorMsg("Recv Error");
return std::make_pair(false, errorMsg);
}
if (onProgressCallback)
onProgressCallback((int)output.size(), (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

View File

@ -1,307 +0,0 @@
/*
* 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);
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

View File

@ -1,150 +0,0 @@
/*
* 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
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;
auto selectInterrupt = ix::make_unique<SelectInterrupt>();
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;
}
}
Socket::closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
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);
struct addrinfo *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; address != nullptr; address = address->ai_next) {
//
// Second try to connect to the remote host
//
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
if (sockfd != -1) {
break;
}
}
freeaddrinfo(res);
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

View File

@ -1,60 +0,0 @@
/*
* 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

View File

@ -1,350 +0,0 @@
/*
* 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 <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 (!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

View File

@ -1,761 +0,0 @@
/*
* 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,
const std::string &hostname,
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) {
X509_NAME_ENTRY *cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509 *)server_cert),
cn_pos);
if (cn_entry) {
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) {
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.)
X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
#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

View File

@ -1,467 +0,0 @@
/*
* 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;
_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);
setThreadName("SocketServer::accept");
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: " <<
#ifndef _WIN32
strerror(Socket::getErrno());
#else
Socket::getErrno();
#endif
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()
{
setThreadName("SocketServer::GC");
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);
_conditionVariableGC.wait(lock);
}
}
}
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
_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

View File

@ -1,86 +0,0 @@
/*
* 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 << " ciphers = " << ciphers << std::endl;
return ss.str();
}
} // namespace ix

View File

@ -1,39 +0,0 @@
/*
* 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

View File

@ -1,125 +0,0 @@
/*
* 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

View File

@ -1,363 +0,0 @@
/*
* 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;
}
} // namespace
namespace ix
{
bool UrlParser::parse(const std::string &url,
std::string &protocol,
std::string &host,
std::string &path,
std::string &query,
int &port)
{
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;
if (!res.GetPort(&port)) {
if (protocol == "ws" || protocol == "http") {
port = 80;
} else if (protocol == "wss" || protocol == "https") {
port = 443;
} else {
// Invalid protocol. Should be caught by regex check
// but this missing branch trigger cpplint linter.
return false;
}
}
if (path.empty()) {
path = "/";
} else if (path[0] != '/') {
path = '/' + path;
}
if (!query.empty()) {
path += "?";
path += query;
}
return true;
}
} // namespace ix

View File

@ -1,92 +0,0 @@
/*
* 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

View File

@ -1,75 +0,0 @@
/*
* 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 <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

View File

@ -1,588 +0,0 @@
/*
* 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>
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)
{
_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::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();
}
return status;
}
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate)
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_socketTLSOptions,
_enablePong,
_pingIntervalSecs);
}
WebSocketInitResult status = _ws.connectToSocket(std::move(socket),
timeoutSecs,
enablePerMessageDeflate);
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();
}
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 &text,
const OnProgressCallback &onProgressCallback)
{
return sendMessage(text, SendMessageKind::Binary, 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)
{
// Standard limit ping message size
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize)
return WebSocketSendInfo(false);
return sendMessage(text, SendMessageKind::Ping);
}
WebSocketSendInfo
WebSocket::sendMessage(const std::string &text,
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(text, onProgressCallback);
} break;
case SendMessageKind::Binary: {
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
} break;
case SendMessageKind::Ping: {
webSocketSendInfo = _ws.sendPing(text);
} 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

View File

@ -1,46 +0,0 @@
/*
* 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

View File

@ -1,365 +0,0 @@
/*
* IXWebSocketHandshake.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketHandshake.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 &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 24 bytes string which looks like it is base64 encoded
// y3JJHMbDL1EzLkh9GBhXDw==
// 0cb3Vd9HkbpVVumoS3Noka==
//
// See
// https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
//
std::string secWebSocketKey = genRandomString(22);
secWebSocketKey += "==";
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";
}
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)
{
_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 sendErrorResponse(400, "Error reading HTTP request line");
}
// Validate 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);
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);
}
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
auto 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

View File

@ -1,71 +0,0 @@
/*
* 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

View File

@ -1,93 +0,0 @@
/*
* 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 "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 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

View File

@ -1,253 +0,0 @@
/*
* 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 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

View File

@ -1,193 +0,0 @@
/*
* 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=" << _serverMaxWindowBits;
ss << "; client_max_window_bits=" << _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

View File

@ -1,117 +0,0 @@
/*
* 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

View File

@ -1,206 +0,0 @@
/*
* 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)
{
setThreadName("WebSocketServer::" + connectionState->getId());
auto webSocket = std::make_shared<WebSocket>();
if (_onConnectionCallback) {
_onConnectionCallback(webSocket, connectionState);
if (!webSocket->isOnMessageCallbackRegistered()) {
logError("WebSocketServer Application developer error: Server "
"callback improperly "
"registerered.");
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);
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");
}
}
connectionState->setTerminated();
}
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

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. Neither the name of the copyright holder 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 THE COPYRIGHT
HOLDER OR CONTRIBUTORS 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.

View File

@ -1,32 +0,0 @@
/*
* IXBench.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <chrono>
#include <stdint.h>
#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

View File

@ -1,19 +0,0 @@
/*
* 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

View File

@ -1,54 +0,0 @@
/*
* IXConnectionState.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <stdint.h>
#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

View File

@ -1,73 +0,0 @@
/*
* 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 <memory>
#include <mutex>
#include <set>
#include <string>
struct addrinfo;
namespace ix
{
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
DNSLookup(const std::string &hostname,
int port,
int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default;
struct addrinfo *resolve(std::string &errMsg,
const CancellationRequest &isCancellationRequested,
bool cancellable = true);
void release(struct addrinfo *addr);
private:
struct addrinfo *
resolveCancellable(std::string &errMsg,
const CancellationRequest &isCancellationRequested);
struct addrinfo *resolveUnCancellable(
std::string &errMsg,
const CancellationRequest &isCancellationRequested);
static struct addrinfo *
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(struct addrinfo *addr);
struct addrinfo *getRes();
std::string _hostname;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
struct addrinfo *_res;
std::mutex _resMutex;
std::string _errMsg;
std::mutex _errMsgMutex;
std::atomic<bool> _done;
};
} // namespace ix

View File

@ -1,17 +0,0 @@
/*
* 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

View File

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

View File

@ -1,15 +0,0 @@
/*
* 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

View File

@ -1,117 +0,0 @@
/*
* IXHttp.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#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,
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;
};
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

View File

@ -1,126 +0,0 @@
/*
* 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

View File

@ -1,62 +0,0 @@
/*
* IXHttpServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXHttp.h"
#include "IXSocketServer.h"
#include "IXWebSocket.h"
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class HttpServer final : public SocketServer
{
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);
virtual ~HttpServer();
virtual void stop() final;
void setOnConnectionCallback(const OnConnectionCallback &callback);
void makeRedirectServer(const std::string &redirectUrl);
void makeDebugServer();
int getTimeoutSecs();
private:
// Member variables
OnConnectionCallback _onConnectionCallback;
std::atomic<int> _connectedClientsCount;
const static int kDefaultTimeoutSecs;
int _timeoutSecs;
// Methods
virtual void handleConnection(
std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
void setDefaultConnectionCallback();
};
} // namespace ix

View File

@ -1,87 +0,0 @@
/*
* IXNetSystem.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <basetsd.h>
#include <io.h>
#include <ws2def.h>
#include <ws2tcpip.h>
#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 <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
#include <errno.h>
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);
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

View File

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

View File

@ -1,34 +0,0 @@
/*
* IXSelectInterrupt.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
#include <stdint.h>
#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;
// 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

View File

@ -1,16 +0,0 @@
/*
* 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

View File

@ -1,40 +0,0 @@
/*
* IXSelectInterruptPipe.h
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSelectInterrupt.h"
#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

View File

@ -1,12 +0,0 @@
/*
* 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);
}

View File

@ -1,97 +0,0 @@
/*
* IXSocket.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#ifdef _WIN32
#ifndef _SSIZE_T_DEFINED
#include <basetsd.h>
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);
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 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;
private:
static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout;
SelectInterruptPtr _selectInterrupt;
};
} // namespace ix

View File

@ -1,56 +0,0 @@
/*
* 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

View File

@ -1,32 +0,0 @@
/*
* 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

View File

@ -1,21 +0,0 @@
/*
* 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

View File

@ -1,61 +0,0 @@
/*
* 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

View File

@ -1,75 +0,0 @@
/*
* 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

View File

@ -1,134 +0,0 @@
/*
* 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;
};
} // namespace ix

View File

@ -1,53 +0,0 @@
/*
* 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;
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

View File

@ -1,23 +0,0 @@
/*
* 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

View File

@ -1,45 +0,0 @@
/*
* IXUdpSocket.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <memory>
#include <string>
#ifdef _WIN32
#ifndef _SSIZE_T_DEFINED
#include <basetsd.h>
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

View File

@ -1,18 +0,0 @@
/*
* 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

View File

@ -1,23 +0,0 @@
/*
* 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);
};
} // namespace ix

View File

@ -1,14 +0,0 @@
/*
* 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

View File

@ -1,184 +0,0 @@
/*
* 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

View File

@ -1,17 +0,0 @@
/*
* 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

View File

@ -1,174 +0,0 @@
/*
* 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 "IXWebSocketSendInfo.h"
#include "IXWebSocketTransport.h"
#include <atomic>
#include <condition_variable>
#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 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 &text,
const OnProgressCallback &onProgressCallback = nullptr);
WebSocketSendInfo
sendText(const std::string &text,
const OnProgressCallback &onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string &text);
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;
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 std::string &text,
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);
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;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
// Subprotocols
std::vector<std::string> _subProtocols;
friend class WebSocketServer;
};
} // namespace ix

View File

@ -1,36 +0,0 @@
/*
* 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

View File

@ -1,27 +0,0 @@
/*
* 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

View File

@ -1,21 +0,0 @@
/*
* 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

View File

@ -1,57 +0,0 @@
/*
* IXWebSocketHandshake.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXCancellationRequest.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 &host,
const std::string &path,
int port,
int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate);
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

View File

@ -1,174 +0,0 @@
// 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);
}
};

View File

@ -1,25 +0,0 @@
/*
* 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

Some files were not shown because too many files have changed in this diff Show More