├── .gitignore ├── kcp_logo.png ├── kcp_cpp_logo.png ├── .github └── workflows │ ├── kcpcpp_macos.yml │ ├── kcpcpp_win.yml │ └── kcpcpp_ubuntu.yml ├── kcp.svg ├── KCPLogger.h ├── LICENSE ├── kissnet ├── LICENSE └── kissnet.hpp ├── CMakeLists.txt ├── main.cpp ├── README.md ├── KCPNet.h └── KCPNet.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /kcp/ 2 | build/* 3 | .idea 4 | cmake-build* 5 | -------------------------------------------------------------------------------- /kcp_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unit-X/kcp-cpp/HEAD/kcp_logo.png -------------------------------------------------------------------------------- /kcp_cpp_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unit-X/kcp-cpp/HEAD/kcp_cpp_logo.png -------------------------------------------------------------------------------- /.github/workflows/kcpcpp_macos.yml: -------------------------------------------------------------------------------- 1 | name: kcpcpp_macos 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: CMake set-up 17 | run: cmake -DCMAKE_BUILD_TYPE=Release . 18 | - name: make 19 | run: make 20 | -------------------------------------------------------------------------------- /.github/workflows/kcpcpp_win.yml: -------------------------------------------------------------------------------- 1 | name: kcpcpp_win 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: CMake set-up 17 | run: cmake -DCMAKE_BUILD_TYPE=Release . 18 | - name: make 19 | run: cmake --build . --config Release 20 | -------------------------------------------------------------------------------- /.github/workflows/kcpcpp_ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: kcpcpp_ubuntu 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: CMake set-up 17 | run: cmake -DCMAKE_BUILD_TYPE=Release . 18 | - name: build 19 | run: cmake --build . --config Release 20 | -------------------------------------------------------------------------------- /kcp.svg: -------------------------------------------------------------------------------- 1 | KCPKCPPoweredPowered -------------------------------------------------------------------------------- /KCPLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // UnitX Edgeware AB 2020 3 | // 4 | 5 | #ifndef KCP_CPP_LOGGER_H 6 | #define KCP_CPP_LOGGER_H 7 | 8 | #include 9 | #include 10 | 11 | #define LOGG_NOTIFY (unsigned)1 12 | #define LOGG_WARN (unsigned)2 13 | #define LOGG_ERROR (unsigned)4 14 | #define LOGG_FATAL (unsigned)8 15 | #define LOGG_MASK LOGG_NOTIFY | LOGG_WARN | LOGG_ERROR | LOGG_FATAL //What to logg? 16 | 17 | //#define DEBUG 18 | 19 | #ifdef DEBUG 20 | #define KCP_LOGGER(l,g,f) \ 21 | { \ 22 | std::ostringstream a; \ 23 | if (g == (LOGG_NOTIFY & (LOGG_MASK))) {a << "Notification: ";} \ 24 | else if (g == (LOGG_WARN & (LOGG_MASK))) {a << "Warning: ";} \ 25 | else if (g == (LOGG_ERROR & (LOGG_MASK))) {a << "Error: ";} \ 26 | else if (g == (LOGG_FATAL & (LOGG_MASK))) {a << "Fatal: ";} \ 27 | if (a.str().length()) { \ 28 | if (l) {a << __FILE__ << " " << __LINE__ << " ";} \ 29 | a << f << std::endl; \ 30 | std::cout << a.str(); \ 31 | } \ 32 | } 33 | #else 34 | #define KCP_LOGGER(l,g,f) 35 | #endif 36 | 37 | #endif //KCP_CPP_LOGGER_H 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Edgeware AB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kissnet/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 Arthur Brainville (Ybalrid) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(kcp_cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 6 | find_package (Threads REQUIRED) 7 | 8 | #If no build type is set then force Release 9 | IF( NOT CMAKE_BUILD_TYPE ) 10 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING 11 | "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." 12 | FORCE) 13 | ENDIF() 14 | 15 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") 16 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 17 | 18 | #Include KCP 19 | include(ExternalProject) 20 | ExternalProject_Add(project_kcp 21 | GIT_REPOSITORY https://github.com/skywind3000/kcp.git 22 | GIT_SUBMODULES "" 23 | GIT_TAG 58139efbbaa6fc82a451b780b05d37fb41f21d15 24 | UPDATE_COMMAND "" 25 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/kcp 26 | BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/kcp 27 | GIT_PROGRESS 1 28 | BUILD_COMMAND cmake --build ${CMAKE_CURRENT_SOURCE_DIR}/kcp --config ${CMAKE_BUILD_TYPE} --target kcp 29 | CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_POSITION_INDEPENDENT_CODE=ON 30 | STEP_TARGETS build 31 | EXCLUDE_FROM_ALL TRUE 32 | INSTALL_COMMAND "" 33 | ) 34 | add_library(kcp STATIC IMPORTED) 35 | 36 | IF (WIN32) 37 | set_property(TARGET kcp PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/kcp/${CMAKE_BUILD_TYPE}/kcp.lib) 38 | ELSE() 39 | set_property(TARGET kcp PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/kcp/libkcp.a) 40 | ENDIF() 41 | 42 | add_dependencies(kcp project_kcp) 43 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/kcp/) 44 | 45 | add_library(kcpnet STATIC KCPNet.cpp) 46 | target_link_libraries(kcpnet kcp Threads::Threads) 47 | 48 | add_executable(kcp_cpp main.cpp) 49 | target_link_libraries(kcp_cpp kcpnet Threads::Threads) 50 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "KCPNet.h" 4 | #include 5 | #include 6 | 7 | std::shared_ptr gRetainThis; 8 | 9 | int gPacketNum = 0; 10 | 11 | // Validate this connection 12 | // Return a nullptr if you want to reject the new connection 13 | // If you want to retain this connection context this is the place to do that. All other calls use the raw pointer to the object. 14 | // You can also skip retaining this and just pass a smart_pointer to std::any within the KCPContext 15 | std::shared_ptr validateConnection(std::string lIP, uint16_t lPort, std::shared_ptr &rpCtx) { 16 | std::cout << "Connecting IP:port > " << lIP << ":" << unsigned(lPort) << std::endl; 17 | rpCtx->mValue = 50; 18 | gRetainThis = rpCtx; 19 | 20 | // You can optionally configure the KCP connection here also. 21 | //rpCtx->mSettings.mMtu = 1000; 22 | 23 | // And set the wanted ID. 24 | //rpCtx->mID = 10; (10 is default) 25 | 26 | return rpCtx; 27 | } 28 | 29 | void gotDataServer(const char* pData, size_t lSize, KCPContext* pCTX) { 30 | std::cout << "The server got -> " << unsigned(lSize) << " bytes of data. pk num -> " << gPacketNum++ << std::endl; 31 | } 32 | 33 | void gotDataClient(const char* pData, size_t lSize, KCPContext* pCTX) { 34 | std::cout << "The client got -> " << unsigned(lSize) << " bytes of data" << std::endl; 35 | } 36 | 37 | void noConnectionServer(KCPContext* pCTX) { 38 | std::cout << "The server timed out a client." << std::endl; 39 | } 40 | 41 | void noConnectionClient(KCPContext* pCTX) { 42 | std::cout << "The server is not active." << std::endl; 43 | } 44 | 45 | class TestClass { 46 | int mTestValue = 100; 47 | }; 48 | 49 | int main() { 50 | std::cout << "KCP-cpp test" << std::endl; 51 | 52 | std::vector lData(4000); 53 | std::generate(lData.begin(), lData.end(), [n = 0]() mutable { return n++; }); 54 | 55 | // Create the server and register the receive data callback and the validate connection callback 56 | std::shared_ptr lx = std::make_shared(""); 57 | lx->mObject = std::make_shared(); 58 | KCPNetServer lKcpServer; 59 | if (lKcpServer.configureKCP(gotDataServer, 60 | noConnectionServer, 61 | validateConnection, 62 | "::1", 63 | 8000, 64 | lx)) { 65 | std::cout << "Failed to configure the KCP Server" << std::endl; 66 | } 67 | 68 | KCPNetClient lKcpClient; 69 | 70 | KCPSettings lSettingsClient; 71 | if(lKcpClient.configureKCP(lSettingsClient, 72 | gotDataClient, 73 | noConnectionClient, 74 | "::1", 75 | 8000, 76 | 10, 77 | nullptr)) { 78 | std::cout << "Failed to configure the KCP Client" << std::endl; 79 | } 80 | 81 | /* 82 | for ( uint64_t i=0 ; i<10000 ; i++ ) { 83 | *(uint64_t*)lData.data() = i; 84 | lKcpClient.sendData((const char*)lData.data(), 1000); 85 | std::cout << "Pushed -> " << unsigned(i) << std::endl; 86 | std::this_thread::sleep_for(std::chrono::milliseconds (1)); 87 | } 88 | std::cout << "STOP SENDING" << std::endl; 89 | std::this_thread::sleep_for(std::chrono::seconds (2)); 90 | */ 91 | 92 | lKcpClient.sendData((const char*)lData.data(), 4000); 93 | 94 | std::this_thread::sleep_for(std::chrono::seconds (1)); 95 | lKcpServer.sendData((const char*)lData.data(), 4000, gRetainThis.get()); 96 | std::this_thread::sleep_for(std::chrono::seconds (1)); 97 | 98 | lKcpServer.mDropAll = true; 99 | 100 | std::this_thread::sleep_for(std::chrono::seconds (1)); 101 | lKcpClient.sendData((const char*)lData.data(), 4000); 102 | lKcpServer.sendData((const char*)lData.data(), 4000, gRetainThis.get()); 103 | std::this_thread::sleep_for(std::chrono::seconds (1)); 104 | lKcpClient.sendData((const char*)lData.data(), 4000); 105 | lKcpServer.sendData((const char*)lData.data(), 4000, gRetainThis.get()); 106 | std::this_thread::sleep_for(std::chrono::seconds (20)); 107 | lKcpClient.sendData((const char*)lData.data(), 4000); 108 | lKcpServer.sendData((const char*)lData.data(), 4000, gRetainThis.get()); 109 | 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KCP - CPP 2 | 3 | ![alt text](kcp_cpp_logo.png) 4 | 5 | **Simple C++ wrapper of the [KCP](https://github.com/skywind3000/kcp) protocol.** 6 | 7 | [![alt text](kcp.svg)](https://github.com/skywind3000/kcp) 8 | 9 | ## Build 10 | 11 | Requires cmake version >= **3.10** and **C++17** 12 | 13 | **Release:** 14 | 15 | ```sh 16 | mkdir build 17 | cd build 18 | cmake -DCMAKE_BUILD_TYPE=Release .. 19 | cmake --build . --config Release 20 | ``` 21 | 22 | ***Debug:*** 23 | 24 | ```sh 25 | mkdir build 26 | cd build 27 | cmake -DCMAKE_BUILD_TYPE=Debug .. 28 | cmake --build . --config Debug 29 | ``` 30 | 31 | 32 | *Output:* 33 | 34 | **(platform specific)kcpnet.(platform specific)** 35 | 36 | (Linux/MacOS -> libkcpnet.a) 37 | 38 | (Windows -> kcpnet.lib) 39 | 40 | 41 | ## Current build status 42 | 43 | [![kcpcpp_ubuntu](https://github.com/Unit-X/kcp-cpp/workflows/kcpcpp_ubuntu/badge.svg)](https://github.com/Unit-X/kcp-cpp/actions?query=workflow%3Akcpcpp_ubuntu) 44 | 45 | [![kcpcpp_macos](https://github.com/Unit-X/kcp-cpp/workflows/kcpcpp_macos/badge.svg)](https://github.com/Unit-X/kcp-cpp/actions?query=workflow%3Akcpcpp_macos) 46 | 47 | [![kcpcpp_win](https://github.com/Unit-X/kcp-cpp/workflows/kcpcpp_win/badge.svg)](https://github.com/Unit-X/kcp-cpp/actions?query=workflow%3Akcpcpp_win) 48 | 49 | 50 | ## Usage 51 | 52 | ```cpp 53 | 54 | //---------- 55 | //Server --- 56 | //---------- 57 | 58 | //Create the server 59 | //1. Data from server callback 60 | //2. Client disconnect callback 61 | //3. Validate new client callback 62 | //4. Listening interface 63 | //5. Listening port 64 | //6. Optional context 65 | KCPNetServer lKcpServer; 66 | lKcpServer.configureKCP(gotDataServer, 67 | noConnectionServer, 68 | validateConnection, 69 | "127.0.0.1", 70 | 8000, 71 | nullptr); 72 | 73 | 74 | //Send data to the client 75 | //1. Pointer to the data 76 | //2. The size of the data 77 | //3. The pointer to the KCPContext you got when accepting the client 78 | lKcpServer.sendData((const char*)lData.data(), 4000, gRetainThis.get()); 79 | 80 | 81 | 82 | //---------- 83 | //Client --- 84 | //---------- 85 | 86 | //Create the client 87 | //1. The settings struct 88 | //2. Got data from server 89 | //3. Lost connection to server 90 | //4. Connect to interface 91 | //5. Connect to port 92 | //6. Connection ID (Must be set identical on the server see -> validateConnection) 93 | //7. Optional context 94 | KCPNetClient lKcpClient; 95 | 96 | KCPSettings lSettingsClient; 97 | configureKCP(lSettingsClient, 98 | gotDataClient, 99 | noConnectionClient, 100 | "127.0.0.1", 101 | 8000, 102 | 10, 103 | nullptr); 104 | 105 | //Send data to the server 106 | //1. Pointer to the data 107 | //2. The size of the data 108 | lKcpClient.sendData((const char*)lData.data(), 4000); 109 | 110 | 111 | //Please see KCP documentation for details. 112 | class KCPSettings { 113 | public: 114 | bool mNodelay = false; //No delay mode. False: Off / True: On. 115 | int mInterval = 100; //KCP update interval in ms 116 | int mResend = 0; //Retransmit when missed mResend number ACK (Default value is 0) 117 | bool mFlow = false; //Flow control, False: Off / True: On. 118 | int mMtu = 1472; //Maximum payload in a single UDP datagram 119 | int mSndWnd = 32; //Send window size 120 | int mRcvWnd = 32; //Receive window size //The doc says 32 the code says 128 121 | }; 122 | 123 | 124 | ``` 125 | 126 | ## Using KCP - CPP in your CMake project 127 | 128 | * **Step1** 129 | 130 | Add this in your CMake file. 131 | 132 | ``` 133 | #Include kcpnet 134 | include(ExternalProject) 135 | ExternalProject_Add(project_kcpnet 136 | GIT_REPOSITORY https://github.com/Unit-X/kcp-cpp 137 | GIT_SUBMODULES "" 138 | UPDATE_COMMAND "" 139 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet 140 | BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet 141 | GIT_PROGRESS 1 142 | BUILD_COMMAND cmake --build ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet --config ${CMAKE_BUILD_TYPE} --target kcpnet 143 | STEP_TARGETS build 144 | EXCLUDE_FROM_ALL TRUE 145 | INSTALL_COMMAND "" 146 | ) 147 | add_library(kcpnet STATIC IMPORTED) 148 | add_library(kcp STATIC IMPORTED) 149 | IF (WIN32) 150 | set_property(TARGET kcpnet PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet/${CMAKE_BUILD_TYPE}/kcpnet.lib) 151 | set_property(TARGET kcpnet PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet/kcp/${CMAKE_BUILD_TYPE}/kcp.lib) 152 | ELSE() 153 | set_property(TARGET kcpnet PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet/libkcpnet.a) 154 | set_property(TARGET kcp PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/kcpnet/kcp/libkcp.a) 155 | ENDIF() 156 | 157 | add_dependencies(kcpnet project_kcpnet) 158 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/kcpnet/) 159 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/kcpnet/kcp/) 160 | ``` 161 | 162 | * **Step2** 163 | 164 | Link your library or executable. 165 | 166 | ``` 167 | target_link_libraries((your target) kcpnet kcp (the rest you want to link)) 168 | ``` 169 | 170 | * **Step3** 171 | 172 | Add header file to your project. 173 | 174 | ``` 175 | #include "KCPNet.h" 176 | ``` 177 | 178 | 179 | ## Credits 180 | 181 | Anders Cedronius for creating the C++ wrapper 182 | 183 | anders.cedronius(at)edgeware.tv 184 | 185 | 186 | ## License 187 | 188 | *MIT* 189 | 190 | Read *LICENCE* for details 191 | -------------------------------------------------------------------------------- /KCPNet.h: -------------------------------------------------------------------------------- 1 | // 2 | // _ _______ ______ _ _ _ 3 | // | | / / __ \| ___ \ \ | | | | 4 | // | |/ /| / \/| |_/ / \| | ___| |_ 5 | // | \| | | __/| . ` |/ _ \ __| 6 | // | |\ \ \__/\| | | |\ | __/ |_ 7 | // \_| \_/\____/\_| \_| \_/\___|\__| 8 | // 9 | // 10 | // Created by Unit-X @ Edgeware AB on 2020-09-30. 11 | // 12 | 13 | // C++ wrapper around KCP 14 | 15 | // All payload data is little endian. 16 | 17 | #ifndef KCP_CPP_KCPNET_H 18 | #define KCP_CPP_KCPNET_H 19 | 20 | #include "ikcp.h" 21 | #include "kissnet/kissnet.hpp" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | // Time preamble 31 | #define TIME_PREAMBLE_V1 0x000100010ff00ff0 32 | 33 | // Time constants server 34 | #define MAX_DELAY_DIFF_MS 20 35 | #define MIN_COLLECTED_TIME_POINTS 5 36 | #define MAX_SAVED_TIME_POINTS 100 37 | #define TIME_PACKETS_BURST_DISTANCE_MS 100 38 | #define TIME_PACKETS_NORMAL_DISTANCE_MS 1000 39 | 40 | // Time constants client 41 | // The maximum adjustment + and - in microseconds per second allowed 42 | #define MAX_TIME_DRIFT_PPM 500 43 | 44 | // Count the HEART_BEAT every x ms. 45 | #define HEART_BEAT_DISTANCE 500 46 | // Time out after HEART_BEAT_DISTANCE ms * HEART_BEAT_TIME_OUT milliseconds 47 | #define HEART_BEAT_TIME_OUT 10 48 | 49 | struct KCPTimePacket{ 50 | uint64_t timePreamble = TIME_PREAMBLE_V1; //Version 1 preamble 51 | int64_t t1 = 0; 52 | int64_t t2 = 0; 53 | int64_t t3 = 0; 54 | int64_t t4 = 0; 55 | int64_t correction = 0; 56 | int64_t correctionActive = 0; 57 | }; 58 | static_assert(sizeof(KCPTimePacket) == 56, "KCPTimePacket is not the expected size"); 59 | 60 | class KCPSettings { 61 | public: 62 | bool mNoDelay = false; // No delay mode. False: Off / True: On. 63 | int mInterval = 10; // KCP update interval in ms 64 | int mResend = 0; // Retransmit when missed mResend number ACK (Default value is 0) 65 | bool mFlow = true; // Flow control, False: Off / True: On. 66 | int mMtu = 1472; // Maximum payload in a single UDP datagram 67 | int mSndWnd = 32; // Send window size 68 | int mRcvWnd = 32; // Receive window size //The doc says 32 the code says 128 69 | }; 70 | 71 | // Optional context passed to the callbacks 72 | class KCPContext { 73 | public: 74 | explicit KCPContext (std::string lKey): mKCPSocket(lKey) { 75 | 76 | } 77 | std::any mObject = nullptr; // For safe object lifecycles 78 | void* mUnsafePointer = nullptr; // Lightweight alternative for unsafe pointers 79 | uint64_t mValue = 0; // Generic 64-bit variable 80 | uint64_t mID = 10; //KCP ID to be used 81 | KCPSettings mSettings; 82 | std::string mKCPSocket; 83 | }; 84 | 85 | //------------------------------------------------------------------------------------------ 86 | // 87 | // KCP Client 88 | // 89 | //------------------------------------------------------------------------------------------ 90 | 91 | class KCPNetClient { 92 | public: 93 | explicit KCPNetClient(); 94 | virtual ~KCPNetClient(); 95 | 96 | int sendData(const char* pData, size_t lSize); 97 | 98 | int configureKCP(KCPSettings &rSettings, 99 | const std::function &rGotData, 100 | const std::function &rDisconnect, 101 | const std::string& lIP = "", 102 | uint16_t lPort = 0, 103 | uint32_t lID = 0, 104 | std::shared_ptr pCTX = nullptr); 105 | 106 | int64_t getNetworkTimeus(); //Network time in us 107 | 108 | void udpOutputClient(const char *pBuf, int lSize); //Method used by the bridge function 109 | 110 | // delete copy and move constructors and assign operators 111 | KCPNetClient(KCPNetClient const &) = delete; // Copy construct 112 | KCPNetClient(KCPNetClient &&) = delete; // Move construct 113 | KCPNetClient &operator=(KCPNetClient const &) = delete; // Copy assign 114 | KCPNetClient &operator=(KCPNetClient &&) = delete; // Move assign 115 | 116 | protected: 117 | std::shared_ptr mCTX = nullptr; 118 | 119 | private: 120 | void netWorkerClient(const std::function &rDisconnect); 121 | void kcpNudgeWorkerClient(const std::function &rGotData); 122 | 123 | #ifdef _WIN32 124 | bool mHasSentData = false; 125 | #endif 126 | std::mutex mKCPNetMtx; 127 | ikcpcb *mKCP = nullptr; // The KCP handle for client mode 128 | kissnet::udp_socket mKissnetSocket; 129 | bool mNetworkThreadRunning = false; 130 | bool mNudgeThreadRunning = false; 131 | bool mNudgeThreadActive = false; 132 | uint64_t mConnectionTimeOut = HEART_BEAT_TIME_OUT; 133 | uint64_t mHeartBeatIntervalTrigger = 0; 134 | bool mFirstTimeDelivery = true; 135 | int64_t mLastDeliveredTime; 136 | std::atomic mCurrentCorrectionTarget = 0; 137 | std::atomic mCurrentCorrection = 0; 138 | std::atomic mGotCorrection = false; 139 | }; 140 | 141 | //------------------------------------------------------------------------------------------ 142 | // 143 | // KCP Server 144 | // 145 | //------------------------------------------------------------------------------------------ 146 | 147 | class KCPNetServer { 148 | public: 149 | class KCPServerData { 150 | public: 151 | virtual ~KCPServerData() { 152 | if (mKCPServer) ikcp_release(mKCPServer); 153 | } 154 | KCPNetServer* mWeakKCPNetServer = nullptr; 155 | ikcpcb* mKCPServer = nullptr; 156 | std::shared_ptr mKCPContext = nullptr; 157 | kissnet::addr_collection mDestination; 158 | 159 | uint64_t mConnectionTimeOut = HEART_BEAT_TIME_OUT; 160 | bool mGotStableTime = false; 161 | bool mClientGotCorrection = false; 162 | std::vector> mListOfDelayAndCompensation; 163 | int64_t mCurrentCorrection = 0; 164 | }; 165 | 166 | explicit KCPNetServer(); 167 | 168 | virtual ~KCPNetServer(); 169 | 170 | int sendData(const char* pData, size_t lSize, KCPContext* pCTX); 171 | 172 | int configureKCP(const std::function &rGotData, 173 | const std::function &rDisconnect, 174 | const std::function(std::string, uint16_t, std::shared_ptr&)> &rValidate, 175 | const std::string& lIP = "", 176 | uint16_t lport = 0, 177 | std::shared_ptr pCTX = nullptr); 178 | 179 | // Method used by the bridge function 180 | void udpOutputServer(const char *pBuf, int lSize, KCPServerData* lCTX); 181 | 182 | // delete copy and move constructors and assign operators 183 | KCPNetServer(KCPNetServer const &) = delete; // Copy construct 184 | KCPNetServer(KCPNetServer &&) = delete; // Move construct 185 | KCPNetServer &operator=(KCPNetServer const &) = delete; // Copy assign 186 | KCPNetServer &operator=(KCPNetServer &&) = delete; // Move assign 187 | 188 | bool mDropAll = false; 189 | 190 | protected: 191 | std::shared_ptr mCTX = nullptr; 192 | 193 | private: 194 | int configureInternal(KCPSettings &rSettings, KCPContext *pCtx); 195 | void netWorkerServer(const std::function &rGotData, 196 | const std::function(std::string, uint16_t, std::shared_ptr&)> &rValidate); 197 | void kcpNudgeWorkerServer(const std::function &rDisconnect); 198 | void sendTimePacket(KCPServerData &rServerData); 199 | 200 | std::mutex mKCPMapMtx; 201 | std::unordered_map> mKCPMap; 202 | kissnet::udp_socket mKissnetSocket; 203 | bool mNetworkThreadRunning = false; 204 | bool mNudgeThreadRunning = false; 205 | bool mNudgeThreadActive = false; 206 | uint64_t mHeartBeatIntervalTrigger = 0; 207 | uint64_t mSendTimeIntervalTriggerLow = 0; 208 | uint64_t mSendTimeIntervalTriggerHi = 0; 209 | }; 210 | 211 | #endif //KCP_CPP_KCPNET_H 212 | -------------------------------------------------------------------------------- /KCPNet.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-09-30. 3 | // 4 | 5 | //TODO documentation, unit tests. 6 | 7 | #include "KCPNet.h" 8 | #include "KCPLogger.h" 9 | #include 10 | #include 11 | #include 12 | 13 | #define KCP_MAX_BYTES 4096 14 | 15 | //------------------------------------------------------------------------------------------ 16 | // 17 | // KCP Client 18 | // 19 | //------------------------------------------------------------------------------------------ 20 | 21 | int udp_output_client(const char *pBuf, int lSize, ikcpcb *pKCP, void *pCTX) { 22 | auto *lWeakSelf = (KCPNetClient *) pCTX; 23 | if (lWeakSelf) { 24 | lWeakSelf->udpOutputClient(pBuf, lSize); 25 | } else { 26 | KCP_LOGGER(true, LOGG_FATAL, "udp_output_client failed getting 'this'") 27 | return -1; // Throw 28 | } 29 | return 0; 30 | } 31 | 32 | void KCPNetClient::udpOutputClient(const char *pBuf, int lSize) { 33 | auto[lSentBytes, lStatus] = mKissnetSocket.send((const std::byte *) pBuf, lSize); 34 | if (lSentBytes != lSize || lStatus != kissnet::socket_status::valid) { 35 | KCP_LOGGER(false, LOGG_NOTIFY, "Client failed sending data") 36 | return; 37 | } 38 | #ifdef _WIN32 39 | mHasSentData = true; 40 | #endif 41 | 42 | } 43 | 44 | KCPNetClient::KCPNetClient() { 45 | 46 | } 47 | 48 | KCPNetClient::~KCPNetClient() { 49 | uint32_t lDeadLock; 50 | // Signal close netWorker and nudge thread 51 | mKissnetSocket.shutdown(); // End net thread 52 | mKissnetSocket.close(); // End net thread 53 | mNudgeThreadActive = false; // End nudge thread 54 | 55 | // Join network thread 56 | if (mNetworkThreadRunning) { 57 | lDeadLock = 10; 58 | while (mNetworkThreadRunning) { 59 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 60 | if (!--lDeadLock) { 61 | KCP_LOGGER(true, LOGG_FATAL, "Client network is not ending will terminate anyway") 62 | break; 63 | } 64 | } 65 | } 66 | 67 | // Join nudge thread 68 | lDeadLock = 10; 69 | while (mNudgeThreadRunning) { 70 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 71 | if (!--lDeadLock) { 72 | KCP_LOGGER(true, LOGG_FATAL, "Client nudge thread not ending will terminate anyway") 73 | break; 74 | } 75 | } 76 | std::lock_guard lock(mKCPNetMtx); 77 | if (mKCP) ikcp_release(mKCP); 78 | KCP_LOGGER(false, LOGG_NOTIFY, "KCPNetClient Destruct") 79 | } 80 | 81 | // Fix in KCP later 82 | int KCPNetClient::sendData(const char *pData, size_t lSize) { 83 | std::lock_guard lock(mKCPNetMtx); 84 | return ikcp_send(mKCP, pData, lSize); 85 | } 86 | 87 | int KCPNetClient::configureKCP(KCPSettings &rSettings, 88 | const std::function &rGotData, 89 | const std::function &rDisconnect, 90 | const std::string &lIP, uint16_t lPort, 91 | uint32_t lID, std::shared_ptr pCTX) { 92 | mCTX = std::move(pCTX); 93 | 94 | if (lIP.empty()) { 95 | KCP_LOGGER(true, LOGG_FATAL, "IP / HOST must be provided") 96 | return 1; 97 | } 98 | 99 | if (!lPort) { 100 | KCP_LOGGER(true, LOGG_FATAL, "Port must be provided") 101 | return 1; 102 | } 103 | 104 | if (!lID) { 105 | KCP_LOGGER(true, LOGG_FATAL, "KCP ID can't be 0") 106 | return 1; 107 | } 108 | 109 | kissnet::udp_socket lCreateSocket(kissnet::endpoint(lIP, lPort)); 110 | mKissnetSocket = std::move(lCreateSocket); // Move ownership to this/me 111 | 112 | mKCP = ikcp_create(lID, this); 113 | if (!mKCP) { 114 | KCP_LOGGER(true, LOGG_FATAL, "Failed to create KCP") 115 | return 1; 116 | } 117 | mKCP->output = udp_output_client; 118 | std::thread([=]() { netWorkerClient(rGotData); }).detach(); 119 | std::thread([=]() { kcpNudgeWorkerClient(rDisconnect); }).detach(); 120 | KCP_LOGGER(false, LOGG_NOTIFY, "KCPNetClient Constructed"); 121 | 122 | 123 | std::lock_guard lock(mKCPNetMtx); 124 | int lResult; 125 | lResult = ikcp_nodelay(mKCP, rSettings.mNoDelay, rSettings.mInterval, rSettings.mResend, !rSettings.mFlow); 126 | if (lResult) { 127 | KCP_LOGGER(false, LOGG_ERROR, "ikcp_nodelay client failed.") 128 | return lResult; 129 | } 130 | lResult = ikcp_setmtu(mKCP, rSettings.mMtu); 131 | if (lResult) { 132 | KCP_LOGGER(false, LOGG_ERROR, "ikcp_setmtu client failed.") 133 | return lResult; 134 | } 135 | lResult = ikcp_wndsize(mKCP, rSettings.mSndWnd, rSettings.mRcvWnd); 136 | if (lResult) { 137 | KCP_LOGGER(false, LOGG_ERROR, "ikcp_wndsize client failed.") 138 | } 139 | return lResult; 140 | } 141 | 142 | // TODO Create a drift into corrected time 143 | int64_t KCPNetClient::getNetworkTimeus(){ 144 | if (!mGotCorrection) return 0; 145 | int64_t lLocalTime = std::chrono::duration_cast( 146 | std::chrono::steady_clock::now().time_since_epoch()).count(); 147 | int64_t lRecalculatedTime = lLocalTime - mCurrentCorrection; 148 | 149 | if (!mFirstTimeDelivery) { 150 | if (mLastDeliveredTime < lRecalculatedTime) { 151 | mLastDeliveredTime = lRecalculatedTime; 152 | return lRecalculatedTime; 153 | } else { 154 | return mLastDeliveredTime; 155 | } 156 | } 157 | 158 | mLastDeliveredTime = lRecalculatedTime; 159 | mFirstTimeDelivery = false; 160 | return lRecalculatedTime; 161 | } 162 | 163 | void KCPNetClient::kcpNudgeWorkerClient(const std::function &rDisconnect) { 164 | mNudgeThreadRunning = true; 165 | mNudgeThreadActive = true; 166 | uint32_t lTimeSleepms = 10; 167 | uint64_t lTimeBaseus = std::chrono::duration_cast( 168 | std::chrono::steady_clock::now().time_since_epoch()).count(); 169 | 170 | uint64_t lTimeLast = 0; 171 | 172 | while (mNudgeThreadActive) { 173 | std::this_thread::sleep_for(std::chrono::milliseconds(lTimeSleepms)); 174 | uint64_t lTimeNowus = std::chrono::duration_cast( 175 | std::chrono::steady_clock::now().time_since_epoch()).count() - lTimeBaseus; 176 | 177 | if (mGotCorrection) { 178 | if (!lTimeLast) { 179 | lTimeLast = lTimeNowus; 180 | } else { 181 | uint64_t lDiffTime = lTimeNowus - lTimeLast; 182 | lTimeLast = lTimeNowus; 183 | if (mCurrentCorrectionTarget != mCurrentCorrection) { 184 | double lMulFac = lDiffTime / 1000000.0; 185 | double lMaxComp = lMulFac * MAX_TIME_DRIFT_PPM; 186 | if (mCurrentCorrectionTarget > mCurrentCorrection) { 187 | //KCP_LOGGER(false, LOGG_NOTIFY, "+curr " << mCurrentCorrection << " target " << mCurrentCorrectionTarget << " compensation " << lMaxComp) 188 | mCurrentCorrection += lMaxComp; 189 | if (mCurrentCorrection > mCurrentCorrectionTarget) { 190 | int64_t lTmp = mCurrentCorrectionTarget; 191 | mCurrentCorrection = lTmp; 192 | } 193 | } else { 194 | //KCP_LOGGER(false, LOGG_NOTIFY, "-curr " << mCurrentCorrection << " target " << mCurrentCorrectionTarget << " compensation " << lMaxComp) 195 | mCurrentCorrection -= lMaxComp; 196 | if (mCurrentCorrection < mCurrentCorrectionTarget) { 197 | int64_t lTmp = mCurrentCorrectionTarget; 198 | mCurrentCorrection = lTmp; 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | uint64_t lTimeNowms = lTimeNowus / 1000; 206 | if (lTimeNowms > mHeartBeatIntervalTrigger) { 207 | mHeartBeatIntervalTrigger += HEART_BEAT_DISTANCE; 208 | KCP_LOGGER(false, LOGG_NOTIFY, "Heart beat client") 209 | if (!mConnectionTimeOut && rDisconnect) { 210 | rDisconnect(mCTX.get()); 211 | mConnectionTimeOut = HEART_BEAT_TIME_OUT; 212 | } 213 | mConnectionTimeOut--; 214 | } 215 | mKCPNetMtx.lock(); 216 | ikcp_update(mKCP, lTimeNowms); 217 | lTimeSleepms = ikcp_check(mKCP, lTimeNowms) - lTimeNowms; 218 | mKCPNetMtx.unlock(); 219 | //KCP_LOGGER(false, LOGG_NOTIFY,"dead client? " << mKCP->dead_link) 220 | //KCP_LOGGER(false, LOGG_NOTIFY,"k " << lTimeSleep << " " << lTimeNow) 221 | } 222 | KCP_LOGGER(false, LOGG_NOTIFY, "kcpNudgeWorkerClient quitting"); 223 | if (rDisconnect) { 224 | rDisconnect(mCTX.get()); 225 | } 226 | mNudgeThreadRunning = false; 227 | } 228 | 229 | void KCPNetClient::netWorkerClient(const std::function &rGotData) { 230 | mNetworkThreadRunning = true; 231 | kissnet::buffer receiveBuffer; 232 | char lBuffer[KCP_MAX_BYTES]; 233 | #ifdef _WIN32 234 | do { 235 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 236 | } while (!mHasSentData && mNetworkThreadRunning); 237 | #endif 238 | while (true) { 239 | auto[received_bytes, status] = mKissnetSocket.recv(receiveBuffer); 240 | if (!received_bytes || status != kissnet::socket_status::valid) { 241 | KCP_LOGGER(false, LOGG_NOTIFY, "netWorkerClient quitting") 242 | break; 243 | } 244 | 245 | if (*(uint64_t *) receiveBuffer.data() == TIME_PREAMBLE_V1) { 246 | auto lTimeData = (KCPTimePacket *) receiveBuffer.data(); 247 | if (lTimeData->correctionActive == 1) { 248 | lTimeData->correctionActive = 2; 249 | if (!mGotCorrection) { 250 | mCurrentCorrection = lTimeData->correction; 251 | } 252 | mCurrentCorrectionTarget = lTimeData->correction; 253 | mGotCorrection = true; 254 | } 255 | mKCPNetMtx.lock(); 256 | int64_t lTimeNow = std::chrono::duration_cast( 257 | std::chrono::steady_clock::now().time_since_epoch()).count(); 258 | lTimeData->t2 = lTimeNow; 259 | lTimeData->t3 = lTimeNow; 260 | auto[lSentBytes, lStatus] = mKissnetSocket.send(receiveBuffer, sizeof(KCPTimePacket)); 261 | if (lSentBytes != sizeof(KCPTimePacket) || lStatus != kissnet::socket_status::valid) { 262 | KCP_LOGGER(false, LOGG_NOTIFY, "Client failed sending data") 263 | } 264 | mConnectionTimeOut = HEART_BEAT_TIME_OUT; 265 | mKCPNetMtx.unlock(); 266 | continue; 267 | } 268 | 269 | mKCPNetMtx.lock(); 270 | mConnectionTimeOut = HEART_BEAT_TIME_OUT; 271 | ikcp_input(mKCP, (const char *) receiveBuffer.data(), received_bytes); 272 | int lRcv = ikcp_recv(mKCP, &lBuffer[0], KCP_MAX_BYTES); 273 | mKCPNetMtx.unlock(); 274 | if (lRcv > 0 && rGotData) { 275 | rGotData(&lBuffer[0], lRcv, mCTX.get()); 276 | } // Else deal with code? 277 | 278 | } 279 | mNetworkThreadRunning = false; 280 | } 281 | 282 | 283 | //------------------------------------------------------------------------------------------ 284 | // 285 | // KCP Server 286 | // 287 | //------------------------------------------------------------------------------------------ 288 | 289 | int udp_output_server(const char *pBuf, int lSize, ikcpcb *pKCP, void *pCTX) { 290 | auto *lWeakSelf = (KCPNetServer::KCPServerData *) pCTX; 291 | if (lWeakSelf) { 292 | if (lWeakSelf->mWeakKCPNetServer) { 293 | lWeakSelf->mWeakKCPNetServer->udpOutputServer(pBuf, lSize, lWeakSelf); 294 | } else { 295 | KCP_LOGGER(true, LOGG_FATAL, "udp_output_server failed getting 'this'") 296 | } 297 | } else { 298 | KCP_LOGGER(true, LOGG_FATAL, "udp_output_server failed getting KCPServerData") 299 | return -1; // Throw 300 | } 301 | return 0; 302 | } 303 | 304 | void KCPNetServer::udpOutputServer(const char *pBuf, int lSize, KCPServerData *lCTX) { 305 | if (mDropAll) return; 306 | auto[lSentBytes, lStatus] = mKissnetSocket.send((const std::byte *) pBuf, lSize, &lCTX->mDestination); 307 | if (lSentBytes != lSize || lStatus != kissnet::socket_status::valid) { 308 | KCP_LOGGER(false, LOGG_NOTIFY, "Server failed sending data") 309 | } 310 | } 311 | 312 | KCPNetServer::KCPNetServer() { 313 | 314 | } 315 | 316 | KCPNetServer::~KCPNetServer() { 317 | uint32_t lDeadLock; 318 | // Signal close netWorker and nudge thread 319 | mKissnetSocket.shutdown(); // End net thread 320 | mKissnetSocket.close(); // End net thread 321 | mNudgeThreadActive = false; // End nudge thread 322 | 323 | // Join network thread 324 | if (mNetworkThreadRunning) { 325 | lDeadLock = 10; 326 | while (mNetworkThreadRunning) { 327 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 328 | if (!--lDeadLock) { 329 | KCP_LOGGER(true, LOGG_FATAL, "Server net thread is not ending will terminate anyway") 330 | break; 331 | } 332 | } 333 | } 334 | 335 | //Join nudge thread 336 | lDeadLock = 10; 337 | while (mNudgeThreadRunning) { 338 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 339 | if (!--lDeadLock) { 340 | KCP_LOGGER(true, LOGG_FATAL, "Nudge thread not ending will terminate anyway") 341 | break; 342 | } 343 | } 344 | 345 | std::lock_guard lock(mKCPMapMtx); 346 | mKCPMap.clear(); 347 | KCP_LOGGER(false, LOGG_NOTIFY, "KCPNetServer Destruct") 348 | } 349 | 350 | int KCPNetServer::sendData(const char *pData, size_t lSize, KCPContext *pCTX) { 351 | std::lock_guard lock(mKCPMapMtx); 352 | int lStatus = -1; 353 | if (mKCPMap.count(pCTX->mKCPSocket)) { 354 | lStatus = ikcp_send(mKCPMap[pCTX->mKCPSocket]->mKCPServer, pData, lSize); 355 | } else { 356 | KCP_LOGGER(false, LOGG_NOTIFY, "KCP Connection is unknown") 357 | } 358 | return lStatus; 359 | } 360 | 361 | int KCPNetServer::configureKCP(const std::function &rGotData, 362 | const std::function &rDisconnect, 363 | const std::function(std::string, uint16_t, 364 | std::shared_ptr &)> &rValidate, 365 | const std::string &lIP, 366 | uint16_t lPort, 367 | std::shared_ptr pCTX) { 368 | mCTX = std::move(pCTX); 369 | if (lIP.empty()) { 370 | KCP_LOGGER(true, LOGG_FATAL, "IP / HOST must be provided"); 371 | return 1; 372 | } 373 | if (!lPort) { 374 | KCP_LOGGER(true, LOGG_FATAL, "Port must be provided") 375 | return 1; 376 | } 377 | 378 | kissnet::udp_socket lCreateSocket(kissnet::endpoint(lIP, lPort)); 379 | mKissnetSocket = std::move(lCreateSocket); //Move ownership to this/me 380 | mKissnetSocket.bind(); 381 | 382 | std::thread([=]() { netWorkerServer(rGotData, rValidate); }).detach(); 383 | std::thread([=]() { kcpNudgeWorkerServer(rDisconnect); }).detach(); 384 | KCP_LOGGER(false, LOGG_NOTIFY, "KCPNetServer Constructed"); 385 | return 0; 386 | } 387 | 388 | int KCPNetServer::configureInternal(KCPSettings &rSettings, KCPContext *pCTX) { 389 | std::lock_guard lock(mKCPMapMtx); 390 | int lResult = 0; 391 | if (mKCPMap.count(pCTX->mKCPSocket)) { 392 | lResult = ikcp_nodelay(mKCPMap[pCTX->mKCPSocket]->mKCPServer, rSettings.mNoDelay, rSettings.mInterval, 393 | rSettings.mResend, !rSettings.mFlow); 394 | if (lResult) { 395 | KCP_LOGGER(false, LOGG_ERROR, "ikcp_nodelay server failed.") 396 | return lResult; 397 | } 398 | lResult = ikcp_setmtu(mKCPMap[pCTX->mKCPSocket]->mKCPServer, rSettings.mMtu); 399 | if (lResult) { 400 | KCP_LOGGER(false, LOGG_ERROR, "ikcp_setmtu server failed.") 401 | return lResult; 402 | } 403 | lResult = ikcp_wndsize(mKCPMap[pCTX->mKCPSocket]->mKCPServer, rSettings.mSndWnd, rSettings.mRcvWnd); 404 | if (lResult) { 405 | KCP_LOGGER(false, LOGG_ERROR, "ikcp_wndsize server failed.") 406 | return lResult; 407 | } 408 | } else { 409 | KCP_LOGGER(false, LOGG_NOTIFY, "KCP Connection is unknown") 410 | } 411 | return lResult; 412 | } 413 | 414 | // Im in lock here no need to lock again 415 | void KCPNetServer::sendTimePacket(KCPServerData &rServerData) { 416 | 417 | KCPTimePacket lTimePacket; 418 | lTimePacket.t1 = std::chrono::duration_cast( 419 | std::chrono::steady_clock::now().time_since_epoch()).count(); 420 | if (rServerData.mGotStableTime) { 421 | lTimePacket.correctionActive = 1; 422 | lTimePacket.correction = rServerData.mCurrentCorrection; 423 | } 424 | auto[lSentBytes, lStatus] = mKissnetSocket.send((const std::byte *) &lTimePacket, sizeof(lTimePacket), &rServerData.mDestination); 425 | if (lSentBytes != sizeof(lTimePacket) || lStatus != kissnet::socket_status::valid) { 426 | KCP_LOGGER(false, LOGG_NOTIFY, "Server failed sending timing data") 427 | } 428 | } 429 | 430 | // For now the server is updating all connections every 10ms 431 | void KCPNetServer::kcpNudgeWorkerServer(const std::function &rDisconnect) { 432 | mNudgeThreadRunning = true; 433 | mNudgeThreadActive = true; 434 | uint32_t lTimeSleep = 10; 435 | uint64_t lTimeBase = std::chrono::duration_cast( 436 | std::chrono::steady_clock::now().time_since_epoch()).count(); 437 | while (mNudgeThreadActive) { 438 | std::this_thread::sleep_for(std::chrono::milliseconds(lTimeSleep)); 439 | uint64_t lTimeNow = std::chrono::duration_cast( 440 | std::chrono::steady_clock::now().time_since_epoch()).count() - lTimeBase; 441 | 442 | bool ltimeOutFlag = false; 443 | if (lTimeNow > mHeartBeatIntervalTrigger) { 444 | ltimeOutFlag = true; 445 | mHeartBeatIntervalTrigger += HEART_BEAT_DISTANCE; 446 | KCP_LOGGER(false, LOGG_NOTIFY, "Heart beat ") 447 | } 448 | 449 | bool lSendTimeLowFreq = false; 450 | if (lTimeNow > mSendTimeIntervalTriggerLow) { 451 | lSendTimeLowFreq = true; 452 | mSendTimeIntervalTriggerLow += TIME_PACKETS_NORMAL_DISTANCE_MS; 453 | //KCP_LOGGER(false, LOGG_NOTIFY,"Send time low") 454 | } 455 | 456 | bool lSendTimeHiFreq = false; 457 | if (lTimeNow > mSendTimeIntervalTriggerHi) { 458 | lSendTimeHiFreq = true; 459 | mSendTimeIntervalTriggerHi += TIME_PACKETS_BURST_DISTANCE_MS; 460 | //KCP_LOGGER(false, LOGG_NOTIFY,"Send time hi") 461 | } 462 | 463 | uint32_t lTimeSleepLowest = UINT32_MAX; 464 | mKCPMapMtx.lock(); 465 | if (!mKCPMap.empty()) { 466 | std::vector lRemoveList; 467 | //for (const auto &rKCP: mKCPMap) { 468 | for (auto pKCP = mKCPMap.cbegin(); pKCP != mKCPMap.cend() /* not hoisted */; /* no increment */) { 469 | // KCP_LOGGER(false, LOGG_NOTIFY,"dead server? " << pKCP->second->mKCPServer->dead_link) 470 | bool lDeleteThis = false; 471 | if (ltimeOutFlag) { 472 | if (!pKCP->second->mConnectionTimeOut && rDisconnect) { 473 | mKCPMapMtx.unlock(); 474 | rDisconnect(pKCP->second->mKCPContext.get()); 475 | mKCPMapMtx.lock(); 476 | lDeleteThis = true; 477 | } 478 | pKCP->second->mConnectionTimeOut--; 479 | } 480 | 481 | if (lDeleteThis) { 482 | KCP_LOGGER(false, LOGG_NOTIFY, "Removed stale client") 483 | mKCPMap.erase(pKCP++); 484 | } else { 485 | ikcp_update(pKCP->second->mKCPServer, lTimeNow); 486 | uint32_t lTimeSleepCandidate = ikcp_check(pKCP->second->mKCPServer, lTimeNow) - lTimeNow; 487 | if (lTimeSleepCandidate < lTimeSleepLowest) { 488 | lTimeSleepLowest = lTimeSleepCandidate; 489 | } 490 | 491 | // Time transfer section 492 | 493 | if (!pKCP->second->mClientGotCorrection && lSendTimeHiFreq) { 494 | // We have not corrected any time. send time packet 495 | sendTimePacket(*pKCP->second); 496 | } else if (pKCP->second->mClientGotCorrection && lSendTimeLowFreq) { 497 | // We have synced send a normal packet 498 | sendTimePacket(*pKCP->second); 499 | } 500 | 501 | // --------------------- 502 | 503 | ++pKCP; 504 | } 505 | } 506 | } 507 | mKCPMapMtx.unlock(); 508 | if (lTimeSleepLowest != UINT32_MAX) { 509 | lTimeSleep = lTimeSleepLowest; 510 | } else { 511 | lTimeSleep = 10; 512 | } 513 | 514 | } 515 | KCP_LOGGER(false, LOGG_NOTIFY, "kcpNudgeWorker quitting"); 516 | for (auto pKCP = mKCPMap.cbegin(); pKCP != mKCPMap.cend(); ++pKCP) { 517 | if (rDisconnect) { 518 | rDisconnect(pKCP->second->mKCPContext.get()); 519 | } 520 | } 521 | mNudgeThreadRunning = false; 522 | } 523 | 524 | void KCPNetServer::netWorkerServer(const std::function& rGotData, 525 | const std::function(std::string, 526 | uint16_t, 527 | std::shared_ptr &)> & rValidate) { 528 | mNetworkThreadRunning = true; 529 | kissnet::buffer receiveBuffer; 530 | kissnet::addr_collection receiveConnection; 531 | char lBuffer[KCP_MAX_BYTES]; 532 | while (true) { 533 | auto[received_bytes, status] = mKissnetSocket.recv(receiveBuffer,0, &receiveConnection); 534 | if (!received_bytes || status != kissnet::socket_status::valid) { 535 | KCP_LOGGER(false, LOGG_NOTIFY, "serverWorker quitting"); 536 | break; 537 | } 538 | 539 | if (mDropAll) continue; 540 | 541 | kissnet::endpoint lFromWho = mKissnetSocket.get_recv_endpoint(); 542 | std::string lKey = lFromWho.address + ":" + std::to_string(lFromWho.port); 543 | 544 | mKCPMapMtx.lock(); 545 | if (!mKCPMap.count(lKey)) { 546 | KCP_LOGGER(false, LOGG_NOTIFY, "New server connection") 547 | std::shared_ptr lx = std::make_shared(lKey); 548 | if (mCTX) { 549 | lx->mUnsafePointer = mCTX->mUnsafePointer; 550 | lx->mValue = mCTX->mValue; 551 | lx->mObject = mCTX->mObject; 552 | } 553 | 554 | if (rValidate) { 555 | mKCPMapMtx.unlock(); 556 | auto lCTX = rValidate(lFromWho.address, lFromWho.port, lx); 557 | if (lCTX == nullptr) { 558 | KCP_LOGGER(false, LOGG_NOTIFY, "Connection rejected") 559 | continue; 560 | } 561 | mKCPMapMtx.lock(); 562 | } 563 | 564 | // Create the connection and hand RCP the data 565 | auto lConnection = std::make_unique(); 566 | lConnection->mKCPContext = lx; 567 | lConnection->mWeakKCPNetServer = this; 568 | lConnection->mKCPServer = ikcp_create(lx->mID, lConnection.get()); 569 | if (!lConnection->mKCPServer) { 570 | throw std::runtime_error("Failed creating KCP"); 571 | } 572 | lConnection->mKCPServer->output = udp_output_server; 573 | lConnection->mDestination = receiveConnection; 574 | 575 | mKCPMap[lKey] = std::move(lConnection); 576 | mKCPMapMtx.unlock(); 577 | configureInternal(lx->mSettings, lx.get()); 578 | if (*(uint64_t *) receiveBuffer.data() == TIME_PREAMBLE_V1) { 579 | KCP_LOGGER(false, LOGG_NOTIFY, "server got time do nothing") 580 | continue; 581 | } 582 | mKCPMapMtx.lock(); 583 | ikcp_input(mKCPMap[lKey]->mKCPServer, (const char *) receiveBuffer.data(), received_bytes); 584 | int lRcv = ikcp_recv(mKCPMap[lKey]->mKCPServer, &lBuffer[0], KCP_MAX_BYTES); 585 | mKCPMapMtx.unlock(); 586 | if (lRcv > 0 && rGotData) { 587 | rGotData(&lBuffer[0], lRcv, lx.get()); 588 | } 589 | // The connection is known pass the data to RCP 590 | } else { 591 | if (*(uint64_t *) receiveBuffer.data() == TIME_PREAMBLE_V1) { 592 | auto lTimeData = (KCPTimePacket *) receiveBuffer.data(); 593 | int64_t lTimeNow = std::chrono::duration_cast( 594 | std::chrono::steady_clock::now().time_since_epoch()).count(); 595 | lTimeData->t4 = lTimeNow; 596 | int64_t lDelay = lTimeData->t4 - lTimeData->t1; 597 | int64_t lCompensation = ((lTimeData->t2 - lTimeData->t1) + (lTimeData->t3 - lTimeData->t4)) / 2; 598 | 599 | // Check T2 - T1 compared to T4 - T3 ? 600 | // The lowest delay has the lowest diff anyway so let's do that later if needed. 601 | 602 | mKCPMap[lKey]->mListOfDelayAndCompensation.emplace_back(std::make_pair(lDelay, lCompensation)); 603 | if (mKCPMap[lKey]->mListOfDelayAndCompensation.size() > MAX_SAVED_TIME_POINTS) { 604 | mKCPMap[lKey]->mListOfDelayAndCompensation.erase( 605 | mKCPMap[lKey]->mListOfDelayAndCompensation.begin()); 606 | } 607 | 608 | if (mKCPMap[lKey]->mListOfDelayAndCompensation.size() >= MIN_COLLECTED_TIME_POINTS) { 609 | std::vector> lTimePoints(MIN_COLLECTED_TIME_POINTS); 610 | std::partial_sort_copy(mKCPMap[lKey]->mListOfDelayAndCompensation.begin(), 611 | mKCPMap[lKey]->mListOfDelayAndCompensation.end(), 612 | lTimePoints.begin(), 613 | lTimePoints.end()); 614 | auto[lMinDelay, lMinCompensation] = lTimePoints[0]; 615 | auto[lMaxDelay, lMaxCompensation] = lTimePoints[MIN_COLLECTED_TIME_POINTS - 1]; 616 | 617 | if (((lMaxDelay - lMinDelay) / 1000) < MAX_DELAY_DIFF_MS && !mKCPMap[lKey]->mGotStableTime) { 618 | mKCPMap[lKey]->mGotStableTime = true; 619 | } 620 | 621 | if (mKCPMap[lKey]->mGotStableTime) { 622 | mKCPMap[lKey]->mCurrentCorrection = lMinCompensation; 623 | } 624 | 625 | if (!mKCPMap[lKey]->mClientGotCorrection && lTimeData->correctionActive == 2) { 626 | mKCPMap[lKey]->mClientGotCorrection = true; 627 | } 628 | } 629 | 630 | KCP_LOGGER(false, LOGG_NOTIFY, "server got time. Delay -> " << lDelay 631 | << " lCompensation -> " << lCompensation 632 | << " StableTime -> " 633 | << mKCPMap[lKey]->mGotStableTime 634 | << " Compensation -> " 635 | << mKCPMap[lKey]->mCurrentCorrection 636 | ) 637 | mKCPMapMtx.unlock(); 638 | continue; 639 | } 640 | mKCPMap[lKey]->mConnectionTimeOut = HEART_BEAT_TIME_OUT; 641 | ikcp_input(mKCPMap[lKey]->mKCPServer, (const char *) receiveBuffer.data(), received_bytes); 642 | int lRcv = ikcp_recv(mKCPMap[lKey]->mKCPServer, &lBuffer[0], KCP_MAX_BYTES); 643 | mKCPMapMtx.unlock(); 644 | if (lRcv > 0 && rGotData) { 645 | rGotData(&lBuffer[0], lRcv, mKCPMap[lKey]->mKCPContext.get()); 646 | } 647 | } 648 | } 649 | mNetworkThreadRunning = false; 650 | } 651 | -------------------------------------------------------------------------------- /kissnet/kissnet.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018-2020 Arthur Brainville (Ybalrid) and with the help of 5 | * Comunity Contributors! 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | * 25 | * INTRODUCTION 26 | * ============ 27 | * 28 | * Kissnet is a simple C++17 layer around the raw OS provided socket API to be 29 | * used on IP networks with the TCP and UDP protocols. 30 | * 31 | * Kissnet is not a networking framework, and it will not process your data or 32 | * assist you in any way. Kissnet's only goal is to provide a simple API to send 33 | * and receive bytes, 34 | * without having to play around with a bunch of structure, file descriptors, 35 | * handles and pointers given to a C-style API. The other goal of kissnet is to 36 | * provide an API that will works in a cross platform setting. 37 | * 38 | * Kissnet will automatically manage the eventual startup/shutdown of the 39 | * library needed to perform socket operations on a particular platform. (e.g. 40 | * the Windows Socket API on MS-Windows. 41 | * 42 | * Kissnet leverages (and expect you to do so), multiple features from C++17, 43 | * including: std::byte, if constexpr, structured bindings, if-initializer and 44 | * template parameter type deduction. 45 | * 46 | * The library is structured across 4 exposed data types: 47 | * 48 | * - buffer : a static array of std::byte implemented via std::array. 49 | * This is what you should use to hold raw data you are getting from a socket, 50 | * before extracting what you need from the bytes 51 | * - port_t : a 16 bit unsigned number. Represent a network port number 52 | * - endpoint : a structure that represent a location where you need to connect 53 | * to. Contains a hostname (as std::string) and a port number (as port_t) 54 | * - socket : a templated class that represents an ipv4 or ipv6 socket. 55 | * Protocol is either TCP or UDP 56 | * 57 | * Kissnet does error handling in 2 ways: 58 | * 59 | * 1: 60 | * When an operation can generate an error that the user should handle by hand 61 | * anyway, a tuple containing the expected type returned, and an object that 62 | * represent the status of what happens is returned. 63 | * 64 | * For example, socket send/receive operation can discover that the connection 65 | * was closed, or was shut down properly. It could also be the fact that a 66 | * socket was configured "non blocking" and would have blocked in this 67 | * situation. On both occasion, these methods will return the fact that 0 bytes 68 | * came across as the transaction size, and the status will indicate either an 69 | * error (socket no longer valid), or an actual status message (connection 70 | * closed, socket would have blocked) 71 | * 72 | * These status objects will behave like a const bool that equals "false" when 73 | * an error occurred, and "true" when it's just a status notification 74 | * 75 | * 2: 76 | * Fatal errors are by default handled by throwing a runtime_error exception. 77 | * But, for many reasons, you may want to 78 | * not use exceptions entirely. 79 | * 80 | * kissnet give you some facilities to get fatal errors information back, and 81 | * to choose how to handle it. Kissnet give you a few levers you can use: 82 | * 83 | * - You can deactivate the exception support by #defining KISSNET_NO_EXCEP 84 | * before #including kissnet.hpp. Instead, kissnet will use a function based 85 | * error handler 86 | * - By default, the error handler prints to stderr the error message, and 87 | * abort the program 88 | * - kissnet::error::callback is a function pointer that gets a string, and a 89 | * context pointer. The string is the error message, and the context pointer 90 | * what ever you gave kissnet for the occasion. This is a global pointer that 91 | * you can set as you want. This will override the "print to stderr" behavior 92 | * at fatal error time. 93 | * - kissnet::error::ctx is a void*, this will be passed to your error handler 94 | * as a "context" pointer. If you need your handler to write to a log, 95 | * or to turn on the HTCPCP enabled teapot on John's desk, you can. 96 | * - kissnet::abortOnFatalError is a boolean that will control the call to 97 | * abort(). This is independent to the fact that you did set or not an error 98 | * callback. please note that any object involved with the operation that 99 | * triggered the fatal error is probably in an invalid state, and probably 100 | * deserve to be thrown away. 101 | */ 102 | 103 | #ifndef KISS_NET 104 | #define KISS_NET 105 | 106 | ///Define this to not use exceptions 107 | #ifndef KISSNET_NO_EXCEP 108 | #define kissnet_fatal_error(STR) throw std::runtime_error(STR) 109 | #else 110 | #define kissnet_fatal_error(STR) kissnet::error::handle(STR); 111 | #endif 112 | 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | #include 119 | #include 120 | #include 121 | #include 122 | 123 | #ifdef _WIN32 124 | 125 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 126 | #define WIN32_LEAN_AND_MEAN 127 | 128 | #ifndef NOMINMAX 129 | #define NOMINMAX 130 | #endif //endif nominmax 131 | 132 | #include 133 | #include 134 | #include 135 | 136 | using ioctl_setting = u_long; 137 | using buffsize_t = int; 138 | 139 | #define AI_ADDRCONFIG 0x00000400 140 | 141 | #ifndef SHUT_RDWR 142 | #define SHUT_RDWR SD_BOTH 143 | #endif 144 | 145 | // taken from: https://github.com/rxi/dyad/blob/915ae4939529b9aaaf6ebfd2f65c6cff45fc0eac/src/dyad.c#L58 146 | inline const char* inet_ntop(int af, const void* src, char* dst, socklen_t size) 147 | { 148 | union 149 | { 150 | struct sockaddr sa; 151 | struct sockaddr_in sai; 152 | struct sockaddr_in6 sai6; 153 | } addr; 154 | int res; 155 | memset(&addr, 0, sizeof(addr)); 156 | addr.sa.sa_family = af; 157 | if (af == AF_INET6) 158 | { 159 | memcpy(&addr.sai6.sin6_addr, src, sizeof(addr.sai6.sin6_addr)); 160 | } 161 | else 162 | { 163 | memcpy(&addr.sai.sin_addr, src, sizeof(addr.sai.sin_addr)); 164 | } 165 | res = WSAAddressToStringA(&addr.sa, sizeof(addr), 0, dst, reinterpret_cast(&size)); 166 | if (res != 0) return NULL; 167 | return dst; 168 | } 169 | 170 | //Handle WinSock2/Windows Socket API initialization and cleanup 171 | #pragma comment(lib, "Ws2_32.lib") 172 | namespace kissnet 173 | { 174 | 175 | namespace win32_specific 176 | { 177 | ///Forward declare the object that will permit to manage the WSAStartup/Cleanup automatically 178 | struct WSA; 179 | 180 | ///Enclose the global pointer in this namespace. Only use this inside a shared_ptr 181 | namespace internal_state 182 | { 183 | static WSA* global_WSA = nullptr; 184 | } 185 | 186 | ///WSA object. Only to be constructed with std::make_shared() 187 | struct WSA : std::enable_shared_from_this 188 | { 189 | //For safety, only initialize Windows Socket API once, and delete it once 190 | ///Prevent copy construct 191 | WSA(const WSA&) = delete; 192 | ///Prevent copy assignment 193 | WSA& operator=(const WSA&) = delete; 194 | ///Prevent moving 195 | WSA(WSA&&) = delete; 196 | ///Prevent move assignment 197 | WSA& operator=(WSA&&) = delete; 198 | 199 | ///data storage 200 | WSADATA wsa_data; 201 | 202 | ///Startup 203 | WSA() : 204 | wsa_data {} 205 | { 206 | if (const auto status = WSAStartup(MAKEWORD(2, 2), &wsa_data); status != 0) 207 | { 208 | std::string error_message; 209 | switch (status) // https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup#return-value 210 | { 211 | default: 212 | error_message = "Unknown error happened."; 213 | break; 214 | case WSASYSNOTREADY: 215 | error_message = "The underlying network subsystem is not ready for network communication."; 216 | break; 217 | case WSAVERNOTSUPPORTED: //unlikely, we specify 2.2! 218 | error_message = " The version of Windows Sockets support requested " 219 | "(2.2)" //we know here the version was 2.2, add that to the error message copied from MSDN 220 | " is not provided by this particular Windows Sockets implementation. "; 221 | break; 222 | case WSAEINPROGRESS: 223 | error_message = "A blocking Windows Sockets 1.1 operation is in progress."; 224 | break; 225 | case WSAEPROCLIM: 226 | error_message = "A limit on the number of tasks supported by the Windows Sockets implementation has been reached."; 227 | break; 228 | case WSAEFAULT: //unlikely, if this ctor is running, wsa_data is part of this object's "stack" data 229 | error_message = "The lpWSAData parameter is not a valid pointer."; 230 | break; 231 | } 232 | kissnet_fatal_error(error_message); 233 | } 234 | #ifdef KISSNET_WSA_DEBUG 235 | std::cerr << "Initialized Windows Socket API\n"; 236 | #endif 237 | } 238 | 239 | ///Cleanup 240 | ~WSA() 241 | { 242 | WSACleanup(); 243 | internal_state::global_WSA = nullptr; 244 | #ifdef KISSNET_WSA_DEBUG 245 | std::cerr << "Cleanup Windows Socket API\n"; 246 | #endif 247 | } 248 | 249 | ///get the shared pointer 250 | std::shared_ptr getPtr() 251 | { 252 | return shared_from_this(); 253 | } 254 | }; 255 | 256 | ///Get-or-create the global pointer 257 | inline std::shared_ptr getWSA() 258 | { 259 | //If it has been created already: 260 | if (internal_state::global_WSA) 261 | return internal_state::global_WSA->getPtr(); //fetch the smart pointer from the naked pointer 262 | 263 | //Create in wsa 264 | auto wsa = std::make_shared(); 265 | 266 | //Save the raw address in the global state 267 | internal_state::global_WSA = wsa.get(); 268 | 269 | //Return the smart pointer 270 | return wsa; 271 | } 272 | } 273 | 274 | #define KISSNET_OS_SPECIFIC_PAYLOAD_NAME wsa_ptr 275 | #define KISSNET_OS_SPECIFIC std::shared_ptr KISSNET_OS_SPECIFIC_PAYLOAD_NAME 276 | #define KISSNET_OS_INIT KISSNET_OS_SPECIFIC_PAYLOAD_NAME = kissnet::win32_specific::getWSA() 277 | 278 | ///Return the last error code 279 | inline int get_error_code() 280 | { 281 | const auto error = WSAGetLastError(); 282 | 283 | //We need to posixify the values that we are actually using inside this header. 284 | switch (error) 285 | { 286 | case WSAEWOULDBLOCK: 287 | return EWOULDBLOCK; 288 | case WSAEBADF: 289 | return EBADF; 290 | case WSAEINTR: 291 | return EINTR; 292 | default: 293 | return error; 294 | } 295 | } 296 | } 297 | #else //UNIX platform 298 | 299 | #include 300 | #include 301 | #include 302 | #include 303 | #include 304 | #include 305 | #include 306 | #include 307 | #include 308 | #include 309 | 310 | using ioctl_setting = int; 311 | using buffsize_t = size_t; 312 | 313 | //To get consistent socket API between Windows and Linux: 314 | static const int INVALID_SOCKET = -1; 315 | static const int SOCKET_ERROR = -1; 316 | using SOCKET = int; 317 | using SOCKADDR_IN = sockaddr_in; 318 | using SOCKADDR = sockaddr; 319 | using IN_ADDR = in_addr; 320 | 321 | //Wrap them in their WIN32 names 322 | inline int closesocket(SOCKET in) 323 | { 324 | return close(in); 325 | } 326 | 327 | template 328 | inline int ioctlsocket(int fd, int request, Params&&... params) 329 | { 330 | return ioctl(fd, request, params...); 331 | } 332 | 333 | #define KISSNET_OS_SPECIFIC_PAYLOAD_NAME dummy 334 | #define KISSNET_OS_SPECIFIC char dummy 335 | #define KISSNET_OS_INIT dummy = 42; 336 | 337 | namespace unix_specific 338 | { 339 | } 340 | 341 | inline int get_error_code() 342 | { 343 | return errno; 344 | } 345 | 346 | #endif //ifdef WIN32 347 | 348 | #ifdef KISSNET_USE_OPENSSL 349 | 350 | #include 351 | #include 352 | 353 | #include 354 | #include 355 | 356 | #endif //Kissnet use OpenSSL 357 | 358 | #ifndef SOL_TCP 359 | #define SOL_TCP IPPROTO_TCP 360 | #endif 361 | 362 | ///Main namespace of kissnet 363 | namespace kissnet 364 | { 365 | 366 | ///Exception-less error handling infrastructure 367 | namespace error 368 | { 369 | static void (*callback)(const std::string&, void* ctx) = nullptr; 370 | static void* ctx = nullptr; 371 | static bool abortOnFatalError = true; 372 | 373 | inline void handle(const std::string& str) 374 | { 375 | //if the error::callback function has been provided, call that 376 | if (callback) 377 | { 378 | callback(str, ctx); 379 | } 380 | //Print error into the standard error output 381 | else 382 | { 383 | fputs(str.c_str(), stderr); 384 | } 385 | 386 | //If the error abort hasn't been deactivated 387 | if (abortOnFatalError) 388 | { 389 | abort(); 390 | } 391 | } 392 | } 393 | 394 | ///low level protocol used, between TCP\TCP_SSL and UDP 395 | enum class protocol { 396 | tcp, 397 | tcp_ssl, 398 | udp 399 | }; 400 | 401 | ///Address information structs 402 | struct addr_collection { 403 | sockaddr_storage adrinf = {0}; 404 | socklen_t sock_size = 0; 405 | }; 406 | 407 | ///File descriptor set types 408 | static constexpr int fds_read = 0x1; 409 | static constexpr int fds_write = 0x2; 410 | static constexpr int fds_except = 0x4; 411 | 412 | ///buffer is an array of std::byte 413 | template 414 | using buffer = std::array; 415 | 416 | ///port_t is the port 417 | using port_t = uint16_t; 418 | 419 | ///An endpoint is where the network will connect to (address and port) 420 | struct endpoint 421 | { 422 | ///The address to connect to 423 | std::string address {}; 424 | 425 | ///The port to connect to 426 | port_t port {}; 427 | 428 | ///Default constructor, the endpoint is not valid at that point, but you can set the address/port manually 429 | endpoint() = default; 430 | 431 | ///Basically create the endpoint with what you give it 432 | endpoint(std::string addr, port_t prt) : 433 | address { std::move(addr) }, port { prt } 434 | { } 435 | 436 | static bool is_valid_port_number(unsigned long n) 437 | { 438 | return n < 1 << 16; 439 | } 440 | 441 | ///Construct the endpoint from "address:port" 442 | endpoint(std::string addr) 443 | { 444 | const auto separator = addr.find_last_of(':'); 445 | 446 | //Check if input wasn't missformed 447 | if (separator == std::string::npos) 448 | kissnet_fatal_error("string is not of address:port form"); 449 | if (separator == addr.size() - 1) 450 | kissnet_fatal_error("string has ':' as last character. Expected port number here"); 451 | 452 | //Isolate address 453 | address = addr.substr(0, separator); 454 | 455 | //Read from string as unsigned 456 | const auto parsed_port = strtoul(addr.substr(separator + 1).c_str(), nullptr, 10); 457 | 458 | //In all other cases, port was always given as a port_t type, strongly preventing it to be a number outside of the [0; 65535] range. Here it's not the case. 459 | //To detect errors early, check it here : 460 | if (!is_valid_port_number(parsed_port)) 461 | kissnet_fatal_error("Invalid port number " + std::to_string(parsed_port)); 462 | 463 | //Store it 464 | port = static_cast(parsed_port); 465 | } 466 | 467 | ///Construct an endpoint from a SOCKADDR 468 | endpoint(SOCKADDR* addr) 469 | { 470 | 471 | switch (addr->sa_family) 472 | { 473 | case AF_INET: { 474 | auto ip_addr = (SOCKADDR_IN*)(addr); 475 | address = inet_ntoa(ip_addr->sin_addr); 476 | port = ntohs(ip_addr->sin_port); 477 | } 478 | break; 479 | 480 | case AF_INET6: { 481 | auto ip_addr = (sockaddr_in6*)(addr); 482 | char buffer[INET6_ADDRSTRLEN]; 483 | address = inet_ntop(AF_INET6, &(ip_addr->sin6_addr), buffer, INET6_ADDRSTRLEN); 484 | port = ntohs(ip_addr->sin6_port); 485 | } 486 | break; 487 | 488 | default: { 489 | kissnet_fatal_error("Trying to construct an endpoint for a protocol familly that is neither AF_INET or AF_INET6"); 490 | } 491 | } 492 | 493 | if (address.empty()) 494 | kissnet_fatal_error("Couldn't construct endpoint from sockaddr(_storage) struct"); 495 | } 496 | }; 497 | 498 | //Wrap "system calls" here to avoid conflicts with the names used in the socket class 499 | 500 | ///socket() 501 | inline auto syscall_socket = [](int af, int type, int protocol) { 502 | return ::socket(af, type, protocol); 503 | }; 504 | 505 | ///select() 506 | inline auto syscall_select = [](int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout) { 507 | return ::select(nfds, readfds, writefds, exceptfds, timeout); 508 | }; 509 | 510 | ///recv() 511 | inline auto syscall_recv = [](SOCKET s, char* buff, buffsize_t len, int flags) { 512 | return ::recv(s, buff, len, flags); 513 | }; 514 | 515 | ///send() 516 | inline auto syscall_send = [](SOCKET s, const char* buff, buffsize_t len, int flags) { 517 | return ::send(s, buff, len, flags); 518 | }; 519 | 520 | ///bind() 521 | inline auto syscall_bind = [](SOCKET s, const struct sockaddr* name, socklen_t namelen) { 522 | return ::bind(s, name, namelen); 523 | }; 524 | 525 | ///connect() 526 | inline auto syscall_connect = [](SOCKET s, const struct sockaddr* name, socklen_t namelen) { 527 | return ::connect(s, name, namelen); 528 | }; 529 | 530 | ///listen() 531 | inline auto syscall_listen = [](SOCKET s, int backlog) { 532 | return ::listen(s, backlog); 533 | }; 534 | 535 | ///accept() 536 | inline auto syscall_accept = [](SOCKET s, struct sockaddr* addr, socklen_t* addrlen) { 537 | return ::accept(s, addr, addrlen); 538 | }; 539 | 540 | ///shutdown() 541 | inline auto syscall_shutdown = [](SOCKET s) { 542 | return ::shutdown(s, SHUT_RDWR); 543 | }; 544 | 545 | ///Represent the status of a socket as returned by a socket operation (send, received). Implicitly convertible to bool 546 | struct socket_status 547 | { 548 | ///Enumeration of socket status, with a 1 byte footprint 549 | enum values : int8_t { 550 | errored = 0x0, 551 | valid = 0x1, 552 | cleanly_disconnected = 0x2, 553 | non_blocking_would_have_blocked = 0x3, 554 | timed_out = 0x4 555 | 556 | /* ... any other info on a "still valid socket" goes here ... */ 557 | 558 | }; 559 | 560 | ///Actual value of the socket_status. 561 | const values value; 562 | 563 | ///Use the default constructor 564 | socket_status() : 565 | value { errored } { } 566 | 567 | ///Construct a "errored/valid" status for a true/false 568 | explicit socket_status(bool state) : 569 | value(values(state ? valid : errored)) { } 570 | 571 | socket_status(values v) : 572 | value(v) { } 573 | 574 | ///Copy socket status by default 575 | socket_status(const socket_status&) = default; 576 | 577 | ///Move socket status by default 578 | socket_status(socket_status&&) = default; 579 | 580 | ///implicitly convert this object to const bool (as the status should not change) 581 | operator bool() const 582 | { 583 | //See the above enum: every value <= 0 correspond to an error, and will return false. Every value > 0 returns true 584 | return value > 0; 585 | } 586 | 587 | int8_t get_value() 588 | { 589 | return value; 590 | } 591 | 592 | bool operator==(values v) 593 | { 594 | return v == value; 595 | } 596 | }; 597 | 598 | #ifdef KISSNET_USE_OPENSSL 599 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 600 | static std::shared_ptr> SSL_lock_cs; 601 | 602 | class ThreadSafe_SSL 603 | { 604 | public: 605 | ThreadSafe_SSL() 606 | { 607 | SSL_lock_cs = std::make_shared>(CRYPTO_num_locks()); 608 | 609 | CRYPTO_set_locking_callback((void (*)(int, int, const char*, int)) 610 | win32_locking_callback); 611 | } 612 | 613 | ~ThreadSafe_SSL() { CRYPTO_set_locking_callback(nullptr); } 614 | 615 | private: 616 | static void win32_locking_callback(int mode, int type, const char* file, int line) 617 | { 618 | auto& locks = *SSL_lock_cs; 619 | 620 | if (mode & CRYPTO_LOCK) 621 | { 622 | locks[type].lock(); 623 | } 624 | else 625 | { 626 | locks[type].unlock(); 627 | } 628 | } 629 | }; 630 | 631 | #endif 632 | 633 | class Initialize_SSL 634 | { 635 | public: 636 | Initialize_SSL() 637 | { 638 | #if OPENSSL_VERSION_NUMBER < 0x1010001fL 639 | SSL_load_error_strings(); 640 | SSL_library_init(); 641 | #else 642 | OPENSSL_init_ssl( 643 | OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); 644 | 645 | OPENSSL_init_crypto( 646 | OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, 647 | nullptr); 648 | #endif 649 | } 650 | 651 | ~Initialize_SSL() 652 | { 653 | #if OPENSSL_VERSION_NUMBER < 0x1010001fL 654 | ERR_free_strings(); 655 | #endif 656 | } 657 | 658 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 659 | private: 660 | ThreadSafe_SSL thread_setup; 661 | #endif 662 | }; 663 | 664 | static Initialize_SSL InitializeSSL; 665 | #endif 666 | 667 | ///Class that represent a socket 668 | template 669 | class socket 670 | { 671 | ///Represent a number of bytes with a status information. Some of the methods of this class returns this. 672 | using bytes_with_status = std::tuple; 673 | 674 | ///OS specific stuff. payload we have to hold onto for RAII management of the Operating System's socket library (e.g. Windows Socket API WinSock2) 675 | KISSNET_OS_SPECIFIC; 676 | 677 | ///operatic-system type for a socket object 678 | SOCKET sock = INVALID_SOCKET; 679 | 680 | #ifdef KISSNET_USE_OPENSSL 681 | SSL* pSSL = nullptr; 682 | SSL_CTX* pContext = nullptr; 683 | #endif 684 | 685 | ///Location where this socket is bound 686 | endpoint bind_loc = {}; 687 | 688 | ///Address information structures 689 | addrinfo getaddrinfo_hints = {}; 690 | addrinfo* getaddrinfo_results = nullptr; 691 | addrinfo* socket_addrinfo = nullptr; 692 | 693 | void initialize_addrinfo() 694 | { 695 | int type {}; 696 | int iprotocol {}; 697 | if constexpr (sock_proto == protocol::tcp || sock_proto == protocol::tcp_ssl) 698 | { 699 | type = SOCK_STREAM; 700 | iprotocol = IPPROTO_TCP; 701 | } 702 | 703 | else if constexpr (sock_proto == protocol::udp) 704 | { 705 | type = SOCK_DGRAM; 706 | iprotocol = IPPROTO_UDP; 707 | } 708 | 709 | getaddrinfo_hints = {}; 710 | getaddrinfo_hints.ai_family = AF_UNSPEC; 711 | getaddrinfo_hints.ai_socktype = type; 712 | getaddrinfo_hints.ai_protocol = iprotocol; 713 | getaddrinfo_hints.ai_flags = AI_ADDRCONFIG; 714 | } 715 | 716 | ///Create and connect to socket 717 | socket_status connect(addrinfo* addr, int64_t timeout, bool createsocket) 718 | { 719 | if constexpr (sock_proto == protocol::tcp || sock_proto == protocol::tcp_ssl) //only TCP is a connected protocol 720 | { 721 | if (createsocket) 722 | { 723 | close(); 724 | socket_addrinfo = nullptr; 725 | sock = syscall_socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); 726 | } 727 | 728 | if (sock == INVALID_SOCKET) 729 | return socket_status::errored; 730 | 731 | socket_addrinfo = addr; 732 | 733 | if (timeout > 0) 734 | set_non_blocking(true); 735 | 736 | int error = syscall_connect(sock, addr->ai_addr, socklen_t(addr->ai_addrlen)); 737 | if (error == SOCKET_ERROR) 738 | { 739 | error = get_error_code(); 740 | if (error == EWOULDBLOCK || error == EAGAIN || error == EINPROGRESS) 741 | { 742 | struct timeval tv; 743 | tv.tv_sec = static_cast(timeout / 1000); 744 | tv.tv_usec = 1000 * static_cast(timeout % 1000); 745 | 746 | fd_set fd_write, fd_except; 747 | ; 748 | FD_ZERO(&fd_write); 749 | FD_SET(sock, &fd_write); 750 | FD_ZERO(&fd_except); 751 | FD_SET(sock, &fd_except); 752 | 753 | int ret = syscall_select(static_cast(sock) + 1, NULL, &fd_write, &fd_except, &tv); 754 | if (ret == -1) 755 | error = get_error_code(); 756 | else if (ret == 0) 757 | error = ETIMEDOUT; 758 | else 759 | { 760 | socklen_t errlen = sizeof(error); 761 | if (getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &errlen) != 0) 762 | kissnet_fatal_error("getting socket error returned an error"); 763 | } 764 | } 765 | } 766 | 767 | if (timeout > 0) 768 | set_non_blocking(false); 769 | 770 | if (error == 0) 771 | { 772 | return socket_status::valid; 773 | } 774 | else 775 | { 776 | close(); 777 | socket_addrinfo = nullptr; 778 | return socket_status::errored; 779 | } 780 | } 781 | 782 | kissnet_fatal_error("connect called for non-tcp socket"); 783 | } 784 | 785 | ///sockaddr struct 786 | sockaddr_storage socket_input = {}; 787 | socklen_t socket_input_socklen = 0; 788 | 789 | public: 790 | 791 | ///Construct an invalid socket 792 | socket() = default; 793 | 794 | ///socket<> isn't copyable 795 | socket(const socket&) = delete; 796 | 797 | ///socket<> isn't copyable 798 | socket& operator=(const socket&) = delete; 799 | 800 | ///Move constructor. socket<> isn't copyable 801 | socket(socket&& other) noexcept 802 | { 803 | KISSNET_OS_SPECIFIC_PAYLOAD_NAME = std::move(other.KISSNET_OS_SPECIFIC_PAYLOAD_NAME); 804 | bind_loc = std::move(other.bind_loc); 805 | sock = std::move(other.sock); 806 | socket_input = std::move(other.socket_input); 807 | socket_input_socklen = std::move(other.socket_input_socklen); 808 | getaddrinfo_results = std::move(other.getaddrinfo_results); 809 | socket_addrinfo = std::move(other.socket_addrinfo); 810 | 811 | #ifdef KISSNET_USE_OPENSSL 812 | pSSL = other.pSSL; 813 | pContext = other.pContext; 814 | other.pSSL = nullptr; 815 | other.pContext = nullptr; 816 | #endif 817 | 818 | other.sock = INVALID_SOCKET; 819 | other.getaddrinfo_results = nullptr; 820 | other.socket_addrinfo = nullptr; 821 | } 822 | 823 | ///Move assign operation 824 | socket& operator=(socket&& other) noexcept 825 | { 826 | if (this != &other) 827 | { 828 | if (!(sock < 0) || sock != INVALID_SOCKET) 829 | closesocket(sock); 830 | 831 | KISSNET_OS_SPECIFIC_PAYLOAD_NAME = std::move(other.KISSNET_OS_SPECIFIC_PAYLOAD_NAME); 832 | bind_loc = std::move(other.bind_loc); 833 | sock = std::move(other.sock); 834 | socket_input = std::move(other.socket_input); 835 | socket_input_socklen = std::move(other.socket_input_socklen); 836 | getaddrinfo_results = std::move(other.getaddrinfo_results); 837 | socket_addrinfo = std::move(other.socket_addrinfo); 838 | 839 | #ifdef KISSNET_USE_OPENSSL 840 | pSSL = other.pSSL; 841 | pContext = other.pContext; 842 | other.pSSL = nullptr; 843 | other.pContext = nullptr; 844 | #endif 845 | 846 | other.sock = INVALID_SOCKET; 847 | other.getaddrinfo_results = nullptr; 848 | other.socket_addrinfo = nullptr; 849 | } 850 | return *this; 851 | } 852 | 853 | ///Return true if the underlying OS provided socket representation (file descriptor, handle...). Both socket are pointing to the same thing in this case 854 | bool operator==(const socket& other) const 855 | { 856 | return sock == other.sock; 857 | } 858 | 859 | ///Return true if socket is valid. If this is false, you probably shouldn't attempt to send/receive anything, it will probably explode in your face! 860 | bool is_valid() const 861 | { 862 | return sock != INVALID_SOCKET; 863 | } 864 | 865 | inline operator bool() const 866 | { 867 | return is_valid(); 868 | } 869 | 870 | ///Construct socket and (if applicable) connect to the endpoint 871 | socket(endpoint bind_to) : 872 | bind_loc { std::move(bind_to) } 873 | { 874 | //operating system related housekeeping 875 | KISSNET_OS_INIT; 876 | 877 | //Do we use streams or datagrams 878 | initialize_addrinfo(); 879 | 880 | if (getaddrinfo(bind_loc.address.c_str(), std::to_string(bind_loc.port).c_str(), &getaddrinfo_hints, &getaddrinfo_results) != 0) 881 | { 882 | kissnet_fatal_error("getaddrinfo failed!"); 883 | } 884 | 885 | for (auto* addr = getaddrinfo_results; addr; addr = addr->ai_next) 886 | { 887 | sock = syscall_socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); 888 | if (sock != INVALID_SOCKET) 889 | { 890 | socket_addrinfo = addr; 891 | break; 892 | } 893 | } 894 | 895 | if (sock == INVALID_SOCKET) 896 | { 897 | kissnet_fatal_error("unable to create socket!"); 898 | } 899 | } 900 | 901 | ///Construct a socket from an operating system socket, an additional endpoint to remember from where we are 902 | socket(SOCKET native_sock, endpoint bind_to) : 903 | sock { native_sock }, bind_loc(std::move(bind_to)) 904 | { 905 | KISSNET_OS_INIT; 906 | 907 | initialize_addrinfo(); 908 | } 909 | 910 | ///Set the socket in non blocking mode 911 | /// \param state By default "true". If put to false, it will set the socket back into blocking, normal mode 912 | void set_non_blocking(bool state = true) const 913 | { 914 | #ifdef _WIN32 915 | ioctl_setting set = state ? 1 : 0; 916 | if (ioctlsocket(sock, FIONBIO, &set) < 0) 917 | #else 918 | const auto flags = fcntl(sock, F_GETFL, 0); 919 | const auto newflags = state ? flags | O_NONBLOCK : flags ^ O_NONBLOCK; 920 | if (fcntl(sock, F_SETFL, newflags) < 0) 921 | #endif 922 | kissnet_fatal_error("setting socket to nonblock returned an error"); 923 | } 924 | 925 | ///Set the socket option for broadcasts 926 | /// \param state By default "true". If put to false, it will disable broadcasts 927 | void set_broadcast(bool state = true) const 928 | { 929 | const int broadcast = state ? 1 : 0; 930 | if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&broadcast), sizeof(broadcast)) != 0) 931 | kissnet_fatal_error("setting socket broadcast mode returned an error"); 932 | } 933 | 934 | /// Set the socket option for TCPNoDelay 935 | /// \param state By default "true". If put to false, it will disable TCPNoDelay 936 | void set_tcp_no_delay(bool state = true) const 937 | { 938 | if constexpr (sock_proto == protocol::tcp) 939 | { 940 | const int tcpnodelay = state ? 1 : 0; 941 | if (setsockopt(sock, SOL_TCP, TCP_NODELAY, reinterpret_cast(&tcpnodelay), sizeof(tcpnodelay)) != 0) 942 | kissnet_fatal_error("setting socket tcpnodelay mode returned an error"); 943 | } 944 | } 945 | 946 | /// Get socket status 947 | socket_status get_status() const 948 | { 949 | int sockerror = 0; 950 | socklen_t errlen = sizeof(sockerror); 951 | if (getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&sockerror), &errlen) != 0) 952 | kissnet_fatal_error("getting socket error returned an error"); 953 | 954 | return sockerror == SOCKET_ERROR ? socket_status::errored : socket_status::valid; 955 | } 956 | 957 | ///Bind socket locally using the address and port of the endpoint 958 | void bind() 959 | { 960 | if (syscall_bind(sock, static_cast(socket_addrinfo->ai_addr), socklen_t(socket_addrinfo->ai_addrlen)) == SOCKET_ERROR) 961 | { 962 | kissnet_fatal_error("bind() failed\n"); 963 | } 964 | } 965 | 966 | ///Join a multicast group 967 | void join(const endpoint& multi_cast_endpoint, const std::string& interface = "") 968 | { 969 | if (sock_proto != protocol::udp) 970 | { 971 | kissnet_fatal_error("joining a multicast is only possible in UDP mode\n"); 972 | } 973 | 974 | addrinfo *multicast_addr; 975 | addrinfo *local_addr; 976 | addrinfo hints = {0}; 977 | hints.ai_family = PF_UNSPEC; 978 | hints.ai_flags = AI_NUMERICHOST; 979 | if (getaddrinfo(multi_cast_endpoint.address.c_str(), nullptr, &hints, &multicast_addr) != 0) 980 | { 981 | kissnet_fatal_error("getaddrinfo() failed\n"); 982 | } 983 | 984 | hints.ai_family = multicast_addr->ai_family; 985 | hints.ai_socktype = SOCK_DGRAM; 986 | hints.ai_flags = AI_PASSIVE; 987 | if (getaddrinfo(nullptr, std::to_string(multi_cast_endpoint.port).c_str(), &hints, &local_addr) != 0) 988 | { 989 | kissnet_fatal_error("getaddrinfo() failed\n"); 990 | } 991 | 992 | sock = syscall_socket(local_addr->ai_family, local_addr->ai_socktype, local_addr->ai_protocol); 993 | if (sock != INVALID_SOCKET) 994 | { 995 | socket_addrinfo = local_addr; 996 | } else { 997 | kissnet_fatal_error("syscall_socket() failed\n"); 998 | } 999 | 1000 | bind(); 1001 | 1002 | //IPv4 1003 | if (multicast_addr->ai_family == PF_INET && multicast_addr->ai_addrlen == sizeof(struct sockaddr_in)) 1004 | { 1005 | struct ip_mreq multicastRequest = {0}; 1006 | memcpy(&multicastRequest.imr_multiaddr, 1007 | &((struct sockaddr_in*)(multicast_addr->ai_addr))->sin_addr, 1008 | sizeof(multicastRequest.imr_multiaddr)); 1009 | 1010 | if (interface.length()) { 1011 | multicastRequest.imr_interface.s_addr = inet_addr(interface.c_str());; 1012 | } else { 1013 | multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY); 1014 | } 1015 | 1016 | if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &multicastRequest, sizeof(multicastRequest)) != 0) 1017 | { 1018 | kissnet_fatal_error("setsockopt() failed\n"); 1019 | } 1020 | } 1021 | 1022 | //IPv6 1023 | else if (multicast_addr->ai_family == PF_INET6 && multicast_addr->ai_addrlen == sizeof(struct sockaddr_in6)) 1024 | { 1025 | struct ipv6_mreq multicastRequest = {0}; 1026 | memcpy(&multicastRequest.ipv6mr_multiaddr, 1027 | &((struct sockaddr_in6*)(multicast_addr->ai_addr))->sin6_addr, 1028 | sizeof(multicastRequest.ipv6mr_multiaddr)); 1029 | 1030 | if (interface.length()) { 1031 | struct addrinfo *reslocal; 1032 | if (getaddrinfo(interface.c_str(), nullptr, nullptr, &reslocal)){ 1033 | kissnet_fatal_error("getaddrinfo() failed\n"); 1034 | } 1035 | multicastRequest.ipv6mr_interface = ((sockaddr_in6 *)reslocal->ai_addr)->sin6_scope_id; 1036 | freeaddrinfo(reslocal); 1037 | } else { 1038 | multicastRequest.ipv6mr_interface = 0; 1039 | } 1040 | 1041 | 1042 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*) &multicastRequest, sizeof(multicastRequest)) != 0) 1043 | { 1044 | kissnet_fatal_error("setsockopt() failed\n"); 1045 | } 1046 | } 1047 | else 1048 | { 1049 | kissnet_fatal_error("unknown AI family.\n"); 1050 | } 1051 | 1052 | freeaddrinfo(multicast_addr); 1053 | } 1054 | 1055 | ///(For TCP) connect to the endpoint as client 1056 | socket_status connect(int64_t timeout = 0) 1057 | { 1058 | if constexpr (sock_proto == protocol::tcp) //only TCP is a connected protocol 1059 | { 1060 | // try to connect to existing native socket, if any. 1061 | auto curr_addr = socket_addrinfo; 1062 | if (connect(curr_addr, timeout, false) != socket_status::valid) 1063 | { 1064 | // try to create/connect native socket for one of the other addrinfo, if any 1065 | for (auto* addr = getaddrinfo_results; addr; addr = addr->ai_next) 1066 | { 1067 | if (addr == curr_addr) 1068 | continue; // already checked 1069 | 1070 | if (connect(addr, timeout, true) == socket_status::valid) 1071 | break; // success 1072 | } 1073 | } 1074 | 1075 | if (sock == INVALID_SOCKET) 1076 | kissnet_fatal_error("unable to create connectable socket!"); 1077 | 1078 | return socket_status::valid; 1079 | } 1080 | #ifdef KISSNET_USE_OPENSSL 1081 | else if constexpr (sock_proto == protocol::tcp_ssl) //only TCP is a connected protocol 1082 | { 1083 | // try to connect to existing native socket, if any. 1084 | auto curr_addr = socket_addrinfo; 1085 | if (connect(curr_addr, timeout, false) != socket_status::valid) 1086 | { 1087 | // try to create/connect native socket for one of the other addrinfo, if any 1088 | for (auto* addr = getaddrinfo_results; addr; addr = addr->ai_next) 1089 | { 1090 | if (addr == curr_addr) 1091 | continue; // already checked 1092 | 1093 | if (connect(addr, timeout, true) == socket_status::valid) 1094 | break; // success 1095 | } 1096 | } 1097 | 1098 | if (sock == INVALID_SOCKET) 1099 | kissnet_fatal_error("unable to create connectable socket!"); 1100 | 1101 | auto* pMethod = 1102 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) 1103 | TLSv1_2_client_method(); 1104 | #else 1105 | TLS_client_method(); 1106 | #endif 1107 | 1108 | pContext = SSL_CTX_new(pMethod); 1109 | pSSL = SSL_new(pContext); 1110 | if (!pSSL) 1111 | return socket_status::errored; 1112 | 1113 | if (!(static_cast(SSL_set_fd(pSSL, sock)))) 1114 | return socket_status::errored; 1115 | 1116 | if (SSL_connect(pSSL) != 1) 1117 | return socket_status::errored; 1118 | 1119 | return socket_status::valid; 1120 | } 1121 | #endif 1122 | } 1123 | 1124 | ///(for TCP= setup socket to listen to connection. Need to be called on binded socket, before being able to accept() 1125 | void listen() 1126 | { 1127 | if constexpr (sock_proto == protocol::tcp) 1128 | { 1129 | if (syscall_listen(sock, SOMAXCONN) == SOCKET_ERROR) 1130 | { 1131 | kissnet_fatal_error("listen failed\n"); 1132 | } 1133 | } 1134 | } 1135 | 1136 | ///(for TCP) Wait for incoming connection, return socket connect to the client. Blocking. 1137 | socket accept() 1138 | { 1139 | if constexpr (sock_proto != protocol::tcp) 1140 | { 1141 | return { INVALID_SOCKET, {} }; 1142 | } 1143 | 1144 | sockaddr_storage socket_address; 1145 | SOCKET s; 1146 | socklen_t size = sizeof socket_address; 1147 | 1148 | if ((s = syscall_accept(sock, reinterpret_cast(&socket_address), &size)) == INVALID_SOCKET) 1149 | { 1150 | const auto error = get_error_code(); 1151 | switch (error) 1152 | { 1153 | case EWOULDBLOCK: //if socket "would have blocked" from the call, ignore 1154 | case EINTR: //if blocking call got interrupted, ignore; 1155 | return {}; 1156 | } 1157 | 1158 | kissnet_fatal_error("accept() returned an invalid socket\n"); 1159 | } 1160 | 1161 | return { s, endpoint(reinterpret_cast(&socket_address)) }; 1162 | } 1163 | 1164 | void close() 1165 | { 1166 | if (sock != INVALID_SOCKET) 1167 | { 1168 | #ifdef KISSNET_USE_OPENSSL 1169 | if constexpr (sock_proto == protocol::tcp_ssl) 1170 | { 1171 | if (pSSL) 1172 | { 1173 | SSL_set_shutdown(pSSL, SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN); 1174 | SSL_shutdown(pSSL); 1175 | SSL_free(pSSL); 1176 | if (pContext) 1177 | SSL_CTX_free(pContext); 1178 | } 1179 | } 1180 | #endif 1181 | 1182 | closesocket(sock); 1183 | } 1184 | 1185 | sock = INVALID_SOCKET; 1186 | } 1187 | 1188 | void shutdown() 1189 | { 1190 | if (sock != INVALID_SOCKET) 1191 | { 1192 | syscall_shutdown(sock); 1193 | } 1194 | } 1195 | 1196 | ///Close socket on destruction 1197 | ~socket() 1198 | { 1199 | close(); 1200 | 1201 | if (getaddrinfo_results) 1202 | freeaddrinfo(getaddrinfo_results); 1203 | } 1204 | 1205 | ///Select socket with timeout 1206 | socket_status select(int fds, int64_t timeout) 1207 | { 1208 | fd_set fd_read, fd_write, fd_except; 1209 | ; 1210 | struct timeval tv; 1211 | 1212 | tv.tv_sec = static_cast(timeout / 1000); 1213 | tv.tv_usec = 1000 * static_cast(timeout % 1000); 1214 | 1215 | if (fds & fds_read) 1216 | { 1217 | FD_ZERO(&fd_read); 1218 | FD_SET(sock, &fd_read); 1219 | } 1220 | if (fds & fds_write) 1221 | { 1222 | FD_ZERO(&fd_write); 1223 | FD_SET(sock, &fd_write); 1224 | } 1225 | if (fds & fds_except) 1226 | { 1227 | FD_ZERO(&fd_except); 1228 | FD_SET(sock, &fd_except); 1229 | } 1230 | 1231 | int ret = syscall_select(static_cast(sock) + 1, 1232 | fds & fds_read ? &fd_read : NULL, 1233 | fds & fds_write ? &fd_write : NULL, 1234 | fds & fds_except ? &fd_except : NULL, 1235 | &tv); 1236 | if (ret == -1) 1237 | return socket_status::errored; 1238 | else if (ret == 0) 1239 | return socket_status::timed_out; 1240 | return socket_status::valid; 1241 | } 1242 | 1243 | template 1244 | bytes_with_status send(const buffer& buff, const size_t length = buff_size, addr_collection* addr = nullptr) 1245 | { 1246 | assert(buff_size >= length); 1247 | return send(buff.data(), length, addr); 1248 | } 1249 | 1250 | ///Send some bytes through the pipe 1251 | bytes_with_status send(const std::byte* read_buff, size_t length, addr_collection* addr = nullptr) 1252 | { 1253 | auto received_bytes { 0 }; 1254 | if constexpr (sock_proto == protocol::tcp) 1255 | { 1256 | received_bytes = syscall_send(sock, reinterpret_cast(read_buff), static_cast(length), 0); 1257 | } 1258 | #ifdef KISSNET_USE_OPENSSL 1259 | else if constexpr (sock_proto == protocol::tcp_ssl) 1260 | { 1261 | received_bytes = SSL_write(pSSL, reinterpret_cast(read_buff), static_cast(length)); 1262 | } 1263 | #endif 1264 | else if constexpr (sock_proto == protocol::udp) 1265 | { 1266 | if (addr) { 1267 | received_bytes = sendto(sock, reinterpret_cast(read_buff), static_cast(length), 0, reinterpret_cast(&addr->adrinf) , addr->sock_size); 1268 | } else { 1269 | received_bytes = sendto(sock, reinterpret_cast(read_buff), static_cast(length), 0, static_cast(socket_addrinfo->ai_addr), socklen_t(socket_addrinfo->ai_addrlen)); 1270 | } 1271 | } 1272 | 1273 | if (received_bytes < 0) 1274 | { 1275 | if (get_error_code() == EWOULDBLOCK) 1276 | { 1277 | return { 0, socket_status::non_blocking_would_have_blocked }; 1278 | } 1279 | 1280 | return { 0, socket_status::errored }; 1281 | } 1282 | 1283 | return { received_bytes, socket_status::valid }; 1284 | } 1285 | 1286 | ///receive bytes inside the buffer, return the number of bytes you got. You can choose to write inside the buffer at a specific start offset (in number of bytes) 1287 | template 1288 | bytes_with_status recv(buffer& write_buff, size_t start_offset = 0, addr_collection* addr_info = nullptr) 1289 | { 1290 | auto received_bytes = 0; 1291 | if constexpr (sock_proto == protocol::tcp) 1292 | { 1293 | received_bytes = syscall_recv(sock, reinterpret_cast(write_buff.data()) + start_offset, static_cast(buff_size - start_offset), 0); 1294 | } 1295 | #ifdef KISSNET_USE_OPENSSL 1296 | else if constexpr (sock_proto == protocol::tcp_ssl) 1297 | { 1298 | received_bytes = SSL_read(pSSL, reinterpret_cast(write_buff.data()) + start_offset, static_cast(buff_size - start_offset)); 1299 | } 1300 | #endif 1301 | else if constexpr (sock_proto == protocol::udp) 1302 | { 1303 | socket_input_socklen = sizeof socket_input; 1304 | 1305 | received_bytes = ::recvfrom(sock, reinterpret_cast(write_buff.data()) + start_offset, static_cast(buff_size - start_offset), 0, reinterpret_cast(&socket_input), &socket_input_socklen); 1306 | if (addr_info) { 1307 | addr_info->adrinf = socket_input; 1308 | addr_info->sock_size = socket_input_socklen; 1309 | } 1310 | } 1311 | 1312 | if (received_bytes < 0) 1313 | { 1314 | const auto error = get_error_code(); 1315 | if (error == EWOULDBLOCK) 1316 | return { 0, socket_status::non_blocking_would_have_blocked }; 1317 | if (error == EAGAIN) 1318 | return { 0, socket_status::non_blocking_would_have_blocked }; 1319 | return { 0, socket_status::errored }; 1320 | } 1321 | 1322 | if (received_bytes == 0) 1323 | { 1324 | return { received_bytes, socket_status::cleanly_disconnected }; 1325 | } 1326 | 1327 | return { size_t(received_bytes), socket_status::valid }; 1328 | } 1329 | 1330 | ///receive up-to len bytes inside the memory location pointed by buffer 1331 | bytes_with_status recv(std::byte* buffer, size_t len, bool wait = true, addr_collection* addr_info = nullptr) 1332 | { 1333 | auto received_bytes = 0; 1334 | if constexpr (sock_proto == protocol::tcp) 1335 | { 1336 | int flags; 1337 | if (wait) 1338 | flags = MSG_WAITALL; 1339 | else 1340 | { 1341 | #ifdef _WIN32 1342 | flags = 0; // MSG_DONTWAIT not avail on windows, need to make socket nonblockingto emulate 1343 | set_non_blocking(true); 1344 | #else 1345 | flags = MSG_DONTWAIT; 1346 | #endif 1347 | } 1348 | received_bytes = syscall_recv(sock, reinterpret_cast(buffer), static_cast(len), flags); 1349 | #ifdef _WIN32 1350 | set_non_blocking(false); 1351 | #endif 1352 | } 1353 | 1354 | #ifdef KISSNET_USE_OPENSSL 1355 | else if constexpr (sock_proto == protocol::tcp_ssl) 1356 | { 1357 | received_bytes = SSL_read(pSSL, reinterpret_cast(buffer), static_cast(len)); 1358 | } 1359 | #endif 1360 | 1361 | else if constexpr (sock_proto == protocol::udp) 1362 | { 1363 | socket_input_socklen = sizeof socket_input; 1364 | 1365 | received_bytes = ::recvfrom(sock, reinterpret_cast(buffer), static_cast(len), 0, reinterpret_cast(&socket_input), &socket_input_socklen); 1366 | if (addr_info) { 1367 | addr_info->adrinf = socket_input; 1368 | addr_info->sock_size = socket_input_socklen; 1369 | } 1370 | } 1371 | 1372 | if (received_bytes < 0) 1373 | { 1374 | const auto error = get_error_code(); 1375 | if (error == EWOULDBLOCK) 1376 | return { 0, socket_status::non_blocking_would_have_blocked }; 1377 | if (error == EAGAIN) 1378 | return { 0, socket_status::non_blocking_would_have_blocked }; 1379 | return { 0, socket_status::errored }; 1380 | } 1381 | 1382 | if (received_bytes == 0) 1383 | { 1384 | return { received_bytes, socket_status::cleanly_disconnected }; 1385 | } 1386 | 1387 | return { size_t(received_bytes), socket_status::valid }; 1388 | } 1389 | 1390 | ///Return the endpoint where this socket is talking to 1391 | endpoint get_bind_loc() const 1392 | { 1393 | return bind_loc; 1394 | } 1395 | 1396 | ///Return an endpoint that originated the data in the last recv 1397 | endpoint get_recv_endpoint() const 1398 | { 1399 | if constexpr (sock_proto == protocol::tcp) 1400 | { 1401 | return get_bind_loc(); 1402 | } 1403 | if constexpr (sock_proto == protocol::udp) 1404 | { 1405 | return { (sockaddr*)&socket_input }; 1406 | } 1407 | } 1408 | 1409 | ///Return the number of bytes available inside the socket 1410 | size_t bytes_available() const 1411 | { 1412 | static ioctl_setting size = 0; 1413 | const auto status = ioctlsocket(sock, FIONREAD, &size); 1414 | 1415 | if (status < 0) 1416 | { 1417 | kissnet_fatal_error("ioctlsocket status is negative when getting FIONREAD\n"); 1418 | } 1419 | 1420 | return size > 0 ? size : 0; 1421 | } 1422 | 1423 | ///Return the protocol used by this socket 1424 | static protocol get_protocol() 1425 | { 1426 | return sock_proto; 1427 | } 1428 | }; 1429 | 1430 | ///Alias for socket 1431 | using tcp_socket = socket; 1432 | #ifdef KISSNET_USE_OPENSSL 1433 | ///Alias for socket 1434 | using tcp_ssl_socket = socket; 1435 | #endif //KISSNET_USE_OPENSSL 1436 | ///Alias for socket 1437 | using udp_socket = socket; 1438 | } 1439 | 1440 | //cleanup preprocessor macros 1441 | #undef KISSNET_OS_SPECIFIC_PAYLOAD_NAME 1442 | #undef KISSNET_OS_SPECIFIC 1443 | #undef KISSNET_OS_INIT 1444 | #undef kissnet_fatal_error 1445 | 1446 | #endif //KISS_NET --------------------------------------------------------------------------------