2 Commits

Author SHA1 Message Date
Kilian f30eeffc90 SSL Test pt. 2 2022-09-04 20:49:44 +02:00
Kilian fd62679c25 SSL Test Pt. 1 2022-09-04 19:50:47 +02:00
373 changed files with 1407 additions and 80520 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ IndentCaseLabels: true
IndentFunctionDeclarationAfterType: true
IndentWidth: 2
MaxEmptyLinesToKeep: 2
NamespaceIndentation: Inner
NamespaceIndentation: All
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
ObjCBlockIndentWidth: 4
-48
View File
@@ -1,48 +0,0 @@
{
"configurations": [{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"LIN",
"XPLM200",
"XPLM210"
],
"compilerPath": "/opt/llvm-mingw/bin/clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "linux-clang-x64"
}, {
"name": "Windows",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"IBM",
"XPLM200",
"XPLM210"
],
"compilerPath": "/opt/llvm-mingw/bin/x86_64-w64-mingw32-clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"APL",
"XPLM200",
"XPLM210"
],
"compilerPath": "/opt/osxcross/target/bin/o64-clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "macos-clang-x64"
}
],
"version": 4
}
+3 -86
View File
@@ -1,90 +1,7 @@
{
"cmake.generator": "Unix Makefiles",
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"files.associations": {
"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",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"__threading_support": "cpp",
"__bit_reference": "cpp",
"__bits": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__hash_table": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__nullptr": "cpp",
"__split_buffer": "cpp",
"__string": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"compare": "cpp",
"concepts": "cpp",
"ios": "cpp",
"locale": "cpp",
"queue": "cpp",
"stack": "cpp",
"variant": "cpp"
},
"cmath": "cpp"
}
}
+2 -7
View File
@@ -14,13 +14,8 @@ set(PLUGIN_NAME GAConnector)
option(DEBUG "Debug symbols" OFF)
add_subdirectory(
ixwebsocket
makerwysxp
)
add_subdirectory(
file
xplugin
)
if(NOT (WIN32 AND (BIT STREQUAL "32")))
add_subdirectory(
xplugin
)
endif()
Vendored
+118 -4
View File
@@ -37,8 +37,10 @@ pipeline {
DEBUG = 1
}
steps {
sh 'bash ./build.sh lin'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/lin'
sh 'bash ./build.sh lin32'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/lin32'
sh 'bash ./build.sh lin64'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/lin64'
}
}
stage('Build MacOSX Debug') {
@@ -65,11 +67,123 @@ pipeline {
branch 'develop'
}
steps {
zip zipFile: 'X-Plane Debug.zip', archive: true, dir: 'build/X-Plane'
zip zipFile: 'ESP Debug.zip', archive: true, dir: 'build/ESP'
zip zipFile: 'Debug.zip', archive: true, dir: 'build/Plugin'
zip zipFile: 'Analysis.zip', archive: true, dir: 'build/Analysis'
sh 'rm -rf build'
}
}
stage('Build Windows Stageing') {
when {
branch 'stage'
beforeAgent true
}
agent {
docker {
image 'llvm-mingw:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh win32'
sh 'bash ./build.sh win64'
}
}
stage('Build Linux Stageing') {
when {
branch 'stage'
beforeAgent true
}
agent {
docker {
image 'llvm:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh lin32'
sh 'bash ./build.sh lin64'
}
}
stage('Build MacOSX Stageing') {
when {
branch 'stage'
beforeAgent true
}
agent {
docker {
image 'osxcross:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh mac'
}
}
stage('Archive Stageing') {
when {
branch 'stage'
}
steps {
zip zipFile: 'Stage.zip', archive: true, dir: 'build/Plugin'
sh 'rm -rf build'
}
}
stage('Build Windows Release') {
when {
branch 'master'
beforeAgent true
}
agent {
docker {
image 'llvm-mingw:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh win32'
sh 'bash ./build.sh win64'
}
}
stage('Build Linux Release') {
when {
branch 'master'
beforeAgent true
}
agent {
docker {
image 'llvm:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh lin32'
sh 'bash ./build.sh lin64'
}
}
stage('Build MacOSX Release') {
when {
branch 'master'
beforeAgent true
}
agent {
docker {
image 'osxcross:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh mac'
}
}
stage('Archive Release') {
when {
branch 'master'
beforeAgent true
}
steps {
zip zipFile: 'Release.zip', archive: true, dir: 'build/Plugin'
sh 'rm -rf build'
}
}
}
}
-3
View File
@@ -9,6 +9,3 @@
X-Plane Plugin for all supported platforms.
Demo of WebSocket capabilities
Requirements:
- openSSL 1.1.x
-2
View File
@@ -1,2 +0,0 @@
- Update OSXCross Docker image to SDK 11
- Implement ARM64 arch for Plugin
+7 -14
View File
@@ -2,30 +2,25 @@
mkdir -p build
cd build
mkdir -p X-Plane/GAConnector/64
mkdir -p X-Plane/GAConnector/recordings
mkdir -p ESP/GAConnector
mkdir -p ESP/GAConnector/recordings
mkdir -p Plugin/GAConnector/32
mkdir -p Plugin/GAConnector/64
rm -f CMakeCache.txt
case $1 in
"mac")
cmake -DDEBUG=$DEBUG -DCMAKE_TOOLCHAIN_FILE=../toolchain-mac.cmake ..
;;
"lin")
"lin32")
cmake -DDEBUG=$DEBUG -DBIT=32 -DCMAKE_TOOLCHAIN_FILE=../toolchain-lin.cmake ..
;;
"lin64")
cmake -DDEBUG=$DEBUG -DBIT=64 -DCMAKE_TOOLCHAIN_FILE=../toolchain-lin.cmake ..
;;
"win32")
cmake -DDEBUG=$DEBUG -DBIT=32 -DCMAKE_TOOLCHAIN_FILE=../toolchain-win-32.cmake ..
\cp -rf ../openSSL/win32/*.dll ESP/GAConnector/
\cp -rf /opt/llvm-mingw/i686-w64-mingw32/bin/libc++.dll ESP/GAConnector/
\cp -rf /opt/llvm-mingw/i686-w64-mingw32/bin/libunwind.dll ESP/GAConnector/
;;
"win64")
cmake -DDEBUG=$DEBUG -DBIT=64 -DCMAKE_TOOLCHAIN_FILE=../toolchain-win-64.cmake ..
\cp -rf ../openSSL/win64/*.dll X-Plane/GAConnector/64/
\cp -rf /opt/llvm-mingw/x86_64-w64-mingw32/bin/libc++.dll X-Plane/GAConnector/64
\cp -rf /opt/llvm-mingw/x86_64-w64-mingw32/bin/libunwind.dll X-Plane/GAConnector/64
;;
esac
@@ -38,7 +33,5 @@ fi
if [ "$1" = "mac" ] && [ "$DEBUG" = "1" ]
then
/opt/osxcross/target/bin/osxcross-llvm-dsymutil X-Plane/GAConnector/mac.xpl
/opt/osxcross/target/bin/osxcross-llvm-dsymutil Plugin/GAConnector/mac.xpl
fi
-84
View File
@@ -1,84 +0,0 @@
file(GLOB file CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/file/*.cpp)
add_library(file SHARED
${file}
)
target_include_directories(file PRIVATE
${CMAKE_SOURCE_DIR}/file/include
${CMAKE_SOURCE_DIR}/simdata/include
${CMAKE_SOURCE_DIR}/utilities/include
)
set_target_properties(file PROPERTIES
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/file/include
)
target_compile_options(file PRIVATE
-Wall
-Wextra
-pedantic
)
if(DEBUG)
target_compile_options(file PRIVATE
-g
)
target_link_options(file PRIVATE
-g
)
else()
target_compile_options(file PRIVATE
-O2
)
endif()
if(APPLE)
message("Building file for MacOSX Universal into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}")
set_target_properties(file PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}"
BUILD_WITH_INSTALL_NAME_DIR TRUE
)
target_compile_options(file PRIVATE
"SHELL:-arch x86_64"
)
target_link_options(file PRIVATE
"SHELL:-arch x86_64"
)
target_link_libraries(file PRIVATE
"-framework Security"
)
elseif(UNIX)
message("Building file for Linux ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(file PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}"
)
target_compile_options(file PRIVATE
-nodefaultlibs
)
elseif(WIN32)
if (BIT STREQUAL "32")
message("Building file for Windows ${BIT} into ${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}")
set_target_properties(file PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}"
)
else()
message("Building file for Windows ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(file PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}"
)
endif()
if(DEBUG)
target_compile_options(file PRIVATE
-gcodeview
)
target_link_options(file PRIVATE
-Wl,-pdb=
)
endif()
endif()
-80
View File
@@ -1,80 +0,0 @@
VARIABLE STRING (>=3)
*********************
0 1 | 2 ... |
------+-------+
LEN |STRING |
UINT16|CHAR* |
LOGBOOK HEADER (28)
***********************************************************************************************************************************************
0 1 2 3 4 | 5 | 6 7 | 8 9 10 11 | 12 13 14 15 | 16 17 18 19 | 20 21 22 23 | 24 25 26 27 |
------------------+-------+-------------+---------------------+-------------------+-------------------+-------------------+-------------------+
IDENT |VERSION|TOTAL FLIGHTS|TOTAL TIME |TOTAL PTS |TOTAL FUEL |TOTAL DIST |RATE |
CHAR[5] |UINT8 |UINT16 |FLOAT32, Decimal, min|FLOAT32 |FLOAT32, kgs |FLOAT32, nm |FLOAT32, ft/min |
FLIGHT PREAMBLE (24)
**********************************************************************************************************************
0 1 2 3 4 5 6 7 8 9 | 10 11 12 13 | 14 15 16 17 | 18 19 20 21 22 23 |
--------------------------------------+----------------------------+-------------------+-----------------------------+
DATE |FLIGHT NUMBER |AIRCRAFT TYPE |AIRCRAFT REGISTRATION |
CHAR, DD.MM.YYYY |CHAR[4],[0-9][0-9A-z\0]{0,3}|CHAR[4], ICAO code |CHAR[6], D-[A-Z]{4} |
TIMES (24)
**************************************************************************************************************
0 1 2 3 4 | 5 6 7 8 9 | 10 11 12 13 14 | 15 16 17 18 19 | 20 21 22 23 |
------------------+-------------------+------------------------+------------------------+--------------------+
OFF BLOCK |TAKEOFF (OUT) |LANDING (ON) |ON BLOCK (IN) |TOTAL |
CHAR[5], HH:MM |CHAR[5], HH:MM |CHAR[5], HH:MM |CHAR[5], HH:MM |FLOAT32, Decimal min|
FUELS (16)
*********************************************************************
0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15 |
--------------+---------------+-----------------+-------------------+
TAXI OUT |IN FLIGHT |TAXI IN |TOTAL |
FLOAT32, kgs |FLOAT32, kgs |FLOAT32, kgs |FLOAT32, kgs |
DISTANCES (16)
*********************************************************************
0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15 |
--------------+---------------+-----------------+-------------------+
TAXI OUT |IN FLIGHT |TAXI IN |TOTAL |
FLOAT32, nm |FLOAT32, nm |FLOAT32, nm |FLOAT32, nm |
LANDING (9)
*******************************************
0 1 2 3 | 4 | 5 6 7 8 |
---------------+----------+---------------+
MAX RATE |TOUCHDOWNS|MAX G-FORCE |
FLOAT32, ft/min|CHAR |FLOAT32, g |
FLIGHT POSTAMBLE (5)
*********************
0 1 2 3 | 5 |
--------------+---------+
POINTS |FLAGS |
FLOAT32 |BITFIELD |
FLAGS BITFIELD
**********************************
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---+---+---+---+---+---+---+-----+
NIL|NIL|NIL|NIL|NIL|NIL|NIL|FILED|
LOGBOOK FILE:
LOGBOOK HEADER
[
FLIGHT PREAMBLE
VARIABLE STRING (DEPARTURE AIRPORT NAME AND CODE)
VARIABLE STRING (DEPARTURE GATE NAME)
VARIABLE STRING (DEPARTURE RUNWAY DESIGNATOR)
VARIABLE STRING (ARRIVAL AIRPORT NAME AND CODE)
VARIABLE STRING (ARRIVAL GATE NAME)
VARIABLE STRING (ARRIVAL RUNWAY DESIGNATOR)
TIMES
FUELS
DISTANCES
LANDING
VARIABLE STRING (RECORDING FILE NAME)
FLIGHT POSTAMBLE
VARIABLE STRING (FLIGHT RECORDING PATH RELATIVE TO LOGBOOK)
]0..n
-47
View File
@@ -1,47 +0,0 @@
#include "config/config.h"
namespace germanairlinesva
{
namespace file
{
namespace config
{
void Config::writeFile() const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY CONFIG);
out << "scenery=" << this->scenery << "\n";
out << "user=" << this->user << "\n";
out << "token=" << this->token << "\n";
out.close();
}
Config::Config()
{
std::ifstream in(XPLANE_PLUGIN_DIRECTORY CONFIG);
std::string line;
while (std::getline(in, line)) {
std::vector<std::string> fields = util::split(line, '=');
if (fields.size() >= 2) {
util::trim(fields[0]);
util::trim(fields[1]);
if (fields[0] == "scenery") {
this->scenery = fields[1];
} else if (fields[0] == "user") {
this->user = fields[1];
} else if (fields[0] == "token") {
this->token = fields[1];
}
}
}
in.close();
}
void Config::updateScenery(std::string scenery)
{
this->scenery = scenery;
this->writeFile();
}
} // namespace config
} // namespace file
} // namespace germanairlinesva
+47
View File
@@ -0,0 +1,47 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_CONFIG_H
#define GERMANAIRLINESVA_GACONNECTOR_CONFIG_H
#include "stringExtensions.hpp"
#include <map>
#include <fstream>
#include <map>
#include <string>
#include <vector>
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;
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();
}
} // namespace config
#endif
-42
View File
@@ -1,42 +0,0 @@
#include "simdata/gate.h"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
Gate::Gate(std::string designator,
double latitude,
double longitude,
std::uint8_t radius)
{
this->designator = designator;
this->center = {geodata::toRadians(latitude),
geodata::toRadians(longitude)};
this->radius = radius;
}
Gate::Gate(std::string designator,
struct geodata::point center,
std::uint8_t radius)
{
this->designator = designator;
this->center = center;
this->radius = radius;
}
void Gate::toFile(std::ofstream &out) const
{
writeString(out, this->designator);
write<decltype(this->center)>(out, this->center);
write<decltype(this->radius)>(out, this->radius);
}
bool Gate::contains(geodata::point coordinates) const
{
return geodata::distanceEarthP(this->center, coordinates);
}
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
-18
View File
@@ -1,18 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSGEODATA_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSGEODATA_H
#include "FsLatLonPoint.h"
#include "FsLatLonQuadrilateral.h"
#include "FsLatitude.h"
#include "FsLatitudeSpan.h"
#include "FsLongitude.h"
#include "FsLongitudeSpan.h"
#include "FsLatLonPointImpl.h"
#include "FsLatLonQuadrilateralImpl.h"
#include "FsLatitudeImpl.h"
#include "FsLatitudeSpanImpl.h"
#include "FsLongitudeImpl.h"
#include "FsLongitudeSpanImpl.h"
#endif
-57
View File
@@ -1,57 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINT_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINT_H
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdint>
#include <string>
#include "FsLatitude.h"
#include "FsLatitudeSpan.h"
#include "FsLongitude.h"
#include "FsLongitudeSpan.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatLonPoint
{
private:
FsLatitude lat;
FsLongitude lon;
public:
inline FsLatLonPoint();
inline FsLatLonPoint(FsLatitude Latitude, FsLongitude Longitude);
inline const FsLongitude Longitude() const;
inline const FsLatitude Latitude() const;
inline double DistanceFromInFeet(const FsLatLonPoint &Point) const;
inline double
DistanceFromInNauticalMiles(const FsLatLonPoint &Point) const;
inline double DistanceFromInMetres(const FsLatLonPoint &Point) const;
inline double BearingTo(const FsLatLonPoint &Point) const;
inline double BearingFrom(const FsLatLonPoint &Point) const;
inline FsLatLonPoint OffsetByFeet(double Bearing,
double Distance) const;
inline FsLatLonPoint OffsetByMetres(double Bearing,
double Distance) const;
inline FsLatLonPoint OffsetByNauticalMiles(double Bearing,
double Distance) const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-145
View File
@@ -1,145 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINTIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINTIMPL_H
#include "FsLatLonPoint.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatLonPoint::FsLatLonPoint() = default;
inline FsLatLonPoint::FsLatLonPoint(FsLatitude Latitude,
FsLongitude Longitude)
{
this->lon = Longitude;
this->lat = Latitude;
}
inline const FsLongitude FsLatLonPoint::Longitude() const
{
return this->lon;
}
inline const FsLatitude FsLatLonPoint::Latitude() const
{
return this->lat;
}
inline double
FsLatLonPoint::DistanceFromInFeet(const FsLatLonPoint &Point) const
{
FsLongitudeSpan fsLongitudeSpan =
FsLongitudeSpan::BetweenTwoLongitudes(Point.Longitude(), this->lon);
double num = (fsLongitudeSpan.ToFeet(this->lat) +
fsLongitudeSpan.ToFeet(Point.lat)) /
2.0;
double feet = FsLatitudeSpan(Point.Latitude().DecimalDegrees() -
this->lat.DecimalDegrees())
.ToFeet();
return sqrt(num * num + feet * feet);
}
inline double FsLatLonPoint::DistanceFromInNauticalMiles(
const FsLatLonPoint &Point) const
{
return this->DistanceFromInFeet(Point) / 6076.1155;
}
inline double
FsLatLonPoint::DistanceFromInMetres(const FsLatLonPoint &Point) const
{
return this->DistanceFromInFeet(Point) / 3.2808;
}
inline double FsLatLonPoint::BearingTo(const FsLatLonPoint &Point) const
{
double num1 = 0.0;
double num2 = abs(FsLatitudeSpan(this->lat.DecimalDegrees() -
Point.Latitude().DecimalDegrees())
.ToFeet());
double num3 = abs((FsLongitudeSpan(Point.Longitude().DecimalDegrees() -
this->lon.DecimalDegrees())
.ToFeet(this->lat) +
FsLongitudeSpan(Point.Longitude().DecimalDegrees() -
this->lon.DecimalDegrees())
.ToFeet(Point.lat)) /
2.0);
if (num2 == 0.0)
num1 = this->lon.DecimalDegrees() > Point.Longitude().DecimalDegrees()
? 270.0
: 90.0;
else if (this->lat.DecimalDegrees() < Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() < Point.Longitude().DecimalDegrees())
num1 = atan(num3 / num2) * 180.0 / M_PI;
else if (this->lat.DecimalDegrees() > Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() < Point.Longitude().DecimalDegrees())
num1 = atan(num2 / num3) * 180.0 / M_PI + 90.0;
else if (this->lat.DecimalDegrees() > Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() > Point.Longitude().DecimalDegrees())
num1 = atan(num3 / num2) * 180.0 / M_PI + 180.0;
else if (this->lat.DecimalDegrees() < Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() > Point.Longitude().DecimalDegrees())
num1 = atan(num2 / num3) * 180.0 / M_PI + 270.0;
return num1;
}
inline double FsLatLonPoint::BearingFrom(const FsLatLonPoint &Point) const
{
double num = 180.0 + this->BearingTo(Point);
if (num >= 360.0)
num -= 360.0;
if (num < 0.0)
num += 360.0;
return num;
}
inline FsLatLonPoint FsLatLonPoint::OffsetByFeet(double Bearing,
double Distance) const
{
double Feet1 = sin(M_PI * Bearing / 180.0) * Distance;
double Feet2 = cos(M_PI * Bearing / 180.0) * Distance;
FsLatLonPoint fsLatLonPoint = FsLatLonPoint();
fsLatLonPoint.lat = this->lat.Add(FsLatitudeSpan::FromFeet(Feet2));
fsLatLonPoint.lon =
this->lon.Add(FsLongitudeSpan::FromFeet(Feet1, fsLatLonPoint.lat));
return fsLatLonPoint;
}
inline FsLatLonPoint FsLatLonPoint::OffsetByMetres(double Bearing,
double Distance) const
{
double Metres1 = sin(M_PI * Bearing / 180.0) * Distance;
double Metres2 = cos(M_PI * Bearing / 180.0) * Distance;
FsLatLonPoint fsLatLonPoint = FsLatLonPoint();
fsLatLonPoint.lat = this->lat.Add(FsLatitudeSpan::FromMetres(Metres2));
fsLatLonPoint.lon = this->lon.Add(
FsLongitudeSpan::FromMetres(Metres1, fsLatLonPoint.lat));
return fsLatLonPoint;
}
inline FsLatLonPoint
FsLatLonPoint::OffsetByNauticalMiles(double Bearing,
double Distance) const
{
double NauticalMiles1 = sin(M_PI * Bearing / 180.0) * Distance;
double NauticalMiles2 = cos(M_PI * Bearing / 180.0) * Distance;
FsLatLonPoint fsLatLonPoint = FsLatLonPoint();
fsLatLonPoint.lat =
this->lat.Add(FsLatitudeSpan::FromNauticalMiles(NauticalMiles2));
fsLatLonPoint.lon =
this->lon.Add(FsLongitudeSpan::FromNauticalMiles(NauticalMiles1,
fsLatLonPoint.lat));
return fsLatLonPoint;
}
inline const std::string FsLatLonPoint::to_string(bool HemisphereAsText,
char DetailLevel) const
{
return this->lat.to_string(HemisphereAsText, DetailLevel) + ", " +
this->lon.to_string(HemisphereAsText, DetailLevel);
}
inline const std::string FsLatLonPoint::to_string() const
{
return this->to_string(true, 'm');
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
@@ -1,57 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERAL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERAL_H
#define _USE_MATH_DEFINES
#include <cmath>
#include <string>
#include <vector>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatitude;
class FsLatitudeSpan;
class FsLongitude;
class FsLongitudeSpan;
class FsLatLonPoint;
class FsLatLonQuadrilateral
{
private:
FsLatLonPoint ne;
FsLatLonPoint se;
FsLatLonPoint sw;
FsLatLonPoint nw;
public:
inline FsLatLonQuadrilateral();
inline FsLatLonQuadrilateral(FsLatLonPoint P0,
FsLatLonPoint P1,
FsLatLonPoint P2,
FsLatLonPoint P3);
inline FsLatLonPoint NE() const;
inline FsLatLonPoint SE() const;
inline FsLatLonPoint SW() const;
inline FsLatLonPoint NW() const;
inline bool ContainsPoint(FsLatLonPoint point) const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
inline static FsLatLonQuadrilateral
ForRunway(FsLatLonPoint &ThresholdCentre,
double HeadingTrue,
double WidthInFeet,
double LengthInFeet);
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
@@ -1,166 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERALIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERALIMPL_H
#include "FsLatLonQuadrilateral.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatLonQuadrilateral::FsLatLonQuadrilateral() = default;
inline FsLatLonQuadrilateral::FsLatLonQuadrilateral(FsLatLonPoint P0,
FsLatLonPoint P1,
FsLatLonPoint P2,
FsLatLonPoint P3)
{
std::vector<FsLatLonPoint> fsLatLonPointList1;
std::vector<FsLatLonPoint> fsLatLonPointList2;
fsLatLonPointList1.push_back(P0);
fsLatLonPointList1.push_back(P1);
fsLatLonPointList1.push_back(P2);
fsLatLonPointList1.push_back(P3);
for (int index1 = 0; index1 < 3; ++index1) {
double num = 0.0;
int index2 = 0;
for (size_t index3 = 0; index3 < fsLatLonPointList1.size(); ++index3) {
if (fsLatLonPointList1[index3].Latitude().UDegrees() > num) {
num = fsLatLonPointList1[index3].Latitude().UDegrees();
index2 = index3;
}
}
fsLatLonPointList2.push_back(fsLatLonPointList1[index2]);
fsLatLonPointList1.erase(fsLatLonPointList1.begin() + index2);
}
fsLatLonPointList2.push_back(fsLatLonPointList1[0]);
if (fsLatLonPointList2[1].Longitude().UDegrees() >
fsLatLonPointList2[0].Longitude().UDegrees()) {
this->ne = fsLatLonPointList2[1];
this->nw = fsLatLonPointList2[0];
} else {
this->ne = fsLatLonPointList2[0];
this->nw = fsLatLonPointList2[1];
}
if (fsLatLonPointList2[3].Longitude().UDegrees() >
fsLatLonPointList2[2].Longitude().UDegrees()) {
this->se = fsLatLonPointList2[3];
this->sw = fsLatLonPointList2[2];
} else {
this->se = fsLatLonPointList2[2];
this->sw = fsLatLonPointList2[3];
}
}
inline FsLatLonPoint FsLatLonQuadrilateral::NE() const { return this->ne; }
inline FsLatLonPoint FsLatLonQuadrilateral::SE() const { return this->se; }
inline FsLatLonPoint FsLatLonQuadrilateral::SW() const { return this->sw; }
inline FsLatLonPoint FsLatLonQuadrilateral::NW() const { return this->nw; }
inline bool FsLatLonQuadrilateral::ContainsPoint(FsLatLonPoint point) const
{
bool flag = false;
double udegrees1 = this->nw.Latitude().UDegrees();
double udegrees2 = this->sw.Latitude().UDegrees();
double udegrees3 = this->se.Latitude().UDegrees();
double udegrees4 = this->ne.Latitude().UDegrees();
double udegrees5 = this->nw.Longitude().UDegrees();
double udegrees6 = this->sw.Longitude().UDegrees();
double udegrees7 = this->se.Longitude().UDegrees();
double udegrees8 = this->ne.Longitude().UDegrees();
double udegrees9 = point.Longitude().UDegrees();
double udegrees10 = point.Latitude().UDegrees();
if (udegrees9 > udegrees5 + (udegrees6 - udegrees5) /
(udegrees1 - udegrees2) *
(udegrees1 - udegrees10) &&
udegrees9 < udegrees8 + (udegrees7 - udegrees8) /
(udegrees4 - udegrees3) *
(udegrees4 - udegrees10) &&
udegrees10 > udegrees2 + (udegrees3 - udegrees2) /
(udegrees7 - udegrees6) *
(udegrees9 - udegrees6))
flag = udegrees10 < udegrees1 + (udegrees4 - udegrees1) /
(udegrees8 - udegrees5) *
(udegrees9 - udegrees5);
return flag;
}
inline const std::string
FsLatLonQuadrilateral::to_string(bool HemisphereAsText,
char DetailLevel) const
{
return this->ne.to_string(HemisphereAsText, DetailLevel) + ", " +
this->nw.to_string(HemisphereAsText, DetailLevel) + ", " +
this->sw.to_string(HemisphereAsText, DetailLevel) + ", " +
this->se.to_string(HemisphereAsText, DetailLevel);
}
inline const std::string FsLatLonQuadrilateral::to_string() const
{
return this->to_string(true, 'm');
}
inline FsLatLonQuadrilateral
FsLatLonQuadrilateral::ForRunway(FsLatLonPoint &ThresholdCentre,
double HeadingTrue,
double WidthInFeet,
double LengthInFeet)
{
double num = M_PI * HeadingTrue / 180.0;
double Feet = cos(num) * WidthInFeet / 2.0;
double decimalDegrees1 =
FsLatitudeSpan::FromFeet(sin(num) * WidthInFeet / 2.0)
.DecimalDegrees();
FsLatitude fsLatitude1 = FsLatitude(
ThresholdCentre.Latitude().DecimalDegrees() - decimalDegrees1);
double decimalDegrees2 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
FsLongitude Longitude1 = FsLongitude(
ThresholdCentre.Longitude().DecimalDegrees() + decimalDegrees2);
FsLatLonPoint P1 = FsLatLonPoint(fsLatitude1, Longitude1);
fsLatitude1 = FsLatitude(ThresholdCentre.Latitude().DecimalDegrees() +
decimalDegrees1);
double decimalDegrees3 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
Longitude1 = FsLongitude(ThresholdCentre.Longitude().DecimalDegrees() -
decimalDegrees3);
FsLatLonPoint P0 = FsLatLonPoint(fsLatitude1, Longitude1);
double decimalDegrees4 =
FsLatitudeSpan::FromFeet(cos(num) * LengthInFeet).DecimalDegrees();
FsLatitude fsLatitude2 = FsLatitude(
ThresholdCentre.Latitude().DecimalDegrees() + decimalDegrees4);
double decimalDegrees5 =
FsLongitudeSpan ::FromFeet(sin(num) * LengthInFeet, fsLatitude2)
.DecimalDegrees();
FsLongitude Longitude2 = FsLongitude(
ThresholdCentre.Longitude().DecimalDegrees() + decimalDegrees5);
FsLatLonPoint fsLatLonPoint = FsLatLonPoint(fsLatitude2, Longitude2);
fsLatitude1 = FsLatitude(fsLatLonPoint.Latitude().DecimalDegrees() -
decimalDegrees1);
double decimalDegrees6 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
Longitude1 = FsLongitude(fsLatLonPoint.Longitude().DecimalDegrees() +
decimalDegrees6);
FsLatLonPoint P3 = FsLatLonPoint(fsLatitude1, Longitude1);
fsLatitude1 = FsLatitude(fsLatLonPoint.Latitude().DecimalDegrees() +
decimalDegrees1);
double decimalDegrees7 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
Longitude1 = FsLongitude(fsLatLonPoint.Longitude().DecimalDegrees() -
decimalDegrees7);
FsLatLonPoint P2 = FsLatLonPoint(fsLatitude1, Longitude1);
return FsLatLonQuadrilateral(P0, P1, P2, P3);
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-60
View File
@@ -1,60 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDE_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDE_H
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatitudeSpan;
class FsLatitude
{
private:
double pos;
public:
inline FsLatitude();
inline FsLatitude(double DecimalDegrees);
inline FsLatitude(std::int64_t FSUnits);
inline FsLatitude(std::int32_t FSUnits);
inline FsLatitude(std::int32_t Degrees, double DecimalMinutes);
inline FsLatitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline std::int64_t ToFSUnits8() const;
inline std::int32_t ToFSUnits4() const;
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degree() const;
inline std::int32_t Minute() const;
inline std::int32_t Second() const;
inline double UDegrees() const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
inline FsLatitude Add(const FsLatitudeSpan &Distance) const;
inline FsLatitude Subtract(const FsLatitudeSpan &Distance) const;
inline FsLatitude AddDegrees(double Degrees) const;
inline FsLatitude AddMinutes(double Minutes) const;
inline FsLatitude AddSeconds(double Seconds) const;
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-129
View File
@@ -1,129 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDEIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDEIMPL_H
#include "FsLatitude.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatitude::FsLatitude() = default;
inline FsLatitude::FsLatitude(double DecimalDegrees)
{
this->pos = DecimalDegrees;
while (this->pos > 90.0 || this->pos < -90.0) {
if (this->pos > 90.0)
this->pos = 180.0 - this->pos;
if (this->pos < -90.0)
this->pos = -180.0 - this->pos;
}
}
inline FsLatitude::FsLatitude(std::int64_t FSUnits)
: FsLatitude(FSUnits * 90.0 / 4.2957189152768E+16)
{
}
inline FsLatitude::FsLatitude(std::int32_t FSUnits)
: FsLatitude(FSUnits * 90.0 / 10001750.0)
{
}
inline FsLatitude::FsLatitude(std::int32_t Degrees, double DecimalMinutes)
: FsLatitude(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLatitude::FsLatitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLatitude(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline std::int64_t FsLatitude::ToFSUnits8() const
{
return this->pos * 4.2957189152768E+16 / 90.0;
}
inline std::int32_t FsLatitude::ToFSUnits4() const
{
return this->pos * 10001750.0 / 90.0;
}
inline double FsLatitude::DecimalDegrees() const { return this->pos; }
inline double FsLatitude::DecimalMinutes() const
{
return (this->pos - trunc(this->pos)) * 60.0;
}
inline double FsLatitude::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLatitude::Degree() const { return trunc(this->pos); }
inline std::int32_t FsLatitude::Minute() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLatitude::Second() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLatitude::UDegrees() const { return this->pos + 90.0; }
inline const std::string FsLatitude::to_string(bool HemisphereAsText,
char DetailLevel) const
{
std::ostringstream str;
if (!HemisphereAsText) {
str << (this->pos < 0.0 ? "-" : "");
} else {
str << (this->pos < 0.0 ? "S" : "N");
}
switch (DetailLevel) {
case 'm':
str << this->Degree() << "° " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degree() << "° " << this->Minute() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->pos << "*";
break;
}
return str.str();
}
inline const std::string FsLatitude::to_string() const
{
return this->to_string(true, 'm');
}
inline FsLatitude FsLatitude::Add(const FsLatitudeSpan &Distance) const
{
return FsLatitude(this->pos + Distance.DecimalDegrees());
}
inline FsLatitude FsLatitude::Subtract(const FsLatitudeSpan &Distance) const
{
return FsLatitude(this->pos - Distance.DecimalDegrees());
}
inline FsLatitude FsLatitude::AddDegrees(double Degrees) const
{
return FsLatitude(this->pos + Degrees);
}
inline FsLatitude FsLatitude::AddMinutes(double Minutes) const
{
return FsLatitude(this->pos + Minutes / 60.0);
}
inline FsLatitude FsLatitude::AddSeconds(double Seconds) const
{
return FsLatitude(this->pos + Seconds / 3600.0);
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-58
View File
@@ -1,58 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPAN_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPAN_H
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatitude;
class FsLatitudeSpan
{
private:
double span;
public:
inline FsLatitudeSpan();
inline FsLatitudeSpan(double DecimalDegrees);
inline FsLatitudeSpan(std::int32_t Degrees, double DecimalMinutes);
inline FsLatitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline static FsLatitudeSpan FromFeet(double Feet);
inline static FsLatitudeSpan FromNauticalMiles(double NauticalMiles);
inline static FsLatitudeSpan FromMetres(double Metres);
inline static FsLatitudeSpan
BetweenTwoLatitides(const FsLatitude &Lat1, const FsLatitude &Lat2);
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degrees() const;
inline std::int32_t Minutes() const;
inline std::int32_t Seconds() const;
inline double TotalMinutes() const;
inline double TotalSeconds() const;
inline double ToFeet() const;
inline double ToNauticalMiles() const;
inline double ToMetres() const;
inline const std::string to_string(char DetailLevel) const;
inline const std::string to_string();
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-121
View File
@@ -1,121 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPANIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPANIMPL_H
#include "FsLatitudeSpan.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatitudeSpan::FsLatitudeSpan() = default;
inline FsLatitudeSpan::FsLatitudeSpan(double DecimalDegrees)
{
this->span = DecimalDegrees;
}
inline FsLatitudeSpan::FsLatitudeSpan(std::int32_t Degrees,
double DecimalMinutes)
: FsLatitudeSpan(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLatitudeSpan::FsLatitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLatitudeSpan(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline FsLatitudeSpan FsLatitudeSpan::FromFeet(double Feet)
{
return FsLatitudeSpan(Feet / 364601.4567);
}
inline FsLatitudeSpan
FsLatitudeSpan::FromNauticalMiles(double NauticalMiles)
{
return FsLatitudeSpan::FromFeet(NauticalMiles * 6076.1155);
}
inline FsLatitudeSpan FsLatitudeSpan::FromMetres(double Metres)
{
return FsLatitudeSpan::FromFeet(Metres * 3.2808);
}
inline FsLatitudeSpan
FsLatitudeSpan::BetweenTwoLatitides(const FsLatitude &Lat1,
const FsLatitude &Lat2)
{
return FsLatitudeSpan(abs(Lat2.UDegrees() - Lat1.UDegrees()));
}
inline double FsLatitudeSpan::DecimalDegrees() const { return this->span; }
inline double FsLatitudeSpan::DecimalMinutes() const
{
return (this->span - trunc(this->span)) * 60.0;
}
inline double FsLatitudeSpan::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLatitudeSpan::Degrees() const
{
return trunc(this->span);
}
inline std::int32_t FsLatitudeSpan::Minutes() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLatitudeSpan::Seconds() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLatitudeSpan::TotalMinutes() const
{
return this->span * 60.0;
}
inline double FsLatitudeSpan::TotalSeconds() const
{
return this->span * 3600.0;
}
inline double FsLatitudeSpan::ToFeet() const
{
return 364601.4567 * this->span;
}
inline double FsLatitudeSpan::ToNauticalMiles() const
{
return this->ToFeet() / 6076.1155;
}
inline double FsLatitudeSpan::ToMetres() const
{
return this->ToFeet() / 3.2808;
}
inline const std::string FsLatitudeSpan::to_string(char DetailLevel) const
{
std::ostringstream str;
switch (DetailLevel) {
case 'm':
str << this->Degrees() << "* " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degrees() << "* " << this->Minutes() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->span << "*";
break;
}
return str.str();
}
inline const std::string FsLatitudeSpan::to_string()
{
return this->to_string('m');
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-60
View File
@@ -1,60 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDE_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDE_H
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLongitudeSpan;
class FsLongitude
{
private:
double pos;
public:
inline FsLongitude();
inline FsLongitude(double DecimalDegrees);
inline FsLongitude(std::int64_t FSUnits);
inline FsLongitude(std::int32_t FSUnits);
inline FsLongitude(std::int32_t Degrees, double DecimalMinutes);
inline FsLongitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline long ToFSUnits8() const;
inline std::int32_t ToFSUnits4() const;
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degree() const;
inline std::int32_t Minute() const;
inline std::int32_t Second() const;
inline double UDegrees() const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
inline FsLongitude Add(const FsLongitudeSpan &Distance) const;
inline FsLongitude Subtract(const FsLongitudeSpan &Distance) const;
inline FsLongitude AddDegrees(double Degrees) const;
inline FsLongitude AddMinutes(double Minutes) const;
inline FsLongitude AddSeconds(double Seconds) const;
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-133
View File
@@ -1,133 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDEIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDEIMPL_H
#include "FsLongitude.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLongitude::FsLongitude() = default;
inline FsLongitude::FsLongitude(double DecimalDegrees)
{
this->pos = DecimalDegrees;
while (this->pos < -180.0 || this->pos > 180.0) {
if (this->pos > 180.0)
this->pos -= 360.0;
if (this->pos < -180.0)
this->pos += 360.0;
}
}
inline FsLongitude::FsLongitude(std::int64_t FSUnits)
: FsLongitude(FSUnits * 360.0 / 1.8446744073709552E+19)
{
}
inline FsLongitude::FsLongitude(std::int32_t FSUnits)
: FsLongitude(FSUnits * 360.0 / 4294967296.0)
{
}
inline FsLongitude::FsLongitude(std::int32_t Degrees, double DecimalMinutes)
: FsLongitude(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLongitude::FsLongitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLongitude(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline long FsLongitude::ToFSUnits8() const
{
return this->pos * 1.8446744073709552E+19 / 360.0;
}
inline std::int32_t FsLongitude::ToFSUnits4() const
{
return this->pos * 4294967296.0 / 360.0;
}
inline double FsLongitude::DecimalDegrees() const { return this->pos; }
inline double FsLongitude::DecimalMinutes() const
{
return (this->pos - trunc(this->pos)) * 60.0;
}
inline double FsLongitude::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLongitude::Degree() const
{
return trunc(this->DecimalDegrees());
}
inline std::int32_t FsLongitude::Minute() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLongitude::Second() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLongitude::UDegrees() const { return this->pos + 180.0; }
inline const std::string FsLongitude::to_string(bool HemisphereAsText,
char DetailLevel) const
{
std::ostringstream str;
if (!HemisphereAsText) {
str << (this->pos < 0.0 ? "-" : "");
} else {
str << (this->pos < 0.0 ? "W" : "E");
}
switch (DetailLevel) {
case 'm':
str << this->Degree() << "° " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degree() << "° " << this->Minute() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->pos << "*";
break;
}
return str.str();
}
inline const std::string FsLongitude::to_string() const
{
return this->to_string(true, 'm');
}
inline FsLongitude FsLongitude::Add(const FsLongitudeSpan &Distance) const
{
return FsLongitude(this->pos + Distance.DecimalDegrees());
}
inline FsLongitude
FsLongitude::Subtract(const FsLongitudeSpan &Distance) const
{
return FsLongitude(this->pos - Distance.DecimalDegrees());
}
inline FsLongitude FsLongitude::AddDegrees(double Degrees) const
{
return FsLongitude(this->pos + Degrees);
}
inline FsLongitude FsLongitude::AddMinutes(double Minutes) const
{
return FsLongitude(this->pos + Minutes / 60.0);
}
inline FsLongitude FsLongitude::AddSeconds(double Seconds) const
{
return FsLongitude(this->pos + Seconds / 3600.0);
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-65
View File
@@ -1,65 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPAN_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPAN_H
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLongitude;
class FsLatitude;
class FsLongitudeSpan
{
private:
double span;
public:
inline FsLongitudeSpan();
inline FsLongitudeSpan(double DecimalDegrees);
inline FsLongitudeSpan(std::int32_t Degrees, double DecimalMinutes);
inline FsLongitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline static FsLongitudeSpan FromFeet(double Feet,
const FsLatitude &AtLatitude);
inline static FsLongitudeSpan
FromNauticalMiles(double NauticalMiles,
const FsLatitude &AtLatitude);
inline static FsLongitudeSpan FromMetres(double Metres,
const FsLatitude &AtLatitude);
inline static FsLongitudeSpan
BetweenTwoLongitudes(const FsLongitude &Lon1,
const FsLongitude &Lon2);
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degrees() const;
inline std::int32_t Minutes() const;
inline std::int32_t Seconds() const;
inline double TotalMinutes() const;
inline double TotalSeconds() const;
inline double ToFeet(const FsLatitude &AtLatitude) const;
inline double ToNauticalMiles(const FsLatitude &AtLatitude) const;
inline double ToMetres(const FsLatitude &AtLatitude) const;
inline const std::string to_string(char DetailLevel) const;
inline const std::string to_string();
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-133
View File
@@ -1,133 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPANIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPANIMPL_H
#include "FsLongitudeSpan.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLongitudeSpan::FsLongitudeSpan() = default;
inline FsLongitudeSpan::FsLongitudeSpan(double DecimalDegrees)
{
this->span = DecimalDegrees;
}
inline FsLongitudeSpan::FsLongitudeSpan(std::int32_t Degrees,
double DecimalMinutes)
: FsLongitudeSpan(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLongitudeSpan::FsLongitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLongitudeSpan(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline FsLongitudeSpan
FsLongitudeSpan::FromFeet(double Feet, const FsLatitude &AtLatitude)
{
double num =
cos(M_PI * AtLatitude.DecimalDegrees() / 180.0) * 131479672.3 / 360.0;
return FsLongitudeSpan(Feet / num);
}
inline FsLongitudeSpan
FsLongitudeSpan::FromNauticalMiles(double NauticalMiles,
const FsLatitude &AtLatitude)
{
return FsLongitudeSpan::FromFeet(NauticalMiles * 6076.1155, AtLatitude);
}
inline FsLongitudeSpan
FsLongitudeSpan::FromMetres(double Metres, const FsLatitude &AtLatitude)
{
return FsLongitudeSpan::FromFeet(Metres * 3.2808, AtLatitude);
}
inline FsLongitudeSpan
FsLongitudeSpan::BetweenTwoLongitudes(const FsLongitude &Lon1,
const FsLongitude &Lon2)
{
return fmod(Lon2.UDegrees() - Lon1.UDegrees(), 360.0) <
fmod(Lon1.UDegrees() - Lon2.UDegrees(), 360.0)
? FsLongitudeSpan(
fmod(Lon2.UDegrees() - Lon1.UDegrees(), 360.0))
: FsLongitudeSpan(
fmod(Lon1.UDegrees() - Lon2.UDegrees(), 360.0));
}
inline double FsLongitudeSpan::DecimalDegrees() const { return this->span; }
inline double FsLongitudeSpan::DecimalMinutes() const
{
return (this->span - trunc(this->span)) * 60.0;
}
inline double FsLongitudeSpan::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLongitudeSpan::Degrees() const
{
return trunc(this->span);
}
inline std::int32_t FsLongitudeSpan::Minutes() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLongitudeSpan::Seconds() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLongitudeSpan::TotalMinutes() const
{
return this->span * 60.0;
}
inline double FsLongitudeSpan::TotalSeconds() const
{
return this->span * 3600.0;
}
inline double FsLongitudeSpan::ToFeet(const FsLatitude &AtLatitude) const
{
return cos(M_PI * AtLatitude.DecimalDegrees() / 180.0) * 131479672.3 /
360.0 * this->span;
}
inline double
FsLongitudeSpan::ToNauticalMiles(const FsLatitude &AtLatitude) const
{
return this->ToFeet(AtLatitude) / 6076.1155;
}
inline double FsLongitudeSpan::ToMetres(const FsLatitude &AtLatitude) const
{
return this->ToFeet(AtLatitude) / 3.2808;
}
inline const std::string FsLongitudeSpan::to_string(char DetailLevel) const
{
std::ostringstream str;
switch (DetailLevel) {
case 'm':
str << this->Degrees() << "* " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degrees() << "* " << this->Minutes() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->span << "*";
break;
}
return str.str();
}
inline const std::string FsLongitudeSpan::to_string()
{
return this->to_string('m');
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
-42
View File
@@ -1,42 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_CONFIG_H
#define GERMANAIRLINESVA_FILE_CONFIG_H
#include <map>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include "constants.h"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace config
{
class Config
{
private:
std::string user;
std::string scenery;
std::string token;
void writeFile() const;
public:
Config();
void updateScenery(std::string scenery);
inline const std::string getUser() const { return this->user; }
inline const std::string getScenery() const { return this->scenery; }
inline const std::string getToken() const { return this->token; }
};
} // namespace config
} // namespace file
} // namespace germanairlinesva
#endif
-57
View File
@@ -1,57 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_HELPERS_H
#define GERMANAIRLINESVA_FILE_HELPERS_H
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
namespace germanairlinesva
{
namespace file
{
static inline const std::string readString(std::ifstream &in,
std::uint8_t length)
{
std::vector<char> value(length + 1);
in.read(value.data(), length);
return std::string(std::move(value.data()));
}
static inline const std::string readString(std::ifstream &in)
{
std::uint8_t length;
in.read(reinterpret_cast<char *>(&length), 1);
std::vector<char> value(length + 1);
in.read(value.data(), length);
return std::string(std::move(value.data()));
}
template <typename T> static inline const T read(std::ifstream &in)
{
T value;
in.read(reinterpret_cast<char *>(&value), sizeof(value));
return value;
}
static inline void writeString(std::ofstream &out, std::string value)
{
std::uint8_t length = value.length();
out.write(reinterpret_cast<char *>(&length), sizeof(length));
out.write(value.data(), length);
}
static inline void
writeString(std::ofstream &out, std::string value, std::uint8_t length)
{
out.write(value.data(), length);
}
template <typename T> static inline void write(std::ofstream &out, T value)
{
out.write(reinterpret_cast<char *>(&value), sizeof(value));
}
} // namespace file
} // namespace germanairlinesva
#endif
-50
View File
@@ -1,50 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_LOGBOOK_H
#define GERMANAIRLINESVA_FILE_LOGBOOK_H
#include <cstdint>
#include <fstream>
#include <string>
#include <utility>
#include <vector>
#include "constants.h"
#include "helpers.hpp"
#include "logbookEntry.h"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
/*
* Logbook Header (6)
* CHAR[5] | UINT8
* --------+--------
* VGAL | VERSION
*
* Logbook Entries (n)
* LOGBOOKENTRY[]
*/
class Logbook
{
private:
std::vector<LogbookEntry> entries;
void fromFile();
void readVersion1(std::ifstream &in);
public:
Logbook();
template <class... Args> inline void addEntry(Args &&...args)
{
this->entries.emplace_back(std::forward<Args>(args)...);
}
void toFile() const;
};
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
#endif
-158
View File
@@ -1,158 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_LOGBOOKENTRY_H
#define GERMANAIRLINESVA_FILE_LOGBOOKENTRY_H
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
#include "helpers.hpp"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
/*
* Preamble (24)
* CHAR[10] | CHAR[4] | CHAR[4] | CHAR[6]
* ---------+---------------+----------+-------------
* DATE | FLIGHT NUMBER | AIRCRAFT | REGISTRATION
* Departure Airport Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Departure Gate Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Departure Runway Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Arrival Airport Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Arrival Gate Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Arrival Runway Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Times (24)
* CHAR[5] | CHAR[5] | CHAR[5] | CHAR[4] | FLOAT
* ----------+---------------+--------------+---------------+------
* OFF BLOCK | TAKEOFF (OUT) | LANDING (ON) | ON BLOCK (IN) | TOTAL
* Fuels (16)
* FLOAT | FLOAT | FLOAT | FLOAT
* ---------+-----------+---------+------
* TAXI OUT | IN FLIGHT | TAXI IN | TOTAL
* Distances (16)
* FLOAT | FLOAT | FLOAT |FLOAT
* ---------+-----------+---------+-----
* TAXI OUT | IN FLIGHT | TAXI IN |TOTAL
* Landing (9)
* FLOAT | CHAR | FLOAT
* ---------+---------- +------------
* MAX RATE | TOUCHDOWNS | MAX G-FORCE
* Recording Filename (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Postamble (5)
* FLOAT | BITFIELD
* -------+---------
* POINTS | FLAGS
* Flags Bitfield
* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* ----+-----+-----+-----+-----+-----+-----+------
* NIL | NIL | NIL | NIL | NIL | NIL | NIL | FILED
*/
class LogbookEntry
{
private:
std::string date;
std::string flightNumber;
std::string aircraftType;
std::string aircraftRegistration;
std::string departureAirport;
std::string departureGate;
std::string departureRunway;
std::string arrivalAirport;
std::string arrivalGate;
std::string arrivalRunway;
std::string offBlockTime;
std::string outTime;
std::string onTime;
std::string onBlockTime;
float totalFlightTime;
float taxiOutFuel;
float inFlightFuel;
float taxiInFuel;
float totalFuel;
float taxiOutDistance;
float inFlightDistance;
float taxiInDistance;
float totalDistance;
float maxLandingRate;
std::uint8_t touchdowns;
float maxLandingGees;
std::string recordingFilename;
float points;
std::uint8_t flags;
public:
LogbookEntry(std::string date,
std::string flightNumber,
std::string aircraftType,
std::string aircraftRegistration,
std::string departureAirport,
std::string departureGate,
std::string departureRunway,
std::string arrivalAirport,
std::string arrivalGate,
std::string arrivalRunway,
std::string offBlockTime,
std::string outTime,
std::string onTime,
std::string onBlockTime,
float totalFlightTime,
float taxiOutFuel,
float inFlightFuel,
float taxiInFuel,
float totalFuel,
float taxiOutDistance,
float inFlightDistance,
float taxiInDistance,
float totalDistance,
float maxLandingRate,
std::uint8_t touchdowns,
float maxLandingGees,
std::string recordingFilename,
float points,
std::uint8_t flags);
void toFile(std::ofstream &out) const;
};
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
#endif
-45
View File
@@ -1,45 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_RECORDING_H
#define GERMANAIRLINESVA_FILE_RECORDING_H
#include <cstdint>
#include <fstream>
#include <vector>
#include "constants.h"
#include "recordingEntry.h"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
/*
* Path Recording (6 + n * 24)
* HEADER | SEGMENTS
* Header (6)
* CHAR[5] | UINT8
* --------+--------
* VGAR | VERSION
* Path Segments (n)
* PATHSEGMENT[]
*/
class Recording
{
private:
std::vector<RecordingEntry> entries;
public:
template <class... Args> inline void addEntry(Args &&...args)
{
this->entries.emplace_back(std::forward<Args>(args)...);
}
void toFile(std::string fileName) const;
};
} // namespace recording
} // namespace file
} // namespace germanairlinesva
#endif
-60
View File
@@ -1,60 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_RECORDINGENTRY_H
#define GERMANAIRLINESVA_FILE_RECORDINGENTRY_H
#include <cstdint>
#include <fstream>
#include <vector>
#include "geodata.hpp"
#include "helpers.hpp"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
/*
* Path Segment (24)
* UINT32 | UINT16 | UINT16 | POINT
* -------+----------+-------------+------------
* TIME | ALTITUDE | GROUNDSPEED | COORDINATES
*/
class RecordingEntry
{
private:
std::uint32_t time;
std::uint16_t altitude = 0;
std::uint16_t groundSpeed = 0;
struct geodata::point coordinates {
NAN, NAN
};
public:
RecordingEntry() = default;
RecordingEntry(std::uint32_t time,
std::uint16_t altitude,
std::uint16_t groundSpeed,
struct geodata::point coordinates);
void toFile(std::ofstream &out) const;
friend inline bool operator==(const RecordingEntry &lhs,
const RecordingEntry &rhs)
{
return lhs.altitude == rhs.altitude &&
lhs.groundSpeed == rhs.groundSpeed &&
lhs.coordinates.latitude == rhs.coordinates.latitude &&
lhs.coordinates.longitude == rhs.coordinates.longitude;
}
friend inline bool operator!=(const RecordingEntry &lhs,
const RecordingEntry &rhs)
{
return !(lhs == rhs);
}
};
} // namespace recording
} // namespace file
} // namespace germanairlinesva
#endif
-72
View File
@@ -1,72 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_GATE_H
#define GERMANAIRLINESVA_FILE_GATE_H
#include <cstdint>
#include <fstream>
#include <iostream>
#include <sstream>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include "geodata.hpp"
#include "helpers.hpp"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
/*
* Representation of gate
* Center in lat/lon radians
* Radius in metres
*
* UINT8 | CHAR[] | POINT | UINT8
* -------+------------+--------+-------
* STRLEN | DESIGNATOR | CENTER | RADIUS
*/
class Gate
{
private:
std::string designator;
struct geodata::point center;
std::uint8_t radius;
public:
// From X-Plane or MakeRwys
Gate(std::string designator,
double latitude,
double longitude,
std::uint8_t radius);
// From Database
Gate(std::string designator,
struct geodata::point center,
std::uint8_t radius);
void toFile(std::ofstream &out) const;
bool contains(geodata::point coordinates) const;
inline const std::string to_string() const
{
std::ostringstream str;
str << "Gate " << this->designator << " at "
<< geodata::toDegrees(this->center.latitude) << "N "
<< geodata::toDegrees(this->center.longitude) << "E, Radius "
<< (int)this->radius;
return str.str();
}
friend inline std::ostream &operator<<(std::ostream &os,
const Gate &gate)
{
return os << gate.to_string();
}
};
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
-125
View File
@@ -1,125 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_SIMDATA_RUNWAY_H
#define GERMANAIRLINESVA_FILE_SIMDATA_RUNWAY_H
#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <utility>
#include "FSUIPC/FsGeoData.h"
#include "geodata.hpp"
#include "helpers.hpp"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
/*
* Representation of one runway with supplementary information
* Heading in degrees true
* Threshold center in lat/lon degrees
* Width and length in feet
*
* UINT8 | CHAR[] | POINT | UINT8 | UINT16 | DOUBLE
* -------+------------+--------+-------+--------+-------
* STRLEN | DESIGNATOR | CENTER | WIDTH | LENGTH | TRUHDG
*/
class Runway
{
private:
std::string designator;
struct geodata::point center;
std::uint8_t width;
std::uint16_t length;
double trueHeading;
// QUAD TEST
FSUIPC::FsLatLonQuadrilateral quad;
public:
// From X-Plane or MakeRwys
inline Runway(std::string designator,
double latitudeStart,
double longitudeStart,
double latitudeEnd,
double longitudeEnd,
double width)
{
this->designator = designator;
this->width = width * 33.280839895;
this->length = geodata::distanceEarthD(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd) *
3.280839895;
this->trueHeading = geodata::bearingDD(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd);
this->center = {latitudeStart, longitudeStart};
FSUIPC::FsLatLonPoint threshold(latitudeStart, longitudeStart);
quad = FSUIPC::FsLatLonQuadrilateral::ForRunway(threshold,
this->trueHeading,
this->width,
this->length);
}
// From database
inline Runway(std::string designator,
struct geodata::point center,
std::uint8_t width,
std::uint16_t length,
double trueHeading)
{
this->designator = designator;
this->center = center;
this->width = width;
this->length = length;
this->trueHeading = trueHeading;
FSUIPC::FsLatLonPoint threshold(center.latitude, center.longitude);
quad = FSUIPC::FsLatLonQuadrilateral::ForRunway(threshold,
trueHeading,
width,
length);
}
inline void toFile(std::ofstream &out) const
{
writeString(out, this->designator);
write<decltype(this->center)>(out, this->center);
write<decltype(this->width)>(out, this->width);
write<decltype(this->length)>(out, this->length);
write<decltype(this->trueHeading)>(out, this->trueHeading);
}
inline const std::string to_string() const
{
std::ostringstream str;
str << "Runway " << this->designator << " with threshold center "
<< this->center.latitude << "N " << this->center.longitude
<< "E, Width " << (int)this->width << "ft, Length "
<< this->length << "ft, True Heading " << this->trueHeading
<< "°, Quad " << quad.to_string(true, ' ');
return str.str();
}
friend inline std::ostream &operator<<(std::ostream &os,
const Runway &runway)
{
return os << runway.to_string();
}
};
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
-76
View File
@@ -1,76 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_SIMDATABASE_H
#define GERMANAIRLINESVA_FILE_SIMDATABASE_H
#include <cstdint>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "config/config.h"
#include "constants.h"
#include "simdata/gate.h"
#include "simdata/runway.h"
#include "simdata/simdataXP.h"
/*
* Header
*
* CHAR[5] | UINT8
* --------+--------
* VGAS | VERSION
*/
/*
* Airport
*
* UINT8 | CHAR[] | UINT16 | GATE[] | UINT8 | RUNWAY[]
* --------+--------+----------+--------+---------+---------
* STRLEN | ICAO | NUMGATES | GATES | NUMRWYS | RUNWAYS
*/
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
class SimDatabase
{
private:
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
void fromFile();
void readVersion1(std::ifstream &in);
public:
SimDatabase(int xPlaneVersion,
const char *hash,
std::unique_ptr<config::Config> &configuration,
std::function<void(const std::string)> toLog);
void toFile() const;
inline std::size_t getCount() const { return this->airports.size(); }
inline const std::pair<const std::vector<Gate>,
const std::vector<Runway>>
operator[](std::string key)
{
auto it = this->airports.find(key);
if (it != this->airports.end()) {
return this->airports[key];
} else {
return std::pair<std::vector<Gate>, std::vector<Runway>>();
}
}
};
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
-42
View File
@@ -1,42 +0,0 @@
#ifndef GERMANAIRLINESVA_FILE_SIMDATAXP_H
#define GERMANAIRLINESVA_FILE_SIMDATAXP_H
#include <fstream>
#include <map>
#include <string>
#include "simdata/gate.h"
#include "simdata/runway.h"
#include "simdata/simDatabase.h"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
int scan(
const std::string defaultFile,
const std::string sceneryPack,
const std::string logFile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports);
void makeAirport(
const std::string &kind,
std::ifstream &infile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
std::ofstream &logfile);
void makeGate15(std::vector<Gate> &gates,
const std::vector<std::string> &fields);
void makeRunway(std::vector<Runway> &runways,
const std::vector<std::string> &fields);
void makeGate1300(std::vector<Gate> &gates,
const std::vector<std::string> &fields);
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
-109
View File
@@ -1,109 +0,0 @@
#include "logbook/logbook.h"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
Logbook::Logbook()
{
if (util::fileExists(XPLANE_PLUGIN_DIRECTORY LOGBOOK)) {
this->fromFile();
}
}
void Logbook::fromFile()
{
std::ifstream in(XPLANE_PLUGIN_DIRECTORY LOGBOOK, std::ifstream::binary);
// File Header
std::string ident = readString(in, 5);
if (ident.compare("VGAL") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version = read<std::uint8_t>(in);
if (version == 1) {
while (in.peek() != EOF) {
this->readVersion1(in);
}
}
}
void Logbook::readVersion1(std::ifstream &in)
{
std::string date = readString(in, 10);
std::string flightNumber = readString(in, 4);
std::string aircraftType = readString(in, 4);
std::string aircraftRegistration = readString(in, 6);
std::string departureAirport = readString(in);
std::string departureGate = readString(in);
std::string departureRunway = readString(in);
std::string arrivalAirport = readString(in);
std::string arrivalGate = readString(in);
std::string arrivalRunway = readString(in);
std::string offBlockTime = readString(in, 5);
std::string outTime = readString(in, 5);
std::string inTime = readString(in, 5);
std::string onBlockTime = readString(in, 5);
float totalFlightTime = read<float>(in);
float taxiOutFuel = read<float>(in);
float inFlightFuel = read<float>(in);
float taxiInFuel = read<float>(in);
float totalFuel = read<float>(in);
float taxiOutDistance = read<float>(in);
float inFlightDistance = read<float>(in);
float taxiInDistance = read<float>(in);
float totalDistance = read<float>(in);
float maxLandingRate = read<float>(in);
std::uint8_t touchdowns = read<std::uint8_t>(in);
float maxLandingGees = read<float>(in);
std::string recordingFilename = readString(in);
float points = read<float>(in);
std::uint8_t flags = read<std::uint8_t>(in);
this->addEntry(date,
flightNumber,
aircraftType,
aircraftRegistration,
departureAirport,
departureGate,
departureRunway,
arrivalAirport,
arrivalGate,
arrivalRunway,
offBlockTime,
outTime,
inTime,
onBlockTime,
totalFlightTime,
taxiOutFuel,
inFlightFuel,
taxiInFuel,
totalFuel,
taxiOutDistance,
inFlightDistance,
taxiInDistance,
totalDistance,
maxLandingRate,
touchdowns,
maxLandingGees,
recordingFilename,
points,
flags);
}
void Logbook::toFile() const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY LOGBOOK, std::fstream::binary);
char header[] = {'V', 'G', 'A', 'L', '\0', 1};
out.write(header, 6);
for (const LogbookEntry &entry : this->entries) {
entry.toFile(out);
}
out.close();
}
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
-104
View File
@@ -1,104 +0,0 @@
#include "logbook/logbookEntry.h"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
LogbookEntry::LogbookEntry(std::string date,
std::string flightNumber,
std::string aircraftType,
std::string aircraftRegistration,
std::string departureAirport,
std::string departureGate,
std::string departureRunway,
std::string arrivalAirport,
std::string arrivalGate,
std::string arrivalRunway,
std::string offBlockTime,
std::string outTime,
std::string onTime,
std::string onBlockTime,
float totalFlightTime,
float taxiOutFuel,
float inFlightFuel,
float taxiInFuel,
float totalFuel,
float taxiOutDistance,
float inFlightDistance,
float taxiInDistance,
float totalDistance,
float maxLandingRate,
std::uint8_t touchdowns,
float maxLandingGees,
std::string recordingFilename,
float points,
std::uint8_t flags)
{
this->date = date;
this->flightNumber = flightNumber;
this->aircraftType = aircraftType;
this->aircraftRegistration = aircraftRegistration;
this->departureAirport = departureAirport;
this->departureGate = departureGate;
this->departureRunway = departureRunway;
this->arrivalAirport = arrivalAirport;
this->arrivalGate = arrivalGate;
this->arrivalRunway = arrivalRunway;
this->offBlockTime = offBlockTime;
this->outTime = outTime;
this->onTime = onTime;
this->onBlockTime = onBlockTime;
this->totalFlightTime = totalFlightTime;
this->taxiOutFuel = taxiOutFuel;
this->inFlightFuel = inFlightFuel;
this->taxiInFuel = taxiInFuel;
this->totalFuel = totalFuel;
this->taxiOutDistance = taxiOutDistance;
this->inFlightDistance = inFlightDistance;
this->taxiInDistance = taxiInDistance;
this->totalDistance = totalDistance;
this->maxLandingRate = maxLandingRate;
this->touchdowns = touchdowns;
this->maxLandingGees = maxLandingGees;
this->recordingFilename = recordingFilename;
this->points = points;
this->flags = flags;
}
void LogbookEntry::toFile(std::ofstream &out) const
{
writeString(out, this->date, 10);
writeString(out, this->flightNumber, 4);
writeString(out, this->aircraftType, 4);
writeString(out, this->aircraftRegistration, 6);
writeString(out, this->departureAirport);
writeString(out, this->departureGate);
writeString(out, this->departureRunway);
writeString(out, this->arrivalAirport);
writeString(out, this->arrivalGate);
writeString(out, this->arrivalRunway);
writeString(out, this->offBlockTime, 5);
writeString(out, this->outTime, 5);
writeString(out, this->onTime, 5);
writeString(out, this->onBlockTime, 5);
write<decltype(this->totalFlightTime)>(out, this->totalFlightTime);
write<decltype(this->taxiOutFuel)>(out, this->taxiOutFuel);
write<decltype(this->inFlightFuel)>(out, this->inFlightFuel);
write<decltype(this->taxiInFuel)>(out, this->taxiInFuel);
write<decltype(this->totalFuel)>(out, this->totalFuel);
write<decltype(this->taxiOutDistance)>(out, this->taxiOutDistance);
write<decltype(this->inFlightDistance)>(out, this->inFlightDistance);
write<decltype(this->taxiInDistance)>(out, this->taxiInDistance);
write<decltype(this->totalDistance)>(out, this->totalDistance);
write<decltype(this->maxLandingRate)>(out, this->maxLandingRate);
write<decltype(this->touchdowns)>(out, this->touchdowns);
write<decltype(this->maxLandingGees)>(out, this->maxLandingGees);
writeString(out, this->recordingFilename);
write<decltype(this->points)>(out, this->points);
write<decltype(this->flags)>(out, this->flags);
}
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
+62
View File
@@ -0,0 +1,62 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_PATHSEGMENT_H
#define GERMANAIRLINESVA_GACONNECTOR_PATHSEGMENT_H
#include <cstdint>
#include <cstring>
#include <vector>
/*
* Length in bytes: 20
*
* UINT16 | UINT16 | DOUBLE | DOUBLE |
* ---------+-------------+----------+-----------+
* ALTITUDE | GROUNDSPEED | LATITUDE | LONGITUDE |
*/
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;
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));
}
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);
}
};
#endif
-21
View File
@@ -1,21 +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": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 0,
"runtimeArgs": [
"-dxdebug.start_with_request=yes"
],
"env": {
"XDEBUG_MODE": "debug,develop",
"XDEBUG_CONFIG": "client_port=${port}"
}
}]
}
-136
View File
@@ -1,136 +0,0 @@
<?php
namespace germanairlinesva_recording;
class Recording
{
private const ident = "VGAR";
private const header_unpack = "Z5ident/Cversion";
private const segment_unpack = "Vtime/Saltitude/Sgroundspeed/elatitude/elongitude";
private string $file_name;
public function __construct(string $file)
{
$this->file_name = $file;
}
/**
* Generates a geoJSON representation
*
* @return geoJSON representation of path
*/
public function geoJSON()
{
$geoJSON = [
"type" => "FeatureCollection",
"features" => [
0 => [
"type" => "Feature",
"properties" => [
"stroke" => "#ff0000",
"stroke-width" => 2,
"stroke-opacity" => 1
],
"geometry" => [
"type" => "LineString",
"coordinates" => []
]
]
]
];
$segments = $this->read();
$geoJSON["features"][0]["geometry"]["coordinates"] = array_map(function ($segment) {
return array($segment["longitude"], $segment["latitude"]);
}, $segments);
return json_encode($geoJSON);
}
/**
* Reads recording
*
* Array of objects with
* @return time Segment time (number)
* @return altitude Altitude
* @return groundspeed Ground speed
* @return latitude Latitude
* @return longitude Longitude
*/
public function read()
{
$file = fopen($this->file_name, "rb");
flock($file, LOCK_SH);
$segments = [];
$version = $this->read_header($file);
if ($version == 1) {
while (($segment = $this->read_segment_1($file)) != false) {
array_push($segments, $segment);
}
}
flock($file, LOCK_UN);
fclose($file);
return $segments;
}
/**
* Reads one segment entry
*
* @param resource $file File handle
*
* If not EOF
* @return time Segment time (number)
* @return altitude Altitude
* @return groundspeed Ground speed
* @return latitude Latitude
* @return longitude Longitude
* Else
* @return false
*
* @throws InvalidArgumentException If file is not a resource
*/
private function read_segment_1($file)
{
if (false === is_resource($file)) {
throw new \InvalidArgumentException(sprintf('Argument must be a valid resource type. %s given.', gettype($file)));
}
$data = fread($file, 24);
if ($data) {
return unpack(Recording::segment_unpack, $data);
}
return false;
}
/**
* Reads the file header
*
* @param resource file File handle
*
* @return version File version
*
* @throws InvalidArgumentException If file is not a resource
* @throws UnexpectedValueException If ident mismatches
*/
private function read_header($file)
{
if (false === is_resource($file)) {
throw new \InvalidArgumentException(sprintf('Argument must be a valid resource type. %s given.', gettype($file)));
}
$header = unpack(Recording::header_unpack, fread($file, 6));
if ($header["ident"] !== Recording::ident) {
throw
new \UnexpectedValueException(sprintf("Ident mismatch. Got %s, expected %s", $header["ident"], Recording::ident));
}
return $header["version"];
}
}
-6
View File
@@ -1,6 +0,0 @@
<?php
require "Recording.php";
$r = new germanairlinesva_recording\Recording("/mnt/f/X-Plane 11/Resources/plugins/GAConnector/recordings/flight.rec");
print_r($r->geoJSON());
-22
View File
@@ -1,22 +0,0 @@
#include "recording/recording.h"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
void Recording::toFile(std::string fileName) const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY RECORDING_DIRECTORY + fileName,
std::fstream::binary);
char header[] = {'V', 'G', 'A', 'R', '\0', 1};
out.write(header, 6);
for (const RecordingEntry &entry : this->entries) {
entry.toFile(out);
}
out.close();
}
} // namespace recording
} // namespace file
} // namespace germanairlinesva
-30
View File
@@ -1,30 +0,0 @@
#include "recording/recordingEntry.h"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
RecordingEntry::RecordingEntry(std::uint32_t time,
std::uint16_t altitude,
std::uint16_t groundSpeed,
struct geodata::point coordinates)
{
this->time = time;
this->altitude = altitude;
this->groundSpeed = groundSpeed;
this->coordinates = coordinates;
};
void RecordingEntry::toFile(std::ofstream &out) const
{
write<decltype(this->time)>(out, this->time);
write<decltype(this->altitude)>(out, this->altitude);
write<decltype(this->groundSpeed)>(out, this->groundSpeed);
write<decltype(this->coordinates)>(out, this->coordinates);
}
} // namespace recording
} // namespace file
} // namespace germanairlinesva
+29
View File
@@ -0,0 +1,29 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_RECORDINGPATH_H
#define GERMANAIRLINESVA_GACONNECTOR_RECORDINGPATH_H
#include <cstdint>
#include <cstring>
#include <vector>
#include "pathSegment.hpp"
class Path
{
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++;
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif
-121
View File
@@ -1,121 +0,0 @@
#include "simdata/simDatabase.h"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
SimDatabase::SimDatabase(int xPlaneVersion,
const char *hash,
std::unique_ptr<config::Config> &configuration,
std::function<void(const std::string)> toLog)
{
if (strcmp(configuration->getScenery().c_str(), hash) != 0 ||
!util::fileExists(XPLANE_PLUGIN_DIRECTORY SIMDATABASE)) {
scan(xPlaneVersion < 12000 ? XPLANE11_BASE_SCENERY
: XPLANE12_BASE_SCENERY,
XPLANE_CUSTOM_SCENERY,
XPLANE_PLUGIN_DIRECTORY "log.txt",
airports);
configuration->updateScenery(hash);
this->toFile();
toLog("Sim Database updated");
} else {
this->fromFile();
toLog("Sim Database loaded");
}
}
void SimDatabase::fromFile()
{
std::ifstream in(XPLANE_PLUGIN_DIRECTORY SIMDATABASE,
std::ifstream::binary);
// File Header
std::string ident = readString(in, 5);
if (ident.compare("VGAS") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version = read<std::uint8_t>(in);
if (version == 1) {
this->readVersion1(in);
}
}
void SimDatabase::readVersion1(std::ifstream &in)
{
std::uint16_t numAirports = read<std::uint16_t>(in);
for (int i = 0; i < numAirports; i++) {
// ICAO
std::string icao = readString(in);
// Gates
std::uint16_t numGates = read<std::uint16_t>(in);
for (int j = 0; j < numGates; j++) {
std::string designator = readString(in);
struct geodata::point center = read<struct geodata::point>(in);
std::uint8_t radius = read<std::uint8_t>(in);
this->airports[icao].first.emplace_back(designator, center, radius);
}
// Runways
std::uint8_t numRunways = read<std::uint8_t>(in);
for (int j = 0; j < numRunways; j++) {
std::string designator = readString(in);
// Center
struct geodata::point center = read<struct geodata::point>(in);
std::uint8_t width = read<std::uint8_t>(in);
std::uint16_t length = read<std::uint16_t>(in);
double trueHeading = read<double>(in);
this->airports[icao].second.emplace_back(designator,
center,
width,
length,
trueHeading);
}
}
in.close();
}
void SimDatabase::toFile() const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY SIMDATABASE,
std::fstream::binary);
// File Header, Last member is version
std::uint8_t header[] = {'V', 'G', 'A', 'S', '\0', 1};
out.write(reinterpret_cast<const char *>(header), 6);
// Num Airports
write<std::uint16_t>(out, airports.size());
// Airport
for (const std::pair<const std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
&airport : airports) {
std::string icao = airport.first;
const std::vector<Gate> gates = airport.second.first;
const std::vector<Runway> runways = airport.second.second;
// ICAO
writeString(out, icao);
// Gates
write<std::uint16_t>(out, gates.size());
for (const Gate &gate : gates) {
gate.toFile(out);
}
// Runways
write<std::uint8_t>(out, runways.size());
for (const Runway &runway : runways) {
runway.toFile(out);
}
}
out.close();
}
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
-196
View File
@@ -1,196 +0,0 @@
#include "simdata/simdataXP.h"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
int scan(
const std::string defaultFile,
const std::string sceneryPack,
const std::string logFile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports)
{
std::ifstream base(defaultFile);
if (!base.good()) {
return 1;
}
std::ifstream custom(sceneryPack);
if (!custom.good()) {
base.close();
return 2;
}
std::ofstream logfile(logFile, std::fstream::trunc);
if (!logfile.good()) {
base.close();
custom.close();
return 3;
}
// Default
logfile << "<FILE> " << defaultFile << std::endl;
makeAirport("DEFAULT", base, airports, logfile);
base.close();
std::string line;
size_t pos;
std::vector<std::string> packs;
while (std::getline(custom, line)) {
if ((pos = line.find("SCENERY_PACK")) != std::string::npos) {
std::string path = util::rtrim_copy(line.substr(pos + 13)) +
"Earth nav data/apt.dat";
packs.emplace_back(path);
}
}
std::reverse(packs.begin(), packs.end());
for (std::string const &path : packs) {
std::ifstream pack(path);
if (pack.good()) {
logfile << "<FILE> " << path << std::endl;
makeAirport("CUSTOM", pack, airports, logfile);
pack.close();
} else {
pack.close();
logfile << "<STATUS>"
<< "Could not find " << path << std::endl;
}
}
logfile << std::endl
<< "<STATUS> Total airports: " << airports.size() << std::endl;
custom.close();
logfile.close();
return 0;
}
void makeAirport(
const std::string &kind,
std::ifstream &infile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
std::ofstream &logfile)
{
std::string line;
std::string *currentIcao = nullptr;
std::vector<Gate> tmpGates;
std::vector<Runway> tmpRunways;
int apCount = 0;
int validCount = 0;
while (std::getline(infile, line)) {
std::vector<std::string> fields = util::split(line, ' ');
fields = util::select_T<std::string>(fields, [](const std::string &s) {
return s.length() > 0;
});
if (fields.empty())
continue;
if (fields[0] == "1") {
// Write to file if ICAO is valid, and we have gates and runways
if (currentIcao != nullptr && !tmpRunways.empty() &&
!tmpGates.empty()) {
airports[*currentIcao] = {tmpGates, tmpRunways};
validCount += 1;
logfile << "\t<STATUS> " << *currentIcao << " committed"
<< std::endl;
} else if (currentIcao != nullptr) {
logfile << "\t<STATUS> " << *currentIcao
<< " had no gates or runways" << std::endl;
}
tmpGates = std::vector<Gate>();
tmpRunways = std::vector<Runway>();
currentIcao = new std::string(fields[4]);
apCount += 1;
logfile << "\t<" << kind << "> " << line << std::endl;
} else if (currentIcao != nullptr && fields[0] == "15") {
makeGate15(tmpGates, fields);
logfile << "\t\t<GATE OLD> " << line << std::endl;
} else if (fields[0] == "16" || fields[0] == "17") {
// Write to file if ICAO is valid, and we have gates and runways
if (currentIcao != nullptr && !tmpRunways.empty() &&
!tmpGates.empty()) {
airports[*currentIcao] = {tmpGates, tmpRunways};
validCount += 1;
logfile << "\t<STATUS> " << *currentIcao << " committed"
<< std::endl;
} else if (currentIcao != nullptr) {
logfile << "\t<STATUS> " << *currentIcao
<< " had no gates or runways" << std::endl;
}
tmpGates = std::vector<Gate>();
tmpRunways = std::vector<Runway>();
currentIcao = nullptr;
logfile << "\t<" << kind << " SKIPPED> " << line << std::endl;
} else if (currentIcao != nullptr && fields[0] == "100") {
makeRunway(tmpRunways, fields);
logfile << "\t\t<RUNWAY> " << line << std::endl;
} else if (currentIcao != nullptr && fields[0] == "1300") {
makeGate1300(tmpGates, fields);
logfile << "\t\t<GATE> " << line << std::endl;
}
}
if (currentIcao != nullptr && !tmpRunways.empty() && !tmpGates.empty()) {
airports[*currentIcao] = {tmpGates, tmpRunways};
validCount += 1;
logfile << "\t<STATUS> " << *currentIcao << " committed" << std::endl;
}
logfile << "<STATUS> " << apCount << " airports found, of which "
<< validCount << " are valid" << std::endl;
}
void makeGate15(std::vector<Gate> &gates,
const std::vector<std::string> &fields)
{
std::string gateName;
for (size_t j = 4; j < fields.size() - 1; j++) {
gateName += fields[j] + " ";
}
gateName += fields.back();
gateName = std::regex_replace(gateName, std::regex{","}, "0");
gates.emplace_back(gateName,
std::stod(fields[1]),
std::stod(fields[2]),
40);
}
void makeRunway(std::vector<Runway> &runways,
const std::vector<std::string> &fields)
{
runways.emplace_back(fields[8],
std::stod(fields[9]),
std::stod(fields[10]),
std::stod(fields[18]),
std::stod(fields[19]),
std::stod(fields[1]));
runways.emplace_back(fields[17],
std::stod(fields[18]),
std::stod(fields[19]),
std::stod(fields[9]),
std::stod(fields[10]),
std::stod(fields[1]));
}
void makeGate1300(std::vector<Gate> &gates,
const std::vector<std::string> &fields)
{
std::string gateName;
for (size_t j = 6; j < fields.size() - 1; j++) {
gateName += fields[j] + " ";
}
gateName += fields.back();
gateName = std::regex_replace(gateName, std::regex{","}, "0");
gates.emplace_back(gateName,
std::stod(fields[1]),
std::stod(fields[2]),
40);
}
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
+183
View File
@@ -0,0 +1,183 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_SIMULATORDATABASE_H
#define GERMANAIRLINESVA_GACONNECTOR_SIMULATORDATABASE_H
#include "gate.hpp"
#include "runway.hpp"
#include "stringExtensions.hpp"
#define CURRENT_VERSION 1
#include <cstdint>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>
/*
* Header
*
* CHAR[5] | UINT8
* --------+--------
* VGAS | VERSION
*/
/*
* Airport
*
* UINT8 | CHAR[] | UINT16 | GATE[] | UINT8 | RUNWAY[]
* --------+--------+----------+--------+---------+---------
* STRLEN | ICAO | NUMGATES | GATES | NUMRWYS | RUNWAYS
*/
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);
// 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);
}
}
in.close();
return airports;
}
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;
}
} // namespace simulatorDatabase
#endif
+1 -1
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*:**/openSSL/**:openSSL/**:**/openSSL'
GLOBIGNORE='**/XPLM/**:XPLM/**:**/XPLM:**/nlohmann/**:nlohmann/**:**/nlohmann:**/XPSDK/**:XPSDK/**:**/XPSDK:**/build*/**:build*/**:**/build*'
clang-format -verbose -i **/*.cpp
clang-format -verbose -i **/*.h
-126
View File
@@ -1,126 +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
)
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}/X-Plane/${PLUGIN_NAME}")
set_target_properties(ixwebsocket PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}
BUILD_WITH_INSTALL_NAME_DIR TRUE
)
target_compile_definitions(ixwebsocket PRIVATE
IXWEBSOCKET_USE_TLS
IXWEBSOCKET_USE_SECURE_TRANSPORT
)
target_compile_options(ixwebsocket PRIVATE
"SHELL:-arch x86_64"
)
target_link_options(ixwebsocket PRIVATE
"SHELL:-arch x86_64"
)
target_link_libraries(ixwebsocket PRIVATE
"-framework Foundation"
"-framework Security"
)
elseif(UNIX)
message("Building ixwebsocket for Linux ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}
INSTALL_RPATH "\$ORIGIN"
)
target_compile_definitions(ixwebsocket PRIVATE
IXWEBSOCKET_USE_TLS
IXWEBSOCKET_USE_OPEN_SSL
)
target_compile_options(ixwebsocket PRIVATE
-nodefaultlibs
)
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
message(STATUS "System OpenSSL: " ${OPENSSL_VERSION})
add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(ixwebsocket PRIVATE
${OPENSSL_INCLUDE_DIR}
)
target_link_libraries(ixwebsocket PRIVATE
${OPENSSL_LIBRARIES}
)
elseif(WIN32)
if (BIT STREQUAL "32")
message("Building ixwebsocket for Windows ${BIT} into ${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}")
set_target_properties(ixwebsocket PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}"
)
else()
message("Building fileixwebsocket for Windows ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}"
)
endif()
target_compile_definitions(ixwebsocket PRIVATE
IXWEBSOCKET_USE_TLS
IXWEBSOCKET_USE_OPEN_SSL
)
if(DEBUG)
target_compile_options(ixwebsocket PRIVATE
-gcodeview
)
target_link_options(ixwebsocket PRIVATE
-Wl,-pdb=
)
endif()
target_include_directories(ixwebsocket PRIVATE
${CMAKE_SOURCE_DIR}/openSSL/win${BIT}/include
)
target_link_libraries(ixwebsocket PRIVATE
wsock32
ws2_32
shlwapi
crypt32
${CMAKE_SOURCE_DIR}/openSSL/win${BIT}/libcrypto-1_1.dll
${CMAKE_SOURCE_DIR}/openSSL/win${BIT}/libssl-1_1.dll
)
endif()
target_link_libraries(ixwebsocket PRIVATE
pthread
)
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)
-61
View File
@@ -1,61 +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
-35
View File
@@ -1,35 +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
-73
View File
@@ -1,73 +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
-195
View File
@@ -1,195 +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
-44
View File
@@ -1,44 +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)
{
// It's easy with a power function to go beyond 2^32, and then
// have unexpected results, so prepare for that
const uint32_t maxRetryCountWithoutOverflow = 26;
uint32_t waitTime = 0;
if (retryCount < maxRetryCountWithoutOverflow)
{
waitTime = std::pow(2, retryCount) * 100;
}
if (waitTime < minWaitBetweenReconnectionRetries)
{
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
if (retryCount >= maxRetryCountWithoutOverflow)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
}
} // namespace ix
-97
View File
@@ -1,97 +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
-120
View File
@@ -1,120 +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
namespace ix
{
std::string gzipCompress(const std::string& str)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return std::string();
#else
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
// deflateInit2 configure the file format: request gzip instead of deflate
const int windowBits = 15;
const int GZIP_ENCODING = 16;
deflateInit2(&zs,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
windowBits | GZIP_ENCODING,
8,
Z_DEFAULT_STRATEGY);
zs.next_in = (Bytef*) str.data();
zs.avail_in = (uInt) str.size(); // set the z_stream's input
int ret;
char outbuffer[32768];
std::string outstring;
// retrieve the compressed bytes blockwise
do
{
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
zs.avail_out = sizeof(outbuffer);
ret = deflate(&zs, Z_FINISH);
if (outstring.size() < zs.total_out)
{
// append the block to the output string
outstring.append(outbuffer, zs.total_out - outstring.size());
}
} while (ret == Z_OK);
deflateEnd(&zs);
return outstring;
#endif // IXWEBSOCKET_USE_ZLIB
}
#ifdef IXWEBSOCKET_USE_DEFLATE
static uint32_t loadDecompressedGzipSize(const uint8_t* p)
{
return ((uint32_t) p[0] << 0) | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) |
((uint32_t) p[3] << 24);
}
#endif
bool gzipDecompress(const std::string& in, std::string& out)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return false;
#else
z_stream inflateState;
memset(&inflateState, 0, sizeof(inflateState));
inflateState.zalloc = Z_NULL;
inflateState.zfree = Z_NULL;
inflateState.opaque = Z_NULL;
inflateState.avail_in = 0;
inflateState.next_in = Z_NULL;
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
{
return false;
}
inflateState.avail_in = (uInt) in.size();
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
const int kBufferSize = 1 << 14;
std::array<unsigned char, kBufferSize> compressBuffer;
do
{
inflateState.avail_out = (uInt) kBufferSize;
inflateState.next_out = &compressBuffer.front();
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
inflateEnd(&inflateState);
return false;
}
out.append(reinterpret_cast<char*>(&compressBuffer.front()),
kBufferSize - inflateState.avail_out);
} while (inflateState.avail_out == 0);
inflateEnd(&inflateState);
return true;
#endif // IXWEBSOCKET_USE_ZLIB
}
} // namespace ix
-213
View File
@@ -1,213 +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, 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
-772
View File
@@ -1,772 +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 && !args->onChunkCallback)
{
ss << "Accept-Encoding: gzip"
<< "\r\n";
}
#endif
// Append extra headers
for (auto&& it : args->extraHeaders)
{
ss << it.first << ": " << it.second << "\r\n";
}
// Set a default Accept header if none is present
if (args->extraHeaders.find("Accept") == args->extraHeaders.end())
{
ss << "Accept: */*"
<< "\r\n";
}
// Set a default User agent if none is present
if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
{
// Set request compression header
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest)
{
ss << "Content-Encoding: gzip"
<< "\r\n";
}
#endif
ss << "Content-Length: " << body.size() << "\r\n";
// Set default Content-Type if unspecified
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
{
if (args->multipartBoundary.empty())
{
ss << "Content-Type: application/x-www-form-urlencoded"
<< "\r\n";
}
else
{
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
<< "\r\n";
}
}
ss << "\r\n";
ss << body;
}
else
{
ss << "\r\n";
}
std::string req(ss.str());
std::string errMsg;
// Make a cancellation object dealing with connection timeout
auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel);
auto isCancellationRequested = [&]() {
return cancelled() || _stop;
};
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotConnect;
std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Make a new cancellation object dealing with transfer timeout
cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel);
if (args->verbose)
{
std::stringstream ss;
ss << "Sending " << verb << " request "
<< "to " << host << ":" << port << std::endl
<< "request size: " << req.size() << " bytes" << std::endl
<< "=============" << std::endl
<< req << "=============" << std::endl
<< std::endl;
log(ss.str(), args);
}
if (!_socket->writeBytes(req, isCancellationRequested))
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::SendError;
std::string errorMsg("Cannot send request");
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uploadSize = req.size();
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine;
std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (args->verbose)
{
std::stringstream ss;
ss << "Status line " << line;
log(ss.str(), args);
}
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{
std::string errorMsg("Cannot parse response code from status line");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingStatus,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
headers = result.second;
if (!headersValid)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError;
std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
// Redirect ?
if ((code >= 301 && code <= 308) && args->followRedirects)
{
if (headers.find("Location") == headers.end())
{
std::string errorMsg("Missing location header for redirect");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingLocation,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (redirects >= args->maxRedirects)
{
std::stringstream ss;
ss << "Too many redirects: " << redirects;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::TooManyRedirects,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Recurse
std::string location = headers["Location"];
return request(location, verb, body, args, redirects + 1);
}
if (verb == "HEAD")
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
headers,
payload,
std::string(),
uploadSize,
downloadSize);
}
// Parse response:
if (headers.find("Content-Length") != headers.end())
{
ssize_t contentLength = -1;
ss.str("");
ss << headers["Content-Length"];
ss >> contentLength;
auto chunkResult = _socket->readBytes(contentLength,
args->onProgressCallback,
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (!args->onChunkCallback)
{
payload.reserve(contentLength);
payload += chunkResult.second;
}
}
else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked")
{
std::stringstream ss;
while (true)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second;
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uint64_t chunkSize;
ss.str("");
ss << std::hex << line;
ss >> chunkSize;
if (args->verbose)
{
std::stringstream oss;
oss << "Reading " << chunkSize << " bytes" << std::endl;
log(oss.str(), args);
}
// Read a chunk
auto chunkResult = _socket->readBytes((size_t) chunkSize,
args->onProgressCallback,
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (!args->onChunkCallback)
{
payload.reserve(payload.size() + (size_t) chunkSize);
payload += chunkResult.second;
}
// Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
return std::make_shared<HttpResponse>(code,
description,
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (chunkSize == 0) break;
}
}
else if (code == 204)
{
; // 204 is NoContent response code
}
else
{
std::string errorMsg("Cannot read http body");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotReadBody,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
downloadSize = payload.size();
// If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip")
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload;
if (!gzipDecompress(payload, decompressedPayload))
{
std::string errorMsg("Error decompressing payload");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload = decompressedPayload;
#else
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
#endif
}
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
headers,
payload,
std::string(),
uploadSize,
downloadSize);
}
HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kGet, std::string(), args);
}
HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kHead, std::string(), args);
}
HttpResponsePtr HttpClient::Delete(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kDelete, std::string(), args);
}
HttpResponsePtr HttpClient::request(const std::string& url,
const std::string& verb,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
std::string body;
if (httpFormDataParameters.empty())
{
body = serializeHttpParameters(httpParameters);
}
else
{
std::string multipartBoundary = generateMultipartBoundary();
args->multipartBoundary = multipartBoundary;
body = serializeHttpFormDataParameters(
multipartBoundary, httpFormDataParameters, httpParameters);
}
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest)
{
body = gzipCompress(body);
}
#endif
return request(url, verb, body, args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPost, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args)
{
return request(url, kPost, body, args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPut, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPut, body, args);
}
HttpResponsePtr HttpClient::patch(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPatch, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::patch(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPatch, body, args);
}
std::string HttpClient::urlEncode(const std::string& value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i)
{
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
{
std::stringstream ss;
size_t count = httpParameters.size();
size_t i = 0;
for (auto&& it : httpParameters)
{
ss << urlEncode(it.first) << "=" << urlEncode(it.second);
if (i++ < (count - 1))
{
ss << "&";
}
}
return ss.str();
}
std::string HttpClient::serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters)
{
//
// --AaB03x
// Content-Disposition: form-data; name="submit-name"
// Larry
// --AaB03x
// Content-Disposition: form-data; name="foo.txt"; filename="file1.txt"
// Content-Type: text/plain
// ... contents of file1.txt ...
// --AaB03x--
//
std::stringstream ss;
for (auto&& it : httpFormDataParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< " filename=\"" << it.first << "\""
<< "\r\n"
<< "Content-Type: application/octet-stream"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
for (auto&& it : httpParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
ss << "--" << multipartBoundary << "--\r\n";
return ss.str();
}
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
{
if (args->logger)
{
args->logger(msg);
}
}
std::string HttpClient::generateMultipartBoundary()
{
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
static std::random_device rd;
static std::mt19937 generator(rd());
std::shuffle(str.begin(), str.end(), generator);
return str;
}
} // namespace ix
-235
View File
@@ -1,235 +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
-461
View File
@@ -1,461 +0,0 @@
/*
* IXNetSystem.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXNetSystem.h"
#include <cstdint>
#include <cstdio>
#ifdef _WIN32
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 102
#endif
#ifndef ENOSPC
#define ENOSPC 28
#endif
#include <vector>
#endif
namespace ix
{
bool initNetSystem()
{
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
int err;
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
return err == 0;
#else
return true;
#endif
}
bool uninitNetSystem()
{
#ifdef _WIN32
int err = WSACleanup();
return err == 0;
#else
return true;
#endif
}
#ifdef _WIN32
struct WSAEvent
{
public:
WSAEvent(struct pollfd* fd)
: _fd(fd)
{
_event = WSACreateEvent();
}
WSAEvent(WSAEvent&& source) noexcept
{
_event = source._event;
source._event = WSA_INVALID_EVENT; // invalidate the event in the source
_fd = source._fd;
}
~WSAEvent()
{
if (_event != WSA_INVALID_EVENT)
{
// We must deselect the networkevents from the socket event. Otherwise the
// socket will report states that aren't there.
if (_fd != nullptr && _fd->fd != -1)
WSAEventSelect(_fd->fd, _event, 0);
WSACloseEvent(_event);
}
}
operator HANDLE()
{
return _event;
}
operator struct pollfd*()
{
return _fd;
}
private:
HANDLE _event;
struct pollfd* _fd;
};
#endif
//
// That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet
// (the curl folks have had problems with it).
//
// So we make it a select wrapper
//
// UPDATE: WSAPoll was fixed in Windows 10 Version 2004
//
// The optional "event" is set to nullptr if it wasn't signaled.
int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event)
{
#ifdef _WIN32
if (event && *event)
{
HANDLE interruptEvent = reinterpret_cast<HANDLE>(*event);
*event = nullptr; // the event wasn't signaled yet
if (nfds < 0 || nfds >= MAXIMUM_WAIT_OBJECTS - 1)
{
WSASetLastError(WSAEINVAL);
return SOCKET_ERROR;
}
std::vector<WSAEvent> socketEvents;
std::vector<HANDLE> handles;
// put the interrupt event as first element, making it highest priority
handles.push_back(interruptEvent);
// create the WSAEvents for the sockets
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (fd->fd >= 0)
{
// create WSAEvent and add it to the vectors
socketEvents.push_back(std::move(WSAEvent(fd)));
HANDLE handle = socketEvents.back();
if (handle == WSA_INVALID_EVENT)
{
WSASetLastError(WSAENOBUFS);
return SOCKET_ERROR;
}
handles.push_back(handle);
// mapping
long networkEvents = 0;
if (fd->events & (POLLIN )) networkEvents |= FD_READ | FD_ACCEPT;
if (fd->events & (POLLOUT /*| POLLWRNORM | POLLWRBAND*/)) networkEvents |= FD_WRITE | FD_CONNECT;
//if (fd->events & (POLLPRI | POLLRDBAND )) networkEvents |= FD_OOB;
if (WSAEventSelect(fd->fd, handle, networkEvents) != 0)
{
fd->revents = POLLNVAL;
socketEvents.pop_back();
handles.pop_back();
}
}
}
DWORD n = WSAWaitForMultipleEvents(handles.size(), handles.data(), FALSE, timeout != -1 ? static_cast<DWORD>(timeout) : WSA_INFINITE, FALSE);
if (n == WSA_WAIT_FAILED) return SOCKET_ERROR;
if (n == WSA_WAIT_TIMEOUT) return 0;
if (n == WSA_WAIT_EVENT_0)
{
// the interrupt event was signaled
*event = reinterpret_cast<void*>(interruptEvent);
return 1;
}
int handleIndex = n - WSA_WAIT_EVENT_0;
int socketIndex = handleIndex - 1;
WSANETWORKEVENTS netEvents;
int count = 0;
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
// all the signaled events must be processed.
while (socketIndex < socketEvents.size())
{
struct pollfd* fd = socketEvents[socketIndex];
memset(&netEvents, 0, sizeof(netEvents));
if (WSAEnumNetworkEvents(fd->fd, socketEvents[socketIndex], &netEvents) != 0)
{
fd->revents = POLLERR;
}
else if (netEvents.lNetworkEvents != 0)
{
// mapping
if (netEvents.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_OOB)) fd->revents |= POLLIN;
if (netEvents.lNetworkEvents & (FD_WRITE | FD_CONNECT )) fd->revents |= POLLOUT;
for (int i = 0; i < FD_MAX_EVENTS; ++i)
{
if (netEvents.iErrorCode[i] != 0)
{
fd->revents |= POLLERR;
break;
}
}
if (fd->revents != 0)
{
// only signaled sockets count
count++;
}
}
socketIndex++;
}
return count;
}
else
{
if (event && *event) *event = nullptr;
socket_t maxfd = 0;
fd_set readfds, writefds, errorfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
if (fd->fd > maxfd)
{
maxfd = fd->fd;
}
if ((fd->events & POLLIN))
{
FD_SET(fd->fd, &readfds);
}
if ((fd->events & POLLOUT))
{
FD_SET(fd->fd, &writefds);
}
if ((fd->events & POLLERR))
{
FD_SET(fd->fd, &errorfds);
}
}
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
if (ret < 0)
{
return ret;
}
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (FD_ISSET(fd->fd, &readfds))
{
fd->revents |= POLLIN;
}
if (FD_ISSET(fd->fd, &writefds))
{
fd->revents |= POLLOUT;
}
if (FD_ISSET(fd->fd, &errorfds))
{
fd->revents |= POLLERR;
}
}
return ret;
}
#else
if (event && *event) *event = nullptr;
//
// It was reported that on Android poll can fail and return -1 with
// errno == EINTR, which should be a temp error and should typically
// be handled by retrying in a loop.
// Maybe we need to put all syscall / C functions in
// a new IXSysCalls.cpp and wrap them all.
//
// The style from libuv is as such.
//
int ret = -1;
do
{
ret = ::poll(fds, nfds, timeout);
} while (ret == -1 && errno == EINTR);
return ret;
#endif
}
//
// mingw does not have inet_ntop, which were taken as is from the musl C library.
//
const char* inet_ntop(int af, const void* a0, char* s, socklen_t l)
{
#if defined(_WIN32) && defined(__GNUC__)
const unsigned char* a = (const unsigned char*) a0;
int i, j, max, best;
char buf[100];
switch (af)
{
case AF_INET:
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) return s;
break;
case AF_INET6:
if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12))
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%x:%x",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
256 * a[12] + a[13],
256 * a[14] + a[15]);
else
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%d.%d.%d.%d",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
a[12],
a[13],
a[14],
a[15]);
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
for (i = best = 0, max = 2; buf[i]; i++)
{
if (i && buf[i] != ':') continue;
j = strspn(buf + i, ":0");
if (j > max) best = i, max = j;
}
if (max > 3)
{
buf[best] = buf[best + 1] = ':';
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
}
if (strlen(buf) < l)
{
strcpy(s, buf);
return s;
}
break;
default: errno = EAFNOSUPPORT; return 0;
}
errno = ENOSPC;
return 0;
#else
return ::inet_ntop(af, a0, s, l);
#endif
}
#if defined(_WIN32) && defined(__GNUC__)
static int hexval(unsigned c)
{
if (c - '0' < 10) return c - '0';
c |= 32;
if (c - 'a' < 6) return c - 'a' + 10;
return -1;
}
#endif
//
// mingw does not have inet_pton, which were taken as is from the musl C library.
//
int inet_pton(int af, const char* s, void* a0)
{
#if defined(_WIN32) && defined(__GNUC__)
uint16_t ip[8];
unsigned char* a = (unsigned char*) a0;
int i, j, v, d, brk = -1, need_v4 = 0;
if (af == AF_INET)
{
for (i = 0; i < 4; i++)
{
for (v = j = 0; j < 3 && isdigit(s[j]); j++)
v = 10 * v + s[j] - '0';
if (j == 0 || (j > 1 && s[0] == '0') || v > 255) return 0;
a[i] = v;
if (s[j] == 0 && i == 3) return 1;
if (s[j] != '.') return 0;
s += j + 1;
}
return 0;
}
else if (af != AF_INET6)
{
errno = EAFNOSUPPORT;
return -1;
}
if (*s == ':' && *++s != ':') return 0;
for (i = 0;; i++)
{
if (s[0] == ':' && brk < 0)
{
brk = i;
ip[i & 7] = 0;
if (!*++s) break;
if (i == 7) return 0;
continue;
}
for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++)
v = 16 * v + d;
if (j == 0) return 0;
ip[i & 7] = v;
if (!s[j] && (brk >= 0 || i == 7)) break;
if (i == 7) return 0;
if (s[j] != ':')
{
if (s[j] != '.' || (i < 6 && brk < 0)) return 0;
need_v4 = 1;
i++;
break;
}
s += j + 1;
}
if (brk >= 0)
{
memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk));
for (j = 0; j < 7 - i; j++)
ip[brk + j] = 0;
}
for (j = 0; j < 8; j++)
{
*a++ = ip[j] >> 8;
*a++ = ip[j];
}
if (need_v4 && inet_pton(AF_INET, (const char*) s, a - 4) <= 0) return 0;
return 1;
#else
return ::inet_pton(af, s, a0);
#endif
}
// Convert network bytes to host bytes. Copied from the ASIO library
unsigned short network_to_host_short(unsigned short value)
{
#if defined(_WIN32)
unsigned char* value_p = reinterpret_cast<unsigned char*>(&value);
unsigned short result = (static_cast<unsigned short>(value_p[0]) << 8)
| static_cast<unsigned short>(value_p[1]);
return result;
#else // defined(_WIN32)
return ntohs(value);
#endif // defined(_WIN32)
}
} // namespace ix
-53
View File
@@ -1,53 +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;
}
void* SelectInterrupt::getEvent() const
{
return nullptr;
}
} // namespace ix
-85
View File
@@ -1,85 +0,0 @@
/*
* IXSelectInterruptEvent.cpp
*/
//
// On Windows we use a Windows Event to wake up ix::poll() (WSAWaitForMultipleEvents).
// And on any other platform that doesn't support pipe file descriptors we
// emulate the interrupt event by using a short timeout with ix::poll() and
// read from the SelectInterrupt. (see Socket::poll() "Emulation mode")
//
#include <algorithm>
#include "IXSelectInterruptEvent.h"
namespace ix
{
SelectInterruptEvent::SelectInterruptEvent()
{
#ifdef _WIN32
_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#endif
}
SelectInterruptEvent::~SelectInterruptEvent()
{
#ifdef _WIN32
CloseHandle(_event);
#endif
}
bool SelectInterruptEvent::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterruptEvent::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_valuesMutex);
// WebSocket implementation detail: We only need one of the values in the queue
if (std::find(_values.begin(), _values.end(), value) == _values.end())
_values.push_back(value);
#ifdef _WIN32
SetEvent(_event); // wake up
#endif
return true;
}
uint64_t SelectInterruptEvent::read()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
if (_values.size() > 0)
{
uint64_t value = _values.front();
_values.pop_front();
#ifdef _WIN32
// signal the event if there is still data in the queue
if (_values.size() == 0)
ResetEvent(_event);
#endif
return value;
}
return 0;
}
bool SelectInterruptEvent::clear()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
_values.clear();
#ifdef _WIN32
ResetEvent(_event);
#endif
return true;
}
void* SelectInterruptEvent::getEvent() const
{
#ifdef _WIN32
return reinterpret_cast<void*>(_event);
#else
return nullptr;
#endif
}
} // namespace ix
-26
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 _WIN32
#include "IXSelectInterruptEvent.h"
#else
#include "IXSelectInterruptPipe.h"
#endif
namespace ix
{
SelectInterruptPtr createSelectInterrupt()
{
#ifdef _WIN32
return ix::make_unique<SelectInterruptEvent>();
#else
return ix::make_unique<SelectInterruptPipe>();
#endif
}
} // namespace ix
-161
View File
@@ -1,161 +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
-83
View File
@@ -1,83 +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
-453
View File
@@ -1,453 +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)
{
PollResultType pollResult = PollResultType::ReadyForRead;
//
// We used to use ::select to poll but on Android 9 we get large fds out of
// ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching
// to ::poll does fix that.
//
// However poll isn't as portable as select and has bugs on Windows, so we
// have a shim to fallback to select on those platforms. See
// https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
//
nfds_t nfds = 1;
struct pollfd fds[2];
memset(fds, 0, sizeof(fds));
fds[0].fd = sockfd;
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
// this is ignored by poll, but our select based poll wrapper on Windows needs it
fds[0].events |= POLLERR;
// File descriptor used to interrupt select when needed
int interruptFd = -1;
void* interruptEvent = nullptr;
if (selectInterrupt)
{
interruptFd = selectInterrupt->getFd();
interruptEvent = selectInterrupt->getEvent();
if (interruptFd != -1)
{
nfds = 2;
fds[1].fd = interruptFd;
fds[1].events = POLLIN;
}
else if (interruptEvent == nullptr)
{
// Emulation mode: SelectInterrupt neither supports file descriptors nor events
// Check the selectInterrupt for requests before doing the poll().
if (readSelectInterruptRequest(selectInterrupt, &pollResult))
{
return pollResult;
}
}
}
void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled
int ret = ix::poll(fds, nfds, timeoutMs, &event);
if (ret < 0)
{
pollResult = PollResultType::Error;
}
else if (ret == 0)
{
pollResult = PollResultType::Timeout;
if (selectInterrupt && interruptFd == -1 && interruptEvent == nullptr)
{
// Emulation mode: SelectInterrupt neither supports fd nor events
// Check the selectInterrupt for requests
readSelectInterruptRequest(selectInterrupt, &pollResult);
}
}
else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr))
{
// The InterruptEvent was signaled
readSelectInterruptRequest(selectInterrupt, &pollResult);
}
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN)
{
pollResult = PollResultType::ReadyForRead;
}
else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT)
{
pollResult = PollResultType::ReadyForWrite;
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (fds[0].revents & POLLERR)
{
pollResult = PollResultType::Error;
}
#else
int optval = -1;
socklen_t optlen = sizeof(optval);
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0)
{
pollResult = PollResultType::Error;
// set errno to optval so that external callers can have an
// appropriate error description when calling strerror
errno = optval;
}
#endif
}
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
fds[0].revents & POLLNVAL))
{
pollResult = PollResultType::Error;
}
return pollResult;
}
bool Socket::readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
PollResultType* pollResult)
{
uint64_t value = selectInterrupt->read();
if (value == SelectInterrupt::kSendRequest)
{
*pollResult = PollResultType::SendRequest;
return true;
}
else if (value == SelectInterrupt::kCloseRequest)
{
*pollResult = PollResultType::CloseRequest;
return true;
}
return false;
}
PollResultType Socket::isReadyToRead(int timeoutMs)
{
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = true;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
PollResultType Socket::isReadyToWrite(int timeoutMs)
{
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = false;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
// Wake up from poll/select by writing to the pipe which is watched by select
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
{
return _selectInterrupt->notify(wakeUpCode);
}
bool Socket::isWakeUpFromPollSupported()
{
return _selectInterrupt->getFd() != -1 || _selectInterrupt->getEvent() != nullptr;
}
bool Socket::accept(std::string& errMsg)
{
if (_sockfd == -1)
{
errMsg = "Socket is uninitialized";
return false;
}
return true;
}
bool Socket::connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (!_selectInterrupt->clear()) return false;
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
return _sockfd != -1;
}
void Socket::close()
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (_sockfd == -1) return;
closeSocket(_sockfd);
_sockfd = -1;
}
ssize_t Socket::send(char* buffer, size_t length)
{
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return ::send(_sockfd, buffer, length, flags);
}
ssize_t Socket::send(const std::string& buffer)
{
return send((char*) &buffer[0], buffer.size());
}
ssize_t Socket::recv(void* buffer, size_t length)
{
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return ::recv(_sockfd, (char*) buffer, length, flags);
}
int Socket::getErrno()
{
int err;
#ifdef _WIN32
err = WSAGetLastError();
#else
err = errno;
#endif
return err;
}
bool Socket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
}
void Socket::closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
bool Socket::init(std::string& errorMsg)
{
return _selectInterrupt->init(errorMsg);
}
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
int offset = 0;
int len = (int) str.size();
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send((char*) &str[offset], len);
// We wrote some bytes, as needed, all good.
if (ret > 0)
{
if (ret == len)
{
return true;
}
else
{
offset += ret;
len -= ret;
continue;
}
}
// There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded())
{
continue;
}
// There was an error during the write, abort
else
{
return false;
}
}
}
bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested)
{
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret;
ret = recv(buffer, 1);
// We read one byte, as needed, all good.
if (ret == 1)
{
return true;
}
// There is possibly something to be read, try again
else if (ret < 0 && Socket::isWaitNeeded())
{
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error)
{
return false;
}
}
// There was an error during the read, abort
else
{
return false;
}
}
}
std::pair<bool, std::string> Socket::readLine(
const CancellationRequest& isCancellationRequested)
{
char c;
std::string line;
line.reserve(64);
for (int i = 0; i < 2 || (line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
{
if (!readByte(&c, isCancellationRequested))
{
// Return what we were able to read
return std::make_pair(false, line);
}
line += c;
}
return std::make_pair(true, line);
}
std::pair<bool, std::string> Socket::readBytes(
size_t length,
const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested)
{
std::array<uint8_t, 1 << 14> readBuffer;
std::vector<uint8_t> output;
size_t bytesRead = 0;
while (bytesRead != length)
{
if (isCancellationRequested && isCancellationRequested())
{
const std::string errorMsg("Cancellation Requested");
return std::make_pair(false, errorMsg);
}
size_t size = std::min(readBuffer.size(), length - bytesRead);
ssize_t ret = recv((char*) &readBuffer[0], size);
if (ret > 0)
{
if (onChunkCallback)
{
std::string chunk(readBuffer.begin(), readBuffer.begin() + ret);
onChunkCallback(chunk);
}
else
{
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
}
bytesRead += ret;
}
else if (ret <= 0 && !Socket::isWaitNeeded())
{
const std::string errorMsg("Recv Error");
return std::make_pair(false, errorMsg);
}
if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length);
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error)
{
const std::string errorMsg("Poll Error");
return std::make_pair(false, errorMsg);
}
}
return std::make_pair(true, std::string(output.begin(), output.end()));
}
} // namespace ix
-313
View File
@@ -1,313 +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
-152
View File
@@ -1,152 +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
#include "IXSelectInterruptFactory.h"
namespace ix
{
//
// This function can be cancelled every 50 ms
// This is important so that we don't block the main UI thread when shutting down a
// connection which is already trying to reconnect, and can be blocked waiting for
// ::connect to respond.
//
int SocketConnect::connectToAddress(const struct addrinfo* address,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
if (fd < 0)
{
errMsg = "Cannot create a socket";
return -1;
}
// Set the socket to non blocking mode, so that slow responses cannot
// block us for too long
SocketConnect::configure(fd);
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
if (res == -1 && !Socket::isWaitNeeded())
{
errMsg = strerror(Socket::getErrno());
Socket::closeSocket(fd);
return -1;
}
for (;;)
{
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
Socket::closeSocket(fd);
errMsg = "Cancelled";
return -1;
}
int timeoutMs = 10;
bool readyToRead = false;
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt();
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
if (pollResult == PollResultType::Timeout)
{
continue;
}
else if (pollResult == PollResultType::Error)
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
else if (pollResult == PollResultType::ReadyForWrite)
{
return fd;
}
else
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
}
}
int SocketConnect::connect(const std::string& hostname,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
//
// First do DNS resolution
//
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
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
-64
View File
@@ -1,64 +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
-361
View File
@@ -1,361 +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
-849
View File
@@ -1,849 +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 >= 0)
{
X509_NAME_ENTRY* cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
if (cn_entry != nullptr)
{
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char* cn = (char*) ASN1_STRING_data(cn_asn1);
if ((size_t) ASN1_STRING_length(cn_asn1) == strlen(cn) &&
checkHost(hostname, cn))
{
hostname_verifies_ok = true;
}
}
}
}
if (!hostname_verifies_ok)
{
errMsg = "OpenSSL failed - certificate was issued for a different domain.";
return false;
}
#endif
X509_free(server_cert);
return true;
}
bool SocketOpenSSL::openSSLClientHandshake(const std::string& host,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
while (true)
{
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return false;
}
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
return false;
}
ERR_clear_error();
int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1)
{
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(), host.size());
#endif
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
}
if (!handshakeSuccessful)
{
close();
return false;
}
return true;
}
void SocketOpenSSL::close()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection != nullptr)
{
SSL_free(_ssl_connection);
_ssl_connection = nullptr;
}
if (_ssl_context != nullptr)
{
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
}
Socket::close();
}
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return 0;
}
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE)
{
return write_result;
}
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
}
}
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
{
while (true)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return 0;
}
ERR_clear_error();
ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
if (read_result > 0)
{
return read_result;
}
int reason = SSL_get_error(_ssl_connection, (int) read_result);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK;
}
return -1;
}
}
} // namespace ix
#endif // IXWEBSOCKET_USE_OPEN_SSL
-495
View File
@@ -1,495 +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);
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:ac:" + std::to_string(_port));
for (;;)
{
if (_stop) return;
// Use poll to check whether a new connection is in progress
int timeoutMs = -1;
#ifdef _WIN32
// select cannot be interrupted on Windows so we need to pass a small timeout
timeoutMs = 10;
#endif
bool readyToRead = true;
PollResultType pollResult =
Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt);
if (pollResult == PollResultType::Error)
{
std::stringstream ss;
ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno());
logError(ss.str());
continue;
}
if (pollResult != PollResultType::ReadyForRead)
{
continue;
}
// Accept a connection.
// FIXME: Is this working for ipv6 ?
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(client);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr*) &client, &addressLen)) < 0)
{
if (!Socket::isWaitNeeded())
{
// FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error accepting connection: " << err << ", "
<< strerror(err);
logError(ss.str());
}
continue;
}
if (getConnectedClientsCount() >= _maxConnections)
{
std::stringstream ss;
ss << "SocketServer::run() reached max connections = " << _maxConnections << ". "
<< "Not accepting connection";
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
// Retrieve connection info, the ip address of the remote peer/client)
std::string remoteIp;
int remotePort;
if (_addressFamily == AF_INET)
{
char remoteIp4[INET_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr)
{
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp4;
}
else // AF_INET6
{
char remoteIp6[INET6_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) ==
nullptr)
{
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp6;
}
std::shared_ptr<ConnectionState> connectionState;
if (_connectionStateFactory)
{
connectionState = _connectionStateFactory();
}
connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); });
connectionState->setRemoteIp(remoteIp);
connectionState->setRemotePort(remotePort);
if (_stop) return;
// create socket
std::string errorMsg;
bool tls = _socketTLSOptions.tls;
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
if (socket == nullptr)
{
logError("SocketServer::run() cannot create socket: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
}
// Set the socket to non blocking mode + other tweaks
SocketConnect::configure(clientFd);
if (!socket->accept(errorMsg))
{
logError("SocketServer::run() tls accept failed: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
}
// Launch the handleConnection work asynchronously in its own thread.
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
_connectionsThreads.push_back(std::make_pair(
connectionState,
std::thread(
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
}
}
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
void SocketServer::runGC()
{
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:gc:" + std::to_string(_port));
for (;;)
{
// Garbage collection to shutdown/join threads for closed connections.
closeTerminatedThreads();
// We quit this thread if all connections are closed and we received
// a stop request by setting _stopGc to true.
if (_stopGc && getConnectionsThreadsCount() == 0)
{
break;
}
// Unless we are stopping the server, wait for a connection
// to be terminated to run the threads GC, instead of busy waiting
// with a sleep
if (!_stopGc)
{
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
_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
-93
View File
@@ -1,93 +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 << " tls = " << tls << std::endl;
return ss.str();
}
} // namespace ix
-37
View File
@@ -1,37 +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
-126
View File
@@ -1,126 +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
-395
View File
@@ -1,395 +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
-89
View File
@@ -1,89 +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
-75
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
-614
View File
@@ -1,614 +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& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const IXWebSocketSendData& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendUtf8Text(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendUtf8Text(const IXWebSocketSendData& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendText(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
if (!validateUtf8(text))
{
close(WebSocketCloseConstants::kInvalidFramePayloadData,
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
return false;
}
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::ping(const std::string& text)
{
// 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 IXWebSocketSendData& message,
SendMessageKind sendMessageKind,
const OnProgressCallback& onProgressCallback)
{
if (!isConnected()) return WebSocketSendInfo(false);
//
// It is OK to read and write on the same socket in 2 different threads.
// https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid
//
// This makes it so that messages are sent right away, and we dont need
// a timeout while we poll to keep wake ups to a minimum (which helps
// with battery life), and use the system select call to notify us when
// incoming messages are arriving / there's data to be received.
//
std::lock_guard<std::mutex> lock(_writeMutex);
WebSocketSendInfo webSocketSendInfo;
switch (sendMessageKind)
{
case SendMessageKind::Text:
{
webSocketSendInfo = _ws.sendText(message, onProgressCallback);
}
break;
case SendMessageKind::Binary:
{
webSocketSendInfo = _ws.sendBinary(message, onProgressCallback);
}
break;
case SendMessageKind::Ping:
{
webSocketSendInfo = _ws.sendPing(message);
}
break;
}
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
return webSocketSendInfo;
}
ReadyState WebSocket::getReadyState() const
{
switch (_ws.getReadyState())
{
case ix::WebSocketTransport::ReadyState::OPEN: return ReadyState::Open;
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
case ix::WebSocketTransport::ReadyState::CLOSING: return ReadyState::Closing;
case ix::WebSocketTransport::ReadyState::CLOSED: return ReadyState::Closed;
default: return ReadyState::Closed;
}
}
std::string WebSocket::readyStateToString(ReadyState readyState)
{
switch (readyState)
{
case ReadyState::Open: return "OPEN";
case ReadyState::Connecting: return "CONNECTING";
case ReadyState::Closing: return "CLOSING";
case ReadyState::Closed: return "CLOSED";
default: return "UNKNOWN";
}
}
void WebSocket::enableAutomaticReconnection()
{
_automaticReconnection = true;
}
void WebSocket::disableAutomaticReconnection()
{
_automaticReconnection = false;
}
bool WebSocket::isAutomaticReconnectionEnabled() const
{
return _automaticReconnection;
}
size_t WebSocket::bufferedAmount() const
{
return _ws.bufferedAmount();
}
void WebSocket::addSubProtocol(const std::string& subProtocol)
{
std::lock_guard<std::mutex> lock(_configMutex);
_subProtocols.push_back(subProtocol);
}
const std::vector<std::string>& WebSocket::getSubProtocols()
{
std::lock_guard<std::mutex> lock(_configMutex);
return _subProtocols;
}
} // namespace ix
-36
View File
@@ -1,36 +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
-363
View File
@@ -1,363 +0,0 @@
/*
* IXWebSocketHandshake.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketHandshake.h"
#include "IXBase64.h"
#include "IXHttp.h"
#include "IXSocketConnect.h"
#include "IXStrCaseCompare.h"
#include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXWebSocketHandshakeKeyGen.h"
#include <algorithm>
#include <iostream>
#include <random>
#include <sstream>
namespace ix
{
WebSocketHandshake::WebSocketHandshake(
std::atomic<bool>& requestInitCancellation,
std::unique_ptr<Socket>& socket,
WebSocketPerMessageDeflatePtr& perMessageDeflate,
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
std::atomic<bool>& enablePerMessageDeflate)
: _requestInitCancellation(requestInitCancellation)
, _socket(socket)
, _perMessageDeflate(perMessageDeflate)
, _perMessageDeflateOptions(perMessageDeflateOptions)
, _enablePerMessageDeflate(enablePerMessageDeflate)
{
}
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
{
return CaseInsensitiveLess::cmp(a, b) == 0;
}
std::string WebSocketHandshake::genRandomString(const int len)
{
std::string alphanum = "0123456789"
"ABCDEFGH"
"abcdefgh";
std::random_device r;
std::default_random_engine e1(r());
std::uniform_int_distribution<int> dist(0, (int) alphanum.size() - 1);
std::string s;
s.resize(len);
for (int i = 0; i < len; ++i)
{
int x = dist(e1);
s[i] = alphanum[x];
}
return s;
}
WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
{
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << code;
ss << " ";
ss << reason;
ss << "\r\n";
ss << "Server: " << userAgent() << "\r\n";
// Socket write can only be cancelled through a timeout here, not manually.
static std::atomic<bool> requestInitCancellation(false);
auto isCancellationRequested =
makeCancellationRequestWithTimeout(1, requestInitCancellation);
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(false, 500, "Timed out while sending error response");
}
return WebSocketInitResult(false, code, reason);
}
WebSocketInitResult WebSocketHandshake::clientHandshake(
const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs)
{
_requestInitCancellation = false;
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string errMsg;
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success)
{
std::stringstream ss;
ss << "Unable to connect to " << host << " on port " << port << ", error: " << errMsg;
return WebSocketInitResult(false, 0, ss.str());
}
// Generate a random 16 bytes string and base64 encode it.
//
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16));
std::stringstream ss;
ss << "GET " << path << " HTTP/1.1\r\n";
ss << "Host: " << host << ":" << port << "\r\n";
ss << "Upgrade: websocket\r\n";
ss << "Connection: Upgrade\r\n";
ss << "Sec-WebSocket-Version: 13\r\n";
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
// User-Agent can be customized by users
if (extraHeaders.find("User-Agent") == extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
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
-74
View File
@@ -1,74 +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
@@ -1,96 +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 IXWebSocketSendData& in, std::string& out)
{
return _compressor->compress(in, out);
}
bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out)
{
return _compressor->compress(in, out);
}
bool WebSocketPerMessageDeflate::decompress(const std::string& in, std::string& out)
{
return _decompressor->decompress(in, out);
}
} // namespace ix
@@ -1,252 +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 IXWebSocketSendData& in,
std::string& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
std::vector<uint8_t>& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
std::string& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
std::vector<uint8_t>& out)
{
return compressData(in, out);
}
template<typename T, typename S>
bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
{
#ifdef IXWEBSOCKET_USE_ZLIB
//
// 7.2.1. Compression
//
// An endpoint uses the following algorithm to compress a message.
//
// 1. Compress all the octets of the payload of the message using
// DEFLATE.
//
// 2. If the resulting data does not end with an empty DEFLATE block
// with no compression (the "BTYPE" bits are set to 00), append an
// empty DEFLATE block with no compression to the tail end.
//
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
// After this step, the last octet of the compressed data contains
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
// set to 00.
//
size_t output;
// Clear output
out.clear();
if (in.empty())
{
// See issue #167
// The normal buffer size should be 6 but
// we remove the 4 octets from the tail (#4)
uint8_t buf[2] = {0x02, 0x00};
out.push_back(buf[0]);
out.push_back(buf[1]);
return true;
}
_deflateState.avail_in = (uInt) in.size();
_deflateState.next_in = (Bytef*) in.data();
do
{
// Output to local buffer
_deflateState.avail_out = (uInt) _compressBuffer.size();
_deflateState.next_out = &_compressBuffer.front();
deflate(&_deflateState, _flush);
output = _compressBuffer.size() - _deflateState.avail_out;
out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output);
} while (_deflateState.avail_out == 0);
if (endsWithEmptyUnCompressedBlock(out))
{
out.resize(out.size() - 4);
}
return true;
#else
return false;
#endif
}
//
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
memset(&_inflateState, 0, sizeof(_inflateState));
_inflateState.zalloc = Z_NULL;
_inflateState.zfree = Z_NULL;
_inflateState.opaque = Z_NULL;
_inflateState.avail_in = 0;
_inflateState.next_in = Z_NULL;
#endif
}
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
inflateEnd(&_inflateState);
#endif
}
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool clientNoContextTakeOver)
{
#ifdef IXWEBSOCKET_USE_ZLIB
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
if (ret != Z_OK) return false;
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
return true;
#else
return false;
#endif
}
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
{
#ifdef IXWEBSOCKET_USE_ZLIB
//
// 7.2.2. Decompression
//
// An endpoint uses the following algorithm to decompress a message.
//
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
// payload of the message.
//
// 2. Decompress the resulting data using DEFLATE.
//
std::string inFixed(in);
inFixed += kEmptyUncompressedBlock;
_inflateState.avail_in = (uInt) inFixed.size();
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
// Clear output
out.clear();
do
{
_inflateState.avail_out = (uInt) _compressBuffer.size();
_inflateState.next_out = &_compressBuffer.front();
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
return false; // zlib error
}
out.append(reinterpret_cast<char*>(&_compressBuffer.front()),
_compressBuffer.size() - _inflateState.avail_out);
} while (_inflateState.avail_out == 0);
return true;
#else
return false;
#endif
}
} // namespace ix
@@ -1,185 +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=" << static_cast<int>(_serverMaxWindowBits);
ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits);
ss << "\r\n";
return ss.str();
#else
return std::string();
#endif
}
bool WebSocketPerMessageDeflateOptions::enabled() const
{
#ifdef IXWEBSOCKET_USE_ZLIB
return _enabled;
#else
return false;
#endif
}
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
{
return _clientNoContextTakeover;
}
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
{
return _serverNoContextTakeover;
}
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
{
return _clientMaxWindowBits;
}
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
{
return _serverMaxWindowBits;
}
bool WebSocketPerMessageDeflateOptions::startsWith(const std::string& str,
const std::string& start)
{
return str.compare(0, start.length(), start) == 0;
}
std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
{
std::string out(str);
out.erase(
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
out.end());
return out;
}
} // namespace ix
-137
View File
@@ -1,137 +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
-229
View File
@@ -1,229 +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("Srv:ws:" + connectionState->getId());
auto webSocket = std::make_shared<WebSocket>();
if (_onConnectionCallback)
{
_onConnectionCallback(webSocket, connectionState);
if (!webSocket->isOnMessageCallbackRegistered())
{
logError("WebSocketServer Application developer error: Server callback improperly "
"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
-124
View File
@@ -1,124 +0,0 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string>
namespace macaron {
class Base64 {
public:
static std::string Encode(const std::string data) {
static constexpr char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
}
#endif /* _MACARON_BASE64_H_ */
-32
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
@@ -1,18 +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
-54
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
-67
View File
@@ -1,67 +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
@@ -1,16 +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
-12
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
-15
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
-134
View File
@@ -1,134 +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 <atomic>
#include <tuple>
#include <unordered_map>
namespace ix
{
enum class HttpErrorCode : int
{
Ok = 0,
CannotConnect = 1,
Timeout = 2,
Gzip = 3,
UrlMalformed = 4,
CannotCreateSocket = 5,
SendError = 6,
ReadError = 7,
CannotReadStatusLine = 8,
MissingStatus = 9,
HeaderParsingError = 10,
MissingLocation = 11,
TooManyRedirects = 12,
ChunkReadError = 13,
CannotReadBody = 14,
Cancelled = 15,
Invalid = 100
};
struct HttpResponse
{
int statusCode;
std::string description;
HttpErrorCode errorCode;
WebSocketHttpHeaders headers;
std::string body;
std::string errorMsg;
uint64_t uploadSize;
uint64_t downloadSize;
HttpResponse(int s = 0,
const std::string& des = std::string(),
const HttpErrorCode& c = HttpErrorCode::Ok,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& b = std::string(),
const std::string& e = std::string(),
uint64_t u = 0,
uint64_t d = 0)
: statusCode(s)
, description(des)
, errorCode(c)
, headers(h)
, body(b)
, errorMsg(e)
, uploadSize(u)
, downloadSize(d)
{
;
}
};
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
using HttpParameters = std::unordered_map<std::string, std::string>;
using HttpFormDataParameters = std::unordered_map<std::string, std::string>;
using Logger = std::function<void(const std::string&)>;
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
struct HttpRequestArgs
{
std::string url;
std::string verb;
WebSocketHttpHeaders extraHeaders;
std::string body;
std::string multipartBoundary;
int connectTimeout = 60;
int transferTimeout = 1800;
bool followRedirects = true;
int maxRedirects = 5;
bool verbose = false;
bool compress = true;
bool compressRequest = false;
Logger logger;
OnProgressCallback onProgressCallback;
OnChunkCallback onChunkCallback;
std::atomic<bool> cancel;
};
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
struct HttpRequest
{
std::string uri;
std::string method;
std::string version;
std::string body;
WebSocketHttpHeaders headers;
HttpRequest(const std::string& u,
const std::string& m,
const std::string& v,
const std::string& b,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
: uri(u)
, method(m)
, version(v)
, body(b)
, headers(h)
{
}
};
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class Http
{
public:
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
std::unique_ptr<Socket>& socket, int timeoutSecs);
static bool sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket);
static std::pair<std::string, int> parseStatusLine(const std::string& line);
static std::tuple<std::string, std::string, std::string> parseRequestLine(
const std::string& line);
static std::string trim(const std::string& str);
};
} // namespace ix
-123
View File
@@ -1,123 +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
-59
View File
@@ -1,59 +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

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