├── .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 |
--------------------------------------------------------------------------------
/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 | 
4 |
5 | **Simple C++ wrapper of the [KCP](https://github.com/skywind3000/kcp) protocol.**
6 |
7 | [](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 | [](https://github.com/Unit-X/kcp-cpp/actions?query=workflow%3Akcpcpp_ubuntu)
44 |
45 | [](https://github.com/Unit-X/kcp-cpp/actions?query=workflow%3Akcpcpp_macos)
46 |
47 | [](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
--------------------------------------------------------------------------------