43 Commits

Author SHA1 Message Date
Kilian 92159b0669 Implement FSUIPC FSData Classes 2022-09-23 22:16:01 +02:00
Kilian 115e4d9265 Migrate to radians instead of degrees to lessen conversion errors 2022-09-23 00:16:52 +02:00
Kilian 07487e9e51 Format 2022-09-11 23:26:28 +02:00
Kilian 46a0607b55 Fix Linux 2022-09-11 21:45:09 +02:00
Kilian de8b282ab1 Fix merge errors 2022-09-11 21:34:26 +02:00
Kilian 6027095c38 Merge branch 'develop' of https://git.hofmannnet.myhome-server.de/GermanAirlines/GermanAirlinesVA-GAConnector into develop 2022-09-11 21:20:13 +02:00
Kilian 2cf03d2933 Refactor SimDatabase 2022-09-11 21:14:46 +02:00
Kilian 161941ac1b REfactor SimDatabase PRODUCES CRASH ON EXIT 2022-09-11 04:31:01 +02:00
Kilian 4445f1f12a Refactor Config 2022-09-10 22:55:04 +02:00
Kilian d6d3c0b4d9 Recording now uses new write 2022-09-10 02:05:38 +02:00
Kilian 94fbc1f558 Runways and Gates now with new read/write 2022-09-10 01:07:00 +02:00
Kilian 2fc2170763 Future proofed file read/write 2022-09-09 23:28:25 +02:00
Kilian 699e2a4784 Consts and references 2022-09-09 20:10:38 +02:00
Kilian b050c23577 Initial Logbook read and write 2022-09-09 02:05:57 +02:00
Kilian a5e23f4b43 Fix linux 2022-09-08 19:10:55 +02:00
Kilian 7d45ca71ba Adjust for new Platform Requirements 2022-09-08 02:20:55 +02:00
Kilian 8b9f5d0406 Start on Logbook 2022-09-08 02:02:09 +02:00
Kilian 2bda541bce PHP Namespacing und Test 2022-09-07 20:32:14 +02:00
Kilian 25c4d8474d Refactor 2022-09-07 03:04:26 +02:00
Kilian 910afad3b5 Fix Recording file format 2022-09-07 02:49:42 +02:00
Kilian 2aa658c15e Refactor 2022-09-06 22:12:32 +02:00
Kilian 3559b124a7 Namespacing and Lib for files 2022-09-06 21:15:01 +02:00
Kilian 2510f4968c Version
Global AP
2022-09-06 16:41:34 +02:00
Kilian 752646c744 Fix linux 2022-09-06 01:16:36 +02:00
Kilian b22a60c85e Namespaces
Better File handling
2022-09-06 00:29:19 +02:00
Kilian 9dcca746ed Reimpl send prot 2022-09-05 17:52:52 +02:00
Kilian 0612afb37b adjust protocoll 2022-09-05 17:45:18 +02:00
Kilian ff179ee3a4 Specify 32 bit SSL 2022-09-05 16:19:59 +02:00
Kilian cd022c8063 System openSSL for linux 2022-09-05 16:04:25 +02:00
Kilian a536a9c918 Remove tlsOptions 2022-09-05 15:33:31 +02:00
Kilian 3dce2ca488 Mutate libs to use default system certs file 2022-09-05 14:59:08 +02:00
Kilian b695f4cb6c Allow non standard cert dir 2022-09-05 14:32:08 +02:00
Kilian 2914e1fa59 SSL Cert dir 2022-09-05 12:27:38 +02:00
Kilian 71656e4a66 Reimplementd IX with OpenSSL 2022-09-05 01:41:09 +02:00
Kilian 5e80c41538 Platform openSSL 2022-09-04 22:15:36 +02:00
Kilian c6ad412df9 Remove bug test 2022-09-04 18:56:48 +02:00
Kilian 0fe7335987 Style, Analyze 2022-09-04 03:06:09 +02:00
Kilian 3b2f146679 Move MakeRwys to own lib 2022-09-04 01:19:50 +02:00
Kilian a820f7456a Remove ixWebSocket 2022-09-04 00:50:49 +02:00
Kilian fbee417594 IX own module 2022-09-02 19:41:26 +02:00
Kilian 7439747876 merge branch 2022-09-02 17:42:48 +02:00
Kilian 27edf28f42 Merge branch 'develop' of https://git.hofmannnet.myhome-server.de/GermanAirlines/GermanAirlinesVA-GAConnector into develop 2022-09-02 17:40:27 +02:00
Kilian ca5aab1344 @rpath in plugin 2022-09-02 17:40:14 +02:00
383 changed files with 77623 additions and 10082 deletions
+5 -4
View File
@@ -18,18 +18,19 @@ BreakBeforeBraces: Linux
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CommentPragmas: "^ IWYU pragma:"
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerBinding: false
ExperimentalAutoDetectBinPacking: false
IndentAccessModifiers: true
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: true
IndentWidth: 4
IndentWidth: 2
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
NamespaceIndentation: Inner
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
ObjCBlockIndentWidth: 4
@@ -50,5 +51,5 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
Standard: c++14
SortIncludes: true
TabWidth: 4
TabWidth: 2
UseTab: Never
-8
View File
@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
Generated
-1
View File
@@ -1 +0,0 @@
GermanAirlinesVA_GAConnector
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/GermanAirlinesVA-GAConnector.iml" filepath="$PROJECT_DIR$/.idea/GermanAirlinesVA-GAConnector.iml" />
</modules>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+38 -5
View File
@@ -1,14 +1,47 @@
{
"configurations": [
{
"configurations": [{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/opt/llvm-mingw/bin/clang++",
"cStandard": "c17",
"defines": [
"LIN",
"XPLM200",
"XPLM210"
],
"compilerPath": "/opt/llvm-mingw/bin/clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "linux-clang-x64"
}, {
"name": "Windows",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"IBM",
"XPLM200",
"XPLM210"
],
"compilerPath": "/opt/llvm-mingw/bin/x86_64-w64-mingw32-clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "windows-clang-x64"
},
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"APL",
"XPLM200",
"XPLM210"
],
"compilerPath": "/opt/osxcross/target/bin/o64-clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "macos-clang-x64"
}
],
"version": 4
+17
View File
@@ -0,0 +1,17 @@
[{
"name": "Clang 13.0.0 Mac",
"toolchainFile": "${workspaceFolder}/toolchain-mac.cmake"
},
{
"name": "Clang 13.0.0 Linux",
"toolchainFile": "${workspaceFolder}/toolchain-lin.cmake"
},
{
"name": "Clang 13.0.0 Windows 32",
"toolchainFile": "${workspaceFolder}/toolchain-win-32.cmake"
},
{
"name": "Clang 13.0.0 Windows 64",
"toolchainFile": "${workspaceFolder}/toolchain-win-64.cmake"
}
]
-34
View File
@@ -1,34 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "clang++-10 - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: clang++-10 build active file",
"miDebuggerPath": "/usr/bin/lldb-mi-10"
}
]
}
+26 -3
View File
@@ -1,6 +1,6 @@
{
"cmake.generator": "Unix Makefiles",
"files.associations": {
"thread": "cpp",
"algorithm": "cpp",
"array": "cpp",
"atomic": "cpp",
@@ -60,8 +60,31 @@
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"valarray": "cpp"
}
"valarray": "cpp",
"__threading_support": "cpp",
"__bit_reference": "cpp",
"__bits": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__hash_table": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__nullptr": "cpp",
"__split_buffer": "cpp",
"__string": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"compare": "cpp",
"concepts": "cpp",
"ios": "cpp",
"locale": "cpp",
"queue": "cpp",
"stack": "cpp",
"variant": "cpp"
},
}
-28
View File
@@ -1,28 +0,0 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: clang++-10 build active file",
"command": "/usr/bin/clang++-10",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}
+13 -2
View File
@@ -1,15 +1,26 @@
cmake_minimum_required(VERSION 3.8)
cmake_policy(SET CMP0068 NEW)
project(GermanAirlinesVA_GAConnector)
project(GermanAirlinesVA_GAConnector C CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR TRUE)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(PLUGIN_NAME GAConnector)
option(DEBUG "Debug symbols" OFF)
add_subdirectory(
xplugin
ixwebsocket
)
add_subdirectory(
file
)
if(NOT (WIN32 AND (BIT STREQUAL "32")))
add_subdirectory(
xplugin
)
endif()
Vendored
+8 -116
View File
@@ -17,7 +17,9 @@ pipeline {
}
steps {
sh 'bash ./build.sh win32'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/win32'
sh 'bash ./build.sh win64'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/win64'
}
}
stage('Build Linux Debug') {
@@ -35,8 +37,8 @@ pipeline {
DEBUG = 1
}
steps {
sh 'bash ./build.sh lin32'
sh 'bash ./build.sh lin64'
sh 'bash ./build.sh lin'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/lin'
}
}
stage('Build MacOSX Debug') {
@@ -55,6 +57,7 @@ pipeline {
}
steps {
sh 'bash ./build.sh mac'
sh 'analyze-build --verbose --cdb build/compile_commands.json --output build/Analysis/mac'
}
}
stage('Archive Debug') {
@@ -62,120 +65,9 @@ pipeline {
branch 'develop'
}
steps {
zip zipFile: 'Debug.zip', archive: true, dir: 'build/Plugin'
sh 'rm -rf build'
}
}
stage('Build Windows Stageing') {
when {
branch 'stage'
beforeAgent true
}
agent {
docker {
image 'llvm-mingw:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh win32'
sh 'bash ./build.sh win64'
}
}
stage('Build Linux Stageing') {
when {
branch 'stage'
beforeAgent true
}
agent {
docker {
image 'llvm:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh lin32'
sh 'bash ./build.sh lin64'
}
}
stage('Build MacOSX Stageing') {
when {
branch 'stage'
beforeAgent true
}
agent {
docker {
image 'osxcross:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh mac'
}
}
stage('Archive Stageing') {
when {
branch 'stage'
}
steps {
zip zipFile: 'Stage.zip', archive: true, dir: 'build/Plugin'
sh 'rm -rf build'
}
}
stage('Build Windows Release') {
when {
branch 'master'
beforeAgent true
}
agent {
docker {
image 'llvm-mingw:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh win32'
sh 'bash ./build.sh win64'
}
}
stage('Build Linux Release') {
when {
branch 'master'
beforeAgent true
}
agent {
docker {
image 'llvm:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh lin32'
sh 'bash ./build.sh lin64'
}
}
stage('Build MacOSX Release') {
when {
branch 'master'
beforeAgent true
}
agent {
docker {
image 'osxcross:latest'
reuseNode true
}
}
steps {
sh 'bash ./build.sh mac'
}
}
stage('Archive Release') {
when {
branch 'master'
beforeAgent true
}
steps {
zip zipFile: 'Release.zip', archive: true, dir: 'build/Plugin'
zip zipFile: 'X-Plane Debug.zip', archive: true, dir: 'build/X-Plane'
zip zipFile: 'ESP Debug.zip', archive: true, dir: 'build/ESP'
zip zipFile: 'Analysis.zip', archive: true, dir: 'build/Analysis'
sh 'rm -rf build'
}
}
+3
View File
@@ -9,3 +9,6 @@
X-Plane Plugin for all supported platforms.
Demo of WebSocket capabilities
Requirements:
- openSSL 1.1.x
+2
View File
@@ -0,0 +1,2 @@
- Update OSXCross Docker image to SDK 11
- Implement ARM64 arch for Plugin
+14 -7
View File
@@ -2,25 +2,30 @@
mkdir -p build
cd build
mkdir -p Plugin/GAConnector/32
mkdir -p Plugin/GAConnector/64
mkdir -p X-Plane/GAConnector/64
mkdir -p X-Plane/GAConnector/recordings
mkdir -p ESP/GAConnector
mkdir -p ESP/GAConnector/recordings
rm -f CMakeCache.txt
case $1 in
"mac")
cmake -DDEBUG=$DEBUG -DCMAKE_TOOLCHAIN_FILE=../toolchain-mac.cmake ..
;;
"lin32")
cmake -DDEBUG=$DEBUG -DBIT=32 -DCMAKE_TOOLCHAIN_FILE=../toolchain-lin.cmake ..
;;
"lin64")
"lin")
cmake -DDEBUG=$DEBUG -DBIT=64 -DCMAKE_TOOLCHAIN_FILE=../toolchain-lin.cmake ..
;;
"win32")
cmake -DDEBUG=$DEBUG -DBIT=32 -DCMAKE_TOOLCHAIN_FILE=../toolchain-win-32.cmake ..
\cp -rf ../openSSL/win32/*.dll ESP/GAConnector/
\cp -rf /opt/llvm-mingw/i686-w64-mingw32/bin/libc++.dll ESP/GAConnector/
\cp -rf /opt/llvm-mingw/i686-w64-mingw32/bin/libunwind.dll ESP/GAConnector/
;;
"win64")
cmake -DDEBUG=$DEBUG -DBIT=64 -DCMAKE_TOOLCHAIN_FILE=../toolchain-win-64.cmake ..
\cp -rf ../openSSL/win64/*.dll X-Plane/GAConnector/64/
\cp -rf /opt/llvm-mingw/x86_64-w64-mingw32/bin/libc++.dll X-Plane/GAConnector/64
\cp -rf /opt/llvm-mingw/x86_64-w64-mingw32/bin/libunwind.dll X-Plane/GAConnector/64
;;
esac
@@ -33,5 +38,7 @@ fi
if [ "$1" = "mac" ] && [ "$DEBUG" = "1" ]
then
/opt/osxcross/target/bin/osxcross-llvm-dsymutil Plugin/GAConnector/mac.xpl
/opt/osxcross/target/bin/osxcross-llvm-dsymutil X-Plane/GAConnector/mac.xpl
fi
+84
View File
@@ -0,0 +1,84 @@
file(GLOB file CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/file/*.cpp)
add_library(file SHARED
${file}
)
target_include_directories(file PRIVATE
${CMAKE_SOURCE_DIR}/file/include
${CMAKE_SOURCE_DIR}/simdata/include
${CMAKE_SOURCE_DIR}/utilities/include
)
set_target_properties(file PROPERTIES
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/file/include
)
target_compile_options(file PRIVATE
-Wall
-Wextra
-pedantic
)
if(DEBUG)
target_compile_options(file PRIVATE
-g
)
target_link_options(file PRIVATE
-g
)
else()
target_compile_options(file PRIVATE
-O2
)
endif()
if(APPLE)
message("Building file for MacOSX Universal into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}")
set_target_properties(file PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}"
BUILD_WITH_INSTALL_NAME_DIR TRUE
)
target_compile_options(file PRIVATE
"SHELL:-arch x86_64"
)
target_link_options(file PRIVATE
"SHELL:-arch x86_64"
)
target_link_libraries(file PRIVATE
"-framework Security"
)
elseif(UNIX)
message("Building file for Linux ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(file PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}"
)
target_compile_options(file PRIVATE
-nodefaultlibs
)
elseif(WIN32)
if (BIT STREQUAL "32")
message("Building file for Windows ${BIT} into ${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}")
set_target_properties(file PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}"
)
else()
message("Building file for Windows ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(file PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}"
)
endif()
if(DEBUG)
target_compile_options(file PRIVATE
-gcodeview
)
target_link_options(file PRIVATE
-Wl,-pdb=
)
endif()
endif()
+80
View File
@@ -0,0 +1,80 @@
VARIABLE STRING (>=3)
*********************
0 1 | 2 ... |
------+-------+
LEN |STRING |
UINT16|CHAR* |
LOGBOOK HEADER (28)
***********************************************************************************************************************************************
0 1 2 3 4 | 5 | 6 7 | 8 9 10 11 | 12 13 14 15 | 16 17 18 19 | 20 21 22 23 | 24 25 26 27 |
------------------+-------+-------------+---------------------+-------------------+-------------------+-------------------+-------------------+
IDENT |VERSION|TOTAL FLIGHTS|TOTAL TIME |TOTAL PTS |TOTAL FUEL |TOTAL DIST |RATE |
CHAR[5] |UINT8 |UINT16 |FLOAT32, Decimal, min|FLOAT32 |FLOAT32, kgs |FLOAT32, nm |FLOAT32, ft/min |
FLIGHT PREAMBLE (24)
**********************************************************************************************************************
0 1 2 3 4 5 6 7 8 9 | 10 11 12 13 | 14 15 16 17 | 18 19 20 21 22 23 |
--------------------------------------+----------------------------+-------------------+-----------------------------+
DATE |FLIGHT NUMBER |AIRCRAFT TYPE |AIRCRAFT REGISTRATION |
CHAR, DD.MM.YYYY |CHAR[4],[0-9][0-9A-z\0]{0,3}|CHAR[4], ICAO code |CHAR[6], D-[A-Z]{4} |
TIMES (24)
**************************************************************************************************************
0 1 2 3 4 | 5 6 7 8 9 | 10 11 12 13 14 | 15 16 17 18 19 | 20 21 22 23 |
------------------+-------------------+------------------------+------------------------+--------------------+
OFF BLOCK |TAKEOFF (OUT) |LANDING (ON) |ON BLOCK (IN) |TOTAL |
CHAR[5], HH:MM |CHAR[5], HH:MM |CHAR[5], HH:MM |CHAR[5], HH:MM |FLOAT32, Decimal min|
FUELS (16)
*********************************************************************
0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15 |
--------------+---------------+-----------------+-------------------+
TAXI OUT |IN FLIGHT |TAXI IN |TOTAL |
FLOAT32, kgs |FLOAT32, kgs |FLOAT32, kgs |FLOAT32, kgs |
DISTANCES (16)
*********************************************************************
0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15 |
--------------+---------------+-----------------+-------------------+
TAXI OUT |IN FLIGHT |TAXI IN |TOTAL |
FLOAT32, nm |FLOAT32, nm |FLOAT32, nm |FLOAT32, nm |
LANDING (9)
*******************************************
0 1 2 3 | 4 | 5 6 7 8 |
---------------+----------+---------------+
MAX RATE |TOUCHDOWNS|MAX G-FORCE |
FLOAT32, ft/min|CHAR |FLOAT32, g |
FLIGHT POSTAMBLE (5)
*********************
0 1 2 3 | 5 |
--------------+---------+
POINTS |FLAGS |
FLOAT32 |BITFIELD |
FLAGS BITFIELD
**********************************
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---+---+---+---+---+---+---+-----+
NIL|NIL|NIL|NIL|NIL|NIL|NIL|FILED|
LOGBOOK FILE:
LOGBOOK HEADER
[
FLIGHT PREAMBLE
VARIABLE STRING (DEPARTURE AIRPORT NAME AND CODE)
VARIABLE STRING (DEPARTURE GATE NAME)
VARIABLE STRING (DEPARTURE RUNWAY DESIGNATOR)
VARIABLE STRING (ARRIVAL AIRPORT NAME AND CODE)
VARIABLE STRING (ARRIVAL GATE NAME)
VARIABLE STRING (ARRIVAL RUNWAY DESIGNATOR)
TIMES
FUELS
DISTANCES
LANDING
VARIABLE STRING (RECORDING FILE NAME)
FLIGHT POSTAMBLE
VARIABLE STRING (FLIGHT RECORDING PATH RELATIVE TO LOGBOOK)
]0..n
+47
View File
@@ -0,0 +1,47 @@
#include "config/config.h"
namespace germanairlinesva
{
namespace file
{
namespace config
{
void Config::writeFile() const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY CONFIG);
out << "scenery=" << this->scenery << "\n";
out << "user=" << this->user << "\n";
out << "token=" << this->token << "\n";
out.close();
}
Config::Config()
{
std::ifstream in(XPLANE_PLUGIN_DIRECTORY CONFIG);
std::string line;
while (std::getline(in, line)) {
std::vector<std::string> fields = util::split(line, '=');
if (fields.size() >= 2) {
util::trim(fields[0]);
util::trim(fields[1]);
if (fields[0] == "scenery") {
this->scenery = fields[1];
} else if (fields[0] == "user") {
this->user = fields[1];
} else if (fields[0] == "token") {
this->token = fields[1];
}
}
}
in.close();
}
void Config::updateScenery(std::string scenery)
{
this->scenery = scenery;
this->writeFile();
}
} // namespace config
} // namespace file
} // namespace germanairlinesva
-47
View File
@@ -1,47 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_CONFIG_H
#define GERMANAIRLINESVA_GACONNECTOR_CONFIG_H
#include "stringExtensions.hpp"
#include <map>
#include <fstream>
#include <map>
#include <string>
#include <vector>
namespace config
{
static inline std::map<std::string, std::string>
readConfig(const std::string &file)
{
std::ifstream config(file);
std::map<std::string, std::string> settings;
std::string line;
while (std::getline(config, line)) {
std::vector<std::string> fields = split(line, '=');
if (fields.size() >= 2) {
trim(fields[0]);
trim(fields[1]);
settings[fields[0]] = fields[1];
}
}
config.close();
return settings;
}
static inline void
writeConfig(const std::map<std::string, std::string> &config,
const std::string &file)
{
std::ofstream cfg(file);
for (const std::pair<const std::string, std::string> &entry : config) {
cfg << entry.first << '=' << entry.second << '\n';
}
cfg.close();
}
} // namespace config
#endif
+42
View File
@@ -0,0 +1,42 @@
#include "simdata/gate.h"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
Gate::Gate(std::string designator,
double latitude,
double longitude,
std::uint8_t radius)
{
this->designator = designator;
this->center = {geodata::toRadians(latitude),
geodata::toRadians(longitude)};
this->radius = radius;
}
Gate::Gate(std::string designator,
struct geodata::point center,
std::uint8_t radius)
{
this->designator = designator;
this->center = center;
this->radius = radius;
}
void Gate::toFile(std::ofstream &out) const
{
writeString(out, this->designator);
write<decltype(this->center)>(out, this->center);
write<decltype(this->radius)>(out, this->radius);
}
bool Gate::contains(geodata::point coordinates) const
{
return geodata::distanceEarthP(this->center, coordinates);
}
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
-56
View File
@@ -1,56 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_GATE_H
#define GERMANAIRLINESVA_GACONNECTOR_GATE_H
#include <cstdint>
#include <cstring>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
/*
* Representation of X-Plane gate
* Heading in degrees (0...360)
*
* Length in bytes: 18 + length of designator
* Designator must be null terminated
*
* UINT8 | CHAR[] | DOUBLE | DOUBLE | UINT8
* ------+------------+--------+--------+------
* LEN | DESIGNATOR | LAT | LON | WIDTH
*/
class Gate
{
private:
std::string designator;
double latitude;
double longitude;
std::vector<std::uint8_t> file;
public:
Gate(const std::string &designator, double latitude, double longitude)
{
this->designator = designator;
this->latitude = latitude;
this->longitude = longitude;
file = std::vector<std::uint8_t>(18 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++; // Designator length
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr +=
this->designator.length() + 1; // Designator plus null termination
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += 8; // Latitude
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif
+18
View File
@@ -0,0 +1,18 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSGEODATA_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSGEODATA_H
#include "FsLatLonPoint.h"
#include "FsLatLonQuadrilateral.h"
#include "FsLatitude.h"
#include "FsLatitudeSpan.h"
#include "FsLongitude.h"
#include "FsLongitudeSpan.h"
#include "FsLatLonPointImpl.h"
#include "FsLatLonQuadrilateralImpl.h"
#include "FsLatitudeImpl.h"
#include "FsLatitudeSpanImpl.h"
#include "FsLongitudeImpl.h"
#include "FsLongitudeSpanImpl.h"
#endif
+57
View File
@@ -0,0 +1,57 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINT_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINT_H
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdint>
#include <string>
#include "FsLatitude.h"
#include "FsLatitudeSpan.h"
#include "FsLongitude.h"
#include "FsLongitudeSpan.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatLonPoint
{
private:
FsLatitude lat;
FsLongitude lon;
public:
inline FsLatLonPoint();
inline FsLatLonPoint(FsLatitude Latitude, FsLongitude Longitude);
inline const FsLongitude Longitude() const;
inline const FsLatitude Latitude() const;
inline double DistanceFromInFeet(const FsLatLonPoint &Point) const;
inline double
DistanceFromInNauticalMiles(const FsLatLonPoint &Point) const;
inline double DistanceFromInMetres(const FsLatLonPoint &Point) const;
inline double BearingTo(const FsLatLonPoint &Point) const;
inline double BearingFrom(const FsLatLonPoint &Point) const;
inline FsLatLonPoint OffsetByFeet(double Bearing,
double Distance) const;
inline FsLatLonPoint OffsetByMetres(double Bearing,
double Distance) const;
inline FsLatLonPoint OffsetByNauticalMiles(double Bearing,
double Distance) const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+145
View File
@@ -0,0 +1,145 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINTIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONPOINTIMPL_H
#include "FsLatLonPoint.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatLonPoint::FsLatLonPoint() = default;
inline FsLatLonPoint::FsLatLonPoint(FsLatitude Latitude,
FsLongitude Longitude)
{
this->lon = Longitude;
this->lat = Latitude;
}
inline const FsLongitude FsLatLonPoint::Longitude() const
{
return this->lon;
}
inline const FsLatitude FsLatLonPoint::Latitude() const
{
return this->lat;
}
inline double
FsLatLonPoint::DistanceFromInFeet(const FsLatLonPoint &Point) const
{
FsLongitudeSpan fsLongitudeSpan =
FsLongitudeSpan::BetweenTwoLongitudes(Point.Longitude(), this->lon);
double num = (fsLongitudeSpan.ToFeet(this->lat) +
fsLongitudeSpan.ToFeet(Point.lat)) /
2.0;
double feet = FsLatitudeSpan(Point.Latitude().DecimalDegrees() -
this->lat.DecimalDegrees())
.ToFeet();
return sqrt(num * num + feet * feet);
}
inline double FsLatLonPoint::DistanceFromInNauticalMiles(
const FsLatLonPoint &Point) const
{
return this->DistanceFromInFeet(Point) / 6076.1155;
}
inline double
FsLatLonPoint::DistanceFromInMetres(const FsLatLonPoint &Point) const
{
return this->DistanceFromInFeet(Point) / 3.2808;
}
inline double FsLatLonPoint::BearingTo(const FsLatLonPoint &Point) const
{
double num1 = 0.0;
double num2 = abs(FsLatitudeSpan(this->lat.DecimalDegrees() -
Point.Latitude().DecimalDegrees())
.ToFeet());
double num3 = abs((FsLongitudeSpan(Point.Longitude().DecimalDegrees() -
this->lon.DecimalDegrees())
.ToFeet(this->lat) +
FsLongitudeSpan(Point.Longitude().DecimalDegrees() -
this->lon.DecimalDegrees())
.ToFeet(Point.lat)) /
2.0);
if (num2 == 0.0)
num1 = this->lon.DecimalDegrees() > Point.Longitude().DecimalDegrees()
? 270.0
: 90.0;
else if (this->lat.DecimalDegrees() < Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() < Point.Longitude().DecimalDegrees())
num1 = atan(num3 / num2) * 180.0 / M_PI;
else if (this->lat.DecimalDegrees() > Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() < Point.Longitude().DecimalDegrees())
num1 = atan(num2 / num3) * 180.0 / M_PI + 90.0;
else if (this->lat.DecimalDegrees() > Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() > Point.Longitude().DecimalDegrees())
num1 = atan(num3 / num2) * 180.0 / M_PI + 180.0;
else if (this->lat.DecimalDegrees() < Point.Latitude().DecimalDegrees() &&
this->lon.DecimalDegrees() > Point.Longitude().DecimalDegrees())
num1 = atan(num2 / num3) * 180.0 / M_PI + 270.0;
return num1;
}
inline double FsLatLonPoint::BearingFrom(const FsLatLonPoint &Point) const
{
double num = 180.0 + this->BearingTo(Point);
if (num >= 360.0)
num -= 360.0;
if (num < 0.0)
num += 360.0;
return num;
}
inline FsLatLonPoint FsLatLonPoint::OffsetByFeet(double Bearing,
double Distance) const
{
double Feet1 = sin(M_PI * Bearing / 180.0) * Distance;
double Feet2 = cos(M_PI * Bearing / 180.0) * Distance;
FsLatLonPoint fsLatLonPoint = FsLatLonPoint();
fsLatLonPoint.lat = this->lat.Add(FsLatitudeSpan::FromFeet(Feet2));
fsLatLonPoint.lon =
this->lon.Add(FsLongitudeSpan::FromFeet(Feet1, fsLatLonPoint.lat));
return fsLatLonPoint;
}
inline FsLatLonPoint FsLatLonPoint::OffsetByMetres(double Bearing,
double Distance) const
{
double Metres1 = sin(M_PI * Bearing / 180.0) * Distance;
double Metres2 = cos(M_PI * Bearing / 180.0) * Distance;
FsLatLonPoint fsLatLonPoint = FsLatLonPoint();
fsLatLonPoint.lat = this->lat.Add(FsLatitudeSpan::FromMetres(Metres2));
fsLatLonPoint.lon = this->lon.Add(
FsLongitudeSpan::FromMetres(Metres1, fsLatLonPoint.lat));
return fsLatLonPoint;
}
inline FsLatLonPoint
FsLatLonPoint::OffsetByNauticalMiles(double Bearing,
double Distance) const
{
double NauticalMiles1 = sin(M_PI * Bearing / 180.0) * Distance;
double NauticalMiles2 = cos(M_PI * Bearing / 180.0) * Distance;
FsLatLonPoint fsLatLonPoint = FsLatLonPoint();
fsLatLonPoint.lat =
this->lat.Add(FsLatitudeSpan::FromNauticalMiles(NauticalMiles2));
fsLatLonPoint.lon =
this->lon.Add(FsLongitudeSpan::FromNauticalMiles(NauticalMiles1,
fsLatLonPoint.lat));
return fsLatLonPoint;
}
inline const std::string FsLatLonPoint::to_string(bool HemisphereAsText,
char DetailLevel) const
{
return this->lat.to_string(HemisphereAsText, DetailLevel) + ", " +
this->lon.to_string(HemisphereAsText, DetailLevel);
}
inline const std::string FsLatLonPoint::to_string() const
{
return this->to_string(true, 'm');
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
@@ -0,0 +1,57 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERAL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERAL_H
#define _USE_MATH_DEFINES
#include <cmath>
#include <string>
#include <vector>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatitude;
class FsLatitudeSpan;
class FsLongitude;
class FsLongitudeSpan;
class FsLatLonPoint;
class FsLatLonQuadrilateral
{
private:
FsLatLonPoint ne;
FsLatLonPoint se;
FsLatLonPoint sw;
FsLatLonPoint nw;
public:
inline FsLatLonQuadrilateral();
inline FsLatLonQuadrilateral(FsLatLonPoint P0,
FsLatLonPoint P1,
FsLatLonPoint P2,
FsLatLonPoint P3);
inline FsLatLonPoint NE() const;
inline FsLatLonPoint SE() const;
inline FsLatLonPoint SW() const;
inline FsLatLonPoint NW() const;
inline bool ContainsPoint(FsLatLonPoint point) const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
inline static FsLatLonQuadrilateral
ForRunway(FsLatLonPoint &ThresholdCentre,
double HeadingTrue,
double WidthInFeet,
double LengthInFeet);
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
@@ -0,0 +1,166 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERALIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATLONQUADRILATERALIMPL_H
#include "FsLatLonQuadrilateral.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatLonQuadrilateral::FsLatLonQuadrilateral() = default;
inline FsLatLonQuadrilateral::FsLatLonQuadrilateral(FsLatLonPoint P0,
FsLatLonPoint P1,
FsLatLonPoint P2,
FsLatLonPoint P3)
{
std::vector<FsLatLonPoint> fsLatLonPointList1;
std::vector<FsLatLonPoint> fsLatLonPointList2;
fsLatLonPointList1.push_back(P0);
fsLatLonPointList1.push_back(P1);
fsLatLonPointList1.push_back(P2);
fsLatLonPointList1.push_back(P3);
for (int index1 = 0; index1 < 3; ++index1) {
double num = 0.0;
int index2 = 0;
for (size_t index3 = 0; index3 < fsLatLonPointList1.size(); ++index3) {
if (fsLatLonPointList1[index3].Latitude().UDegrees() > num) {
num = fsLatLonPointList1[index3].Latitude().UDegrees();
index2 = index3;
}
}
fsLatLonPointList2.push_back(fsLatLonPointList1[index2]);
fsLatLonPointList1.erase(fsLatLonPointList1.begin() + index2);
}
fsLatLonPointList2.push_back(fsLatLonPointList1[0]);
if (fsLatLonPointList2[1].Longitude().UDegrees() >
fsLatLonPointList2[0].Longitude().UDegrees()) {
this->ne = fsLatLonPointList2[1];
this->nw = fsLatLonPointList2[0];
} else {
this->ne = fsLatLonPointList2[0];
this->nw = fsLatLonPointList2[1];
}
if (fsLatLonPointList2[3].Longitude().UDegrees() >
fsLatLonPointList2[2].Longitude().UDegrees()) {
this->se = fsLatLonPointList2[3];
this->sw = fsLatLonPointList2[2];
} else {
this->se = fsLatLonPointList2[2];
this->sw = fsLatLonPointList2[3];
}
}
inline FsLatLonPoint FsLatLonQuadrilateral::NE() const { return this->ne; }
inline FsLatLonPoint FsLatLonQuadrilateral::SE() const { return this->se; }
inline FsLatLonPoint FsLatLonQuadrilateral::SW() const { return this->sw; }
inline FsLatLonPoint FsLatLonQuadrilateral::NW() const { return this->nw; }
inline bool FsLatLonQuadrilateral::ContainsPoint(FsLatLonPoint point) const
{
bool flag = false;
double udegrees1 = this->nw.Latitude().UDegrees();
double udegrees2 = this->sw.Latitude().UDegrees();
double udegrees3 = this->se.Latitude().UDegrees();
double udegrees4 = this->ne.Latitude().UDegrees();
double udegrees5 = this->nw.Longitude().UDegrees();
double udegrees6 = this->sw.Longitude().UDegrees();
double udegrees7 = this->se.Longitude().UDegrees();
double udegrees8 = this->ne.Longitude().UDegrees();
double udegrees9 = point.Longitude().UDegrees();
double udegrees10 = point.Latitude().UDegrees();
if (udegrees9 > udegrees5 + (udegrees6 - udegrees5) /
(udegrees1 - udegrees2) *
(udegrees1 - udegrees10) &&
udegrees9 < udegrees8 + (udegrees7 - udegrees8) /
(udegrees4 - udegrees3) *
(udegrees4 - udegrees10) &&
udegrees10 > udegrees2 + (udegrees3 - udegrees2) /
(udegrees7 - udegrees6) *
(udegrees9 - udegrees6))
flag = udegrees10 < udegrees1 + (udegrees4 - udegrees1) /
(udegrees8 - udegrees5) *
(udegrees9 - udegrees5);
return flag;
}
inline const std::string
FsLatLonQuadrilateral::to_string(bool HemisphereAsText,
char DetailLevel) const
{
return this->ne.to_string(HemisphereAsText, DetailLevel) + ", " +
this->nw.to_string(HemisphereAsText, DetailLevel) + ", " +
this->sw.to_string(HemisphereAsText, DetailLevel) + ", " +
this->se.to_string(HemisphereAsText, DetailLevel);
}
inline const std::string FsLatLonQuadrilateral::to_string() const
{
return this->to_string(true, 'm');
}
inline FsLatLonQuadrilateral
FsLatLonQuadrilateral::ForRunway(FsLatLonPoint &ThresholdCentre,
double HeadingTrue,
double WidthInFeet,
double LengthInFeet)
{
double num = M_PI * HeadingTrue / 180.0;
double Feet = cos(num) * WidthInFeet / 2.0;
double decimalDegrees1 =
FsLatitudeSpan::FromFeet(sin(num) * WidthInFeet / 2.0)
.DecimalDegrees();
FsLatitude fsLatitude1 = FsLatitude(
ThresholdCentre.Latitude().DecimalDegrees() - decimalDegrees1);
double decimalDegrees2 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
FsLongitude Longitude1 = FsLongitude(
ThresholdCentre.Longitude().DecimalDegrees() + decimalDegrees2);
FsLatLonPoint P1 = FsLatLonPoint(fsLatitude1, Longitude1);
fsLatitude1 = FsLatitude(ThresholdCentre.Latitude().DecimalDegrees() +
decimalDegrees1);
double decimalDegrees3 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
Longitude1 = FsLongitude(ThresholdCentre.Longitude().DecimalDegrees() -
decimalDegrees3);
FsLatLonPoint P0 = FsLatLonPoint(fsLatitude1, Longitude1);
double decimalDegrees4 =
FsLatitudeSpan::FromFeet(cos(num) * LengthInFeet).DecimalDegrees();
FsLatitude fsLatitude2 = FsLatitude(
ThresholdCentre.Latitude().DecimalDegrees() + decimalDegrees4);
double decimalDegrees5 =
FsLongitudeSpan ::FromFeet(sin(num) * LengthInFeet, fsLatitude2)
.DecimalDegrees();
FsLongitude Longitude2 = FsLongitude(
ThresholdCentre.Longitude().DecimalDegrees() + decimalDegrees5);
FsLatLonPoint fsLatLonPoint = FsLatLonPoint(fsLatitude2, Longitude2);
fsLatitude1 = FsLatitude(fsLatLonPoint.Latitude().DecimalDegrees() -
decimalDegrees1);
double decimalDegrees6 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
Longitude1 = FsLongitude(fsLatLonPoint.Longitude().DecimalDegrees() +
decimalDegrees6);
FsLatLonPoint P3 = FsLatLonPoint(fsLatitude1, Longitude1);
fsLatitude1 = FsLatitude(fsLatLonPoint.Latitude().DecimalDegrees() +
decimalDegrees1);
double decimalDegrees7 =
FsLongitudeSpan::FromFeet(Feet, fsLatitude1).DecimalDegrees();
Longitude1 = FsLongitude(fsLatLonPoint.Longitude().DecimalDegrees() -
decimalDegrees7);
FsLatLonPoint P2 = FsLatLonPoint(fsLatitude1, Longitude1);
return FsLatLonQuadrilateral(P0, P1, P2, P3);
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+60
View File
@@ -0,0 +1,60 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDE_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDE_H
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatitudeSpan;
class FsLatitude
{
private:
double pos;
public:
inline FsLatitude();
inline FsLatitude(double DecimalDegrees);
inline FsLatitude(std::int64_t FSUnits);
inline FsLatitude(std::int32_t FSUnits);
inline FsLatitude(std::int32_t Degrees, double DecimalMinutes);
inline FsLatitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline std::int64_t ToFSUnits8() const;
inline std::int32_t ToFSUnits4() const;
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degree() const;
inline std::int32_t Minute() const;
inline std::int32_t Second() const;
inline double UDegrees() const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
inline FsLatitude Add(const FsLatitudeSpan &Distance) const;
inline FsLatitude Subtract(const FsLatitudeSpan &Distance) const;
inline FsLatitude AddDegrees(double Degrees) const;
inline FsLatitude AddMinutes(double Minutes) const;
inline FsLatitude AddSeconds(double Seconds) const;
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+129
View File
@@ -0,0 +1,129 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDEIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDEIMPL_H
#include "FsLatitude.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatitude::FsLatitude() = default;
inline FsLatitude::FsLatitude(double DecimalDegrees)
{
this->pos = DecimalDegrees;
while (this->pos > 90.0 || this->pos < -90.0) {
if (this->pos > 90.0)
this->pos = 180.0 - this->pos;
if (this->pos < -90.0)
this->pos = -180.0 - this->pos;
}
}
inline FsLatitude::FsLatitude(std::int64_t FSUnits)
: FsLatitude(FSUnits * 90.0 / 4.2957189152768E+16)
{
}
inline FsLatitude::FsLatitude(std::int32_t FSUnits)
: FsLatitude(FSUnits * 90.0 / 10001750.0)
{
}
inline FsLatitude::FsLatitude(std::int32_t Degrees, double DecimalMinutes)
: FsLatitude(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLatitude::FsLatitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLatitude(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline std::int64_t FsLatitude::ToFSUnits8() const
{
return this->pos * 4.2957189152768E+16 / 90.0;
}
inline std::int32_t FsLatitude::ToFSUnits4() const
{
return this->pos * 10001750.0 / 90.0;
}
inline double FsLatitude::DecimalDegrees() const { return this->pos; }
inline double FsLatitude::DecimalMinutes() const
{
return (this->pos - trunc(this->pos)) * 60.0;
}
inline double FsLatitude::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLatitude::Degree() const { return trunc(this->pos); }
inline std::int32_t FsLatitude::Minute() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLatitude::Second() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLatitude::UDegrees() const { return this->pos + 90.0; }
inline const std::string FsLatitude::to_string(bool HemisphereAsText,
char DetailLevel) const
{
std::ostringstream str;
if (!HemisphereAsText) {
str << (this->pos < 0.0 ? "-" : "");
} else {
str << (this->pos < 0.0 ? "S" : "N");
}
switch (DetailLevel) {
case 'm':
str << this->Degree() << "° " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degree() << "° " << this->Minute() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->pos << "*";
break;
}
return str.str();
}
inline const std::string FsLatitude::to_string() const
{
return this->to_string(true, 'm');
}
inline FsLatitude FsLatitude::Add(const FsLatitudeSpan &Distance) const
{
return FsLatitude(this->pos + Distance.DecimalDegrees());
}
inline FsLatitude FsLatitude::Subtract(const FsLatitudeSpan &Distance) const
{
return FsLatitude(this->pos - Distance.DecimalDegrees());
}
inline FsLatitude FsLatitude::AddDegrees(double Degrees) const
{
return FsLatitude(this->pos + Degrees);
}
inline FsLatitude FsLatitude::AddMinutes(double Minutes) const
{
return FsLatitude(this->pos + Minutes / 60.0);
}
inline FsLatitude FsLatitude::AddSeconds(double Seconds) const
{
return FsLatitude(this->pos + Seconds / 3600.0);
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+58
View File
@@ -0,0 +1,58 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPAN_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPAN_H
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLatitude;
class FsLatitudeSpan
{
private:
double span;
public:
inline FsLatitudeSpan();
inline FsLatitudeSpan(double DecimalDegrees);
inline FsLatitudeSpan(std::int32_t Degrees, double DecimalMinutes);
inline FsLatitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline static FsLatitudeSpan FromFeet(double Feet);
inline static FsLatitudeSpan FromNauticalMiles(double NauticalMiles);
inline static FsLatitudeSpan FromMetres(double Metres);
inline static FsLatitudeSpan
BetweenTwoLatitides(const FsLatitude &Lat1, const FsLatitude &Lat2);
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degrees() const;
inline std::int32_t Minutes() const;
inline std::int32_t Seconds() const;
inline double TotalMinutes() const;
inline double TotalSeconds() const;
inline double ToFeet() const;
inline double ToNauticalMiles() const;
inline double ToMetres() const;
inline const std::string to_string(char DetailLevel) const;
inline const std::string to_string();
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+121
View File
@@ -0,0 +1,121 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPANIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLATITUDESPANIMPL_H
#include "FsLatitudeSpan.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLatitudeSpan::FsLatitudeSpan() = default;
inline FsLatitudeSpan::FsLatitudeSpan(double DecimalDegrees)
{
this->span = DecimalDegrees;
}
inline FsLatitudeSpan::FsLatitudeSpan(std::int32_t Degrees,
double DecimalMinutes)
: FsLatitudeSpan(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLatitudeSpan::FsLatitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLatitudeSpan(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline FsLatitudeSpan FsLatitudeSpan::FromFeet(double Feet)
{
return FsLatitudeSpan(Feet / 364601.4567);
}
inline FsLatitudeSpan
FsLatitudeSpan::FromNauticalMiles(double NauticalMiles)
{
return FsLatitudeSpan::FromFeet(NauticalMiles * 6076.1155);
}
inline FsLatitudeSpan FsLatitudeSpan::FromMetres(double Metres)
{
return FsLatitudeSpan::FromFeet(Metres * 3.2808);
}
inline FsLatitudeSpan
FsLatitudeSpan::BetweenTwoLatitides(const FsLatitude &Lat1,
const FsLatitude &Lat2)
{
return FsLatitudeSpan(abs(Lat2.UDegrees() - Lat1.UDegrees()));
}
inline double FsLatitudeSpan::DecimalDegrees() const { return this->span; }
inline double FsLatitudeSpan::DecimalMinutes() const
{
return (this->span - trunc(this->span)) * 60.0;
}
inline double FsLatitudeSpan::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLatitudeSpan::Degrees() const
{
return trunc(this->span);
}
inline std::int32_t FsLatitudeSpan::Minutes() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLatitudeSpan::Seconds() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLatitudeSpan::TotalMinutes() const
{
return this->span * 60.0;
}
inline double FsLatitudeSpan::TotalSeconds() const
{
return this->span * 3600.0;
}
inline double FsLatitudeSpan::ToFeet() const
{
return 364601.4567 * this->span;
}
inline double FsLatitudeSpan::ToNauticalMiles() const
{
return this->ToFeet() / 6076.1155;
}
inline double FsLatitudeSpan::ToMetres() const
{
return this->ToFeet() / 3.2808;
}
inline const std::string FsLatitudeSpan::to_string(char DetailLevel) const
{
std::ostringstream str;
switch (DetailLevel) {
case 'm':
str << this->Degrees() << "* " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degrees() << "* " << this->Minutes() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->span << "*";
break;
}
return str.str();
}
inline const std::string FsLatitudeSpan::to_string()
{
return this->to_string('m');
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+60
View File
@@ -0,0 +1,60 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDE_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDE_H
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLongitudeSpan;
class FsLongitude
{
private:
double pos;
public:
inline FsLongitude();
inline FsLongitude(double DecimalDegrees);
inline FsLongitude(std::int64_t FSUnits);
inline FsLongitude(std::int32_t FSUnits);
inline FsLongitude(std::int32_t Degrees, double DecimalMinutes);
inline FsLongitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline long ToFSUnits8() const;
inline std::int32_t ToFSUnits4() const;
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degree() const;
inline std::int32_t Minute() const;
inline std::int32_t Second() const;
inline double UDegrees() const;
inline const std::string to_string(bool HemisphereAsText,
char DetailLevel) const;
inline const std::string to_string() const;
inline FsLongitude Add(const FsLongitudeSpan &Distance) const;
inline FsLongitude Subtract(const FsLongitudeSpan &Distance) const;
inline FsLongitude AddDegrees(double Degrees) const;
inline FsLongitude AddMinutes(double Minutes) const;
inline FsLongitude AddSeconds(double Seconds) const;
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+133
View File
@@ -0,0 +1,133 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDEIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDEIMPL_H
#include "FsLongitude.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLongitude::FsLongitude() = default;
inline FsLongitude::FsLongitude(double DecimalDegrees)
{
this->pos = DecimalDegrees;
while (this->pos < -180.0 || this->pos > 180.0) {
if (this->pos > 180.0)
this->pos -= 360.0;
if (this->pos < -180.0)
this->pos += 360.0;
}
}
inline FsLongitude::FsLongitude(std::int64_t FSUnits)
: FsLongitude(FSUnits * 360.0 / 1.8446744073709552E+19)
{
}
inline FsLongitude::FsLongitude(std::int32_t FSUnits)
: FsLongitude(FSUnits * 360.0 / 4294967296.0)
{
}
inline FsLongitude::FsLongitude(std::int32_t Degrees, double DecimalMinutes)
: FsLongitude(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLongitude::FsLongitude(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLongitude(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline long FsLongitude::ToFSUnits8() const
{
return this->pos * 1.8446744073709552E+19 / 360.0;
}
inline std::int32_t FsLongitude::ToFSUnits4() const
{
return this->pos * 4294967296.0 / 360.0;
}
inline double FsLongitude::DecimalDegrees() const { return this->pos; }
inline double FsLongitude::DecimalMinutes() const
{
return (this->pos - trunc(this->pos)) * 60.0;
}
inline double FsLongitude::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLongitude::Degree() const
{
return trunc(this->DecimalDegrees());
}
inline std::int32_t FsLongitude::Minute() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLongitude::Second() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLongitude::UDegrees() const { return this->pos + 180.0; }
inline const std::string FsLongitude::to_string(bool HemisphereAsText,
char DetailLevel) const
{
std::ostringstream str;
if (!HemisphereAsText) {
str << (this->pos < 0.0 ? "-" : "");
} else {
str << (this->pos < 0.0 ? "W" : "E");
}
switch (DetailLevel) {
case 'm':
str << this->Degree() << "° " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degree() << "° " << this->Minute() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->pos << "*";
break;
}
return str.str();
}
inline const std::string FsLongitude::to_string() const
{
return this->to_string(true, 'm');
}
inline FsLongitude FsLongitude::Add(const FsLongitudeSpan &Distance) const
{
return FsLongitude(this->pos + Distance.DecimalDegrees());
}
inline FsLongitude
FsLongitude::Subtract(const FsLongitudeSpan &Distance) const
{
return FsLongitude(this->pos - Distance.DecimalDegrees());
}
inline FsLongitude FsLongitude::AddDegrees(double Degrees) const
{
return FsLongitude(this->pos + Degrees);
}
inline FsLongitude FsLongitude::AddMinutes(double Minutes) const
{
return FsLongitude(this->pos + Minutes / 60.0);
}
inline FsLongitude FsLongitude::AddSeconds(double Seconds) const
{
return FsLongitude(this->pos + Seconds / 3600.0);
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+65
View File
@@ -0,0 +1,65 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPAN_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPAN_H
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdint>
#include <sstream>
#include <string>
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
class FsLongitude;
class FsLatitude;
class FsLongitudeSpan
{
private:
double span;
public:
inline FsLongitudeSpan();
inline FsLongitudeSpan(double DecimalDegrees);
inline FsLongitudeSpan(std::int32_t Degrees, double DecimalMinutes);
inline FsLongitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds);
inline static FsLongitudeSpan FromFeet(double Feet,
const FsLatitude &AtLatitude);
inline static FsLongitudeSpan
FromNauticalMiles(double NauticalMiles,
const FsLatitude &AtLatitude);
inline static FsLongitudeSpan FromMetres(double Metres,
const FsLatitude &AtLatitude);
inline static FsLongitudeSpan
BetweenTwoLongitudes(const FsLongitude &Lon1,
const FsLongitude &Lon2);
inline double DecimalDegrees() const;
inline double DecimalMinutes() const;
inline double DecimalSeconds() const;
inline std::int32_t Degrees() const;
inline std::int32_t Minutes() const;
inline std::int32_t Seconds() const;
inline double TotalMinutes() const;
inline double TotalSeconds() const;
inline double ToFeet(const FsLatitude &AtLatitude) const;
inline double ToNauticalMiles(const FsLatitude &AtLatitude) const;
inline double ToMetres(const FsLatitude &AtLatitude) const;
inline const std::string to_string(char DetailLevel) const;
inline const std::string to_string();
};
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+133
View File
@@ -0,0 +1,133 @@
#ifndef GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPANIMPL_H
#define GERMANAIRLINESVA_FILE_FSUIPC_FSLONGITUDESPANIMPL_H
#include "FsLongitudeSpan.h"
namespace germanairlinesva
{
namespace file
{
namespace FSUIPC
{
inline FsLongitudeSpan::FsLongitudeSpan() = default;
inline FsLongitudeSpan::FsLongitudeSpan(double DecimalDegrees)
{
this->span = DecimalDegrees;
}
inline FsLongitudeSpan::FsLongitudeSpan(std::int32_t Degrees,
double DecimalMinutes)
: FsLongitudeSpan(Degrees + DecimalMinutes / 60.0)
{
}
inline FsLongitudeSpan::FsLongitudeSpan(std::int32_t Degrees,
std::int32_t Minutes,
double DecimalSeconds)
: FsLongitudeSpan(Degrees + Minutes / 60.0 + DecimalSeconds / 3600.0)
{
}
inline FsLongitudeSpan
FsLongitudeSpan::FromFeet(double Feet, const FsLatitude &AtLatitude)
{
double num =
cos(M_PI * AtLatitude.DecimalDegrees() / 180.0) * 131479672.3 / 360.0;
return FsLongitudeSpan(Feet / num);
}
inline FsLongitudeSpan
FsLongitudeSpan::FromNauticalMiles(double NauticalMiles,
const FsLatitude &AtLatitude)
{
return FsLongitudeSpan::FromFeet(NauticalMiles * 6076.1155, AtLatitude);
}
inline FsLongitudeSpan
FsLongitudeSpan::FromMetres(double Metres, const FsLatitude &AtLatitude)
{
return FsLongitudeSpan::FromFeet(Metres * 3.2808, AtLatitude);
}
inline FsLongitudeSpan
FsLongitudeSpan::BetweenTwoLongitudes(const FsLongitude &Lon1,
const FsLongitude &Lon2)
{
return fmod(Lon2.UDegrees() - Lon1.UDegrees(), 360.0) <
fmod(Lon1.UDegrees() - Lon2.UDegrees(), 360.0)
? FsLongitudeSpan(
fmod(Lon2.UDegrees() - Lon1.UDegrees(), 360.0))
: FsLongitudeSpan(
fmod(Lon1.UDegrees() - Lon2.UDegrees(), 360.0));
}
inline double FsLongitudeSpan::DecimalDegrees() const { return this->span; }
inline double FsLongitudeSpan::DecimalMinutes() const
{
return (this->span - trunc(this->span)) * 60.0;
}
inline double FsLongitudeSpan::DecimalSeconds() const
{
double decimalMinutes = this->DecimalMinutes();
return (decimalMinutes - trunc(decimalMinutes)) * 60.0;
}
inline std::int32_t FsLongitudeSpan::Degrees() const
{
return trunc(this->span);
}
inline std::int32_t FsLongitudeSpan::Minutes() const
{
return trunc(this->DecimalMinutes());
}
inline std::int32_t FsLongitudeSpan::Seconds() const
{
return trunc(this->DecimalSeconds());
}
inline double FsLongitudeSpan::TotalMinutes() const
{
return this->span * 60.0;
}
inline double FsLongitudeSpan::TotalSeconds() const
{
return this->span * 3600.0;
}
inline double FsLongitudeSpan::ToFeet(const FsLatitude &AtLatitude) const
{
return cos(M_PI * AtLatitude.DecimalDegrees() / 180.0) * 131479672.3 /
360.0 * this->span;
}
inline double
FsLongitudeSpan::ToNauticalMiles(const FsLatitude &AtLatitude) const
{
return this->ToFeet(AtLatitude) / 6076.1155;
}
inline double FsLongitudeSpan::ToMetres(const FsLatitude &AtLatitude) const
{
return this->ToFeet(AtLatitude) / 3.2808;
}
inline const std::string FsLongitudeSpan::to_string(char DetailLevel) const
{
std::ostringstream str;
switch (DetailLevel) {
case 'm':
str << this->Degrees() << "* " << this->DecimalMinutes() << "'";
break;
case 's':
str << this->Degrees() << "* " << this->Minutes() << "' "
<< this->DecimalSeconds() << "\"";
break;
default:
str << this->span << "*";
break;
}
return str.str();
}
inline const std::string FsLongitudeSpan::to_string()
{
return this->to_string('m');
}
} // namespace FSUIPC
} // namespace file
} // namespace germanairlinesva
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef GERMANAIRLINESVA_FILE_CONFIG_H
#define GERMANAIRLINESVA_FILE_CONFIG_H
#include <map>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include "constants.h"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace config
{
class Config
{
private:
std::string user;
std::string scenery;
std::string token;
void writeFile() const;
public:
Config();
void updateScenery(std::string scenery);
inline const std::string getUser() const { return this->user; }
inline const std::string getScenery() const { return this->scenery; }
inline const std::string getToken() const { return this->token; }
};
} // namespace config
} // namespace file
} // namespace germanairlinesva
#endif
+57
View File
@@ -0,0 +1,57 @@
#ifndef GERMANAIRLINESVA_FILE_HELPERS_H
#define GERMANAIRLINESVA_FILE_HELPERS_H
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
namespace germanairlinesva
{
namespace file
{
static inline const std::string readString(std::ifstream &in,
std::uint8_t length)
{
std::vector<char> value(length + 1);
in.read(value.data(), length);
return std::string(std::move(value.data()));
}
static inline const std::string readString(std::ifstream &in)
{
std::uint8_t length;
in.read(reinterpret_cast<char *>(&length), 1);
std::vector<char> value(length + 1);
in.read(value.data(), length);
return std::string(std::move(value.data()));
}
template <typename T> static inline const T read(std::ifstream &in)
{
T value;
in.read(reinterpret_cast<char *>(&value), sizeof(value));
return value;
}
static inline void writeString(std::ofstream &out, std::string value)
{
std::uint8_t length = value.length();
out.write(reinterpret_cast<char *>(&length), sizeof(length));
out.write(value.data(), length);
}
static inline void
writeString(std::ofstream &out, std::string value, std::uint8_t length)
{
out.write(value.data(), length);
}
template <typename T> static inline void write(std::ofstream &out, T value)
{
out.write(reinterpret_cast<char *>(&value), sizeof(value));
}
} // namespace file
} // namespace germanairlinesva
#endif
+50
View File
@@ -0,0 +1,50 @@
#ifndef GERMANAIRLINESVA_FILE_LOGBOOK_H
#define GERMANAIRLINESVA_FILE_LOGBOOK_H
#include <cstdint>
#include <fstream>
#include <string>
#include <utility>
#include <vector>
#include "constants.h"
#include "helpers.hpp"
#include "logbookEntry.h"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
/*
* Logbook Header (6)
* CHAR[5] | UINT8
* --------+--------
* VGAL | VERSION
*
* Logbook Entries (n)
* LOGBOOKENTRY[]
*/
class Logbook
{
private:
std::vector<LogbookEntry> entries;
void fromFile();
void readVersion1(std::ifstream &in);
public:
Logbook();
template <class... Args> inline void addEntry(Args &&...args)
{
this->entries.emplace_back(std::forward<Args>(args)...);
}
void toFile() const;
};
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
#endif
+158
View File
@@ -0,0 +1,158 @@
#ifndef GERMANAIRLINESVA_FILE_LOGBOOKENTRY_H
#define GERMANAIRLINESVA_FILE_LOGBOOKENTRY_H
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
#include "helpers.hpp"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
/*
* Preamble (24)
* CHAR[10] | CHAR[4] | CHAR[4] | CHAR[6]
* ---------+---------------+----------+-------------
* DATE | FLIGHT NUMBER | AIRCRAFT | REGISTRATION
* Departure Airport Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Departure Gate Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Departure Runway Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Arrival Airport Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Arrival Gate Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Arrival Runway Name (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Times (24)
* CHAR[5] | CHAR[5] | CHAR[5] | CHAR[4] | FLOAT
* ----------+---------------+--------------+---------------+------
* OFF BLOCK | TAKEOFF (OUT) | LANDING (ON) | ON BLOCK (IN) | TOTAL
* Fuels (16)
* FLOAT | FLOAT | FLOAT | FLOAT
* ---------+-----------+---------+------
* TAXI OUT | IN FLIGHT | TAXI IN | TOTAL
* Distances (16)
* FLOAT | FLOAT | FLOAT |FLOAT
* ---------+-----------+---------+-----
* TAXI OUT | IN FLIGHT | TAXI IN |TOTAL
* Landing (9)
* FLOAT | CHAR | FLOAT
* ---------+---------- +------------
* MAX RATE | TOUCHDOWNS | MAX G-FORCE
* Recording Filename (2...256)
* UINT8 | CHAR[]
* -------+-------
* STRLEN | STRING
* Postamble (5)
* FLOAT | BITFIELD
* -------+---------
* POINTS | FLAGS
* Flags Bitfield
* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* ----+-----+-----+-----+-----+-----+-----+------
* NIL | NIL | NIL | NIL | NIL | NIL | NIL | FILED
*/
class LogbookEntry
{
private:
std::string date;
std::string flightNumber;
std::string aircraftType;
std::string aircraftRegistration;
std::string departureAirport;
std::string departureGate;
std::string departureRunway;
std::string arrivalAirport;
std::string arrivalGate;
std::string arrivalRunway;
std::string offBlockTime;
std::string outTime;
std::string onTime;
std::string onBlockTime;
float totalFlightTime;
float taxiOutFuel;
float inFlightFuel;
float taxiInFuel;
float totalFuel;
float taxiOutDistance;
float inFlightDistance;
float taxiInDistance;
float totalDistance;
float maxLandingRate;
std::uint8_t touchdowns;
float maxLandingGees;
std::string recordingFilename;
float points;
std::uint8_t flags;
public:
LogbookEntry(std::string date,
std::string flightNumber,
std::string aircraftType,
std::string aircraftRegistration,
std::string departureAirport,
std::string departureGate,
std::string departureRunway,
std::string arrivalAirport,
std::string arrivalGate,
std::string arrivalRunway,
std::string offBlockTime,
std::string outTime,
std::string onTime,
std::string onBlockTime,
float totalFlightTime,
float taxiOutFuel,
float inFlightFuel,
float taxiInFuel,
float totalFuel,
float taxiOutDistance,
float inFlightDistance,
float taxiInDistance,
float totalDistance,
float maxLandingRate,
std::uint8_t touchdowns,
float maxLandingGees,
std::string recordingFilename,
float points,
std::uint8_t flags);
void toFile(std::ofstream &out) const;
};
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
#endif
+45
View File
@@ -0,0 +1,45 @@
#ifndef GERMANAIRLINESVA_FILE_RECORDING_H
#define GERMANAIRLINESVA_FILE_RECORDING_H
#include <cstdint>
#include <fstream>
#include <vector>
#include "constants.h"
#include "recordingEntry.h"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
/*
* Path Recording (6 + n * 24)
* HEADER | SEGMENTS
* Header (6)
* CHAR[5] | UINT8
* --------+--------
* VGAR | VERSION
* Path Segments (n)
* PATHSEGMENT[]
*/
class Recording
{
private:
std::vector<RecordingEntry> entries;
public:
template <class... Args> inline void addEntry(Args &&...args)
{
this->entries.emplace_back(std::forward<Args>(args)...);
}
void toFile(std::string fileName) const;
};
} // namespace recording
} // namespace file
} // namespace germanairlinesva
#endif
+60
View File
@@ -0,0 +1,60 @@
#ifndef GERMANAIRLINESVA_FILE_RECORDINGENTRY_H
#define GERMANAIRLINESVA_FILE_RECORDINGENTRY_H
#include <cstdint>
#include <fstream>
#include <vector>
#include "geodata.hpp"
#include "helpers.hpp"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
/*
* Path Segment (24)
* UINT32 | UINT16 | UINT16 | POINT
* -------+----------+-------------+------------
* TIME | ALTITUDE | GROUNDSPEED | COORDINATES
*/
class RecordingEntry
{
private:
std::uint32_t time;
std::uint16_t altitude = 0;
std::uint16_t groundSpeed = 0;
struct geodata::point coordinates {
NAN, NAN
};
public:
RecordingEntry() = default;
RecordingEntry(std::uint32_t time,
std::uint16_t altitude,
std::uint16_t groundSpeed,
struct geodata::point coordinates);
void toFile(std::ofstream &out) const;
friend inline bool operator==(const RecordingEntry &lhs,
const RecordingEntry &rhs)
{
return lhs.altitude == rhs.altitude &&
lhs.groundSpeed == rhs.groundSpeed &&
lhs.coordinates.latitude == rhs.coordinates.latitude &&
lhs.coordinates.longitude == rhs.coordinates.longitude;
}
friend inline bool operator!=(const RecordingEntry &lhs,
const RecordingEntry &rhs)
{
return !(lhs == rhs);
}
};
} // namespace recording
} // namespace file
} // namespace germanairlinesva
#endif
+72
View File
@@ -0,0 +1,72 @@
#ifndef GERMANAIRLINESVA_FILE_GATE_H
#define GERMANAIRLINESVA_FILE_GATE_H
#include <cstdint>
#include <fstream>
#include <iostream>
#include <sstream>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include "geodata.hpp"
#include "helpers.hpp"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
/*
* Representation of gate
* Center in lat/lon radians
* Radius in metres
*
* UINT8 | CHAR[] | POINT | UINT8
* -------+------------+--------+-------
* STRLEN | DESIGNATOR | CENTER | RADIUS
*/
class Gate
{
private:
std::string designator;
struct geodata::point center;
std::uint8_t radius;
public:
// From X-Plane or MakeRwys
Gate(std::string designator,
double latitude,
double longitude,
std::uint8_t radius);
// From Database
Gate(std::string designator,
struct geodata::point center,
std::uint8_t radius);
void toFile(std::ofstream &out) const;
bool contains(geodata::point coordinates) const;
inline const std::string to_string() const
{
std::ostringstream str;
str << "Gate " << this->designator << " at "
<< geodata::toDegrees(this->center.latitude) << "N "
<< geodata::toDegrees(this->center.longitude) << "E, Radius "
<< (int)this->radius;
return str.str();
}
friend inline std::ostream &operator<<(std::ostream &os,
const Gate &gate)
{
return os << gate.to_string();
}
};
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
+125
View File
@@ -0,0 +1,125 @@
#ifndef GERMANAIRLINESVA_FILE_SIMDATA_RUNWAY_H
#define GERMANAIRLINESVA_FILE_SIMDATA_RUNWAY_H
#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <utility>
#include "FSUIPC/FsGeoData.h"
#include "geodata.hpp"
#include "helpers.hpp"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
/*
* Representation of one runway with supplementary information
* Heading in degrees true
* Threshold center in lat/lon degrees
* Width and length in feet
*
* UINT8 | CHAR[] | POINT | UINT8 | UINT16 | DOUBLE
* -------+------------+--------+-------+--------+-------
* STRLEN | DESIGNATOR | CENTER | WIDTH | LENGTH | TRUHDG
*/
class Runway
{
private:
std::string designator;
struct geodata::point center;
std::uint8_t width;
std::uint16_t length;
double trueHeading;
// QUAD TEST
FSUIPC::FsLatLonQuadrilateral quad;
public:
// From X-Plane or MakeRwys
inline Runway(std::string designator,
double latitudeStart,
double longitudeStart,
double latitudeEnd,
double longitudeEnd,
double width)
{
this->designator = designator;
this->width = width * 33.280839895;
this->length = geodata::distanceEarthD(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd) *
3.280839895;
this->trueHeading = geodata::bearingDD(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd);
this->center = {latitudeStart, longitudeStart};
FSUIPC::FsLatLonPoint threshold(latitudeStart, longitudeStart);
quad = FSUIPC::FsLatLonQuadrilateral::ForRunway(threshold,
this->trueHeading,
this->width,
this->length);
}
// From database
inline Runway(std::string designator,
struct geodata::point center,
std::uint8_t width,
std::uint16_t length,
double trueHeading)
{
this->designator = designator;
this->center = center;
this->width = width;
this->length = length;
this->trueHeading = trueHeading;
FSUIPC::FsLatLonPoint threshold(center.latitude, center.longitude);
quad = FSUIPC::FsLatLonQuadrilateral::ForRunway(threshold,
trueHeading,
width,
length);
}
inline void toFile(std::ofstream &out) const
{
writeString(out, this->designator);
write<decltype(this->center)>(out, this->center);
write<decltype(this->width)>(out, this->width);
write<decltype(this->length)>(out, this->length);
write<decltype(this->trueHeading)>(out, this->trueHeading);
}
inline const std::string to_string() const
{
std::ostringstream str;
str << "Runway " << this->designator << " with threshold center "
<< this->center.latitude << "N " << this->center.longitude
<< "E, Width " << (int)this->width << "ft, Length "
<< this->length << "ft, True Heading " << this->trueHeading
<< "°, Quad " << quad.to_string(true, ' ');
return str.str();
}
friend inline std::ostream &operator<<(std::ostream &os,
const Runway &runway)
{
return os << runway.to_string();
}
};
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
+76
View File
@@ -0,0 +1,76 @@
#ifndef GERMANAIRLINESVA_FILE_SIMDATABASE_H
#define GERMANAIRLINESVA_FILE_SIMDATABASE_H
#include <cstdint>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "config/config.h"
#include "constants.h"
#include "simdata/gate.h"
#include "simdata/runway.h"
#include "simdata/simdataXP.h"
/*
* Header
*
* CHAR[5] | UINT8
* --------+--------
* VGAS | VERSION
*/
/*
* Airport
*
* UINT8 | CHAR[] | UINT16 | GATE[] | UINT8 | RUNWAY[]
* --------+--------+----------+--------+---------+---------
* STRLEN | ICAO | NUMGATES | GATES | NUMRWYS | RUNWAYS
*/
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
class SimDatabase
{
private:
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
void fromFile();
void readVersion1(std::ifstream &in);
public:
SimDatabase(int xPlaneVersion,
const char *hash,
std::unique_ptr<config::Config> &configuration,
std::function<void(const std::string)> toLog);
void toFile() const;
inline std::size_t getCount() const { return this->airports.size(); }
inline const std::pair<const std::vector<Gate>,
const std::vector<Runway>>
operator[](std::string key)
{
auto it = this->airports.find(key);
if (it != this->airports.end()) {
return this->airports[key];
} else {
return std::pair<std::vector<Gate>, std::vector<Runway>>();
}
}
};
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef GERMANAIRLINESVA_FILE_SIMDATAXP_H
#define GERMANAIRLINESVA_FILE_SIMDATAXP_H
#include <fstream>
#include <map>
#include <string>
#include "simdata/gate.h"
#include "simdata/runway.h"
#include "simdata/simDatabase.h"
#include "util.hpp"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
int scan(
const std::string defaultFile,
const std::string sceneryPack,
const std::string logFile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports);
void makeAirport(
const std::string &kind,
std::ifstream &infile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
std::ofstream &logfile);
void makeGate15(std::vector<Gate> &gates,
const std::vector<std::string> &fields);
void makeRunway(std::vector<Runway> &runways,
const std::vector<std::string> &fields);
void makeGate1300(std::vector<Gate> &gates,
const std::vector<std::string> &fields);
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
#endif
+109
View File
@@ -0,0 +1,109 @@
#include "logbook/logbook.h"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
Logbook::Logbook()
{
if (util::fileExists(XPLANE_PLUGIN_DIRECTORY LOGBOOK)) {
this->fromFile();
}
}
void Logbook::fromFile()
{
std::ifstream in(XPLANE_PLUGIN_DIRECTORY LOGBOOK, std::ifstream::binary);
// File Header
std::string ident = readString(in, 5);
if (ident.compare("VGAL") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version = read<std::uint8_t>(in);
if (version == 1) {
while (in.peek() != EOF) {
this->readVersion1(in);
}
}
}
void Logbook::readVersion1(std::ifstream &in)
{
std::string date = readString(in, 10);
std::string flightNumber = readString(in, 4);
std::string aircraftType = readString(in, 4);
std::string aircraftRegistration = readString(in, 6);
std::string departureAirport = readString(in);
std::string departureGate = readString(in);
std::string departureRunway = readString(in);
std::string arrivalAirport = readString(in);
std::string arrivalGate = readString(in);
std::string arrivalRunway = readString(in);
std::string offBlockTime = readString(in, 5);
std::string outTime = readString(in, 5);
std::string inTime = readString(in, 5);
std::string onBlockTime = readString(in, 5);
float totalFlightTime = read<float>(in);
float taxiOutFuel = read<float>(in);
float inFlightFuel = read<float>(in);
float taxiInFuel = read<float>(in);
float totalFuel = read<float>(in);
float taxiOutDistance = read<float>(in);
float inFlightDistance = read<float>(in);
float taxiInDistance = read<float>(in);
float totalDistance = read<float>(in);
float maxLandingRate = read<float>(in);
std::uint8_t touchdowns = read<std::uint8_t>(in);
float maxLandingGees = read<float>(in);
std::string recordingFilename = readString(in);
float points = read<float>(in);
std::uint8_t flags = read<std::uint8_t>(in);
this->addEntry(date,
flightNumber,
aircraftType,
aircraftRegistration,
departureAirport,
departureGate,
departureRunway,
arrivalAirport,
arrivalGate,
arrivalRunway,
offBlockTime,
outTime,
inTime,
onBlockTime,
totalFlightTime,
taxiOutFuel,
inFlightFuel,
taxiInFuel,
totalFuel,
taxiOutDistance,
inFlightDistance,
taxiInDistance,
totalDistance,
maxLandingRate,
touchdowns,
maxLandingGees,
recordingFilename,
points,
flags);
}
void Logbook::toFile() const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY LOGBOOK, std::fstream::binary);
char header[] = {'V', 'G', 'A', 'L', '\0', 1};
out.write(header, 6);
for (const LogbookEntry &entry : this->entries) {
entry.toFile(out);
}
out.close();
}
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
+104
View File
@@ -0,0 +1,104 @@
#include "logbook/logbookEntry.h"
namespace germanairlinesva
{
namespace file
{
namespace logbook
{
LogbookEntry::LogbookEntry(std::string date,
std::string flightNumber,
std::string aircraftType,
std::string aircraftRegistration,
std::string departureAirport,
std::string departureGate,
std::string departureRunway,
std::string arrivalAirport,
std::string arrivalGate,
std::string arrivalRunway,
std::string offBlockTime,
std::string outTime,
std::string onTime,
std::string onBlockTime,
float totalFlightTime,
float taxiOutFuel,
float inFlightFuel,
float taxiInFuel,
float totalFuel,
float taxiOutDistance,
float inFlightDistance,
float taxiInDistance,
float totalDistance,
float maxLandingRate,
std::uint8_t touchdowns,
float maxLandingGees,
std::string recordingFilename,
float points,
std::uint8_t flags)
{
this->date = date;
this->flightNumber = flightNumber;
this->aircraftType = aircraftType;
this->aircraftRegistration = aircraftRegistration;
this->departureAirport = departureAirport;
this->departureGate = departureGate;
this->departureRunway = departureRunway;
this->arrivalAirport = arrivalAirport;
this->arrivalGate = arrivalGate;
this->arrivalRunway = arrivalRunway;
this->offBlockTime = offBlockTime;
this->outTime = outTime;
this->onTime = onTime;
this->onBlockTime = onBlockTime;
this->totalFlightTime = totalFlightTime;
this->taxiOutFuel = taxiOutFuel;
this->inFlightFuel = inFlightFuel;
this->taxiInFuel = taxiInFuel;
this->totalFuel = totalFuel;
this->taxiOutDistance = taxiOutDistance;
this->inFlightDistance = inFlightDistance;
this->taxiInDistance = taxiInDistance;
this->totalDistance = totalDistance;
this->maxLandingRate = maxLandingRate;
this->touchdowns = touchdowns;
this->maxLandingGees = maxLandingGees;
this->recordingFilename = recordingFilename;
this->points = points;
this->flags = flags;
}
void LogbookEntry::toFile(std::ofstream &out) const
{
writeString(out, this->date, 10);
writeString(out, this->flightNumber, 4);
writeString(out, this->aircraftType, 4);
writeString(out, this->aircraftRegistration, 6);
writeString(out, this->departureAirport);
writeString(out, this->departureGate);
writeString(out, this->departureRunway);
writeString(out, this->arrivalAirport);
writeString(out, this->arrivalGate);
writeString(out, this->arrivalRunway);
writeString(out, this->offBlockTime, 5);
writeString(out, this->outTime, 5);
writeString(out, this->onTime, 5);
writeString(out, this->onBlockTime, 5);
write<decltype(this->totalFlightTime)>(out, this->totalFlightTime);
write<decltype(this->taxiOutFuel)>(out, this->taxiOutFuel);
write<decltype(this->inFlightFuel)>(out, this->inFlightFuel);
write<decltype(this->taxiInFuel)>(out, this->taxiInFuel);
write<decltype(this->totalFuel)>(out, this->totalFuel);
write<decltype(this->taxiOutDistance)>(out, this->taxiOutDistance);
write<decltype(this->inFlightDistance)>(out, this->inFlightDistance);
write<decltype(this->taxiInDistance)>(out, this->taxiInDistance);
write<decltype(this->totalDistance)>(out, this->totalDistance);
write<decltype(this->maxLandingRate)>(out, this->maxLandingRate);
write<decltype(this->touchdowns)>(out, this->touchdowns);
write<decltype(this->maxLandingGees)>(out, this->maxLandingGees);
writeString(out, this->recordingFilename);
write<decltype(this->points)>(out, this->points);
write<decltype(this->flags)>(out, this->flags);
}
} // namespace logbook
} // namespace file
} // namespace germanairlinesva
-57
View File
@@ -1,57 +0,0 @@
#include <cstdint>
#include <cstring>
#include <vector>
/*
* Length in bytes: 20
*
* UINT16 | UINT16 | DOUBLE | DOUBLE |
* ---------+-------------+----------+-----------+
* ALTITUDE | GROUNDSPEED | LATITUDE | LONGITUDE |
*/
class PathSegment
{
private:
std::uint16_t altitude = 0;
std::uint16_t groundSpeed = 0;
double latitude = 0;
double longitude = 0;
std::vector<std::uint8_t> file;
public:
PathSegment() = default;
PathSegment(std::uint16_t altitude,
std::uint16_t groundSpeed,
double latitude,
double longitude)
{
this->altitude = altitude;
this->groundSpeed = groundSpeed;
this->latitude = latitude;
this->longitude = longitude;
file = std::vector<std::uint8_t>(20, 0);
std::uint8_t *bufPtr = file.data();
memcpy(bufPtr, &this->altitude, sizeof(this->altitude));
bufPtr += sizeof(this->altitude);
memcpy(bufPtr, &this->groundSpeed, sizeof(this->groundSpeed));
bufPtr += sizeof(this->groundSpeed);
memcpy(bufPtr, &this->latitude, sizeof(this->latitude));
bufPtr += sizeof(this->latitude);
memcpy(bufPtr, &this->longitude, sizeof(this->longitude));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
friend bool operator==(const PathSegment &lhs, const PathSegment &rhs)
{
return lhs.altitude == rhs.altitude &&
lhs.groundSpeed == rhs.groundSpeed &&
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
}
friend bool operator!=(const PathSegment &lhs, const PathSegment &rhs)
{
return !(lhs == rhs);
}
};
+21
View File
@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 0,
"runtimeArgs": [
"-dxdebug.start_with_request=yes"
],
"env": {
"XDEBUG_MODE": "debug,develop",
"XDEBUG_CONFIG": "client_port=${port}"
}
}]
}
+136
View File
@@ -0,0 +1,136 @@
<?php
namespace germanairlinesva_recording;
class Recording
{
private const ident = "VGAR";
private const header_unpack = "Z5ident/Cversion";
private const segment_unpack = "Vtime/Saltitude/Sgroundspeed/elatitude/elongitude";
private string $file_name;
public function __construct(string $file)
{
$this->file_name = $file;
}
/**
* Generates a geoJSON representation
*
* @return geoJSON representation of path
*/
public function geoJSON()
{
$geoJSON = [
"type" => "FeatureCollection",
"features" => [
0 => [
"type" => "Feature",
"properties" => [
"stroke" => "#ff0000",
"stroke-width" => 2,
"stroke-opacity" => 1
],
"geometry" => [
"type" => "LineString",
"coordinates" => []
]
]
]
];
$segments = $this->read();
$geoJSON["features"][0]["geometry"]["coordinates"] = array_map(function ($segment) {
return array($segment["longitude"], $segment["latitude"]);
}, $segments);
return json_encode($geoJSON);
}
/**
* Reads recording
*
* Array of objects with
* @return time Segment time (number)
* @return altitude Altitude
* @return groundspeed Ground speed
* @return latitude Latitude
* @return longitude Longitude
*/
public function read()
{
$file = fopen($this->file_name, "rb");
flock($file, LOCK_SH);
$segments = [];
$version = $this->read_header($file);
if ($version == 1) {
while (($segment = $this->read_segment_1($file)) != false) {
array_push($segments, $segment);
}
}
flock($file, LOCK_UN);
fclose($file);
return $segments;
}
/**
* Reads one segment entry
*
* @param resource $file File handle
*
* If not EOF
* @return time Segment time (number)
* @return altitude Altitude
* @return groundspeed Ground speed
* @return latitude Latitude
* @return longitude Longitude
* Else
* @return false
*
* @throws InvalidArgumentException If file is not a resource
*/
private function read_segment_1($file)
{
if (false === is_resource($file)) {
throw new \InvalidArgumentException(sprintf('Argument must be a valid resource type. %s given.', gettype($file)));
}
$data = fread($file, 24);
if ($data) {
return unpack(Recording::segment_unpack, $data);
}
return false;
}
/**
* Reads the file header
*
* @param resource file File handle
*
* @return version File version
*
* @throws InvalidArgumentException If file is not a resource
* @throws UnexpectedValueException If ident mismatches
*/
private function read_header($file)
{
if (false === is_resource($file)) {
throw new \InvalidArgumentException(sprintf('Argument must be a valid resource type. %s given.', gettype($file)));
}
$header = unpack(Recording::header_unpack, fread($file, 6));
if ($header["ident"] !== Recording::ident) {
throw
new \UnexpectedValueException(sprintf("Ident mismatch. Got %s, expected %s", $header["ident"], Recording::ident));
}
return $header["version"];
}
}
+6
View File
@@ -0,0 +1,6 @@
<?php
require "Recording.php";
$r = new germanairlinesva_recording\Recording("/mnt/f/X-Plane 11/Resources/plugins/GAConnector/recordings/flight.rec");
print_r($r->geoJSON());
+22
View File
@@ -0,0 +1,22 @@
#include "recording/recording.h"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
void Recording::toFile(std::string fileName) const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY RECORDING_DIRECTORY + fileName,
std::fstream::binary);
char header[] = {'V', 'G', 'A', 'R', '\0', 1};
out.write(header, 6);
for (const RecordingEntry &entry : this->entries) {
entry.toFile(out);
}
out.close();
}
} // namespace recording
} // namespace file
} // namespace germanairlinesva
+30
View File
@@ -0,0 +1,30 @@
#include "recording/recordingEntry.h"
namespace germanairlinesva
{
namespace file
{
namespace recording
{
RecordingEntry::RecordingEntry(std::uint32_t time,
std::uint16_t altitude,
std::uint16_t groundSpeed,
struct geodata::point coordinates)
{
this->time = time;
this->altitude = altitude;
this->groundSpeed = groundSpeed;
this->coordinates = coordinates;
};
void RecordingEntry::toFile(std::ofstream &out) const
{
write<decltype(this->time)>(out, this->time);
write<decltype(this->altitude)>(out, this->altitude);
write<decltype(this->groundSpeed)>(out, this->groundSpeed);
write<decltype(this->coordinates)>(out, this->coordinates);
}
} // namespace recording
} // namespace file
} // namespace germanairlinesva
-24
View File
@@ -1,24 +0,0 @@
#include <cstdint>
#include <cstring>
#include <vector>
#include "pathSegment.hpp"
class Path
{
private:
std::uint64_t count = 0;
std::vector<std::uint8_t> file;
public:
void addSegment(PathSegment segment)
{
file.resize(file.size() + segment.getBinaryLength());
std::uint8_t *bufPtr = file.data() + count * segment.getBinaryLength();
memcpy(bufPtr, segment.getBinaryData(), segment.getBinaryLength());
count++;
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
-114
View File
@@ -1,114 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_RUNWAY_H
#define GERMANAIRLINESVA_GACONNECTOR_RUNWAY_H
#include <iomanip>
#include <regex>
#include <sstream>
#include <string>
#include <utility>
#include "util.hpp"
/*
* Representation of one X-Plane runway with supplementary information
* Heading in degrees (0...360)
* Width and length in feet
*
* Length in bytes: 23 + length of designator
* Designator must be null terminated
*
* UINT8 | CHAR[] | DOUBLE | DOUBLE | UINT8 | UINT16 | UINT16
* ------+------------+--------+--------+-------+--------+-------
* LEN | DESIGNATOR | LAT | LON | WIDTH | LENGTH | TRUHDG
*/
class Runway
{
private:
std::string designator;
double latitudeStart;
double longitudeStart;
std::uint8_t width;
std::uint16_t length;
std::uint16_t trueHeading;
std::vector<std::uint8_t> file;
public:
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
double latitudeEnd,
double longitudeEnd,
double width)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = (std::uint8_t)std::round(util::to_feet(width));
double dist = util::distanceEarth(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd);
this->length = (std::uint16_t)std::round(util::to_feet(dist));
this->trueHeading =
(std::uint16_t)std::round(util::bearing(latitudeStart,
longitudeStart,
latitudeEnd,
longitudeEnd));
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
Runway(std::string designator,
double latitudeStart,
double longitudeStart,
std::uint8_t width,
std::uint16_t length,
std::uint16_t trueHeading)
{
this->designator = std::move(designator);
this->latitudeStart = latitudeStart;
this->longitudeStart = longitudeStart;
this->width = width;
this->length = length;
this->trueHeading = trueHeading;
file = std::vector<std::uint8_t>(23 + this->designator.length(), 0);
std::uint8_t *bufPtr = file.data();
memset(bufPtr,
static_cast<std::uint8_t>(this->designator.length()),
sizeof(std::uint8_t));
bufPtr++;
memcpy(bufPtr, this->designator.c_str(), this->designator.length());
bufPtr += this->designator.length() + 1;
memcpy(bufPtr, &this->latitudeStart, sizeof(this->latitudeStart));
bufPtr += sizeof(this->latitudeStart);
memcpy(bufPtr, &this->longitudeStart, sizeof(this->longitudeStart));
bufPtr += sizeof(this->longitudeStart);
memcpy(bufPtr, &this->width, sizeof(this->width));
bufPtr += sizeof(this->width);
memcpy(bufPtr, &this->length, sizeof(this->length));
bufPtr += sizeof(this->length);
memcpy(bufPtr, &this->trueHeading, sizeof(this->trueHeading));
}
std::uint8_t *getBinaryData() { return file.data(); }
std::size_t getBinaryLength() { return file.size(); }
};
#endif
+121
View File
@@ -0,0 +1,121 @@
#include "simdata/simDatabase.h"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
SimDatabase::SimDatabase(int xPlaneVersion,
const char *hash,
std::unique_ptr<config::Config> &configuration,
std::function<void(const std::string)> toLog)
{
if (strcmp(configuration->getScenery().c_str(), hash) != 0 ||
!util::fileExists(XPLANE_PLUGIN_DIRECTORY SIMDATABASE)) {
scan(xPlaneVersion < 12000 ? XPLANE11_BASE_SCENERY
: XPLANE12_BASE_SCENERY,
XPLANE_CUSTOM_SCENERY,
XPLANE_PLUGIN_DIRECTORY "log.txt",
airports);
configuration->updateScenery(hash);
this->toFile();
toLog("Sim Database updated");
} else {
this->fromFile();
toLog("Sim Database loaded");
}
}
void SimDatabase::fromFile()
{
std::ifstream in(XPLANE_PLUGIN_DIRECTORY SIMDATABASE,
std::ifstream::binary);
// File Header
std::string ident = readString(in, 5);
if (ident.compare("VGAS") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version = read<std::uint8_t>(in);
if (version == 1) {
this->readVersion1(in);
}
}
void SimDatabase::readVersion1(std::ifstream &in)
{
std::uint16_t numAirports = read<std::uint16_t>(in);
for (int i = 0; i < numAirports; i++) {
// ICAO
std::string icao = readString(in);
// Gates
std::uint16_t numGates = read<std::uint16_t>(in);
for (int j = 0; j < numGates; j++) {
std::string designator = readString(in);
struct geodata::point center = read<struct geodata::point>(in);
std::uint8_t radius = read<std::uint8_t>(in);
this->airports[icao].first.emplace_back(designator, center, radius);
}
// Runways
std::uint8_t numRunways = read<std::uint8_t>(in);
for (int j = 0; j < numRunways; j++) {
std::string designator = readString(in);
// Center
struct geodata::point center = read<struct geodata::point>(in);
std::uint8_t width = read<std::uint8_t>(in);
std::uint16_t length = read<std::uint16_t>(in);
double trueHeading = read<double>(in);
this->airports[icao].second.emplace_back(designator,
center,
width,
length,
trueHeading);
}
}
in.close();
}
void SimDatabase::toFile() const
{
std::ofstream out(XPLANE_PLUGIN_DIRECTORY SIMDATABASE,
std::fstream::binary);
// File Header, Last member is version
std::uint8_t header[] = {'V', 'G', 'A', 'S', '\0', 1};
out.write(reinterpret_cast<const char *>(header), 6);
// Num Airports
write<std::uint16_t>(out, airports.size());
// Airport
for (const std::pair<const std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
&airport : airports) {
std::string icao = airport.first;
const std::vector<Gate> gates = airport.second.first;
const std::vector<Runway> runways = airport.second.second;
// ICAO
writeString(out, icao);
// Gates
write<std::uint16_t>(out, gates.size());
for (const Gate &gate : gates) {
gate.toFile(out);
}
// Runways
write<std::uint8_t>(out, runways.size());
for (const Runway &runway : runways) {
runway.toFile(out);
}
}
out.close();
}
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
+196
View File
@@ -0,0 +1,196 @@
#include "simdata/simdataXP.h"
namespace germanairlinesva
{
namespace file
{
namespace simdata
{
int scan(
const std::string defaultFile,
const std::string sceneryPack,
const std::string logFile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports)
{
std::ifstream base(defaultFile);
if (!base.good()) {
return 1;
}
std::ifstream custom(sceneryPack);
if (!custom.good()) {
base.close();
return 2;
}
std::ofstream logfile(logFile, std::fstream::trunc);
if (!logfile.good()) {
base.close();
custom.close();
return 3;
}
// Default
logfile << "<FILE> " << defaultFile << std::endl;
makeAirport("DEFAULT", base, airports, logfile);
base.close();
std::string line;
size_t pos;
std::vector<std::string> packs;
while (std::getline(custom, line)) {
if ((pos = line.find("SCENERY_PACK")) != std::string::npos) {
std::string path = util::rtrim_copy(line.substr(pos + 13)) +
"Earth nav data/apt.dat";
packs.emplace_back(path);
}
}
std::reverse(packs.begin(), packs.end());
for (std::string const &path : packs) {
std::ifstream pack(path);
if (pack.good()) {
logfile << "<FILE> " << path << std::endl;
makeAirport("CUSTOM", pack, airports, logfile);
pack.close();
} else {
pack.close();
logfile << "<STATUS>"
<< "Could not find " << path << std::endl;
}
}
logfile << std::endl
<< "<STATUS> Total airports: " << airports.size() << std::endl;
custom.close();
logfile.close();
return 0;
}
void makeAirport(
const std::string &kind,
std::ifstream &infile,
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
std::ofstream &logfile)
{
std::string line;
std::string *currentIcao = nullptr;
std::vector<Gate> tmpGates;
std::vector<Runway> tmpRunways;
int apCount = 0;
int validCount = 0;
while (std::getline(infile, line)) {
std::vector<std::string> fields = util::split(line, ' ');
fields = util::select_T<std::string>(fields, [](const std::string &s) {
return s.length() > 0;
});
if (fields.empty())
continue;
if (fields[0] == "1") {
// Write to file if ICAO is valid, and we have gates and runways
if (currentIcao != nullptr && !tmpRunways.empty() &&
!tmpGates.empty()) {
airports[*currentIcao] = {tmpGates, tmpRunways};
validCount += 1;
logfile << "\t<STATUS> " << *currentIcao << " committed"
<< std::endl;
} else if (currentIcao != nullptr) {
logfile << "\t<STATUS> " << *currentIcao
<< " had no gates or runways" << std::endl;
}
tmpGates = std::vector<Gate>();
tmpRunways = std::vector<Runway>();
currentIcao = new std::string(fields[4]);
apCount += 1;
logfile << "\t<" << kind << "> " << line << std::endl;
} else if (currentIcao != nullptr && fields[0] == "15") {
makeGate15(tmpGates, fields);
logfile << "\t\t<GATE OLD> " << line << std::endl;
} else if (fields[0] == "16" || fields[0] == "17") {
// Write to file if ICAO is valid, and we have gates and runways
if (currentIcao != nullptr && !tmpRunways.empty() &&
!tmpGates.empty()) {
airports[*currentIcao] = {tmpGates, tmpRunways};
validCount += 1;
logfile << "\t<STATUS> " << *currentIcao << " committed"
<< std::endl;
} else if (currentIcao != nullptr) {
logfile << "\t<STATUS> " << *currentIcao
<< " had no gates or runways" << std::endl;
}
tmpGates = std::vector<Gate>();
tmpRunways = std::vector<Runway>();
currentIcao = nullptr;
logfile << "\t<" << kind << " SKIPPED> " << line << std::endl;
} else if (currentIcao != nullptr && fields[0] == "100") {
makeRunway(tmpRunways, fields);
logfile << "\t\t<RUNWAY> " << line << std::endl;
} else if (currentIcao != nullptr && fields[0] == "1300") {
makeGate1300(tmpGates, fields);
logfile << "\t\t<GATE> " << line << std::endl;
}
}
if (currentIcao != nullptr && !tmpRunways.empty() && !tmpGates.empty()) {
airports[*currentIcao] = {tmpGates, tmpRunways};
validCount += 1;
logfile << "\t<STATUS> " << *currentIcao << " committed" << std::endl;
}
logfile << "<STATUS> " << apCount << " airports found, of which "
<< validCount << " are valid" << std::endl;
}
void makeGate15(std::vector<Gate> &gates,
const std::vector<std::string> &fields)
{
std::string gateName;
for (size_t j = 4; j < fields.size() - 1; j++) {
gateName += fields[j] + " ";
}
gateName += fields.back();
gateName = std::regex_replace(gateName, std::regex{","}, "0");
gates.emplace_back(gateName,
std::stod(fields[1]),
std::stod(fields[2]),
40);
}
void makeRunway(std::vector<Runway> &runways,
const std::vector<std::string> &fields)
{
runways.emplace_back(fields[8],
std::stod(fields[9]),
std::stod(fields[10]),
std::stod(fields[18]),
std::stod(fields[19]),
std::stod(fields[1]));
runways.emplace_back(fields[17],
std::stod(fields[18]),
std::stod(fields[19]),
std::stod(fields[9]),
std::stod(fields[10]),
std::stod(fields[1]));
}
void makeGate1300(std::vector<Gate> &gates,
const std::vector<std::string> &fields)
{
std::string gateName;
for (size_t j = 6; j < fields.size() - 1; j++) {
gateName += fields[j] + " ";
}
gateName += fields.back();
gateName = std::regex_replace(gateName, std::regex{","}, "0");
gates.emplace_back(gateName,
std::stod(fields[1]),
std::stod(fields[2]),
40);
}
} // namespace simdata
} // namespace file
} // namespace germanairlinesva
-191
View File
@@ -1,191 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_SIMULATORDATABASE_H
#define GERMANAIRLINESVA_GACONNECTOR_SIMULATORDATABASE_H
#include "gate.hpp"
#include "runway.hpp"
#include "stringExtensions.hpp"
#define CURRENT_VERSION 1
#include <cstdint>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>
/*
* Header
*
* CHAR[5] | UINT8
* --------+--------
* VGAS | VERSION
*/
/*
* Airport
*
* UINT8 | CHAR[] | UINT16 | GATE[] | UINT8 | RUNWAY[]
* --------+--------+----------+--------+---------+---------
* STRLEN | ICAO | NUMGATES | GATES | NUMRWYS | RUNWAYS
*/
namespace simulatorDatabase
{
static inline void toFile(
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
&airports,
const std::string &file)
{
std::uint8_t null = 0;
std::ofstream out(file, std::fstream::binary);
// File Header
std::uint8_t header[] = {'V', 'G', 'A', 'S', 0, CURRENT_VERSION};
out.write(reinterpret_cast<const char *>(header), 6);
// Num Airports
std::uint16_t numAirports = airports.size();
out.write(reinterpret_cast<const char *>(&numAirports),
sizeof(numAirports));
// Airport
for (const std::pair<const std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
&airport : airports) {
std::string icao = airport.first;
std::vector<Gate> gates = airport.second.first;
std::vector<Runway> runways = airport.second.second;
// ICAO
std::uint8_t icaoLength = icao.length();
out.write(reinterpret_cast<const char *>(&icaoLength),
sizeof(icaoLength));
out.write(icao.c_str(), icaoLength);
out.write(reinterpret_cast<const char *>(&null), sizeof(null));
// Gates
std::uint16_t numGates = gates.size();
out.write(reinterpret_cast<const char *>(&numGates),
sizeof(numGates));
for (Gate &gate : gates) {
out.write(reinterpret_cast<const char *>(gate.getBinaryData()),
(std::streamsize)gate.getBinaryLength());
}
// Runways
std::uint8_t numRunways = runways.size();
out.write(reinterpret_cast<const char *>(&numRunways),
sizeof(numRunways));
for (Runway &runway : runways) {
out.write(
reinterpret_cast<const char *>(runway.getBinaryData()),
(std::streamsize)runway.getBinaryLength());
}
}
out.close();
}
static inline std::map<std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
readVersion1(std::ifstream &in)
{
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
std::uint16_t numAirports;
in.read(reinterpret_cast<char *>(&numAirports), sizeof(numAirports));
for (int i = 0; i < numAirports; i++) {
// ICAO
std::uint8_t icaoLength;
in.read(reinterpret_cast<char *>(&icaoLength), sizeof(icaoLength));
char *icao =
static_cast<char *>(calloc(icaoLength + 1, sizeof(char)));
in.read(icao, icaoLength + 1);
// Gates
std::uint16_t numGates;
in.read(reinterpret_cast<char *>(&numGates), sizeof(numGates));
for (int j = 0; j < numGates; j++) {
// ICAO
std::uint8_t designatorLength;
in.read(reinterpret_cast<char *>(&designatorLength),
sizeof(designatorLength));
char *designator = static_cast<char *>(
calloc(designatorLength + 1, sizeof(char)));
in.read(designator, designatorLength + 1);
// Latitude
double latitude;
in.read(reinterpret_cast<char *>(&latitude), sizeof(latitude));
// Latitude
double longitude;
in.read(reinterpret_cast<char *>(&longitude),
sizeof(longitude));
airports[icao].first.emplace_back(designator,
latitude,
longitude);
}
// Runways
std::uint8_t numRunways;
in.read(reinterpret_cast<char *>(&numRunways), sizeof(numRunways));
for (int j = 0; j < numRunways; j++) {
// ICAO
std::uint8_t designatorLength;
in.read(reinterpret_cast<char *>(&designatorLength),
sizeof(designatorLength));
char *designator = static_cast<char *>(
calloc(designatorLength + 1, sizeof(char)));
in.read(designator, designatorLength + 1);
// Latitude
double latitude;
in.read(reinterpret_cast<char *>(&latitude), sizeof(latitude));
// Latitude
double longitude;
in.read(reinterpret_cast<char *>(&longitude),
sizeof(longitude));
// Width
std::uint8_t width;
in.read(reinterpret_cast<char *>(&width), sizeof(width));
// Length
std::uint16_t length;
in.read(reinterpret_cast<char *>(&length), sizeof(length));
// True Heading
std::uint16_t trueHeading;
in.read(reinterpret_cast<char *>(&trueHeading),
sizeof(trueHeading));
airports[icao].second.emplace_back(designator,
latitude,
longitude,
width,
length,
trueHeading);
}
}
in.close();
return airports;
}
static inline std::map<std::string,
std::pair<std::vector<Gate>, std::vector<Runway>>>
fromFile(const std::string &file)
{
std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
airports;
std::ifstream in(file);
// File Header
char ident[5];
in.read(ident, 5);
if (strcmp(ident, "VGAS") != 0) {
throw std::invalid_argument("Wrong file");
}
std::uint8_t version;
in.read(reinterpret_cast<char *>(&version), 1);
if (version == 1) {
return readVersion1(in);
}
return airports;
}
} // namespace simulatorDatabase
#endif
-53
View File
@@ -1,53 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_STRINGEXTENSIONS_H
#define GERMANAIRLINESVA_GACONNECTOR_STRINGEXTENSIONS_H
#include <algorithm>
#include <sstream>
#include <string>
#include <vector>
// trim from start (in place)
static inline void ltrim(std::string &s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s)
{
s.erase(std::find_if(s.rbegin(),
s.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
s.end());
}
static inline std::string rtrim_copy(std::string s)
{
rtrim(s);
return s;
}
// trim from both ends (in place)
static inline void trim(std::string &s)
{
ltrim(s);
rtrim(s);
}
static inline std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> result;
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) {
result.push_back(item);
}
return result;
}
#endif
-304
View File
@@ -1,304 +0,0 @@
#ifndef GERMANAIRLINESVA_GACONNECTOR_UTIL_H
#define GERMANAIRLINESVA_GACONNECTOR_UTIL_H
#ifdef IBM
#define WIN32_LEAN_AND_MEAN
#endif
#define BUFSIZE 1024
#define MD5LEN 16
#define EARTH_M 6371000
#ifdef IBM
// clang-format off
#include <wtypes.h>
#include <wincrypt.h>
// clang-format on
#endif
#ifdef APL
#include <CommonCrypto/CommonDigest.h>
#include <sys/mman.h>
#endif
#ifdef LIN
#include <openssl/md5.h>
#include <sys/mman.h>
#endif
#if defined APL || defined LIN
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <cmath>
#include <functional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
namespace util
{
static inline double to_feet(double value) { return value * 3.280839895; }
static inline double to_degrees(double value) { return value * 180 / M_PI; }
static inline double to_radians(double value) { return value * M_PI / 180; }
static inline double normalize(double value)
{
return fmod(value + 360, 360);
}
static inline double bearing(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double y = sin(to_radians(toLongitude) - to_radians(fromLongitude)) *
cos(to_radians(toLatitude));
double x = cos(to_radians(fromLatitude)) * sin(to_radians(toLatitude)) -
sin(to_radians(fromLatitude)) * cos(to_radians(toLatitude)) *
cos(to_radians(toLongitude) - to_radians(fromLongitude));
return normalize(to_degrees(atan2(y, x)));
}
static inline double distanceEarth(double fromLatitude,
double fromLongitude,
double toLatitude,
double toLongitude)
{
double lat1r, lon1r, lat2r, lon2r, u, v;
lat1r = to_radians(fromLatitude);
lon1r = to_radians(fromLongitude);
lat2r = to_radians(toLatitude);
lon2r = to_radians(toLongitude);
u = sin((lat2r - lat1r) / 2);
v = sin((lon2r - lon1r) / 2);
return 2.0 * EARTH_M *
asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
}
template <typename T>
static inline std::vector<T>
select_T(const std::vector<T> &inVec,
std::function<bool(const T &)> predicate)
{
std::vector<T> result;
copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
return result;
}
#if defined APL || defined LIN
static unsigned long get_size_by_fd(int fd)
{
struct stat buf {
};
if (fstat(fd, &buf) < 0)
return 0;
return buf.st_size;
}
#endif
static void to_hex(const char *hash, char *buffer)
{
for (int i = 0; i < MD5LEN; i++) {
if (buffer != nullptr) {
sprintf(&buffer[2 * i], "%02x", hash[i] & 0xff);
}
}
}
#ifdef IBM
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> toLog)
{
BOOL bResult = FALSE;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
HANDLE hFile;
BYTE rgbFile[BUFSIZE] = {0};
DWORD cbRead = 0;
BYTE rgbHash[MD5LEN] = {0};
DWORD cbHash = 0;
// Logic to check usage goes here.
hFile = CreateFile(filepath,
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
// Get handle to the crypto provider
if (!CryptAcquireContext(&hProv,
nullptr,
nullptr,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
std::stringstream debug_msg;
debug_msg << "CryptAcquireContext returned with error "
<< GetLastError();
toLog(debug_msg.str());
CloseHandle(hFile);
return 1;
}
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
std::stringstream debug_msg;
debug_msg << "CryptCreateHash returned with error "
<< GetLastError();
toLog(debug_msg.str());
CloseHandle(hFile);
CryptReleaseContext(hProv, 0);
return 1;
}
while (
(bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, nullptr))) {
if (0 == cbRead) {
break;
}
if (!CryptHashData(hHash, rgbFile, cbRead, 0)) {
std::stringstream debug_msg;
debug_msg << "CryptHashData returned with error "
<< GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
}
if (!bResult) {
std::stringstream debug_msg;
debug_msg << "ReadFile returned with error " << GetLastError();
toLog(debug_msg.str());
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CloseHandle(hFile);
return 1;
}
cbHash = MD5LEN;
if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) {
to_hex((char *)rgbHash, lastHash);
} else {
std::stringstream debug_msg;
debug_msg << "CryptGetHashParam returned with error "
<< GetLastError();
toLog(debug_msg.str());
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
CloseHandle(hFile);
return 0;
}
#endif
#ifdef APL
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline int
generateMD5(const char *filepath,
char *lastHash,
const std::function<void(const std::string)> &toLog)
{
int file_descript;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
file_descript = open(filepath, O_RDONLY);
if (file_descript < 0)
return 1;
file_size = get_size_by_fd(file_descript);
file_buffer =
(char *)mmap(0, file_size, PROT_READ, MAP_SHARED, file_descript, 0);
CC_MD5_CTX context;
CC_MD5_Init(&context);
CC_MD5_Update(&context, file_buffer, (CC_LONG)file_size);
CC_MD5_Final(result, &context);
munmap(file_buffer, file_size);
close(file_descript);
to_hex((char *)result, lastHash);
return 0;
}
#pragma clang diagnostic pop
#endif
#ifdef LIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline int
generateMD5(const char *filepath,
char *buffer,
const std::function<void(const std::string)> &toLog)
{
int file_descriptor;
unsigned long file_size;
char *file_buffer;
unsigned char result[MD5LEN];
file_descriptor = open(filepath, O_RDONLY);
if (file_descriptor < 0)
return 1;
file_size = get_size_by_fd(file_descriptor);
if (file_size == 0)
return 1;
file_buffer = (char *)
mmap(nullptr, file_size, PROT_READ, MAP_SHARED, file_descriptor, 0);
MD5((unsigned char *)file_buffer, file_size, result);
munmap(file_buffer, file_size);
close(file_descriptor);
to_hex((char *)result, buffer);
return 0;
}
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static inline void setThreadName(const std::string &name)
{
#ifdef APL
//
// Apple reserves 16 bytes for its thread names
// Notice that the Apple version of pthread_setname_np
// does not take a pthread_t argument
//
pthread_setname_np(name.substr(0, 63).c_str());
#endif
#ifdef LIN
//
// Linux only reserves 16 bytes for its thread names
// See prctl and PR_SET_NAME property in
// http://man7.org/linux/man-pages/man2/prctl.2.html
//
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
#endif
}
#pragma clang diagnostic pop
} // namespace util
#endif
+1 -1
View File
@@ -2,7 +2,7 @@
shopt -s globstar
GLOBIGNORE='**/XPLM/**:XPLM/**:**/XPLM:**/ixwebsocket/**:ixwebsocket/**:**/ixwebsocket:**/nlohmann/**:nlohmann/**:**/nlohmann:**/XPSDK/**:XPSDK/**:**/XPSDK:**/build*/**:build*/**:**/build*'
GLOBIGNORE='**/XPLM/**:XPLM/**:**/XPLM:**/ixwebsocket/**:ixwebsocket/**:**/ixwebsocket:**/nlohmann/**:nlohmann/**:**/nlohmann:**/XPSDK/**:XPSDK/**:**/XPSDK:**/build*/**:build*/**:**/build*:**/openSSL/**:openSSL/**:**/openSSL'
clang-format -verbose -i **/*.cpp
clang-format -verbose -i **/*.h
+126
View File
@@ -0,0 +1,126 @@
file(GLOB ixwebsocket CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/ixwebsocket/*.cpp)
add_library(ixwebsocket SHARED
${ixwebsocket}
)
target_include_directories(ixwebsocket PRIVATE
${CMAKE_SOURCE_DIR}/ixwebsocket/include
)
set_target_properties(ixwebsocket PROPERTIES
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/ixwebsocket/include
)
target_compile_options(ixwebsocket PRIVATE
-Wall
-Wextra
-pedantic
)
if(DEBUG)
target_compile_options(ixwebsocket PRIVATE
-g
)
target_link_options(ixwebsocket PRIVATE
-g
)
else()
target_compile_options(ixwebsocket PRIVATE
-O2
)
endif()
if(APPLE)
message("Building ixwebsocket for MacOSX Universal into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}")
set_target_properties(ixwebsocket PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}
BUILD_WITH_INSTALL_NAME_DIR TRUE
)
target_compile_definitions(ixwebsocket PRIVATE
IXWEBSOCKET_USE_TLS
IXWEBSOCKET_USE_SECURE_TRANSPORT
)
target_compile_options(ixwebsocket PRIVATE
"SHELL:-arch x86_64"
)
target_link_options(ixwebsocket PRIVATE
"SHELL:-arch x86_64"
)
target_link_libraries(ixwebsocket PRIVATE
"-framework Foundation"
"-framework Security"
)
elseif(UNIX)
message("Building ixwebsocket for Linux ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}
INSTALL_RPATH "\$ORIGIN"
)
target_compile_definitions(ixwebsocket PRIVATE
IXWEBSOCKET_USE_TLS
IXWEBSOCKET_USE_OPEN_SSL
)
target_compile_options(ixwebsocket PRIVATE
-nodefaultlibs
)
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
message(STATUS "System OpenSSL: " ${OPENSSL_VERSION})
add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(ixwebsocket PRIVATE
${OPENSSL_INCLUDE_DIR}
)
target_link_libraries(ixwebsocket PRIVATE
${OPENSSL_LIBRARIES}
)
elseif(WIN32)
if (BIT STREQUAL "32")
message("Building ixwebsocket for Windows ${BIT} into ${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}")
set_target_properties(ixwebsocket PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/ESP/${PLUGIN_NAME}"
)
else()
message("Building fileixwebsocket for Windows ${BIT} into ${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}")
set_target_properties(ixwebsocket PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/X-Plane/${PLUGIN_NAME}/${BIT}"
)
endif()
target_compile_definitions(ixwebsocket PRIVATE
IXWEBSOCKET_USE_TLS
IXWEBSOCKET_USE_OPEN_SSL
)
if(DEBUG)
target_compile_options(ixwebsocket PRIVATE
-gcodeview
)
target_link_options(ixwebsocket PRIVATE
-Wl,-pdb=
)
endif()
target_include_directories(ixwebsocket PRIVATE
${CMAKE_SOURCE_DIR}/openSSL/win${BIT}/include
)
target_link_libraries(ixwebsocket PRIVATE
wsock32
ws2_32
shlwapi
crypt32
${CMAKE_SOURCE_DIR}/openSSL/win${BIT}/libcrypto-1_1.dll
${CMAKE_SOURCE_DIR}/openSSL/win${BIT}/libssl-1_1.dll
)
endif()
target_link_libraries(ixwebsocket PRIVATE
pthread
)
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)
+39 -34
View File
@@ -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
+16 -20
View File
@@ -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
+56 -37
View File
@@ -8,47 +8,66 @@
namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState() : _terminated(false) { computeId(); }
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
View File
@@ -5,10 +5,10 @@
*/
//
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to
// returns wchar_t which is different from all other platforms. We want the non
// unicode version. See https://github.com/microsoft/vcpkg/pull/11030 We could
// do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t
// which is different from all other platforms. We want the non unicode version.
// See https://github.com/microsoft/vcpkg/pull/11030
// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
//
#ifdef _UNICODE
#undef _UNICODE
@@ -32,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
+29 -15
View File
@@ -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
+57 -55
View File
@@ -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
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+181 -185
View File
@@ -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
View File
@@ -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
+35 -9
View File
@@ -8,20 +8,46 @@
namespace ix
{
const uint64_t SelectInterrupt::kSendRequest = 1;
const uint64_t SelectInterrupt::kCloseRequest = 2;
const uint64_t SelectInterrupt::kSendRequest = 1;
const uint64_t SelectInterrupt::kCloseRequest = 2;
SelectInterrupt::SelectInterrupt() { ; }
SelectInterrupt::SelectInterrupt()
{
;
}
SelectInterrupt::~SelectInterrupt() { ; }
SelectInterrupt::~SelectInterrupt()
{
;
}
bool SelectInterrupt::init(std::string & /*errorMsg*/) { return true; }
bool SelectInterrupt::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterrupt::notify(uint64_t /*value*/) { return true; }
bool SelectInterrupt::notify(uint64_t /*value*/)
{
return true;
}
uint64_t SelectInterrupt::read() { return 0; }
uint64_t SelectInterrupt::read()
{
return 0;
}
bool SelectInterrupt::clear() { return true; }
bool SelectInterrupt::clear()
{
return true;
}
int SelectInterrupt::getFd() const { return -1; }
int SelectInterrupt::getFd() const
{
return -1;
}
void* SelectInterrupt::getEvent() const
{
return nullptr;
}
} // namespace ix
+85
View File
@@ -0,0 +1,85 @@
/*
* IXSelectInterruptEvent.cpp
*/
//
// On Windows we use a Windows Event to wake up ix::poll() (WSAWaitForMultipleEvents).
// And on any other platform that doesn't support pipe file descriptors we
// emulate the interrupt event by using a short timeout with ix::poll() and
// read from the SelectInterrupt. (see Socket::poll() "Emulation mode")
//
#include <algorithm>
#include "IXSelectInterruptEvent.h"
namespace ix
{
SelectInterruptEvent::SelectInterruptEvent()
{
#ifdef _WIN32
_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#endif
}
SelectInterruptEvent::~SelectInterruptEvent()
{
#ifdef _WIN32
CloseHandle(_event);
#endif
}
bool SelectInterruptEvent::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterruptEvent::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_valuesMutex);
// WebSocket implementation detail: We only need one of the values in the queue
if (std::find(_values.begin(), _values.end(), value) == _values.end())
_values.push_back(value);
#ifdef _WIN32
SetEvent(_event); // wake up
#endif
return true;
}
uint64_t SelectInterruptEvent::read()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
if (_values.size() > 0)
{
uint64_t value = _values.front();
_values.pop_front();
#ifdef _WIN32
// signal the event if there is still data in the queue
if (_values.size() == 0)
ResetEvent(_event);
#endif
return value;
}
return 0;
}
bool SelectInterruptEvent::clear()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
_values.clear();
#ifdef _WIN32
ResetEvent(_event);
#endif
return true;
}
void* SelectInterruptEvent::getEvent() const
{
#ifdef _WIN32
return reinterpret_cast<void*>(_event);
#else
return nullptr;
#endif
}
} // namespace ix
+9 -9
View File
@@ -7,20 +7,20 @@
#include "IXSelectInterruptFactory.h"
#include "IXUniquePtr.h"
#if defined(__linux__) || defined(__APPLE__)
#include "IXSelectInterruptPipe.h"
#if _WIN32
#include "IXSelectInterruptEvent.h"
#else
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptPipe.h"
#endif
namespace ix
{
SelectInterruptPtr createSelectInterrupt()
{
#if defined(__linux__) || defined(__APPLE__)
return ix::make_unique<SelectInterruptPipe>();
SelectInterruptPtr createSelectInterrupt()
{
#ifdef _WIN32
return ix::make_unique<SelectInterruptEvent>();
#else
return ix::make_unique<SelectInterrupt>();
return ix::make_unique<SelectInterruptPipe>();
#endif
}
}
} // namespace ix
+108 -103
View File
@@ -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
+42 -40
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+26 -22
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+436 -408
View File
@@ -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
+67 -60
View File
@@ -12,75 +12,82 @@
namespace ix
{
const char *kTLSCAFileUseSystemDefaults = "SYSTEM";
const char *kTLSCAFileDisableVerify = "NONE";
const char *kTLSCiphersUseDefault = "DEFAULT";
const char *kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
const char* kTLSCAFileDisableVerify = "NONE";
const char* kTLSCiphersUseDefault = "DEFAULT";
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
bool SocketTLSOptions::isValid() const
{
if (!_validated) {
if (!certFile.empty() && !std::ifstream(certFile)) {
_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
+18 -20
View File
@@ -11,29 +11,27 @@
namespace ix
{
bool CaseInsensitiveLess::NocaseCompare::operator()(
const unsigned char &c1,
const unsigned char &c2) const
{
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
const unsigned char& c2) const
{
#if defined(_WIN32) && !defined(__GNUC__)
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
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
View File
@@ -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
View File
@@ -13,8 +13,8 @@
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -37,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
View File
@@ -20,12 +20,9 @@
#elif defined(__CYGWIN__) && !defined(_WIN32)
#define PLATFORM_NAME "windows" // Windows (Cygwin POSIX under Microsoft Window)
#elif defined(__ANDROID__)
#define PLATFORM_NAME \
"android" // Android (implies Linux, so it must come first)
#define PLATFORM_NAME "android" // Android (implies Linux, so it must come first)
#elif defined(__linux__)
#define PLATFORM_NAME \
"linux" // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and
// other
#define PLATFORM_NAME "linux" // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and other
#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__)
#include <sys/param.h>
#if defined(BSD)
@@ -59,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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+21 -31
View File
@@ -8,39 +8,29 @@
namespace ix
{
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
const uint16_t WebSocketCloseConstants::kInvalidFramePayloadData(1007);
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
const uint16_t WebSocketCloseConstants::kInvalidFramePayloadData(1007);
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
const std::string
WebSocketCloseConstants::kNormalClosureMessage("Normal closure");
const std::string
WebSocketCloseConstants::kInternalErrorMessage("Internal error");
const std::string
WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure");
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
const std::string
WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
const std::string
WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
const std::string
WebSocketCloseConstants::kProtocolErrorReservedBitUsed("Reserved bit used");
const std::string WebSocketCloseConstants::kProtocolErrorPingPayloadOversized(
"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
+315 -317
View File
@@ -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
+50 -47
View File
@@ -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
+37 -34
View File
@@ -32,14 +32,12 @@
* (same license as MZ: https://opensource.org/licenses/BSD-3-Clause)
*
* - Reused zlib compression + decompression bits.
* - Refactored to have 2 class for compression and decompression, to allow
* multi-threading and make sure that _compressBuffer is not shared between
* threads.
* - Refactored to have 2 class for compression and decompression, to allow multi-threading
* and make sure that _compressBuffer is not shared between threads.
* - Original code wasn't working for some reason, I had to add checks
* for the presence of the kEmptyUncompressedBlock at the end of buffer so
* that servers would start accepting receiving/decoding compressed messages.
* Original code was probably modifying the passed in buffers before processing
* in enabled.hpp ?
* for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers
* would start accepting receiving/decoding compressed messages. Original code was probably
* modifying the passed in buffers before processing in enabled.hpp ?
* - Added more documentation.
*
* Per message Deflate RFC: https://tools.ietf.org/html/rfc7692
@@ -56,38 +54,43 @@
namespace ix
{
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
: _compressor(ix::make_unique<WebSocketPerMessageDeflateCompressor>()),
_decompressor(ix::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
;
}
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
: _compressor(ix::make_unique<WebSocketPerMessageDeflateCompressor>())
, _decompressor(ix::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
;
}
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate() { ; }
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
{
;
}
bool WebSocketPerMessageDeflate::init(
const WebSocketPerMessageDeflateOptions &perMessageDeflateOptions)
{
bool clientNoContextTakeover =
perMessageDeflateOptions.getClientNoContextTakeover();
bool WebSocketPerMessageDeflate::init(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{
bool clientNoContextTakeover = perMessageDeflateOptions.getClientNoContextTakeover();
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
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
+208 -209
View File
@@ -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