43 Commits

Author SHA1 Message Date
Kilian 92159b0669 Implement FSUIPC FSData Classes 2022-09-23 22:16:01 +02:00
Kilian 115e4d9265 Migrate to radians instead of degrees to lessen conversion errors 2022-09-23 00:16:52 +02:00
Kilian 07487e9e51 Format 2022-09-11 23:26:28 +02:00
Kilian 46a0607b55 Fix Linux 2022-09-11 21:45:09 +02:00
Kilian de8b282ab1 Fix merge errors 2022-09-11 21:34:26 +02:00
Kilian 6027095c38 Merge branch 'develop' of https://git.hofmannnet.myhome-server.de/GermanAirlines/GermanAirlinesVA-GAConnector into develop 2022-09-11 21:20:13 +02:00
Kilian 2cf03d2933 Refactor SimDatabase 2022-09-11 21:14:46 +02:00
Kilian 161941ac1b REfactor SimDatabase PRODUCES CRASH ON EXIT 2022-09-11 04:31:01 +02:00
Kilian 4445f1f12a Refactor Config 2022-09-10 22:55:04 +02:00
Kilian d6d3c0b4d9 Recording now uses new write 2022-09-10 02:05:38 +02:00
Kilian 94fbc1f558 Runways and Gates now with new read/write 2022-09-10 01:07:00 +02:00
Kilian 2fc2170763 Future proofed file read/write 2022-09-09 23:28:25 +02:00
Kilian 699e2a4784 Consts and references 2022-09-09 20:10:38 +02:00
Kilian b050c23577 Initial Logbook read and write 2022-09-09 02:05:57 +02:00
Kilian a5e23f4b43 Fix linux 2022-09-08 19:10:55 +02:00
Kilian 7d45ca71ba Adjust for new Platform Requirements 2022-09-08 02:20:55 +02:00
Kilian 8b9f5d0406 Start on Logbook 2022-09-08 02:02:09 +02:00
Kilian 2bda541bce PHP Namespacing und Test 2022-09-07 20:32:14 +02:00
Kilian 25c4d8474d Refactor 2022-09-07 03:04:26 +02:00
Kilian 910afad3b5 Fix Recording file format 2022-09-07 02:49:42 +02:00
Kilian 2aa658c15e Refactor 2022-09-06 22:12:32 +02:00
Kilian 3559b124a7 Namespacing and Lib for files 2022-09-06 21:15:01 +02:00
Kilian 2510f4968c Version
Global AP
2022-09-06 16:41:34 +02:00
Kilian 752646c744 Fix linux 2022-09-06 01:16:36 +02:00
Kilian b22a60c85e Namespaces
Better File handling
2022-09-06 00:29:19 +02:00
Kilian 9dcca746ed Reimpl send prot 2022-09-05 17:52:52 +02:00
Kilian 0612afb37b adjust protocoll 2022-09-05 17:45:18 +02:00
Kilian ff179ee3a4 Specify 32 bit SSL 2022-09-05 16:19:59 +02:00
Kilian cd022c8063 System openSSL for linux 2022-09-05 16:04:25 +02:00
Kilian a536a9c918 Remove tlsOptions 2022-09-05 15:33:31 +02:00
Kilian 3dce2ca488 Mutate libs to use default system certs file 2022-09-05 14:59:08 +02:00
Kilian b695f4cb6c Allow non standard cert dir 2022-09-05 14:32:08 +02:00
Kilian 2914e1fa59 SSL Cert dir 2022-09-05 12:27:38 +02:00
Kilian 71656e4a66 Reimplementd IX with OpenSSL 2022-09-05 01:41:09 +02:00
Kilian 5e80c41538 Platform openSSL 2022-09-04 22:15:36 +02:00
Kilian c6ad412df9 Remove bug test 2022-09-04 18:56:48 +02:00
Kilian 0fe7335987 Style, Analyze 2022-09-04 03:06:09 +02:00
Kilian 3b2f146679 Move MakeRwys to own lib 2022-09-04 01:19:50 +02:00
Kilian a820f7456a Remove ixWebSocket 2022-09-04 00:50:49 +02:00
Kilian fbee417594 IX own module 2022-09-02 19:41:26 +02:00
Kilian 7439747876 merge branch 2022-09-02 17:42:48 +02:00
Kilian 27edf28f42 Merge branch 'develop' of https://git.hofmannnet.myhome-server.de/GermanAirlines/GermanAirlinesVA-GAConnector into develop 2022-09-02 17:40:27 +02:00
Kilian ca5aab1344 @rpath in plugin 2022-09-02 17:40:14 +02:00
383 changed files with 77623 additions and 10082 deletions
+5 -4
View File
@@ -18,18 +18,19 @@ BreakBeforeBraces: Linux
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CommentPragmas: "^ IWYU pragma:"
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerBinding: false
ExperimentalAutoDetectBinPacking: false
IndentAccessModifiers: true
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: true
IndentWidth: 4
IndentWidth: 2
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
NamespaceIndentation: Inner
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
ObjCBlockIndentWidth: 4
@@ -50,5 +51,5 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
Standard: c++14
SortIncludes: true
TabWidth: 4
TabWidth: 2
UseTab: Never
-8
View File
@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
Generated
-1
View File
@@ -1 +0,0 @@
GermanAirlinesVA_GAConnector
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/GermanAirlinesVA-GAConnector.iml" filepath="$PROJECT_DIR$/.idea/GermanAirlinesVA-GAConnector.iml" />
</modules>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+38 -5
View File
@@ -1,14 +1,47 @@
{
"configurations": [
{
"configurations": [{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/opt/llvm-mingw/bin/clang++",
"cStandard": "c17",
"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
+17
View File
@@ -0,0 +1,17 @@
[{
"name": "Clang 13.0.0 Mac",
"toolchainFile": "${workspaceFolder}/toolchain-mac.cmake"
},
{
"name": "Clang 13.0.0 Linux",
"toolchainFile": "${workspaceFolder}/toolchain-lin.cmake"
},
{
"name": "Clang 13.0.0 Windows 32",
"toolchainFile": "${workspaceFolder}/toolchain-win-32.cmake"
},
{
"name": "Clang 13.0.0 Windows 64",
"toolchainFile": "${workspaceFolder}/toolchain-win-64.cmake"
}
]
-34
View File
@@ -1,34 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "clang++-10 - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: clang++-10 build active file",
"miDebuggerPath": "/usr/bin/lldb-mi-10"
}
]
}
+26 -3
View File
@@ -1,6 +1,6 @@
{
"cmake.generator": "Unix Makefiles",
"files.associations": {
"thread": "cpp",
"algorithm": "cpp",
"array": "cpp",
"atomic": "cpp",
@@ -60,8 +60,31 @@
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"valarray": "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"
},
}
-28
View File
@@ -1,28 +0,0 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: clang++-10 build active file",
"command": "/usr/bin/clang++-10",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}
+13 -2
View File
@@ -1,15 +1,26 @@
cmake_minimum_required(VERSION 3.8)
cmake_policy(SET CMP0068 NEW)
project(GermanAirlinesVA_GAConnector)
project(GermanAirlinesVA_GAConnector C CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR TRUE)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(PLUGIN_NAME GAConnector)
option(DEBUG "Debug symbols" OFF)
add_subdirectory(
xplugin
ixwebsocket
)
add_subdirectory(
file
)
if(NOT (WIN32 AND (BIT STREQUAL "32")))
add_subdirectory(
xplugin
)
endif()
Vendored
+8 -116
View File
@@ -17,7 +17,9 @@ pipeline {
}
steps {
sh 'bash ./build.sh win32'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/win32'
sh 'bash ./build.sh win64'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/win64'
}
}
stage('Build Linux Debug') {
@@ -35,8 +37,8 @@ pipeline {
DEBUG = 1
}
steps {
sh 'bash ./build.sh lin32'
sh 'bash ./build.sh lin64'
sh 'bash ./build.sh lin'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/lin'
}
}
stage('Build MacOSX Debug') {
@@ -55,6 +57,7 @@ pipeline {
}
steps {
sh 'bash ./build.sh mac'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/mac'
}
}
stage('Archive Debug') {
@@ -62,120 +65,9 @@ pipeline {
branch 'develop'
}
steps {
zip zipFile: 'Debug.zip', archive: true, dir: 'build/Plugin'
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'
zip zipFile: 'X-Plane Debug.zip', archive: true, dir: 'build/X-Plane'
zip zipFile: 'ESP Debug.zip', archive: true, dir: 'build/ESP'
zip zipFile: 'Analysis.zip', archive: true, dir: 'build/Analysis'
sh 'rm -rf build'
}
}
+3
View File
@@ -9,3 +9,6 @@
X-Plane Plugin for all supported platforms.
Demo of WebSocket capabilities
Requirements:
- openSSL 1.1.x
+2
View File
@@ -0,0 +1,2 @@
- Update OSXCross Docker image to SDK 11
- Implement ARM64 arch for Plugin
+14 -7
View File
@@ -2,25 +2,30 @@
mkdir -p build
cd build
mkdir -p Plugin/GAConnector/32
mkdir -p Plugin/GAConnector/64
mkdir -p X-Plane/GAConnector/64
mkdir -p X-Plane/GAConnector/recordings
mkdir -p ESP/GAConnector
mkdir -p ESP/GAConnector/recordings
rm -f CMakeCache.txt
case $1 in
"mac")
cmake -DDEBUG=$DEBUG -DCMAKE_TOOLCHAIN_FILE=../toolchain-mac.cmake ..
;;
"lin32")
cmake -DDEBUG=$DEBUG -DBIT=32 -DCMAKE_TOOLCHAIN_FILE=../toolchain-lin.cmake ..
;;
"lin64")
"lin")
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
@@ -33,5 +38,7 @@ fi
if [ "$1" = "mac" ] && [ "$DEBUG" = "1" ]
then
/opt/osxcross/target/bin/osxcross-llvm-dsymutil Plugin/GAConnector/mac.xpl
/opt/osxcross/target/bin/osxcross-llvm-dsymutil X-Plane/GAConnector/mac.xpl
fi
+84
View File
@@ -0,0 +1,84 @@
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
@@ -0,0 +1,80 @@
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
@@ -0,0 +1,47 @@
#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
@@ -1,47 +0,0 @@
#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
@@ -0,0 +1,42 @@
#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
-56
View File
@@ -1,56 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_GATE_H
#define GERMANAIRLINESVA_GACONNECTOR_GATE_H
#include <cstdint>
#include <cstring>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
/*
* Representation of X-Plane gate
* Heading in degrees (0...360)
*
* Length in bytes: 18 + length of designator
* Designator must be null terminated
*
* UINT8 | CHAR[] | DOUBLE | DOUBLE | UINT8
* ------+------------+--------+--------+------
* LEN | DESIGNATOR | LAT | LON | WIDTH
*/
class Gate
{
private:
std::string designator;
double latitude;
double longitude;
std::vector<std::uint8_t> file;
public:
Gate(const std::string &designator, double latitude, double longitude)
{
this->designator = designator;
this->latitude = latitude;
this->longitude = longitude;
file = std::vector<std::uint8_t>(18 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++; // Designator length
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr +=
this->designator.length() + 1; // Designator plus null termination
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += 8; // Latitude
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif
+18
View File
@@ -0,0 +1,18 @@
#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
@@ -0,0 +1,57 @@
#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
@@ -0,0 +1,145 @@
#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
@@ -0,0 +1,57 @@
#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
@@ -0,0 +1,166 @@
#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
@@ -0,0 +1,60 @@
#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
@@ -0,0 +1,129 @@
#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
@@ -0,0 +1,58 @@
#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
@@ -0,0 +1,121 @@
#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
@@ -0,0 +1,60 @@
#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
@@ -0,0 +1,133 @@
#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
@@ -0,0 +1,65 @@
#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
@@ -0,0 +1,133 @@
#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
@@ -0,0 +1,42 @@
#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
@@ -0,0 +1,57 @@
#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
@@ -0,0 +1,50 @@
#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
@@ -0,0 +1,158 @@
#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
@@ -0,0 +1,45 @@
#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
@@ -0,0 +1,60 @@
#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
@@ -0,0 +1,72 @@
#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
@@ -0,0 +1,125 @@
#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
@@ -0,0 +1,76 @@
#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
@@ -0,0 +1,42 @@
#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
@@ -0,0 +1,109 @@
#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
@@ -0,0 +1,104 @@
#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
-57
View File
@@ -1,57 +0,0 @@
#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);
}
};
+21
View File
@@ -0,0 +1,21 @@
{
// 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
@@ -0,0 +1,136 @@
<?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
@@ -0,0 +1,6 @@
<?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
@@ -0,0 +1,22 @@
#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
@@ -0,0 +1,30 @@
#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
-24
View File
@@ -1,24 +0,0 @@
#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(); }
};
-114
View File
@@ -1,114 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_RUNWAY_H
#define GERMANAIRLINESVA_GACONNECTOR_RUNWAY_H
#include <iomanip>
#include <regex>
#include <sstream>
#include <string>
#include <utility>
#include "util.hpp"
/*
* Representation of one X-Plane runway with supplementary information
* Heading in degrees (0...360)
* Width and length in feet
*
* Length in bytes: 23 + length of designator
* Designator must be null terminated
*
* UINT8 | CHAR[] | DOUBLE | DOUBLE | UINT8 | UINT16 | UINT16
* ------+------------+--------+--------+-------+--------+-------
* LEN | DESIGNATOR | LAT | LON | WIDTH | LENGTH | TRUHDG
*/
class Runway
{
private:
std::string designator;
double latitudeStart;
double longitudeStart;
std::uint8_t width;
std::uint16_t length;
std::uint16_t trueHeading;
std::vector<std::uint8_t> file;
public:
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
double latitudeEnd,
double longitudeEnd,
double width)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = (std::uint8_t)std::round(util::to_feet(width));
double dist = util::distanceEarth(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd);
this->length = (std::uint16_t)std::round(util::to_feet(dist));
this->trueHeading =
(std::uint16_t)std::round(util::bearing(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd));
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
std::uint8_t width,
std::uint16_t length,
std::uint16_t trueHeading)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = width;
this->length = length;
this->trueHeading = trueHeading;
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif
+121
View File
@@ -0,0 +1,121 @@
#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
@@ -0,0 +1,196 @@
#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
-191
View File
@@ -1,191 +0,0 @@
#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
-53
View File
@@ -1,53 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_STRINGEXTENSIONS_H
#define GERMANAIRLINESVA_GACONNECTOR_STRINGEXTENSIONS_H
#include <algorithm>
#include <sstream>
#include <string>
#include <vector>
// trim from start (in place)
static inline void ltrim(std::string &s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s)
{
s.erase(std::find_if(s.rbegin(),
s.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
s.end());
}
static inline std::string rtrim_copy(std::string s)
{
rtrim(s);
return s;
}
// trim from both ends (in place)
static inline void trim(std::string &s)
{
ltrim(s);
rtrim(s);
}
static inline std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> result;
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) {
result.push_back(item);
}
return result;
}
#endif
-304
View File
@@ -1,304 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_UTIL_H
#define GERMANAIRLINESVA_GACONNECTOR_UTIL_H
#ifdef IBM
#define WIN32_LEAN_AND_MEAN
#endif
#define BUFSIZE 1024
#define MD5LEN 16
#define EARTH_M 6371000
#ifdef IBM
// clang-format off
#include <wtypes.h>
#include <wincrypt.h>
// clang-format on
#endif
#ifdef APL
#include <CommonCrypto/CommonDigest.h>
#include <sys/mman.h>
#endif
#ifdef LIN
#include <openssl/md5.h>
#include <sys/mman.h>
#endif
#if defined APL || defined LIN
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <cmath>
#include <functional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
namespace util
{
static inline double to_feet(double value) { return value * 3.280839895; }
static inline double to_degrees(double value) { return value * 180 / M_PI; }
static inline double to_radians(double value) { return value * M_PI / 180; }
static inline double normalize(double value)
{
return fmod(value + 360, 360);
}
static inline double bearing(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double y = sin(to_radians(toLongitude) - to_radians(fromLongitude)) *
cos(to_radians(toLatitude));
double x = cos(to_radians(fromLatitude)) * sin(to_radians(toLatitude)) -
sin(to_radians(fromLatitude)) * cos(to_radians(toLatitude)) *
cos(to_radians(toLongitude) - to_radians(fromLongitude));
return normalize(to_degrees(atan2(y, x)));
}
static inline double distanceEarth(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double lat1r, lon1r, lat2r, lon2r, u, v;
lat1r = to_radians(fromLatitude);
lon1r = to_radians(fromLongitude);
lat2r = to_radians(toLatitude);
lon2r = to_radians(toLongitude);
u = sin((lat2r - lat1r) / 2);
v = sin((lon2r - lon1r) / 2);
return 2.0 * EARTH_M *
asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
}
template <typename T>
static inline std::vector<T>
select_T(const std::vector<T> &inVec,
std::function<bool(const T &)> predicate)
{
std::vector<T> result;
copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
return result;
}
#if defined APL || defined LIN
static unsigned long get_size_by_fd(int fd)
{
struct stat buf {
};
if (fstat(fd, &buf) < 0)
return 0;
return buf.st_size;
}
#endif
static void to_hex(const char *hash, char *buffer)
{
for (int i = 0; i < MD5LEN; i++) {
if (buffer != nullptr) {
sprintf(&buffer[2 * i], "%02x", hash[i] & 0xff);
}
}
}
#ifdef IBM
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> toLog)
{
BOOL bResult = FALSE;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
HANDLE hFile;
BYTE rgbFile[BUFSIZE] = {0};
DWORD cbRead = 0;
BYTE rgbHash[MD5LEN] = {0};
DWORD cbHash = 0;
// Logic to check usage goes here.
hFile = CreateFile(filepath,
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
// Get handle to the crypto provider
if (!CryptAcquireContext(&hProv,
nullptr,
nullptr,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
std::stringstream debug_msg;
debug_msg << "CryptAcquireContext returned with error "
<< GetLastError();
toLog(debug_msg.str());
CloseHandle(hFile);
return 1;
}
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
std::stringstream debug_msg;
debug_msg << "CryptCreateHash returned with error "
<< GetLastError();
toLog(debug_msg.str());
CloseHandle(hFile);
CryptReleaseContext(hProv, 0);
return 1;
}
while (
(bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, nullptr))) {
if (0 == cbRead) {
break;
}
if (!CryptHashData(hHash, rgbFile, cbRead, 0)) {
std::stringstream debug_msg;
debug_msg << "CryptHashData returned with error "
<< GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
}
if (!bResult) {
std::stringstream debug_msg;
debug_msg << "ReadFile returned with error " << GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
cbHash = MD5LEN;
if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) {
to_hex((char *)rgbHash, lastHash);
} else {
std::stringstream debug_msg;
debug_msg << "CryptGetHashParam returned with error "
<< GetLastError();
toLog(debug_msg.str());
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
CloseHandle(hFile);
return 0;
}
#endif
#ifdef APL
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> &toLog)
{
int file_descript;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
file_descript = open(filepath, O_RDONLY);
if (file_descript < 0)
return 1;
file_size = get_size_by_fd(file_descript);
file_buffer =
(char *)mmap(0, file_size, PROT_READ, MAP_SHARED, file_descript, 0);
CC_MD5_CTX context;
CC_MD5_Init(&context);
CC_MD5_Update(&context, file_buffer, (CC_LONG)file_size);
CC_MD5_Final(result, &context);
munmap(file_buffer, file_size);
close(file_descript);
to_hex((char *)result, lastHash);
return 0;
}
#pragma clang diagnostic pop
#endif
#ifdef LIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline int
generateMD5(const char *filepath,
char *buffer,
const std::function<void(const std::string)> &toLog)
{
int file_descriptor;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
file_descriptor = open(filepath, O_RDONLY);
if (file_descriptor < 0)
return 1;
file_size = get_size_by_fd(file_descriptor);
if (file_size == 0)
return 1;
file_buffer = (char *)
mmap(nullptr, file_size, PROT_READ, MAP_SHARED, file_descriptor, 0);
MD5((unsigned char *)file_buffer, file_size, result);
munmap(file_buffer, file_size);
close(file_descriptor);
to_hex((char *)result, buffer);
return 0;
}
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline void setThreadName(const std::string &name)
{
#ifdef APL
//
// Apple reserves 16 bytes for its thread names
// Notice that the Apple version of pthread_setname_np
// does not take a pthread_t argument
//
pthread_setname_np(name.substr(0, 63).c_str());
#endif
#ifdef LIN
//
// Linux only reserves 16 bytes for its thread names
// See prctl and PR_SET_NAME property in
// http://man7.org/linux/man-pages/man2/prctl.2.html
//
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
#endif
}
#pragma clang diagnostic pop
} // namespace util
#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*'
GLOBIGNORE='**/XPLM/**:XPLM/**:**/XPLM:**/ixwebsocket/**:ixwebsocket/**:**/ixwebsocket:**/nlohmann/**:nlohmann/**:**/nlohmann:**/XPSDK/**:XPSDK/**:**/XPSDK:**/build*/**:build*/**:**/build*:**/openSSL/**:openSSL/**:**/openSSL'
clang-format -verbose -i **/*.cpp
clang-format -verbose -i **/*.h
+126
View File
@@ -0,0 +1,126 @@
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)
+29 -24
View File
@@ -10,47 +10,52 @@
namespace ix
{
Bench::Bench(const std::string &description) : _description(description)
{
Bench::Bench(const std::string& description)
: _description(description)
{
reset();
}
}
Bench::~Bench()
{
if (!_reported) {
Bench::~Bench()
{
if (!_reported)
{
report();
}
}
}
void Bench::reset()
{
void Bench::reset()
{
_start = std::chrono::high_resolution_clock::now();
_reported = false;
}
}
void Bench::report()
{
void Bench::report()
{
auto now = std::chrono::high_resolution_clock::now();
auto microseconds =
std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
_duration = microseconds.count();
std::cerr << _description << " completed in " << _duration << " us"
<< std::endl;
std::cerr << _description << " completed in " << _duration << " us" << std::endl;
setReported();
}
}
void Bench::record()
{
void Bench::record()
{
auto now = std::chrono::high_resolution_clock::now();
auto microseconds =
std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
_duration = microseconds.count();
}
}
void Bench::setReported() { _reported = true; }
void Bench::setReported()
{
_reported = true;
}
uint64_t Bench::getDuration() const { return _duration; }
uint64_t Bench::getDuration() const
{
return _duration;
}
} // namespace ix
+7 -11
View File
@@ -11,29 +11,25 @@
namespace ix
{
CancellationRequest makeCancellationRequestWithTimeout(
int secs,
std::atomic<bool> &requestInitCancellation)
{
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 {
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool {
// Was an explicit cancellation requested ?
if (requestInitCancellation)
return true;
if (requestInitCancellation) return true;
auto now = std::chrono::system_clock::now();
if ((now - start) > timeout)
return true;
if ((now - start) > timeout) return true;
// No cancellation request
return false;
};
return isCancellationRequested;
}
}
} // namespace ix
+43 -24
View File
@@ -8,47 +8,66 @@
namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState() : _terminated(false) { computeId(); }
ConnectionState::ConnectionState()
: _terminated(false)
{
computeId();
}
void ConnectionState::computeId() { _id = std::to_string(_globalId++); }
void ConnectionState::computeId()
{
_id = std::to_string(_globalId++);
}
const std::string &ConnectionState::getId() const { return _id; }
const std::string& ConnectionState::getId() const
{
return _id;
}
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
{
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
{
return std::make_shared<ConnectionState>();
}
}
void ConnectionState::setOnSetTerminatedCallback(
const OnSetTerminatedCallback &callback)
{
void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback)
{
_onSetTerminatedCallback = callback;
}
}
bool ConnectionState::isTerminated() const { return _terminated; }
bool ConnectionState::isTerminated() const
{
return _terminated;
}
void ConnectionState::setTerminated()
{
void ConnectionState::setTerminated()
{
_terminated = true;
if (_onSetTerminatedCallback) {
if (_onSetTerminatedCallback)
{
_onSetTerminatedCallback();
}
}
}
const std::string &ConnectionState::getRemoteIp() { return _remoteIp; }
const std::string& ConnectionState::getRemoteIp()
{
return _remoteIp;
}
int ConnectionState::getRemotePort() { return _remotePort; }
int ConnectionState::getRemotePort()
{
return _remotePort;
}
void ConnectionState::setRemoteIp(const std::string &remoteIp)
{
void ConnectionState::setRemoteIp(const std::string& remoteIp)
{
_remoteIp = remoteIp;
}
}
void ConnectionState::setRemotePort(int remotePort)
{
void ConnectionState::setRemotePort(int remotePort)
{
_remotePort = remotePort;
}
}
} // namespace ix
+70 -65
View File
@@ -5,10 +5,10 @@
*/
//
// 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.
// 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
@@ -32,18 +32,22 @@
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 1; // ms
const int64_t DNSLookup::kDefaultWait = 1; // ms
DNSLookup::DNSLookup(const std::string &hostname, int port, int64_t wait)
: _hostname(hostname), _port(port), _wait(wait), _res(nullptr), _done(false)
{
DNSLookup::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,
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
int port,
std::string &errMsg)
{
std::string& errMsg)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
@@ -52,52 +56,53 @@ struct addrinfo *DNSLookup::getAddrInfo(const std::string &hostname,
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) {
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,
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); }
void DNSLookup::release(struct addrinfo* addr)
{
freeaddrinfo(addr);
}
struct addrinfo *DNSLookup::resolveUnCancellable(
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
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()) {
// 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)
{
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) {
if (_done)
{
return nullptr; // programming error, create a second DNSLookup instance
// if you need a second lookup.
}
@@ -115,76 +120,76 @@ struct addrinfo *DNSLookup::resolveCancellable(
// 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);
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
t->detach();
while (!_done) {
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()) {
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
}
// Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested()) {
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
errMsg = getErrMsg();
return getRes();
}
}
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
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.
{
// 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);
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock()) {
if (auto lock = self.lock())
{
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_done = true;
}
}
}
void DNSLookup::setErrMsg(const std::string &errMsg)
{
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
_errMsg = errMsg;
}
}
const std::string &DNSLookup::getErrMsg()
{
const std::string& DNSLookup::getErrMsg()
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
return _errMsg;
}
}
void DNSLookup::setRes(struct addrinfo *addr)
{
void DNSLookup::setRes(struct addrinfo* addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
}
}
struct addrinfo *DNSLookup::getRes()
{
struct addrinfo* DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
}
} // namespace ix
+21 -7
View File
@@ -10,21 +10,35 @@
namespace ix
{
uint32_t
calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries)
{
uint32_t waitTime = (retryCount < 26) ? (std::pow(2, retryCount) * 100) : 0;
{
// 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;
if (waitTime < minWaitBetweenReconnectionRetries) {
uint32_t waitTime = 0;
if (retryCount < maxRetryCountWithoutOverflow)
{
waitTime = std::pow(2, retryCount) * 100;
}
if (waitTime < minWaitBetweenReconnectionRetries)
{
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries || waitTime == 0) {
if (waitTime > maxWaitBetweenReconnectionRetries)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
if (retryCount >= maxRetryCountWithoutOverflow)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
}
}
} // namespace ix
+21 -19
View File
@@ -21,27 +21,25 @@
namespace ix
{
int getAnyFreePortRandom()
{
int getAnyFreePortRandom()
{
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
}
}
int getAnyFreePort()
{
int getAnyFreePort()
{
socket_t sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
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) {
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
{
return getAnyFreePortRandom();
}
@@ -51,14 +49,16 @@ int getAnyFreePort()
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) {
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) {
if (getsockname(sockfd, (struct sockaddr*) &sa, &len) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
@@ -67,11 +67,12 @@ int getAnyFreePort()
Socket::closeSocket(sockfd);
return port;
}
}
int getFreePort()
{
while (true) {
int getFreePort()
{
while (true)
{
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom();
@@ -85,11 +86,12 @@ int getFreePort()
// 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) {
if (port > 1024)
{
return port;
}
}
return -1;
}
}
} // namespace ix
+28 -94
View File
@@ -14,56 +14,12 @@
#include <zlib.h>
#endif
#ifdef IXWEBSOCKET_USE_DEFLATE
#include <libdeflate.h>
#endif
namespace ix
{
std::string gzipCompress(const std::string &str)
{
std::string gzipCompress(const std::string& str)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return std::string();
#else
#ifdef IXWEBSOCKET_USE_DEFLATE
int compressionLevel = 6;
struct libdeflate_compressor *compressor;
compressor = libdeflate_alloc_compressor(compressionLevel);
const void *uncompressed_data = str.data();
size_t uncompressed_size = str.size();
void *compressed_data;
size_t actual_compressed_size;
size_t max_compressed_size;
max_compressed_size =
libdeflate_gzip_compress_bound(compressor, uncompressed_size);
compressed_data = malloc(max_compressed_size);
if (compressed_data == NULL) {
return std::string();
}
actual_compressed_size = libdeflate_gzip_compress(compressor,
uncompressed_data,
uncompressed_size,
compressed_data,
max_compressed_size);
libdeflate_free_compressor(compressor);
if (actual_compressed_size == 0) {
free(compressed_data);
return std::string();
}
std::string out;
out.assign(reinterpret_cast<char *>(compressed_data),
actual_compressed_size);
free(compressed_data);
return out;
#else
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
@@ -79,21 +35,23 @@ std::string gzipCompress(const std::string &str)
8,
Z_DEFAULT_STRATEGY);
zs.next_in = (Bytef *)str.data();
zs.avail_in = (uInt)str.size(); // set the z_stream's input
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);
do
{
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
zs.avail_out = sizeof(outbuffer);
ret = deflate(&zs, Z_FINISH);
if (outstring.size() < zs.total_out) {
if (outstring.size() < zs.total_out)
{
// append the block to the output string
outstring.append(outbuffer, zs.total_out - outstring.size());
}
@@ -102,47 +60,21 @@ std::string gzipCompress(const std::string &str)
deflateEnd(&zs);
return outstring;
#endif // IXWEBSOCKET_USE_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB
}
}
#ifdef IXWEBSOCKET_USE_DEFLATE
static uint32_t loadDecompressedGzipSize(const uint8_t *p)
{
return ((uint32_t)p[0] << 0) | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
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)
{
bool gzipDecompress(const std::string& in, std::string& out)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return false;
#else
#ifdef IXWEBSOCKET_USE_DEFLATE
struct libdeflate_decompressor *decompressor;
decompressor = libdeflate_alloc_decompressor();
const void *compressed_data = in.data();
size_t compressed_size = in.size();
// Retrieve uncompressed size from the trailer of the gziped data
const uint8_t *ptr = reinterpret_cast<const uint8_t *>(&in.front());
auto uncompressed_size =
loadDecompressedGzipSize(&ptr[compressed_size - 4]);
// Use it to redimension our output buffer
out.resize(uncompressed_size);
libdeflate_result result = libdeflate_gzip_decompress(decompressor,
compressed_data,
compressed_size,
&out.front(),
uncompressed_size,
NULL);
libdeflate_free_decompressor(decompressor);
return result == LIBDEFLATE_SUCCESS;
#else
z_stream inflateState;
memset(&inflateState, 0, sizeof(inflateState));
@@ -153,34 +85,36 @@ bool gzipDecompress(const std::string &in, std::string &out)
inflateState.avail_in = 0;
inflateState.next_in = Z_NULL;
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK) {
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()));
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;
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) {
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
inflateEnd(&inflateState);
return false;
}
out.append(reinterpret_cast<char *>(&compressBuffer.front()),
out.append(reinterpret_cast<char*>(&compressBuffer.front()),
kBufferSize - inflateState.avail_out);
} while (inflateState.avail_out == 0);
inflateEnd(&inflateState);
return true;
#endif // IXWEBSOCKET_USE_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB
}
}
} // namespace ix
+74 -69
View File
@@ -14,96 +14,103 @@
namespace ix
{
std::string Http::trim(const std::string &str)
{
std::string Http::trim(const std::string& str)
{
std::string out;
for (auto c : str) {
if (c != ' ' && c != '\n' && c != '\r') {
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)
{
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, ' ')) {
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string httpVersion;
if (tokens.size() >= 1) {
if (tokens.size() >= 1)
{
httpVersion = trim(tokens[0]);
}
int statusCode = -1;
if (tokens.size() >= 2) {
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)
{
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, ' ')) {
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string method;
if (tokens.size() >= 1) {
if (tokens.size() >= 1)
{
method = trim(tokens[0]);
}
std::string requestUri;
if (tokens.size() >= 2) {
if (tokens.size() >= 2)
{
requestUri = trim(tokens[1]);
}
std::string httpVersion;
if (tokens.size() >= 3) {
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)
{
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);
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);
if (!lineValid)
{
return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
}
// Parse request line (GET /foo HTTP/1.1\r\n)
@@ -117,67 +124,63 @@ std::tuple<bool, std::string, HttpRequestPtr>
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid) {
return std::make_tuple(false,
"Error parsing HTTP headers",
httpRequest);
if (!headersValid)
{
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
}
std::string body;
if (headers.find("Content-Length") != headers.end()) {
if (headers.find("Content-Length") != headers.end())
{
int contentLength = 0;
try {
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) {
catch (const std::exception&)
{
return std::make_tuple(
false,
"Error: 'Content-Length' should be a positive integer",
httpRequest);
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
}
auto res =
socket->readBytes(contentLength, nullptr, isCancellationRequested);
if (!res.first) {
return std::make_tuple(false,
std::string("Error reading request: ") +
res.second,
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") {
if (headers["Content-Encoding"] == "gzip")
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload;
if (!gzipDecompress(body, decompressedPayload)) {
if (!gzipDecompress(body, decompressedPayload))
{
return std::make_tuple(
false,
std::string("Error during gzip decompression of the body"),
httpRequest);
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");
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);
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)
{
bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket)
{
// Write the response to the socket
std::stringstream ss;
ss << "HTTP/1.1 ";
@@ -186,23 +189,25 @@ bool Http::sendResponse(HttpResponsePtr response,
ss << response->description;
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr)) {
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) {
for (auto&& it : response->headers)
{
ss << it.first << ": " << it.second << "\r\n";
}
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr)) {
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
return response->body.empty() ? true
: socket->writeBytes(response->body, nullptr);
}
return response->body.empty() ? true : socket->writeBytes(response->body, nullptr);
}
} // namespace ix
+256 -208
View File
@@ -20,56 +20,57 @@
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";
// 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;
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;
HttpClient::~HttpClient()
{
if (!_thread.joinable()) return;
_stop = true;
_condition.notify_one();
_thread.join();
}
}
void HttpClient::setTLSOptions(const SocketTLSOptions &tlsOptions)
{
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
{
_tlsOptions = tlsOptions;
}
}
void HttpClient::setForceBody(bool value) { _forceBody = value; }
void HttpClient::setForceBody(bool value)
{
_forceBody = value;
}
HttpRequestArgsPtr HttpClient::createRequest(const std::string &url,
const std::string &verb)
{
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)
{
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;
if (!_async) return false;
// Enqueue the task
{
@@ -84,23 +85,24 @@ bool HttpClient::performRequest(HttpRequestArgsPtr args,
_condition.notify_one();
return true;
}
}
void HttpClient::run()
{
while (true) {
void HttpClient::run()
{
while (true)
{
HttpRequestArgsPtr args;
OnResponseCallback onResponseCallback;
{
std::unique_lock<std::mutex> lock(_queueMutex);
while (!_stop && _queue.empty()) {
while (!_stop && _queue.empty())
{
_condition.wait(lock);
}
if (_stop)
return;
if (_stop) return;
auto p = _queue.front();
_queue.pop();
@@ -109,24 +111,21 @@ void HttpClient::run()
onResponseCallback = p.second;
}
if (_stop)
return;
if (_stop) return;
HttpResponsePtr response =
request(args->url, args->verb, args->body, args);
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
onResponseCallback(response);
if (_stop)
return;
if (_stop) return;
}
}
}
HttpResponsePtr HttpClient::request(const std::string &url,
const std::string &verb,
const std::string &body,
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);
@@ -141,7 +140,8 @@ HttpResponsePtr HttpClient::request(const std::string &url,
std::string protocol, host, path, query;
int port;
if (!UrlParser::parse(url, protocol, host, path, query, port)) {
if (!UrlParser::parse(url, protocol, host, path, query, port))
{
std::stringstream ss;
ss << "Cannot parse url: " << url;
return std::make_shared<HttpResponse>(code,
@@ -158,7 +158,8 @@ HttpResponsePtr HttpClient::request(const std::string &url,
std::string errorMsg;
_socket = createSocket(tls, -1, errorMsg, _tlsOptions);
if (!_socket) {
if (!_socket)
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotCreateSocket,
@@ -175,32 +176,38 @@ HttpResponsePtr HttpClient::request(const std::string &url,
ss << "Host: " << host << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress) {
if (args->compress && !args->onChunkCallback)
{
ss << "Accept-Encoding: gzip"
<< "\r\n";
}
#endif
// Append extra headers
for (auto &&it : args->extraHeaders) {
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()) {
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()) {
if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody) {
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
{
// Set request compression header
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest) {
if (args->compressRequest)
{
ss << "Content-Encoding: gzip"
<< "\r\n";
}
@@ -209,19 +216,24 @@ HttpResponsePtr HttpClient::request(const std::string &url,
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()) {
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";
}
else
{
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
<< "\r\n";
}
}
ss << "\r\n";
ss << body;
} else {
}
else
{
ss << "\r\n";
}
@@ -229,17 +241,21 @@ HttpResponsePtr HttpClient::request(const std::string &url,
std::string errMsg;
// Make a cancellation object dealing with connection timeout
auto isCancellationRequested =
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel);
bool success =
_socket->connect(host, port, errMsg, isCancellationRequested);
if (!success) {
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,
HttpErrorCode::CannotConnect,
errorCode,
headers,
payload,
ss.str(),
@@ -248,10 +264,10 @@ HttpResponsePtr HttpClient::request(const std::string &url,
}
// Make a new cancellation object dealing with transfer timeout
isCancellationRequested =
makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel);
if (args->verbose) {
if (args->verbose)
{
std::stringstream ss;
ss << "Sending " << verb << " request "
<< "to " << host << ":" << port << std::endl
@@ -263,11 +279,13 @@ HttpResponsePtr HttpClient::request(const std::string &url,
log(ss.str(), args);
}
if (!_socket->writeBytes(req, isCancellationRequested)) {
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,
HttpErrorCode::SendError,
errorCode,
headers,
payload,
errorMsg,
@@ -281,12 +299,13 @@ HttpResponsePtr HttpClient::request(const std::string &url,
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) {
if (!lineValid)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine;
std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(
code,
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotReadStatusLine,
errorCode,
headers,
payload,
errorMsg,
@@ -294,13 +313,15 @@ HttpResponsePtr HttpClient::request(const std::string &url,
downloadSize);
}
if (args->verbose) {
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) {
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,
@@ -316,11 +337,13 @@ HttpResponsePtr HttpClient::request(const std::string &url,
auto headersValid = result.first;
headers = result.second;
if (!headersValid) {
if (!headersValid)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError;
std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::HeaderParsingError,
errorCode,
headers,
payload,
errorMsg,
@@ -329,11 +352,12 @@ HttpResponsePtr HttpClient::request(const std::string &url,
}
// Redirect ?
if ((code >= 301 && code <= 308) && args->followRedirects) {
if (headers.find("Location") == headers.end()) {
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,
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingLocation,
headers,
@@ -343,11 +367,11 @@ HttpResponsePtr HttpClient::request(const std::string &url,
downloadSize);
}
if (redirects >= args->maxRedirects) {
if (redirects >= args->maxRedirects)
{
std::stringstream ss;
ss << "Too many redirects: " << redirects;
return std::make_shared<HttpResponse>(
code,
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::TooManyRedirects,
headers,
@@ -362,7 +386,8 @@ HttpResponsePtr HttpClient::request(const std::string &url,
return request(location, verb, body, args, redirects + 1);
}
if (verb == "HEAD") {
if (verb == "HEAD")
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
@@ -374,42 +399,53 @@ HttpResponsePtr HttpClient::request(const std::string &url,
}
// Parse response:
if (headers.find("Content-Length") != headers.end()) {
if (headers.find("Content-Length") != headers.end())
{
ssize_t contentLength = -1;
ss.str("");
ss << headers["Content-Length"];
ss >> contentLength;
payload.reserve(contentLength);
auto chunkResult = _socket->readBytes(contentLength,
args->onProgressCallback,
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first) {
if (!chunkResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
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") {
}
}
else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked")
{
std::stringstream ss;
while (true) {
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,
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
errorCode,
headers,
payload,
errorMsg,
@@ -422,40 +458,47 @@ HttpResponsePtr HttpClient::request(const std::string &url,
ss << std::hex << line;
ss >> chunkSize;
if (args->verbose) {
if (args->verbose)
{
std::stringstream oss;
oss << "Reading " << chunkSize << " bytes" << std::endl;
log(oss.str(), args);
}
payload.reserve(payload.size() + (size_t)chunkSize);
// Read a chunk
auto chunkResult = _socket->readBytes((size_t)chunkSize,
auto chunkResult = _socket->readBytes((size_t) chunkSize,
args->onProgressCallback,
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first) {
if (!chunkResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(
code,
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
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) {
return std::make_shared<HttpResponse>(
code,
if (!lineResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
errorCode,
headers,
payload,
errorMsg,
@@ -463,12 +506,15 @@ HttpResponsePtr HttpClient::request(const std::string &url,
downloadSize);
}
if (chunkSize == 0)
break;
if (chunkSize == 0) break;
}
} else if (code == 204) {
}
else if (code == 204)
{
; // 204 is NoContent response code
} else {
}
else
{
std::string errorMsg("Cannot read http body");
return std::make_shared<HttpResponse>(code,
description,
@@ -483,10 +529,12 @@ HttpResponsePtr HttpClient::request(const std::string &url,
downloadSize = payload.size();
// If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip") {
if (headers["Content-Encoding"] == "gzip")
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload;
if (!gzipDecompress(payload, decompressedPayload)) {
if (!gzipDecompress(payload, decompressedPayload))
{
std::string errorMsg("Error decompressing payload");
return std::make_shared<HttpResponse>(code,
description,
@@ -499,8 +547,7 @@ HttpResponsePtr HttpClient::request(const std::string &url,
}
payload = decompressedPayload;
#else
std::string errorMsg(
"ixwebsocket was not compiled with gzip support on");
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
@@ -520,148 +567,147 @@ HttpResponsePtr HttpClient::request(const std::string &url,
std::string(),
uploadSize,
downloadSize);
}
}
HttpResponsePtr HttpClient::get(const std::string &url, HttpRequestArgsPtr args)
{
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)
{
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)
{
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,
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()) {
if (httpFormDataParameters.empty())
{
body = serializeHttpParameters(httpParameters);
} else {
}
else
{
std::string multipartBoundary = generateMultipartBoundary();
args->multipartBoundary = multipartBoundary;
body = serializeHttpFormDataParameters(multipartBoundary,
httpFormDataParameters,
httpParameters);
body = serializeHttpFormDataParameters(
multipartBoundary, httpFormDataParameters, httpParameters);
}
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest) {
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,
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,
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,
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,
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,
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,
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::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) {
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 == '~') {
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::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
}
std::string
HttpClient::serializeHttpParameters(const HttpParameters &httpParameters)
{
std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
{
std::stringstream ss;
size_t count = httpParameters.size();
size_t i = 0;
for (auto &&it : httpParameters) {
for (auto&& it : httpParameters)
{
ss << urlEncode(it.first) << "=" << urlEncode(it.second);
if (i++ < (count - 1)) {
if (i++ < (count - 1))
{
ss << "&";
}
}
return ss.str();
}
}
std::string HttpClient::serializeHttpFormDataParameters(
const std::string &multipartBoundary,
const HttpFormDataParameters &httpFormDataParameters,
const HttpParameters &httpParameters)
{
std::string HttpClient::serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters)
{
//
// --AaB03x
// Content-Disposition: form-data; name="submit-name"
@@ -676,7 +722,8 @@ std::string HttpClient::serializeHttpFormDataParameters(
//
std::stringstream ss;
for (auto &&it : httpFormDataParameters) {
for (auto&& it : httpFormDataParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
@@ -688,7 +735,8 @@ std::string HttpClient::serializeHttpFormDataParameters(
<< it.second << "\r\n";
}
for (auto &&it : httpParameters) {
for (auto&& it : httpParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
@@ -700,19 +748,19 @@ std::string HttpClient::serializeHttpFormDataParameters(
ss << "--" << multipartBoundary << "--\r\n";
return ss.str();
}
}
void HttpClient::log(const std::string &msg, HttpRequestArgsPtr args)
{
if (args->logger) {
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
{
if (args->logger)
{
args->logger(msg);
}
}
}
std::string HttpClient::generateMultipartBoundary()
{
std::string str(
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
std::string HttpClient::generateMultipartBoundary()
{
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
static std::random_device rd;
static std::mt19937 generator(rd());
@@ -720,5 +768,5 @@ std::string HttpClient::generateMultipartBoundary()
std::shuffle(str.begin(), str.end(), generator);
return str;
}
}
} // namespace ix
+91 -95
View File
@@ -17,95 +17,101 @@
namespace
{
std::pair<bool, std::vector<uint8_t>> load(const std::string &path)
{
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);
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));
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)
{
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);
const int HttpServer::kDefaultTimeoutSecs(30);
HttpServer::HttpServer(int port,
const std::string &host,
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)
{
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _connectedClientsCount(0)
, _timeoutSecs(timeoutSecs)
{
setDefaultConnectionCallback();
}
}
HttpServer::~HttpServer() { stop(); }
HttpServer::~HttpServer()
{
stop();
}
void HttpServer::stop()
{
void HttpServer::stop()
{
stopAcceptingConnections();
// FIXME: cancelling / closing active clients ...
SocketServer::stop();
}
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback &callback)
{
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
}
}
void HttpServer::handleConnection(
std::unique_ptr<Socket> socket,
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)) {
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; }
size_t HttpServer::getConnectedClientsCount()
{
return _connectedClientsCount;
}
void HttpServer::setDefaultConnectionCallback()
{
void HttpServer::setDefaultConnectionCallback()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState)
-> HttpResponsePtr {
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
std::string uri(request->uri);
if (uri.empty() || uri == "/") {
if (uri.empty() || uri == "/")
{
uri = "/index.html";
}
@@ -115,20 +121,18 @@ void HttpServer::setDefaultConnectionCallback()
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());
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) {
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
{
content = gzipCompress(content);
headers["Content-Encoding"] = "gzip";
}
@@ -136,86 +140,78 @@ void HttpServer::setDefaultConnectionCallback()
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":"
<< connectionState->getRemotePort() << " " << request->method
<< " " << request->headers["User-Agent"] << " " << request->uri
<< " " << content.size();
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) {
for (auto&& it : request->headers)
{
headers[it.first] = it.second;
}
return std::make_shared<HttpResponse>(200,
"OK",
HttpErrorCode::Ok,
headers,
content);
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, content);
});
}
}
void HttpServer::makeRedirectServer(const std::string &redirectUrl)
{
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 {
[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;
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());
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());
return std::make_shared<HttpResponse>(
301, "OK", HttpErrorCode::Ok, headers, std::string());
});
}
}
//
// Display the client parameter and body on the console
//
void HttpServer::makeDebugServer()
{
//
// Display the client parameter and body on the console
//
void HttpServer::makeDebugServer()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState)
-> HttpResponsePtr {
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;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri;
logInfo(ss.str());
logInfo("== Headers == ");
for (auto &&it : request->headers) {
for (auto&& it : request->headers)
{
std::ostringstream oss;
oss << it.first << ": " << it.second;
logInfo(oss.str());
@@ -226,14 +222,14 @@ void HttpServer::makeDebugServer()
logInfo(request->body);
logInfo("");
return std::make_shared<HttpResponse>(200,
"OK",
HttpErrorCode::Ok,
headers,
std::string("OK"));
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
});
}
}
int HttpServer::getTimeoutSecs() { return _timeoutSecs; }
int HttpServer::getTimeoutSecs()
{
return _timeoutSecs;
}
} // namespace ix
+276 -111
View File
@@ -7,11 +7,20 @@
#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()
{
bool initNetSystem()
{
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
@@ -25,47 +34,206 @@ bool initNetSystem()
#else
return true;
#endif
}
}
bool uninitNetSystem()
{
bool uninitNetSystem()
{
#ifdef _WIN32
int err = WSACleanup();
return err == 0;
#else
return true;
#endif
}
}
//
// That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet
// (the curl folks have had problems with it).
//
// So we make it a select wrapper
//
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
#ifdef _WIN32
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];
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
if (fd->fd > maxfd) {
if (fd->fd > maxfd)
{
maxfd = fd->fd;
}
if ((fd->events & POLLIN)) {
if ((fd->events & POLLIN))
{
FD_SET(fd->fd, &readfds);
}
if ((fd->events & POLLOUT)) {
if ((fd->events & POLLOUT))
{
FD_SET(fd->fd, &writefds);
}
if ((fd->events & POLLERR)) {
if ((fd->events & POLLERR))
{
FD_SET(fd->fd, &errorfds);
}
}
@@ -74,33 +242,36 @@ int poll(struct pollfd *fds, nfds_t nfds, int timeout)
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int ret = select(maxfd + 1,
&readfds,
&writefds,
&errorfds,
timeout != -1 ? &tv : NULL);
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
if (ret < 0) {
if (ret < 0)
{
return ret;
}
for (nfds_t i = 0; i < nfds; ++i) {
struct pollfd *fd = &fds[i];
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (FD_ISSET(fd->fd, &readfds)) {
if (FD_ISSET(fd->fd, &readfds))
{
fd->revents |= POLLIN;
}
if (FD_ISSET(fd->fd, &writefds)) {
if (FD_ISSET(fd->fd, &writefds))
{
fd->revents |= POLLOUT;
}
if (FD_ISSET(fd->fd, &errorfds)) {
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
@@ -111,29 +282,29 @@ int poll(struct pollfd *fds, nfds_t nfds, int timeout)
// The style from libuv is as such.
//
int ret = -1;
do {
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)
{
//
// 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;
const unsigned char* a = (const unsigned char*) a0;
int i, j, max, best;
char buf[100];
switch (af) {
switch (af)
{
case AF_INET:
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l)
return s;
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))
@@ -163,134 +334,128 @@ const char *inet_ntop(int af, const void *a0, char *s, socklen_t l)
a[14],
a[15]);
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
for (i = best = 0, max = 2; buf[i]; i++) {
if (i && buf[i] != ':')
continue;
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 (j > max) best = i, max = j;
}
if (max > 3) {
if (max > 3)
{
buf[best] = buf[best + 1] = ':';
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
}
if (strlen(buf) < l) {
if (strlen(buf) < l)
{
strcpy(s, buf);
return s;
}
break;
default:
errno = EAFNOSUPPORT;
return 0;
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';
static int hexval(unsigned c)
{
if (c - '0' < 10) return c - '0';
c |= 32;
if (c - 'a' < 6)
return c - 'a' + 10;
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)
{
//
// 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;
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++) {
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;
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;
if (s[j] == 0 && i == 3) return 1;
if (s[j] != '.') return 0;
s += j + 1;
}
return 0;
} else if (af != AF_INET6) {
}
else if (af != AF_INET6)
{
errno = EAFNOSUPPORT;
return -1;
}
if (*s == ':' && *++s != ':')
return 0;
if (*s == ':' && *++s != ':') return 0;
for (i = 0;; i++) {
if (s[0] == ':' && brk < 0) {
for (i = 0;; i++)
{
if (s[0] == ':' && brk < 0)
{
brk = i;
ip[i & 7] = 0;
if (!*++s)
break;
if (i == 7)
return 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;
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;
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) {
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++) {
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;
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]);
// 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)
#else // defined(_WIN32)
return ntohs(value);
#endif // defined(_WIN32)
}
#endif // defined(_WIN32)
}
} // namespace ix
+35 -9
View File
@@ -8,20 +8,46 @@
namespace ix
{
const uint64_t SelectInterrupt::kSendRequest = 1;
const uint64_t SelectInterrupt::kCloseRequest = 2;
const uint64_t SelectInterrupt::kSendRequest = 1;
const uint64_t SelectInterrupt::kCloseRequest = 2;
SelectInterrupt::SelectInterrupt() { ; }
SelectInterrupt::SelectInterrupt()
{
;
}
SelectInterrupt::~SelectInterrupt() { ; }
SelectInterrupt::~SelectInterrupt()
{
;
}
bool SelectInterrupt::init(std::string & /*errorMsg*/) { return true; }
bool SelectInterrupt::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterrupt::notify(uint64_t /*value*/) { return true; }
bool SelectInterrupt::notify(uint64_t /*value*/)
{
return true;
}
uint64_t SelectInterrupt::read() { return 0; }
uint64_t SelectInterrupt::read()
{
return 0;
}
bool SelectInterrupt::clear() { return true; }
bool SelectInterrupt::clear()
{
return true;
}
int SelectInterrupt::getFd() const { return -1; }
int SelectInterrupt::getFd() const
{
return -1;
}
void* SelectInterrupt::getEvent() const
{
return nullptr;
}
} // namespace ix
+85
View File
@@ -0,0 +1,85 @@
/*
* IXSelectInterruptEvent.cpp
*/
//
// On Windows we use a Windows Event to wake up ix::poll() (WSAWaitForMultipleEvents).
// And on any other platform that doesn't support pipe file descriptors we
// emulate the interrupt event by using a short timeout with ix::poll() and
// read from the SelectInterrupt. (see Socket::poll() "Emulation mode")
//
#include <algorithm>
#include "IXSelectInterruptEvent.h"
namespace ix
{
SelectInterruptEvent::SelectInterruptEvent()
{
#ifdef _WIN32
_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#endif
}
SelectInterruptEvent::~SelectInterruptEvent()
{
#ifdef _WIN32
CloseHandle(_event);
#endif
}
bool SelectInterruptEvent::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterruptEvent::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_valuesMutex);
// WebSocket implementation detail: We only need one of the values in the queue
if (std::find(_values.begin(), _values.end(), value) == _values.end())
_values.push_back(value);
#ifdef _WIN32
SetEvent(_event); // wake up
#endif
return true;
}
uint64_t SelectInterruptEvent::read()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
if (_values.size() > 0)
{
uint64_t value = _values.front();
_values.pop_front();
#ifdef _WIN32
// signal the event if there is still data in the queue
if (_values.size() == 0)
ResetEvent(_event);
#endif
return value;
}
return 0;
}
bool SelectInterruptEvent::clear()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
_values.clear();
#ifdef _WIN32
ResetEvent(_event);
#endif
return true;
}
void* SelectInterruptEvent::getEvent() const
{
#ifdef _WIN32
return reinterpret_cast<void*>(_event);
#else
return nullptr;
#endif
}
} // namespace ix
+9 -9
View File
@@ -7,20 +7,20 @@
#include "IXSelectInterruptFactory.h"
#include "IXUniquePtr.h"
#if defined(__linux__) || defined(__APPLE__)
#include "IXSelectInterruptPipe.h"
#if _WIN32
#include "IXSelectInterruptEvent.h"
#else
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptPipe.h"
#endif
namespace ix
{
SelectInterruptPtr createSelectInterrupt()
{
#if defined(__linux__) || defined(__APPLE__)
return ix::make_unique<SelectInterruptPipe>();
SelectInterruptPtr createSelectInterrupt()
{
#ifdef _WIN32
return ix::make_unique<SelectInterruptEvent>();
#else
return ix::make_unique<SelectInterrupt>();
return ix::make_unique<SelectInterruptPipe>();
#endif
}
}
} // namespace ix
+46 -41
View File
@@ -21,34 +21,35 @@
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;
// 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()
{
SelectInterruptPipe::SelectInterruptPipe()
{
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
}
}
SelectInterruptPipe::~SelectInterruptPipe()
{
SelectInterruptPipe::~SelectInterruptPipe()
{
::close(_fildes[kPipeReadIndex]);
::close(_fildes[kPipeWriteIndex]);
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
}
}
bool SelectInterruptPipe::init(std::string &errorMsg)
{
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) {
if (pipe(_fildes) < 0)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in pipe() call"
<< " : " << strerror(errno);
@@ -56,10 +57,10 @@ bool SelectInterruptPipe::init(std::string &errorMsg)
return false;
}
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1) {
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) "
"call"
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
@@ -68,10 +69,10 @@ bool SelectInterruptPipe::init(std::string &errorMsg)
return false;
}
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1) {
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) "
"call"
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
@@ -81,10 +82,10 @@ bool SelectInterruptPipe::init(std::string &errorMsg)
}
#ifdef F_SETNOSIGPIPE
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1) {
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(.... "
"F_SETNOSIGPIPE) call"
ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
@@ -93,10 +94,10 @@ bool SelectInterruptPipe::init(std::string &errorMsg)
return false;
}
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1) {
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., "
"F_SETNOSIGPIPE) call"
ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
@@ -107,28 +108,28 @@ bool SelectInterruptPipe::init(std::string &errorMsg)
#endif
return true;
}
}
bool SelectInterruptPipe::notify(uint64_t value)
{
bool SelectInterruptPipe::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeWriteIndex];
if (fd == -1)
return false;
if (fd == -1) return false;
ssize_t ret = -1;
do {
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()
{
// TODO: return max uint64_t for errors ?
uint64_t SelectInterruptPipe::read()
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeReadIndex];
@@ -136,21 +137,25 @@ uint64_t SelectInterruptPipe::read()
uint64_t value = 0;
ssize_t ret = -1;
do {
do
{
ret = ::read(fd, &value, sizeof(value));
} while (ret == -1 && errno == EINTR);
return value;
}
}
bool SelectInterruptPipe::clear() { return true; }
bool SelectInterruptPipe::clear()
{
return true;
}
int SelectInterruptPipe::getFd() const
{
int SelectInterruptPipe::getFd() const
{
std::lock_guard<std::mutex> lock(_fildesMutex);
return _fildes[kPipeReadIndex];
}
}
} // namespace ix
#endif // !_WIN32
+17 -15
View File
@@ -23,19 +23,20 @@
namespace ix
{
#ifdef _WIN32
const DWORD MS_VC_EXCEPTION = 0x406D1388;
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push, 8)
typedef struct tagTHREADNAME_INFO {
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;
} THREADNAME_INFO;
#pragma pack(pop)
void SetThreadName(DWORD dwThreadID, const char *threadName)
{
void SetThreadName(DWORD dwThreadID, const char* threadName)
{
#ifndef __GNUC__
THREADNAME_INFO info;
info.dwType = 0x1000;
@@ -43,19 +44,20 @@ void SetThreadName(DWORD dwThreadID, const char *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) {
__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)
{
void setThreadName(const std::string& name)
{
#if defined(__APPLE__)
//
// Apple reserves 16 bytes for its thread names
@@ -77,5 +79,5 @@ void setThreadName(const std::string &name)
#else
// ... assert here ?
#endif
}
}
} // namespace ix
+225 -145
View File
@@ -27,30 +27,36 @@
namespace ix
{
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
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(int fd)
: _sockfd(fd)
, _selectInterrupt(createSelectInterrupt())
{
;
}
}
Socket::~Socket() { close(); }
Socket::~Socket()
{
close();
}
PollResultType Socket::poll(bool readyToRead,
PollResultType Socket::poll(bool readyToRead,
int timeoutMs,
int sockfd,
const SelectInterruptPtr &selectInterrupt)
{
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.
// ::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.
// https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
//
nfds_t nfds = 1;
struct pollfd fds[2];
@@ -59,46 +65,70 @@ PollResultType Socket::poll(bool readyToRead,
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
// this is ignored by poll, but our select based poll wrapper on Windows needs it
fds[0].events |= POLLERR;
// File descriptor used to interrupt select when needed
int interruptFd = -1;
if (selectInterrupt) {
void* interruptEvent = nullptr;
if (selectInterrupt)
{
interruptFd = selectInterrupt->getFd();
interruptEvent = selectInterrupt->getEvent();
if (interruptFd != -1) {
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;
}
}
}
int ret = ix::poll(fds, nfds, timeoutMs);
void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled
int ret = ix::poll(fds, nfds, timeoutMs, &event);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0) {
if (ret < 0)
{
pollResult = PollResultType::Error;
} else if (ret == 0) {
pollResult = PollResultType::Timeout;
} else if (interruptFd != -1 && fds[1].revents & POLLIN) {
uint64_t value = selectInterrupt->read();
if (value == SelectInterrupt::kSendRequest) {
pollResult = PollResultType::SendRequest;
} else if (value == SelectInterrupt::kCloseRequest) {
pollResult = PollResultType::CloseRequest;
}
} else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN) {
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) {
}
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) {
// On connect error, in async mode, windows will write to the exceptions fds
if (fds[0].revents & POLLERR)
{
pollResult = PollResultType::Error;
}
#else
@@ -107,8 +137,8 @@ PollResultType Socket::poll(bool readyToRead,
// 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) {
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
@@ -116,103 +146,128 @@ PollResultType Socket::poll(bool readyToRead,
errno = optval;
}
#endif
} else if (sockfd != -1 &&
(fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
fds[0].revents & POLLNVAL)) {
}
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
fds[0].revents & POLLNVAL))
{
pollResult = PollResultType::Error;
}
return pollResult;
}
}
PollResultType Socket::isReadyToRead(int timeoutMs)
{
if (_sockfd == -1) {
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) {
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)
{
// Wake up from poll/select by writing to the pipe which is watched by select
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
{
return _selectInterrupt->notify(wakeUpCode);
}
}
bool Socket::accept(std::string &errMsg)
{
if (_sockfd == -1) {
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,
bool Socket::connect(const std::string& host,
int port,
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (!_selectInterrupt->clear())
return false;
if (!_selectInterrupt->clear()) return false;
_sockfd =
SocketConnect::connect(host, port, errMsg, isCancellationRequested);
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
return _sockfd != -1;
}
}
void Socket::close()
{
void Socket::close()
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (_sockfd == -1)
return;
if (_sockfd == -1) return;
closeSocket(_sockfd);
_sockfd = -1;
}
}
ssize_t Socket::send(char *buffer, size_t length)
{
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::send(const std::string& buffer)
{
return send((char*) &buffer[0], buffer.size());
}
ssize_t Socket::recv(void *buffer, size_t length)
{
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);
}
return ::recv(_sockfd, (char*) buffer, length, flags);
}
int Socket::getErrno()
{
int Socket::getErrno()
{
int err;
#ifdef _WIN32
@@ -222,105 +277,116 @@ int Socket::getErrno()
#endif
return err;
}
}
bool Socket::isWaitNeeded()
{
bool Socket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS) {
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
}
}
void Socket::closeSocket(int fd)
{
void Socket::closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
}
bool Socket::init(std::string &errorMsg)
{
bool Socket::init(std::string& errorMsg)
{
return _selectInterrupt->init(errorMsg);
}
}
bool Socket::writeBytes(const std::string &str,
const CancellationRequest &isCancellationRequested)
{
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
int offset = 0;
int len = (int)str.size();
int len = (int) str.size();
while (true) {
if (isCancellationRequested && isCancellationRequested())
return false;
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send((char *)&str[offset], len);
ssize_t ret = send((char*) &str[offset], len);
// We wrote some bytes, as needed, all good.
if (ret > 0) {
if (ret == len) {
if (ret > 0)
{
if (ret == len)
{
return true;
} else {
}
else
{
offset += ret;
len -= ret;
continue;
}
}
// There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded()) {
else if (ret < 0 && Socket::isWaitNeeded())
{
continue;
}
// There was an error during the write, abort
else {
else
{
return false;
}
}
}
}
bool Socket::readByte(void *buffer,
const CancellationRequest &isCancellationRequested)
{
while (true) {
if (isCancellationRequested && isCancellationRequested())
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) {
if (ret == 1)
{
return true;
}
// There is possibly something to be read, try again
else if (ret < 0 && Socket::isWaitNeeded()) {
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) {
if (isReadyToRead(1) == PollResultType::Error)
{
return false;
}
}
// There was an error during the read, abort
else {
else
{
return false;
}
}
}
}
std::pair<bool, std::string>
Socket::readLine(const CancellationRequest &isCancellationRequested)
{
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)) {
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);
}
@@ -329,45 +395,59 @@ std::pair<bool, std::string>
}
return std::make_pair(true, line);
}
}
std::pair<bool, std::string>
Socket::readBytes(size_t length,
const OnProgressCallback &onProgressCallback,
const CancellationRequest &isCancellationRequested)
{
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;
while (output.size() != length) {
if (isCancellationRequested && isCancellationRequested()) {
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 - output.size());
ssize_t ret = recv((char *)&readBuffer[0], size);
size_t size = std::min(readBuffer.size(), length - bytesRead);
ssize_t ret = recv((char*) &readBuffer[0], size);
if (ret > 0) {
output.insert(output.end(),
readBuffer.begin(),
readBuffer.begin() + ret);
} else if (ret <= 0 && !Socket::isWaitNeeded()) {
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)output.size(), (int)length);
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) {
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
+133 -127
View File
@@ -28,32 +28,34 @@
namespace ix
{
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions &tlsOptions, int fd)
: Socket(fd), _sslContext(nullptr), _tlsOptions(tlsOptions)
{
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _sslContext(nullptr)
, _tlsOptions(tlsOptions)
{
;
}
}
SocketAppleSSL::~SocketAppleSSL() { SocketAppleSSL::close(); }
SocketAppleSSL::~SocketAppleSSL()
{
SocketAppleSSL::close();
}
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
{
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
{
std::string errMsg("Unknown SSL error.");
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault,
kCFErrorDomainOSStatus,
status,
NULL);
if (error) {
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
if (error)
{
CFStringRef message = CFErrorCopyDescription(error);
if (message) {
if (message)
{
char localBuffer[128];
Boolean success;
success = CFStringGetCString(message,
localBuffer,
128,
kCFStringEncodingUTF8);
if (success) {
success = CFStringGetCString(message, localBuffer, 128, kCFStringEncodingUTF8);
if (success)
{
errMsg = localBuffer;
}
CFRelease(message);
@@ -62,15 +64,12 @@ std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
}
return errMsg;
}
}
OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection,
void *data,
size_t *len)
{
int fd = (int)(long)connection;
if (fd < 0)
return errSSLInternal;
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);
@@ -79,44 +78,46 @@ OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection,
ssize_t status = read(fd, data, requested_sz);
if (status > 0) {
*len = (size_t)status;
if (requested_sz > *len) {
if (status > 0)
{
*len = (size_t) status;
if (requested_sz > *len)
{
return errSSLWouldBlock;
} else {
}
else
{
return noErr;
}
} else if (status == 0) {
}
else if (status == 0)
{
*len = 0;
return errSSLClosedGraceful;
} else {
}
else
{
*len = 0;
switch (errno) {
case ENOENT:
return errSSLClosedGraceful;
switch (errno)
{
case ENOENT: return errSSLClosedGraceful;
case EAGAIN:
return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN
// on osx
case EINPROGRESS:
return errSSLWouldBlock;
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
case EINPROGRESS: return errSSLWouldBlock;
case ECONNRESET:
return errSSLClosedAbort;
case ECONNRESET: return errSSLClosedAbort;
default:
return errSecIO;
default: return errSecIO;
}
}
}
}
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
const void *data,
size_t *len)
{
int fd = (int)(long)connection;
if (fd < 0)
return errSSLInternal;
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);
@@ -124,183 +125,188 @@ OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
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) {
if (status > 0)
{
*len = (size_t) status;
if (to_write_sz > *len)
{
return errSSLWouldBlock;
} else {
}
else
{
return noErr;
}
} else if (status == 0) {
}
else if (status == 0)
{
*len = 0;
return errSSLClosedGraceful;
} else {
}
else
{
*len = 0;
switch (errno) {
case ENOENT:
return errSSLClosedGraceful;
switch (errno)
{
case ENOENT: return errSSLClosedGraceful;
case EAGAIN:
return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN
// on osx
case EINPROGRESS:
return errSSLWouldBlock;
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
case EINPROGRESS: return errSSLWouldBlock;
case ECONNRESET:
return errSSLClosedAbort;
case ECONNRESET: return errSSLClosedAbort;
default:
return errSecIO;
default: return errSecIO;
}
}
}
}
bool SocketAppleSSL::accept(std::string &errMsg)
{
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 SocketAppleSSL::tlsHandShake(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
OSStatus status;
do {
do
{
status = SSLHandshake(_sslContext);
// Interrupt the handshake
if (isCancellationRequested()) {
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
return errSSLInternal;
}
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
return status;
}
}
// No wait support
bool SocketAppleSSL::connect(const std::string &host,
// No wait support
bool SocketAppleSSL::connect(const std::string& host,
int port,
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
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;
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
_sslContext = SSLCreateContext(kCFAllocatorDefault,
kSSLClientSide,
kSSLStreamType);
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
SSLSetIOFuncs(_sslContext,
SocketAppleSSL::readFromSocket,
SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long)_sockfd);
SSLSetIOFuncs(
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (_tlsOptions.isPeerVerifyDisabled()) {
if (_tlsOptions.isPeerVerifyDisabled())
{
Boolean option(1);
SSLSetSessionOption(_sslContext,
kSSLSessionOptionBreakOnServerAuth,
option);
SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option);
status = tlsHandShake(errMsg, isCancellationRequested);
if (status == errSSLServerAuthCompleted) {
if (status == errSSLServerAuthCompleted)
{
// proceed with the handshake
status = tlsHandShake(errMsg, isCancellationRequested);
}
} else {
}
else
{
status = tlsHandShake(errMsg, isCancellationRequested);
}
}
if (status != noErr) {
if (status != noErr)
{
errMsg = getSSLErrorDescription(status);
close();
return false;
}
return true;
}
}
void SocketAppleSSL::close()
{
void SocketAppleSSL::close()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_sslContext == nullptr)
return;
if (_sslContext == nullptr) return;
SSLClose(_sslContext);
CFRelease(_sslContext);
_sslContext = nullptr;
Socket::close();
}
}
ssize_t SocketAppleSSL::send(char *buf, size_t nbyte)
{
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
{
OSStatus status = errSSLWouldBlock;
while (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;
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) {
status == errSSLClosedAbort)
{
errno = ECONNRESET;
return -1;
}
if (status == errSSLWouldBlock) {
if (status == errSSLWouldBlock)
{
errno = EWOULDBLOCK;
return -1;
}
}
return -1;
}
}
// No wait support
ssize_t SocketAppleSSL::recv(void *buf, size_t nbyte)
{
// No wait support
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
{
OSStatus status = errSSLWouldBlock;
while (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;
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) {
status == errSSLClosedAbort)
{
errno = ECONNRESET;
return -1;
}
if (status == errSSLWouldBlock) {
if (status == errSSLWouldBlock)
{
errno = EWOULDBLOCK;
return -1;
}
}
return -1;
}
}
} // namespace ix
+55 -53
View File
@@ -20,26 +20,25 @@
#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)
{
//
// 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) {
socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
if (fd < 0)
{
errMsg = "Cannot create a socket";
return -1;
}
@@ -50,15 +49,16 @@ int SocketConnect::connectToAddress(
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
if (res == -1 && !Socket::isWaitNeeded()) {
if (res == -1 && !Socket::isWaitNeeded())
{
errMsg = strerror(Socket::getErrno());
Socket::closeSocket(fd);
return -1;
}
for (;;) {
if (isCancellationRequested &&
isCancellationRequested()) // Must handle timeout as well
for (;;)
{
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
Socket::closeSocket(fd);
errMsg = "Cancelled";
@@ -67,70 +67,73 @@ int SocketConnect::connectToAddress(
int timeoutMs = 10;
bool readyToRead = false;
auto selectInterrupt = ix::make_unique<SelectInterrupt>();
PollResultType pollResult =
Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt();
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
if (pollResult == PollResultType::Timeout) {
if (pollResult == PollResultType::Timeout)
{
continue;
} else if (pollResult == PollResultType::Error) {
}
else if (pollResult == PollResultType::Error)
{
Socket::closeSocket(fd);
errMsg =
std::string("Connect error: ") + strerror(Socket::getErrno());
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
} else if (pollResult == PollResultType::ReadyForWrite) {
}
else if (pollResult == PollResultType::ReadyForWrite)
{
return fd;
} else {
}
else
{
Socket::closeSocket(fd);
errMsg =
std::string("Connect error: ") + strerror(Socket::getErrno());
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
}
}
Socket::closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
return -1;
}
int SocketConnect::connect(const std::string &hostname,
int SocketConnect::connect(const std::string& hostname,
int port,
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
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) {
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) {
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) {
if (sockfd != -1)
{
break;
}
}
freeaddrinfo(res);
return sockfd;
}
}
// FIXME: configure is a terrible name
void SocketConnect::configure(int 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));
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
// 2. make socket non blocking
#ifdef _WIN32
@@ -140,11 +143,10 @@ void SocketConnect::configure(int sockfd)
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
#endif
// 3. (apple) prevent SIGPIPE from being emitted when the remote end
// disconnect
// 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));
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value));
#endif
}
}
} // namespace ix
+13 -9
View File
@@ -25,18 +25,21 @@
namespace ix
{
std::unique_ptr<Socket> createSocket(bool tls,
std::unique_ptr<Socket> createSocket(bool tls,
int fd,
std::string &errorMsg,
const SocketTLSOptions &tlsOptions)
{
(void)tlsOptions;
std::string& errorMsg,
const SocketTLSOptions& tlsOptions)
{
(void) tlsOptions;
errorMsg.clear();
std::unique_ptr<Socket> socket;
if (!tls) {
if (!tls)
{
socket = ix::make_unique<Socket>(fd);
} else {
}
else
{
#ifdef IXWEBSOCKET_USE_TLS
#if defined(IXWEBSOCKET_USE_MBED_TLS)
socket = ix::make_unique<SocketMbedTLS>(tlsOptions, fd);
@@ -51,10 +54,11 @@ std::unique_ptr<Socket> createSocket(bool tls,
#endif
}
if (!socket->init(errorMsg)) {
if (!socket->init(errorMsg))
{
socket.reset();
}
return socket;
}
}
} // namespace ix
+128 -117
View File
@@ -23,16 +23,20 @@
namespace ix
{
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions &tlsOptions, int fd)
: Socket(fd), _tlsOptions(tlsOptions)
{
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _tlsOptions(tlsOptions)
{
initMBedTLS();
}
}
SocketMbedTLS::~SocketMbedTLS() { SocketMbedTLS::close(); }
SocketMbedTLS::~SocketMbedTLS()
{
SocketMbedTLS::close();
}
void SocketMbedTLS::initMBedTLS()
{
void SocketMbedTLS::initMBedTLS()
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_init(&_ssl);
@@ -42,17 +46,17 @@ void SocketMbedTLS::initMBedTLS()
mbedtls_x509_crt_init(&_cacert);
mbedtls_x509_crt_init(&_cert);
mbedtls_pk_init(&_pkey);
}
}
bool SocketMbedTLS::loadSystemCertificates(std::string &errorMsg)
{
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");
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
if (!systemStore) {
if (!systemStore)
{
errorMsg = "CertOpenStore failed with ";
errorMsg += std::to_string(GetLastError());
return false;
@@ -61,14 +65,15 @@ bool SocketMbedTLS::loadSystemCertificates(std::string &errorMsg)
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,
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) {
if (ret == 0)
{
++certificateCount;
}
}
@@ -77,7 +82,8 @@ bool SocketMbedTLS::loadSystemCertificates(std::string &errorMsg)
CertFreeCertificateContext(certificateIterator);
CertCloseStore(systemStore, 0);
if (certificateCount == 0) {
if (certificateCount == 0)
{
errorMsg = "No certificates found";
return false;
}
@@ -89,88 +95,92 @@ bool SocketMbedTLS::loadSystemCertificates(std::string &errorMsg)
// 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)
{
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
{
initMBedTLS();
std::lock_guard<std::mutex> lock(_mutex);
const char *pers = "IXSocketMbedTLS";
const char* pers = "IXSocketMbedTLS";
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
mbedtls_entropy_func,
&_entropy,
(const unsigned char *)pers,
strlen(pers)) != 0) {
(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,
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
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) {
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)
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)
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) {
if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0)
{
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
return false;
}
}
if (_tlsOptions.isPeerVerifyDisabled()) {
if (_tlsOptions.isPeerVerifyDisabled())
{
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
} else {
}
else
{
// FIXME: should we call mbedtls_ssl_conf_verify ?
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
if (_tlsOptions.isUsingSystemDefaults()) {
if (!loadSystemCertificates(errMsg)) {
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
}
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) {
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) {
}
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
{
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
return false;
}
@@ -179,56 +189,54 @@ bool SocketMbedTLS::init(const std::string &host,
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
}
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0) {
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) {
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 SocketMbedTLS::accept(std::string& errMsg)
{
bool isClient = false;
bool initialized = init(std::string(), isClient, errMsg);
if (!initialized) {
if (!initialized)
{
close();
return false;
}
mbedtls_ssl_set_bio(&_ssl,
&_sockfd,
mbedtls_net_send,
mbedtls_net_recv,
NULL);
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
int res;
do {
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);
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
if (res != 0) {
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) {
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);
mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags);
errMsg += " : ";
errMsg += verifyBuf;
}
@@ -238,50 +246,47 @@ bool SocketMbedTLS::accept(std::string &errMsg)
}
return true;
}
}
bool SocketMbedTLS::connect(const std::string &host,
bool SocketMbedTLS::connect(const std::string& host,
int port,
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
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;
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
}
bool isClient = true;
bool initialized = init(host, isClient, errMsg);
if (!initialized) {
if (!initialized)
{
close();
return false;
}
mbedtls_ssl_set_bio(&_ssl,
&_sockfd,
mbedtls_net_send,
mbedtls_net_recv,
NULL);
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
int res;
do {
do
{
{
std::lock_guard<std::mutex> lock(_mutex);
res = mbedtls_ssl_handshake(&_ssl);
}
if (isCancellationRequested()) {
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
close();
return false;
}
} while (res == MBEDTLS_ERR_SSL_WANT_READ ||
res == MBEDTLS_ERR_SSL_WANT_WRITE);
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
if (res != 0) {
if (res != 0)
{
char buf[256];
mbedtls_strerror(res, buf, sizeof(buf));
@@ -293,10 +298,10 @@ bool SocketMbedTLS::connect(const std::string &host,
}
return true;
}
}
void SocketMbedTLS::close()
{
void SocketMbedTLS::close()
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_free(&_ssl);
@@ -307,43 +312,49 @@ void SocketMbedTLS::close()
mbedtls_x509_crt_free(&_cert);
Socket::close();
}
}
ssize_t SocketMbedTLS::send(char *buf, size_t nbyte)
{
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);
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
if (res > 0) {
if (res > 0)
{
return res;
} else if (res == MBEDTLS_ERR_SSL_WANT_READ ||
res == MBEDTLS_ERR_SSL_WANT_WRITE) {
}
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
} else {
}
else
{
return -1;
}
}
}
ssize_t SocketMbedTLS::recv(void *buf, size_t nbyte)
{
while (true) {
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);
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte);
if (res > 0) {
if (res > 0)
{
return res;
}
if (res == MBEDTLS_ERR_SSL_WANT_READ ||
res == MBEDTLS_ERR_SSL_WANT_WRITE) {
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
}
return -1;
}
}
}
} // namespace ix
File diff suppressed because it is too large Load Diff
+197 -169
View File
@@ -20,43 +20,49 @@
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);
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(
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(); }
SocketServer::~SocketServer()
{
stop();
}
void SocketServer::logError(const std::string &str)
{
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)
{
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::pair<bool, std::string> SocketServer::listen()
{
std::string acceptSelectInterruptInitErrorMsg;
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg)) {
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg))
{
std::stringstream ss;
ss << "SocketServer::listen() error in SelectInterrupt::init: "
<< acceptSelectInterruptInitErrorMsg;
@@ -64,46 +70,42 @@ std::pair<bool, std::string> SocketServer::listen()
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 "
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) {
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error creating socket): "
<< strerror(Socket::getErrno());
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) {
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());
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
if (_addressFamily == AF_INET) {
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) {
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 << " : "
@@ -114,7 +116,8 @@ std::pair<bool, std::string> SocketServer::listen()
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0) {
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : "
@@ -123,14 +126,15 @@ std::pair<bool, std::string> SocketServer::listen()
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
} else // AF_INET6
}
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) {
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 << " : "
@@ -141,7 +145,8 @@ std::pair<bool, std::string> SocketServer::listen()
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0) {
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : "
@@ -155,47 +160,54 @@ std::pair<bool, std::string> SocketServer::listen()
//
// Listen for connections. Specify the tcp backlog.
//
if (::listen(_serverFd, _backlog) < 0) {
if (::listen(_serverFd, _backlog) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling listen "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
<< "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()
{
void SocketServer::start()
{
_stop = false;
if (!_thread.joinable()) {
if (!_thread.joinable())
{
_thread = std::thread(&SocketServer::run, this);
}
if (!_gcThread.joinable()) {
if (!_gcThread.joinable())
{
_gcThread = std::thread(&SocketServer::runGC, this);
}
}
}
void SocketServer::wait()
{
void SocketServer::wait()
{
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
_conditionVariable.wait(lock);
}
}
void SocketServer::stopAcceptingConnections() { _stop = true; }
void SocketServer::stopAcceptingConnections()
{
_stop = true;
}
void SocketServer::stop()
{
void SocketServer::stop()
{
// Stop accepting connections, and close the 'accept' thread
if (_thread.joinable()) {
if (_thread.joinable())
{
_stop = true;
// Wake up select
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest)) {
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest))
{
logError("SocketServer::stop: Cannot wake up from select");
}
@@ -204,7 +216,8 @@ void SocketServer::stop()
}
// Join all threads and make sure that all connections are terminated
if (_gcThread.joinable()) {
if (_gcThread.joinable())
{
_stopGc = true;
_conditionVariableGC.notify_one();
_gcThread.join();
@@ -213,82 +226,78 @@ void SocketServer::stop()
_conditionVariable.notify_one();
Socket::closeSocket(_serverFd);
}
}
void SocketServer::setConnectionStateFactory(
const ConnectionStateFactory &connectionStateFactory)
{
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()
{
//
// 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;
while (it != itEnd)
{
auto& connectionState = it->first;
auto& thread = it->second;
if (!connectionState->isTerminated()) {
if (!connectionState->isTerminated())
{
++it;
continue;
}
if (thread.joinable())
thread.join();
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
void SocketServer::run()
{
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
setThreadName("SocketServer::accept");
// 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;
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
// 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);
PollResultType pollResult =
Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt);
if (pollResult == PollResultType::Error) {
if (pollResult == PollResultType::Error)
{
std::stringstream ss;
ss << "SocketServer::run() error in select: " <<
#ifndef _WIN32
strerror(Socket::getErrno());
#else
Socket::getErrno();
#endif
ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno());
logError(ss.str());
continue;
}
if (pollResult != PollResultType::ReadyForRead) {
if (pollResult != PollResultType::ReadyForRead)
{
continue;
}
@@ -299,24 +308,24 @@ void SocketServer::run()
socklen_t addressLen = sizeof(client);
memset(&client, 0, sizeof(client));
if ((clientFd =
accept(_serverFd, (struct sockaddr *)&client, &addressLen)) <
0) {
if (!Socket::isWaitNeeded()) {
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);
ss << "SocketServer::run() error accepting connection: " << err << ", "
<< strerror(err);
logError(ss.str());
}
continue;
}
if (getConnectedClientsCount() >= _maxConnections) {
if (getConnectedClientsCount() >= _maxConnections)
{
std::stringstream ss;
ss << "SocketServer::run() reached max connections = "
<< _maxConnections << ". "
ss << "SocketServer::run() reached max connections = " << _maxConnections << ". "
<< "Not accepting connection";
logError(ss.str());
@@ -329,16 +338,15 @@ void SocketServer::run()
std::string remoteIp;
int remotePort;
if (_addressFamily == AF_INET) {
if (_addressFamily == AF_INET)
{
char remoteIp4[INET_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET,
&client.sin_addr,
remoteIp4,
INET_ADDRSTRLEN) == nullptr) {
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);
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
@@ -348,17 +356,17 @@ void SocketServer::run()
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp4;
} else // AF_INET6
}
else // AF_INET6
{
char remoteIp6[INET6_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET6,
&client.sin_addr,
remoteIp6,
INET6_ADDRSTRLEN) == nullptr) {
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);
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
@@ -371,23 +379,23 @@ void SocketServer::run()
}
std::shared_ptr<ConnectionState> connectionState;
if (_connectionStateFactory) {
if (_connectionStateFactory)
{
connectionState = _connectionStateFactory();
}
connectionState->setOnSetTerminatedCallback(
[this] { onSetTerminatedCallback(); });
connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); });
connectionState->setRemoteIp(remoteIp);
connectionState->setRemotePort(remotePort);
if (_stop)
return;
if (_stop) return;
// create socket
std::string errorMsg;
bool tls = _socketTLSOptions.tls;
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
if (socket == nullptr) {
if (socket == nullptr)
{
logError("SocketServer::run() cannot create socket: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
@@ -396,7 +404,8 @@ void SocketServer::run()
// Set the socket to non blocking mode + other tweaks
SocketConnect::configure(clientFd);
if (!socket->accept(errorMsg)) {
if (!socket->accept(errorMsg))
{
logError("SocketServer::run() tls accept failed: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
@@ -404,64 +413,83 @@ void SocketServer::run()
// 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)));
_connectionsThreads.push_back(std::make_pair(
connectionState,
std::thread(
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
}
}
}
size_t SocketServer::getConnectionsThreadsCount()
{
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
}
void SocketServer::runGC()
{
setThreadName("SocketServer::GC");
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 (;;) {
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) {
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) {
if (!_stopGc)
{
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
_conditionVariableGC.wait(lock);
}
}
}
}
void SocketServer::setTLSOptions(const SocketTLSOptions &socketTLSOptions)
{
void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
_socketTLSOptions = socketTLSOptions;
}
}
void SocketServer::onSetTerminatedCallback()
{
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; }
int SocketServer::getPort()
{
return _port;
}
std::string SocketServer::getHost() { return _host; }
std::string SocketServer::getHost()
{
return _host;
}
int SocketServer::getBacklog() { return _backlog; }
int SocketServer::getBacklog()
{
return _backlog;
}
std::size_t SocketServer::getMaxConnections() { return _maxConnections; }
std::size_t SocketServer::getMaxConnections()
{
return _maxConnections;
}
int SocketServer::getAddressFamily() { return _addressFamily; }
int SocketServer::getAddressFamily()
{
return _addressFamily;
}
} // namespace ix
+41 -34
View File
@@ -12,75 +12,82 @@
namespace ix
{
const char *kTLSCAFileUseSystemDefaults = "SYSTEM";
const char *kTLSCAFileDisableVerify = "NONE";
const char *kTLSCiphersUseDefault = "DEFAULT";
const char *kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
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)) {
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)) {
if (!keyFile.empty() && !std::ifstream(keyFile))
{
_errMsg = "keyFile not found: " + keyFile;
return false;
}
if (!caFile.empty() && caFile != kTLSCAFileDisableVerify &&
caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile)) {
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";
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
{
bool SocketTLSOptions::hasCertAndKey() const
{
return !certFile.empty() && !keyFile.empty();
}
}
bool SocketTLSOptions::isUsingSystemDefaults() const
{
bool SocketTLSOptions::isUsingSystemDefaults() const
{
return caFile == kTLSCAFileUseSystemDefaults;
}
}
bool SocketTLSOptions::isUsingInMemoryCAs() const
{
bool SocketTLSOptions::isUsingInMemoryCAs() const
{
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
}
}
bool SocketTLSOptions::isPeerVerifyDisabled() const
{
bool SocketTLSOptions::isPeerVerifyDisabled() const
{
return caFile == kTLSCAFileDisableVerify;
}
}
bool SocketTLSOptions::isUsingDefaultCiphers() const
{
bool SocketTLSOptions::isUsingDefaultCiphers() const
{
return ciphers.empty() || ciphers == kTLSCiphersUseDefault;
}
}
const std::string &SocketTLSOptions::getErrorMsg() const { return _errMsg; }
const std::string& SocketTLSOptions::getErrorMsg() const
{
return _errMsg;
}
std::string SocketTLSOptions::getDescription() const
{
std::string SocketTLSOptions::getDescription() const
{
std::stringstream ss;
ss << "TLS Options:" << std::endl;
ss << " certFile = " << certFile << std::endl;
ss << " keyFile = " << keyFile << std::endl;
ss << " caFile = " << caFile << std::endl;
ss << " ciphers = " << ciphers << std::endl;
ss << " ciphers = " << ciphers << std::endl;
ss << " tls = " << tls << std::endl;
return ss.str();
}
}
} // namespace ix
+10 -12
View File
@@ -11,29 +11,27 @@
namespace ix
{
bool CaseInsensitiveLess::NocaseCompare::operator()(
const unsigned char &c1,
const unsigned char &c2) const
{
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)
{
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
{
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
{
return CaseInsensitiveLess::cmp(s1, s2);
}
}
} // namespace ix
+44 -43
View File
@@ -12,21 +12,27 @@
namespace ix
{
UdpSocket::UdpSocket(int fd) : _sockfd(fd) { ; }
UdpSocket::UdpSocket(int fd)
: _sockfd(fd)
{
;
}
UdpSocket::~UdpSocket() { close(); }
UdpSocket::~UdpSocket()
{
close();
}
void UdpSocket::close()
{
if (_sockfd == -1)
return;
void UdpSocket::close()
{
if (_sockfd == -1) return;
closeSocket(_sockfd);
_sockfd = -1;
}
}
int UdpSocket::getErrno()
{
int UdpSocket::getErrno()
{
int err;
#ifdef _WIN32
@@ -36,32 +42,34 @@ int UdpSocket::getErrno()
#endif
return err;
}
}
bool UdpSocket::isWaitNeeded()
{
bool UdpSocket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS) {
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
}
}
void UdpSocket::closeSocket(int fd)
{
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)
{
bool UdpSocket::init(const std::string& host, int port, std::string& errMsg)
{
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sockfd < 0) {
if (_sockfd < 0)
{
errMsg = "Could not create socket";
return false;
}
@@ -84,42 +92,35 @@ bool UdpSocket::init(const std::string &host, int port, std::string &errMsg)
hints.ai_socktype = SOCK_DGRAM;
int ret = getaddrinfo(host.c_str(), nullptr, &hints, &result);
if (ret != 0) {
if (ret != 0)
{
errMsg = strerror(UdpSocket::getErrno());
freeaddrinfo(result);
close();
return false;
}
struct sockaddr_in *host_addr = (struct sockaddr_in *)result->ai_addr;
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::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)
{
ssize_t UdpSocket::recvfrom(char* buffer, size_t length)
{
#ifdef _WIN32
int addressLen = (int)sizeof(_server);
int addressLen = (int) sizeof(_server);
#else
socklen_t addressLen = (socklen_t)sizeof(_server);
socklen_t addressLen = (socklen_t) sizeof(_server);
#endif
return (ssize_t)::recvfrom(_sockfd,
buffer,
length,
0,
(struct sockaddr *)&_server,
&addressLen);
}
return (ssize_t)::recvfrom(
_sockfd, buffer, length, 0, (struct sockaddr*) &_server, &addressLen);
}
} // namespace ix
+120 -88
View File
@@ -13,8 +13,8 @@
* 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 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,
@@ -37,7 +37,8 @@
namespace
{
enum LUrlParserError {
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
@@ -46,11 +47,11 @@ enum LUrlParserError {
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
};
class clParseURL
{
public:
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
@@ -61,86 +62,97 @@ public:
std::string m_UserName;
std::string m_Password;
clParseURL() : m_ErrorCode(LUrlParserError_Uninitialized) {}
clParseURL()
: m_ErrorCode(LUrlParserError_Uninitialized)
{
}
/// return 'true' if the parsing was successful
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
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;
/// 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);
static clParseURL ParseURL(const std::string& URL);
private:
explicit clParseURL(LUrlParserError ErrorCode) : m_ErrorCode(ErrorCode) {}
};
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;
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()) {
bool clParseURL::GetPort(int* OutPort) const
{
if (!IsValid())
{
return false;
}
int Port = atoi(m_Port.c_str());
if (Port <= 0 || Port > 65535) {
if (Port <= 0 || Port > 65535)
{
return false;
}
if (OutPort) {
if (OutPort)
{
*OutPort = Port;
}
return true;
}
}
// based on RFC 1738 and RFC 3986
clParseURL clParseURL::ParseURL(const std::string &URL)
{
// based on RFC 1738 and RFC 3986
clParseURL clParseURL::ParseURL(const std::string& URL)
{
clParseURL Result;
const char *CurrentString = URL.c_str();
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
* 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, ':');
const char* LocalString = strchr(CurrentString, ':');
if (!LocalString) {
if (!LocalString)
{
return clParseURL(LUrlParserError_NoUrlCharacter);
}
// save the scheme name
Result.m_Scheme =
std::string(CurrentString, LocalString - CurrentString);
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
if (!IsSchemeValid(Result.m_Scheme)) {
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);
std::transform(
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
// skip ':'
CurrentString = LocalString + 1;
@@ -152,22 +164,24 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
*/
// skip "//"
if (*CurrentString++ != '/')
return clParseURL(LUrlParserError_NoDoubleSlash);
if (*CurrentString++ != '/')
return clParseURL(LUrlParserError_NoDoubleSlash);
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;
const char* LocalString = CurrentString;
while (*LocalString) {
if (*LocalString == '@') {
while (*LocalString)
{
if (*LocalString == '@')
{
// user name and password are specified
bHasUserName = true;
break;
} else if (*LocalString == '/') {
}
else if (*LocalString == '/')
{
// end of <host>:<port> specification
bHasUserName = false;
break;
@@ -179,18 +193,19 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
// user name and password
LocalString = CurrentString;
if (bHasUserName) {
if (bHasUserName)
{
// read user name
while (*LocalString && *LocalString != ':' && *LocalString != '@')
LocalString++;
Result.m_UserName =
std::string(CurrentString, LocalString - CurrentString);
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
// proceed with the current pointer
CurrentString = LocalString;
if (*CurrentString == ':') {
if (*CurrentString == ':')
{
// skip ':'
CurrentString++;
@@ -200,14 +215,14 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
while (*LocalString && *LocalString != '@')
LocalString++;
Result.m_Password =
std::string(CurrentString, LocalString - CurrentString);
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
}
// skip '@'
if (*CurrentString != '@') {
if (*CurrentString != '@')
{
return clParseURL(LUrlParserError_NoAtSign);
}
@@ -219,13 +234,16 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
// go ahead, read the host name
LocalString = CurrentString;
while (*LocalString) {
if (bHasBracket && *LocalString == ']') {
while (*LocalString)
{
if (bHasBracket && *LocalString == ']')
{
// end of IPv6 address
LocalString++;
break;
} else if (!bHasBracket &&
(*LocalString == ':' || *LocalString == '/')) {
}
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
{
// port number is specified
break;
}
@@ -238,7 +256,8 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
CurrentString = LocalString;
// is port number specified?
if (*CurrentString == ':') {
if (*CurrentString == ':')
{
CurrentString++;
// read port number
@@ -253,14 +272,16 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
}
// end of string
if (!*CurrentString) {
if (!*CurrentString)
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if (*CurrentString != '/') {
if (*CurrentString != '/')
{
return clParseURL(LUrlParserError_NoSlash);
}
@@ -277,7 +298,8 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
CurrentString = LocalString;
// check for query
if (*CurrentString == '?') {
if (*CurrentString == '?')
{
// skip '?'
CurrentString++;
@@ -287,14 +309,14 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
while (*LocalString && *LocalString != '#')
LocalString++;
Result.m_Query =
std::string(CurrentString, LocalString - CurrentString);
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
CurrentString = LocalString;
}
// check for fragment
if (*CurrentString == '#') {
if (*CurrentString == '#')
{
// skip '#'
CurrentString++;
@@ -304,28 +326,28 @@ clParseURL clParseURL::ParseURL(const std::string &URL)
while (*LocalString)
LocalString++;
Result.m_Fragment =
std::string(CurrentString, LocalString - CurrentString);
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)
{
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()) {
if (!res.IsValid())
{
return false;
}
@@ -334,30 +356,40 @@ bool UrlParser::parse(const std::string &url,
path = res.m_Path;
query = res.m_Query;
if (!res.GetPort(&port)) {
if (protocol == "ws" || protocol == "http") {
if (!res.GetPort(&port))
{
if (protocol == "ws" || protocol == "http")
{
port = 80;
} else if (protocol == "wss" || protocol == "https") {
}
else if (protocol == "wss" || protocol == "https")
{
port = 443;
} else {
}
else
{
// Invalid protocol. Should be caught by regex check
// but this missing branch trigger cpplint linter.
return false;
}
}
if (path.empty()) {
if (path.empty())
{
path = "/";
} else if (path[0] != '/') {
}
else if (path[0] != '/')
{
path = '/' + path;
}
if (!query.empty()) {
if (!query.empty())
{
path += "?";
path += query;
}
return true;
}
}
} // namespace ix
+5 -8
View File
@@ -20,12 +20,9 @@
#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)
#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
#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)
@@ -59,8 +56,8 @@
namespace ix
{
std::string userAgent()
{
std::string userAgent()
{
std::stringstream ss;
// IXWebSocket Version
@@ -88,5 +85,5 @@ std::string userAgent()
#endif
return ss.str();
}
}
} // namespace ix
+14 -14
View File
@@ -24,19 +24,19 @@
namespace ix
{
class Uuid
{
public:
class Uuid
{
public:
Uuid();
std::string toString() const;
private:
private:
uint64_t _ab;
uint64_t _cd;
};
};
Uuid::Uuid()
{
Uuid::Uuid()
{
static std::random_device rd;
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
@@ -45,10 +45,10 @@ Uuid::Uuid()
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
}
}
std::string Uuid::toString() const
{
std::string Uuid::toString() const
{
std::stringstream ss;
ss << std::hex << std::nouppercase << std::setfill('0');
@@ -65,11 +65,11 @@ std::string Uuid::toString() const
ss << std::setw(8) << d;
return ss.str();
}
}
std::string uuid4()
{
std::string uuid4()
{
Uuid id;
return id.toString();
}
}
} // namespace ix
+296 -270
View File
@@ -17,171 +17,164 @@
namespace
{
const std::string emptyMsg;
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
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,
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()
{
WebSocket::~WebSocket()
{
stop();
_ws.setOnCloseCallback(nullptr);
}
}
void WebSocket::setUrl(const std::string &url)
{
void WebSocket::setUrl(const std::string& url)
{
std::lock_guard<std::mutex> lock(_configMutex);
_url = url;
}
}
void WebSocket::setHandshakeTimeout(int handshakeTimeoutSecs)
{
void WebSocket::setHandshakeTimeout(int handshakeTimeoutSecs)
{
_handshakeTimeoutSecs = handshakeTimeoutSecs;
}
}
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders &headers)
{
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
{
std::lock_guard<std::mutex> lock(_configMutex);
_extraHeaders = headers;
}
}
const std::string WebSocket::getUrl() const
{
const std::string WebSocket::getUrl() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _url;
}
}
void WebSocket::setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions &perMessageDeflateOptions)
{
void WebSocket::setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{
std::lock_guard<std::mutex> lock(_configMutex);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
}
void WebSocket::setTLSOptions(const SocketTLSOptions &socketTLSOptions)
{
void WebSocket::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
std::lock_guard<std::mutex> lock(_configMutex);
_socketTLSOptions = socketTLSOptions;
}
}
const WebSocketPerMessageDeflateOptions
WebSocket::getPerMessageDeflateOptions() const
{
const WebSocketPerMessageDeflateOptions WebSocket::getPerMessageDeflateOptions() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _perMessageDeflateOptions;
}
}
void WebSocket::setPingInterval(int pingIntervalSecs)
{
void WebSocket::setPingInterval(int pingIntervalSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = pingIntervalSecs;
}
}
int WebSocket::getPingInterval() const
{
int WebSocket::getPingInterval() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs;
}
}
void WebSocket::enablePong()
{
void WebSocket::enablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = true;
}
}
void WebSocket::disablePong()
{
void WebSocket::disablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = false;
}
}
void WebSocket::enablePerMessageDeflate()
{
void WebSocket::enablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
WebSocketPerMessageDeflateOptions perMessageDeflateOptions(true);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
}
void WebSocket::disablePerMessageDeflate()
{
void WebSocket::disablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
WebSocketPerMessageDeflateOptions perMessageDeflateOptions(false);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
}
void WebSocket::setMaxWaitBetweenReconnectionRetries(
uint32_t maxWaitBetweenReconnectionRetries)
{
void WebSocket::setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries;
}
}
void WebSocket::setMinWaitBetweenReconnectionRetries(
uint32_t minWaitBetweenReconnectionRetries)
{
void WebSocket::setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_minWaitBetweenReconnectionRetries = minWaitBetweenReconnectionRetries;
}
}
uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const
{
uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _maxWaitBetweenReconnectionRetries;
}
}
uint32_t WebSocket::getMinWaitBetweenReconnectionRetries() const
{
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
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)
{
void WebSocket::stop(uint16_t code, const std::string& reason)
{
close(code, reason);
if (_thread.joinable()) {
if (_thread.joinable())
{
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
@@ -189,22 +182,21 @@ void WebSocket::stop(uint16_t code, const std::string &reason)
_thread.join();
_stop = false;
}
}
}
WebSocketInitResult WebSocket::connect(int timeoutSecs)
{
WebSocketInitResult WebSocket::connect(int timeoutSecs)
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_socketTLSOptions,
_enablePong,
_pingIntervalSecs);
_ws.configure(
_perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs);
}
WebSocketHttpHeaders headers(_extraHeaders);
std::string subProtocolsHeader;
auto subProtocols = getSubProtocols();
if (!subProtocols.empty()) {
if (!subProtocols.empty())
{
//
// Sub Protocol strings are comma separated.
// Python code to do that is:
@@ -212,8 +204,10 @@ WebSocketInitResult WebSocket::connect(int timeoutSecs)
// 'json,msgpack'
//
int i = 0;
for (auto subProtocol : subProtocols) {
if (i++ != 0) {
for (auto subProtocol : subProtocols)
{
if (i++ != 0)
{
subProtocolsHeader += ",";
}
subProtocolsHeader += subProtocol;
@@ -222,7 +216,8 @@ WebSocketInitResult WebSocket::connect(int timeoutSecs)
}
WebSocketInitResult status = _ws.connectToUrl(_url, headers, timeoutSecs);
if (!status.success) {
if (!status.success)
{
return status;
}
@@ -234,78 +229,81 @@ WebSocketInitResult WebSocket::connect(int timeoutSecs)
WebSocketOpenInfo(status.uri, status.headers, status.protocol),
WebSocketCloseInfo()));
if (_pingIntervalSecs > 0) {
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) {
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,
_onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
emptyMsg,
0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()));
if (_pingIntervalSecs > 0) {
if (_pingIntervalSecs > 0)
{
// Send a heart beat right away
_ws.sendHeartBeat();
}
return status;
}
}
bool WebSocket::isConnected() const
{
bool WebSocket::isConnected() const
{
return getReadyState() == ReadyState::Open;
}
}
bool WebSocket::isClosing() const
{
bool WebSocket::isClosing() const
{
return getReadyState() == ReadyState::Closing;
}
}
void WebSocket::close(uint16_t code, const std::string &reason)
{
void WebSocket::close(uint16_t code, const std::string& reason)
{
_ws.close(code, reason);
}
}
void WebSocket::checkConnection(bool firstConnectionAttempt)
{
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) {
while (true)
{
if (isConnected() || isClosing() || _stop)
{
break;
}
if (!firstConnectionAttempt && !_automaticReconnection) {
if (!firstConnectionAttempt && !_automaticReconnection)
{
// Do not attempt to reconnect
break;
}
@@ -313,24 +311,28 @@ void WebSocket::checkConnection(bool firstConnectionAttempt)
firstConnectionAttempt = false;
// Only sleep if we are retrying
if (duration.count() > 0) {
if (duration.count() > 0)
{
std::unique_lock<std::mutex> lock(_sleepMutex);
_sleepCondition.wait_for(lock, duration);
}
if (_stop) {
if (_stop)
{
break;
}
// Try to connect synchronously
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
if (!status.success) {
if (!status.success)
{
WebSocketErrorInfo connectErr;
if (_automaticReconnection) {
duration = millis(calculateRetryWaitMilliseconds(
retries++,
if (_automaticReconnection)
{
duration =
millis(calculateRetryWaitMilliseconds(retries++,
_maxWaitBetweenReconnectionRetries,
_minWaitBetweenReconnectionRetries));
@@ -341,8 +343,7 @@ void WebSocket::checkConnection(bool firstConnectionAttempt)
connectErr.reason = status.errorStr;
connectErr.http_status = status.http_status;
_onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
_onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
emptyMsg,
0,
connectErr,
@@ -350,28 +351,29 @@ void WebSocket::checkConnection(bool firstConnectionAttempt)
WebSocketCloseInfo()));
}
}
}
}
void WebSocket::run()
{
void WebSocket::run()
{
setThreadName(getUrl());
bool firstConnectionAttempt = true;
while (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) {
if (getReadyState() == ReadyState::Closed)
{
break;
}
// We can avoid to poll if we want to stop and are not closing
if (_stop && !isClosing())
break;
if (_stop && !isClosing()) break;
// 2. Poll to see if there's any new data available
WebSocketTransport::PollResult pollResult = _ws.poll();
@@ -379,39 +381,45 @@ void WebSocket::run()
// 3. Dispatch the incoming messages
_ws.dispatch(
pollResult,
[this](const std::string &msg,
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
WebSocketTransport::MessageKind messageKind) {
WebSocketMessageType webSocketMessageType{
WebSocketMessageType::Error};
switch (messageKind) {
WebSocketMessageType webSocketMessageType{WebSocketMessageType::Error};
switch (messageKind)
{
case WebSocketTransport::MessageKind::MSG_TEXT:
case WebSocketTransport::MessageKind::MSG_BINARY: {
case WebSocketTransport::MessageKind::MSG_BINARY:
{
webSocketMessageType = WebSocketMessageType::Message;
} break;
}
break;
case WebSocketTransport::MessageKind::PING: {
case WebSocketTransport::MessageKind::PING:
{
webSocketMessageType = WebSocketMessageType::Ping;
} break;
}
break;
case WebSocketTransport::MessageKind::PONG: {
case WebSocketTransport::MessageKind::PONG:
{
webSocketMessageType = WebSocketMessageType::Pong;
} break;
}
break;
case WebSocketTransport::MessageKind::FRAGMENT: {
case WebSocketTransport::MessageKind::FRAGMENT:
{
webSocketMessageType = WebSocketMessageType::Fragment;
} break;
}
break;
}
WebSocketErrorInfo webSocketErrorInfo;
webSocketErrorInfo.decompressionError = decompressionError;
bool binary =
messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
_onMessageCallback(
ix::make_unique<WebSocketMessage>(webSocketMessageType,
_onMessageCallback(ix::make_unique<WebSocketMessage>(webSocketMessageType,
msg,
wireSize,
webSocketErrorInfo,
@@ -422,80 +430,93 @@ void WebSocket::run()
WebSocket::invokeTrafficTrackerCallback(wireSize, true);
});
}
}
}
void WebSocket::setOnMessageCallback(const OnMessageCallback &callback)
{
void WebSocket::setOnMessageCallback(const OnMessageCallback& callback)
{
_onMessageCallback = callback;
}
}
bool WebSocket::isOnMessageCallbackRegistered() const
{
bool WebSocket::isOnMessageCallbackRegistered() const
{
return _onMessageCallback != nullptr;
}
}
void WebSocket::setTrafficTrackerCallback(
const OnTrafficTrackerCallback &callback)
{
void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback)
{
_onTrafficTrackerCallback = callback;
}
}
void WebSocket::resetTrafficTrackerCallback()
{
void WebSocket::resetTrafficTrackerCallback()
{
setTrafficTrackerCallback(nullptr);
}
}
void WebSocket::invokeTrafficTrackerCallback(size_t size, bool incoming)
{
if (_onTrafficTrackerCallback) {
void WebSocket::invokeTrafficTrackerCallback(size_t size, bool incoming)
{
if (_onTrafficTrackerCallback)
{
_onTrafficTrackerCallback(size, incoming);
}
}
}
WebSocketSendInfo WebSocket::send(const std::string &data,
WebSocketSendInfo WebSocket::send(const std::string& data,
bool binary,
const OnProgressCallback &onProgressCallback)
{
return (binary) ? sendBinary(data, onProgressCallback)
: sendText(data, onProgressCallback);
}
const OnProgressCallback& onProgressCallback)
{
return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback);
}
WebSocketSendInfo
WebSocket::sendBinary(const std::string &text,
const OnProgressCallback &onProgressCallback)
{
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const std::string& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo
WebSocket::sendText(const std::string &text,
const OnProgressCallback &onProgressCallback)
{
if (!validateUtf8(text)) {
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)
{
WebSocketSendInfo WebSocket::ping(const std::string& text)
{
// Standard limit ping message size
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize)
return WebSocketSendInfo(false);
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
return sendMessage(text, SendMessageKind::Ping);
}
}
WebSocketSendInfo
WebSocket::sendMessage(const std::string &text,
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,
SendMessageKind sendMessageKind,
const OnProgressCallback &onProgressCallback)
{
if (!isConnected())
return WebSocketSendInfo(false);
const OnProgressCallback& onProgressCallback)
{
if (!isConnected()) return WebSocketSendInfo(false);
//
// It is OK to read and write on the same socket in 2 different threads.
@@ -509,80 +530,85 @@ WebSocketSendInfo
std::lock_guard<std::mutex> lock(_writeMutex);
WebSocketSendInfo webSocketSendInfo;
switch (sendMessageKind) {
case SendMessageKind::Text: {
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
} break;
switch (sendMessageKind)
{
case SendMessageKind::Text:
{
webSocketSendInfo = _ws.sendText(message, onProgressCallback);
}
break;
case SendMessageKind::Binary: {
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
} break;
case SendMessageKind::Binary:
{
webSocketSendInfo = _ws.sendBinary(message, onProgressCallback);
}
break;
case SendMessageKind::Ping: {
webSocketSendInfo = _ws.sendPing(text);
} 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";
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;
}
}
}
void WebSocket::enableAutomaticReconnection() { _automaticReconnection = true; }
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::disableAutomaticReconnection()
{
void WebSocket::enableAutomaticReconnection()
{
_automaticReconnection = true;
}
void WebSocket::disableAutomaticReconnection()
{
_automaticReconnection = false;
}
}
bool WebSocket::isAutomaticReconnectionEnabled() const
{
bool WebSocket::isAutomaticReconnectionEnabled() const
{
return _automaticReconnection;
}
}
size_t WebSocket::bufferedAmount() const { return _ws.bufferedAmount(); }
size_t WebSocket::bufferedAmount() const
{
return _ws.bufferedAmount();
}
void WebSocket::addSubProtocol(const std::string &subProtocol)
{
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()
{
const std::vector<std::string>& WebSocket::getSubProtocols()
{
std::lock_guard<std::mutex> lock(_configMutex);
return _subProtocols;
}
}
} // namespace ix
+19 -29
View File
@@ -8,39 +8,29 @@
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 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(
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(
const std::string WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented(
"Control message fragmented");
const std::string
WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence(
const std::string WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence(
"Fragmentation: data message out of sequence");
const std::string
WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence(
const std::string WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence(
"Fragmentation: continuation opcode out of sequence");
const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage(
const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage(
"Invalid frame payload data");
const std::string
WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code");
const std::string WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code");
} // namespace ix
+107 -109
View File
@@ -6,6 +6,7 @@
#include "IXWebSocketHandshake.h"
#include "IXBase64.h"
#include "IXHttp.h"
#include "IXSocketConnect.h"
#include "IXStrCaseCompare.h"
@@ -17,52 +18,51 @@
#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)
{
}
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)
{
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 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::uniform_int_distribution<int> dist(0, (int) alphanum.size() - 1);
std::string s;
s.resize(len);
for (int i = 0; i < len; ++i) {
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)
{
WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
{
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << code;
@@ -76,49 +76,40 @@ WebSocketInitResult
auto isCancellationRequested =
makeCancellationRequestWithTimeout(1, requestInitCancellation);
if (!_socket->writeBytes(ss.str(), isCancellationRequested)) {
return WebSocketInitResult(false,
500,
"Timed out while sending error response");
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,
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);
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string errMsg;
bool success =
_socket->connect(host, port, errMsg, isCancellationRequested);
if (!success) {
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success)
{
std::stringstream ss;
ss << "Unable to connect to " << host << " on port " << port
<< ", error: " << errMsg;
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.
//
// Generate a random 24 bytes string which looks like it is base64 encoded
// y3JJHMbDL1EzLkh9GBhXDw==
// 0cb3Vd9HkbpVVumoS3Noka==
//
// See
// https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
//
std::string secWebSocketKey = genRandomString(22);
secWebSocketKey += "==";
// 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";
@@ -129,25 +120,27 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
// User-Agent can be customized by users
if (extraHeaders.find("User-Agent") == extraHeaders.end()) {
if (extraHeaders.find("User-Agent") == extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
for (auto &it : extraHeaders) {
for (auto& it : extraHeaders)
{
ss << it.first << ": " << it.second << "\r\n";
}
if (_enablePerMessageDeflate) {
if (_enablePerMessageDeflate)
{
ss << _perMessageDeflateOptions.generateHeader();
}
ss << "\r\n";
if (!_socket->writeBytes(ss.str(), isCancellationRequested)) {
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(
false,
0,
std::string("Failed sending GET request to ") + url);
false, 0, std::string("Failed sending GET request to ") + url);
}
// Read HTTP status line
@@ -155,11 +148,10 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) {
if (!lineValid)
{
return WebSocketInitResult(
false,
0,
std::string("Failed reading HTTP status line from ") + url);
false, 0, std::string("Failed reading HTTP status line from ") + url);
}
// Validate status
@@ -168,7 +160,8 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
int status = statusLine.second;
// HTTP/1.0 is too old.
if (httpVersion != "HTTP/1.1") {
if (httpVersion != "HTTP/1.1")
{
std::stringstream ss;
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
<< "Rejecting connection to " << url << ", status: " << status
@@ -180,13 +173,15 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid) {
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) {
if (status != 101)
{
std::stringstream ss;
ss << "Expecting status 101 (Switching Protocol), got " << status
<< " status connecting to " << url << ", HTTP Status line: " << line;
@@ -195,7 +190,8 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
}
// Check the presence of the connection field
if (headers.find("connection") == headers.end()) {
if (headers.find("connection") == headers.end())
{
std::string errorMsg("Missing connection value");
return WebSocketInitResult(false, status, errorMsg);
}
@@ -204,10 +200,10 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
// 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
// See https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a
//
if (!insensitiveStringCompare(headers["connection"], "Upgrade")) {
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
{
std::stringstream ss;
ss << "Invalid connection value: " << headers["connection"];
return WebSocketInitResult(false, status, ss.str());
@@ -215,49 +211,49 @@ WebSocketInitResult WebSocketHandshake::clientHandshake(
char output[29] = {};
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
if (std::string(output) != headers["sec-websocket-accept"]) {
if (std::string(output) != headers["sec-websocket-accept"])
{
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
return WebSocketInitResult(false, status, errorMsg);
}
if (_enablePerMessageDeflate) {
if (_enablePerMessageDeflate)
{
// Parse the server response. Does it support deflate ?
std::string header = headers["sec-websocket-extensions"];
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
header);
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
// If the server does not support that extension, disable it.
if (!webSocketPerMessageDeflateOptions.enabled()) {
if (!webSocketPerMessageDeflateOptions.enabled())
{
_enablePerMessageDeflate = false;
}
// Otherwise try to initialize the deflate engine (zlib)
else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions)) {
else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
{
return WebSocketInitResult(
false,
0,
"Failed to initialize per message deflate engine");
false, 0, "Failed to initialize per message deflate engine");
}
}
return WebSocketInitResult(true, status, "", headers, path);
}
}
WebSocketInitResult
WebSocketHandshake::serverHandshake(int timeoutSecs,
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate)
{
{
_requestInitCancellation = false;
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs,
_requestInitCancellation);
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
// Read first line
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) {
if (!lineValid)
{
return sendErrorResponse(400, "Error reading HTTP request line");
}
@@ -267,16 +263,15 @@ WebSocketInitResult
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 (method != "GET")
{
return sendErrorResponse(400, "Invalid HTTP method, need GET, got " + method);
}
if (httpVersion != "HTTP/1.1") {
if (httpVersion != "HTTP/1.1")
{
return sendErrorResponse(400,
"Invalid HTTP version, need HTTP/1.1, got: " +
httpVersion);
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
}
// Retrieve and validate HTTP headers
@@ -284,15 +279,18 @@ WebSocketInitResult
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid) {
if (!headersValid)
{
return sendErrorResponse(400, "Error parsing HTTP headers");
}
if (headers.find("sec-websocket-key") == headers.end()) {
if (headers.find("sec-websocket-key") == headers.end())
{
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
}
if (headers.find("upgrade") == headers.end()) {
if (headers.find("upgrade") == headers.end())
{
return sendErrorResponse(400, "Missing Upgrade header");
}
@@ -305,7 +303,8 @@ WebSocketInitResult
headers["upgrade"]);
}
if (headers.find("sec-websocket-version") == headers.end()) {
if (headers.find("sec-websocket-version") == headers.end())
{
return sendErrorResponse(400, "Missing Sec-WebSocket-Version value");
}
@@ -315,7 +314,8 @@ WebSocketInitResult
int version;
ss >> version;
if (version != 13) {
if (version != 13)
{
return sendErrorResponse(400,
"Invalid Sec-WebSocket-Version, "
"need 13, got " +
@@ -338,28 +338,26 @@ WebSocketInitResult
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
// If the client has requested that extension,
if (webSocketPerMessageDeflateOptions.enabled() &&
enablePerMessageDeflate) {
if (webSocketPerMessageDeflateOptions.enabled() && enablePerMessageDeflate)
{
_enablePerMessageDeflate = true;
if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions)) {
if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
{
return WebSocketInitResult(
false,
0,
"Failed to initialize per message deflate engine");
false, 0, "Failed to initialize per message deflate engine");
}
ss << webSocketPerMessageDeflateOptions.generateHeader();
}
ss << "\r\n";
if (!_socket->writeBytes(ss.str(), isCancellationRequested)) {
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(
false,
0,
std::string("Failed sending response to remote end"));
false, 0, std::string("Failed sending response to remote end"));
}
return WebSocketInitResult(true, 200, "", headers, uri);
}
}
} // namespace ix
+28 -25
View File
@@ -12,60 +12,63 @@
namespace ix
{
std::pair<bool, WebSocketHttpHeaders>
parseHttpHeaders(std::unique_ptr<Socket> &socket,
const CancellationRequest &isCancellationRequested)
{
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::unique_ptr<Socket>& socket, const CancellationRequest& isCancellationRequested)
{
WebSocketHttpHeaders headers;
char line[1024];
int i;
while (true) {
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)) {
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) {
if (line[i] == ':' && colon == 0)
{
colon = i;
}
}
if (line[0] == '\r' && line[1] == '\n') {
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) {
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
// 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] == ' ') {
while (lineStr[start] == ' ')
{
start++;
}
std::string name(lineStr.substr(0, colon));
std::string value(
lineStr.substr(start, lineStr.size() - start - 2));
std::string value(lineStr.substr(start, lineStr.size() - start - 2));
headers[name] = value;
}
}
return std::make_pair(true, headers);
}
}
} // namespace ix
+30 -27
View File
@@ -32,14 +32,12 @@
* (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.
* - 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 ?
* 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
@@ -56,38 +54,43 @@
namespace ix
{
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
: _compressor(ix::make_unique<WebSocketPerMessageDeflateCompressor>()),
_decompressor(ix::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
: _compressor(ix::make_unique<WebSocketPerMessageDeflateCompressor>())
, _decompressor(ix::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
;
}
}
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate() { ; }
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
{
;
}
bool WebSocketPerMessageDeflate::init(
const WebSocketPerMessageDeflateOptions &perMessageDeflateOptions)
{
bool clientNoContextTakeover =
perMessageDeflateOptions.getClientNoContextTakeover();
bool WebSocketPerMessageDeflate::init(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{
bool clientNoContextTakeover = perMessageDeflateOptions.getClientNoContextTakeover();
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
return _compressor->init(deflateBits, clientNoContextTakeover) &&
_decompressor->init(inflateBits, clientNoContextTakeover);
}
}
bool WebSocketPerMessageDeflate::compress(const std::string &in,
std::string &out)
{
bool WebSocketPerMessageDeflate::compress(const IXWebSocketSendData& in, std::string& out)
{
return _compressor->compress(in, out);
}
}
bool WebSocketPerMessageDeflate::decompress(const std::string &in,
std::string &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
@@ -12,19 +12,19 @@
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);
// 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()
{
//
// Compressor
//
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
memset(&_deflateState, 0, sizeof(_deflateState));
@@ -32,18 +32,18 @@ WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
_deflateState.zfree = Z_NULL;
_deflateState.opaque = Z_NULL;
#endif
}
}
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
{
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
deflateEnd(&_deflateState);
#endif
}
}
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
bool clientNoContextTakeOver)
{
{
#ifdef IXWEBSOCKET_USE_ZLIB
int ret = deflateInit2(&_deflateState,
Z_DEFAULT_COMPRESSION,
@@ -52,8 +52,7 @@ bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
return false;
if (ret != Z_OK) return false;
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
@@ -61,50 +60,51 @@ bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
#else
return false;
#endif
}
}
template <typename T>
bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(
const T &value)
{
if (kEmptyUncompressedBlock.size() > value.size())
return false;
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)
{
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out)
{
return compressData(in, out);
}
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string &in,
std::vector<uint8_t> &out)
{
bool WebSocketPerMessageDeflateCompressor::compress(const IXWebSocketSendData& in,
std::string& out)
{
return compressData(in, out);
}
}
bool WebSocketPerMessageDeflateCompressor::compress(
const std::vector<uint8_t> &in,
std::string &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::vector<uint8_t> &out)
{
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
std::string& out)
{
return compressData(in, out);
}
}
template <typename T, typename S>
bool WebSocketPerMessageDeflateCompressor::compressData(const T &in, S &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
@@ -128,7 +128,8 @@ bool WebSocketPerMessageDeflateCompressor::compressData(const T &in, S &out)
// Clear output
out.clear();
if (in.empty()) {
if (in.empty())
{
// See issue #167
// The normal buffer size should be 6 but
// we remove the 4 octets from the tail (#4)
@@ -139,24 +140,24 @@ bool WebSocketPerMessageDeflateCompressor::compressData(const T &in, S &out)
return true;
}
_deflateState.avail_in = (uInt)in.size();
_deflateState.next_in = (Bytef *)in.data();
_deflateState.avail_in = (uInt) in.size();
_deflateState.next_in = (Bytef*) in.data();
do {
do
{
// Output to local buffer
_deflateState.avail_out = (uInt)_compressBuffer.size();
_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);
out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output);
} while (_deflateState.avail_out == 0);
if (endsWithEmptyUnCompressedBlock(out)) {
if (endsWithEmptyUnCompressedBlock(out))
{
out.resize(out.size() - 4);
}
@@ -164,13 +165,13 @@ bool WebSocketPerMessageDeflateCompressor::compressData(const T &in, S &out)
#else
return false;
#endif
}
}
//
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
{
//
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
memset(&_inflateState, 0, sizeof(_inflateState));
@@ -180,24 +181,22 @@ WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
_inflateState.avail_in = 0;
_inflateState.next_in = Z_NULL;
#endif
}
}
WebSocketPerMessageDeflateDecompressor::
~WebSocketPerMessageDeflateDecompressor()
{
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
{
#ifdef IXWEBSOCKET_USE_ZLIB
inflateEnd(&_inflateState);
#endif
}
}
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool clientNoContextTakeOver)
{
{
#ifdef IXWEBSOCKET_USE_ZLIB
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
if (ret != Z_OK)
return false;
if (ret != Z_OK) return false;
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
@@ -205,11 +204,10 @@ bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
#else
return false;
#endif
}
}
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string &in,
std::string &out)
{
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
{
#ifdef IXWEBSOCKET_USE_ZLIB
//
// 7.2.2. Decompression
@@ -224,24 +222,25 @@ bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string &in,
std::string inFixed(in);
inFixed += kEmptyUncompressedBlock;
_inflateState.avail_in = (uInt)inFixed.size();
_inflateState.next_in =
(unsigned char *)(const_cast<char *>(inFixed.data()));
_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();
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) {
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
return false; // zlib error
}
out.append(reinterpret_cast<char *>(&_compressBuffer.front()),
out.append(reinterpret_cast<char*>(&_compressBuffer.front()),
_compressBuffer.size() - _inflateState.avail_out);
} while (_inflateState.avail_out == 0);
@@ -249,5 +248,5 @@ bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string &in,
#else
return false;
#endif
}
}
} // namespace ix
@@ -12,24 +12,22 @@
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;
/// 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;
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15;
static const uint8_t minClientMaxWindowBits = 8;
static const uint8_t maxClientMaxWindowBits = 15;
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
bool enabled,
bool clientNoContextTakeover,
bool serverNoContextTakeover,
uint8_t clientMaxWindowBits,
uint8_t serverMaxWindowBits)
{
{
_enabled = enabled;
_clientNoContextTakeover = clientNoContextTakeover;
_serverNoContextTakeover = serverNoContextTakeover;
@@ -37,25 +35,24 @@ WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
_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)
{
//
// 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;
@@ -69,72 +66,69 @@ WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
std::string token;
std::stringstream tokenStream(extension);
while (std::getline(tokenStream, token, ';')) {
if (token == "permessage-deflate") {
while (std::getline(tokenStream, token, ';'))
{
if (token == "permessage-deflate")
{
_enabled = true;
}
if (token == "server_no_context_takeover") {
if (token == "server_no_context_takeover")
{
_serverNoContextTakeover = true;
}
if (token == "client_no_context_takeover") {
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);
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));
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);
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));
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) {
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()
{
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";
if (_clientNoContextTakeover) ss << "; client_no_context_takeover";
if (_serverNoContextTakeover) ss << "; server_no_context_takeover";
ss << "; server_max_window_bits=" << _serverMaxWindowBits;
ss << "; client_max_window_bits=" << _clientMaxWindowBits;
ss << "; server_max_window_bits=" << static_cast<int>(_serverMaxWindowBits);
ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits);
ss << "\r\n";
@@ -142,52 +136,50 @@ std::string WebSocketPerMessageDeflateOptions::generateHeader()
#else
return std::string();
#endif
}
}
bool WebSocketPerMessageDeflateOptions::enabled() const
{
bool WebSocketPerMessageDeflateOptions::enabled() const
{
#ifdef IXWEBSOCKET_USE_ZLIB
return _enabled;
#else
return false;
#endif
}
}
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
{
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
{
return _clientNoContextTakeover;
}
}
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
{
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
{
return _serverNoContextTakeover;
}
}
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
{
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
{
return _clientMaxWindowBits;
}
}
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
{
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
{
return _serverMaxWindowBits;
}
}
bool WebSocketPerMessageDeflateOptions::startsWith(const std::string &str,
const std::string &start)
{
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 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.erase(
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
out.end());
return out;
}
}
} // namespace ix

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