Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92159b0669 | |||
| 115e4d9265 | |||
| 07487e9e51 | |||
| 46a0607b55 | |||
| de8b282ab1 | |||
| 6027095c38 | |||
| 2cf03d2933 | |||
| 161941ac1b | |||
| 4445f1f12a | |||
| d6d3c0b4d9 | |||
| 94fbc1f558 | |||
| 2fc2170763 | |||
| 699e2a4784 | |||
| b050c23577 | |||
| a5e23f4b43 | |||
| 7d45ca71ba | |||
| 8b9f5d0406 | |||
| 2bda541bce | |||
| 25c4d8474d | |||
| 910afad3b5 | |||
| 2aa658c15e | |||
| 3559b124a7 | |||
| 2510f4968c | |||
| 752646c744 | |||
| b22a60c85e | |||
| 9dcca746ed | |||
| 0612afb37b | |||
| ff179ee3a4 | |||
| cd022c8063 | |||
| a536a9c918 | |||
| 3dce2ca488 | |||
| b695f4cb6c | |||
| 2914e1fa59 | |||
| 71656e4a66 | |||
| 5e80c41538 | |||
| c6ad412df9 | |||
| 0fe7335987 | |||
| 3b2f146679 | |||
| a820f7456a | |||
| fbee417594 | |||
| 7439747876 | |||
| 27edf28f42 | |||
| ca5aab1344 |
+5
-4
@@ -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
|
||||
|
||||
Generated
-8
@@ -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
@@ -1 +0,0 @@
|
||||
GermanAirlinesVA_GAConnector
|
||||
Generated
-2
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
-4
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
</project>
|
||||
Generated
-8
@@ -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
@@ -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>
|
||||
Vendored
+38
-5
@@ -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
|
||||
|
||||
Vendored
+17
@@ -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"
|
||||
}
|
||||
]
|
||||
Vendored
-34
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+26
-3
@@ -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"
|
||||
},
|
||||
}
|
||||
Vendored
-28
@@ -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
@@ -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
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,6 @@
|
||||
|
||||
X-Plane Plugin for all supported platforms.
|
||||
Demo of WebSocket capabilities
|
||||
|
||||
Requirements:
|
||||
- openSSL 1.1.x
|
||||
@@ -0,0 +1,2 @@
|
||||
- Update OSXCross Docker image to SDK 11
|
||||
- Implement ARM64 arch for Plugin
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
Vendored
+21
@@ -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}"
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -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"];
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
+39
-34
@@ -10,47 +10,52 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
Bench::Bench(const std::string &description) : _description(description)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
Bench::~Bench()
|
||||
{
|
||||
if (!_reported) {
|
||||
report();
|
||||
Bench::Bench(const std::string& description)
|
||||
: _description(description)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Bench::reset()
|
||||
{
|
||||
_start = std::chrono::high_resolution_clock::now();
|
||||
_reported = false;
|
||||
}
|
||||
Bench::~Bench()
|
||||
{
|
||||
if (!_reported)
|
||||
{
|
||||
report();
|
||||
}
|
||||
}
|
||||
|
||||
void Bench::report()
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto microseconds =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
||||
void Bench::reset()
|
||||
{
|
||||
_start = std::chrono::high_resolution_clock::now();
|
||||
_reported = false;
|
||||
}
|
||||
|
||||
_duration = microseconds.count();
|
||||
std::cerr << _description << " completed in " << _duration << " us"
|
||||
<< std::endl;
|
||||
void Bench::report()
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
||||
|
||||
setReported();
|
||||
}
|
||||
_duration = microseconds.count();
|
||||
std::cerr << _description << " completed in " << _duration << " us" << std::endl;
|
||||
|
||||
void Bench::record()
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto microseconds =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
||||
setReported();
|
||||
}
|
||||
|
||||
_duration = microseconds.count();
|
||||
}
|
||||
void Bench::record()
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
||||
|
||||
void Bench::setReported() { _reported = true; }
|
||||
_duration = microseconds.count();
|
||||
}
|
||||
|
||||
uint64_t Bench::getDuration() const { return _duration; }
|
||||
void Bench::setReported()
|
||||
{
|
||||
_reported = true;
|
||||
}
|
||||
|
||||
uint64_t Bench::getDuration() const
|
||||
{
|
||||
return _duration;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -11,29 +11,25 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
CancellationRequest makeCancellationRequestWithTimeout(
|
||||
int secs,
|
||||
std::atomic<bool> &requestInitCancellation)
|
||||
{
|
||||
assert(secs > 0);
|
||||
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 start = std::chrono::system_clock::now();
|
||||
auto timeout = std::chrono::seconds(secs);
|
||||
|
||||
auto isCancellationRequested =
|
||||
[&requestInitCancellation, start, timeout]() -> bool {
|
||||
// Was an explicit cancellation requested ?
|
||||
if (requestInitCancellation)
|
||||
return true;
|
||||
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool {
|
||||
// Was an explicit cancellation requested ?
|
||||
if (requestInitCancellation) return true;
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if ((now - start) > timeout)
|
||||
return true;
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if ((now - start) > timeout) return true;
|
||||
|
||||
// No cancellation request
|
||||
return false;
|
||||
};
|
||||
// No cancellation request
|
||||
return false;
|
||||
};
|
||||
|
||||
return isCancellationRequested;
|
||||
}
|
||||
return isCancellationRequested;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
void ConnectionState::computeId() { _id = std::to_string(_globalId++); }
|
||||
|
||||
const std::string &ConnectionState::getId() const { return _id; }
|
||||
|
||||
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
|
||||
{
|
||||
return std::make_shared<ConnectionState>();
|
||||
}
|
||||
|
||||
void ConnectionState::setOnSetTerminatedCallback(
|
||||
const OnSetTerminatedCallback &callback)
|
||||
{
|
||||
_onSetTerminatedCallback = callback;
|
||||
}
|
||||
|
||||
bool ConnectionState::isTerminated() const { return _terminated; }
|
||||
|
||||
void ConnectionState::setTerminated()
|
||||
{
|
||||
_terminated = true;
|
||||
|
||||
if (_onSetTerminatedCallback) {
|
||||
_onSetTerminatedCallback();
|
||||
ConnectionState::ConnectionState()
|
||||
: _terminated(false)
|
||||
{
|
||||
computeId();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &ConnectionState::getRemoteIp() { return _remoteIp; }
|
||||
void ConnectionState::computeId()
|
||||
{
|
||||
_id = std::to_string(_globalId++);
|
||||
}
|
||||
|
||||
int ConnectionState::getRemotePort() { return _remotePort; }
|
||||
const std::string& ConnectionState::getId() const
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
void ConnectionState::setRemoteIp(const std::string &remoteIp)
|
||||
{
|
||||
_remoteIp = remoteIp;
|
||||
}
|
||||
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
|
||||
{
|
||||
return std::make_shared<ConnectionState>();
|
||||
}
|
||||
|
||||
void ConnectionState::setRemotePort(int remotePort)
|
||||
{
|
||||
_remotePort = remotePort;
|
||||
}
|
||||
void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback)
|
||||
{
|
||||
_onSetTerminatedCallback = callback;
|
||||
}
|
||||
|
||||
bool ConnectionState::isTerminated() const
|
||||
{
|
||||
return _terminated;
|
||||
}
|
||||
|
||||
void ConnectionState::setTerminated()
|
||||
{
|
||||
_terminated = true;
|
||||
|
||||
if (_onSetTerminatedCallback)
|
||||
{
|
||||
_onSetTerminatedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& ConnectionState::getRemoteIp()
|
||||
{
|
||||
return _remoteIp;
|
||||
}
|
||||
|
||||
int ConnectionState::getRemotePort()
|
||||
{
|
||||
return _remotePort;
|
||||
}
|
||||
|
||||
void ConnectionState::setRemoteIp(const std::string& remoteIp)
|
||||
{
|
||||
_remoteIp = remoteIp;
|
||||
}
|
||||
|
||||
void ConnectionState::setRemotePort(int remotePort)
|
||||
{
|
||||
_remotePort = remotePort;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+140
-135
@@ -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,159 +32,164 @@
|
||||
|
||||
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)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
struct addrinfo *DNSLookup::getAddrInfo(const std::string &hostname,
|
||||
int port,
|
||||
std::string &errMsg)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
std::string sport = std::to_string(port);
|
||||
|
||||
struct addrinfo *res;
|
||||
int getaddrinfo_result =
|
||||
getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res);
|
||||
if (getaddrinfo_result) {
|
||||
errMsg = gai_strerror(getaddrinfo_result);
|
||||
res = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
struct addrinfo *
|
||||
DNSLookup::resolve(std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested,
|
||||
bool cancellable)
|
||||
{
|
||||
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
|
||||
: resolveUnCancellable(errMsg, isCancellationRequested);
|
||||
}
|
||||
|
||||
void DNSLookup::release(struct addrinfo *addr) { freeaddrinfo(addr); }
|
||||
|
||||
struct addrinfo *DNSLookup::resolveUnCancellable(
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
// Maybe a cancellation request got in before the background thread
|
||||
// terminated ?
|
||||
if (isCancellationRequested()) {
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait)
|
||||
: _hostname(hostname)
|
||||
, _port(port)
|
||||
, _wait(wait)
|
||||
, _res(nullptr)
|
||||
, _done(false)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
return getAddrInfo(_hostname, _port, errMsg);
|
||||
}
|
||||
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
||||
int port,
|
||||
std::string& errMsg)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
struct addrinfo *DNSLookup::resolveCancellable(
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
std::string sport = std::to_string(port);
|
||||
|
||||
// Can only be called once, otherwise we would have to manage a pool
|
||||
// of background thread which is overkill for our usage.
|
||||
if (_done) {
|
||||
return nullptr; // programming error, create a second DNSLookup instance
|
||||
// if you need a second lookup.
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// Good resource on thread forced termination
|
||||
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
||||
//
|
||||
auto ptr = shared_from_this();
|
||||
std::weak_ptr<DNSLookup> self(ptr);
|
||||
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested,
|
||||
bool cancellable)
|
||||
{
|
||||
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
|
||||
: resolveUnCancellable(errMsg, isCancellationRequested);
|
||||
}
|
||||
|
||||
int port = _port;
|
||||
std::string hostname(_hostname);
|
||||
void DNSLookup::release(struct addrinfo* addr)
|
||||
{
|
||||
freeaddrinfo(addr);
|
||||
}
|
||||
|
||||
// We make the background thread doing the work a shared pointer
|
||||
// instead of a member variable, because it can keep running when
|
||||
// this object goes out of scope, in case of cancellation
|
||||
auto t = std::make_shared<std::thread>(&DNSLookup::run,
|
||||
this,
|
||||
self,
|
||||
hostname,
|
||||
port);
|
||||
t->detach();
|
||||
struct addrinfo* DNSLookup::resolveUnCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
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()) {
|
||||
// Maybe a cancellation request got in before the background thread terminated ?
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return getAddrInfo(_hostname, _port, errMsg);
|
||||
}
|
||||
|
||||
// Maybe a cancellation request got in before the bg terminated ?
|
||||
if (isCancellationRequested()) {
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
struct addrinfo* DNSLookup::resolveCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
// Can only be called once, otherwise we would have to manage a pool
|
||||
// of background thread which is overkill for our usage.
|
||||
if (_done)
|
||||
{
|
||||
return nullptr; // programming error, create a second DNSLookup instance
|
||||
// if you need a second lookup.
|
||||
}
|
||||
|
||||
//
|
||||
// Good resource on thread forced termination
|
||||
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
||||
//
|
||||
auto ptr = shared_from_this();
|
||||
std::weak_ptr<DNSLookup> self(ptr);
|
||||
|
||||
int port = _port;
|
||||
std::string hostname(_hostname);
|
||||
|
||||
// We make the background thread doing the work a shared pointer
|
||||
// instead of a member variable, because it can keep running when
|
||||
// this object goes out of scope, in case of cancellation
|
||||
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
|
||||
t->detach();
|
||||
|
||||
while (!_done)
|
||||
{
|
||||
// Wait for 1 milliseconds, to see if the bg thread has terminated.
|
||||
// We do not use a condition variable to wait, as destroying this one
|
||||
// if the bg thread is alive can cause undefined behavior.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
|
||||
|
||||
// Were we cancelled ?
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe a cancellation request got in before the bg terminated ?
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
errMsg = getErrMsg();
|
||||
return getRes();
|
||||
}
|
||||
|
||||
errMsg = getErrMsg();
|
||||
return getRes();
|
||||
}
|
||||
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
|
||||
std::string hostname,
|
||||
int port) // thread runner
|
||||
{
|
||||
// We don't want to read or write into members variables of an object that could be
|
||||
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
||||
// getAddrInfo needs to work.
|
||||
std::string errMsg;
|
||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
||||
|
||||
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
|
||||
std::string hostname,
|
||||
int port) // thread runner
|
||||
{
|
||||
// We don't want to read or write into members variables of an object that
|
||||
// could be gone, so we use temporary variables (res) or we pass in by copy
|
||||
// everything that getAddrInfo needs to work.
|
||||
std::string errMsg;
|
||||
struct addrinfo *res = getAddrInfo(hostname, port, errMsg);
|
||||
if (auto lock = self.lock())
|
||||
{
|
||||
// Copy result into the member variables
|
||||
setRes(res);
|
||||
setErrMsg(errMsg);
|
||||
|
||||
if (auto lock = self.lock()) {
|
||||
// Copy result into the member variables
|
||||
setRes(res);
|
||||
setErrMsg(errMsg);
|
||||
|
||||
_done = true;
|
||||
_done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DNSLookup::setErrMsg(const std::string &errMsg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
||||
_errMsg = errMsg;
|
||||
}
|
||||
void DNSLookup::setErrMsg(const std::string& errMsg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
||||
_errMsg = errMsg;
|
||||
}
|
||||
|
||||
const std::string &DNSLookup::getErrMsg()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
||||
return _errMsg;
|
||||
}
|
||||
const std::string& DNSLookup::getErrMsg()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
||||
return _errMsg;
|
||||
}
|
||||
|
||||
void DNSLookup::setRes(struct addrinfo *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_resMutex);
|
||||
_res = addr;
|
||||
}
|
||||
void DNSLookup::setRes(struct addrinfo* addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_resMutex);
|
||||
_res = addr;
|
||||
}
|
||||
|
||||
struct addrinfo *DNSLookup::getRes()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_resMutex);
|
||||
return _res;
|
||||
}
|
||||
struct addrinfo* DNSLookup::getRes()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_resMutex);
|
||||
return _res;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -10,21 +10,35 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
uint32_t
|
||||
calculateRetryWaitMilliseconds(uint32_t retryCount,
|
||||
uint32_t maxWaitBetweenReconnectionRetries,
|
||||
uint32_t minWaitBetweenReconnectionRetries)
|
||||
{
|
||||
uint32_t waitTime = (retryCount < 26) ? (std::pow(2, retryCount) * 100) : 0;
|
||||
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
|
||||
uint32_t maxWaitBetweenReconnectionRetries,
|
||||
uint32_t minWaitBetweenReconnectionRetries)
|
||||
{
|
||||
// It's easy with a power function to go beyond 2^32, and then
|
||||
// have unexpected results, so prepare for that
|
||||
const uint32_t maxRetryCountWithoutOverflow = 26;
|
||||
|
||||
if (waitTime < minWaitBetweenReconnectionRetries) {
|
||||
waitTime = minWaitBetweenReconnectionRetries;
|
||||
uint32_t waitTime = 0;
|
||||
if (retryCount < maxRetryCountWithoutOverflow)
|
||||
{
|
||||
waitTime = std::pow(2, retryCount) * 100;
|
||||
}
|
||||
|
||||
if (waitTime < minWaitBetweenReconnectionRetries)
|
||||
{
|
||||
waitTime = minWaitBetweenReconnectionRetries;
|
||||
}
|
||||
|
||||
if (waitTime > maxWaitBetweenReconnectionRetries)
|
||||
{
|
||||
waitTime = maxWaitBetweenReconnectionRetries;
|
||||
}
|
||||
|
||||
if (retryCount >= maxRetryCountWithoutOverflow)
|
||||
{
|
||||
waitTime = maxWaitBetweenReconnectionRetries;
|
||||
}
|
||||
|
||||
return waitTime;
|
||||
}
|
||||
|
||||
if (waitTime > maxWaitBetweenReconnectionRetries || waitTime == 0) {
|
||||
waitTime = maxWaitBetweenReconnectionRetries;
|
||||
}
|
||||
|
||||
return waitTime;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -21,75 +21,77 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int getAnyFreePortRandom()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
|
||||
int getAnyFreePortRandom()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
|
||||
|
||||
return dist(rd);
|
||||
}
|
||||
|
||||
int getAnyFreePort()
|
||||
{
|
||||
socket_t sockfd;
|
||||
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
return getAnyFreePortRandom();
|
||||
return dist(rd);
|
||||
}
|
||||
|
||||
int enable = 1;
|
||||
if (setsockopt(sockfd,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
(char *)&enable,
|
||||
sizeof(enable)) < 0) {
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
int getAnyFreePort()
|
||||
{
|
||||
socket_t sockfd;
|
||||
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
// Bind to port 0. This is the standard way to get a free port.
|
||||
struct sockaddr_in server; // server address information
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(0);
|
||||
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
int enable = 1;
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
|
||||
{
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) {
|
||||
// Bind to port 0. This is the standard way to get a free port.
|
||||
struct sockaddr_in server; // server address information
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(0);
|
||||
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
|
||||
if (bind(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0)
|
||||
{
|
||||
Socket::closeSocket(sockfd);
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
struct sockaddr_in sa; // server address information
|
||||
socklen_t len = sizeof(sa);
|
||||
if (getsockname(sockfd, (struct sockaddr*) &sa, &len) < 0)
|
||||
{
|
||||
Socket::closeSocket(sockfd);
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
int port = ntohs(sa.sin_port);
|
||||
Socket::closeSocket(sockfd);
|
||||
return getAnyFreePortRandom();
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
struct sockaddr_in sa; // server address information
|
||||
socklen_t len = sizeof(sa);
|
||||
if (getsockname(sockfd, (struct sockaddr *)&sa, &len) < 0) {
|
||||
Socket::closeSocket(sockfd);
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
int port = ntohs(sa.sin_port);
|
||||
Socket::closeSocket(sockfd);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
int getFreePort()
|
||||
{
|
||||
while (true) {
|
||||
int getFreePort()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(address_sanitizer)
|
||||
int port = getAnyFreePortRandom();
|
||||
int port = getAnyFreePortRandom();
|
||||
#else
|
||||
int port = getAnyFreePort();
|
||||
int port = getAnyFreePort();
|
||||
#endif
|
||||
#else
|
||||
int port = getAnyFreePort();
|
||||
int port = getAnyFreePort();
|
||||
#endif
|
||||
//
|
||||
// Only port above 1024 can be used by non root users, but for some
|
||||
// reason I got port 7 returned with macOS when binding on port 0...
|
||||
//
|
||||
if (port > 1024) {
|
||||
return port;
|
||||
//
|
||||
// Only port above 1024 can be used by non root users, but for some
|
||||
// reason I got port 7 returned with macOS when binding on port 0...
|
||||
//
|
||||
if (port > 1024)
|
||||
{
|
||||
return port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+75
-141
@@ -14,173 +14,107 @@
|
||||
#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));
|
||||
z_stream zs; // z_stream is zlib's control structure
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
|
||||
// deflateInit2 configure the file format: request gzip instead of deflate
|
||||
const int windowBits = 15;
|
||||
const int GZIP_ENCODING = 16;
|
||||
// deflateInit2 configure the file format: request gzip instead of deflate
|
||||
const int windowBits = 15;
|
||||
const int GZIP_ENCODING = 16;
|
||||
|
||||
deflateInit2(&zs,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
windowBits | GZIP_ENCODING,
|
||||
8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
deflateInit2(&zs,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
windowBits | GZIP_ENCODING,
|
||||
8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
|
||||
zs.next_in = (Bytef *)str.data();
|
||||
zs.avail_in = (uInt)str.size(); // set the z_stream's input
|
||||
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;
|
||||
int ret;
|
||||
char outbuffer[32768];
|
||||
std::string outstring;
|
||||
|
||||
// retrieve the compressed bytes blockwise
|
||||
do {
|
||||
zs.next_out = reinterpret_cast<Bytef *>(outbuffer);
|
||||
zs.avail_out = sizeof(outbuffer);
|
||||
// retrieve the compressed bytes blockwise
|
||||
do
|
||||
{
|
||||
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
|
||||
zs.avail_out = sizeof(outbuffer);
|
||||
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (outstring.size() < zs.total_out) {
|
||||
// append the block to the output string
|
||||
outstring.append(outbuffer, zs.total_out - outstring.size());
|
||||
}
|
||||
} while (ret == Z_OK);
|
||||
if (outstring.size() < zs.total_out)
|
||||
{
|
||||
// append the block to the output string
|
||||
outstring.append(outbuffer, zs.total_out - outstring.size());
|
||||
}
|
||||
} while (ret == Z_OK);
|
||||
|
||||
deflateEnd(&zs);
|
||||
deflateEnd(&zs);
|
||||
|
||||
return outstring;
|
||||
#endif // IXWEBSOCKET_USE_DEFLATE
|
||||
return outstring;
|
||||
#endif // IXWEBSOCKET_USE_ZLIB
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
||||
static uint32_t loadDecompressedGzipSize(const uint8_t *p)
|
||||
{
|
||||
return ((uint32_t)p[0] << 0) | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
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));
|
||||
|
||||
inflateState.zalloc = Z_NULL;
|
||||
inflateState.zfree = Z_NULL;
|
||||
inflateState.opaque = Z_NULL;
|
||||
inflateState.avail_in = 0;
|
||||
inflateState.next_in = Z_NULL;
|
||||
|
||||
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
z_stream inflateState;
|
||||
memset(&inflateState, 0, sizeof(inflateState));
|
||||
|
||||
inflateState.avail_in = (uInt)in.size();
|
||||
inflateState.next_in = (unsigned char *)(const_cast<char *>(in.data()));
|
||||
inflateState.zalloc = Z_NULL;
|
||||
inflateState.zfree = Z_NULL;
|
||||
inflateState.opaque = Z_NULL;
|
||||
inflateState.avail_in = 0;
|
||||
inflateState.next_in = Z_NULL;
|
||||
|
||||
const int kBufferSize = 1 << 14;
|
||||
std::array<unsigned char, kBufferSize> compressBuffer;
|
||||
|
||||
do {
|
||||
inflateState.avail_out = (uInt)kBufferSize;
|
||||
inflateState.next_out = &compressBuffer.front();
|
||||
|
||||
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
|
||||
|
||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
|
||||
inflateEnd(&inflateState);
|
||||
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
out.append(reinterpret_cast<char *>(&compressBuffer.front()),
|
||||
kBufferSize - inflateState.avail_out);
|
||||
} while (inflateState.avail_out == 0);
|
||||
inflateState.avail_in = (uInt) in.size();
|
||||
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
|
||||
|
||||
inflateEnd(&inflateState);
|
||||
return true;
|
||||
#endif // IXWEBSOCKET_USE_DEFLATE
|
||||
const int kBufferSize = 1 << 14;
|
||||
std::array<unsigned char, kBufferSize> compressBuffer;
|
||||
|
||||
do
|
||||
{
|
||||
inflateState.avail_out = (uInt) kBufferSize;
|
||||
inflateState.next_out = &compressBuffer.front();
|
||||
|
||||
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
|
||||
|
||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
||||
{
|
||||
inflateEnd(&inflateState);
|
||||
return false;
|
||||
}
|
||||
|
||||
out.append(reinterpret_cast<char*>(&compressBuffer.front()),
|
||||
kBufferSize - inflateState.avail_out);
|
||||
} while (inflateState.avail_out == 0);
|
||||
|
||||
inflateEnd(&inflateState);
|
||||
return true;
|
||||
#endif // IXWEBSOCKET_USE_ZLIB
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+184
-179
@@ -14,195 +14,200 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
std::string Http::trim(const std::string &str)
|
||||
{
|
||||
std::string out;
|
||||
for (auto c : str) {
|
||||
if (c != ' ' && c != '\n' && c != '\r') {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<std::string, int> Http::parseStatusLine(const std::string &line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' ')) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 1) {
|
||||
httpVersion = trim(tokens[0]);
|
||||
}
|
||||
|
||||
int statusCode = -1;
|
||||
if (tokens.size() >= 2) {
|
||||
std::stringstream ss;
|
||||
ss << trim(tokens[1]);
|
||||
ss >> statusCode;
|
||||
}
|
||||
|
||||
return std::make_pair(httpVersion, statusCode);
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string>
|
||||
Http::parseRequestLine(const std::string &line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' ')) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string method;
|
||||
if (tokens.size() >= 1) {
|
||||
method = trim(tokens[0]);
|
||||
}
|
||||
|
||||
std::string requestUri;
|
||||
if (tokens.size() >= 2) {
|
||||
requestUri = trim(tokens[1]);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 3) {
|
||||
httpVersion = trim(tokens[2]);
|
||||
}
|
||||
|
||||
return std::make_tuple(method, requestUri, httpVersion);
|
||||
}
|
||||
|
||||
std::tuple<bool, std::string, HttpRequestPtr>
|
||||
Http::parseRequest(std::unique_ptr<Socket> &socket, int timeoutSecs)
|
||||
{
|
||||
HttpRequestPtr httpRequest;
|
||||
|
||||
std::atomic<bool> requestInitCancellation(false);
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs,
|
||||
requestInitCancellation);
|
||||
|
||||
// Read first line
|
||||
auto lineResult = socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid) {
|
||||
return std::make_tuple(false,
|
||||
"Error reading HTTP request line",
|
||||
httpRequest);
|
||||
}
|
||||
|
||||
// Parse request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid) {
|
||||
return std::make_tuple(false,
|
||||
"Error parsing HTTP headers",
|
||||
httpRequest);
|
||||
}
|
||||
|
||||
std::string body;
|
||||
if (headers.find("Content-Length") != headers.end()) {
|
||||
int contentLength = 0;
|
||||
try {
|
||||
contentLength = std::stoi(headers["Content-Length"]);
|
||||
} catch (const std::exception &) {
|
||||
return std::make_tuple(false,
|
||||
"Error parsing HTTP Header 'Content-Length'",
|
||||
httpRequest);
|
||||
std::string Http::trim(const std::string& str)
|
||||
{
|
||||
std::string out;
|
||||
for (auto c : str)
|
||||
{
|
||||
if (c != ' ' && c != '\n' && c != '\r')
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (contentLength < 0) {
|
||||
return std::make_tuple(
|
||||
false,
|
||||
"Error: 'Content-Length' should be a positive integer",
|
||||
httpRequest);
|
||||
}
|
||||
|
||||
auto res =
|
||||
socket->readBytes(contentLength, nullptr, isCancellationRequested);
|
||||
if (!res.first) {
|
||||
return std::make_tuple(false,
|
||||
std::string("Error reading request: ") +
|
||||
res.second,
|
||||
httpRequest);
|
||||
}
|
||||
body = res.second;
|
||||
return out;
|
||||
}
|
||||
|
||||
// If the content was compressed with gzip, decode it
|
||||
if (headers["Content-Encoding"] == "gzip") {
|
||||
std::pair<std::string, int> Http::parseStatusLine(const std::string& line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' '))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 1)
|
||||
{
|
||||
httpVersion = trim(tokens[0]);
|
||||
}
|
||||
|
||||
int statusCode = -1;
|
||||
if (tokens.size() >= 2)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << trim(tokens[1]);
|
||||
ss >> statusCode;
|
||||
}
|
||||
|
||||
return std::make_pair(httpVersion, statusCode);
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(
|
||||
const std::string& line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' '))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string method;
|
||||
if (tokens.size() >= 1)
|
||||
{
|
||||
method = trim(tokens[0]);
|
||||
}
|
||||
|
||||
std::string requestUri;
|
||||
if (tokens.size() >= 2)
|
||||
{
|
||||
requestUri = trim(tokens[1]);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 3)
|
||||
{
|
||||
httpVersion = trim(tokens[2]);
|
||||
}
|
||||
|
||||
return std::make_tuple(method, requestUri, httpVersion);
|
||||
}
|
||||
|
||||
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
|
||||
std::unique_ptr<Socket>& socket, int timeoutSecs)
|
||||
{
|
||||
HttpRequestPtr httpRequest;
|
||||
|
||||
std::atomic<bool> requestInitCancellation(false);
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
|
||||
|
||||
// Read first line
|
||||
auto lineResult = socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid)
|
||||
{
|
||||
return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
|
||||
}
|
||||
|
||||
// Parse request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid)
|
||||
{
|
||||
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
|
||||
}
|
||||
|
||||
std::string body;
|
||||
if (headers.find("Content-Length") != headers.end())
|
||||
{
|
||||
int contentLength = 0;
|
||||
try
|
||||
{
|
||||
contentLength = std::stoi(headers["Content-Length"]);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
return std::make_tuple(
|
||||
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
||||
}
|
||||
|
||||
if (contentLength < 0)
|
||||
{
|
||||
return std::make_tuple(
|
||||
false, "Error: 'Content-Length' should be a positive integer", httpRequest);
|
||||
}
|
||||
|
||||
auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested);
|
||||
if (!res.first)
|
||||
{
|
||||
return std::make_tuple(
|
||||
false, std::string("Error reading request: ") + res.second, httpRequest);
|
||||
}
|
||||
body = res.second;
|
||||
}
|
||||
|
||||
// If the content was compressed with gzip, decode it
|
||||
if (headers["Content-Encoding"] == "gzip")
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::string decompressedPayload;
|
||||
if (!gzipDecompress(body, decompressedPayload)) {
|
||||
return std::make_tuple(
|
||||
false,
|
||||
std::string("Error during gzip decompression of the body"),
|
||||
httpRequest);
|
||||
}
|
||||
body = decompressedPayload;
|
||||
std::string decompressedPayload;
|
||||
if (!gzipDecompress(body, decompressedPayload))
|
||||
{
|
||||
return std::make_tuple(
|
||||
false, std::string("Error during gzip decompression of the body"), httpRequest);
|
||||
}
|
||||
body = decompressedPayload;
|
||||
#else
|
||||
std::string errorMsg(
|
||||
"ixwebsocket was not compiled with gzip support on");
|
||||
return std::make_tuple(false, errorMsg, httpRequest);
|
||||
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
|
||||
return std::make_tuple(false, errorMsg, httpRequest);
|
||||
#endif
|
||||
}
|
||||
|
||||
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers);
|
||||
return std::make_tuple(true, "", httpRequest);
|
||||
}
|
||||
|
||||
httpRequest =
|
||||
std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers);
|
||||
return std::make_tuple(true, "", httpRequest);
|
||||
}
|
||||
bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket)
|
||||
{
|
||||
// Write the response to the socket
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 ";
|
||||
ss << response->statusCode;
|
||||
ss << " ";
|
||||
ss << response->description;
|
||||
ss << "\r\n";
|
||||
|
||||
bool Http::sendResponse(HttpResponsePtr response,
|
||||
std::unique_ptr<Socket> &socket)
|
||||
{
|
||||
// Write the response to the socket
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 ";
|
||||
ss << response->statusCode;
|
||||
ss << " ";
|
||||
ss << response->description;
|
||||
ss << "\r\n";
|
||||
if (!socket->writeBytes(ss.str(), nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!socket->writeBytes(ss.str(), nullptr)) {
|
||||
return false;
|
||||
// Write headers
|
||||
ss.str("");
|
||||
ss << "Content-Length: " << response->body.size() << "\r\n";
|
||||
for (auto&& it : response->headers)
|
||||
{
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
ss << "\r\n";
|
||||
|
||||
if (!socket->writeBytes(ss.str(), nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return response->body.empty() ? true : socket->writeBytes(response->body, nullptr);
|
||||
}
|
||||
|
||||
// Write headers
|
||||
ss.str("");
|
||||
ss << "Content-Length: " << response->body.size() << "\r\n";
|
||||
for (auto &&it : response->headers) {
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
ss << "\r\n";
|
||||
|
||||
if (!socket->writeBytes(ss.str(), nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return response->body.empty() ? true
|
||||
: socket->writeBytes(response->body, nullptr);
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+690
-642
File diff suppressed because it is too large
Load Diff
+181
-185
@@ -17,223 +17,219 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
std::pair<bool, std::vector<uint8_t>> load(const std::string &path)
|
||||
{
|
||||
std::vector<uint8_t> memblock;
|
||||
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);
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) return std::make_pair(false, memblock);
|
||||
|
||||
file.seekg(0, file.end);
|
||||
std::streamoff size = file.tellg();
|
||||
file.seekg(0, file.beg);
|
||||
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);
|
||||
}
|
||||
return std::make_pair(true, memblock);
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> readAsString(const std::string &path)
|
||||
{
|
||||
auto res = load(path);
|
||||
auto vec = res.second;
|
||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||
}
|
||||
std::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,
|
||||
int backlog,
|
||||
size_t maxConnections,
|
||||
int addressFamily,
|
||||
int timeoutSecs)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily),
|
||||
_connectedClientsCount(0), _timeoutSecs(timeoutSecs)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer() { stop(); }
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
stopAcceptingConnections();
|
||||
|
||||
// FIXME: cancelling / closing active clients ...
|
||||
|
||||
SocketServer::stop();
|
||||
}
|
||||
|
||||
void HttpServer::setOnConnectionCallback(const OnConnectionCallback &callback)
|
||||
{
|
||||
_onConnectionCallback = callback;
|
||||
}
|
||||
|
||||
void HttpServer::handleConnection(
|
||||
std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
||||
// FIXME: handle errors in parseRequest
|
||||
|
||||
if (std::get<0>(ret)) {
|
||||
auto response =
|
||||
_onConnectionCallback(std::get<2>(ret), connectionState);
|
||||
if (!Http::sendResponse(response, socket)) {
|
||||
logError("Cannot send response");
|
||||
}
|
||||
HttpServer::HttpServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections,
|
||||
int addressFamily,
|
||||
int timeoutSecs)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _connectedClientsCount(0)
|
||||
, _timeoutSecs(timeoutSecs)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
}
|
||||
connectionState->setTerminated();
|
||||
|
||||
_connectedClientsCount--;
|
||||
}
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
size_t HttpServer::getConnectedClientsCount() { return _connectedClientsCount; }
|
||||
void HttpServer::stop()
|
||||
{
|
||||
stopAcceptingConnections();
|
||||
|
||||
void HttpServer::setDefaultConnectionCallback()
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
-> HttpResponsePtr {
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/") {
|
||||
uri = "/index.html";
|
||||
// FIXME: cancelling / closing active clients ...
|
||||
|
||||
SocketServer::stop();
|
||||
}
|
||||
|
||||
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
||||
{
|
||||
_onConnectionCallback = callback;
|
||||
}
|
||||
|
||||
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
||||
// FIXME: handle errors in parseRequest
|
||||
|
||||
if (std::get<0>(ret))
|
||||
{
|
||||
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||
if (!Http::sendResponse(response, socket))
|
||||
{
|
||||
logError("Cannot send response");
|
||||
}
|
||||
}
|
||||
connectionState->setTerminated();
|
||||
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
_connectedClientsCount--;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
size_t HttpServer::getConnectedClientsCount()
|
||||
{
|
||||
return _connectedClientsCount;
|
||||
}
|
||||
|
||||
std::string content = res.second;
|
||||
void HttpServer::setDefaultConnectionCallback()
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/")
|
||||
{
|
||||
uri = "/index.html";
|
||||
}
|
||||
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
std::string path("." + uri);
|
||||
auto res = readAsString(path);
|
||||
bool found = res.first;
|
||||
if (!found)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(
|
||||
404, "Not Found", HttpErrorCode::Ok, WebSocketHttpHeaders(), std::string());
|
||||
}
|
||||
|
||||
std::string content = res.second;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::string acceptEncoding = request->headers["Accept-encoding"];
|
||||
if (acceptEncoding == "*" ||
|
||||
acceptEncoding.find("gzip") != std::string::npos) {
|
||||
content = gzipCompress(content);
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
}
|
||||
std::string acceptEncoding = request->headers["Accept-encoding"];
|
||||
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
|
||||
{
|
||||
content = gzipCompress(content);
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionState->getRemoteIp() << ":"
|
||||
<< connectionState->getRemotePort() << " " << request->method
|
||||
<< " " << request->headers["User-Agent"] << " " << request->uri
|
||||
<< " " << content.size();
|
||||
logInfo(ss.str());
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri << " " << content.size();
|
||||
logInfo(ss.str());
|
||||
|
||||
// FIXME: check extensions to set the content type
|
||||
// headers["Content-Type"] = "application/octet-stream";
|
||||
headers["Accept-Ranges"] = "none";
|
||||
// FIXME: check extensions to set the content type
|
||||
// headers["Content-Type"] = "application/octet-stream";
|
||||
headers["Accept-Ranges"] = "none";
|
||||
|
||||
for (auto &&it : request->headers) {
|
||||
headers[it.first] = it.second;
|
||||
}
|
||||
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)
|
||||
{
|
||||
//
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||
//
|
||||
setOnConnectionCallback(
|
||||
[this,
|
||||
redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
logInfo(ss.str());
|
||||
|
||||
if (request->method == "POST")
|
||||
{
|
||||
return std::make_shared<HttpResponse>(
|
||||
200, "OK", HttpErrorCode::Ok, headers, std::string());
|
||||
}
|
||||
|
||||
headers["Location"] = redirectUrl;
|
||||
|
||||
return std::make_shared<HttpResponse>(
|
||||
301, "OK", HttpErrorCode::Ok, headers, std::string());
|
||||
});
|
||||
}
|
||||
|
||||
void HttpServer::makeRedirectServer(const std::string &redirectUrl)
|
||||
{
|
||||
//
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||
// Display the client parameter and body on the console
|
||||
//
|
||||
setOnConnectionCallback(
|
||||
[this, redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
-> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
void HttpServer::makeDebugServer()
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionState->getRemoteIp() << ":"
|
||||
<< connectionState->getRemotePort() << " " << request->method
|
||||
<< " " << request->headers["User-Agent"] << " " << request->uri;
|
||||
logInfo(ss.str());
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
logInfo(ss.str());
|
||||
|
||||
if (request->method == "POST") {
|
||||
return std::make_shared<HttpResponse>(200,
|
||||
"OK",
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
std::string());
|
||||
}
|
||||
logInfo("== Headers == ");
|
||||
for (auto&& it : request->headers)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << it.first << ": " << it.second;
|
||||
logInfo(oss.str());
|
||||
}
|
||||
logInfo("");
|
||||
|
||||
headers["Location"] = redirectUrl;
|
||||
logInfo("== Body == ");
|
||||
logInfo(request->body);
|
||||
logInfo("");
|
||||
|
||||
return std::make_shared<HttpResponse>(301,
|
||||
"OK",
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
std::string());
|
||||
});
|
||||
}
|
||||
return std::make_shared<HttpResponse>(
|
||||
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Display the client parameter and body on the console
|
||||
//
|
||||
void HttpServer::makeDebugServer()
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
-> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionState->getRemoteIp() << ":"
|
||||
<< connectionState->getRemotePort() << " " << request->method
|
||||
<< " " << request->headers["User-Agent"] << " " << request->uri;
|
||||
logInfo(ss.str());
|
||||
|
||||
logInfo("== Headers == ");
|
||||
for (auto &&it : request->headers) {
|
||||
std::ostringstream oss;
|
||||
oss << it.first << ": " << it.second;
|
||||
logInfo(oss.str());
|
||||
}
|
||||
logInfo("");
|
||||
|
||||
logInfo("== Body == ");
|
||||
logInfo(request->body);
|
||||
logInfo("");
|
||||
|
||||
return std::make_shared<HttpResponse>(200,
|
||||
"OK",
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
std::string("OK"));
|
||||
});
|
||||
}
|
||||
|
||||
int HttpServer::getTimeoutSecs() { return _timeoutSecs; }
|
||||
int HttpServer::getTimeoutSecs()
|
||||
{
|
||||
return _timeoutSecs;
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
+421
-256
@@ -7,290 +7,455 @@
|
||||
#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;
|
||||
int err;
|
||||
WORD wVersionRequested;
|
||||
WSADATA wsaData;
|
||||
int err;
|
||||
|
||||
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h
|
||||
wVersionRequested = MAKEWORD(2, 2);
|
||||
err = WSAStartup(wVersionRequested, &wsaData);
|
||||
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h
|
||||
wVersionRequested = MAKEWORD(2, 2);
|
||||
err = WSAStartup(wVersionRequested, &wsaData);
|
||||
|
||||
return err == 0;
|
||||
return err == 0;
|
||||
#else
|
||||
return true;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool uninitNetSystem()
|
||||
{
|
||||
bool uninitNetSystem()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int err = WSACleanup();
|
||||
return err == 0;
|
||||
int err = WSACleanup();
|
||||
return err == 0;
|
||||
#else
|
||||
return true;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// That function could 'return WSAPoll(pfd, nfds, timeout);'
|
||||
// but WSAPoll is said to have weird behaviors on the internet
|
||||
// (the curl folks have had problems with it).
|
||||
//
|
||||
// So we make it a select wrapper
|
||||
//
|
||||
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
socket_t maxfd = 0;
|
||||
fd_set readfds, writefds, errorfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_ZERO(&writefds);
|
||||
FD_ZERO(&errorfds);
|
||||
|
||||
for (nfds_t i = 0; i < nfds; ++i) {
|
||||
struct pollfd *fd = &fds[i];
|
||||
|
||||
if (fd->fd > maxfd) {
|
||||
maxfd = fd->fd;
|
||||
struct WSAEvent
|
||||
{
|
||||
public:
|
||||
WSAEvent(struct pollfd* fd)
|
||||
: _fd(fd)
|
||||
{
|
||||
_event = WSACreateEvent();
|
||||
}
|
||||
if ((fd->events & POLLIN)) {
|
||||
FD_SET(fd->fd, &readfds);
|
||||
|
||||
WSAEvent(WSAEvent&& source) noexcept
|
||||
{
|
||||
_event = source._event;
|
||||
source._event = WSA_INVALID_EVENT; // invalidate the event in the source
|
||||
_fd = source._fd;
|
||||
}
|
||||
if ((fd->events & POLLOUT)) {
|
||||
FD_SET(fd->fd, &writefds);
|
||||
}
|
||||
if ((fd->events & POLLERR)) {
|
||||
FD_SET(fd->fd, &errorfds);
|
||||
}
|
||||
}
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
int ret = select(maxfd + 1,
|
||||
&readfds,
|
||||
&writefds,
|
||||
&errorfds,
|
||||
timeout != -1 ? &tv : NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (nfds_t i = 0; i < nfds; ++i) {
|
||||
struct pollfd *fd = &fds[i];
|
||||
fd->revents = 0;
|
||||
|
||||
if (FD_ISSET(fd->fd, &readfds)) {
|
||||
fd->revents |= POLLIN;
|
||||
}
|
||||
if (FD_ISSET(fd->fd, &writefds)) {
|
||||
fd->revents |= POLLOUT;
|
||||
}
|
||||
if (FD_ISSET(fd->fd, &errorfds)) {
|
||||
fd->revents |= POLLERR;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
#else
|
||||
//
|
||||
// It was reported that on Android poll can fail and return -1 with
|
||||
// errno == EINTR, which should be a temp error and should typically
|
||||
// be handled by retrying in a loop.
|
||||
// Maybe we need to put all syscall / C functions in
|
||||
// a new IXSysCalls.cpp and wrap them all.
|
||||
//
|
||||
// The style from libuv is as such.
|
||||
//
|
||||
int ret = -1;
|
||||
do {
|
||||
ret = ::poll(fds, nfds, timeout);
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// mingw does not have inet_ntop, which were taken as is from the musl C
|
||||
// library.
|
||||
//
|
||||
const char *inet_ntop(int af, const void *a0, char *s, socklen_t l)
|
||||
{
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
const unsigned char *a = (const unsigned char *)a0;
|
||||
int i, j, max, best;
|
||||
char buf[100];
|
||||
|
||||
switch (af) {
|
||||
case AF_INET:
|
||||
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l)
|
||||
return s;
|
||||
break;
|
||||
case AF_INET6:
|
||||
if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12))
|
||||
snprintf(buf,
|
||||
sizeof buf,
|
||||
"%x:%x:%x:%x:%x:%x:%x:%x",
|
||||
256 * a[0] + a[1],
|
||||
256 * a[2] + a[3],
|
||||
256 * a[4] + a[5],
|
||||
256 * a[6] + a[7],
|
||||
256 * a[8] + a[9],
|
||||
256 * a[10] + a[11],
|
||||
256 * a[12] + a[13],
|
||||
256 * a[14] + a[15]);
|
||||
else
|
||||
snprintf(buf,
|
||||
sizeof buf,
|
||||
"%x:%x:%x:%x:%x:%x:%d.%d.%d.%d",
|
||||
256 * a[0] + a[1],
|
||||
256 * a[2] + a[3],
|
||||
256 * a[4] + a[5],
|
||||
256 * a[6] + a[7],
|
||||
256 * a[8] + a[9],
|
||||
256 * a[10] + a[11],
|
||||
a[12],
|
||||
a[13],
|
||||
a[14],
|
||||
a[15]);
|
||||
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
|
||||
for (i = best = 0, max = 2; buf[i]; i++) {
|
||||
if (i && buf[i] != ':')
|
||||
continue;
|
||||
j = strspn(buf + i, ":0");
|
||||
if (j > max)
|
||||
best = i, max = j;
|
||||
~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);
|
||||
}
|
||||
if (max > 3) {
|
||||
buf[best] = buf[best + 1] = ':';
|
||||
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
|
||||
}
|
||||
if (strlen(buf) < l) {
|
||||
strcpy(s, buf);
|
||||
return s;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errno = EAFNOSUPPORT;
|
||||
return 0;
|
||||
}
|
||||
errno = ENOSPC;
|
||||
return 0;
|
||||
#else
|
||||
return ::inet_ntop(af, a0, s, l);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
static int hexval(unsigned c)
|
||||
{
|
||||
if (c - '0' < 10)
|
||||
return c - '0';
|
||||
c |= 32;
|
||||
if (c - 'a' < 6)
|
||||
return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
operator HANDLE()
|
||||
{
|
||||
return _event;
|
||||
}
|
||||
|
||||
operator struct pollfd*()
|
||||
{
|
||||
return _fd;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE _event;
|
||||
struct pollfd* _fd;
|
||||
};
|
||||
#endif
|
||||
|
||||
//
|
||||
// mingw does not have inet_pton, which were taken as is from the musl C
|
||||
// library.
|
||||
//
|
||||
int inet_pton(int af, const char *s, void *a0)
|
||||
{
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
uint16_t ip[8];
|
||||
unsigned char *a = (unsigned char *)a0;
|
||||
int i, j, v, d, brk = -1, need_v4 = 0;
|
||||
//
|
||||
// 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 (af == AF_INET) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (v = j = 0; j < 3 && isdigit(s[j]); j++)
|
||||
v = 10 * v + s[j] - '0';
|
||||
if (j == 0 || (j > 1 && s[0] == '0') || v > 255)
|
||||
return 0;
|
||||
a[i] = v;
|
||||
if (s[j] == 0 && i == 3)
|
||||
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;
|
||||
if (s[j] != '.')
|
||||
return 0;
|
||||
s += j + 1;
|
||||
}
|
||||
|
||||
int handleIndex = n - WSA_WAIT_EVENT_0;
|
||||
int socketIndex = handleIndex - 1;
|
||||
|
||||
WSANETWORKEVENTS netEvents;
|
||||
int count = 0;
|
||||
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
|
||||
// all the signaled events must be processed.
|
||||
while (socketIndex < socketEvents.size())
|
||||
{
|
||||
struct pollfd* fd = socketEvents[socketIndex];
|
||||
|
||||
memset(&netEvents, 0, sizeof(netEvents));
|
||||
if (WSAEnumNetworkEvents(fd->fd, socketEvents[socketIndex], &netEvents) != 0)
|
||||
{
|
||||
fd->revents = POLLERR;
|
||||
}
|
||||
else if (netEvents.lNetworkEvents != 0)
|
||||
{
|
||||
// mapping
|
||||
if (netEvents.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_OOB)) fd->revents |= POLLIN;
|
||||
if (netEvents.lNetworkEvents & (FD_WRITE | FD_CONNECT )) fd->revents |= POLLOUT;
|
||||
|
||||
for (int i = 0; i < FD_MAX_EVENTS; ++i)
|
||||
{
|
||||
if (netEvents.iErrorCode[i] != 0)
|
||||
{
|
||||
fd->revents |= POLLERR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fd->revents != 0)
|
||||
{
|
||||
// only signaled sockets count
|
||||
count++;
|
||||
}
|
||||
}
|
||||
socketIndex++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (event && *event) *event = nullptr;
|
||||
|
||||
socket_t maxfd = 0;
|
||||
fd_set readfds, writefds, errorfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_ZERO(&writefds);
|
||||
FD_ZERO(&errorfds);
|
||||
|
||||
for (nfds_t i = 0; i < nfds; ++i)
|
||||
{
|
||||
struct pollfd* fd = &fds[i];
|
||||
|
||||
if (fd->fd > maxfd)
|
||||
{
|
||||
maxfd = fd->fd;
|
||||
}
|
||||
if ((fd->events & POLLIN))
|
||||
{
|
||||
FD_SET(fd->fd, &readfds);
|
||||
}
|
||||
if ((fd->events & POLLOUT))
|
||||
{
|
||||
FD_SET(fd->fd, &writefds);
|
||||
}
|
||||
if ((fd->events & POLLERR))
|
||||
{
|
||||
FD_SET(fd->fd, &errorfds);
|
||||
}
|
||||
}
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (nfds_t i = 0; i < nfds; ++i)
|
||||
{
|
||||
struct pollfd* fd = &fds[i];
|
||||
fd->revents = 0;
|
||||
|
||||
if (FD_ISSET(fd->fd, &readfds))
|
||||
{
|
||||
fd->revents |= POLLIN;
|
||||
}
|
||||
if (FD_ISSET(fd->fd, &writefds))
|
||||
{
|
||||
fd->revents |= POLLOUT;
|
||||
}
|
||||
if (FD_ISSET(fd->fd, &errorfds))
|
||||
{
|
||||
fd->revents |= POLLERR;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
if (event && *event) *event = nullptr;
|
||||
|
||||
//
|
||||
// It was reported that on Android poll can fail and return -1 with
|
||||
// errno == EINTR, which should be a temp error and should typically
|
||||
// be handled by retrying in a loop.
|
||||
// Maybe we need to put all syscall / C functions in
|
||||
// a new IXSysCalls.cpp and wrap them all.
|
||||
//
|
||||
// The style from libuv is as such.
|
||||
//
|
||||
int ret = -1;
|
||||
do
|
||||
{
|
||||
ret = ::poll(fds, nfds, timeout);
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// mingw does not have inet_ntop, which were taken as is from the musl C library.
|
||||
//
|
||||
const char* inet_ntop(int af, const void* a0, char* s, socklen_t l)
|
||||
{
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
const unsigned char* a = (const unsigned char*) a0;
|
||||
int i, j, max, best;
|
||||
char buf[100];
|
||||
|
||||
switch (af)
|
||||
{
|
||||
case AF_INET:
|
||||
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) return s;
|
||||
break;
|
||||
case AF_INET6:
|
||||
if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12))
|
||||
snprintf(buf,
|
||||
sizeof buf,
|
||||
"%x:%x:%x:%x:%x:%x:%x:%x",
|
||||
256 * a[0] + a[1],
|
||||
256 * a[2] + a[3],
|
||||
256 * a[4] + a[5],
|
||||
256 * a[6] + a[7],
|
||||
256 * a[8] + a[9],
|
||||
256 * a[10] + a[11],
|
||||
256 * a[12] + a[13],
|
||||
256 * a[14] + a[15]);
|
||||
else
|
||||
snprintf(buf,
|
||||
sizeof buf,
|
||||
"%x:%x:%x:%x:%x:%x:%d.%d.%d.%d",
|
||||
256 * a[0] + a[1],
|
||||
256 * a[2] + a[3],
|
||||
256 * a[4] + a[5],
|
||||
256 * a[6] + a[7],
|
||||
256 * a[8] + a[9],
|
||||
256 * a[10] + a[11],
|
||||
a[12],
|
||||
a[13],
|
||||
a[14],
|
||||
a[15]);
|
||||
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
|
||||
for (i = best = 0, max = 2; buf[i]; i++)
|
||||
{
|
||||
if (i && buf[i] != ':') continue;
|
||||
j = strspn(buf + i, ":0");
|
||||
if (j > max) best = i, max = j;
|
||||
}
|
||||
if (max > 3)
|
||||
{
|
||||
buf[best] = buf[best + 1] = ':';
|
||||
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
|
||||
}
|
||||
if (strlen(buf) < l)
|
||||
{
|
||||
strcpy(s, buf);
|
||||
return s;
|
||||
}
|
||||
break;
|
||||
default: errno = EAFNOSUPPORT; return 0;
|
||||
}
|
||||
errno = ENOSPC;
|
||||
return 0;
|
||||
} else if (af != AF_INET6) {
|
||||
errno = EAFNOSUPPORT;
|
||||
#else
|
||||
return ::inet_ntop(af, a0, s, l);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
static int hexval(unsigned c)
|
||||
{
|
||||
if (c - '0' < 10) return c - '0';
|
||||
c |= 32;
|
||||
if (c - 'a' < 6) return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*s == ':' && *++s != ':')
|
||||
return 0;
|
||||
|
||||
for (i = 0;; i++) {
|
||||
if (s[0] == ':' && brk < 0) {
|
||||
brk = i;
|
||||
ip[i & 7] = 0;
|
||||
if (!*++s)
|
||||
break;
|
||||
if (i == 7)
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++)
|
||||
v = 16 * v + d;
|
||||
if (j == 0)
|
||||
return 0;
|
||||
ip[i & 7] = v;
|
||||
if (!s[j] && (brk >= 0 || i == 7))
|
||||
break;
|
||||
if (i == 7)
|
||||
return 0;
|
||||
if (s[j] != ':') {
|
||||
if (s[j] != '.' || (i < 6 && brk < 0))
|
||||
return 0;
|
||||
need_v4 = 1;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
s += j + 1;
|
||||
}
|
||||
if (brk >= 0) {
|
||||
memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk));
|
||||
for (j = 0; j < 7 - i; j++)
|
||||
ip[brk + j] = 0;
|
||||
}
|
||||
for (j = 0; j < 8; j++) {
|
||||
*a++ = ip[j] >> 8;
|
||||
*a++ = ip[j];
|
||||
}
|
||||
if (need_v4 && inet_pton(AF_INET, (const char *)s, a - 4) <= 0)
|
||||
return 0;
|
||||
return 1;
|
||||
#else
|
||||
return ::inet_pton(af, s, a0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convert network bytes to host bytes. Copied from the ASIO library
|
||||
unsigned short network_to_host_short(unsigned short value)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
unsigned char *value_p = reinterpret_cast<unsigned char *>(&value);
|
||||
unsigned short result = (static_cast<unsigned short>(value_p[0]) << 8) |
|
||||
static_cast<unsigned short>(value_p[1]);
|
||||
return result;
|
||||
#else // defined(_WIN32)
|
||||
return ntohs(value);
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
//
|
||||
// mingw does not have inet_pton, which were taken as is from the musl C library.
|
||||
//
|
||||
int inet_pton(int af, const char* s, void* a0)
|
||||
{
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
uint16_t ip[8];
|
||||
unsigned char* a = (unsigned char*) a0;
|
||||
int i, j, v, d, brk = -1, need_v4 = 0;
|
||||
|
||||
if (af == AF_INET)
|
||||
{
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
for (v = j = 0; j < 3 && isdigit(s[j]); j++)
|
||||
v = 10 * v + s[j] - '0';
|
||||
if (j == 0 || (j > 1 && s[0] == '0') || v > 255) return 0;
|
||||
a[i] = v;
|
||||
if (s[j] == 0 && i == 3) return 1;
|
||||
if (s[j] != '.') return 0;
|
||||
s += j + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (af != AF_INET6)
|
||||
{
|
||||
errno = EAFNOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*s == ':' && *++s != ':') return 0;
|
||||
|
||||
for (i = 0;; i++)
|
||||
{
|
||||
if (s[0] == ':' && brk < 0)
|
||||
{
|
||||
brk = i;
|
||||
ip[i & 7] = 0;
|
||||
if (!*++s) break;
|
||||
if (i == 7) return 0;
|
||||
continue;
|
||||
}
|
||||
for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++)
|
||||
v = 16 * v + d;
|
||||
if (j == 0) return 0;
|
||||
ip[i & 7] = v;
|
||||
if (!s[j] && (brk >= 0 || i == 7)) break;
|
||||
if (i == 7) return 0;
|
||||
if (s[j] != ':')
|
||||
{
|
||||
if (s[j] != '.' || (i < 6 && brk < 0)) return 0;
|
||||
need_v4 = 1;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
s += j + 1;
|
||||
}
|
||||
if (brk >= 0)
|
||||
{
|
||||
memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk));
|
||||
for (j = 0; j < 7 - i; j++)
|
||||
ip[brk + j] = 0;
|
||||
}
|
||||
for (j = 0; j < 8; j++)
|
||||
{
|
||||
*a++ = ip[j] >> 8;
|
||||
*a++ = ip[j];
|
||||
}
|
||||
if (need_v4 && inet_pton(AF_INET, (const char*) s, a - 4) <= 0) return 0;
|
||||
return 1;
|
||||
#else
|
||||
return ::inet_pton(af, s, a0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convert network bytes to host bytes. Copied from the ASIO library
|
||||
unsigned short network_to_host_short(unsigned short value)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
unsigned char* value_p = reinterpret_cast<unsigned char*>(&value);
|
||||
unsigned short result = (static_cast<unsigned short>(value_p[0]) << 8)
|
||||
| static_cast<unsigned short>(value_p[1]);
|
||||
return result;
|
||||
#else // defined(_WIN32)
|
||||
return ntohs(value);
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -21,136 +21,141 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
// File descriptor at index 0 in _fildes is the read end of the pipe
|
||||
// File descriptor at index 1 in _fildes is the write end of the pipe
|
||||
const int SelectInterruptPipe::kPipeReadIndex = 0;
|
||||
const int SelectInterruptPipe::kPipeWriteIndex = 1;
|
||||
|
||||
SelectInterruptPipe::SelectInterruptPipe()
|
||||
{
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
}
|
||||
|
||||
SelectInterruptPipe::~SelectInterruptPipe()
|
||||
{
|
||||
::close(_fildes[kPipeReadIndex]);
|
||||
::close(_fildes[kPipeWriteIndex]);
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
}
|
||||
|
||||
bool SelectInterruptPipe::init(std::string &errorMsg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
// calling init twice is a programming error
|
||||
assert(_fildes[kPipeReadIndex] == -1);
|
||||
assert(_fildes[kPipeWriteIndex] == -1);
|
||||
|
||||
if (pipe(_fildes) < 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in pipe() call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1) {
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) "
|
||||
"call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
// File descriptor at index 0 in _fildes is the read end of the pipe
|
||||
// File descriptor at index 1 in _fildes is the write end of the pipe
|
||||
const int SelectInterruptPipe::kPipeReadIndex = 0;
|
||||
const int SelectInterruptPipe::kPipeWriteIndex = 1;
|
||||
|
||||
SelectInterruptPipe::SelectInterruptPipe()
|
||||
{
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1) {
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) "
|
||||
"call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
SelectInterruptPipe::~SelectInterruptPipe()
|
||||
{
|
||||
::close(_fildes[kPipeReadIndex]);
|
||||
::close(_fildes[kPipeWriteIndex]);
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SelectInterruptPipe::init(std::string& errorMsg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
// calling init twice is a programming error
|
||||
assert(_fildes[kPipeReadIndex] == -1);
|
||||
assert(_fildes[kPipeWriteIndex] == -1);
|
||||
|
||||
if (pipe(_fildes) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in pipe() call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef F_SETNOSIGPIPE
|
||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1) {
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(.... "
|
||||
"F_SETNOSIGPIPE) call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1) {
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., "
|
||||
"F_SETNOSIGPIPE) call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectInterruptPipe::notify(uint64_t value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
bool SelectInterruptPipe::notify(uint64_t value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
int fd = _fildes[kPipeWriteIndex];
|
||||
if (fd == -1)
|
||||
return false;
|
||||
int fd = _fildes[kPipeWriteIndex];
|
||||
if (fd == -1) return false;
|
||||
|
||||
ssize_t ret = -1;
|
||||
do {
|
||||
ret = ::write(fd, &value, sizeof(value));
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
ssize_t ret = -1;
|
||||
do
|
||||
{
|
||||
ret = ::write(fd, &value, sizeof(value));
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
|
||||
// we should write 8 bytes for an uint64_t
|
||||
return ret == 8;
|
||||
}
|
||||
// we should write 8 bytes for an uint64_t
|
||||
return ret == 8;
|
||||
}
|
||||
|
||||
// TODO: return max uint64_t for errors ?
|
||||
uint64_t SelectInterruptPipe::read()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
// TODO: return max uint64_t for errors ?
|
||||
uint64_t SelectInterruptPipe::read()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
int fd = _fildes[kPipeReadIndex];
|
||||
int fd = _fildes[kPipeReadIndex];
|
||||
|
||||
uint64_t value = 0;
|
||||
uint64_t value = 0;
|
||||
|
||||
ssize_t ret = -1;
|
||||
do {
|
||||
ret = ::read(fd, &value, sizeof(value));
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
ssize_t ret = -1;
|
||||
do
|
||||
{
|
||||
ret = ::read(fd, &value, sizeof(value));
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool SelectInterruptPipe::clear() { return true; }
|
||||
bool SelectInterruptPipe::clear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int SelectInterruptPipe::getFd() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
int SelectInterruptPipe::getFd() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
return _fildes[kPipeReadIndex];
|
||||
}
|
||||
return _fildes[kPipeReadIndex];
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
@@ -23,59 +23,61 @@
|
||||
namespace ix
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push, 8)
|
||||
typedef struct tagTHREADNAME_INFO {
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} THREADNAME_INFO;
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} THREADNAME_INFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
void SetThreadName(DWORD dwThreadID, const char *threadName)
|
||||
{
|
||||
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
||||
{
|
||||
#ifndef __GNUC__
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = threadName;
|
||||
info.dwThreadID = dwThreadID;
|
||||
info.dwFlags = 0;
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = threadName;
|
||||
info.dwThreadID = dwThreadID;
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try {
|
||||
RaiseException(MS_VC_EXCEPTION,
|
||||
0,
|
||||
sizeof(info) / sizeof(ULONG_PTR),
|
||||
(ULONG_PTR *)&info);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
__try
|
||||
{
|
||||
RaiseException(
|
||||
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#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
|
||||
// Notice that the Apple version of pthread_setname_np
|
||||
// does not take a pthread_t argument
|
||||
//
|
||||
pthread_setname_np(name.substr(0, 63).c_str());
|
||||
//
|
||||
// Apple reserves 16 bytes for its thread names
|
||||
// Notice that the Apple version of pthread_setname_np
|
||||
// does not take a pthread_t argument
|
||||
//
|
||||
pthread_setname_np(name.substr(0, 63).c_str());
|
||||
#elif defined(__linux__)
|
||||
//
|
||||
// Linux only reserves 16 bytes for its thread names
|
||||
// See prctl and PR_SET_NAME property in
|
||||
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
||||
//
|
||||
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
||||
//
|
||||
// Linux only reserves 16 bytes for its thread names
|
||||
// See prctl and PR_SET_NAME property in
|
||||
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
||||
//
|
||||
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
||||
#elif defined(_WIN32)
|
||||
SetThreadName(-1, name.c_str());
|
||||
SetThreadName(-1, name.c_str());
|
||||
#elif defined(BSD)
|
||||
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
|
||||
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
|
||||
#else
|
||||
// ... assert here ?
|
||||
// ... assert here ?
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+384
-304
@@ -27,347 +27,427 @@
|
||||
|
||||
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() { close(); }
|
||||
|
||||
PollResultType Socket::poll(bool readyToRead,
|
||||
int timeoutMs,
|
||||
int sockfd,
|
||||
const SelectInterruptPtr &selectInterrupt)
|
||||
{
|
||||
//
|
||||
// We used to use ::select to poll but on Android 9 we get large fds out of
|
||||
// ::connect which crash in FD_SET as they are larger than FD_SETSIZE.
|
||||
// Switching to ::poll does fix that.
|
||||
//
|
||||
// However poll isn't as portable as select and has bugs on Windows, so we
|
||||
// have a shim to fallback to select on those platforms. See
|
||||
// https://github.com/mpv-player/mpv/pull/5203/files for such a select
|
||||
// wrapper.
|
||||
//
|
||||
nfds_t nfds = 1;
|
||||
struct pollfd fds[2];
|
||||
memset(fds, 0, sizeof(fds));
|
||||
|
||||
fds[0].fd = sockfd;
|
||||
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
|
||||
|
||||
// this is ignored by poll, but our select based poll wrapper on Windows
|
||||
// needs it
|
||||
fds[0].events |= POLLERR;
|
||||
|
||||
// File descriptor used to interrupt select when needed
|
||||
int interruptFd = -1;
|
||||
if (selectInterrupt) {
|
||||
interruptFd = selectInterrupt->getFd();
|
||||
|
||||
if (interruptFd != -1) {
|
||||
nfds = 2;
|
||||
fds[1].fd = interruptFd;
|
||||
fds[1].events = POLLIN;
|
||||
}
|
||||
Socket::Socket(int fd)
|
||||
: _sockfd(fd)
|
||||
, _selectInterrupt(createSelectInterrupt())
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
int ret = ix::poll(fds, nfds, timeoutMs);
|
||||
Socket::~Socket()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
PollResultType pollResult = PollResultType::ReadyForRead;
|
||||
if (ret < 0) {
|
||||
pollResult = PollResultType::Error;
|
||||
} else if (ret == 0) {
|
||||
pollResult = PollResultType::Timeout;
|
||||
} else if (interruptFd != -1 && fds[1].revents & POLLIN) {
|
||||
PollResultType Socket::poll(bool readyToRead,
|
||||
int timeoutMs,
|
||||
int sockfd,
|
||||
const SelectInterruptPtr& selectInterrupt)
|
||||
{
|
||||
PollResultType pollResult = PollResultType::ReadyForRead;
|
||||
|
||||
//
|
||||
// We used to use ::select to poll but on Android 9 we get large fds out of
|
||||
// ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching
|
||||
// to ::poll does fix that.
|
||||
//
|
||||
// However poll isn't as portable as select and has bugs on Windows, so we
|
||||
// have a shim to fallback to select on those platforms. See
|
||||
// https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
|
||||
//
|
||||
nfds_t nfds = 1;
|
||||
struct pollfd fds[2];
|
||||
memset(fds, 0, sizeof(fds));
|
||||
|
||||
fds[0].fd = sockfd;
|
||||
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
|
||||
|
||||
// this is ignored by poll, but our select based poll wrapper on Windows needs it
|
||||
fds[0].events |= POLLERR;
|
||||
|
||||
// File descriptor used to interrupt select when needed
|
||||
int interruptFd = -1;
|
||||
void* interruptEvent = nullptr;
|
||||
if (selectInterrupt)
|
||||
{
|
||||
interruptFd = selectInterrupt->getFd();
|
||||
interruptEvent = selectInterrupt->getEvent();
|
||||
|
||||
if (interruptFd != -1)
|
||||
{
|
||||
nfds = 2;
|
||||
fds[1].fd = interruptFd;
|
||||
fds[1].events = POLLIN;
|
||||
}
|
||||
else if (interruptEvent == nullptr)
|
||||
{
|
||||
// Emulation mode: SelectInterrupt neither supports file descriptors nor events
|
||||
|
||||
// Check the selectInterrupt for requests before doing the poll().
|
||||
if (readSelectInterruptRequest(selectInterrupt, &pollResult))
|
||||
{
|
||||
return pollResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled
|
||||
int ret = ix::poll(fds, nfds, timeoutMs, &event);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
pollResult = PollResultType::Error;
|
||||
}
|
||||
else if (ret == 0)
|
||||
{
|
||||
pollResult = PollResultType::Timeout;
|
||||
if (selectInterrupt && interruptFd == -1 && interruptEvent == nullptr)
|
||||
{
|
||||
// Emulation mode: SelectInterrupt neither supports fd nor events
|
||||
|
||||
// Check the selectInterrupt for requests
|
||||
readSelectInterruptRequest(selectInterrupt, &pollResult);
|
||||
}
|
||||
}
|
||||
else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr))
|
||||
{
|
||||
// The InterruptEvent was signaled
|
||||
readSelectInterruptRequest(selectInterrupt, &pollResult);
|
||||
}
|
||||
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN)
|
||||
{
|
||||
pollResult = PollResultType::ReadyForRead;
|
||||
}
|
||||
else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT)
|
||||
{
|
||||
pollResult = PollResultType::ReadyForWrite;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On connect error, in async mode, windows will write to the exceptions fds
|
||||
if (fds[0].revents & POLLERR)
|
||||
{
|
||||
pollResult = PollResultType::Error;
|
||||
}
|
||||
#else
|
||||
int optval = -1;
|
||||
socklen_t optlen = sizeof(optval);
|
||||
|
||||
// getsockopt() puts the errno value for connect into optval so 0
|
||||
// means no-error.
|
||||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0)
|
||||
{
|
||||
pollResult = PollResultType::Error;
|
||||
|
||||
// set errno to optval so that external callers can have an
|
||||
// appropriate error description when calling strerror
|
||||
errno = optval;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
|
||||
fds[0].revents & POLLNVAL))
|
||||
{
|
||||
pollResult = PollResultType::Error;
|
||||
}
|
||||
|
||||
return pollResult;
|
||||
}
|
||||
|
||||
bool Socket::readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
|
||||
PollResultType* pollResult)
|
||||
{
|
||||
uint64_t value = selectInterrupt->read();
|
||||
|
||||
if (value == SelectInterrupt::kSendRequest) {
|
||||
pollResult = PollResultType::SendRequest;
|
||||
} else if (value == SelectInterrupt::kCloseRequest) {
|
||||
pollResult = PollResultType::CloseRequest;
|
||||
if (value == SelectInterrupt::kSendRequest)
|
||||
{
|
||||
*pollResult = PollResultType::SendRequest;
|
||||
return true;
|
||||
}
|
||||
} else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN) {
|
||||
pollResult = PollResultType::ReadyForRead;
|
||||
} else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT) {
|
||||
pollResult = PollResultType::ReadyForWrite;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On connect error, in async mode, windows will write to the exceptions
|
||||
// fds
|
||||
if (fds[0].revents & POLLERR) {
|
||||
pollResult = PollResultType::Error;
|
||||
else if (value == SelectInterrupt::kCloseRequest)
|
||||
{
|
||||
*pollResult = PollResultType::CloseRequest;
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
int optval = -1;
|
||||
socklen_t optlen = sizeof(optval);
|
||||
|
||||
// getsockopt() puts the errno value for connect into optval so 0
|
||||
// means no-error.
|
||||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
|
||||
optval != 0) {
|
||||
pollResult = PollResultType::Error;
|
||||
|
||||
// set errno to optval so that external callers can have an
|
||||
// appropriate error description when calling strerror
|
||||
errno = optval;
|
||||
}
|
||||
#endif
|
||||
} else if (sockfd != -1 &&
|
||||
(fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
|
||||
fds[0].revents & POLLNVAL)) {
|
||||
pollResult = PollResultType::Error;
|
||||
}
|
||||
|
||||
return pollResult;
|
||||
}
|
||||
|
||||
PollResultType Socket::isReadyToRead(int timeoutMs)
|
||||
{
|
||||
if (_sockfd == -1) {
|
||||
return PollResultType::Error;
|
||||
}
|
||||
|
||||
bool readyToRead = true;
|
||||
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
|
||||
}
|
||||
|
||||
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
||||
{
|
||||
if (_sockfd == -1) {
|
||||
return PollResultType::Error;
|
||||
}
|
||||
|
||||
bool readyToRead = false;
|
||||
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
|
||||
}
|
||||
|
||||
// Wake up from poll/select by writing to the pipe which is watched by select
|
||||
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
|
||||
{
|
||||
return _selectInterrupt->notify(wakeUpCode);
|
||||
}
|
||||
|
||||
bool Socket::accept(std::string &errMsg)
|
||||
{
|
||||
if (_sockfd == -1) {
|
||||
errMsg = "Socket is uninitialized";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Socket::connect(const std::string &host,
|
||||
int port,
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
PollResultType Socket::isReadyToRead(int timeoutMs)
|
||||
{
|
||||
if (_sockfd == -1)
|
||||
{
|
||||
return PollResultType::Error;
|
||||
}
|
||||
|
||||
if (!_selectInterrupt->clear())
|
||||
return false;
|
||||
bool readyToRead = true;
|
||||
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
|
||||
}
|
||||
|
||||
_sockfd =
|
||||
SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||
return _sockfd != -1;
|
||||
}
|
||||
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
||||
{
|
||||
if (_sockfd == -1)
|
||||
{
|
||||
return PollResultType::Error;
|
||||
}
|
||||
|
||||
void Socket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
bool readyToRead = false;
|
||||
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
|
||||
}
|
||||
|
||||
if (_sockfd == -1)
|
||||
return;
|
||||
// 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);
|
||||
}
|
||||
|
||||
closeSocket(_sockfd);
|
||||
_sockfd = -1;
|
||||
}
|
||||
bool Socket::isWakeUpFromPollSupported()
|
||||
{
|
||||
return _selectInterrupt->getFd() != -1 || _selectInterrupt->getEvent() != nullptr;
|
||||
}
|
||||
|
||||
ssize_t Socket::send(char *buffer, size_t length)
|
||||
{
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
|
||||
return ::send(_sockfd, buffer, length, flags);
|
||||
}
|
||||
|
||||
ssize_t Socket::send(const std::string &buffer)
|
||||
{
|
||||
return send((char *)&buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
ssize_t Socket::recv(void *buffer, size_t length)
|
||||
{
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
|
||||
return ::recv(_sockfd, (char *)buffer, length, flags);
|
||||
}
|
||||
|
||||
int Socket::getErrno()
|
||||
{
|
||||
int err;
|
||||
|
||||
#ifdef _WIN32
|
||||
err = WSAGetLastError();
|
||||
#else
|
||||
err = errno;
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool Socket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS) {
|
||||
bool Socket::accept(std::string& errMsg)
|
||||
{
|
||||
if (_sockfd == -1)
|
||||
{
|
||||
errMsg = "Socket is uninitialized";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool Socket::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
|
||||
void Socket::closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
if (!_selectInterrupt->clear()) return false;
|
||||
|
||||
bool Socket::init(std::string &errorMsg)
|
||||
{
|
||||
return _selectInterrupt->init(errorMsg);
|
||||
}
|
||||
|
||||
bool Socket::writeBytes(const std::string &str,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
int offset = 0;
|
||||
int len = (int)str.size();
|
||||
|
||||
while (true) {
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
return false;
|
||||
|
||||
ssize_t ret = send((char *)&str[offset], len);
|
||||
|
||||
// We wrote some bytes, as needed, all good.
|
||||
if (ret > 0) {
|
||||
if (ret == len) {
|
||||
return true;
|
||||
} else {
|
||||
offset += ret;
|
||||
len -= ret;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// There is possibly something to be writen, try again
|
||||
else if (ret < 0 && Socket::isWaitNeeded()) {
|
||||
continue;
|
||||
}
|
||||
// There was an error during the write, abort
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||
return _sockfd != -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool Socket::readByte(void *buffer,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
while (true) {
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
return false;
|
||||
void Socket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
|
||||
ssize_t ret;
|
||||
ret = recv(buffer, 1);
|
||||
if (_sockfd == -1) return;
|
||||
|
||||
// We read one byte, as needed, all good.
|
||||
if (ret == 1) {
|
||||
closeSocket(_sockfd);
|
||||
_sockfd = -1;
|
||||
}
|
||||
|
||||
ssize_t Socket::send(char* buffer, size_t length)
|
||||
{
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
|
||||
return ::send(_sockfd, buffer, length, flags);
|
||||
}
|
||||
|
||||
ssize_t Socket::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
ssize_t Socket::recv(void* buffer, size_t length)
|
||||
{
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
|
||||
return ::recv(_sockfd, (char*) buffer, length, flags);
|
||||
}
|
||||
|
||||
int Socket::getErrno()
|
||||
{
|
||||
int err;
|
||||
|
||||
#ifdef _WIN32
|
||||
err = WSAGetLastError();
|
||||
#else
|
||||
err = errno;
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool Socket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// There is possibly something to be read, try again
|
||||
else if (ret < 0 && Socket::isWaitNeeded()) {
|
||||
// Wait with a 1ms timeout until the socket is ready to read.
|
||||
// This way we are not busy looping
|
||||
if (isReadyToRead(1) == PollResultType::Error) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Socket::closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Socket::init(std::string& errorMsg)
|
||||
{
|
||||
return _selectInterrupt->init(errorMsg);
|
||||
}
|
||||
|
||||
bool Socket::writeBytes(const std::string& str,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
int offset = 0;
|
||||
int len = (int) str.size();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||
|
||||
ssize_t ret = send((char*) &str[offset], len);
|
||||
|
||||
// We wrote some bytes, as needed, all good.
|
||||
if (ret > 0)
|
||||
{
|
||||
if (ret == len)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += ret;
|
||||
len -= ret;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// There is possibly something to be writen, try again
|
||||
else if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// There was an error during the write, abort
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// There was an error during the read, abort
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<bool, std::string>
|
||||
Socket::readLine(const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
char c;
|
||||
std::string line;
|
||||
line.reserve(64);
|
||||
|
||||
for (int i = 0; i < 2 || (line[i - 2] != '\r' && line[i - 1] != '\n');
|
||||
++i) {
|
||||
if (!readByte(&c, isCancellationRequested)) {
|
||||
// Return what we were able to read
|
||||
return std::make_pair(false, line);
|
||||
}
|
||||
|
||||
line += c;
|
||||
}
|
||||
|
||||
return std::make_pair(true, line);
|
||||
}
|
||||
bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||
|
||||
std::pair<bool, std::string>
|
||||
Socket::readBytes(size_t length,
|
||||
const OnProgressCallback &onProgressCallback,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
std::array<uint8_t, 1 << 14> readBuffer;
|
||||
ssize_t ret;
|
||||
ret = recv(buffer, 1);
|
||||
|
||||
std::vector<uint8_t> output;
|
||||
while (output.size() != length) {
|
||||
if (isCancellationRequested && isCancellationRequested()) {
|
||||
const std::string errorMsg("Cancellation Requested");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
size_t size = std::min(readBuffer.size(), length - output.size());
|
||||
ssize_t ret = recv((char *)&readBuffer[0], size);
|
||||
|
||||
if (ret > 0) {
|
||||
output.insert(output.end(),
|
||||
readBuffer.begin(),
|
||||
readBuffer.begin() + ret);
|
||||
} else if (ret <= 0 && !Socket::isWaitNeeded()) {
|
||||
const std::string errorMsg("Recv Error");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
if (onProgressCallback)
|
||||
onProgressCallback((int)output.size(), (int)length);
|
||||
|
||||
// Wait with a 1ms timeout until the socket is ready to read.
|
||||
// This way we are not busy looping
|
||||
if (isReadyToRead(1) == PollResultType::Error) {
|
||||
const std::string errorMsg("Poll Error");
|
||||
return std::make_pair(false, errorMsg);
|
||||
// We read one byte, as needed, all good.
|
||||
if (ret == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// There is possibly something to be read, try again
|
||||
else if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
// Wait with a 1ms timeout until the socket is ready to read.
|
||||
// This way we are not busy looping
|
||||
if (isReadyToRead(1) == PollResultType::Error)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// There was an error during the read, abort
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(true, std::string(output.begin(), output.end()));
|
||||
}
|
||||
std::pair<bool, std::string> Socket::readLine(
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
char c;
|
||||
std::string line;
|
||||
line.reserve(64);
|
||||
|
||||
for (int i = 0; i < 2 || (line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
|
||||
{
|
||||
if (!readByte(&c, isCancellationRequested))
|
||||
{
|
||||
// Return what we were able to read
|
||||
return std::make_pair(false, line);
|
||||
}
|
||||
|
||||
line += c;
|
||||
}
|
||||
|
||||
return std::make_pair(true, line);
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> Socket::readBytes(
|
||||
size_t length,
|
||||
const OnProgressCallback& onProgressCallback,
|
||||
const OnChunkCallback& onChunkCallback,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
std::array<uint8_t, 1 << 14> readBuffer;
|
||||
std::vector<uint8_t> output;
|
||||
size_t bytesRead = 0;
|
||||
|
||||
while (bytesRead != length)
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
{
|
||||
const std::string errorMsg("Cancellation Requested");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
size_t size = std::min(readBuffer.size(), length - bytesRead);
|
||||
ssize_t ret = recv((char*) &readBuffer[0], size);
|
||||
|
||||
if (ret > 0)
|
||||
{
|
||||
if (onChunkCallback)
|
||||
{
|
||||
std::string chunk(readBuffer.begin(), readBuffer.begin() + ret);
|
||||
onChunkCallback(chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
|
||||
}
|
||||
bytesRead += ret;
|
||||
}
|
||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||
{
|
||||
const std::string errorMsg("Recv Error");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length);
|
||||
|
||||
// Wait with a 1ms timeout until the socket is ready to read.
|
||||
// This way we are not busy looping
|
||||
if (isReadyToRead(1) == PollResultType::Error)
|
||||
{
|
||||
const std::string errorMsg("Poll Error");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(true, std::string(output.begin(), output.end()));
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+257
-251
@@ -28,279 +28,285 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions &tlsOptions, int fd)
|
||||
: Socket(fd), _sslContext(nullptr), _tlsOptions(tlsOptions)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
SocketAppleSSL::~SocketAppleSSL() { SocketAppleSSL::close(); }
|
||||
|
||||
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
|
||||
{
|
||||
std::string errMsg("Unknown SSL error.");
|
||||
|
||||
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault,
|
||||
kCFErrorDomainOSStatus,
|
||||
status,
|
||||
NULL);
|
||||
if (error) {
|
||||
CFStringRef message = CFErrorCopyDescription(error);
|
||||
if (message) {
|
||||
char localBuffer[128];
|
||||
Boolean success;
|
||||
success = CFStringGetCString(message,
|
||||
localBuffer,
|
||||
128,
|
||||
kCFStringEncodingUTF8);
|
||||
if (success) {
|
||||
errMsg = localBuffer;
|
||||
}
|
||||
CFRelease(message);
|
||||
}
|
||||
CFRelease(error);
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection,
|
||||
void *data,
|
||||
size_t *len)
|
||||
{
|
||||
int fd = (int)(long)connection;
|
||||
if (fd < 0)
|
||||
return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t requested_sz = *len;
|
||||
|
||||
ssize_t status = read(fd, data, requested_sz);
|
||||
|
||||
if (status > 0) {
|
||||
*len = (size_t)status;
|
||||
if (requested_sz > *len) {
|
||||
return errSSLWouldBlock;
|
||||
} else {
|
||||
return noErr;
|
||||
}
|
||||
} else if (status == 0) {
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
} else {
|
||||
*len = 0;
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN:
|
||||
return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN
|
||||
// on osx
|
||||
case EINPROGRESS:
|
||||
return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET:
|
||||
return errSSLClosedAbort;
|
||||
|
||||
default:
|
||||
return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
|
||||
const void *data,
|
||||
size_t *len)
|
||||
{
|
||||
int fd = (int)(long)connection;
|
||||
if (fd < 0)
|
||||
return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t to_write_sz = *len;
|
||||
ssize_t status = write(fd, data, to_write_sz);
|
||||
|
||||
if (status > 0) {
|
||||
*len = (size_t)status;
|
||||
if (to_write_sz > *len) {
|
||||
return errSSLWouldBlock;
|
||||
} else {
|
||||
return noErr;
|
||||
}
|
||||
} else if (status == 0) {
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
} else {
|
||||
*len = 0;
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN:
|
||||
return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN
|
||||
// on osx
|
||||
case EINPROGRESS:
|
||||
return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET:
|
||||
return errSSLClosedAbort;
|
||||
|
||||
default:
|
||||
return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SocketAppleSSL::accept(std::string &errMsg)
|
||||
{
|
||||
errMsg = "TLS not supported yet in server mode with apple ssl backend";
|
||||
return false;
|
||||
}
|
||||
|
||||
OSStatus SocketAppleSSL::tlsHandShake(
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
OSStatus status;
|
||||
|
||||
do {
|
||||
status = SSLHandshake(_sslContext);
|
||||
|
||||
// Interrupt the handshake
|
||||
if (isCancellationRequested()) {
|
||||
errMsg = "Cancellation requested";
|
||||
return errSSLInternal;
|
||||
}
|
||||
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// No wait support
|
||||
bool SocketAppleSSL::connect(const std::string &host,
|
||||
int port,
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
OSStatus status;
|
||||
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
, _sslContext(nullptr)
|
||||
, _tlsOptions(tlsOptions)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
;
|
||||
}
|
||||
|
||||
_sockfd =
|
||||
SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||
if (_sockfd == -1)
|
||||
return false;
|
||||
SocketAppleSSL::~SocketAppleSSL()
|
||||
{
|
||||
SocketAppleSSL::close();
|
||||
}
|
||||
|
||||
_sslContext = SSLCreateContext(kCFAllocatorDefault,
|
||||
kSSLClientSide,
|
||||
kSSLStreamType);
|
||||
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
|
||||
{
|
||||
std::string errMsg("Unknown SSL error.");
|
||||
|
||||
SSLSetIOFuncs(_sslContext,
|
||||
SocketAppleSSL::readFromSocket,
|
||||
SocketAppleSSL::writeToSocket);
|
||||
SSLSetConnection(_sslContext, (SSLConnectionRef)(long)_sockfd);
|
||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled()) {
|
||||
Boolean option(1);
|
||||
SSLSetSessionOption(_sslContext,
|
||||
kSSLSessionOptionBreakOnServerAuth,
|
||||
option);
|
||||
|
||||
status = tlsHandShake(errMsg, isCancellationRequested);
|
||||
|
||||
if (status == errSSLServerAuthCompleted) {
|
||||
// proceed with the handshake
|
||||
status = tlsHandShake(errMsg, isCancellationRequested);
|
||||
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
|
||||
if (error)
|
||||
{
|
||||
CFStringRef message = CFErrorCopyDescription(error);
|
||||
if (message)
|
||||
{
|
||||
char localBuffer[128];
|
||||
Boolean success;
|
||||
success = CFStringGetCString(message, localBuffer, 128, kCFStringEncodingUTF8);
|
||||
if (success)
|
||||
{
|
||||
errMsg = localBuffer;
|
||||
}
|
||||
CFRelease(message);
|
||||
}
|
||||
CFRelease(error);
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection, void* data, size_t* len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0) return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t requested_sz = *len;
|
||||
|
||||
ssize_t status = read(fd, data, requested_sz);
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (requested_sz > *len)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
else if (status == 0)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
}
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
switch (errno)
|
||||
{
|
||||
case ENOENT: return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
|
||||
case EINPROGRESS: return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET: return errSSLClosedAbort;
|
||||
|
||||
default: return errSecIO;
|
||||
}
|
||||
} else {
|
||||
status = tlsHandShake(errMsg, isCancellationRequested);
|
||||
}
|
||||
}
|
||||
|
||||
if (status != noErr) {
|
||||
errMsg = getSSLErrorDescription(status);
|
||||
close();
|
||||
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
|
||||
const void* data,
|
||||
size_t* len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0) return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t to_write_sz = *len;
|
||||
ssize_t status = write(fd, data, to_write_sz);
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (to_write_sz > *len)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
else if (status == 0)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
}
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
switch (errno)
|
||||
{
|
||||
case ENOENT: return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
|
||||
case EINPROGRESS: return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET: return errSSLClosedAbort;
|
||||
|
||||
default: return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SocketAppleSSL::accept(std::string& errMsg)
|
||||
{
|
||||
errMsg = "TLS not supported yet in server mode with apple ssl backend";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
OSStatus SocketAppleSSL::tlsHandShake(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
OSStatus status;
|
||||
|
||||
void SocketAppleSSL::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
|
||||
if (_sslContext == nullptr)
|
||||
return;
|
||||
// Interrupt the handshake
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "Cancellation requested";
|
||||
return errSSLInternal;
|
||||
}
|
||||
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
|
||||
|
||||
SSLClose(_sslContext);
|
||||
CFRelease(_sslContext);
|
||||
_sslContext = nullptr;
|
||||
|
||||
Socket::close();
|
||||
}
|
||||
|
||||
ssize_t SocketAppleSSL::send(char *buf, size_t nbyte)
|
||||
{
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
while (status == errSSLWouldBlock) {
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLWrite(_sslContext, buf, nbyte, &processed);
|
||||
|
||||
if (processed > 0)
|
||||
return (ssize_t)processed;
|
||||
|
||||
// The connection was reset, inform the caller that this
|
||||
// Socket should close
|
||||
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
|
||||
status == errSSLClosedAbort) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status == errSSLWouldBlock) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// No wait support
|
||||
ssize_t SocketAppleSSL::recv(void *buf, size_t nbyte)
|
||||
{
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
while (status == errSSLWouldBlock) {
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLRead(_sslContext, buf, nbyte, &processed);
|
||||
// No wait support
|
||||
bool SocketAppleSSL::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
OSStatus status;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
if (processed > 0)
|
||||
return (ssize_t)processed;
|
||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||
if (_sockfd == -1) return false;
|
||||
|
||||
// The connection was reset, inform the caller that this
|
||||
// Socket should close
|
||||
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
|
||||
status == errSSLClosedAbort) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
|
||||
|
||||
SSLSetIOFuncs(
|
||||
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
|
||||
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
|
||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
Boolean option(1);
|
||||
SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option);
|
||||
|
||||
status = tlsHandShake(errMsg, isCancellationRequested);
|
||||
|
||||
if (status == errSSLServerAuthCompleted)
|
||||
{
|
||||
// proceed with the handshake
|
||||
status = tlsHandShake(errMsg, isCancellationRequested);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = tlsHandShake(errMsg, isCancellationRequested);
|
||||
}
|
||||
}
|
||||
|
||||
if (status == errSSLWouldBlock) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
if (status != noErr)
|
||||
{
|
||||
errMsg = getSSLErrorDescription(status);
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SocketAppleSSL::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
if (_sslContext == nullptr) return;
|
||||
|
||||
SSLClose(_sslContext);
|
||||
CFRelease(_sslContext);
|
||||
_sslContext = nullptr;
|
||||
|
||||
Socket::close();
|
||||
}
|
||||
|
||||
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
|
||||
{
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
while (status == errSSLWouldBlock)
|
||||
{
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLWrite(_sslContext, buf, nbyte, &processed);
|
||||
|
||||
if (processed > 0) return (ssize_t) processed;
|
||||
|
||||
// The connection was reset, inform the caller that this
|
||||
// Socket should close
|
||||
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
|
||||
status == errSSLClosedAbort)
|
||||
{
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status == errSSLWouldBlock)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// No wait support
|
||||
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
|
||||
{
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
while (status == errSSLWouldBlock)
|
||||
{
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLRead(_sslContext, buf, nbyte, &processed);
|
||||
|
||||
if (processed > 0) return (ssize_t) processed;
|
||||
|
||||
// The connection was reset, inform the caller that this
|
||||
// Socket should close
|
||||
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
|
||||
status == errSSLClosedAbort)
|
||||
{
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status == errSSLWouldBlock)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
|
||||
+103
-101
@@ -20,131 +20,133 @@
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
#endif
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
//
|
||||
// This function can be cancelled every 50 ms
|
||||
// This is important so that we don't block the main UI thread when shutting
|
||||
// down a connection which is already trying to reconnect, and can be blocked
|
||||
// waiting for
|
||||
// ::connect to respond.
|
||||
//
|
||||
int SocketConnect::connectToAddress(
|
||||
const struct addrinfo *address,
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
//
|
||||
// This function can be cancelled every 50 ms
|
||||
// This is important so that we don't block the main UI thread when shutting down a
|
||||
// connection which is already trying to reconnect, and can be blocked waiting for
|
||||
// ::connect to respond.
|
||||
//
|
||||
int SocketConnect::connectToAddress(const struct addrinfo* address,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
socket_t fd =
|
||||
socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
if (fd < 0) {
|
||||
errMsg = "Cannot create a socket";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set the socket to non blocking mode, so that slow responses cannot
|
||||
// block us for too long
|
||||
SocketConnect::configure(fd);
|
||||
|
||||
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
|
||||
|
||||
if (res == -1 && !Socket::isWaitNeeded()) {
|
||||
errMsg = strerror(Socket::getErrno());
|
||||
Socket::closeSocket(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (isCancellationRequested &&
|
||||
isCancellationRequested()) // Must handle timeout as well
|
||||
socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
if (fd < 0)
|
||||
{
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = "Cancelled";
|
||||
errMsg = "Cannot create a socket";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int timeoutMs = 10;
|
||||
bool readyToRead = false;
|
||||
auto selectInterrupt = ix::make_unique<SelectInterrupt>();
|
||||
PollResultType pollResult =
|
||||
Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
|
||||
// Set the socket to non blocking mode, so that slow responses cannot
|
||||
// block us for too long
|
||||
SocketConnect::configure(fd);
|
||||
|
||||
if (pollResult == PollResultType::Timeout) {
|
||||
continue;
|
||||
} else if (pollResult == PollResultType::Error) {
|
||||
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
|
||||
|
||||
if (res == -1 && !Socket::isWaitNeeded())
|
||||
{
|
||||
errMsg = strerror(Socket::getErrno());
|
||||
Socket::closeSocket(fd);
|
||||
errMsg =
|
||||
std::string("Connect error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
} else if (pollResult == PollResultType::ReadyForWrite) {
|
||||
return fd;
|
||||
} else {
|
||||
Socket::closeSocket(fd);
|
||||
errMsg =
|
||||
std::string("Connect error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
||||
{
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = "Cancelled";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int timeoutMs = 10;
|
||||
bool readyToRead = false;
|
||||
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt();
|
||||
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
|
||||
|
||||
if (pollResult == PollResultType::Timeout)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (pollResult == PollResultType::Error)
|
||||
{
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
}
|
||||
else if (pollResult == PollResultType::ReadyForWrite)
|
||||
{
|
||||
return fd;
|
||||
}
|
||||
else
|
||||
{
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = "connect timed out after 60 seconds";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int SocketConnect::connect(const std::string &hostname,
|
||||
int port,
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
//
|
||||
// First do DNS resolution
|
||||
//
|
||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
||||
struct addrinfo *res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||
if (res == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sockfd = -1;
|
||||
|
||||
// iterate through the records to find a working peer
|
||||
struct addrinfo *address;
|
||||
for (address = res; address != nullptr; address = address->ai_next) {
|
||||
int SocketConnect::connect(const std::string& hostname,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
//
|
||||
// Second try to connect to the remote host
|
||||
// First do DNS resolution
|
||||
//
|
||||
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
|
||||
if (sockfd != -1) {
|
||||
break;
|
||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
||||
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sockfd = -1;
|
||||
|
||||
// iterate through the records to find a working peer
|
||||
struct addrinfo* address;
|
||||
for (address = res; address != nullptr; address = address->ai_next)
|
||||
{
|
||||
//
|
||||
// Second try to connect to the remote host
|
||||
//
|
||||
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
|
||||
if (sockfd != -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
return sockfd;
|
||||
}
|
||||
// FIXME: configure is a terrible name
|
||||
void SocketConnect::configure(int sockfd)
|
||||
{
|
||||
// 1. disable Nagle's algorithm
|
||||
int flag = 1;
|
||||
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
|
||||
|
||||
// FIXME: configure is a terrible name
|
||||
void SocketConnect::configure(int sockfd)
|
||||
{
|
||||
// 1. disable Nagle's algorithm
|
||||
int flag = 1;
|
||||
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
|
||||
|
||||
// 2. make socket non blocking
|
||||
// 2. make socket non blocking
|
||||
#ifdef _WIN32
|
||||
unsigned long nonblocking = 1;
|
||||
ioctlsocket(sockfd, FIONBIO, &nonblocking);
|
||||
unsigned long nonblocking = 1;
|
||||
ioctlsocket(sockfd, FIONBIO, &nonblocking);
|
||||
#else
|
||||
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
||||
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));
|
||||
int value = 1;
|
||||
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -25,36 +25,40 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
std::unique_ptr<Socket> createSocket(bool tls,
|
||||
int fd,
|
||||
std::string &errorMsg,
|
||||
const SocketTLSOptions &tlsOptions)
|
||||
{
|
||||
(void)tlsOptions;
|
||||
errorMsg.clear();
|
||||
std::unique_ptr<Socket> socket;
|
||||
std::unique_ptr<Socket> createSocket(bool tls,
|
||||
int fd,
|
||||
std::string& errorMsg,
|
||||
const SocketTLSOptions& tlsOptions)
|
||||
{
|
||||
(void) tlsOptions;
|
||||
errorMsg.clear();
|
||||
std::unique_ptr<Socket> socket;
|
||||
|
||||
if (!tls) {
|
||||
socket = ix::make_unique<Socket>(fd);
|
||||
} else {
|
||||
if (!tls)
|
||||
{
|
||||
socket = ix::make_unique<Socket>(fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
#if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||
socket = ix::make_unique<SocketMbedTLS>(tlsOptions, fd);
|
||||
socket = ix::make_unique<SocketMbedTLS>(tlsOptions, fd);
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
socket = ix::make_unique<SocketOpenSSL>(tlsOptions, fd);
|
||||
socket = ix::make_unique<SocketOpenSSL>(tlsOptions, fd);
|
||||
#elif defined(__APPLE__)
|
||||
socket = ix::make_unique<SocketAppleSSL>(tlsOptions, fd);
|
||||
socket = ix::make_unique<SocketAppleSSL>(tlsOptions, fd);
|
||||
#endif
|
||||
#else
|
||||
errorMsg = "TLS support is not enabled on this platform.";
|
||||
return nullptr;
|
||||
errorMsg = "TLS support is not enabled on this platform.";
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (!socket->init(errorMsg)) {
|
||||
socket.reset();
|
||||
}
|
||||
if (!socket->init(errorMsg))
|
||||
{
|
||||
socket.reset();
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+296
-285
@@ -23,327 +23,338 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions &tlsOptions, int fd)
|
||||
: Socket(fd), _tlsOptions(tlsOptions)
|
||||
{
|
||||
initMBedTLS();
|
||||
}
|
||||
|
||||
SocketMbedTLS::~SocketMbedTLS() { SocketMbedTLS::close(); }
|
||||
|
||||
void SocketMbedTLS::initMBedTLS()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
mbedtls_ssl_init(&_ssl);
|
||||
mbedtls_ssl_config_init(&_conf);
|
||||
mbedtls_ctr_drbg_init(&_ctr_drbg);
|
||||
mbedtls_entropy_init(&_entropy);
|
||||
mbedtls_x509_crt_init(&_cacert);
|
||||
mbedtls_x509_crt_init(&_cert);
|
||||
mbedtls_pk_init(&_pkey);
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::loadSystemCertificates(std::string &errorMsg)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
|
||||
CERT_SYSTEM_STORE_CURRENT_USER;
|
||||
HCERTSTORE systemStore =
|
||||
CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
|
||||
|
||||
if (!systemStore) {
|
||||
errorMsg = "CertOpenStore failed with ";
|
||||
errorMsg += std::to_string(GetLastError());
|
||||
return false;
|
||||
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
, _tlsOptions(tlsOptions)
|
||||
{
|
||||
initMBedTLS();
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT certificateIterator = NULL;
|
||||
|
||||
int certificateCount = 0;
|
||||
while (certificateIterator =
|
||||
CertEnumCertificatesInStore(systemStore, certificateIterator)) {
|
||||
if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING) {
|
||||
int ret =
|
||||
mbedtls_x509_crt_parse(&_cacert,
|
||||
certificateIterator->pbCertEncoded,
|
||||
certificateIterator->cbCertEncoded);
|
||||
if (ret == 0) {
|
||||
++certificateCount;
|
||||
}
|
||||
}
|
||||
SocketMbedTLS::~SocketMbedTLS()
|
||||
{
|
||||
SocketMbedTLS::close();
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(certificateIterator);
|
||||
CertCloseStore(systemStore, 0);
|
||||
|
||||
if (certificateCount == 0) {
|
||||
errorMsg = "No certificates found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
// On macOS we can query the system cert location from the keychain
|
||||
// On Linux we could try to fetch some local files based on the distribution
|
||||
// On Android we could use JNI to get to the system certs
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string &host,
|
||||
bool isClient,
|
||||
std::string &errMsg)
|
||||
{
|
||||
initMBedTLS();
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
const char *pers = "IXSocketMbedTLS";
|
||||
|
||||
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
|
||||
mbedtls_entropy_func,
|
||||
&_entropy,
|
||||
(const unsigned char *)pers,
|
||||
strlen(pers)) != 0) {
|
||||
errMsg = "Setting entropy seed failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_config_defaults(&_conf,
|
||||
(isClient) ? MBEDTLS_SSL_IS_CLIENT
|
||||
: MBEDTLS_SSL_IS_SERVER,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
|
||||
errMsg = "Setting config default failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
|
||||
|
||||
if (_tlsOptions.hasCertAndKey()) {
|
||||
if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) <
|
||||
0) {
|
||||
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
|
||||
if (mbedtls_pk_parse_keyfile(&_pkey,
|
||||
_tlsOptions.keyFile.c_str(),
|
||||
"",
|
||||
mbedtls_ctr_drbg_random,
|
||||
&_ctr_drbg) < 0)
|
||||
#else
|
||||
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") <
|
||||
0)
|
||||
#endif
|
||||
{
|
||||
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
|
||||
return false;
|
||||
}
|
||||
if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0) {
|
||||
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled()) {
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
} else {
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
if (_tlsOptions.isUsingSystemDefaults()) {
|
||||
if (!loadSystemCertificates(errMsg)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (_tlsOptions.isUsingInMemoryCAs()) {
|
||||
const char *buffer = _tlsOptions.caFile.c_str();
|
||||
size_t bufferSize = _tlsOptions.caFile.size() +
|
||||
1; // Needs to include null terminating
|
||||
// character otherwise mbedtls will fail.
|
||||
if (mbedtls_x509_crt_parse(&_cacert,
|
||||
(const unsigned char *)buffer,
|
||||
bufferSize) < 0) {
|
||||
errMsg = "Cannot parse CA from memory.";
|
||||
return false;
|
||||
}
|
||||
} else if (mbedtls_x509_crt_parse_file(&_cacert,
|
||||
_tlsOptions.caFile.c_str()) <
|
||||
0) {
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0) {
|
||||
errMsg = "SSL setup failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0) {
|
||||
errMsg = "SNI setup failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::accept(std::string &errMsg)
|
||||
{
|
||||
bool isClient = false;
|
||||
bool initialized = init(std::string(), isClient, errMsg);
|
||||
if (!initialized) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_set_bio(&_ssl,
|
||||
&_sockfd,
|
||||
mbedtls_net_send,
|
||||
mbedtls_net_recv,
|
||||
NULL);
|
||||
|
||||
int res;
|
||||
do {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
res = mbedtls_ssl_handshake(&_ssl);
|
||||
} while (res == MBEDTLS_ERR_SSL_WANT_READ ||
|
||||
res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
|
||||
if (res != 0) {
|
||||
char buf[256];
|
||||
mbedtls_strerror(res, buf, sizeof(buf));
|
||||
|
||||
errMsg = "error in handshake : ";
|
||||
errMsg += buf;
|
||||
|
||||
if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
|
||||
char verifyBuf[512];
|
||||
uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl);
|
||||
|
||||
mbedtls_x509_crt_verify_info(verifyBuf,
|
||||
sizeof(verifyBuf),
|
||||
" ! ",
|
||||
flags);
|
||||
errMsg += " : ";
|
||||
errMsg += verifyBuf;
|
||||
}
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::connect(const std::string &host,
|
||||
int port,
|
||||
std::string &errMsg,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
void SocketMbedTLS::initMBedTLS()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_sockfd =
|
||||
SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||
if (_sockfd == -1)
|
||||
return false;
|
||||
|
||||
mbedtls_ssl_init(&_ssl);
|
||||
mbedtls_ssl_config_init(&_conf);
|
||||
mbedtls_ctr_drbg_init(&_ctr_drbg);
|
||||
mbedtls_entropy_init(&_entropy);
|
||||
mbedtls_x509_crt_init(&_cacert);
|
||||
mbedtls_x509_crt_init(&_cert);
|
||||
mbedtls_pk_init(&_pkey);
|
||||
}
|
||||
|
||||
bool isClient = true;
|
||||
bool initialized = init(host, isClient, errMsg);
|
||||
if (!initialized) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
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");
|
||||
|
||||
mbedtls_ssl_set_bio(&_ssl,
|
||||
&_sockfd,
|
||||
mbedtls_net_send,
|
||||
mbedtls_net_recv,
|
||||
NULL);
|
||||
|
||||
int res;
|
||||
do {
|
||||
if (!systemStore)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
res = mbedtls_ssl_handshake(&_ssl);
|
||||
errorMsg = "CertOpenStore failed with ";
|
||||
errorMsg += std::to_string(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCancellationRequested()) {
|
||||
errMsg = "Cancellation requested";
|
||||
PCCERT_CONTEXT certificateIterator = NULL;
|
||||
|
||||
int certificateCount = 0;
|
||||
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
|
||||
{
|
||||
if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING)
|
||||
{
|
||||
int ret = mbedtls_x509_crt_parse(&_cacert,
|
||||
certificateIterator->pbCertEncoded,
|
||||
certificateIterator->cbCertEncoded);
|
||||
if (ret == 0)
|
||||
{
|
||||
++certificateCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(certificateIterator);
|
||||
CertCloseStore(systemStore, 0);
|
||||
|
||||
if (certificateCount == 0)
|
||||
{
|
||||
errorMsg = "No certificates found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
// On macOS we can query the system cert location from the keychain
|
||||
// On Linux we could try to fetch some local files based on the distribution
|
||||
// On Android we could use JNI to get to the system certs
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
|
||||
{
|
||||
initMBedTLS();
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
const char* pers = "IXSocketMbedTLS";
|
||||
|
||||
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
|
||||
mbedtls_entropy_func,
|
||||
&_entropy,
|
||||
(const unsigned char*) pers,
|
||||
strlen(pers)) != 0)
|
||||
{
|
||||
errMsg = "Setting entropy seed failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_config_defaults(&_conf,
|
||||
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT) != 0)
|
||||
{
|
||||
errMsg = "Setting config default failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
|
||||
|
||||
if (_tlsOptions.hasCertAndKey())
|
||||
{
|
||||
if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
|
||||
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0)
|
||||
#else
|
||||
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
|
||||
#endif
|
||||
{
|
||||
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
|
||||
return false;
|
||||
}
|
||||
if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0)
|
||||
{
|
||||
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
if (!loadSystemCertificates(errMsg))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
const char* buffer = _tlsOptions.caFile.c_str();
|
||||
size_t bufferSize =
|
||||
_tlsOptions.caFile.size() + 1; // Needs to include null terminating
|
||||
// character otherwise mbedtls will fail.
|
||||
if (mbedtls_x509_crt_parse(
|
||||
&_cacert, (const unsigned char*) buffer, bufferSize) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse CA from memory.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
|
||||
{
|
||||
errMsg = "SSL setup failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
{
|
||||
errMsg = "SNI setup failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::accept(std::string& errMsg)
|
||||
{
|
||||
bool isClient = false;
|
||||
bool initialized = init(std::string(), isClient, errMsg);
|
||||
if (!initialized)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
} while (res == MBEDTLS_ERR_SSL_WANT_READ ||
|
||||
res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
|
||||
if (res != 0) {
|
||||
char buf[256];
|
||||
mbedtls_strerror(res, buf, sizeof(buf));
|
||||
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||
|
||||
errMsg = "error in handshake : ";
|
||||
errMsg += buf;
|
||||
int res;
|
||||
do
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
res = mbedtls_ssl_handshake(&_ssl);
|
||||
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
|
||||
close();
|
||||
return false;
|
||||
if (res != 0)
|
||||
{
|
||||
char buf[256];
|
||||
mbedtls_strerror(res, buf, sizeof(buf));
|
||||
|
||||
errMsg = "error in handshake : ";
|
||||
errMsg += buf;
|
||||
|
||||
if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
|
||||
{
|
||||
char verifyBuf[512];
|
||||
uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl);
|
||||
|
||||
mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags);
|
||||
errMsg += " : ";
|
||||
errMsg += verifyBuf;
|
||||
}
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool SocketMbedTLS::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||
if (_sockfd == -1) return false;
|
||||
}
|
||||
|
||||
void SocketMbedTLS::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
bool isClient = true;
|
||||
bool initialized = init(host, isClient, errMsg);
|
||||
if (!initialized)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_free(&_ssl);
|
||||
mbedtls_ssl_config_free(&_conf);
|
||||
mbedtls_ctr_drbg_free(&_ctr_drbg);
|
||||
mbedtls_entropy_free(&_entropy);
|
||||
mbedtls_x509_crt_free(&_cacert);
|
||||
mbedtls_x509_crt_free(&_cert);
|
||||
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||
|
||||
Socket::close();
|
||||
}
|
||||
int res;
|
||||
do
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
res = mbedtls_ssl_handshake(&_ssl);
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::send(char *buf, size_t nbyte)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "Cancellation requested";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
|
||||
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char *)buf, nbyte);
|
||||
if (res != 0)
|
||||
{
|
||||
char buf[256];
|
||||
mbedtls_strerror(res, buf, sizeof(buf));
|
||||
|
||||
if (res > 0) {
|
||||
return res;
|
||||
} else if (res == MBEDTLS_ERR_SSL_WANT_READ ||
|
||||
res == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
errMsg = "error in handshake : ";
|
||||
errMsg += buf;
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::recv(void *buf, size_t nbyte)
|
||||
{
|
||||
while (true) {
|
||||
void SocketMbedTLS::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char *)buf, (int)nbyte);
|
||||
mbedtls_ssl_free(&_ssl);
|
||||
mbedtls_ssl_config_free(&_conf);
|
||||
mbedtls_ctr_drbg_free(&_ctr_drbg);
|
||||
mbedtls_entropy_free(&_entropy);
|
||||
mbedtls_x509_crt_free(&_cacert);
|
||||
mbedtls_x509_crt_free(&_cert);
|
||||
|
||||
if (res > 0) {
|
||||
Socket::close();
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
|
||||
|
||||
if (res > 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte);
|
||||
|
||||
if (res > 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
|
||||
+707
-619
File diff suppressed because it is too large
Load Diff
+436
-408
@@ -20,345 +20,313 @@
|
||||
|
||||
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() { stop(); }
|
||||
|
||||
void SocketServer::logError(const std::string &str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
fprintf(stderr, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
void SocketServer::logInfo(const std::string &str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
fprintf(stdout, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> SocketServer::listen()
|
||||
{
|
||||
std::string acceptSelectInterruptInitErrorMsg;
|
||||
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg)) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error in SelectInterrupt::init: "
|
||||
<< acceptSelectInterruptInitErrorMsg;
|
||||
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
if (_addressFamily != AF_INET && _addressFamily != AF_INET6) {
|
||||
std::string errMsg(
|
||||
"SocketServer::listen() AF_INET and AF_INET6 are currently "
|
||||
"the only supported address families");
|
||||
return std::make_pair(false, errMsg);
|
||||
}
|
||||
|
||||
// Get a socket for accepting connections.
|
||||
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error creating socket): "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Make that socket reusable. (allow restarting this server at will)
|
||||
int enable = 1;
|
||||
if (setsockopt(_serverFd,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
(char *)&enable,
|
||||
sizeof(enable)) < 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
if (_addressFamily == AF_INET) {
|
||||
struct sockaddr_in server;
|
||||
server.sin_family = _addressFamily;
|
||||
server.sin_port = htons(_port);
|
||||
|
||||
if (ix::inet_pton(_addressFamily,
|
||||
_host.c_str(),
|
||||
&server.sin_addr.s_addr) <= 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling inet_pton "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling bind "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
} else // AF_INET6
|
||||
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())
|
||||
{
|
||||
struct sockaddr_in6 server;
|
||||
server.sin6_family = _addressFamily;
|
||||
server.sin6_port = htons(_port);
|
||||
|
||||
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <=
|
||||
0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling inet_pton "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling bind "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listen for connections. Specify the tcp backlog.
|
||||
//
|
||||
if (::listen(_serverFd, _backlog) < 0) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling listen "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
SocketServer::~SocketServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
return std::make_pair(true, "");
|
||||
}
|
||||
|
||||
void SocketServer::start()
|
||||
{
|
||||
_stop = false;
|
||||
|
||||
if (!_thread.joinable()) {
|
||||
_thread = std::thread(&SocketServer::run, this);
|
||||
void SocketServer::logError(const std::string& str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
fprintf(stderr, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
if (!_gcThread.joinable()) {
|
||||
_gcThread = std::thread(&SocketServer::runGC, this);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||
_conditionVariable.wait(lock);
|
||||
}
|
||||
|
||||
void SocketServer::stopAcceptingConnections() { _stop = true; }
|
||||
|
||||
void SocketServer::stop()
|
||||
{
|
||||
// Stop accepting connections, and close the 'accept' thread
|
||||
if (_thread.joinable()) {
|
||||
_stop = true;
|
||||
// Wake up select
|
||||
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest)) {
|
||||
logError("SocketServer::stop: Cannot wake up from select");
|
||||
}
|
||||
|
||||
_thread.join();
|
||||
_stop = false;
|
||||
void SocketServer::logInfo(const std::string& str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
fprintf(stdout, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
// Join all threads and make sure that all connections are terminated
|
||||
if (_gcThread.joinable()) {
|
||||
_stopGc = true;
|
||||
_conditionVariableGC.notify_one();
|
||||
_gcThread.join();
|
||||
_stopGc = false;
|
||||
}
|
||||
|
||||
_conditionVariable.notify_one();
|
||||
Socket::closeSocket(_serverFd);
|
||||
}
|
||||
|
||||
void SocketServer::setConnectionStateFactory(
|
||||
const ConnectionStateFactory &connectionStateFactory)
|
||||
{
|
||||
_connectionStateFactory = connectionStateFactory;
|
||||
}
|
||||
|
||||
//
|
||||
// join the threads for connections that have been closed
|
||||
//
|
||||
// When a connection is closed by a client, the connection state terminated
|
||||
// field becomes true, and we can use that to know that we can join that thread
|
||||
// and remove it from our _connectionsThreads data structure (a list).
|
||||
//
|
||||
void SocketServer::closeTerminatedThreads()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
auto it = _connectionsThreads.begin();
|
||||
auto itEnd = _connectionsThreads.end();
|
||||
|
||||
while (it != itEnd) {
|
||||
auto &connectionState = it->first;
|
||||
auto &thread = it->second;
|
||||
|
||||
if (!connectionState->isTerminated()) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thread.joinable())
|
||||
thread.join();
|
||||
it = _connectionsThreads.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::run()
|
||||
{
|
||||
// Set the socket to non blocking mode, so that accept calls are not
|
||||
// blocking
|
||||
SocketConnect::configure(_serverFd);
|
||||
|
||||
setThreadName("SocketServer::accept");
|
||||
|
||||
for (;;) {
|
||||
if (_stop)
|
||||
return;
|
||||
|
||||
// Use poll to check whether a new connection is in progress
|
||||
int timeoutMs = -1;
|
||||
#ifdef _WIN32
|
||||
// select cannot be interrupted on Windows so we need to pass a small
|
||||
// timeout
|
||||
timeoutMs = 10;
|
||||
#endif
|
||||
|
||||
bool readyToRead = true;
|
||||
PollResultType pollResult = Socket::poll(readyToRead,
|
||||
timeoutMs,
|
||||
_serverFd,
|
||||
_acceptSelectInterrupt);
|
||||
|
||||
if (pollResult == PollResultType::Error) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error in select: " <<
|
||||
#ifndef _WIN32
|
||||
strerror(Socket::getErrno());
|
||||
#else
|
||||
|
||||
Socket::getErrno();
|
||||
#endif
|
||||
logError(ss.str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pollResult != PollResultType::ReadyForRead) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Accept a connection.
|
||||
// FIXME: Is this working for ipv6 ?
|
||||
struct sockaddr_in client; // client address information
|
||||
int clientFd; // socket connected to client
|
||||
socklen_t addressLen = sizeof(client);
|
||||
memset(&client, 0, sizeof(client));
|
||||
|
||||
if ((clientFd =
|
||||
accept(_serverFd, (struct sockaddr *)&client, &addressLen)) <
|
||||
0) {
|
||||
if (!Socket::isWaitNeeded()) {
|
||||
// FIXME: that error should be propagated
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error accepting connection: " << err
|
||||
<< ", " << strerror(err);
|
||||
logError(ss.str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getConnectedClientsCount() >= _maxConnections) {
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() reached max connections = "
|
||||
<< _maxConnections << ". "
|
||||
<< "Not accepting connection";
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve connection info, the ip address of the remote peer/client)
|
||||
std::string remoteIp;
|
||||
int remotePort;
|
||||
|
||||
if (_addressFamily == AF_INET) {
|
||||
char remoteIp4[INET_ADDRSTRLEN];
|
||||
if (ix::inet_ntop(AF_INET,
|
||||
&client.sin_addr,
|
||||
remoteIp4,
|
||||
INET_ADDRSTRLEN) == nullptr) {
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error calling inet_ntop (ipv4): "
|
||||
<< err << ", " << strerror(err);
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
remotePort = ix::network_to_host_short(client.sin_port);
|
||||
remoteIp = remoteIp4;
|
||||
} else // AF_INET6
|
||||
std::pair<bool, std::string> SocketServer::listen()
|
||||
{
|
||||
std::string acceptSelectInterruptInitErrorMsg;
|
||||
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg))
|
||||
{
|
||||
char remoteIp6[INET6_ADDRSTRLEN];
|
||||
if (ix::inet_ntop(AF_INET6,
|
||||
&client.sin_addr,
|
||||
remoteIp6,
|
||||
INET6_ADDRSTRLEN) == nullptr) {
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error in SelectInterrupt::init: "
|
||||
<< acceptSelectInterruptInitErrorMsg;
|
||||
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
if (_addressFamily != AF_INET && _addressFamily != AF_INET6)
|
||||
{
|
||||
std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently "
|
||||
"the only supported address families");
|
||||
return std::make_pair(false, errMsg);
|
||||
}
|
||||
|
||||
// Get a socket for accepting connections.
|
||||
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno());
|
||||
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Make that socket reusable. (allow restarting this server at will)
|
||||
int enable = 1;
|
||||
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
if (_addressFamily == AF_INET)
|
||||
{
|
||||
struct sockaddr_in server;
|
||||
server.sin_family = _addressFamily;
|
||||
server.sin_port = htons(_port);
|
||||
|
||||
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error calling inet_ntop (ipv6): "
|
||||
<< err << ", " << strerror(err);
|
||||
ss << "SocketServer::listen() error calling inet_pton "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling bind "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
}
|
||||
else // AF_INET6
|
||||
{
|
||||
struct sockaddr_in6 server;
|
||||
server.sin6_family = _addressFamily;
|
||||
server.sin6_port = htons(_port);
|
||||
|
||||
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling inet_pton "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling bind "
|
||||
<< "at address " << _host << ":" << _port << " : "
|
||||
<< strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listen for connections. Specify the tcp backlog.
|
||||
//
|
||||
if (::listen(_serverFd, _backlog) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling listen "
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
return std::make_pair(true, "");
|
||||
}
|
||||
|
||||
void SocketServer::start()
|
||||
{
|
||||
_stop = false;
|
||||
|
||||
if (!_thread.joinable())
|
||||
{
|
||||
_thread = std::thread(&SocketServer::run, this);
|
||||
}
|
||||
|
||||
if (!_gcThread.joinable())
|
||||
{
|
||||
_gcThread = std::thread(&SocketServer::runGC, this);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||
_conditionVariable.wait(lock);
|
||||
}
|
||||
|
||||
void SocketServer::stopAcceptingConnections()
|
||||
{
|
||||
_stop = true;
|
||||
}
|
||||
|
||||
void SocketServer::stop()
|
||||
{
|
||||
// Stop accepting connections, and close the 'accept' thread
|
||||
if (_thread.joinable())
|
||||
{
|
||||
_stop = true;
|
||||
// Wake up select
|
||||
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest))
|
||||
{
|
||||
logError("SocketServer::stop: Cannot wake up from select");
|
||||
}
|
||||
|
||||
_thread.join();
|
||||
_stop = false;
|
||||
}
|
||||
|
||||
// Join all threads and make sure that all connections are terminated
|
||||
if (_gcThread.joinable())
|
||||
{
|
||||
_stopGc = true;
|
||||
_conditionVariableGC.notify_one();
|
||||
_gcThread.join();
|
||||
_stopGc = false;
|
||||
}
|
||||
|
||||
_conditionVariable.notify_one();
|
||||
Socket::closeSocket(_serverFd);
|
||||
}
|
||||
|
||||
void SocketServer::setConnectionStateFactory(
|
||||
const ConnectionStateFactory& connectionStateFactory)
|
||||
{
|
||||
_connectionStateFactory = connectionStateFactory;
|
||||
}
|
||||
|
||||
//
|
||||
// join the threads for connections that have been closed
|
||||
//
|
||||
// When a connection is closed by a client, the connection state terminated
|
||||
// field becomes true, and we can use that to know that we can join that thread
|
||||
// and remove it from our _connectionsThreads data structure (a list).
|
||||
//
|
||||
void SocketServer::closeTerminatedThreads()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
auto it = _connectionsThreads.begin();
|
||||
auto itEnd = _connectionsThreads.end();
|
||||
|
||||
while (it != itEnd)
|
||||
{
|
||||
auto& connectionState = it->first;
|
||||
auto& thread = it->second;
|
||||
|
||||
if (!connectionState->isTerminated())
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thread.joinable()) thread.join();
|
||||
it = _connectionsThreads.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::run()
|
||||
{
|
||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
||||
SocketConnect::configure(_serverFd);
|
||||
|
||||
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
|
||||
// $ echo Srv:gc:64000 | wc -c
|
||||
// 13
|
||||
setThreadName("Srv:ac:" + std::to_string(_port));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (_stop) return;
|
||||
|
||||
// Use poll to check whether a new connection is in progress
|
||||
int timeoutMs = -1;
|
||||
#ifdef _WIN32
|
||||
// select cannot be interrupted on Windows so we need to pass a small timeout
|
||||
timeoutMs = 10;
|
||||
#endif
|
||||
|
||||
bool readyToRead = true;
|
||||
PollResultType pollResult =
|
||||
Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt);
|
||||
|
||||
if (pollResult == PollResultType::Error)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno());
|
||||
logError(ss.str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pollResult != PollResultType::ReadyForRead)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Accept a connection.
|
||||
// FIXME: Is this working for ipv6 ?
|
||||
struct sockaddr_in client; // client address information
|
||||
int clientFd; // socket connected to client
|
||||
socklen_t addressLen = sizeof(client);
|
||||
memset(&client, 0, sizeof(client));
|
||||
|
||||
if ((clientFd = accept(_serverFd, (struct sockaddr*) &client, &addressLen)) < 0)
|
||||
{
|
||||
if (!Socket::isWaitNeeded())
|
||||
{
|
||||
// FIXME: that error should be propagated
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error accepting connection: " << err << ", "
|
||||
<< strerror(err);
|
||||
logError(ss.str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getConnectedClientsCount() >= _maxConnections)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() reached max connections = " << _maxConnections << ". "
|
||||
<< "Not accepting connection";
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
@@ -366,102 +334,162 @@ void SocketServer::run()
|
||||
continue;
|
||||
}
|
||||
|
||||
remotePort = ix::network_to_host_short(client.sin_port);
|
||||
remoteIp = remoteIp6;
|
||||
// Retrieve connection info, the ip address of the remote peer/client)
|
||||
std::string remoteIp;
|
||||
int remotePort;
|
||||
|
||||
if (_addressFamily == AF_INET)
|
||||
{
|
||||
char remoteIp4[INET_ADDRSTRLEN];
|
||||
if (ix::inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr)
|
||||
{
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
|
||||
<< strerror(err);
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
remotePort = ix::network_to_host_short(client.sin_port);
|
||||
remoteIp = remoteIp4;
|
||||
}
|
||||
else // AF_INET6
|
||||
{
|
||||
char remoteIp6[INET6_ADDRSTRLEN];
|
||||
if (ix::inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) ==
|
||||
nullptr)
|
||||
{
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
|
||||
<< strerror(err);
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
remotePort = ix::network_to_host_short(client.sin_port);
|
||||
remoteIp = remoteIp6;
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectionState> connectionState;
|
||||
if (_connectionStateFactory)
|
||||
{
|
||||
connectionState = _connectionStateFactory();
|
||||
}
|
||||
connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); });
|
||||
connectionState->setRemoteIp(remoteIp);
|
||||
connectionState->setRemotePort(remotePort);
|
||||
|
||||
if (_stop) return;
|
||||
|
||||
// create socket
|
||||
std::string errorMsg;
|
||||
bool tls = _socketTLSOptions.tls;
|
||||
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
|
||||
|
||||
if (socket == nullptr)
|
||||
{
|
||||
logError("SocketServer::run() cannot create socket: " + errorMsg);
|
||||
Socket::closeSocket(clientFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the socket to non blocking mode + other tweaks
|
||||
SocketConnect::configure(clientFd);
|
||||
|
||||
if (!socket->accept(errorMsg))
|
||||
{
|
||||
logError("SocketServer::run() tls accept failed: " + errorMsg);
|
||||
Socket::closeSocket(clientFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Launch the handleConnection work asynchronously in its own thread.
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
_connectionsThreads.push_back(std::make_pair(
|
||||
connectionState,
|
||||
std::thread(
|
||||
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectionState> connectionState;
|
||||
if (_connectionStateFactory) {
|
||||
connectionState = _connectionStateFactory();
|
||||
}
|
||||
connectionState->setOnSetTerminatedCallback(
|
||||
[this] { onSetTerminatedCallback(); });
|
||||
connectionState->setRemoteIp(remoteIp);
|
||||
connectionState->setRemotePort(remotePort);
|
||||
|
||||
if (_stop)
|
||||
return;
|
||||
|
||||
// create socket
|
||||
std::string errorMsg;
|
||||
bool tls = _socketTLSOptions.tls;
|
||||
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
|
||||
|
||||
if (socket == nullptr) {
|
||||
logError("SocketServer::run() cannot create socket: " + errorMsg);
|
||||
Socket::closeSocket(clientFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the socket to non blocking mode + other tweaks
|
||||
SocketConnect::configure(clientFd);
|
||||
|
||||
if (!socket->accept(errorMsg)) {
|
||||
logError("SocketServer::run() tls accept failed: " + errorMsg);
|
||||
Socket::closeSocket(clientFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Launch the handleConnection work asynchronously in its own thread.
|
||||
size_t SocketServer::getConnectionsThreadsCount()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
_connectionsThreads.push_back(
|
||||
std::make_pair(connectionState,
|
||||
std::thread(&SocketServer::handleConnection,
|
||||
this,
|
||||
std::move(socket),
|
||||
connectionState)));
|
||||
return _connectionsThreads.size();
|
||||
}
|
||||
}
|
||||
|
||||
size_t SocketServer::getConnectionsThreadsCount()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
return _connectionsThreads.size();
|
||||
}
|
||||
void SocketServer::runGC()
|
||||
{
|
||||
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
|
||||
// $ echo Srv:gc:64000 | wc -c
|
||||
// 13
|
||||
setThreadName("Srv:gc:" + std::to_string(_port));
|
||||
|
||||
void SocketServer::runGC()
|
||||
{
|
||||
setThreadName("SocketServer::GC");
|
||||
for (;;)
|
||||
{
|
||||
// Garbage collection to shutdown/join threads for closed connections.
|
||||
closeTerminatedThreads();
|
||||
|
||||
for (;;) {
|
||||
// Garbage collection to shutdown/join threads for closed connections.
|
||||
closeTerminatedThreads();
|
||||
// We quit this thread if all connections are closed and we received
|
||||
// a stop request by setting _stopGc to true.
|
||||
if (_stopGc && getConnectionsThreadsCount() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We quit this thread if all connections are closed and we received
|
||||
// a stop request by setting _stopGc to true.
|
||||
if (_stopGc && getConnectionsThreadsCount() == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Unless we are stopping the server, wait for a connection
|
||||
// to be terminated to run the threads GC, instead of busy waiting
|
||||
// with a sleep
|
||||
if (!_stopGc) {
|
||||
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
|
||||
_conditionVariableGC.wait(lock);
|
||||
// Unless we are stopping the server, wait for a connection
|
||||
// to be terminated to run the threads GC, instead of busy waiting
|
||||
// with a sleep
|
||||
if (!_stopGc)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
|
||||
_conditionVariableGC.wait(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::setTLSOptions(const SocketTLSOptions &socketTLSOptions)
|
||||
{
|
||||
_socketTLSOptions = socketTLSOptions;
|
||||
}
|
||||
void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_socketTLSOptions = socketTLSOptions;
|
||||
}
|
||||
|
||||
void SocketServer::onSetTerminatedCallback()
|
||||
{
|
||||
// a connection got terminated, we can run the connection thread GC,
|
||||
// so wake up the thread responsible for that
|
||||
_conditionVariableGC.notify_one();
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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)) {
|
||||
_errMsg = "certFile not found: " + certFile;
|
||||
return false;
|
||||
}
|
||||
if (!keyFile.empty() && !std::ifstream(keyFile)) {
|
||||
_errMsg = "keyFile not found: " + keyFile;
|
||||
return false;
|
||||
}
|
||||
if (!caFile.empty() && caFile != kTLSCAFileDisableVerify &&
|
||||
caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile)) {
|
||||
_errMsg = "caFile not found: " + caFile;
|
||||
return false;
|
||||
}
|
||||
bool SocketTLSOptions::isValid() const
|
||||
{
|
||||
if (!_validated)
|
||||
{
|
||||
if (!certFile.empty() && !std::ifstream(certFile))
|
||||
{
|
||||
_errMsg = "certFile not found: " + certFile;
|
||||
return false;
|
||||
}
|
||||
if (!keyFile.empty() && !std::ifstream(keyFile))
|
||||
{
|
||||
_errMsg = "keyFile not found: " + keyFile;
|
||||
return false;
|
||||
}
|
||||
if (!caFile.empty() && caFile != kTLSCAFileDisableVerify &&
|
||||
caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile))
|
||||
{
|
||||
_errMsg = "caFile not found: " + caFile;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (certFile.empty() != keyFile.empty()) {
|
||||
_errMsg =
|
||||
"certFile and keyFile must be both present, or both absent";
|
||||
return false;
|
||||
}
|
||||
if (certFile.empty() != keyFile.empty())
|
||||
{
|
||||
_errMsg = "certFile and keyFile must be both present, or both absent";
|
||||
return false;
|
||||
}
|
||||
|
||||
_validated = true;
|
||||
_validated = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::hasCertAndKey() const
|
||||
{
|
||||
return !certFile.empty() && !keyFile.empty();
|
||||
}
|
||||
bool SocketTLSOptions::hasCertAndKey() const
|
||||
{
|
||||
return !certFile.empty() && !keyFile.empty();
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingSystemDefaults() const
|
||||
{
|
||||
return caFile == kTLSCAFileUseSystemDefaults;
|
||||
}
|
||||
bool SocketTLSOptions::isUsingSystemDefaults() const
|
||||
{
|
||||
return caFile == kTLSCAFileUseSystemDefaults;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingInMemoryCAs() const
|
||||
{
|
||||
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
|
||||
}
|
||||
bool SocketTLSOptions::isUsingInMemoryCAs() const
|
||||
{
|
||||
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||
{
|
||||
return caFile == kTLSCAFileDisableVerify;
|
||||
}
|
||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||
{
|
||||
return caFile == kTLSCAFileDisableVerify;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingDefaultCiphers() const
|
||||
{
|
||||
return ciphers.empty() || ciphers == kTLSCiphersUseDefault;
|
||||
}
|
||||
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::stringstream ss;
|
||||
ss << "TLS Options:" << std::endl;
|
||||
ss << " certFile = " << certFile << std::endl;
|
||||
ss << " keyFile = " << keyFile << std::endl;
|
||||
ss << " caFile = " << caFile << std::endl;
|
||||
ss << " ciphers = " << ciphers << std::endl;
|
||||
ss << " ciphers = " << ciphers << std::endl;
|
||||
return ss.str();
|
||||
}
|
||||
std::string SocketTLSOptions::getDescription() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "TLS Options:" << std::endl;
|
||||
ss << " certFile = " << certFile << std::endl;
|
||||
ss << " keyFile = " << keyFile << std::endl;
|
||||
ss << " caFile = " << caFile << std::endl;
|
||||
ss << " ciphers = " << ciphers << std::endl;
|
||||
ss << " tls = " << tls << std::endl;
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -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());
|
||||
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
|
||||
#else
|
||||
return std::tolower(c1) < std::tolower(c2);
|
||||
return std::tolower(c1) < std::tolower(c2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool CaseInsensitiveLess::cmp(const std::string &s1, const std::string &s2)
|
||||
{
|
||||
return std::lexicographical_compare(s1.begin(),
|
||||
s1.end(), // source range
|
||||
s2.begin(),
|
||||
s2.end(), // dest range
|
||||
NocaseCompare()); // comparison
|
||||
}
|
||||
bool CaseInsensitiveLess::cmp(const std::string& s1, const std::string& s2)
|
||||
{
|
||||
return std::lexicographical_compare(s1.begin(),
|
||||
s1.end(), // source range
|
||||
s2.begin(),
|
||||
s2.end(), // dest range
|
||||
NocaseCompare()); // comparison
|
||||
}
|
||||
|
||||
bool CaseInsensitiveLess::operator()(const std::string &s1,
|
||||
const std::string &s2) const
|
||||
{
|
||||
return CaseInsensitiveLess::cmp(s1, s2);
|
||||
}
|
||||
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
|
||||
{
|
||||
return CaseInsensitiveLess::cmp(s1, s2);
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+91
-90
@@ -12,114 +12,115 @@
|
||||
|
||||
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;
|
||||
}
|
||||
closeSocket(_sockfd);
|
||||
_sockfd = -1;
|
||||
}
|
||||
|
||||
int UdpSocket::getErrno()
|
||||
{
|
||||
int err;
|
||||
int UdpSocket::getErrno()
|
||||
{
|
||||
int err;
|
||||
|
||||
#ifdef _WIN32
|
||||
err = WSAGetLastError();
|
||||
err = WSAGetLastError();
|
||||
#else
|
||||
err = errno;
|
||||
err = errno;
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
bool UdpSocket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
bool UdpSocket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UdpSocket::closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UdpSocket::init(const std::string& host, int port, std::string& errMsg)
|
||||
{
|
||||
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (_sockfd < 0)
|
||||
{
|
||||
errMsg = "Could not create socket";
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned long nonblocking = 1;
|
||||
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
|
||||
#else
|
||||
fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
||||
#endif
|
||||
|
||||
memset(&_server, 0, sizeof(_server));
|
||||
_server.sin_family = AF_INET;
|
||||
_server.sin_port = htons(port);
|
||||
|
||||
// DNS resolution.
|
||||
struct addrinfo hints, *result = nullptr;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
int ret = getaddrinfo(host.c_str(), nullptr, &hints, &result);
|
||||
if (ret != 0)
|
||||
{
|
||||
errMsg = strerror(UdpSocket::getErrno());
|
||||
freeaddrinfo(result);
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_in* host_addr = (struct sockaddr_in*) result->ai_addr;
|
||||
memcpy(&_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr));
|
||||
freeaddrinfo(result);
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UdpSocket::closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UdpSocket::init(const std::string &host, int port, std::string &errMsg)
|
||||
{
|
||||
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (_sockfd < 0) {
|
||||
errMsg = "Could not create socket";
|
||||
return false;
|
||||
ssize_t UdpSocket::sendto(const std::string& buffer)
|
||||
{
|
||||
return (ssize_t)::sendto(
|
||||
_sockfd, buffer.data(), buffer.size(), 0, (struct sockaddr*) &_server, sizeof(_server));
|
||||
}
|
||||
|
||||
ssize_t UdpSocket::recvfrom(char* buffer, size_t length)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
unsigned long nonblocking = 1;
|
||||
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
|
||||
int addressLen = (int) sizeof(_server);
|
||||
#else
|
||||
fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
||||
socklen_t addressLen = (socklen_t) sizeof(_server);
|
||||
#endif
|
||||
|
||||
memset(&_server, 0, sizeof(_server));
|
||||
_server.sin_family = AF_INET;
|
||||
_server.sin_port = htons(port);
|
||||
|
||||
// DNS resolution.
|
||||
struct addrinfo hints, *result = nullptr;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
int ret = getaddrinfo(host.c_str(), nullptr, &hints, &result);
|
||||
if (ret != 0) {
|
||||
errMsg = strerror(UdpSocket::getErrno());
|
||||
freeaddrinfo(result);
|
||||
close();
|
||||
return false;
|
||||
return (ssize_t)::recvfrom(
|
||||
_sockfd, buffer, length, 0, (struct sockaddr*) &_server, &addressLen);
|
||||
}
|
||||
|
||||
struct sockaddr_in *host_addr = (struct sockaddr_in *)result->ai_addr;
|
||||
memcpy(&_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr));
|
||||
freeaddrinfo(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t UdpSocket::sendto(const std::string &buffer)
|
||||
{
|
||||
return (ssize_t)::sendto(_sockfd,
|
||||
buffer.data(),
|
||||
buffer.size(),
|
||||
0,
|
||||
(struct sockaddr *)&_server,
|
||||
sizeof(_server));
|
||||
}
|
||||
|
||||
ssize_t UdpSocket::recvfrom(char *buffer, size_t length)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int addressLen = (int)sizeof(_server);
|
||||
#else
|
||||
socklen_t addressLen = (socklen_t)sizeof(_server);
|
||||
#endif
|
||||
return (ssize_t)::recvfrom(_sockfd,
|
||||
buffer,
|
||||
length,
|
||||
0,
|
||||
(struct sockaddr *)&_server,
|
||||
&addressLen);
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+305
-273
@@ -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,327 +37,359 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
enum LUrlParserError {
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL() : m_ErrorCode(LUrlParserError_Uninitialized) {}
|
||||
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
|
||||
|
||||
/// helper to convert the port number to int, return 'true' if the port is
|
||||
/// valid (within the 0..65535 range)
|
||||
bool GetPort(int *OutPort) const;
|
||||
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL(const std::string &URL);
|
||||
|
||||
private:
|
||||
explicit clParseURL(LUrlParserError ErrorCode) : m_ErrorCode(ErrorCode) {}
|
||||
};
|
||||
|
||||
static bool IsSchemeValid(const std::string &SchemeName)
|
||||
{
|
||||
for (auto c : SchemeName) {
|
||||
if (!isalpha(c) && c != '+' && c != '-' && c != '.')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool clParseURL::GetPort(int *OutPort) const
|
||||
{
|
||||
if (!IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int Port = atoi(m_Port.c_str());
|
||||
|
||||
if (Port <= 0 || Port > 65535) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutPort) {
|
||||
*OutPort = Port;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// based on RFC 1738 and RFC 3986
|
||||
clParseURL clParseURL::ParseURL(const std::string &URL)
|
||||
{
|
||||
clParseURL Result;
|
||||
|
||||
const char *CurrentString = URL.c_str();
|
||||
|
||||
/*
|
||||
* <scheme>:<scheme-specific-part>
|
||||
* <scheme> := [a-z\+\-\.]+
|
||||
* For resiliency, programs interpreting URLs should treat upper case
|
||||
*letters as equivalent to lower case in scheme names
|
||||
*/
|
||||
|
||||
// try to read scheme
|
||||
enum LUrlParserError
|
||||
{
|
||||
const char *LocalString = strchr(CurrentString, ':');
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
if (!LocalString) {
|
||||
return clParseURL(LUrlParserError_NoUrlCharacter);
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL()
|
||||
: m_ErrorCode(LUrlParserError_Uninitialized)
|
||||
{
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme =
|
||||
std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
if (!IsSchemeValid(Result.m_Scheme)) {
|
||||
return clParseURL(LUrlParserError_InvalidSchemeName);
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const
|
||||
{
|
||||
return m_ErrorCode == LUrlParserError_Ok;
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform(Result.m_Scheme.begin(),
|
||||
Result.m_Scheme.end(),
|
||||
Result.m_Scheme.begin(),
|
||||
::tolower);
|
||||
/// 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;
|
||||
|
||||
// skip ':'
|
||||
CurrentString = LocalString + 1;
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL(const std::string& URL);
|
||||
|
||||
private:
|
||||
explicit clParseURL(LUrlParserError ErrorCode)
|
||||
: m_ErrorCode(ErrorCode)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static bool IsSchemeValid(const std::string& SchemeName)
|
||||
{
|
||||
for (auto c : SchemeName)
|
||||
{
|
||||
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
// skip "//"
|
||||
if (*CurrentString++ != '/')
|
||||
return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
if (*CurrentString++ != '/')
|
||||
return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char *LocalString = CurrentString;
|
||||
|
||||
while (*LocalString) {
|
||||
if (*LocalString == '@') {
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
} else if (*LocalString == '/') {
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
bool clParseURL::GetPort(int* OutPort) const
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
int Port = atoi(m_Port.c_str());
|
||||
|
||||
if (Port <= 0 || Port > 65535)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutPort)
|
||||
{
|
||||
*OutPort = Port;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
// based on RFC 1738 and RFC 3986
|
||||
clParseURL clParseURL::ParseURL(const std::string& URL)
|
||||
{
|
||||
clParseURL Result;
|
||||
|
||||
if (bHasUserName) {
|
||||
// read user name
|
||||
while (*LocalString && *LocalString != ':' && *LocalString != '@')
|
||||
LocalString++;
|
||||
const char* CurrentString = URL.c_str();
|
||||
|
||||
Result.m_UserName =
|
||||
std::string(CurrentString, LocalString - CurrentString);
|
||||
/*
|
||||
* <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
|
||||
*/
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
// try to read scheme
|
||||
{
|
||||
const char* LocalString = strchr(CurrentString, ':');
|
||||
|
||||
if (!LocalString)
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoUrlCharacter);
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
if (!IsSchemeValid(Result.m_Scheme))
|
||||
{
|
||||
return clParseURL(LUrlParserError_InvalidSchemeName);
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform(
|
||||
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
|
||||
|
||||
if (*CurrentString == ':') {
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
CurrentString = LocalString + 1;
|
||||
}
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
while (*LocalString && *LocalString != '@')
|
||||
// skip "//"
|
||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char* LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
{
|
||||
if (*LocalString == '@')
|
||||
{
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if (*LocalString == '/')
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
|
||||
if (bHasUserName)
|
||||
{
|
||||
// read user name
|
||||
while (*LocalString && *LocalString != ':' && *LocalString != '@')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Password =
|
||||
std::string(CurrentString, LocalString - CurrentString);
|
||||
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
|
||||
if (*CurrentString == ':')
|
||||
{
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '@')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if (*CurrentString != '@')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoAtSign);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = (*CurrentString == '[');
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
{
|
||||
if (bHasBracket && *LocalString == ']')
|
||||
{
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if (*CurrentString == ':')
|
||||
{
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '/')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if (*CurrentString != '@') {
|
||||
return clParseURL(LUrlParserError_NoAtSign);
|
||||
// end of string
|
||||
if (!*CurrentString)
|
||||
{
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if (*CurrentString != '/')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoSlash);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = (*CurrentString == '[');
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString) {
|
||||
if (bHasBracket && *LocalString == ']') {
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
} else if (!bHasBracket &&
|
||||
(*LocalString == ':' || *LocalString == '/')) {
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if (*CurrentString == ':') {
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '/')
|
||||
while (*LocalString && *LocalString != '#' && *LocalString != '?')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
|
||||
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// end of string
|
||||
if (!*CurrentString) {
|
||||
// check for query
|
||||
if (*CurrentString == '?')
|
||||
{
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if (*CurrentString == '#')
|
||||
{
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
LocalString++;
|
||||
|
||||
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if (*CurrentString != '/') {
|
||||
return clParseURL(LUrlParserError_NoSlash);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#' && *LocalString != '?')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// check for query
|
||||
if (*CurrentString == '?') {
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Query =
|
||||
std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if (*CurrentString == '#') {
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
LocalString++;
|
||||
|
||||
Result.m_Fragment =
|
||||
std::string(CurrentString, LocalString - CurrentString);
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ix
|
||||
{
|
||||
bool UrlParser::parse(const std::string &url,
|
||||
std::string &protocol,
|
||||
std::string &host,
|
||||
std::string &path,
|
||||
std::string &query,
|
||||
int &port)
|
||||
{
|
||||
clParseURL res = clParseURL::ParseURL(url);
|
||||
bool UrlParser::parse(const std::string& url,
|
||||
std::string& protocol,
|
||||
std::string& host,
|
||||
std::string& path,
|
||||
std::string& query,
|
||||
int& port)
|
||||
{
|
||||
clParseURL res = clParseURL::ParseURL(url);
|
||||
|
||||
if (!res.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protocol = res.m_Scheme;
|
||||
host = res.m_Host;
|
||||
path = res.m_Path;
|
||||
query = res.m_Query;
|
||||
|
||||
if (!res.GetPort(&port)) {
|
||||
if (protocol == "ws" || protocol == "http") {
|
||||
port = 80;
|
||||
} else if (protocol == "wss" || protocol == "https") {
|
||||
port = 443;
|
||||
} else {
|
||||
// Invalid protocol. Should be caught by regex check
|
||||
// but this missing branch trigger cpplint linter.
|
||||
if (!res.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.empty()) {
|
||||
path = "/";
|
||||
} else if (path[0] != '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
protocol = res.m_Scheme;
|
||||
host = res.m_Host;
|
||||
path = res.m_Path;
|
||||
query = res.m_Query;
|
||||
|
||||
if (!query.empty()) {
|
||||
path += "?";
|
||||
path += query;
|
||||
}
|
||||
if (!res.GetPort(&port))
|
||||
{
|
||||
if (protocol == "ws" || protocol == "http")
|
||||
{
|
||||
port = 80;
|
||||
}
|
||||
else if (protocol == "wss" || protocol == "https")
|
||||
{
|
||||
port = 443;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid protocol. Should be caught by regex check
|
||||
// but this missing branch trigger cpplint linter.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if (path.empty())
|
||||
{
|
||||
path = "/";
|
||||
}
|
||||
else if (path[0] != '/')
|
||||
{
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
if (!query.empty())
|
||||
{
|
||||
path += "?";
|
||||
path += query;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
+18
-21
@@ -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,34 +56,34 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
std::string userAgent()
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::string userAgent()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
// IXWebSocket Version
|
||||
ss << "ixwebsocket/" << IX_WEBSOCKET_VERSION;
|
||||
// IXWebSocket Version
|
||||
ss << "ixwebsocket/" << IX_WEBSOCKET_VERSION;
|
||||
|
||||
// Platform
|
||||
ss << " " << PLATFORM_NAME;
|
||||
// Platform
|
||||
ss << " " << PLATFORM_NAME;
|
||||
|
||||
// TLS
|
||||
// TLS
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
ss << " ssl/mbedtls " << MBEDTLS_VERSION_STRING;
|
||||
ss << " ssl/mbedtls " << MBEDTLS_VERSION_STRING;
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
ss << " ssl/OpenSSL " << OPENSSL_VERSION_TEXT;
|
||||
ss << " ssl/OpenSSL " << OPENSSL_VERSION_TEXT;
|
||||
#elif __APPLE__
|
||||
ss << " ssl/SecureTransport";
|
||||
ss << " ssl/SecureTransport";
|
||||
#endif
|
||||
#else
|
||||
ss << " nossl";
|
||||
ss << " nossl";
|
||||
#endif
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
// Zlib version
|
||||
ss << " zlib " << ZLIB_VERSION;
|
||||
// Zlib version
|
||||
ss << " zlib " << ZLIB_VERSION;
|
||||
#endif
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+39
-39
@@ -24,52 +24,52 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class Uuid
|
||||
{
|
||||
public:
|
||||
Uuid();
|
||||
std::string toString() const;
|
||||
class Uuid
|
||||
{
|
||||
public:
|
||||
Uuid();
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
uint64_t _ab;
|
||||
uint64_t _cd;
|
||||
};
|
||||
private:
|
||||
uint64_t _ab;
|
||||
uint64_t _cd;
|
||||
};
|
||||
|
||||
Uuid::Uuid()
|
||||
{
|
||||
static std::random_device rd;
|
||||
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
|
||||
Uuid::Uuid()
|
||||
{
|
||||
static std::random_device rd;
|
||||
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
|
||||
|
||||
_ab = dist(rd);
|
||||
_cd = dist(rd);
|
||||
_ab = dist(rd);
|
||||
_cd = dist(rd);
|
||||
|
||||
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
|
||||
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
|
||||
}
|
||||
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
|
||||
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
|
||||
}
|
||||
|
||||
std::string Uuid::toString() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::nouppercase << std::setfill('0');
|
||||
std::string Uuid::toString() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::nouppercase << std::setfill('0');
|
||||
|
||||
uint32_t a = (_ab >> 32);
|
||||
uint32_t b = (_ab & 0xFFFFFFFF);
|
||||
uint32_t c = (_cd >> 32);
|
||||
uint32_t d = (_cd & 0xFFFFFFFF);
|
||||
uint32_t a = (_ab >> 32);
|
||||
uint32_t b = (_ab & 0xFFFFFFFF);
|
||||
uint32_t c = (_cd >> 32);
|
||||
uint32_t d = (_cd & 0xFFFFFFFF);
|
||||
|
||||
ss << std::setw(8) << (a);
|
||||
ss << std::setw(4) << (b >> 16);
|
||||
ss << std::setw(4) << (b & 0xFFFF);
|
||||
ss << std::setw(4) << (c >> 16);
|
||||
ss << std::setw(4) << (c & 0xFFFF);
|
||||
ss << std::setw(8) << d;
|
||||
ss << std::setw(8) << (a);
|
||||
ss << std::setw(4) << (b >> 16);
|
||||
ss << std::setw(4) << (b & 0xFFFF);
|
||||
ss << std::setw(4) << (c >> 16);
|
||||
ss << std::setw(4) << (c & 0xFFFF);
|
||||
ss << std::setw(8) << d;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string uuid4()
|
||||
{
|
||||
Uuid id;
|
||||
return id.toString();
|
||||
}
|
||||
std::string uuid4()
|
||||
{
|
||||
Uuid id;
|
||||
return id.toString();
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
+570
-544
File diff suppressed because it is too large
Load Diff
@@ -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(
|
||||
"Ping reason control frame with payload length > 125 octets");
|
||||
const std::string
|
||||
WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented(
|
||||
const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure");
|
||||
const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error");
|
||||
const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure");
|
||||
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
|
||||
const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorReservedBitUsed("Reserved bit used");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorPingPayloadOversized(
|
||||
"Ping reason control frame with payload length > 125 octets");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented(
|
||||
"Control message fragmented");
|
||||
const std::string
|
||||
WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence(
|
||||
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(
|
||||
"Invalid frame payload data");
|
||||
const std::string
|
||||
WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code");
|
||||
const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage(
|
||||
"Invalid frame payload data");
|
||||
const std::string WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code");
|
||||
} // namespace ix
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXWebSocketHandshake.h"
|
||||
|
||||
#include "IXBase64.h"
|
||||
#include "IXHttp.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXStrCaseCompare.h"
|
||||
@@ -17,349 +18,346 @@
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
WebSocketHandshake::WebSocketHandshake(
|
||||
std::atomic<bool> &requestInitCancellation,
|
||||
std::unique_ptr<Socket> &socket,
|
||||
WebSocketPerMessageDeflatePtr &perMessageDeflate,
|
||||
WebSocketPerMessageDeflateOptions &perMessageDeflateOptions,
|
||||
std::atomic<bool> &enablePerMessageDeflate)
|
||||
: _requestInitCancellation(requestInitCancellation), _socket(socket),
|
||||
_perMessageDeflate(perMessageDeflate),
|
||||
_perMessageDeflateOptions(perMessageDeflateOptions),
|
||||
_enablePerMessageDeflate(enablePerMessageDeflate)
|
||||
{
|
||||
}
|
||||
|
||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string &a,
|
||||
const std::string &b)
|
||||
{
|
||||
return CaseInsensitiveLess::cmp(a, b) == 0;
|
||||
}
|
||||
|
||||
std::string WebSocketHandshake::genRandomString(const int len)
|
||||
{
|
||||
std::string alphanum = "0123456789"
|
||||
"ABCDEFGH"
|
||||
"abcdefgh";
|
||||
|
||||
std::random_device r;
|
||||
std::default_random_engine e1(r());
|
||||
std::uniform_int_distribution<int> dist(0, (int)alphanum.size() - 1);
|
||||
|
||||
std::string s;
|
||||
s.resize(len);
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int x = dist(e1);
|
||||
s[i] = alphanum[x];
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
WebSocketInitResult
|
||||
WebSocketHandshake::sendErrorResponse(int code, const std::string &reason)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 ";
|
||||
ss << code;
|
||||
ss << " ";
|
||||
ss << reason;
|
||||
ss << "\r\n";
|
||||
ss << "Server: " << userAgent() << "\r\n";
|
||||
|
||||
// Socket write can only be cancelled through a timeout here, not manually.
|
||||
static std::atomic<bool> requestInitCancellation(false);
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(1, requestInitCancellation);
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested)) {
|
||||
return WebSocketInitResult(false,
|
||||
500,
|
||||
"Timed out while sending error response");
|
||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
||||
{
|
||||
return CaseInsensitiveLess::cmp(a, b) == 0;
|
||||
}
|
||||
|
||||
return WebSocketInitResult(false, code, reason);
|
||||
}
|
||||
std::string WebSocketHandshake::genRandomString(const int len)
|
||||
{
|
||||
std::string alphanum = "0123456789"
|
||||
"ABCDEFGH"
|
||||
"abcdefgh";
|
||||
|
||||
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
||||
const std::string &url,
|
||||
const WebSocketHttpHeaders &extraHeaders,
|
||||
const std::string &host,
|
||||
const std::string &path,
|
||||
int port,
|
||||
int timeoutSecs)
|
||||
{
|
||||
_requestInitCancellation = false;
|
||||
std::random_device r;
|
||||
std::default_random_engine e1(r());
|
||||
std::uniform_int_distribution<int> dist(0, (int) alphanum.size() - 1);
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs,
|
||||
_requestInitCancellation);
|
||||
std::string s;
|
||||
s.resize(len);
|
||||
|
||||
std::string errMsg;
|
||||
bool success =
|
||||
_socket->connect(host, port, errMsg, isCancellationRequested);
|
||||
if (!success) {
|
||||
std::stringstream ss;
|
||||
ss << "Unable to connect to " << host << " on port " << port
|
||||
<< ", error: " << errMsg;
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
}
|
||||
|
||||
//
|
||||
// Generate a random 24 bytes string which looks like it is base64 encoded
|
||||
// y3JJHMbDL1EzLkh9GBhXDw==
|
||||
// 0cb3Vd9HkbpVVumoS3Noka==
|
||||
//
|
||||
// See
|
||||
// https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
|
||||
//
|
||||
std::string secWebSocketKey = genRandomString(22);
|
||||
secWebSocketKey += "==";
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "GET " << path << " HTTP/1.1\r\n";
|
||||
ss << "Host: " << host << ":" << port << "\r\n";
|
||||
ss << "Upgrade: websocket\r\n";
|
||||
ss << "Connection: Upgrade\r\n";
|
||||
ss << "Sec-WebSocket-Version: 13\r\n";
|
||||
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
|
||||
|
||||
// User-Agent can be customized by users
|
||||
if (extraHeaders.find("User-Agent") == extraHeaders.end()) {
|
||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||
}
|
||||
|
||||
for (auto &it : extraHeaders) {
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
|
||||
if (_enablePerMessageDeflate) {
|
||||
ss << _perMessageDeflateOptions.generateHeader();
|
||||
}
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested)) {
|
||||
return WebSocketInitResult(
|
||||
false,
|
||||
0,
|
||||
std::string("Failed sending GET request to ") + url);
|
||||
}
|
||||
|
||||
// Read HTTP status line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid) {
|
||||
return WebSocketInitResult(
|
||||
false,
|
||||
0,
|
||||
std::string("Failed reading HTTP status line from ") + url);
|
||||
}
|
||||
|
||||
// Validate status
|
||||
auto statusLine = Http::parseStatusLine(line);
|
||||
std::string httpVersion = statusLine.first;
|
||||
int status = statusLine.second;
|
||||
|
||||
// HTTP/1.0 is too old.
|
||||
if (httpVersion != "HTTP/1.1") {
|
||||
std::stringstream ss;
|
||||
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
|
||||
<< "Rejecting connection to " << url << ", status: " << status
|
||||
<< ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid) {
|
||||
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
||||
}
|
||||
|
||||
// We want an 101 HTTP status for websocket, otherwise it could be
|
||||
// a redirection (like 301)
|
||||
if (status != 101) {
|
||||
std::stringstream ss;
|
||||
ss << "Expecting status 101 (Switching Protocol), got " << status
|
||||
<< " status connecting to " << url << ", HTTP Status line: " << line;
|
||||
|
||||
return WebSocketInitResult(false, status, ss.str(), headers, path);
|
||||
}
|
||||
|
||||
// Check the presence of the connection field
|
||||
if (headers.find("connection") == headers.end()) {
|
||||
std::string errorMsg("Missing connection value");
|
||||
return WebSocketInitResult(false, status, errorMsg);
|
||||
}
|
||||
|
||||
// Check the value of the connection field
|
||||
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
||||
// connection header, so do a case insensitive comparison
|
||||
//
|
||||
// See
|
||||
// https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a
|
||||
//
|
||||
if (!insensitiveStringCompare(headers["connection"], "Upgrade")) {
|
||||
std::stringstream ss;
|
||||
ss << "Invalid connection value: " << headers["connection"];
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
char output[29] = {};
|
||||
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
|
||||
if (std::string(output) != headers["sec-websocket-accept"]) {
|
||||
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
|
||||
return WebSocketInitResult(false, status, errorMsg);
|
||||
}
|
||||
|
||||
if (_enablePerMessageDeflate) {
|
||||
// Parse the server response. Does it support deflate ?
|
||||
std::string header = headers["sec-websocket-extensions"];
|
||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
|
||||
header);
|
||||
|
||||
// If the server does not support that extension, disable it.
|
||||
if (!webSocketPerMessageDeflateOptions.enabled()) {
|
||||
_enablePerMessageDeflate = false;
|
||||
for (int i = 0; i < len; ++i)
|
||||
{
|
||||
int x = dist(e1);
|
||||
s[i] = alphanum[x];
|
||||
}
|
||||
// Otherwise try to initialize the deflate engine (zlib)
|
||||
else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions)) {
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 ";
|
||||
ss << code;
|
||||
ss << " ";
|
||||
ss << reason;
|
||||
ss << "\r\n";
|
||||
ss << "Server: " << userAgent() << "\r\n";
|
||||
|
||||
// Socket write can only be cancelled through a timeout here, not manually.
|
||||
static std::atomic<bool> requestInitCancellation(false);
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(1, requestInitCancellation);
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
||||
{
|
||||
return WebSocketInitResult(false, 500, "Timed out while sending error response");
|
||||
}
|
||||
|
||||
return WebSocketInitResult(false, code, reason);
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
||||
const std::string& url,
|
||||
const WebSocketHttpHeaders& extraHeaders,
|
||||
const std::string& host,
|
||||
const std::string& path,
|
||||
int port,
|
||||
int timeoutSecs)
|
||||
{
|
||||
_requestInitCancellation = false;
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
||||
|
||||
std::string errMsg;
|
||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||
if (!success)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unable to connect to " << host << " on port " << port << ", error: " << errMsg;
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
}
|
||||
|
||||
// Generate a random 16 bytes string and base64 encode it.
|
||||
//
|
||||
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
|
||||
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "GET " << path << " HTTP/1.1\r\n";
|
||||
ss << "Host: " << host << ":" << port << "\r\n";
|
||||
ss << "Upgrade: websocket\r\n";
|
||||
ss << "Connection: Upgrade\r\n";
|
||||
ss << "Sec-WebSocket-Version: 13\r\n";
|
||||
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
|
||||
|
||||
// User-Agent can be customized by users
|
||||
if (extraHeaders.find("User-Agent") == extraHeaders.end())
|
||||
{
|
||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||
}
|
||||
|
||||
for (auto& it : extraHeaders)
|
||||
{
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
|
||||
if (_enablePerMessageDeflate)
|
||||
{
|
||||
ss << _perMessageDeflateOptions.generateHeader();
|
||||
}
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
||||
{
|
||||
return WebSocketInitResult(
|
||||
false,
|
||||
0,
|
||||
"Failed to initialize per message deflate engine");
|
||||
false, 0, std::string("Failed sending GET request to ") + url);
|
||||
}
|
||||
|
||||
// Read HTTP status line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid)
|
||||
{
|
||||
return WebSocketInitResult(
|
||||
false, 0, std::string("Failed reading HTTP status line from ") + url);
|
||||
}
|
||||
|
||||
// Validate status
|
||||
auto statusLine = Http::parseStatusLine(line);
|
||||
std::string httpVersion = statusLine.first;
|
||||
int status = statusLine.second;
|
||||
|
||||
// HTTP/1.0 is too old.
|
||||
if (httpVersion != "HTTP/1.1")
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
|
||||
<< "Rejecting connection to " << url << ", status: " << status
|
||||
<< ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid)
|
||||
{
|
||||
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
||||
}
|
||||
|
||||
// We want an 101 HTTP status for websocket, otherwise it could be
|
||||
// a redirection (like 301)
|
||||
if (status != 101)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Expecting status 101 (Switching Protocol), got " << status
|
||||
<< " status connecting to " << url << ", HTTP Status line: " << line;
|
||||
|
||||
return WebSocketInitResult(false, status, ss.str(), headers, path);
|
||||
}
|
||||
|
||||
// Check the presence of the connection field
|
||||
if (headers.find("connection") == headers.end())
|
||||
{
|
||||
std::string errorMsg("Missing connection value");
|
||||
return WebSocketInitResult(false, status, errorMsg);
|
||||
}
|
||||
|
||||
// Check the value of the connection field
|
||||
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
||||
// connection header, so do a case insensitive comparison
|
||||
//
|
||||
// See https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a
|
||||
//
|
||||
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Invalid connection value: " << headers["connection"];
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
char output[29] = {};
|
||||
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
|
||||
if (std::string(output) != headers["sec-websocket-accept"])
|
||||
{
|
||||
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
|
||||
return WebSocketInitResult(false, status, errorMsg);
|
||||
}
|
||||
|
||||
if (_enablePerMessageDeflate)
|
||||
{
|
||||
// Parse the server response. Does it support deflate ?
|
||||
std::string header = headers["sec-websocket-extensions"];
|
||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
||||
|
||||
// If the server does not support that extension, disable it.
|
||||
if (!webSocketPerMessageDeflateOptions.enabled())
|
||||
{
|
||||
_enablePerMessageDeflate = false;
|
||||
}
|
||||
// Otherwise try to initialize the deflate engine (zlib)
|
||||
else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
|
||||
{
|
||||
return WebSocketInitResult(
|
||||
false, 0, "Failed to initialize per message deflate engine");
|
||||
}
|
||||
}
|
||||
|
||||
return WebSocketInitResult(true, status, "", headers, path);
|
||||
}
|
||||
|
||||
return WebSocketInitResult(true, status, "", headers, path);
|
||||
}
|
||||
|
||||
WebSocketInitResult
|
||||
WebSocketHandshake::serverHandshake(int timeoutSecs,
|
||||
bool enablePerMessageDeflate)
|
||||
{
|
||||
_requestInitCancellation = false;
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs,
|
||||
_requestInitCancellation);
|
||||
|
||||
// Read first line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid) {
|
||||
return sendErrorResponse(400, "Error reading HTTP request line");
|
||||
}
|
||||
|
||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
if (method != "GET") {
|
||||
return sendErrorResponse(400,
|
||||
"Invalid HTTP method, need GET, got " +
|
||||
method);
|
||||
}
|
||||
|
||||
if (httpVersion != "HTTP/1.1") {
|
||||
return sendErrorResponse(400,
|
||||
"Invalid HTTP version, need HTTP/1.1, got: " +
|
||||
httpVersion);
|
||||
}
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid) {
|
||||
return sendErrorResponse(400, "Error parsing HTTP headers");
|
||||
}
|
||||
|
||||
if (headers.find("sec-websocket-key") == headers.end()) {
|
||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
|
||||
}
|
||||
|
||||
if (headers.find("upgrade") == headers.end()) {
|
||||
return sendErrorResponse(400, "Missing Upgrade header");
|
||||
}
|
||||
|
||||
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket") &&
|
||||
headers["Upgrade"] != "keep-alive, Upgrade") // special case for firefox
|
||||
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
|
||||
bool enablePerMessageDeflate)
|
||||
{
|
||||
return sendErrorResponse(400,
|
||||
"Invalid Upgrade header, "
|
||||
"need WebSocket, got " +
|
||||
headers["upgrade"]);
|
||||
}
|
||||
_requestInitCancellation = false;
|
||||
|
||||
if (headers.find("sec-websocket-version") == headers.end()) {
|
||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Version value");
|
||||
}
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << headers["sec-websocket-version"];
|
||||
int version;
|
||||
ss >> version;
|
||||
// Read first line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (version != 13) {
|
||||
if (!lineValid)
|
||||
{
|
||||
return sendErrorResponse(400, "Error reading HTTP request line");
|
||||
}
|
||||
|
||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
if (method != "GET")
|
||||
{
|
||||
return sendErrorResponse(400, "Invalid HTTP method, need GET, got " + method);
|
||||
}
|
||||
|
||||
if (httpVersion != "HTTP/1.1")
|
||||
{
|
||||
return sendErrorResponse(400,
|
||||
"Invalid Sec-WebSocket-Version, "
|
||||
"need 13, got " +
|
||||
ss.str());
|
||||
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
||||
}
|
||||
}
|
||||
|
||||
char output[29] = {};
|
||||
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
||||
ss << "Upgrade: websocket\r\n";
|
||||
ss << "Connection: Upgrade\r\n";
|
||||
ss << "Server: " << userAgent() << "\r\n";
|
||||
if (!headersValid)
|
||||
{
|
||||
return sendErrorResponse(400, "Error parsing HTTP headers");
|
||||
}
|
||||
|
||||
// Parse the client headers. Does it support deflate ?
|
||||
std::string header = headers["sec-websocket-extensions"];
|
||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
||||
if (headers.find("sec-websocket-key") == headers.end())
|
||||
{
|
||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
|
||||
}
|
||||
|
||||
// If the client has requested that extension,
|
||||
if (webSocketPerMessageDeflateOptions.enabled() &&
|
||||
enablePerMessageDeflate) {
|
||||
_enablePerMessageDeflate = true;
|
||||
if (headers.find("upgrade") == headers.end())
|
||||
{
|
||||
return sendErrorResponse(400, "Missing Upgrade header");
|
||||
}
|
||||
|
||||
if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions)) {
|
||||
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket") &&
|
||||
headers["Upgrade"] != "keep-alive, Upgrade") // special case for firefox
|
||||
{
|
||||
return sendErrorResponse(400,
|
||||
"Invalid Upgrade header, "
|
||||
"need WebSocket, got " +
|
||||
headers["upgrade"]);
|
||||
}
|
||||
|
||||
if (headers.find("sec-websocket-version") == headers.end())
|
||||
{
|
||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Version value");
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << headers["sec-websocket-version"];
|
||||
int version;
|
||||
ss >> version;
|
||||
|
||||
if (version != 13)
|
||||
{
|
||||
return sendErrorResponse(400,
|
||||
"Invalid Sec-WebSocket-Version, "
|
||||
"need 13, got " +
|
||||
ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
char output[29] = {};
|
||||
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
||||
ss << "Upgrade: websocket\r\n";
|
||||
ss << "Connection: Upgrade\r\n";
|
||||
ss << "Server: " << userAgent() << "\r\n";
|
||||
|
||||
// Parse the client headers. Does it support deflate ?
|
||||
std::string header = headers["sec-websocket-extensions"];
|
||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
||||
|
||||
// If the client has requested that extension,
|
||||
if (webSocketPerMessageDeflateOptions.enabled() && enablePerMessageDeflate)
|
||||
{
|
||||
_enablePerMessageDeflate = true;
|
||||
|
||||
if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
|
||||
{
|
||||
return WebSocketInitResult(
|
||||
false, 0, "Failed to initialize per message deflate engine");
|
||||
}
|
||||
ss << webSocketPerMessageDeflateOptions.generateHeader();
|
||||
}
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
||||
{
|
||||
return WebSocketInitResult(
|
||||
false,
|
||||
0,
|
||||
"Failed to initialize per message deflate engine");
|
||||
false, 0, std::string("Failed sending response to remote end"));
|
||||
}
|
||||
ss << webSocketPerMessageDeflateOptions.generateHeader();
|
||||
|
||||
return WebSocketInitResult(true, 200, "", headers, uri);
|
||||
}
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested)) {
|
||||
return WebSocketInitResult(
|
||||
false,
|
||||
0,
|
||||
std::string("Failed sending response to remote end"));
|
||||
}
|
||||
|
||||
return WebSocketInitResult(true, 200, "", headers, uri);
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -12,60 +12,63 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
std::pair<bool, WebSocketHttpHeaders>
|
||||
parseHttpHeaders(std::unique_ptr<Socket> &socket,
|
||||
const CancellationRequest &isCancellationRequested)
|
||||
{
|
||||
WebSocketHttpHeaders headers;
|
||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
||||
std::unique_ptr<Socket>& socket, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
WebSocketHttpHeaders headers;
|
||||
|
||||
char line[1024];
|
||||
int i;
|
||||
char line[1024];
|
||||
int i;
|
||||
|
||||
while (true) {
|
||||
int colon = 0;
|
||||
while (true)
|
||||
{
|
||||
int colon = 0;
|
||||
|
||||
for (i = 0;
|
||||
i < 2 || (i < 1023 && line[i - 2] != '\r' && line[i - 1] != '\n');
|
||||
++i) {
|
||||
if (!socket->readByte(line + i, isCancellationRequested)) {
|
||||
return std::make_pair(false, headers);
|
||||
for (i = 0; i < 2 || (i < 1023 && line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
|
||||
{
|
||||
if (!socket->readByte(line + i, isCancellationRequested))
|
||||
{
|
||||
return std::make_pair(false, headers);
|
||||
}
|
||||
|
||||
if (line[i] == ':' && colon == 0)
|
||||
{
|
||||
colon = i;
|
||||
}
|
||||
}
|
||||
if (line[0] == '\r' && line[1] == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (line[i] == ':' && colon == 0) {
|
||||
colon = i;
|
||||
// line is a single header entry. split by ':', and add it to our
|
||||
// header map. ignore lines with no colon.
|
||||
if (colon > 0)
|
||||
{
|
||||
line[i] = '\0';
|
||||
std::string lineStr(line);
|
||||
// colon is ':', usually colon+1 is ' ', and colon+2 is the start of the value.
|
||||
// some webservers do not put a space after the colon character, so
|
||||
// the start of the value might be farther than colon+2.
|
||||
// The spec says that space after the : should be discarded.
|
||||
// i is end of string (\0), i-colon is length of string minus key;
|
||||
// subtract 1 for '\0', 1 for '\n', 1 for '\r',
|
||||
// 1 for the ' ' after the ':', and total is -4
|
||||
// since we use an std::string later on and don't account for '\0',
|
||||
// plus the optional first space, total is -2
|
||||
int start = colon + 1;
|
||||
while (lineStr[start] == ' ')
|
||||
{
|
||||
start++;
|
||||
}
|
||||
|
||||
std::string name(lineStr.substr(0, colon));
|
||||
std::string value(lineStr.substr(start, lineStr.size() - start - 2));
|
||||
|
||||
headers[name] = value;
|
||||
}
|
||||
}
|
||||
if (line[0] == '\r' && line[1] == '\n') {
|
||||
break;
|
||||
}
|
||||
|
||||
// line is a single header entry. split by ':', and add it to our
|
||||
// header map. ignore lines with no colon.
|
||||
if (colon > 0) {
|
||||
line[i] = '\0';
|
||||
std::string lineStr(line);
|
||||
// colon is ':', usually colon+1 is ' ', and colon+2 is the start of
|
||||
// the value. some webservers do not put a space after the colon
|
||||
// character, so the start of the value might be farther than
|
||||
// colon+2. The spec says that space after the : should be
|
||||
// discarded. i is end of string (\0), i-colon is length of string
|
||||
// minus key; subtract 1 for '\0', 1 for '\n', 1 for '\r', 1 for the
|
||||
// ' ' after the ':', and total is -4 since we use an std::string
|
||||
// later on and don't account for '\0', plus the optional first
|
||||
// space, total is -2
|
||||
int start = colon + 1;
|
||||
while (lineStr[start] == ' ') {
|
||||
start++;
|
||||
}
|
||||
|
||||
std::string name(lineStr.substr(0, colon));
|
||||
std::string value(
|
||||
lineStr.substr(start, lineStr.size() - start - 2));
|
||||
|
||||
headers[name] = value;
|
||||
}
|
||||
return std::make_pair(true, headers);
|
||||
}
|
||||
|
||||
return std::make_pair(true, headers);
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -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();
|
||||
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
|
||||
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
|
||||
|
||||
return _compressor->init(deflateBits, clientNoContextTakeover) &&
|
||||
_decompressor->init(inflateBits, clientNoContextTakeover);
|
||||
}
|
||||
return _compressor->init(deflateBits, clientNoContextTakeover) &&
|
||||
_decompressor->init(inflateBits, clientNoContextTakeover);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflate::compress(const std::string &in,
|
||||
std::string &out)
|
||||
{
|
||||
return _compressor->compress(in, out);
|
||||
}
|
||||
bool WebSocketPerMessageDeflate::compress(const IXWebSocketSendData& in, std::string& out)
|
||||
{
|
||||
return _compressor->compress(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflate::decompress(const std::string &in,
|
||||
std::string &out)
|
||||
{
|
||||
return _decompressor->decompress(in, out);
|
||||
}
|
||||
bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out)
|
||||
{
|
||||
return _compressor->compress(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflate::decompress(const std::string& in, std::string& out)
|
||||
{
|
||||
return _decompressor->decompress(in, out);
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
@@ -12,242 +12,241 @@
|
||||
|
||||
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));
|
||||
memset(&_deflateState, 0, sizeof(_deflateState));
|
||||
|
||||
_deflateState.zalloc = Z_NULL;
|
||||
_deflateState.zfree = Z_NULL;
|
||||
_deflateState.opaque = Z_NULL;
|
||||
_deflateState.zalloc = Z_NULL;
|
||||
_deflateState.zfree = Z_NULL;
|
||||
_deflateState.opaque = Z_NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
|
||||
{
|
||||
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
deflateEnd(&_deflateState);
|
||||
deflateEnd(&_deflateState);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
|
||||
bool clientNoContextTakeOver)
|
||||
{
|
||||
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
|
||||
bool clientNoContextTakeOver)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
int ret = deflateInit2(&_deflateState,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
-1 * deflateBits,
|
||||
4, // memory level 1-9
|
||||
Z_DEFAULT_STRATEGY);
|
||||
int ret = deflateInit2(&_deflateState,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
-1 * deflateBits,
|
||||
4, // memory level 1-9
|
||||
Z_DEFAULT_STRATEGY);
|
||||
|
||||
if (ret != Z_OK)
|
||||
return false;
|
||||
if (ret != Z_OK) return false;
|
||||
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(
|
||||
const T &value)
|
||||
{
|
||||
if (kEmptyUncompressedBlock.size() > value.size())
|
||||
return false;
|
||||
auto N = value.size();
|
||||
return value[N - 1] == kEmptyUncompressedBlock[3] &&
|
||||
value[N - 2] == kEmptyUncompressedBlock[2] &&
|
||||
value[N - 3] == kEmptyUncompressedBlock[1] &&
|
||||
value[N - 4] == kEmptyUncompressedBlock[0];
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string &in,
|
||||
std::string &out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string &in,
|
||||
std::vector<uint8_t> &out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(
|
||||
const std::vector<uint8_t> &in,
|
||||
std::string &out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(
|
||||
const std::vector<uint8_t> &in,
|
||||
std::vector<uint8_t> &out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
template <typename T, typename S>
|
||||
bool WebSocketPerMessageDeflateCompressor::compressData(const T &in, S &out)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
//
|
||||
// 7.2.1. Compression
|
||||
//
|
||||
// An endpoint uses the following algorithm to compress a message.
|
||||
//
|
||||
// 1. Compress all the octets of the payload of the message using
|
||||
// DEFLATE.
|
||||
//
|
||||
// 2. If the resulting data does not end with an empty DEFLATE block
|
||||
// with no compression (the "BTYPE" bits are set to 00), append an
|
||||
// empty DEFLATE block with no compression to the tail end.
|
||||
//
|
||||
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
|
||||
// After this step, the last octet of the compressed data contains
|
||||
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
|
||||
// set to 00.
|
||||
//
|
||||
size_t output;
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
if (in.empty()) {
|
||||
// See issue #167
|
||||
// The normal buffer size should be 6 but
|
||||
// we remove the 4 octets from the tail (#4)
|
||||
uint8_t buf[2] = {0x02, 0x00};
|
||||
out.push_back(buf[0]);
|
||||
out.push_back(buf[1]);
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_deflateState.avail_in = (uInt)in.size();
|
||||
_deflateState.next_in = (Bytef *)in.data();
|
||||
|
||||
do {
|
||||
// Output to local buffer
|
||||
_deflateState.avail_out = (uInt)_compressBuffer.size();
|
||||
_deflateState.next_out = &_compressBuffer.front();
|
||||
|
||||
deflate(&_deflateState, _flush);
|
||||
|
||||
output = _compressBuffer.size() - _deflateState.avail_out;
|
||||
|
||||
out.insert(out.end(),
|
||||
_compressBuffer.begin(),
|
||||
_compressBuffer.begin() + output);
|
||||
} while (_deflateState.avail_out == 0);
|
||||
|
||||
if (endsWithEmptyUnCompressedBlock(out)) {
|
||||
out.resize(out.size() - 4);
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Decompressor
|
||||
//
|
||||
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
memset(&_inflateState, 0, sizeof(_inflateState));
|
||||
|
||||
_inflateState.zalloc = Z_NULL;
|
||||
_inflateState.zfree = Z_NULL;
|
||||
_inflateState.opaque = Z_NULL;
|
||||
_inflateState.avail_in = 0;
|
||||
_inflateState.next_in = Z_NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
WebSocketPerMessageDeflateDecompressor::
|
||||
~WebSocketPerMessageDeflateDecompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
inflateEnd(&_inflateState);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
|
||||
bool clientNoContextTakeOver)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
|
||||
|
||||
if (ret != Z_OK)
|
||||
return false;
|
||||
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string &in,
|
||||
std::string &out)
|
||||
{
|
||||
template<typename T>
|
||||
bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(const T& value)
|
||||
{
|
||||
if (kEmptyUncompressedBlock.size() > value.size()) return false;
|
||||
auto N = value.size();
|
||||
return value[N - 1] == kEmptyUncompressedBlock[3] &&
|
||||
value[N - 2] == kEmptyUncompressedBlock[2] &&
|
||||
value[N - 3] == kEmptyUncompressedBlock[1] &&
|
||||
value[N - 4] == kEmptyUncompressedBlock[0];
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const IXWebSocketSendData& in,
|
||||
std::string& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
|
||||
std::vector<uint8_t>& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
|
||||
std::string& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
|
||||
std::vector<uint8_t>& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
template<typename T, typename S>
|
||||
bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
//
|
||||
// 7.2.2. Decompression
|
||||
//
|
||||
// An endpoint uses the following algorithm to decompress a message.
|
||||
//
|
||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||
// payload of the message.
|
||||
//
|
||||
// 2. Decompress the resulting data using DEFLATE.
|
||||
//
|
||||
std::string inFixed(in);
|
||||
inFixed += kEmptyUncompressedBlock;
|
||||
//
|
||||
// 7.2.1. Compression
|
||||
//
|
||||
// An endpoint uses the following algorithm to compress a message.
|
||||
//
|
||||
// 1. Compress all the octets of the payload of the message using
|
||||
// DEFLATE.
|
||||
//
|
||||
// 2. If the resulting data does not end with an empty DEFLATE block
|
||||
// with no compression (the "BTYPE" bits are set to 00), append an
|
||||
// empty DEFLATE block with no compression to the tail end.
|
||||
//
|
||||
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
|
||||
// After this step, the last octet of the compressed data contains
|
||||
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
|
||||
// set to 00.
|
||||
//
|
||||
size_t output;
|
||||
|
||||
_inflateState.avail_in = (uInt)inFixed.size();
|
||||
_inflateState.next_in =
|
||||
(unsigned char *)(const_cast<char *>(inFixed.data()));
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
if (in.empty())
|
||||
{
|
||||
// See issue #167
|
||||
// The normal buffer size should be 6 but
|
||||
// we remove the 4 octets from the tail (#4)
|
||||
uint8_t buf[2] = {0x02, 0x00};
|
||||
out.push_back(buf[0]);
|
||||
out.push_back(buf[1]);
|
||||
|
||||
do {
|
||||
_inflateState.avail_out = (uInt)_compressBuffer.size();
|
||||
_inflateState.next_out = &_compressBuffer.front();
|
||||
|
||||
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
|
||||
|
||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
|
||||
return false; // zlib error
|
||||
return true;
|
||||
}
|
||||
|
||||
out.append(reinterpret_cast<char *>(&_compressBuffer.front()),
|
||||
_compressBuffer.size() - _inflateState.avail_out);
|
||||
} while (_inflateState.avail_out == 0);
|
||||
_deflateState.avail_in = (uInt) in.size();
|
||||
_deflateState.next_in = (Bytef*) in.data();
|
||||
|
||||
return true;
|
||||
do
|
||||
{
|
||||
// Output to local buffer
|
||||
_deflateState.avail_out = (uInt) _compressBuffer.size();
|
||||
_deflateState.next_out = &_compressBuffer.front();
|
||||
|
||||
deflate(&_deflateState, _flush);
|
||||
|
||||
output = _compressBuffer.size() - _deflateState.avail_out;
|
||||
|
||||
out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output);
|
||||
} while (_deflateState.avail_out == 0);
|
||||
|
||||
if (endsWithEmptyUnCompressedBlock(out))
|
||||
{
|
||||
out.resize(out.size() - 4);
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Decompressor
|
||||
//
|
||||
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
memset(&_inflateState, 0, sizeof(_inflateState));
|
||||
|
||||
_inflateState.zalloc = Z_NULL;
|
||||
_inflateState.zfree = Z_NULL;
|
||||
_inflateState.opaque = Z_NULL;
|
||||
_inflateState.avail_in = 0;
|
||||
_inflateState.next_in = Z_NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
inflateEnd(&_inflateState);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
|
||||
bool clientNoContextTakeOver)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
|
||||
|
||||
if (ret != Z_OK) return false;
|
||||
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
//
|
||||
// 7.2.2. Decompression
|
||||
//
|
||||
// An endpoint uses the following algorithm to decompress a message.
|
||||
//
|
||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||
// payload of the message.
|
||||
//
|
||||
// 2. Decompress the resulting data using DEFLATE.
|
||||
//
|
||||
std::string inFixed(in);
|
||||
inFixed += kEmptyUncompressedBlock;
|
||||
|
||||
_inflateState.avail_in = (uInt) inFixed.size();
|
||||
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
do
|
||||
{
|
||||
_inflateState.avail_out = (uInt) _compressBuffer.size();
|
||||
_inflateState.next_out = &_compressBuffer.front();
|
||||
|
||||
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
|
||||
|
||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
||||
{
|
||||
return false; // zlib error
|
||||
}
|
||||
|
||||
out.append(reinterpret_cast<char*>(&_compressBuffer.front()),
|
||||
_compressBuffer.size() - _inflateState.avail_out);
|
||||
} while (_inflateState.avail_out == 0);
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
@@ -12,182 +12,174 @@
|
||||
|
||||
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(
|
||||
bool enabled,
|
||||
bool clientNoContextTakeover,
|
||||
bool serverNoContextTakeover,
|
||||
uint8_t clientMaxWindowBits,
|
||||
uint8_t serverMaxWindowBits)
|
||||
{
|
||||
_enabled = enabled;
|
||||
_clientNoContextTakeover = clientNoContextTakeover;
|
||||
_serverNoContextTakeover = serverNoContextTakeover;
|
||||
_clientMaxWindowBits = clientMaxWindowBits;
|
||||
_serverMaxWindowBits = serverMaxWindowBits;
|
||||
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
|
||||
bool enabled,
|
||||
bool clientNoContextTakeover,
|
||||
bool serverNoContextTakeover,
|
||||
uint8_t clientMaxWindowBits,
|
||||
uint8_t serverMaxWindowBits)
|
||||
{
|
||||
_enabled = enabled;
|
||||
_clientNoContextTakeover = clientNoContextTakeover;
|
||||
_serverNoContextTakeover = serverNoContextTakeover;
|
||||
_clientMaxWindowBits = clientMaxWindowBits;
|
||||
_serverMaxWindowBits = serverMaxWindowBits;
|
||||
|
||||
sanitizeClientMaxWindowBits();
|
||||
}
|
||||
sanitizeClientMaxWindowBits();
|
||||
}
|
||||
|
||||
//
|
||||
// Four extension parameters are defined for "permessage-deflate" to
|
||||
// help endpoints manage per-connection resource usage.
|
||||
//
|
||||
// - "server_no_context_takeover"
|
||||
// - "client_no_context_takeover"
|
||||
// - "server_max_window_bits"
|
||||
// - "client_max_window_bits"
|
||||
//
|
||||
// Server response could look like that:
|
||||
//
|
||||
// Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover;
|
||||
// server_no_context_takeover
|
||||
//
|
||||
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
|
||||
std::string extension)
|
||||
{
|
||||
extension = removeSpaces(extension);
|
||||
//
|
||||
// Four extension parameters are defined for "permessage-deflate" to
|
||||
// help endpoints manage per-connection resource usage.
|
||||
//
|
||||
// - "server_no_context_takeover"
|
||||
// - "client_no_context_takeover"
|
||||
// - "server_max_window_bits"
|
||||
// - "client_max_window_bits"
|
||||
//
|
||||
// Server response could look like that:
|
||||
//
|
||||
// Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover;
|
||||
// server_no_context_takeover
|
||||
//
|
||||
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(std::string extension)
|
||||
{
|
||||
extension = removeSpaces(extension);
|
||||
|
||||
_enabled = false;
|
||||
_clientNoContextTakeover = false;
|
||||
_serverNoContextTakeover = false;
|
||||
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
|
||||
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
|
||||
_enabled = false;
|
||||
_clientNoContextTakeover = false;
|
||||
_serverNoContextTakeover = false;
|
||||
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
|
||||
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
// Split by ;
|
||||
std::string token;
|
||||
std::stringstream tokenStream(extension);
|
||||
// Split by ;
|
||||
std::string token;
|
||||
std::stringstream tokenStream(extension);
|
||||
|
||||
while (std::getline(tokenStream, token, ';')) {
|
||||
if (token == "permessage-deflate") {
|
||||
_enabled = true;
|
||||
while (std::getline(tokenStream, token, ';'))
|
||||
{
|
||||
if (token == "permessage-deflate")
|
||||
{
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
if (token == "server_no_context_takeover")
|
||||
{
|
||||
_serverNoContextTakeover = true;
|
||||
}
|
||||
|
||||
if (token == "client_no_context_takeover")
|
||||
{
|
||||
_clientNoContextTakeover = true;
|
||||
}
|
||||
|
||||
if (startsWith(token, "server_max_window_bits="))
|
||||
{
|
||||
uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
|
||||
|
||||
// Sanitize values to be in the proper range [8, 15] in
|
||||
// case a server would give us bogus values
|
||||
_serverMaxWindowBits =
|
||||
std::min(maxServerMaxWindowBits, std::max(x, minServerMaxWindowBits));
|
||||
}
|
||||
|
||||
if (startsWith(token, "client_max_window_bits="))
|
||||
{
|
||||
uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
|
||||
|
||||
// Sanitize values to be in the proper range [8, 15] in
|
||||
// case a server would give us bogus values
|
||||
_clientMaxWindowBits =
|
||||
std::min(maxClientMaxWindowBits, std::max(x, minClientMaxWindowBits));
|
||||
|
||||
sanitizeClientMaxWindowBits();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (token == "server_no_context_takeover") {
|
||||
_serverNoContextTakeover = true;
|
||||
}
|
||||
|
||||
if (token == "client_no_context_takeover") {
|
||||
_clientNoContextTakeover = true;
|
||||
}
|
||||
|
||||
if (startsWith(token, "server_max_window_bits=")) {
|
||||
uint8_t x =
|
||||
strtol(token.substr(token.find_last_of("=") + 1).c_str(),
|
||||
nullptr,
|
||||
10);
|
||||
|
||||
// Sanitize values to be in the proper range [8, 15] in
|
||||
// case a server would give us bogus values
|
||||
_serverMaxWindowBits =
|
||||
std::min(maxServerMaxWindowBits,
|
||||
std::max(x, minServerMaxWindowBits));
|
||||
}
|
||||
|
||||
if (startsWith(token, "client_max_window_bits=")) {
|
||||
uint8_t x =
|
||||
strtol(token.substr(token.find_last_of("=") + 1).c_str(),
|
||||
nullptr,
|
||||
10);
|
||||
|
||||
// Sanitize values to be in the proper range [8, 15] in
|
||||
// case a server would give us bogus values
|
||||
_clientMaxWindowBits =
|
||||
std::min(maxClientMaxWindowBits,
|
||||
std::max(x, minClientMaxWindowBits));
|
||||
|
||||
sanitizeClientMaxWindowBits();
|
||||
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;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits()
|
||||
{
|
||||
// zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it
|
||||
// to 9 See https://bugs.chromium.org/p/chromium/issues/detail?id=691074
|
||||
if (_clientMaxWindowBits == 8) {
|
||||
_clientMaxWindowBits = 9;
|
||||
std::string WebSocketPerMessageDeflateOptions::generateHeader()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::stringstream ss;
|
||||
ss << "Sec-WebSocket-Extensions: permessage-deflate";
|
||||
|
||||
if (_clientNoContextTakeover) ss << "; client_no_context_takeover";
|
||||
if (_serverNoContextTakeover) ss << "; server_no_context_takeover";
|
||||
|
||||
ss << "; server_max_window_bits=" << static_cast<int>(_serverMaxWindowBits);
|
||||
ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits);
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
return ss.str();
|
||||
#else
|
||||
return std::string();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
std::string WebSocketPerMessageDeflateOptions::generateHeader()
|
||||
{
|
||||
bool WebSocketPerMessageDeflateOptions::enabled() const
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::stringstream ss;
|
||||
ss << "Sec-WebSocket-Extensions: permessage-deflate";
|
||||
|
||||
if (_clientNoContextTakeover)
|
||||
ss << "; client_no_context_takeover";
|
||||
if (_serverNoContextTakeover)
|
||||
ss << "; server_no_context_takeover";
|
||||
|
||||
ss << "; server_max_window_bits=" << _serverMaxWindowBits;
|
||||
ss << "; client_max_window_bits=" << _clientMaxWindowBits;
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
return ss.str();
|
||||
return _enabled;
|
||||
#else
|
||||
return std::string();
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateOptions::enabled() const
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
return _enabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
|
||||
{
|
||||
return _clientNoContextTakeover;
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
|
||||
{
|
||||
return _clientNoContextTakeover;
|
||||
}
|
||||
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
|
||||
{
|
||||
return _serverNoContextTakeover;
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
|
||||
{
|
||||
return _serverNoContextTakeover;
|
||||
}
|
||||
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
|
||||
{
|
||||
return _clientMaxWindowBits;
|
||||
}
|
||||
|
||||
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
|
||||
{
|
||||
return _clientMaxWindowBits;
|
||||
}
|
||||
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
|
||||
{
|
||||
return _serverMaxWindowBits;
|
||||
}
|
||||
|
||||
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
|
||||
{
|
||||
return _serverMaxWindowBits;
|
||||
}
|
||||
bool WebSocketPerMessageDeflateOptions::startsWith(const std::string& str,
|
||||
const std::string& start)
|
||||
{
|
||||
return str.compare(0, start.length(), start) == 0;
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateOptions::startsWith(const std::string &str,
|
||||
const std::string &start)
|
||||
{
|
||||
return str.compare(0, start.length(), start) == 0;
|
||||
}
|
||||
std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
|
||||
{
|
||||
std::string out(str);
|
||||
out.erase(
|
||||
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
|
||||
out.end());
|
||||
|
||||
std::string
|
||||
WebSocketPerMessageDeflateOptions::removeSpaces(const std::string &str)
|
||||
{
|
||||
std::string out(str);
|
||||
out.erase(std::remove_if(out.begin(),
|
||||
out.end(),
|
||||
[](unsigned char x) { return std::isspace(x); }),
|
||||
out.end());
|
||||
|
||||
return out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user