├── .github └── workflows │ └── nbnet.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── WEBRTC.md ├── bin └── github-actions │ ├── build_soak_web.sh │ ├── download_and_build_libdatachannel.sh │ ├── install_emsdk.sh │ └── run_soak.sh ├── docs ├── .nojekyll ├── README.md ├── index.html └── logo │ └── logo.png ├── examples ├── CMakeLists.txt ├── echo │ ├── CMakeLists.txt │ ├── README.md │ ├── client.c │ ├── package-lock.json │ ├── package.json │ ├── server.c │ ├── shared.c │ └── shared.h ├── echo_bytes │ ├── CMakeLists.txt │ ├── README.md │ ├── client.c │ ├── package.json │ ├── server.c │ ├── shared.c │ └── shared.h ├── raylib │ ├── CMakeLists.txt │ ├── README.md │ ├── client.c │ ├── package-lock.json │ ├── package.json │ ├── screenshot.png │ ├── server.c │ ├── shared.c │ ├── shared.h │ └── shell.html └── rpc │ ├── CMakeLists.txt │ ├── README.md │ ├── client.c │ ├── package-lock.json │ ├── package.json │ ├── server.c │ ├── shared.c │ └── shared.h ├── logo ├── .DS_Store └── logo.png ├── nbnet.h ├── net_drivers ├── json.h ├── udp.h ├── webrtc.h ├── webrtc │ ├── js │ │ ├── api.js │ │ ├── game_client.js │ │ ├── game_server.js │ │ ├── index.js │ │ ├── logger.js │ │ ├── nbnet.js │ │ ├── peer.js │ │ └── standalone │ │ │ ├── connection.js │ │ │ ├── signaling_client.js │ │ │ └── signaling_server.js │ ├── package-lock.json │ └── package.json └── webrtc_c.h ├── soak ├── CMakeLists.txt ├── cargs.c ├── cargs.h ├── client.c ├── logging.c ├── logging.h ├── package-lock.json ├── package.json ├── server.c ├── soak.c └── soak.h └── tests ├── CMakeLists.txt ├── CuTest.c ├── CuTest.h ├── message_chunks.c ├── serialization.c └── string_tests.c /.github/workflows/nbnet.yml: -------------------------------------------------------------------------------- 1 | name: nbnet 2 | 3 | env: 4 | EMSDK_VERSION: 3.1.64 5 | 6 | on: 7 | push: 8 | branches: [ master, github-actions, release-2.0 ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | unit-tests-linux: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Install prerequisites 18 | run: sudo apt-get install build-essential cmake -y 19 | - name: Compile tests 20 | run: | 21 | cd tests 22 | cmake . 23 | make 24 | - name: Run tests 25 | run: | 26 | cd tests 27 | ctest 28 | 29 | unit-tests-osx: 30 | runs-on: macos-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Install prerequisites 34 | run: brew install cmake 35 | - name: Compile tests 36 | run: | 37 | cd tests 38 | cmake . 39 | make 40 | - name: Run tests 41 | run: | 42 | cd tests 43 | ctest 44 | 45 | unit-tests-windows: 46 | runs-on: windows-latest 47 | steps: 48 | - uses: actions/checkout@v3 49 | - uses: ilammy/msvc-dev-cmd@v1 50 | - name: Compile tests 51 | run: | 52 | cd tests 53 | cmake -G "NMake Makefiles" . 54 | nmake 55 | # FIXME: string_tests hangs forever 56 | # - name: Run tests 57 | # run: ctest 58 | 59 | soak-test-linux-c: 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v3 63 | - name: Install prerequisites 64 | run: sudo apt-get install build-essential cmake -y 65 | - name: Compile soak test 66 | run: | 67 | cd soak 68 | mkdir build 69 | cd build 70 | cmake -DCMAKE_BUILD_TYPE=Debug .. 71 | make 72 | - name: Run soak test 73 | run: timeout 240 ./bin/github-actions/run_soak.sh 74 | 75 | soak-test-linux-cpp: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v3 79 | - name: Install prerequisites 80 | run: sudo apt-get install build-essential cmake -y 81 | - name: Compile soak test 82 | run: | 83 | cd soak 84 | mkdir build 85 | cd build 86 | cmake -DCMAKE_BUILD_TYPE=Debug -DCPP_COMPILE=ON .. 87 | make 88 | - name: Run soak test 89 | run: timeout 240 ./bin/github-actions/run_soak.sh 90 | 91 | soak-test-linux-emcc: 92 | runs-on: ubuntu-latest 93 | steps: 94 | - uses: actions/checkout@v3 95 | - name: Install prerequisites 96 | run: sudo apt-get install build-essential cmake git python2 g++ -y 97 | - name: Install emsdk 98 | run: ./bin/github-actions/install_emsdk.sh $EMSDK_VERSION 99 | - name: Install nbnet NPM dependencies 100 | run: | 101 | npm update 102 | npm install -g node-pre-gyp 103 | npm install -g node-gyp 104 | cd net_drivers/webrtc 105 | npm install --build-from-resource 106 | - name: Compile soak test 107 | run: ./bin/github-actions/build_soak_web.sh 108 | - name: Run soak test 109 | run: | 110 | cd emsdk 111 | source ./emsdk_env.sh 112 | cd .. 113 | WEBRTC=1 timeout 240 ./bin/github-actions/run_soak.sh 114 | 115 | soak-test-osx-c: 116 | runs-on: macos-latest 117 | steps: 118 | - uses: actions/checkout@v3 119 | - name: Install prerequisites 120 | run: brew install cmake coreutils 121 | - name: Compile soak test 122 | run: | 123 | cd soak 124 | mkdir build 125 | cd build 126 | cmake -DCMAKE_BUILD_TYPE=Debug .. 127 | make 128 | - name: Run soak test 129 | run: gtimeout 240 ./bin/github-actions/run_soak.sh 130 | 131 | soak-test-osx-cpp: 132 | runs-on: macos-latest 133 | steps: 134 | - uses: actions/checkout@v3 135 | - name: Install prerequisites 136 | run: brew install cmake coreutils 137 | - name: Compile soak test 138 | run: | 139 | cd soak 140 | mkdir build 141 | cd build 142 | cmake -DCMAKE_BUILD_TYPE=Debug -DCPP_COMPILE=ON .. 143 | make 144 | - name: Run soak test 145 | run: gtimeout 240 ./bin/github-actions/run_soak.sh 146 | 147 | soak-test-osx-emcc: 148 | runs-on: macos-latest 149 | steps: 150 | - uses: actions/checkout@v3 151 | - name: Install prerequisites 152 | run: brew install cmake coreutils 153 | - name: Install emsdk 154 | run: ./bin/github-actions/install_emsdk.sh $EMSDK_VERSION 155 | - name: Install nbnet NPM dependencies 156 | run: | 157 | npm update 158 | npm install -g node-pre-gyp 159 | npm install -g node-gyp 160 | cd net_drivers/webrtc 161 | npm install --build-from-resource 162 | - name: Compile soak test 163 | run: ./bin/github-actions/build_soak_web.sh 164 | - name: Run soak test 165 | run: | 166 | cd emsdk 167 | source ./emsdk_env.sh 168 | cd .. 169 | WEBRTC=1 gtimeout 240 ./bin/github-actions/run_soak.sh 170 | 171 | soak-test-windows-c: 172 | runs-on: windows-latest 173 | steps: 174 | - uses: actions/checkout@v3 175 | - uses: ilammy/msvc-dev-cmd@v1 176 | - name: Compile soak test 177 | run: | 178 | cd soak 179 | mkdir build 180 | cd build 181 | cmake -G "NMake Makefiles" .. 182 | nmake 183 | - name: Run soak test 184 | run: ./bin/github-actions/run_soak.sh 185 | 186 | soak-test-linux-webrtc-native: 187 | runs-on: ubuntu-latest 188 | steps: 189 | - uses: actions/checkout@v3 190 | - name: Install prerequisites 191 | run: sudo apt-get install build-essential cmake git -y 192 | - name: Download and build libdatachannel 193 | run: ./bin/github-actions/download_and_build_libdatachannel.sh 194 | - name: Install emsdk 195 | run: ./bin/github-actions/install_emsdk.sh $EMSDK_VERSION 196 | - name: Install nbnet NPM dependencies 197 | run: | 198 | npm update 199 | npm install -g node-pre-gyp 200 | npm install -g node-gyp 201 | cd net_drivers/webrtc 202 | npm install --build-from-resource 203 | - name: Compile soak test (native) 204 | run: | 205 | cd soak 206 | mkdir build 207 | cd build 208 | cmake -DLIBDATACHANNEL_LIBRARY_PATH=${{ github.workspace }}/libdatachannel/build/libdatachannel.so -DLIBDATACHANNEL_INCLUDE_PATH=${{ github.workspace }}/libdatachannel/include -DWEBRTC_NATIVE=ON .. 209 | make 210 | - name: Compile soak test (web) 211 | run: ./bin/github-actions/build_soak_web.sh 212 | - name: Run soak test 213 | run: | 214 | cd emsdk 215 | source ./emsdk_env.sh 216 | cd .. 217 | WEBRTC_NATIVE=1 timeout 240 ./bin/github-actions/run_soak.sh 218 | 219 | compile-examples-linux-c: 220 | runs-on: ubuntu-latest 221 | steps: 222 | - uses: actions/checkout@v3 223 | - name: Install prerequisites 224 | run: sudo apt-get install build-essential cmake -y 225 | - name: Compile examples 226 | run: | 227 | cd examples 228 | cmake . 229 | make 230 | 231 | compile-examples-linux-cpp: 232 | runs-on: ubuntu-latest 233 | steps: 234 | - uses: actions/checkout@v3 235 | - name: Install prerequisites 236 | run: sudo apt-get install build-essential cmake -y 237 | - name: Compile examples 238 | run: | 239 | cd examples 240 | cmake -DCPP_COMPILE=ON . 241 | make 242 | 243 | compile-examples-osx-c: 244 | runs-on: macos-latest 245 | steps: 246 | - uses: actions/checkout@v3 247 | - name: Install prerequisites 248 | run: brew install cmake 249 | - name: Compile examples 250 | run: | 251 | cd examples 252 | cmake . 253 | make 254 | 255 | compile-examples-osx-cpp: 256 | runs-on: macos-latest 257 | steps: 258 | - uses: actions/checkout@v3 259 | - name: Install prerequisites 260 | run: brew install cmake 261 | - name: Compile examples 262 | run: | 263 | cd examples 264 | cmake -DCPP_COMPILE=ON . 265 | make 266 | 267 | compile-examples-windows-c: 268 | runs-on: windows-latest 269 | steps: 270 | - uses: actions/checkout@v3 271 | - uses: ilammy/msvc-dev-cmd@v1 272 | - name: Compile examples 273 | run: | 274 | cd examples 275 | cmake -G "NMake Makefiles" . 276 | nmake 277 | 278 | compile-examples-windows-cpp: 279 | runs-on: windows-latest 280 | steps: 281 | - uses: actions/checkout@v3 282 | - uses: ilammy/msvc-dev-cmd@v1 283 | - name: Compile examples 284 | run: | 285 | cd examples 286 | cmake -G "NMake Makefiles" -DCPP_COMPILE=ON . 287 | nmake 288 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.wasm 3 | .vscode 4 | .ccls 5 | **/node_modules 6 | .idea 7 | CMakeLists.txt.user 8 | CMakeCache.txt 9 | CMakeFiles 10 | CMakeScripts 11 | Testing 12 | Makefile 13 | cmake_install.cmake 14 | install_manifest.txt 15 | compile_commands.json 16 | CTestTestfile.cmake 17 | _deps 18 | build 19 | build_web 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | 4 | matrix: 5 | include: 6 | - language: c 7 | name: "C/Linux" 8 | os: linux 9 | install: 10 | - chmod +x ./bin/travis/compile_soak.sh 11 | - chmod +x ./bin/travis/run_soak.sh 12 | script: 13 | - CPP_COMPILE=OFF ./bin/travis/compile_soak.sh 14 | - ./bin/travis/run_soak.sh 15 | 16 | - language: c 17 | name: "C/OSX" 18 | os: osx 19 | install: 20 | - chmod +x ./bin/travis/compile_soak.sh 21 | - chmod +x ./bin/travis/run_soak.sh 22 | script: 23 | - CPP_COMPILE=OFF ./bin/travis/compile_soak.sh 24 | - ./bin/travis/run_soak.sh 25 | 26 | - language: c 27 | name: "C/Windows (MinGW)" 28 | os: windows 29 | env: 30 | - CMAKE_GENERATOR="MinGW Makefiles" 31 | install: 32 | - chmod +x ./bin/travis/compile_soak.sh 33 | - chmod +x ./bin/travis/run_soak.sh 34 | script: 35 | - CPP_COMPILE=OFF ./bin/travis/compile_soak.sh 36 | - ./bin/travis/run_soak.sh 37 | 38 | - language: c 39 | name: "C/Windows (MSVC)" 40 | os: windows 41 | env: 42 | - CMAKE_GENERATOR="Visual Studio 16 2019" 43 | install: 44 | - chmod +x ./bin/travis/compile_soak.sh 45 | - chmod +x ./bin/travis/run_soak.sh 46 | - choco install visualstudio2019buildtools --package-parameters "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64" 47 | script: 48 | - CPP_COMPILE=OFF ./bin/travis/compile_soak.sh 49 | # - ./bin/travis/run_soak.sh 50 | 51 | - language: cpp 52 | name: "CPP/Linux" 53 | os: linux 54 | install: 55 | - chmod +x ./bin/travis/compile_soak.sh 56 | - chmod +x ./bin/travis/run_soak.sh 57 | script: 58 | - CPP_COMPILE=ON ./bin/travis/compile_soak.sh 59 | - ./bin/travis/run_soak.sh 60 | 61 | - language: cpp 62 | name: "CPP/OSX" 63 | os: osx 64 | install: 65 | - chmod +x ./bin/travis/compile_soak.sh 66 | - chmod +x ./bin/travis/run_soak.sh 67 | script: 68 | - CPP_COMPILE=ON ./bin/travis/compile_soak.sh 69 | - ./bin/travis/run_soak.sh 70 | 71 | - language: cpp 72 | name: "CPP/Windows (MinGW)" 73 | os: windows 74 | env: 75 | - CMAKE_GENERATOR="MinGW Makefiles" 76 | install: 77 | - chmod +x ./bin/travis/compile_soak.sh 78 | - chmod +x ./bin/travis/run_soak.sh 79 | script: 80 | - CPP_COMPILE=ON ./bin/travis/compile_soak.sh 81 | - ./bin/travis/run_soak.sh 82 | 83 | - language: cpp 84 | name: "CPP/Windows (MSVC)" 85 | os: windows 86 | env: 87 | - CMAKE_GENERATOR="Visual Studio 16 2019" 88 | install: 89 | - chmod +x ./bin/travis/compile_soak.sh 90 | - chmod +x ./bin/travis/run_soak.sh 91 | - choco install visualstudio2019buildtools --package-parameters "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64" 92 | script: 93 | - CPP_COMPILE=ON ./bin/travis/compile_soak.sh 94 | # - ./bin/travis/run_soak.sh 95 | 96 | - language: node_js 97 | name: "C/Emscripten" 98 | node_js: 99 | - node 100 | sudo: required 101 | services: 102 | - docker 103 | before_install: 104 | - docker run -dit --name emscripten -v $(pwd):/nbnet trzeci/emscripten:latest bash 105 | script: 106 | - docker exec -it -e EMSCRIPTEN=1 -e CPP_COMPILE=OFF emscripten /nbnet/bin/travis/compile_soak.sh 107 | - docker exec -it -e EMSCRIPTEN=1 -e CPP_COMPILE=OFF emscripten /nbnet/bin/travis/run_soak.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2024 BIAGINI Nathan 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nbnet 2 | 3 | ![logo](logo/logo.png "Logo") 4 | 5 | ![nbnet](https://github.com/nathhB/nbnet/actions/workflows/nbnet.yml/badge.svg) 6 | [![CodeFactor](https://www.codefactor.io/repository/github/nathhb/nbnet/badge/master)](https://www.codefactor.io/repository/github/nathhb/nbnet/overview/master) 7 | [![Docs site](https://img.shields.io/badge/docs-GitHub_Pages-blue)](https://nathhb.github.io/nbnet) 8 | 9 | [![](https://dcbadge.vercel.app/api/server/P9N7fy677D)](https://discord.gg/P9N7fy677D) 10 | 11 | nbnet is a single header C (C99) library designed to implement client-server architecture, more precisely for online video games. The library is based on this [great series of articles](https://gafferongames.com/) by Glenn Fiedler. 12 | 13 | nbnet can target different protocols such as UDP or [WebRTC](WEBRTC.md) through "drivers" (see below for more information). 14 | 15 | The API is meant to be as straightforward as possible and relies on event polling, making it easy to integrate into a game loop. 16 | 17 | **Disclaimer**: nbnet is in the early stages of its development and is, first and foremost, a learning project of mine as I explore online game development. If you are looking for a battle-tested, production-ready library, this is probably not the one. 18 | 19 | You can see nbnet in action [in this video](https://www.youtube.com/watch?v=BJl_XN3QJhQ&ab_channel=NathanBIAGINI). 20 | 21 | If you want to discuss the library or need help, join the [nbnet's discord server](https://discord.gg/esR8FSyPnF). 22 | 23 | ## Features 24 | 25 | - Connection management 26 | - Sending/Receiving both reliable ordered and unreliable ordered messages 27 | - Sending/Receiving messages larger than the MTU through nbnet's message fragmentation system 28 | - Bit-level serialization (for bandwidth optimization): integers (signed and unsigned), floats, booleans, and byte arrays 29 | - Network conditions simulation: ping, jitter, packet loss, packet duplication, and out-of-order packets) 30 | - Network statistics: ping, bandwidth (upload and download) and packet loss 31 | - Web (WebRTC) support (both natively and through WASM using [emscripten](https://emscripten.org/docs/introducing_emscripten/about_emscripten.html)) 32 | 33 | ## Thanks 34 | 35 | the native WebRTC driver relies on: 36 | 37 | - [libdatachannel](https://github.com/paullouisageneau/libdatachannel) 38 | - [json.h](https://github.com/sheredom/json.h) 39 | 40 | ## Made with nbnet 41 | 42 | ### Boomshakalaka 43 | 44 | A fun action game that runs into a web browser, by Duncan Stead (@duncanstead86). 45 | 46 | [See on YouTube](https://www.youtube.com/watch?v=SJHvXV03uwQ). 47 | 48 | ### nbBR 49 | 50 | A WIP battle royal game playable in a web browser. 51 | 52 | [See on YouTube](https://youtube.com/playlist?list=PLgcJGzE_fX4criMxQAw3pm24RQYMYRLEI) 53 | 54 | ### Llamageddon 55 | 56 | An online multiplayer RTS made for the Ludum Dare 49. 57 | 58 | https://ldjam.com/events/ludum-dare/49/llamageddon 59 | 60 | ### nb_tanks 61 | 62 | A little online tank game prototype. 63 | 64 | [See on GitHub](https://github.com/nathhB/nb_tanks) 65 | 66 | ## Bindings 67 | 68 | A list of user-contributed bindings (they are not officially supported, so they may be outdated): 69 | 70 | - [nbnet-sunder](https://github.com/ashn-dot-dev/nbnet-sunder) by [@ashn](https://github.com/ashn-dot-dev) ([Sunder](https://github.com/ashn-dot-dev/sunder) is a modest systems programming language for Unix-like platforms) 71 | 72 | ## Drivers 73 | 74 | nbnet does not directly implement any low-level "transport" code and relies on *drivers*. 75 | 76 | A driver is a set of function definitions that live outside the nbnet header and provide a transport layer implementation for nbnet used to send and receive packets. 77 | 78 | nbnet comes with three ready-to-use drivers: 79 | 80 | - UDP: works with a single UDP socket, designed for desktop games 81 | - WebRTC (WASM): works with a single unreliable/unordered data channel, implemented in JS using the emscripten API 82 | - WebRTC (Native): works the same way as the WASM WebRTC driver but can be natively compiled 83 | 84 | ## How to use 85 | 86 | In *exactly one* of your source files do: 87 | 88 | ``` 89 | #define NBNET_IMPL 90 | 91 | #include "nbnet.h" 92 | ``` 93 | 94 | Provide a driver implementation. For instance, for the UDP driver, just add: 95 | 96 | ``` 97 | #include "net_drivers/udp.h" 98 | ``` 99 | 100 | after including the nbnet header in the same source file where you defined `NBNET_IMPL`. 101 | 102 | nbnet does not provide any logging capabilities so you have to provide your own: 103 | 104 | ``` 105 | #define NBN_LogInfo(...) SomeLoggingFunction(__VA_ARGS__) 106 | #define NBN_LogError(...) SomeLoggingFunction(__VA_ARGS__) 107 | #define NBN_LogDebug(...) SomeLoggingFunction(__VA_ARGS__) 108 | #define NBN_LogTrace(...) SomeLoggingFunction(__VA_ARGS__) 109 | #define NBN_LogWarning(...) SomeLoggingFunction(__VA_ARGS__) 110 | ``` 111 | 112 | For memory management, nbnet uses `malloc`, `realloc` and `free`. You can redefine them if needed: 113 | 114 | ``` 115 | #define NBN_Allocator malloc 116 | #define NBN_Reallocator realloc 117 | #define NBN_Deallocator free 118 | ``` 119 | 120 | All set, from here, I suggest you hop into the examples. If you are interested in WebRTC, go [here](WEBRTC.md). 121 | 122 | ## Byte arrays 123 | 124 | nbnet comes with a primitive bit-level serialization system; but, if you want to use your own serialization solution, nbnet lets you send and receive raw byte arrays. 125 | 126 | See the [echo_bytes](https://github.com/nathhB/nbnet/tree/master/examples/echo_bytes) example. 127 | -------------------------------------------------------------------------------- /WEBRTC.md: -------------------------------------------------------------------------------- 1 | # WebRTC 2 | 3 | nbnet provides two different WebRTC drivers, WASM and native. The WASM driver has to be compiled with emscripten and can be used to target web browsers or Node JS. The native driver can be compiled as a native application. 4 | 5 | If you want to build an online game that can run in a web browser, I recommend using the WASM driver for the client and the native driver for the server. 6 | 7 | Please refer to the echo example for a concrete demonstration of how to use these two drivers. 8 | 9 | ## Building (WASM) 10 | 11 | If you want to target WASM, you'll need to compile your code with *emscripten*. You can either use the `emcc` command directly or use CMake (refer to the examples to see how to set it up). 12 | 13 | The following emscripten options are mandatory and must always be added to your compilation command line or CMake script. 14 | 15 | *emscripten* does not provide a C API for WebRTC, only a JS one. nbnet provides a wrapper around it so you don't have to write any JS code (oof!). All you have to do is compile with: 16 | 17 | `--js-library "net_drivers/webrtc/js/api.js"` 18 | 19 | The nbnet JS API uses a bunch of asynchronous functions that you'll need to let *emscripten* know about: 20 | 21 | `-s ASYNCIFY` 22 | 23 | `-s ASYNCIFY_IMPORTS="[\"__js_game_client_start\", \"__js_game_client_close\", \"__js_game_server_start\\"]"` 24 | 25 | nbnet network conditions simulation runs in a separate thread so if you want to use it you'll need to compile with: 26 | 27 | `-s USE_PTHREADS=1` 28 | 29 | If you want to run your code in a web browser, you'll also need to provide a shell file: 30 | 31 | `--shell-file ` 32 | 33 | To learn more about shell files: https://emscripten.org/docs/tools_reference/emcc.html 34 | 35 | You can also look at the `shell.html` from the raylib example. 36 | 37 | For more information: https://emscripten.org/docs/tools_reference/emcc.html 38 | 39 | ## Building (Native) 40 | 41 | The Native WebRTC driver depends on [libdatachannel](https://github.com/paullouisageneau/libdatachannel). Please follow the building instructions in the library repository. 42 | 43 | ## NodeJS 44 | 45 | If you decide to go with WASM for your server, you'll need to run it in a *NodeJS* server. You can get *NodeJS* from [here](https://nodejs.org/en/download/). 46 | 47 | Once it's installed, you'll need to create a `package.json` file. Check out the *echo* and *raylib* examples to see what this file should look like. (For more information: https://docs.npmjs.com/creating-a-package-json-file) 48 | 49 | To run your server, you need to install the required *NodeJS* packages by running: 50 | 51 | `npm install` 52 | 53 | from the directory containing your `package.json` file. 54 | 55 | Then to run your server: 56 | 57 | `node server.js` 58 | 59 | `server.js` is the JS file generated by *emscripten*. 60 | 61 | ## Web browser 62 | 63 | Unless your client is a non-graphical application, you'll probably want your client to run in a web browser. 64 | 65 | With the correct options *emscripten* will output an HTML file, from here, all you need to do is run an HTTP server that serves 66 | this file and open it in your web browser. 67 | 68 | For testing purposes, I recommend using Python [SimpleHTTPServer](https://docs.python.org/2/library/simplehttpserver.html). 69 | 70 | Just run: 71 | 72 | `python -m SimpleHTTPServer 8000` 73 | 74 | in the directory containing your HTML file; then, open `http://localhost:8000` in your web browser and open your client HTML file. 75 | 76 | One significant difference between running JS code in a web browser compared to running it in *NodeJS* is that you cannot use the *NodeJS* packaging system. nbnet's WebRTC code relies on *NodeJS* packages, so, for nbnet to run in a web browser we need to "bundle" those packages into something that can be used by a web browser. 77 | 78 | I recommend using [browserify](https://github.com/browserify/browserify). 79 | 80 | To bundle packages with browserify, you need to first install the required *NodeJS* packages by running: 81 | 82 | `npm install` 83 | 84 | from the directory containing the same `package.json` file used by your server. 85 | 86 | Then you can run: 87 | 88 | `browserify net_drivers/webrtc/js/nbnet.js -o nbnet_bundle.js` 89 | 90 | and include the generated `nbnet_bundle.js` script in your HTML shell file: 91 | 92 | `` 93 | 94 | See the *raylib* example to see how you can integrate this operation into a CMake script. 95 | -------------------------------------------------------------------------------- /bin/github-actions/build_soak_web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd emsdk 4 | source ./emsdk_env.sh 5 | cd ../soak 6 | mkdir build_web 7 | cd build_web 8 | emcmake cmake .. 9 | make 10 | cd .. 11 | npm install 12 | -------------------------------------------------------------------------------- /bin/github-actions/download_and_build_libdatachannel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git clone https://github.com/paullouisageneau/libdatachannel.git 4 | cd libdatachannel 5 | git submodule update --init --recursive --depth 1 6 | cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -DCMAKE_BUILD_TYPE=Release 7 | cd build 8 | make -j2 9 | -------------------------------------------------------------------------------- /bin/github-actions/install_emsdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git clone https://github.com/emscripten-core/emsdk.git 4 | cd emsdk 5 | ./emsdk install $1 6 | ./emsdk activate $1 7 | source ./emsdk_env.sh 8 | emcc -v # make sure emcc is available 9 | -------------------------------------------------------------------------------- /bin/github-actions/run_soak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PACKET_LOSS=0.3 4 | PACKET_DUPLICATION=0.2 5 | PING=0.15 6 | JITTER=0.1 7 | CHANNEL_COUNT=3 8 | MESSAGE_COUNT=500 9 | NODE_CMD="$EMSDK_NODE" 10 | 11 | run_client () { 12 | node_client=$1 13 | echo "Running soak client (run in mode: $node_client)..." 14 | 15 | if [ $node_client -eq 1 ] 16 | then 17 | # WASM WebRTC client 18 | $NODE_CMD build_web/client.js --message_count=$MESSAGE_COUNT --channel_count=$CHANNEL_COUNT --packet_loss=$PACKET_LOSS --packet_duplication=$PACKET_DUPLICATION --ping=$PING --jitter=$JITTER &> soak_cli_out 19 | elif [ $node_client -eq 2 ] 20 | then 21 | # native WebRTC client 22 | ./build/client --webrtc --message_count=$MESSAGE_COUNT --channel_count=$CHANNEL_COUNT --packet_loss=$PACKET_LOSS --packet_duplication=$PACKET_DUPLICATION --ping=$PING --jitter=$JITTER &> soak_cli_out 23 | else 24 | # UDP client 25 | ./build/client --message_count=$MESSAGE_COUNT --channel_count=$CHANNEL_COUNT --packet_loss=$PACKET_LOSS --packet_duplication=$PACKET_DUPLICATION --ping=$PING --jitter=$JITTER &> soak_cli_out 26 | fi 27 | 28 | RESULT=$? 29 | 30 | # when running the soak test in the latest version of emscripten with node 16 31 | # the client aborts at the end when calling emscripten_force_exit 32 | # I could not figure out why, hence the condition 33 | [[ $node_client -eq 1 ]] && EXPECTED_RESULT=7 || EXPECTED_RESULT=0 34 | 35 | if [ $RESULT -eq $EXPECTED_RESULT ] 36 | then 37 | echo "Soak test completed with success!" 38 | echo "Printing the end of client logs..." 39 | 40 | tail -n 150 soak_cli_out 41 | 42 | return 0 43 | else 44 | echo "Soak test failed! (code: $RESULT)" 45 | echo "Printing the end of client logs..." 46 | tail -n 150 soak_cli_out 47 | echo "Printing the end of server logs..." 48 | tail -n 150 soak_serv_out 49 | 50 | return 1 51 | fi 52 | } 53 | 54 | exit_soak () { 55 | kill -SIGINT $SERV_PID 2> /dev/null 56 | 57 | exit $1 58 | } 59 | 60 | cd soak 61 | echo "Starting soak server..." 62 | 63 | if [ -n "$WEBRTC" ] 64 | then 65 | $NODE_CMD build_web/server.js --channel_count=$CHANNEL_COUNT --packet_loss=$PACKET_LOSS --packet_duplication=$PACKET_DUPLICATION --ping=$PING --jitter=$JITTER &> soak_serv_out & 66 | else 67 | ./build/server --channel_count=$CHANNEL_COUNT --packet_loss=$PACKET_LOSS --packet_duplication=$PACKET_DUPLICATION --ping=$PING --jitter=$JITTER &> soak_serv_out & 68 | fi 69 | 70 | if [ $? -eq 0 ] 71 | then 72 | SERV_PID=$! 73 | 74 | echo "Server started (PID: $SERV_PID)" 75 | echo "Running soak test..." 76 | else 77 | echo "Failed to start soak server!" 78 | exit 1 79 | fi 80 | 81 | sleep 3 82 | 83 | if [ -n "$WEBRTC" ] 84 | then 85 | run_client 1 86 | 87 | exit_soak $? 88 | else 89 | if [ -n "$WEBRTC_NATIVE" ] 90 | then 91 | # run a UDP client, a webrtc WASM client (emscripten) and a native webrtc client (all connecting to the same server) 92 | 93 | if run_client 0 && run_client 1 && run_client 2; then 94 | exit_soak 0 95 | else 96 | exit_soak 1 97 | fi 98 | fi 99 | fi 100 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/nbnet/79a3bed112037377ed37ac129218efc512ff565a/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # nbnet 2 | 3 | ![logo](logo/logo.png "Logo") 4 | 5 | [![Build Status](https://app.travis-ci.com/nathhB/nbnet.svg?branch=master)](https://app.travis-ci.com/nathhB/nbnet) 6 | [![CodeFactor](https://www.codefactor.io/repository/github/nathhb/nbnet/badge/master)](https://www.codefactor.io/repository/github/nathhb/nbnet/overview/master) 7 | 8 | [![](https://dcbadge.vercel.app/api/server/P9N7fy677D)](https://discord.gg/P9N7fy677D) 9 | 10 | nbnet is a single header C (C99) library to implement client-server network code for games. It is more precisely designed for fast-paced action games. 11 | 12 | nbnet is based on this [great series of articles](https://gafferongames.com/) by Glenn Fiedler. 13 | 14 | nbnet aims to be as easy to use as possible. nbnet's API is *friendly* and goes *straight to the point*; it relies on event polling which makes it easy to integrate into a game loop. 15 | 16 | **Disclaimer**: nbnet is in the early stages of its development and is, first and foremost, a learning project of mine as I explore online game development. If you are looking for a professional production-ready library, this is not the one. 17 | 18 | You can see nbnet in action [in this video](https://www.youtube.com/watch?v=BJl_XN3QJhQ&ab_channel=NathanBIAGINI). 19 | 20 | If you want to discuss the library, you can join the [nbnet's discord server](https://discord.gg/esR8FSyPnF). 21 | 22 | ## Documentation 23 | 24 | docsforge appears to be dead so I need to find another documentation solution :( 25 | 26 | ## Features 27 | 28 | - Connection management 29 | - Sending/Receiving both reliable ordered and unreliable ordered messages 30 | - Sending/Receiving messages larger than the MTU (using nbnet's message fragmentation) 31 | - Bit-level serialization (for bandwidth optimization): integers (signed and unsigned), floats, booleans, and byte arrays 32 | - Network conditions simulation: ping, jitter, packet loss, packet duplication, and out of order packets) 33 | - Network statistics: ping, bandwidth (upload and download) and packet loss 34 | - Web (WebRTC) support (powered by [emscripten](https://emscripten.org/docs/introducing_emscripten/about_emscripten.html)) 35 | - Encrypted and authenticated packets 36 | 37 | ## Thanks 38 | 39 | nbnet encryption and packet authentication would not have been possible without those three open-source libraries: 40 | 41 | - [tiny-ECDH](https://github.com/kokke/tiny-ECDH-c) 42 | - [tiny-AES](https://github.com/kokke/tiny-AES-c) 43 | - [poly1305-donna](https://github.com/floodyberry/poly1305-donna) 44 | 45 | ## Made with nbnet 46 | 47 | ### Boomshakalaka 48 | 49 | A fun action game that runs into a web browser, by Duncan Stead (@duncanstead86). 50 | 51 | [See on YouTube](https://www.youtube.com/watch?v=SJHvXV03uwQ). 52 | 53 | ### nbBR 54 | 55 | A WIP battle royal game playable in a web browser. 56 | 57 | [See on YouTube](https://youtube.com/playlist?list=PLgcJGzE_fX4criMxQAw3pm24RQYMYRLEI) 58 | 59 | ### Llamageddon 60 | 61 | An online multiplayer RTS made for the Ludum Dare 49. 62 | 63 | https://ldjam.com/events/ludum-dare/49/llamageddon 64 | 65 | ### nb_tanks 66 | 67 | A little online tank game prototype. 68 | 69 | [See on GitHub](https://github.com/nathhB/nb_tanks) 70 | 71 | ## Drivers 72 | 73 | nbnet does not directly implement any low level "transport" code and rely on *drivers*. 74 | 75 | A driver is a set of function definitions that live outside the nbnet header and provide a transport layer implementation for nbnet used to send and receive packets. 76 | 77 | nbnet comes with two ready to use drivers: 78 | 79 | - UDP : work with a single UDP socket, designed for desktop games 80 | - WebRTC : work with a single unreliable/unordered data channel, designed for web browser games 81 | 82 | ## Portability 83 | 84 | nbnet is developed with portability in mind. I tested (and will continue to do so) the library on the following platforms: 85 | 86 | - Windows 87 | - OSX 88 | - Linux 89 | - Web (Chrome/Firefox/Microsoft Edge/Brave) 90 | 91 | ## How to use 92 | 93 | In *exactly one* of your source file do: 94 | 95 | ``` 96 | #define NBNET_IMPL 97 | 98 | #include "nbnet.h" 99 | ``` 100 | 101 | Provide a driver implementation. For the UDP driver, just add: 102 | 103 | ``` 104 | #include "net_drivers/udp.h" 105 | ``` 106 | 107 | after including the nbnet header in the same source file where you defined `NBNET_IMPL`. 108 | 109 | nbnet does not provide any logging capacibilities so you have to provide your own: 110 | 111 | ``` 112 | #define NBN_LogInfo(...) SomeLoggingFunction(__VA_ARGS__) 113 | #define NBN_LogError(...) SomeLoggingFunction(__VA_ARGS__) 114 | #define NBN_LogDebug(...) SomeLoggingFunction(__VA_ARGS__) 115 | #define NBN_LogTrace(...) SomeLoggingFunction(__VA_ARGS__) 116 | ``` 117 | 118 | For memory management, nbnet uses `malloc`, `realloc` and `free`. You can redefine it using the following macros: 119 | 120 | ``` 121 | #define NBN_Allocator malloc 122 | #define NBN_Reallocator realloc 123 | #define NBN_Deallocator free 124 | ``` 125 | 126 | All set, from here, I suggest you hop into the examples. If you are interested in using the WebRTC driver, read below. 127 | 128 | ## Byte arrays 129 | 130 | nbnet comes with a primitive bit-level serialization system; but, if you want to use your own serialization solution, nbnet lets you send and receive raw byte arrays. 131 | 132 | See the [echo_bytes](https://github.com/nathhB/nbnet/tree/master/examples/echo_bytes) example. 133 | 134 | ## WebRTC 135 | 136 | nbnet lets you implement web browser online games in C without writing any JS code. 137 | 138 | To do that, you need to compile your code with *emscripten*. You can either use the 139 | `emcc` command directly or use CMake, the examples demonstrate how to use both. 140 | 141 | The following emscripten options are mandatory and must always be added to your compilation command line or CMake script. 142 | 143 | *emscripten* does not provide a C API for WebRTC, only a JS one. nbnet provides a wrapper around it so you don't have to write any JS code (oof!). All you have to is compile with: 144 | 145 | `--js-library "net_drivers/webrtc/js/api.js"` 146 | 147 | The nbnet JS API uses a bunch of asynchronous functions that you need to let *emscripten* know about: 148 | 149 | `-s ASYNCIFY` 150 | 151 | `-s ASYNCIFY_IMPORTS="[\"__js_game_client_start\", \"__js_game_client_close\", \"__js_game_server_start\\"]"` 152 | 153 | nbnet network conditions simulation run in a separate thread so if you want to use it you need to compile with: 154 | 155 | `-s USE_PTHREADS=1` 156 | 157 | If you want to run your code in a web browser, you need to provide a shell file: 158 | 159 | `--shell-file ` 160 | 161 | To learn about shell files: https://emscripten.org/docs/tools_reference/emcc.html 162 | 163 | You can also look at the `shell.html` from the raylib example. 164 | 165 | Apart from that, you probably want to add: 166 | 167 | `-s ALLOW_MEMORY_GROWTH=1` and `-s EXIT_RUNTIME=1` 168 | 169 | For more information: https://emscripten.org/docs/tools_reference/emcc.html 170 | 171 | ### NodeJS 172 | 173 | Most of the time, you want your server code to run in a *NodeJS* server. You can get *NodeJS* it from [here](https://nodejs.org/en/download/). 174 | 175 | Once it's installed, you need to create a `package.json` file. Check out the *echo* and *raylib* examples to see what this file looks like. (For more information: https://docs.npmjs.com/creating-a-package-json-file) 176 | 177 | To run your server, you need to install the required *NodeJS* packages by running: 178 | 179 | `npm install` 180 | 181 | from the directory containing your `package.json` file. 182 | 183 | Then to run your server: 184 | 185 | `node server.js` 186 | 187 | `server.js` being the JS file generated by *emscripten*. 188 | 189 | ### Web browser 190 | 191 | Unless your client is a non-graphical application, you want your client code to run in a web browser. 192 | 193 | With the correct options *emscripten* will output an HTML file. From here, all you need to do is run an HTTP server that serves 194 | this file and open it in your web browser. 195 | 196 | For testing purposes, I recommend using Python [SimpleHTTPServer](https://docs.python.org/2/library/simplehttpserver.html). 197 | 198 | Just run: 199 | 200 | `python -m SimpleHTTPServer 8000` 201 | 202 | in the directory containing your HTML file; then, open `http://localhost:8000` in your web browser and open your client HTML file. 203 | 204 | One significant difference with running JS code in a web browser compared to running it in *NodeJS* is that you cannot use the *NodeJS* packaging system. nbnet's WebRTC code relies on *NodeJS* packages, so, for nbnet to run in a web browser we need to "bundle" those packages into something that can be used by a web browser. 205 | 206 | I recommend using [browserify](https://github.com/browserify/browserify). 207 | 208 | Once installed you can run: 209 | 210 | `browserify net_drivers/webrtc/js/nbnet.js -o nbnet_bundle.js` 211 | 212 | and include the generated `nbnet_bundle.js` script to your HTML shell file: 213 | 214 | `` 215 | 216 | See the *raylib* example to see this operation integrated into a CMake script. 217 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nbnet documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/nbnet/79a3bed112037377ed37ac129218efc512ff565a/docs/logo/logo.png -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(nbnet_examples) 3 | 4 | add_subdirectory(echo) 5 | add_subdirectory(echo_bytes) 6 | 7 | if (NOT DEFINED CPP_COMPILE) 8 | # not supported in CPP 9 | add_subdirectory(rpc) 10 | endif (NOT DEFINED CPP_COMPILE) 11 | -------------------------------------------------------------------------------- /examples/echo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(echo) 4 | 5 | option(CPP_COMPILE OFF) 6 | option(ENABLE_TLS OFF) 7 | option(WEBRTC_NATIVE OFF) 8 | 9 | # allow to compile as cpp 10 | if (CPP_COMPILE) 11 | file(GLOB_RECURSE CFILES "${CMAKE_SOURCE_DIR}/*.c") 12 | SET_SOURCE_FILES_PROPERTIES(${CFILES} PROPERTIES LANGUAGE CXX) 13 | set (CMAKE_CXX_STANDARD 20) 14 | endif (CPP_COMPILE) 15 | 16 | unset(CPP_COMPILE CACHE) 17 | 18 | add_compile_options(-Wall) 19 | 20 | if(CMAKE_COMPILER_IS_GNUCXX) 21 | add_compile_options(-Wextra -Wpedantic) 22 | endif (CMAKE_COMPILER_IS_GNUCXX) 23 | 24 | add_executable(echo_client client.c shared.c) 25 | add_executable(echo_server server.c shared.c) 26 | 27 | if (ENABLE_TLS) 28 | message("Compile with TLS enabled") 29 | target_compile_definitions(echo_server PUBLIC NBN_TLS) 30 | target_compile_definitions(echo_client PUBLIC NBN_TLS) 31 | endif (ENABLE_TLS) 32 | 33 | unset(ENABLE_TLS) 34 | 35 | if (WEBRTC_NATIVE_SERVER) 36 | if (EMSCRIPTEN) 37 | message(FATAL_ERROR "Cannot compile native webrtc driver with emscripten") 38 | endif (EMSCRIPTEN) 39 | 40 | message("Compile server with webrtc native driver") 41 | 42 | target_compile_definitions(echo_server PUBLIC NBN_WEBRTC_NATIVE) 43 | target_link_libraries(echo_server ${LIBDATACHANNEL_LIBRARY_PATH} m) 44 | target_include_directories(echo_server PUBLIC "${LIBDATACHANNEL_INCLUDE_PATH}") 45 | endif (WEBRTC_NATIVE_SERVER) 46 | 47 | unset(WEBRTC_NATIVE_SERVER) 48 | 49 | if (WEBRTC_NATIVE_CLIENT) 50 | if (EMSCRIPTEN) 51 | message(FATAL_ERROR "Cannot compile native webrtc driver with emscripten") 52 | endif (EMSCRIPTEN) 53 | 54 | message("Compile client with webrtc native driver") 55 | 56 | target_compile_definitions(echo_client PUBLIC NBN_WEBRTC_NATIVE) 57 | target_link_libraries(echo_client ${LIBDATACHANNEL_LIBRARY_PATH} m) 58 | target_include_directories(echo_client PUBLIC "${LIBDATACHANNEL_INCLUDE_PATH}") 59 | endif (WEBRTC_NATIVE_CLIENT) 60 | 61 | unset(WEBRTC_NATIVE_CLIENT) 62 | 63 | target_compile_definitions(echo_client PUBLIC NBN_DEBUG) 64 | target_compile_definitions(echo_server PUBLIC NBN_DEBUG) 65 | 66 | if(WIN32) 67 | target_link_libraries(echo_client ws2_32) 68 | target_link_libraries(echo_server ws2_32) 69 | else() 70 | # link with pthread when we are not on windows 71 | target_link_libraries(echo_client pthread) 72 | target_link_libraries(echo_server pthread) 73 | endif(WIN32) 74 | 75 | if (UNIX) 76 | # link with libm on unix 77 | target_link_libraries(echo_client m) 78 | target_link_libraries(echo_server m) 79 | endif (UNIX) 80 | 81 | if (EMSCRIPTEN) 82 | set(ASYNCIFY_IMPORTS "[\"__js_game_server_start\", \"__js_game_client_start\", \"__js_game_client_close\"]") 83 | 84 | set_target_properties(echo_server PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js \ 85 | -s ALLOW_MEMORY_GROWTH=1 \ 86 | -s TOTAL_MEMORY=30MB \ 87 | -s EXIT_RUNTIME=1 \ 88 | -s ASSERTIONS=1 \ 89 | -s ASYNCIFY \ 90 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 91 | 92 | set_target_properties(echo_client PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js \ 93 | -s ALLOW_MEMORY_GROWTH=1 \ 94 | -s TOTAL_MEMORY=30MB \ 95 | -s EXIT_RUNTIME=1 \ 96 | -s ASSERTIONS=1 \ 97 | -s ASYNCIFY \ 98 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 99 | endif() 100 | -------------------------------------------------------------------------------- /examples/echo/README.md: -------------------------------------------------------------------------------- 1 | # Echo 2 | 3 | This is a very basic echo client server example, the server accepts a single client at a time and echoes all 4 | messages it receives. 5 | 6 | ## UDP 7 | 8 | Use the CMake script to build the example: 9 | 10 | ``` 11 | cmake . 12 | make 13 | ``` 14 | 15 | To run the server simply do: 16 | 17 | `./echo_server` 18 | 19 | and to run the client: 20 | 21 | `./echo_client "some message"` 22 | 23 | The client will run indefinitely and send the provided string to the server every tick (30 times per second). 24 | 25 | ## WebRTC 26 | 27 | To target WASM: 28 | 29 | ``` 30 | emcmake cmake . 31 | make 32 | npm install 33 | ``` 34 | 35 | To run this example you need to have nodejs installed (see the package.json file). 36 | 37 | To run the server simply do: 38 | 39 | `npm run server` 40 | 41 | and to run the client: 42 | 43 | `npm run client "some message"` 44 | 45 | You can also compile using the native WebRTC driver: 46 | 47 | ``` 48 | cmake -DWEBRTC_NATIVE_SERVER=ON -DLIBDATACHANNEL_LIBRARY_PATH= -DLIBDATACHANNEL_INCLUDE_PATH= . 49 | make 50 | ``` 51 | 52 | To run the server simply do: 53 | 54 | `./echo_server` 55 | 56 | You should then be able to connect with both UDP and node clients. 57 | 58 | Use `-DWEBRTC_NATIVE_CLIENT=ON` if you want to use the WebRTC native driver on the client instead of UDP. 59 | -------------------------------------------------------------------------------- /examples/echo/client.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Has to be defined in exactly *one* source file before including the nbnet header 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | static bool running = true; 33 | static bool connected = false; 34 | static bool disconnected = false; 35 | 36 | void OnConnected(void) 37 | { 38 | Log(LOG_INFO, "Connected"); 39 | 40 | connected = true; // Start sending messages 41 | } 42 | 43 | void OnDisconnected(void) 44 | { 45 | Log(LOG_INFO, "Disconnected"); 46 | 47 | // Stop the main loop 48 | disconnected = true; 49 | running = false; 50 | 51 | // Retrieve the server code used when closing our client connection 52 | if (NBN_GameClient_GetServerCloseCode() == ECHO_SERVER_BUSY_CODE) 53 | { 54 | Log(LOG_INFO, "Another client is already connected"); 55 | } 56 | } 57 | 58 | void OnMessageReceived(void) 59 | { 60 | // Get info about the received message 61 | NBN_MessageInfo msg_info = NBN_GameClient_GetMessageInfo(); 62 | 63 | assert(msg_info.type == ECHO_MESSAGE_TYPE); 64 | 65 | // Retrieve the received message 66 | EchoMessage *msg = (EchoMessage *)msg_info.data; 67 | 68 | Log(LOG_INFO, "Received echo: %s (%d bytes)", msg->data, msg->length); 69 | 70 | EchoMessage_Destroy(msg); // Destroy the received echo message 71 | } 72 | 73 | int SendEchoMessage(const char *msg) 74 | { 75 | unsigned int length = strlen(msg); // Compute message length 76 | 77 | // Create the echo message 78 | EchoMessage *echo = EchoMessage_Create(); 79 | 80 | if (echo == NULL) 81 | return -1; 82 | 83 | // Fill echo message with message the length and the message 84 | echo->length = length + 1; 85 | memcpy(echo->data, msg, length + 1); 86 | 87 | // Reliably send it to the server 88 | if (NBN_GameClient_SendReliableMessage(ECHO_MESSAGE_TYPE, echo) < 0) 89 | return -1; 90 | 91 | return 0; 92 | } 93 | 94 | int main(int argc, char *argv[]) 95 | { 96 | if (argc != 2) 97 | { 98 | printf("Usage: client MSG\n"); 99 | 100 | // Error, quit the client application 101 | #ifdef __EMSCRIPTEN__ 102 | emscripten_force_exit(1); 103 | #else 104 | return 1; 105 | #endif 106 | } 107 | 108 | const char *msg = argv[1]; 109 | 110 | if (strlen(msg) > ECHO_MESSAGE_LENGTH - 1) 111 | { 112 | Log(LOG_ERROR, "Message length cannot exceed %d. Exit", ECHO_MESSAGE_LENGTH - 1); 113 | 114 | // Error, quit the client application 115 | #ifdef __EMSCRIPTEN__ 116 | emscripten_force_exit(1); 117 | #else 118 | return 1; 119 | #endif 120 | } 121 | 122 | #ifdef __EMSCRIPTEN__ 123 | 124 | // Register the WebRTC driver 125 | #ifdef NBN_TLS 126 | NBN_WebRTC_Register((NBN_WebRTC_Config){.enable_tls = true}); 127 | #else 128 | NBN_WebRTC_Register((NBN_WebRTC_Config){.enable_tls = false}); 129 | #endif // NBN_TLS 130 | 131 | #endif // __EMSCRIPTEN__ 132 | 133 | #ifdef NBN_WEBRTC_NATIVE 134 | 135 | #ifdef NBN_TLS 136 | bool enable_tls = true; 137 | #else 138 | bool enable_tls = false; 139 | #endif // NBN_TLS 140 | 141 | const char *ice_servers[] = { "stun:stun01.sipphone.com" }; 142 | NBN_WebRTC_C_Config cfg = { 143 | .ice_servers = ice_servers, 144 | .ice_servers_count = 1, 145 | .enable_tls = enable_tls, 146 | .cert_path = NULL, 147 | .key_path = NULL, 148 | .passphrase = NULL, 149 | .log_level = RTC_LOG_VERBOSE}; 150 | 151 | NBN_WebRTC_C_Register(cfg); 152 | 153 | #endif // NBN_WEBRTC_NATIVE 154 | 155 | #if !defined(__EMSCRIPTEN__) && !defined(NBN_WEBRTC_NATIVE) 156 | NBN_UDP_Register(); // Register the UDP driver 157 | #endif // __EMSCRIPTEN__ 158 | 159 | // Initialize the client 160 | 161 | // Start the client with a protocol name (must be the same than the one used by the server) 162 | // the server host and port and with packet encryption on or off 163 | if (NBN_GameClient_StartEx(ECHO_PROTOCOL_NAME, "127.0.0.1", ECHO_EXAMPLE_PORT, NULL, 0) < 0) 164 | { 165 | Log(LOG_ERROR, "Failed to start client"); 166 | 167 | // Error, quit the client application 168 | #ifdef __EMSCRIPTEN__ 169 | emscripten_force_exit(1); 170 | #else 171 | return 1; 172 | #endif 173 | } 174 | 175 | // Registering messages, have to be done after NBN_GameClient_StartEx 176 | // Messages need to be registered on both client and server side 177 | NBN_GameClient_RegisterMessage(ECHO_MESSAGE_TYPE, 178 | (NBN_MessageBuilder)EchoMessage_Create, 179 | (NBN_MessageDestructor)EchoMessage_Destroy, 180 | (NBN_MessageSerializer)EchoMessage_Serialize); 181 | 182 | // Number of seconds between client ticks 183 | double dt = 1.0 / ECHO_TICK_RATE; 184 | 185 | while (running) 186 | { 187 | int ev; 188 | 189 | // Poll for client events 190 | while ((ev = NBN_GameClient_Poll()) != NBN_NO_EVENT) 191 | { 192 | if (ev < 0) 193 | { 194 | Log(LOG_ERROR, "An error occured while polling client events. Exit"); 195 | 196 | // Stop main loop 197 | running = false; 198 | break; 199 | } 200 | 201 | switch (ev) 202 | { 203 | // Client is connected to the server 204 | case NBN_CONNECTED: 205 | OnConnected(); 206 | break; 207 | 208 | // Client has disconnected from the server 209 | case NBN_DISCONNECTED: 210 | OnDisconnected(); 211 | break; 212 | 213 | // A message has been received from the server 214 | case NBN_MESSAGE_RECEIVED: 215 | OnMessageReceived(); 216 | break; 217 | } 218 | } 219 | 220 | if (disconnected) 221 | break; 222 | 223 | if (connected) 224 | { 225 | if (SendEchoMessage(msg) < 0) 226 | { 227 | Log(LOG_ERROR, "Failed to send message. Exit"); 228 | 229 | // Stop main loop 230 | running = false; 231 | break; 232 | } 233 | } 234 | 235 | // Pack all enqueued messages as packets and send them 236 | if (NBN_GameClient_SendPackets() < 0) 237 | { 238 | Log(LOG_ERROR, "Failed to send packets. Exit"); 239 | 240 | // Stop main loop 241 | running = false; 242 | break; 243 | } 244 | 245 | // Cap the client tick rate 246 | EchoSleep(dt); 247 | } 248 | 249 | // Stop and deinitialize the client 250 | NBN_GameClient_Stop(); 251 | 252 | #ifdef NBN_WEBRTC_NATIVE 253 | NBN_WebRTC_C_Unregister(); 254 | #endif 255 | 256 | #ifdef __EMSCRIPTEN__ 257 | emscripten_force_exit(0); 258 | #else 259 | return 0; 260 | #endif 261 | } 262 | -------------------------------------------------------------------------------- /examples/echo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "server": "node build_web/echo_server.js", 4 | "client": "node build_web/echo_client.js" 5 | }, 6 | "dependencies": { 7 | "nbnet": "file:../../net_drivers/webrtc" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/echo/server.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Has to be defined in exactly *one* source file before including the nbnet header 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | static NBN_ConnectionHandle client = 0; 33 | 34 | // Echo the received message 35 | static int EchoReceivedMessage(void) 36 | { 37 | // Get info about the received message 38 | NBN_MessageInfo msg_info = NBN_GameServer_GetMessageInfo(); 39 | 40 | assert(msg_info.sender == client); 41 | assert(msg_info.type == ECHO_MESSAGE_TYPE); 42 | 43 | // Retrieve the received message 44 | EchoMessage *msg = (EchoMessage *)msg_info.data; 45 | 46 | // Create an echo message 47 | EchoMessage *echo = EchoMessage_Create(); 48 | 49 | // Fill it with the received message data and length 50 | memcpy(echo->data, msg->data, msg->length); 51 | echo->length = msg->length; 52 | 53 | // Reliably send it to the client 54 | // If the send fails the client will be disconnected and a NBN_CLIENT_DISCONNECTED event 55 | // will be received (see event polling in main) 56 | NBN_GameServer_SendReliableMessageTo(client, ECHO_MESSAGE_TYPE, echo); 57 | 58 | EchoMessage_Destroy(msg); // Destroy the received echo message 59 | 60 | return 0; 61 | } 62 | 63 | static bool error = false; 64 | 65 | int main(int argc, const char **argv) 66 | { 67 | #ifdef __EMSCRIPTEN__ 68 | 69 | // Register the WebRTC driver 70 | #ifdef NBN_TLS 71 | 72 | if (argc != 3) 73 | { 74 | printf("Usage: server CERT_PATH KEY_PATH\n"); 75 | return 1; 76 | } 77 | 78 | const char *cert_path = argv[1]; 79 | const char *key_path = argv[2]; 80 | 81 | NBN_WebRTC_Register((NBN_WebRTC_Config){.enable_tls = true, .cert_path = cert_path, .key_path = key_path}); 82 | #else 83 | NBN_WebRTC_Register((NBN_WebRTC_Config){.enable_tls = false}); 84 | #endif // NBN_TLS 85 | 86 | #endif // __EMSCRIPTEN__ 87 | 88 | #ifdef NBN_WEBRTC_NATIVE 89 | 90 | // Register native WebRTC driver 91 | 92 | #ifdef NBN_TLS 93 | bool enable_tls = true; 94 | #else 95 | bool enable_tls = false; 96 | #endif // NBN_TLS 97 | 98 | const char *ice_servers[] = { "stun:stun01.sipphone.com" }; 99 | NBN_WebRTC_C_Config cfg = { 100 | .ice_servers = ice_servers, 101 | .ice_servers_count = 1, 102 | .enable_tls = enable_tls, 103 | .cert_path = NULL, 104 | .key_path = NULL, 105 | .passphrase = NULL, 106 | .log_level = RTC_LOG_VERBOSE}; 107 | 108 | NBN_WebRTC_C_Register(cfg); 109 | #endif // NBN_WEBRTC_NATIVE 110 | 111 | #if !defined(__EMSCRIPTEN__) && !defined(NBN_WEBRTC_NATIVE) 112 | NBN_UDP_Register(); // Register the UDP driver 113 | #endif 114 | 115 | // Start the server with a protocol name, a port, and with packet encryption on or off 116 | if (NBN_GameServer_StartEx(ECHO_PROTOCOL_NAME, ECHO_EXAMPLE_PORT) < 0) 117 | { 118 | Log(LOG_ERROR, "Failed to start the server"); 119 | 120 | // Error, quit the server application 121 | #ifdef __EMSCRIPTEN__ 122 | emscripten_force_exit(1); 123 | #else 124 | return 1; 125 | #endif 126 | } 127 | 128 | (void) argc; 129 | (void) argv; 130 | // Registering messages, have to be done after NBN_GameServer_StartEx 131 | NBN_GameServer_RegisterMessage(ECHO_MESSAGE_TYPE, 132 | (NBN_MessageBuilder)EchoMessage_Create, 133 | (NBN_MessageDestructor)EchoMessage_Destroy, 134 | (NBN_MessageSerializer)EchoMessage_Serialize); 135 | 136 | // Number of seconds between server ticks 137 | double dt = 1.0 / ECHO_TICK_RATE; 138 | 139 | while (true) 140 | { 141 | int ev; 142 | 143 | // Poll for server events 144 | while ((ev = NBN_GameServer_Poll()) != NBN_NO_EVENT) 145 | { 146 | if (ev < 0) 147 | { 148 | Log(LOG_ERROR, "Something went wrong"); 149 | 150 | // Error, quit the server application 151 | error = true; 152 | break; 153 | } 154 | 155 | switch (ev) 156 | { 157 | // New connection request... 158 | case NBN_NEW_CONNECTION: 159 | // Echo server work with one single client at a time 160 | if (client) 161 | { 162 | NBN_GameServer_RejectIncomingConnectionWithCode(ECHO_SERVER_BUSY_CODE); 163 | } 164 | else 165 | { 166 | NBN_GameServer_AcceptIncomingConnection(); 167 | client = NBN_GameServer_GetIncomingConnection(); 168 | } 169 | 170 | break; 171 | 172 | // The client has disconnected 173 | case NBN_CLIENT_DISCONNECTED: 174 | assert(NBN_GameServer_GetDisconnectedClient() == client); 175 | 176 | client = 0; 177 | break; 178 | 179 | // A message has been received from the client 180 | case NBN_CLIENT_MESSAGE_RECEIVED: 181 | if (EchoReceivedMessage() < 0) 182 | { 183 | Log(LOG_ERROR, "Failed to echo received message"); 184 | 185 | // Error, quit the server application 186 | error = true; 187 | } 188 | break; 189 | } 190 | } 191 | 192 | // Pack all enqueued messages as packets and send them 193 | if (NBN_GameServer_SendPackets() < 0) 194 | { 195 | Log(LOG_ERROR, "Failed to send packets"); 196 | 197 | // Error, quit the server application 198 | error = true; 199 | break; 200 | } 201 | 202 | // Cap the server tick rate 203 | EchoSleep(dt); 204 | } 205 | 206 | // Stop the server 207 | NBN_GameServer_Stop(); 208 | 209 | #ifdef NBN_WEBRTC_NATIVE 210 | NBN_WebRTC_C_Unregister(); 211 | #endif 212 | 213 | int ret = error ? 1 : 0; 214 | 215 | #ifdef __EMSCRIPTEN__ 216 | emscripten_force_exit(ret); 217 | #else 218 | return ret; 219 | #endif 220 | } 221 | -------------------------------------------------------------------------------- /examples/echo/shared.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Sleep function 28 | #if defined(__EMSCRIPTEN__) 29 | #include 30 | #elif defined(_WIN32) || defined(_WIN64) 31 | #include 32 | #include 33 | #include 34 | #else 35 | #include 36 | #endif 37 | 38 | #include "shared.h" 39 | 40 | EchoMessage *EchoMessage_Create(void) 41 | { 42 | return (EchoMessage *) malloc(sizeof(EchoMessage)); 43 | } 44 | 45 | void EchoMessage_Destroy(EchoMessage *msg) 46 | { 47 | free(msg); 48 | } 49 | 50 | int EchoMessage_Serialize(EchoMessage *msg, NBN_Stream *stream) 51 | { 52 | NBN_SerializeUInt(stream, msg->length, 0, ECHO_MESSAGE_LENGTH); 53 | NBN_SerializeBytes(stream, msg->data, msg->length); 54 | 55 | return 0; 56 | } 57 | 58 | // Sleep for a given amount of seconds 59 | // Used to limit client and server tick rate 60 | void EchoSleep(double sec) 61 | { 62 | #if defined(__EMSCRIPTEN__) 63 | emscripten_sleep(sec * 1000); 64 | #elif defined(_WIN32) || defined(_WIN64) 65 | Sleep(sec * 1000); 66 | #else /* UNIX / OSX */ 67 | long nanos = sec * 1e9; 68 | struct timespec t = {.tv_sec = nanos / 999999999, .tv_nsec = nanos % 999999999}; 69 | 70 | nanosleep(&t, &t); 71 | #endif 72 | } 73 | 74 | static const char *log_type_strings[] = { 75 | "INFO", 76 | "ERROR", 77 | "DEBUG", 78 | "TRACE", 79 | "WARNING" 80 | }; 81 | 82 | // Basic logging function 83 | void Log(int type, const char *fmt, ...) 84 | { 85 | va_list args; 86 | 87 | va_start(args, fmt); 88 | 89 | printf("[%s] ", log_type_strings[type]); 90 | vprintf(fmt, args); 91 | printf("\n"); 92 | 93 | va_end(args); 94 | } 95 | -------------------------------------------------------------------------------- /examples/echo/shared.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #ifndef ECHO_EXAMPLE_SHARED_H 24 | #define ECHO_EXAMPLE_SHARED_H 25 | 26 | #define ECHO_PROTOCOL_NAME "echo-example" 27 | #define ECHO_EXAMPLE_PORT 42042 28 | #define ECHO_MESSAGE_TYPE 0 29 | #define ECHO_MESSAGE_LENGTH 255 30 | #define ECHO_TICK_RATE 30 31 | 32 | // An arbitrary chosen code used when rejecting a client to let it know that another client is already connected 33 | #define ECHO_SERVER_BUSY_CODE 42 34 | 35 | // nbnet logging 36 | // nbnet does not implement any logging capabilities, you need to provide your own 37 | enum 38 | { 39 | LOG_INFO, 40 | LOG_ERROR, 41 | LOG_DEBUG, 42 | LOG_TRACE, 43 | LOG_WARNING 44 | }; 45 | 46 | #define NBN_LogInfo(...) Log(LOG_INFO, __VA_ARGS__) 47 | #define NBN_LogError(...) Log(LOG_ERROR, __VA_ARGS__) 48 | #define NBN_LogDebug(...) Log(LOG_DEBUG, __VA_ARGS__) 49 | #define NBN_LogTrace(...) Log(LOG_TRACE, __VA_ARGS__) 50 | #define NBN_LogWarning(...) Log(LOG_WARNING, __VA_ARGS__) 51 | 52 | void Log(int, const char *, ...); 53 | 54 | #include "../../nbnet.h" 55 | 56 | #ifdef __EMSCRIPTEN__ 57 | 58 | #include "../../net_drivers/webrtc.h" 59 | 60 | #else 61 | 62 | #include "../../net_drivers/udp.h" 63 | 64 | #ifdef NBN_WEBRTC_NATIVE 65 | 66 | #include "../../net_drivers/webrtc_c.h" 67 | 68 | #endif // NBN_WEBRTC_NATIVE 69 | 70 | #endif // __EMSCRIPTEN__ 71 | 72 | typedef struct 73 | { 74 | unsigned int length; 75 | char data[ECHO_MESSAGE_LENGTH]; 76 | } EchoMessage; 77 | 78 | EchoMessage *EchoMessage_Create(void); 79 | void EchoMessage_Destroy(EchoMessage *); 80 | int EchoMessage_Serialize(EchoMessage *, NBN_Stream *); 81 | 82 | void EchoSleep(double); 83 | 84 | #endif /* ECHO_EXAMPLE_SHARED_H */ 85 | -------------------------------------------------------------------------------- /examples/echo_bytes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(echo_bytes) 4 | 5 | option(CPP_COMPILE OFF) 6 | 7 | # allow to compile as cpp 8 | if (CPP_COMPILE) 9 | file(GLOB_RECURSE CFILES "${CMAKE_SOURCE_DIR}/*.c") 10 | SET_SOURCE_FILES_PROPERTIES(${CFILES} PROPERTIES LANGUAGE CXX) 11 | set (CMAKE_CXX_STANDARD 20) 12 | endif (CPP_COMPILE) 13 | 14 | unset(CPP_COMPILE CACHE) 15 | 16 | add_compile_options(-Wall) 17 | 18 | if(CMAKE_COMPILER_IS_GNUCXX) 19 | add_compile_options(-Wextra -Wpedantic) 20 | endif (CMAKE_COMPILER_IS_GNUCXX) 21 | 22 | add_executable(echo_bytes_client client.c shared.c) 23 | add_executable(echo_bytes_server server.c shared.c) 24 | 25 | target_compile_definitions(echo_bytes_client PUBLIC NBN_DEBUG) 26 | target_compile_definitions(echo_bytes_server PUBLIC NBN_DEBUG) 27 | 28 | if(WIN32) 29 | target_link_libraries(echo_bytes_client wsock32 ws2_32) 30 | target_link_libraries(echo_bytes_server wsock32 ws2_32) 31 | else() 32 | # link with pthread when we are not on windows 33 | target_link_libraries(echo_bytes_client pthread) 34 | target_link_libraries(echo_bytes_server pthread) 35 | endif(WIN32) 36 | 37 | if (UNIX) 38 | # link with libm on unix 39 | target_link_libraries(echo_bytes_client m) 40 | target_link_libraries(echo_bytes_server m) 41 | endif (UNIX) 42 | 43 | if (EMSCRIPTEN) 44 | set(ASYNCIFY_IMPORTS "[\"__js_game_server_start\", \"__js_game_client_start\", \"__js_game_client_close\"]") 45 | 46 | set_target_properties(echo_bytes_server PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js \ 47 | -s ALLOW_MEMORY_GROWTH=1 \ 48 | -s TOTAL_MEMORY=30MB \ 49 | -s EXIT_RUNTIME=1 \ 50 | -s ASSERTIONS=1 \ 51 | -s ASYNCIFY \ 52 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 53 | 54 | set_target_properties(echo_bytes_client PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js \ 55 | -s ALLOW_MEMORY_GROWTH=1 \ 56 | -s TOTAL_MEMORY=30MB \ 57 | -s EXIT_RUNTIME=1 \ 58 | -s ASSERTIONS=1 \ 59 | -s ASYNCIFY \ 60 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 61 | endif() 62 | -------------------------------------------------------------------------------- /examples/echo_bytes/README.md: -------------------------------------------------------------------------------- 1 | # Echo (byte array) 2 | 3 | The very same example as the "echo" one, except it uses byte arrays instead of a user defined message. 4 | -------------------------------------------------------------------------------- /examples/echo_bytes/client.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Has to be defined in exactly *one* source file before including the nbnet header 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | static bool running = true; 33 | static bool connected = false; 34 | static bool disconnected = false; 35 | 36 | void OnConnected(void) 37 | { 38 | Log(LOG_INFO, "Connected"); 39 | 40 | connected = true; // Start sending messages 41 | } 42 | 43 | void OnDisconnected(void) 44 | { 45 | Log(LOG_INFO, "Disconnected"); 46 | 47 | // Stop the main loop 48 | disconnected = true; 49 | running = false; 50 | 51 | // Retrieve the server code used when closing our client connection 52 | if (NBN_GameClient_GetServerCloseCode() == ECHO_SERVER_BUSY_CODE) 53 | { 54 | Log(LOG_INFO, "Another client is already connected"); 55 | } 56 | } 57 | 58 | void OnMessageReceived(void) 59 | { 60 | // Get info about the received message 61 | NBN_MessageInfo msg_info = NBN_GameClient_GetMessageInfo(); 62 | 63 | assert(msg_info.type == NBN_BYTE_ARRAY_MESSAGE_TYPE); 64 | 65 | // Retrieve the received message 66 | NBN_ByteArrayMessage *msg = (NBN_ByteArrayMessage *)msg_info.data; 67 | 68 | Log(LOG_INFO, "Received echo: %s (%d bytes)", msg->bytes, msg->length); 69 | 70 | // Destroy the received message 71 | NBN_ByteArrayMessage_Destroy(msg); 72 | } 73 | 74 | int SendEchoMessage(const char *msg) 75 | { 76 | unsigned int length = strlen(msg); // Compute message length 77 | 78 | // Reliably send bytes to the server 79 | if (NBN_GameClient_SendReliableByteArray((uint8_t *)msg, length) < 0) 80 | return -1; 81 | 82 | return 0; 83 | } 84 | 85 | int main(int argc, char *argv[]) 86 | { 87 | if (argc != 2) 88 | { 89 | printf("Usage: client MSG\n"); 90 | 91 | // Error, quit the client application 92 | #ifdef __EMSCRIPTEN__ 93 | emscripten_force_exit(1); 94 | #else 95 | return 1; 96 | #endif 97 | } 98 | 99 | const char *msg = argv[1]; 100 | 101 | if (strlen(msg) > NBN_BYTE_ARRAY_MAX_SIZE - 1) 102 | { 103 | Log(LOG_ERROR, "Message length cannot exceed %d. Exit", NBN_BYTE_ARRAY_MAX_SIZE - 1); 104 | 105 | // Error, quit the client application 106 | #ifdef __EMSCRIPTEN__ 107 | emscripten_force_exit(1); 108 | #else 109 | return 1; 110 | #endif 111 | } 112 | 113 | #ifdef __EMSCRIPTEN__ 114 | NBN_WebRTC_Register(); // Register the WebRTC driver 115 | #else 116 | NBN_UDP_Register(); // Register the UDP driver 117 | #endif // __EMSCRIPTEN__ 118 | 119 | // Start the client with a protocol name (must be the same than the one used by the server) 120 | // the server host and port 121 | if (NBN_GameClient_Start(ECHO_PROTOCOL_NAME, "127.0.0.1", ECHO_EXAMPLE_PORT) < 0) 122 | { 123 | Log(LOG_ERROR, "Failed to start client"); 124 | 125 | // Error, quit the client application 126 | #ifdef __EMSCRIPTEN__ 127 | emscripten_force_exit(1); 128 | #else 129 | return 1; 130 | #endif 131 | } 132 | 133 | // Number of seconds between client ticks 134 | double dt = 1.0 / ECHO_TICK_RATE; 135 | 136 | while (running) 137 | { 138 | int ev; 139 | 140 | // Poll for client events 141 | while ((ev = NBN_GameClient_Poll()) != NBN_NO_EVENT) 142 | { 143 | if (ev < 0) 144 | { 145 | Log(LOG_ERROR, "An error occured while polling client events. Exit"); 146 | 147 | // Stop main loop 148 | running = false; 149 | break; 150 | } 151 | 152 | switch (ev) 153 | { 154 | // Client is connected to the server 155 | case NBN_CONNECTED: 156 | OnConnected(); 157 | break; 158 | 159 | // Client has disconnected from the server 160 | case NBN_DISCONNECTED: 161 | OnDisconnected(); 162 | break; 163 | 164 | // A message has been received from the server 165 | case NBN_MESSAGE_RECEIVED: 166 | OnMessageReceived(); 167 | break; 168 | } 169 | } 170 | 171 | if (disconnected) 172 | break; 173 | 174 | if (connected) 175 | { 176 | if (SendEchoMessage(msg) < 0) 177 | { 178 | Log(LOG_ERROR, "Failed to send message. Exit"); 179 | 180 | // Stop main loop 181 | running = false; 182 | break; 183 | } 184 | } 185 | 186 | // Pack all enqueued messages as packets and send them 187 | if (NBN_GameClient_SendPackets() < 0) 188 | { 189 | Log(LOG_ERROR, "Failed to send packets. Exit"); 190 | 191 | // Stop main loop 192 | running = false; 193 | break; 194 | } 195 | 196 | // Cap the client tick rate 197 | EchoSleep(dt); 198 | } 199 | 200 | // Stop the client 201 | NBN_GameClient_Stop(); 202 | 203 | #ifdef __EMSCRIPTEN__ 204 | emscripten_force_exit(0); 205 | #else 206 | return 0; 207 | #endif 208 | } 209 | -------------------------------------------------------------------------------- /examples/echo_bytes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "server": "node echo_bytes_server.js", 4 | "client": "node echo_bytes_client.js" 5 | }, 6 | "dependencies": { 7 | "nbnet": "file:../../net_drivers/webrtc" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/echo_bytes/server.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Has to be defined in exactly *one* source file before including the nbnet header 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | static NBN_ConnectionHandle client = 0; 33 | 34 | // Echo the received message 35 | static int EchoReceivedMessage(void) 36 | { 37 | // Get info about the received message 38 | NBN_MessageInfo msg_info = NBN_GameServer_GetMessageInfo(); 39 | 40 | assert(msg_info.sender == client); 41 | assert(msg_info.type == NBN_BYTE_ARRAY_MESSAGE_TYPE); 42 | 43 | // Retrieve the received message 44 | NBN_ByteArrayMessage *msg = (NBN_ByteArrayMessage *)msg_info.data; 45 | 46 | // If the send fails the client will be disconnected and a NBN_CLIENT_DISCONNECTED event 47 | // will be received (see event polling in main) 48 | if (NBN_GameServer_SendReliableByteArrayTo(client, msg->bytes, msg->length) < 0) 49 | return -1; 50 | 51 | // Destroy the received message 52 | NBN_ByteArrayMessage_Destroy(msg); 53 | 54 | return 0; 55 | } 56 | 57 | static bool error = false; 58 | 59 | int main(void) 60 | { 61 | #ifdef __EMSCRIPTEN__ 62 | NBN_WebRTC_Register(); // Register the WebRTC driver 63 | #else 64 | NBN_UDP_Register(); // Register the UDP driver 65 | #endif // __EMSCRIPTEN__ 66 | 67 | // Start the server with a protocol name and a port 68 | if (NBN_GameServer_Start(ECHO_PROTOCOL_NAME, ECHO_EXAMPLE_PORT) < 0) 69 | { 70 | Log(LOG_ERROR, "Failed to start the server"); 71 | 72 | // Error, quit the server application 73 | #ifdef __EMSCRIPTEN__ 74 | emscripten_force_exit(1); 75 | #else 76 | return 1; 77 | #endif 78 | } 79 | 80 | // Number of seconds between server ticks 81 | double dt = 1.0 / ECHO_TICK_RATE; 82 | 83 | while (true) 84 | { 85 | int ev; 86 | 87 | // Poll for server events 88 | while ((ev = NBN_GameServer_Poll()) != NBN_NO_EVENT) 89 | { 90 | if (ev < 0) 91 | { 92 | Log(LOG_ERROR, "Something went wrong"); 93 | 94 | // Error, quit the server application 95 | error = true; 96 | break; 97 | } 98 | 99 | switch (ev) 100 | { 101 | // New connection request... 102 | case NBN_NEW_CONNECTION: 103 | // Echo server work with one single client at a time 104 | if (client) 105 | { 106 | NBN_GameServer_RejectIncomingConnectionWithCode(ECHO_SERVER_BUSY_CODE); 107 | } 108 | else 109 | { 110 | NBN_GameServer_AcceptIncomingConnection(); 111 | client = NBN_GameServer_GetIncomingConnection(); 112 | } 113 | 114 | break; 115 | 116 | // The client has disconnected 117 | case NBN_CLIENT_DISCONNECTED: 118 | assert(NBN_GameServer_GetDisconnectedClient() == client); 119 | 120 | client = 0; 121 | break; 122 | 123 | // A message has been received from the client 124 | case NBN_CLIENT_MESSAGE_RECEIVED: 125 | if (EchoReceivedMessage() < 0) 126 | { 127 | Log(LOG_ERROR, "Failed to echo received message"); 128 | 129 | // Error, quit the server application 130 | error = true; 131 | } 132 | break; 133 | } 134 | } 135 | 136 | // Pack all enqueued messages as packets and send them 137 | if (NBN_GameServer_SendPackets() < 0) 138 | { 139 | Log(LOG_ERROR, "Failed to send packets"); 140 | 141 | // Error, quit the server application 142 | error = true; 143 | break; 144 | } 145 | 146 | // Cap the server tick rate 147 | EchoSleep(dt); 148 | } 149 | 150 | // Stop the server 151 | NBN_GameServer_Stop(); 152 | 153 | int ret = error ? 1 : 0; 154 | 155 | #ifdef __EMSCRIPTEN__ 156 | emscripten_force_exit(ret); 157 | #else 158 | return ret; 159 | #endif 160 | } 161 | -------------------------------------------------------------------------------- /examples/echo_bytes/shared.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Sleep function 28 | #if defined(__EMSCRIPTEN__) 29 | #include 30 | #elif defined(_WIN32) || defined(_WIN64) 31 | #include 32 | #include 33 | #include 34 | #else 35 | #include 36 | #endif 37 | 38 | #include "shared.h" 39 | 40 | // Sleep for a given amount of seconds 41 | // Used to limit client and server tick rate 42 | void EchoSleep(double sec) 43 | { 44 | #if defined(__EMSCRIPTEN__) 45 | emscripten_sleep(sec * 1000); 46 | #elif defined(_WIN32) || defined(_WIN64) 47 | Sleep(sec * 1000); 48 | #else /* UNIX / OSX */ 49 | long nanos = sec * 1e9; 50 | struct timespec t = {.tv_sec = nanos / 999999999, .tv_nsec = nanos % 999999999}; 51 | 52 | nanosleep(&t, &t); 53 | #endif 54 | } 55 | 56 | static const char *log_type_strings[] = { 57 | "INFO", 58 | "ERROR", 59 | "DEBUG", 60 | "TRACE", 61 | "WARNING" 62 | }; 63 | 64 | // Basic logging function 65 | void Log(int type, const char *fmt, ...) 66 | { 67 | va_list args; 68 | 69 | va_start(args, fmt); 70 | 71 | printf("[%s] ", log_type_strings[type]); 72 | vprintf(fmt, args); 73 | printf("\n"); 74 | 75 | va_end(args); 76 | } 77 | -------------------------------------------------------------------------------- /examples/echo_bytes/shared.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #ifndef ECHO_EXAMPLE_SHARED_H 24 | #define ECHO_EXAMPLE_SHARED_H 25 | 26 | #define ECHO_PROTOCOL_NAME "echo-example" 27 | #define ECHO_EXAMPLE_PORT 42042 28 | #define ECHO_MESSAGE_TYPE 0 29 | #define ECHO_TICK_RATE 30 30 | 31 | // An arbitrary chosen code used when rejecting a client to let it know that another client is already connected 32 | #define ECHO_SERVER_BUSY_CODE 42 33 | 34 | // nbnet logging 35 | // nbnet does not implement any logging capabilities, you need to provide your own 36 | enum 37 | { 38 | LOG_INFO, 39 | LOG_ERROR, 40 | LOG_DEBUG, 41 | LOG_TRACE, 42 | LOG_WARNING 43 | }; 44 | 45 | #define NBN_LogInfo(...) Log(LOG_INFO, __VA_ARGS__) 46 | #define NBN_LogError(...) Log(LOG_ERROR, __VA_ARGS__) 47 | #define NBN_LogDebug(...) Log(LOG_DEBUG, __VA_ARGS__) 48 | #define NBN_LogTrace(...) Log(LOG_TRACE, __VA_ARGS__) 49 | #define NBN_LogWarning(...) Log(LOG_WARNING, __VA_ARGS__) 50 | 51 | void Log(int, const char *, ...); 52 | 53 | #include "../../nbnet.h" 54 | 55 | #ifdef __EMSCRIPTEN__ 56 | #include "../../net_drivers/webrtc.h" 57 | #else 58 | #include "../../net_drivers/udp.h" 59 | #endif 60 | 61 | void EchoSleep(double); 62 | 63 | #endif /* ECHO_EXAMPLE_SHARED_H */ 64 | -------------------------------------------------------------------------------- /examples/raylib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(raylib_example C) 4 | 5 | set(raylib_DIR cmake) 6 | 7 | set(CLIENT_SOURCES client.c shared.c) 8 | set(SERVER_SOURCES server.c shared.c) 9 | 10 | add_compile_options(-Wall -Wno-unknown-pragmas -Wno-type-limits -std=c99) 11 | 12 | if(CMAKE_COMPILER_IS_GNUCXX) 13 | add_compile_options(-Wextra -Wpedantic) 14 | endif (CMAKE_COMPILER_IS_GNUCXX) 15 | 16 | add_executable(raylib_client ${CLIENT_SOURCES}) 17 | add_executable(raylib_server ${SERVER_SOURCES}) 18 | 19 | # set RAYLIB_LIBRARY_PATH from command line 20 | 21 | target_link_libraries(raylib_client ${RAYLIB_LIBRARY_PATH} pthread m) 22 | target_link_libraries(raylib_server ${RAYLIB_LIBRARY_PATH} pthread m) 23 | 24 | # set RAYLIB_INCLUDE_PATH from command line 25 | 26 | target_include_directories(raylib_client PUBLIC "${RAYLIB_INCLUDE_PATH}") 27 | target_include_directories(raylib_server PUBLIC "${RAYLIB_INCLUDE_PATH}") 28 | 29 | if(WIN32) 30 | target_link_libraries(raylib_client wsock32 ws2_32 opengl32 gdi32 winmm) 31 | target_link_libraries(raylib_server wsock32 ws2_32) 32 | endif() 33 | 34 | target_compile_definitions(raylib_client PUBLIC NBN_DEBUG) 35 | target_compile_definitions(raylib_server PUBLIC NBN_DEBUG) 36 | target_compile_definitions(raylib_server PUBLIC NBN_RAYLIB_SERVER) 37 | 38 | # compile with C WebRTC driver 39 | if (WEBRTC_C_DRIVER) 40 | # can't compile WebRTC native driver with emscripten 41 | if (EMSCRIPTEN) 42 | message(SEND_ERROR "Can't compile WebRTC native driver with emscripten") 43 | endif (EMSCRIPTEN) 44 | 45 | message("Compiling with C WebRTC driver") 46 | 47 | target_compile_definitions(raylib_server PUBLIC SOAK_WEBRTC_C_DRIVER) 48 | 49 | target_link_libraries(raylib_server ${LIBFACILIO_LIBRARY_PATH}) 50 | target_link_libraries(raylib_server ${LIBCRYPTO_LIBRARY_PATH}) 51 | target_link_libraries(raylib_server ${LIBSSL_LIBRARY_PATH}) 52 | target_link_libraries(raylib_server ${LIBDATACHANNEL_LIBRARY_PATH}) 53 | 54 | target_include_directories(raylib_server PUBLIC "${LIBFACILIO_INCLUDE_PATH}") 55 | target_include_directories(raylib_server PUBLIC "${OPENSSL_INCLUDE_PATH}") 56 | target_include_directories(raylib_server PUBLIC "${LIBDATACHANNEL_INCLUDE_PATH}") 57 | 58 | if (USE_HTTPS) 59 | target_compile_definitions(raylib_server PUBLIC NBN_HTTPS_SERVER_NAME="localhost") 60 | target_compile_definitions(raylib_server PUBLIC NBN_HTTPS_KEY_PEM="localhost.key") 61 | target_compile_definitions(raylib_server PUBLIC NBN_HTTPS_CERT_PEM="localhost.crt") 62 | endif (USE_HTTPS) 63 | endif (WEBRTC_C_DRIVER) 64 | 65 | unset(WEBRTC_C_DRIVER) 66 | 67 | # Use HTTPS (for WebRTC drivers) 68 | if (USE_HTTPS) 69 | message("Compiling with HTTPS enabled") 70 | 71 | target_compile_definitions(raylib_client PUBLIC NBN_USE_HTTPS) 72 | target_compile_definitions(raylib_server PUBLIC NBN_USE_HTTPS) 73 | endif (USE_HTTPS) 74 | 75 | unset(USE_HTTPS) 76 | 77 | if (EMSCRIPTEN) 78 | set(ASYNCIFY_IMPORTS "[\"__js_game_server_start\", \"__js_game_client_start\", \"__js_game_client_close\"]") 79 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=30MB -s EXIT_RUNTIME=1 -s ASSERTIONS=1 -s ASYNCIFY -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 80 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js") 81 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s USE_GLFW=3 --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/shell.html") 82 | set_target_properties(raylib_client PROPERTIES SUFFIX ".html") 83 | 84 | add_custom_command( 85 | TARGET raylib_client 86 | POST_BUILD 87 | COMMAND browserify ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/nbnet.js -o nbnet_bundle.js) 88 | endif() 89 | 90 | if (APPLE) 91 | target_link_libraries(raylib_client "-framework OpenGL -framework Cocoa -framework IOKit -framework CoreAudio -framework CoreVideo") 92 | endif (APPLE) 93 | -------------------------------------------------------------------------------- /examples/raylib/README.md: -------------------------------------------------------------------------------- 1 | # Raylib 2 | 3 | This example demonstrates almost all the features of nbnet, it is much more advanced than the echo example. 4 | 5 | Each client is represented as a colored square and can: 6 | 7 | - move inside the window (with the directional keys) 8 | - switch color (with the spacebar) 9 | - increase or decrease a float value (with K and J jeys, used to demonstrate float serialization) 10 | 11 | [See it running](https://www.youtube.com/watch?v=BJl_XN3QJhQ&ab_channel=NathanBIAGINI). 12 | 13 | Each client is responsible of sending updates about his own state to the server. 14 | The server gathers states from all clients and stores them. 15 | Every tick, the server packs the latest received client states in a message that is then broadcasted to all clients. 16 | Each client displays a representation of other clients based on the latest received states from the server. 17 | 18 | This example protocol is implemented using three messages: 19 | 20 | - UpdateStateMessage (unreliabe) 21 | 22 | This message is sent by a client to the server every tick. It contains the most up to date client state data (position and float value). 23 | 24 | - ChangeColorMessage (reliable) 25 | 26 | This message is sent by a client to the server every time it changes its color. 27 | 28 | - GameStateMessage (unreliable) 29 | 30 | This message is broadcasted by the server to all connected clients. It contains the most up-to-date states of all clients. 31 | 32 | This example also demonstrates how to use the nbnet network conditions simulation, both client and server accept the following command line options: 33 | 34 | `--packet_loss= # percentage (0 - 1), float value` 35 | 36 | `--packet_duplication= # percentage (0 -1), float value` 37 | 38 | `--ping= # in secondes, float value` 39 | 40 | `--jitter= # in seconds, float value` 41 | 42 | Information about the state of the connection will be displayed in the bottom right of the client window. 43 | 44 | ## Web 45 | 46 | nbnet supports two WebRTC drivers: one using JS and emscripten and a native one fully written in C. 47 | 48 | ### emscripten WebRTC driver 49 | 50 | This driver requires the code to be compiled with emscripten. 51 | 52 | `emcmake cmake -DRAYLIB_LIBRARY_PATH= -DRAYLIB_INCLUDE_PATH= .` 53 | 54 | To run the server: 55 | 56 | `npm run server` 57 | 58 | You can pass options to the server like so: 59 | 60 | `npm run server -- --packet_loss= ...` 61 | 62 | To run the client you need to have an HTTP server running and serving the build directory (it contains the HTML file), then you just have to open `http://localhost:/raylib_client.html` in your browser. 63 | 64 | When compiling the server this way, it will have to run as a nodejs application and only web clients will be able to connect to it. If you want your server to accept both web and native clients, read the next section. 65 | 66 | ### Native WebRTC driver 67 | 68 | Unlike the JS WebRTC driver, this one can be compiled natively and therefore can be used alongside the UDP driver, making it possible to support both UDP socket and WebRTC connections. 69 | 70 | Some external dependencies are required: 71 | 72 | - libssl 73 | - libcrypto 74 | - libdatachannel 75 | - facil.io 76 | 77 | `mkdir build` 78 | 79 | `cd build` 80 | 81 | `cmake -DRAYLIB_LIBRARY_PATH= -DRAYLIB_INCLUDE_PATH= -DLIBFACILIO_LIBRARY_PATH= -DLIBCRYPTO_LIBRARY_PATH= -DLIBSSL_LIBRARY_PATH= -DLIBDATACHANNEL_LIBRARY_PATH= -DLIBFACILIO_INCLUDE_PATH= -DOPENSSL_INCLUDE_PATH= -DLIBDATACHANNEL_INCLUDE_PATH= -DWEBRTC_C_DRIVER=ON .` 82 | 83 | To run the server: 84 | 85 | `./raylib_server` 86 | 87 | To run a native client: 88 | 89 | `./raylib_client` 90 | 91 | To run a web client simply do the same thing as in the previous section. 92 | 93 | [Here is a video](https://www.youtube.com/watch?v=63sC-WW79Oc) showcasing one web client and one native client connected to the same server. 94 | -------------------------------------------------------------------------------- /examples/raylib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "server": "node --experimental-wasm-threads --experimental-wasm-bulk-memory raylib_server.js" 4 | }, 5 | "dependencies": { 6 | "nbnet": "file:../../net_drivers/webrtc" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/raylib/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/nbnet/79a3bed112037377ed37ac129218efc512ff565a/examples/raylib/screenshot.png -------------------------------------------------------------------------------- /examples/raylib/shared.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // nbnet implementation 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | // Command line options 33 | enum 34 | { 35 | OPT_MESSAGES_COUNT, 36 | OPT_PACKET_LOSS, 37 | OPT_PACKET_DUPLICATION, 38 | OPT_PING, 39 | OPT_JITTER 40 | }; 41 | 42 | static Options options = {0}; 43 | 44 | ChangeColorMessage *ChangeColorMessage_Create(void) 45 | { 46 | return malloc(sizeof(ChangeColorMessage)); 47 | } 48 | 49 | void ChangeColorMessage_Destroy(ChangeColorMessage *msg) 50 | { 51 | free(msg); 52 | } 53 | 54 | int ChangeColorMessage_Serialize(ChangeColorMessage *msg, NBN_Stream *stream) 55 | { 56 | NBN_SerializeUInt(stream, msg->color, 0, MAX_COLORS - 1); 57 | 58 | return 0; 59 | } 60 | 61 | UpdateStateMessage *UpdateStateMessage_Create(void) 62 | { 63 | return malloc(sizeof(UpdateStateMessage)); 64 | } 65 | 66 | void UpdateStateMessage_Destroy(UpdateStateMessage *msg) 67 | { 68 | free(msg); 69 | } 70 | 71 | int UpdateStateMessage_Serialize(UpdateStateMessage *msg, NBN_Stream *stream) 72 | { 73 | NBN_SerializeUInt(stream, msg->x, 0, GAME_WIDTH); 74 | NBN_SerializeUInt(stream, msg->y, 0, GAME_HEIGHT); 75 | NBN_SerializeFloat(stream, msg->val, MIN_FLOAT_VAL, MAX_FLOAT_VAL, 3); 76 | 77 | return 0; 78 | } 79 | 80 | GameStateMessage *GameStateMessage_Create(void) 81 | { 82 | return malloc(sizeof(GameStateMessage)); 83 | } 84 | 85 | void GameStateMessage_Destroy(GameStateMessage *msg) 86 | { 87 | free(msg); 88 | } 89 | 90 | int GameStateMessage_Serialize(GameStateMessage *msg, NBN_Stream *stream) 91 | { 92 | NBN_SerializeUInt(stream, msg->client_count, 0, MAX_CLIENTS); 93 | 94 | for (unsigned int i = 0; i < msg->client_count; i++) 95 | { 96 | NBN_SerializeUInt(stream, msg->client_states[i].client_id, 0, UINT_MAX); 97 | NBN_SerializeUInt(stream, msg->client_states[i].color, 0, MAX_COLORS - 1); 98 | NBN_SerializeUInt(stream, msg->client_states[i].x, 0, GAME_WIDTH); 99 | NBN_SerializeUInt(stream, msg->client_states[i].y, 0, GAME_HEIGHT); 100 | NBN_SerializeFloat(stream, msg->client_states[i].val, MIN_FLOAT_VAL, MAX_FLOAT_VAL, 3); 101 | } 102 | 103 | return 0; 104 | } 105 | 106 | // Parse the command line 107 | int ReadCommandLine(int argc, char *argv[]) 108 | { 109 | int opt; 110 | int option_index; 111 | struct option long_options[] = { 112 | { "packet_loss", required_argument, NULL, OPT_PACKET_LOSS }, 113 | { "packet_duplication", required_argument, NULL, OPT_PACKET_DUPLICATION }, 114 | { "ping", required_argument, NULL, OPT_PING }, 115 | { "jitter", required_argument, NULL, OPT_JITTER } 116 | }; 117 | 118 | while ((opt = getopt_long(argc, argv, "", long_options, &option_index)) != -1) 119 | { 120 | switch (opt) 121 | { 122 | case OPT_PACKET_LOSS: 123 | options.packet_loss = atof(optarg); 124 | break; 125 | 126 | case OPT_PACKET_DUPLICATION: 127 | options.packet_duplication = atof(optarg); 128 | break; 129 | 130 | case OPT_PING: 131 | options.ping = atof(optarg); 132 | break; 133 | 134 | case OPT_JITTER: 135 | options.jitter = atof(optarg); 136 | break; 137 | 138 | case '?': 139 | return -1; 140 | 141 | default: 142 | return -1; 143 | } 144 | } 145 | 146 | return 0; 147 | } 148 | 149 | // Return the command line options 150 | Options GetOptions(void) 151 | { 152 | return options; 153 | } 154 | -------------------------------------------------------------------------------- /examples/raylib/shared.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #ifndef RAYLIB_EXAMPLE_SHARED_H 24 | #define RAYLIB_EXAMPLE_SHARED_H 25 | 26 | #include 27 | 28 | #if defined(_WIN32) || defined(_WIN64) 29 | 30 | /* 31 | * The following defines are meant to avoid conflicts between raylib and windows.h 32 | * https://github.com/raysan5/raylib/issues/857 33 | */ 34 | 35 | // If defined, the following flags inhibit definition of the indicated items 36 | #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ 37 | #define NOVIRTUALKEYCODES // VK_* 38 | #define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* 39 | #define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* 40 | #define NOSYSMETRICS // SM_* 41 | #define NOMENUS // MF_* 42 | #define NOICONS // IDI_* 43 | #define NOKEYSTATES // MK_* 44 | #define NOSYSCOMMANDS // SC_* 45 | #define NORASTEROPS // Binary and Tertiary raster ops 46 | #define NOSHOWWINDOW // SW_* 47 | #define OEMRESOURCE // OEM Resource values 48 | #define NOATOM // Atom Manager routines 49 | #define NOCLIPBOARD // Clipboard routines 50 | #define NOCOLOR // Screen colors 51 | #define NOCTLMGR // Control and Dialog routines 52 | #define NODRAWTEXT // DrawText() and DT_* 53 | #define NOGDI // All GDI defines and routines 54 | #define NOKERNEL // All KERNEL defines and routines 55 | #define NOUSER // All USER defines and routines 56 | /*#define NONLS // All NLS defines and routines*/ 57 | #define NOMB // MB_* and MessageBox() 58 | #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines 59 | #define NOMETAFILE // typedef METAFILEPICT 60 | #define NOMINMAX // Macros min(a,b) and max(a,b) 61 | #define NOMSG // typedef MSG and associated routines 62 | #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* 63 | #define NOSCROLL // SB_* and scrolling routines 64 | #define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. 65 | #define NOSOUND // Sound driver routines 66 | #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines 67 | #define NOWH // SetWindowsHook and WH_* 68 | #define NOWINOFFSETS // GWL_*, GCL_*, associated routines 69 | #define NOCOMM // COMM driver routines 70 | #define NOKANJI // Kanji support stuff. 71 | #define NOHELP // Help engine interface. 72 | #define NOPROFILER // Profiler interface. 73 | #define NODEFERWINDOWPOS // DeferWindowPos routines 74 | #define NOMCX // Modem Configuration Extensions 75 | 76 | // Type required before windows.h inclusion 77 | typedef struct tagMSG *LPMSG; 78 | 79 | #include // Has to be included before windows.h 80 | #include 81 | 82 | #endif // WINDOWS 83 | 84 | #define RAYLIB_EXAMPLE_PROTOCOL_NAME "raylib-example" 85 | #define RAYLIB_EXAMPLE_PORT 42042 86 | 87 | // nbnet logging, use raylib logging 88 | 89 | #define NBN_LogInfo(...) TraceLog(LOG_INFO, __VA_ARGS__) 90 | 91 | #define NBN_LogError(...) TraceLog(LOG_ERROR, __VA_ARGS__) 92 | #define NBN_LogWarning(...) TraceLog(LOG_WARNING, __VA_ARGS__) 93 | #define NBN_LogDebug(...) TraceLog(LOG_DEBUG, __VA_ARGS__) 94 | #define NBN_LogTrace(...) TraceLog(LOG_TRACE, __VA_ARGS__) 95 | 96 | #include "../../nbnet.h" 97 | 98 | #ifdef __EMSCRIPTEN__ 99 | #include "../../net_drivers/webrtc.h" 100 | #else 101 | #include "../../net_drivers/udp.h" 102 | 103 | #ifdef SOAK_WEBRTC_C_DRIVER 104 | #include "../../net_drivers/webrtc_c.h" 105 | #endif 106 | 107 | #endif // __EMSCRIPTEN__ 108 | 109 | #define TICK_RATE 60 // Simulation tick rate 110 | 111 | // Window size, used to display window but also to cap the serialized position values within messages 112 | #define GAME_WIDTH 800 113 | #define GAME_HEIGHT 600 114 | 115 | #define MIN_FLOAT_VAL -5 // Minimum value of networked client float value 116 | #define MAX_FLOAT_VAL 5 // Maximum value of networked client float value 117 | 118 | // Maximum number of connected clients at a time 119 | #define MAX_CLIENTS 4 120 | 121 | // Max number of colors for client to switch between 122 | #define MAX_COLORS 7 123 | 124 | // A code passed by the server when closing a client connection due to being full (max client count reached) 125 | #define SERVER_FULL_CODE 42 126 | 127 | // Message ids 128 | enum 129 | { 130 | CHANGE_COLOR_MESSAGE, 131 | UPDATE_STATE_MESSAGE, 132 | GAME_STATE_MESSAGE 133 | }; 134 | 135 | // Messages 136 | 137 | typedef struct 138 | { 139 | int x; 140 | int y; 141 | float val; 142 | } UpdateStateMessage; 143 | 144 | // Client colors used for ChangeColorMessage and GameStateMessage messages 145 | typedef enum 146 | { 147 | CLI_RED, 148 | CLI_GREEN, 149 | CLI_BLUE, 150 | CLI_YELLOW, 151 | CLI_ORANGE, 152 | CLI_PURPLE, 153 | CLI_PINK 154 | } ClientColor; 155 | 156 | typedef struct 157 | { 158 | ClientColor color; 159 | } ChangeColorMessage; 160 | 161 | // Client state, represents a client over the network 162 | typedef struct 163 | { 164 | uint32_t client_id; 165 | int x; 166 | int y; 167 | float val; 168 | ClientColor color; 169 | } ClientState; 170 | 171 | typedef struct 172 | { 173 | unsigned int client_count; 174 | ClientState client_states[MAX_CLIENTS]; 175 | } GameStateMessage; 176 | 177 | // Store all options from the command line 178 | typedef struct 179 | { 180 | float packet_loss; 181 | float packet_duplication; 182 | float ping; 183 | float jitter; 184 | } Options; 185 | 186 | ChangeColorMessage *ChangeColorMessage_Create(void); 187 | void ChangeColorMessage_Destroy(ChangeColorMessage *); 188 | int ChangeColorMessage_Serialize(ChangeColorMessage *msg, NBN_Stream *); 189 | 190 | UpdateStateMessage *UpdateStateMessage_Create(void); 191 | void UpdateStateMessage_Destroy(UpdateStateMessage *); 192 | int UpdateStateMessage_Serialize(UpdateStateMessage *, NBN_Stream *); 193 | 194 | GameStateMessage *GameStateMessage_Create(void); 195 | void GameStateMessage_Destroy(GameStateMessage *); 196 | int GameStateMessage_Serialize(GameStateMessage *, NBN_Stream *); 197 | 198 | int ReadCommandLine(int, char *[]); 199 | Options GetOptions(void); 200 | 201 | #endif /* RAYLIB_EXAMPLE_SHARED_H */ 202 | -------------------------------------------------------------------------------- /examples/rpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(rpc) 4 | 5 | option(CPP_COMPILE OFF) 6 | 7 | # allow to compile as cpp 8 | if (CPP_COMPILE) 9 | file(GLOB_RECURSE CFILES "${CMAKE_SOURCE_DIR}/*.c") 10 | SET_SOURCE_FILES_PROPERTIES(${CFILES} PROPERTIES LANGUAGE CXX) 11 | set (CMAKE_CXX_STANDARD 20) 12 | endif (CPP_COMPILE) 13 | 14 | unset(CPP_COMPILE CACHE) 15 | 16 | add_compile_options(-Wall) 17 | 18 | if(CMAKE_COMPILER_IS_GNUCXX) 19 | add_compile_options(-Wextra -Wpedantic) 20 | endif (CMAKE_COMPILER_IS_GNUCXX) 21 | 22 | add_executable(rpc_client client.c shared.c) 23 | add_executable(rpc_server server.c shared.c) 24 | 25 | target_compile_definitions(rpc_client PUBLIC NBN_DEBUG) 26 | target_compile_definitions(rpc_server PUBLIC NBN_DEBUG) 27 | 28 | option(WEBRTC_DRIVER_C OFF) 29 | 30 | if(WIN32) 31 | target_link_libraries(rpc_client wsock32 ws2_32) 32 | target_link_libraries(rpc_server wsock32 ws2_32) 33 | else() 34 | # link with pthread when we are not on windows 35 | target_link_libraries(rpc_client pthread) 36 | target_link_libraries(rpc_server pthread) 37 | endif(WIN32) 38 | 39 | if (UNIX) 40 | # link with libm on unix 41 | target_link_libraries(rpc_client m) 42 | target_link_libraries(rpc_server m) 43 | endif (UNIX) 44 | 45 | if (EMSCRIPTEN) 46 | set(ASYNCIFY_IMPORTS "[\"__js_game_server_start\", \"__js_game_client_start\", \"__js_game_client_close\"]") 47 | 48 | set_target_properties(rpc_server PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js \ 49 | -s ALLOW_MEMORY_GROWTH=1 \ 50 | -s TOTAL_MEMORY=30MB \ 51 | -s EXIT_RUNTIME=1 \ 52 | -s ASSERTIONS=1 \ 53 | -s ASYNCIFY \ 54 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 55 | 56 | set_target_properties(rpc_client PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../../net_drivers/webrtc/js/api.js \ 57 | -s ALLOW_MEMORY_GROWTH=1 \ 58 | -s TOTAL_MEMORY=30MB \ 59 | -s EXIT_RUNTIME=1 \ 60 | -s ASSERTIONS=1 \ 61 | -s ASYNCIFY \ 62 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 63 | endif() 64 | -------------------------------------------------------------------------------- /examples/rpc/README.md: -------------------------------------------------------------------------------- 1 | # RPC 2 | 3 | Simple example demonstrating how to use RPCs. 4 | 5 | Use the CMake script to compile the example: 6 | 7 | ``` 8 | cmake . 9 | make 10 | ``` 11 | 12 | To run the server simply do: 13 | 14 | `./rpc_server` 15 | 16 | and to run the client: 17 | 18 | `./rpc_client` 19 | 20 | ## WebRTC 21 | 22 | Use the CMake script to compile the example: 23 | 24 | ``` 25 | EMSCRIPTEN=1 cmake . 26 | make 27 | ``` 28 | 29 | To run this example you need to have nodejs installed (see the package.json file). 30 | 31 | To run the server simply do: 32 | 33 | `npm run server` 34 | 35 | and to run the client: 36 | 37 | `npm run client` 38 | -------------------------------------------------------------------------------- /examples/rpc/client.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Has to be defined in exactly *one* source file before including the nbnet header 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | static bool running = true; 33 | static bool connected = false; 34 | static bool disconnected = false; 35 | 36 | static void TestRPC2(unsigned int param_count, NBN_RPC_Param params[NBN_RPC_MAX_PARAM_COUNT], NBN_ConnectionHandle sender) 37 | { 38 | (void) sender; 39 | TEST_VERIFY(param_count == 2); 40 | Log(LOG_INFO, "TestRPC2 called !"); 41 | Log(LOG_INFO, "Parameter 1 (float): %f", NBN_RPC_GetFloat(params, 0)); 42 | Log(LOG_INFO, "Parameter 2 (string): %s", NBN_RPC_GetString(params, 1)); 43 | } 44 | 45 | void OnConnected(void) 46 | { 47 | Log(LOG_INFO, "Connected"); 48 | 49 | connected = true; 50 | 51 | int ret = NBN_GameClient_CallRPC(TEST_RPC_ID, 4242, -1234.5678f, true); 52 | 53 | TEST_VERIFY(ret == 0); 54 | } 55 | 56 | void OnDisconnected(void) 57 | { 58 | Log(LOG_INFO, "Disconnected"); 59 | 60 | // Stop the main loop 61 | disconnected = true; 62 | running = false; 63 | } 64 | 65 | int main(int argc, char *argv[]) 66 | { 67 | (void) argc; 68 | (void) argv; 69 | #ifdef __EMSCRIPTEN__ 70 | NBN_WebRTC_Register(); // Register the WebRTC driver 71 | #else 72 | NBN_UDP_Register(); // Register the UDP driver 73 | #endif // __EMSCRIPTEN__ 74 | 75 | // Initialize the client with a protocol name (must be the same than the one used by the server), the server ip address and port 76 | 77 | if (NBN_GameClient_StartEx(RPC_PROTOCOL_NAME, "127.0.0.1", RPC_EXAMPLE_PORT, NULL, 0) < 0) 78 | { 79 | Log(LOG_ERROR, "Failed to start client"); 80 | 81 | // Error, quit the client application 82 | #ifdef __EMSCRIPTEN__ 83 | emscripten_force_exit(1); 84 | #else 85 | return 1; 86 | #endif 87 | } 88 | 89 | int ret = NBN_GameClient_RegisterRPC(TEST_RPC_ID, TEST_RPC_SIGNATURE, NULL); 90 | 91 | TEST_VERIFY(ret == 0); 92 | 93 | ret = NBN_GameClient_RegisterRPC(TEST_RPC_2_ID, TEST_RPC_2_SIGNATURE, TestRPC2); 94 | 95 | TEST_VERIFY(ret == 0); 96 | 97 | // Number of seconds between client ticks 98 | double dt = 1.0 / RPC_TICK_RATE; 99 | 100 | while (running) 101 | { 102 | int ev; 103 | 104 | // Poll for client events 105 | while ((ev = NBN_GameClient_Poll()) != NBN_NO_EVENT) 106 | { 107 | if (ev < 0) 108 | { 109 | Log(LOG_ERROR, "An error occured while polling client events. Exit"); 110 | 111 | // Stop main loop 112 | running = false; 113 | break; 114 | } 115 | 116 | switch (ev) 117 | { 118 | // Client is connected to the server 119 | case NBN_CONNECTED: 120 | OnConnected(); 121 | break; 122 | 123 | // Client has disconnected from the server 124 | case NBN_DISCONNECTED: 125 | OnDisconnected(); 126 | break; 127 | } 128 | } 129 | 130 | if (disconnected) 131 | break; 132 | 133 | // Pack all enqueued messages as packets and send them 134 | if (NBN_GameClient_SendPackets() < 0) 135 | { 136 | Log(LOG_ERROR, "Failed to send packets. Exit"); 137 | 138 | // Stop main loop 139 | running = false; 140 | break; 141 | } 142 | 143 | // Cap the client tick rate 144 | ExampleSleep(dt); 145 | } 146 | 147 | // Stop the client 148 | NBN_GameClient_Stop(); 149 | 150 | #ifdef __EMSCRIPTEN__ 151 | emscripten_force_exit(0); 152 | #else 153 | return 0; 154 | #endif 155 | } 156 | -------------------------------------------------------------------------------- /examples/rpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "server": "node rpc_server.js", 4 | "client": "node rpc_client.js" 5 | }, 6 | "dependencies": { 7 | "nbnet": "file:../../net_drivers/webrtc" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/rpc/server.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Has to be defined in exactly *one* source file before including the nbnet header 28 | #define NBNET_IMPL 29 | 30 | #include "shared.h" 31 | 32 | static bool error = false; 33 | 34 | static void TestRPC(unsigned int param_count, NBN_RPC_Param params[NBN_RPC_MAX_PARAM_COUNT], NBN_ConnectionHandle sender) 35 | { 36 | TEST_VERIFY(param_count == 3); 37 | Log(LOG_INFO, "TestRPC called ! (Sender: %d)", sender); 38 | Log(LOG_INFO, "Parameter 1 (int): %d", NBN_RPC_GetInt(params, 0)); 39 | Log(LOG_INFO, "Parameter 2 (float): %f", NBN_RPC_GetFloat(params, 1)); 40 | Log(LOG_INFO, "Parameter 3 (bool): %d", NBN_RPC_GetBool(params, 2)); 41 | 42 | NBN_GameServer_CallRPC( 43 | TEST_RPC_2_ID, 44 | sender, 45 | NBN_RPC_GetInt(params, 0) * NBN_RPC_GetFloat(params, 1), 46 | "Some test string"); 47 | } 48 | 49 | int main(void) 50 | { 51 | #ifdef __EMSCRIPTEN__ 52 | NBN_WebRTC_Register(); // Register the WebRTC driver 53 | #else 54 | NBN_UDP_Register(); // Register the UDP driver 55 | #endif // __EMSCRIPTEN__ 56 | 57 | if (NBN_GameServer_StartEx(RPC_PROTOCOL_NAME, RPC_EXAMPLE_PORT) < 0) 58 | { 59 | Log(LOG_ERROR, "Failed to start the server"); 60 | 61 | // Error, quit the server application 62 | #ifdef __EMSCRIPTEN__ 63 | emscripten_force_exit(1); 64 | #else 65 | return 1; 66 | #endif 67 | } 68 | 69 | int ret = NBN_GameServer_RegisterRPC(TEST_RPC_ID, TEST_RPC_SIGNATURE, TestRPC); 70 | 71 | assert(ret == 0); 72 | 73 | ret = NBN_GameServer_RegisterRPC(TEST_RPC_2_ID, TEST_RPC_2_SIGNATURE, NULL); 74 | 75 | assert(ret == 0); 76 | 77 | // Number of seconds between server ticks 78 | double dt = 1.0 / RPC_TICK_RATE; 79 | 80 | while (true) 81 | { 82 | int ev; 83 | 84 | // Poll for server events 85 | while ((ev = NBN_GameServer_Poll()) != NBN_NO_EVENT) 86 | { 87 | if (ev < 0) 88 | { 89 | Log(LOG_ERROR, "Something went wrong"); 90 | 91 | // Error, quit the server application 92 | error = true; 93 | break; 94 | } 95 | 96 | switch (ev) 97 | { 98 | // New connection request... 99 | case NBN_NEW_CONNECTION: 100 | NBN_GameServer_AcceptIncomingConnection(); 101 | 102 | break; 103 | 104 | // A client has disconnected 105 | case NBN_CLIENT_DISCONNECTED: 106 | break; 107 | } 108 | } 109 | 110 | // Pack all enqueued messages as packets and send them 111 | if (NBN_GameServer_SendPackets() < 0) 112 | { 113 | Log(LOG_ERROR, "Failed to send packets"); 114 | 115 | // Error, quit the server application 116 | error = true; 117 | break; 118 | } 119 | 120 | // Cap the server tick rate 121 | ExampleSleep(dt); 122 | } 123 | 124 | // Stop the server 125 | NBN_GameServer_Stop(); 126 | 127 | ret = error ? 1 : 0; 128 | 129 | #ifdef __EMSCRIPTEN__ 130 | emscripten_force_exit(ret); 131 | #else 132 | return ret; 133 | #endif 134 | } 135 | -------------------------------------------------------------------------------- /examples/rpc/shared.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | // Sleep function 28 | #if defined(__EMSCRIPTEN__) 29 | #include 30 | #elif defined(_WIN32) || defined(_WIN64) 31 | #include 32 | #include 33 | #include 34 | #else 35 | #include 36 | #endif 37 | 38 | #include "shared.h" 39 | 40 | // Sleep for a given amount of seconds 41 | // Used to limit client and server tick rate 42 | void ExampleSleep(double sec) 43 | { 44 | #if defined(__EMSCRIPTEN__) 45 | emscripten_sleep(sec * 1000); 46 | #elif defined(_WIN32) || defined(_WIN64) 47 | Sleep(sec * 1000); 48 | #else /* UNIX / OSX */ 49 | long nanos = sec * 1e9; 50 | struct timespec t = {.tv_sec = nanos / 999999999, .tv_nsec = nanos % 999999999}; 51 | 52 | nanosleep(&t, &t); 53 | #endif 54 | } 55 | 56 | static const char *log_type_strings[] = { 57 | "INFO", 58 | "ERROR", 59 | "DEBUG", 60 | "TRACE" 61 | }; 62 | 63 | // Basic logging function 64 | void Log(int type, const char *fmt, ...) 65 | { 66 | va_list args; 67 | 68 | va_start(args, fmt); 69 | 70 | printf("[%s] ", log_type_strings[type]); 71 | vprintf(fmt, args); 72 | printf("\n"); 73 | 74 | va_end(args); 75 | } 76 | -------------------------------------------------------------------------------- /examples/rpc/shared.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | #ifndef RPC_EXAMPLE_SHARED_H 24 | #define RPC_EXAMPLE_SHARED_H 25 | 26 | #include 27 | 28 | #if defined(_WIN32) || defined(_WIN64) 29 | 30 | /* 31 | * The following defines are meant to avoid conflicts between raylib and windows.h 32 | * https://github.com/raysan5/raylib/issues/857 33 | */ 34 | 35 | // If defined, the following flags inhibit definition of the indicated items 36 | #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ 37 | #define NOVIRTUALKEYCODES // VK_* 38 | #define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* 39 | #define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* 40 | #define NOSYSMETRICS // SM_* 41 | #define NOMENUS // MF_* 42 | #define NOICONS // IDI_* 43 | #define NOKEYSTATES // MK_* 44 | #define NOSYSCOMMANDS // SC_* 45 | #define NORASTEROPS // Binary and Tertiary raster ops 46 | #define NOSHOWWINDOW // SW_* 47 | #define OEMRESOURCE // OEM Resource values 48 | #define NOATOM // Atom Manager routines 49 | #define NOCLIPBOARD // Clipboard routines 50 | #define NOCOLOR // Screen colors 51 | #define NOCTLMGR // Control and Dialog routines 52 | #define NODRAWTEXT // DrawText() and DT_* 53 | #define NOGDI // All GDI defines and routines 54 | #define NOKERNEL // All KERNEL defines and routines 55 | #define NOUSER // All USER defines and routines 56 | /*#define NONLS // All NLS defines and routines*/ 57 | #define NOMB // MB_* and MessageBox() 58 | #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines 59 | #define NOMETAFILE // typedef METAFILEPICT 60 | #define NOMINMAX // Macros min(a,b) and max(a,b) 61 | #define NOMSG // typedef MSG and associated routines 62 | #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* 63 | #define NOSCROLL // SB_* and scrolling routines 64 | #define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. 65 | #define NOSOUND // Sound driver routines 66 | #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines 67 | #define NOWH // SetWindowsHook and WH_* 68 | #define NOWINOFFSETS // GWL_*, GCL_*, associated routines 69 | #define NOCOMM // COMM driver routines 70 | #define NOKANJI // Kanji support stuff. 71 | #define NOHELP // Help engine interface. 72 | #define NOPROFILER // Profiler interface. 73 | #define NODEFERWINDOWPOS // DeferWindowPos routines 74 | #define NOMCX // Modem Configuration Extensions 75 | 76 | // Type required before windows.h inclusion 77 | typedef struct tagMSG *LPMSG; 78 | 79 | #include // Has to be included before windows.h 80 | #include 81 | 82 | #endif // WINDOWS 83 | 84 | #define RPC_PROTOCOL_NAME "rpc-example" 85 | #define RPC_EXAMPLE_PORT 42042 86 | #define RPC_TICK_RATE 30 87 | #define TEST_RPC_ID 0 88 | #define TEST_RPC_2_ID 1 89 | #define TEST_RPC_SIGNATURE NBN_RPC_BuildSignature(3, NBN_RPC_PARAM_INT, NBN_RPC_PARAM_FLOAT, NBN_RPC_PARAM_BOOL) 90 | #define TEST_RPC_2_SIGNATURE NBN_RPC_BuildSignature(2, NBN_RPC_PARAM_FLOAT, NBN_RPC_PARAM_STRING) 91 | #define TEST_VERIFY(exp) if(!(exp)) abort() 92 | 93 | // nbnet logging 94 | // nbnet does not implement any logging capabilities, you need to provide your own 95 | enum 96 | { 97 | LOG_INFO, 98 | LOG_ERROR, 99 | LOG_DEBUG, 100 | LOG_TRACE, 101 | LOG_WARNING 102 | }; 103 | 104 | #define NBN_LogInfo(...) Log(LOG_INFO, __VA_ARGS__) 105 | #define NBN_LogError(...) Log(LOG_ERROR, __VA_ARGS__) 106 | #define NBN_LogDebug(...) Log(LOG_DEBUG, __VA_ARGS__) 107 | #define NBN_LogWarning(...) Log(LOG_WARNING, __VA_ARGS__) 108 | #define NBN_LogTrace(...) (void)0; 109 | 110 | void Log(int, const char *, ...); 111 | 112 | #include "../../nbnet.h" 113 | 114 | #ifdef __EMSCRIPTEN__ 115 | #include "../../net_drivers/webrtc.h" 116 | #else 117 | #include "../../net_drivers/udp.h" 118 | #endif 119 | 120 | void ExampleSleep(double); 121 | 122 | #endif /* RPC_EXAMPLE_SHARED_H */ 123 | -------------------------------------------------------------------------------- /logo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/nbnet/79a3bed112037377ed37ac129218efc512ff565a/logo/.DS_Store -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/nbnet/79a3bed112037377ed37ac129218efc512ff565a/logo/logo.png -------------------------------------------------------------------------------- /net_drivers/webrtc/js/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | // --- Game server API --- 24 | 25 | mergeInto(LibraryManager.library, { 26 | __js_game_server_send_packet_to__proxy: 'sync', 27 | __js_game_server_dequeue_packet__proxy: 'sync', 28 | __js_game_server_dequeue_packet__deps: ['$writeArrayToMemory'], 29 | 30 | __js_game_server_init: function (protocol_id, use_https, key_pem, cert_pem) { 31 | const nbnet = require('nbnet') 32 | 33 | const signalingServer = new nbnet.Standalone.SignalingServer( 34 | protocol_id, 35 | use_https ? { https: true, key: UTF8ToString(key_pem), cert: UTF8ToString(cert_pem) } : {} 36 | ) 37 | 38 | this.gameServer = new nbnet.GameServer(signalingServer) 39 | }, 40 | 41 | __js_game_server_start: function (port) { 42 | return Asyncify.handleSleep(function (wakeUp) { 43 | this.gameServer.start(port).then(() => { 44 | wakeUp(0) 45 | }).catch(_ => { 46 | wakeUp(-1) 47 | }) 48 | }) 49 | }, 50 | 51 | __js_game_server_dequeue_packet: function(peerIdPtr, bufferPtr) { 52 | const packet = this.gameServer.packets.shift() 53 | 54 | if (packet) { 55 | const packetData = packet[0] 56 | const packetSenderId = packet[1] 57 | const byteArray = new Uint8Array(packetData) 58 | 59 | setValue(peerIdPtr, packetSenderId, 'i32') 60 | writeArrayToMemory(byteArray, bufferPtr) 61 | 62 | return packetData.byteLength 63 | } else { 64 | return 0 65 | } 66 | }, 67 | 68 | __js_game_server_send_packet_to: function (packetPtr, packetSize, peerId) { 69 | const data = new Uint8Array(Module.HEAPU8.subarray(packetPtr, packetPtr + packetSize)) 70 | 71 | this.gameServer.send(data, peerId) 72 | }, 73 | 74 | __js_game_server_close_client_peer: function(peerId) { 75 | this.gameServer.closePeer(peerId) 76 | }, 77 | 78 | __js_game_server_stop: function() { 79 | this.gameServer.stop() 80 | } 81 | }) 82 | 83 | // --- Game client API --- 84 | 85 | mergeInto(LibraryManager.library, { 86 | __js_game_client_send_packet__proxy: 'sync', 87 | __js_game_client_dequeue_packet__proxy: 'sync', 88 | __js_game_client_dequeue_packet__deps: ['$writeArrayToMemory'], 89 | 90 | __js_game_client_init: function(protocol_id, use_https) { 91 | let nbnet 92 | 93 | if (typeof window === 'undefined') { 94 | // we are running in node so we can use require 95 | nbnet = require('nbnet') 96 | } else { 97 | // we are running in a web browser so we used to "browserified" nbnet (see nbnet.js) 98 | nbnet = Module.nbnet 99 | } 100 | 101 | const signalingClient = new nbnet.Standalone.SignalingClient(protocol_id, { https: use_https }) 102 | 103 | this.gameClient = new nbnet.GameClient(signalingClient) 104 | }, 105 | 106 | __js_game_client_start: function(hostPtr, port) { 107 | return Asyncify.handleSleep(function (wakeUp) { 108 | this.gameClient.connect(UTF8ToString(hostPtr), port).then(() => { 109 | wakeUp(0) 110 | }).catch(_ => { 111 | wakeUp(-1) 112 | }) 113 | }) 114 | }, 115 | 116 | __js_game_client_dequeue_packet: function(bufferPtr) { 117 | const packet = this.gameClient.packets.shift() 118 | 119 | if (packet) { 120 | const byteArray = new Uint8Array(packet) 121 | 122 | writeArrayToMemory(byteArray, bufferPtr) 123 | 124 | return packet.byteLength 125 | } else { 126 | return 0 127 | } 128 | }, 129 | 130 | __js_game_client_send_packet: function (packetPtr, packetSize) { 131 | const data = new Uint8Array(Module.HEAPU8.subarray(packetPtr, packetPtr + packetSize)) 132 | 133 | this.gameClient.send(data) 134 | }, 135 | 136 | __js_game_client_close: function() { 137 | Asyncify.handleSleep(function (wakeUp) { 138 | this.gameClient.close().then(wakeUp).catch(wakeUp) 139 | }); 140 | } 141 | }) 142 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/game_client.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | const loggerFactory = require('./logger.js') 24 | const Peer = require('./peer.js') 25 | 26 | function GameClient(signalingClient) { 27 | this.signalingClient = signalingClient 28 | this.peer = new Peer(0, signalingClient) 29 | this.packets = [] 30 | this.logger = loggerFactory.createLogger('GameClient') 31 | 32 | this.signalingClient.onDataReceived = (data) => { 33 | this.peer.notifySignalingData(data) 34 | } 35 | 36 | this.signalingClient.onClosed = () => { 37 | // this.onClosed() 38 | } 39 | 40 | this.peer.onError = (err) => { 41 | this.logger.info('Peer has ecountered an error: %s. Closing connection', err) 42 | 43 | this.signalingClient.close() 44 | } 45 | 46 | this.peer.onClosed = (err) => { 47 | this.logger.info('Peer closed') 48 | 49 | this.signalingClient.close() 50 | } 51 | 52 | this.peer.onPacketReceived = (packet) => { 53 | this.packets.push(packet) 54 | } 55 | } 56 | 57 | GameClient.prototype.connect = function(host, port) { 58 | this.logger.info('Connecting...') 59 | 60 | return new Promise((resolve, reject) => { 61 | this.signalingClient.connect(host, port).then(() => { 62 | this.logger.info('Creating server peer...') 63 | 64 | this.peer.onConnected = () => { 65 | this.logger.info('Connected') 66 | 67 | resolve() 68 | } 69 | 70 | this.peer.onConnectionError = (err) => { 71 | this.logger.info('Connection failed: %s', err) 72 | 73 | reject(err) 74 | } 75 | 76 | this.peer.connect() 77 | }).catch((err) => { 78 | reject(err) 79 | }) 80 | }) 81 | } 82 | 83 | GameClient.prototype.send = function(data) { 84 | this.peer.send(data) 85 | } 86 | 87 | GameClient.prototype.close = function() { 88 | return new Promise((resolve, reject) => { 89 | this.signalingClient.close() 90 | .then(() => resolve()) 91 | .catch(() => reject()) 92 | }) 93 | } 94 | 95 | module.exports = GameClient 96 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/game_server.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | const loggerFactory = require('./logger.js') 24 | const Peer = require('./peer.js') 25 | 26 | function GameServer(signalingServer) { 27 | this.signalingServer = signalingServer 28 | this.peers = {} 29 | this.packets = [] 30 | this.nextPeerId = 1; // nbnet connection ids start at 1 31 | this.logger = loggerFactory.createLogger('GameServer') 32 | } 33 | 34 | GameServer.prototype.start = function(port) { 35 | if (this.signalingServer.isSecure()) { 36 | this.logger.info('Starting (HTTPS is enabled)...') 37 | } else { 38 | this.logger.info('Starting (HTTPS is disabled)...') 39 | } 40 | 41 | return new Promise((resolve, reject) => { 42 | this.signalingServer.onConnection = (connection) => { handleConnection(this, connection) } 43 | 44 | this.signalingServer.start(port).then(() => { 45 | this.logger.info('Started') 46 | 47 | resolve() 48 | }).catch((err) => { 49 | this.logger.error('Failed to start: %s', err) 50 | 51 | reject(err) 52 | }) 53 | }) 54 | } 55 | 56 | GameServer.prototype.send = function(packet, peerId) { 57 | const peer = this.peers[peerId] 58 | 59 | if (peer) { 60 | peer.send(packet) 61 | } else { 62 | this.logger.warn("Trying to send packet to an unknown peer: " + peerId) 63 | } 64 | } 65 | 66 | GameServer.prototype.closePeer = function(peerId) { 67 | const peer = this.peers[peerId] 68 | 69 | if (peer) { 70 | peer.close() 71 | } 72 | } 73 | 74 | GameServer.prototype.stop = function() { 75 | this.signalingServer.stop() 76 | } 77 | 78 | function handleConnection(gameServer, connection) { 79 | const peer = new Peer(gameServer.nextPeerId++, connection) 80 | 81 | peer.onConnected = () => { 82 | gameServer.logger.info('Peer %d is connected', peer.id) 83 | 84 | // gameServer.onPeerConnected(peer.id) 85 | } 86 | 87 | peer.onClosed = () => { 88 | gameServer.logger.info('Peer %d has disconnected', peer.id) 89 | 90 | removePeer(gameServer, peer) 91 | // gameServer.onPeerDisconnected(peer.id) 92 | } 93 | 94 | peer.onError = (err) => { 95 | gameServer.logger.info('Peer %d has ecountered an error: %s. Closing peer', peer.id, err) 96 | 97 | peer.close() 98 | removePeer(gameServer, peer) // make sure the peer is acutally removed, may not be needed 99 | } 100 | 101 | peer.onPacketReceived = (packet) => { 102 | if (gameServer.onPacketReceived) { 103 | gameServer.onPacketReceived(packet, peer.id) 104 | } else { 105 | gameServer.packets.push([packet, peer.id]) 106 | } 107 | } 108 | 109 | gameServer.logger.info('Created new peer (id: %d) for connection %d', peer.id, connection.id) 110 | 111 | gameServer.peers[peer.id] = peer 112 | 113 | connection.onMessageReceived = (msg) => { 114 | gameServer.logger.info('Received signaling message for connection %s: %s', connection.id, msg) 115 | 116 | peer.notifySignalingData(JSON.parse(msg)) 117 | } 118 | 119 | connection.onClosed = () => { 120 | gameServer.logger.info('Connection %s closed, will close peer %d', connection.id, peer.id) 121 | 122 | peer.close() 123 | } 124 | } 125 | 126 | function removePeer(gameServer, peer) { 127 | gameServer.logger.info('Removing peer %d', peer.id) 128 | 129 | for (const peerId in gameServer.peers) { 130 | if (gameServer.peers.hasOwnProperty(peerId) && gameServer.peers[peerId].id === peer.id) { 131 | delete gameServer.peers[peerId] 132 | return 133 | } 134 | } 135 | } 136 | 137 | module.exports = GameServer 138 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | module.exports = { 24 | GameServer: require('./game_server.js'), 25 | GameClient: require('./game_client.js'), 26 | Standalone: { 27 | SignalingServer: require('./standalone/signaling_server.js'), 28 | SignalingClient: require('./standalone/signaling_client.js') 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | var exports = module.exports = {} 24 | 25 | const { createLogger, format, transports } = require('winston') 26 | const { combine, timestamp, label, printf } = format 27 | 28 | exports.createLogger = function(name, options = {}) { 29 | const loggerFormat = combine( 30 | label({ label: name }), 31 | timestamp(), 32 | format.colorize(), 33 | format.splat(), 34 | printf(({ level, message, label, timestamp }) => `${timestamp} [${label}] ${level}: ${message}`) 35 | ) 36 | 37 | const logger = createLogger({ 38 | level: 'info', 39 | format: loggerFormat, 40 | transports: [ 41 | new transports.Console() 42 | ] 43 | }) 44 | 45 | return logger 46 | } 47 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/nbnet.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | Module.nbnet = require('./index.js') 24 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/peer.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | const webrtc = require('@roamhq/wrtc') 24 | const RTCPeerConnection = webrtc.RTCPeerConnection 25 | const RTCSessionDescription = webrtc.RTCSessionDescription 26 | const loggerFactory = require('./logger.js') 27 | 28 | function Peer(id, connection) { 29 | this.id = id 30 | this.connection = connection 31 | this.candidates = [] 32 | this.peerConnection = new RTCPeerConnection({ 'iceServers': [{ 'urls': 'stun:stun01.sipphone.com' }] }) 33 | this.channel = this.peerConnection.createDataChannel('unreliable', 34 | { negotiated: true, id: 0, maxRetransmits: 0, ordered: false }) 35 | this.channel.binaryType = 'arraybuffer' 36 | 37 | // peer connection event listeners 38 | this.peerConnection.addEventListener('icecandidate', ({ candidate }) => { onIceCandidate(this, candidate) }) 39 | this.peerConnection.addEventListener('signalingstatechange', () => { onSignalingStateChange(this) }) 40 | this.peerConnection.addEventListener('icegatheringstatechange', () => { onIceGatheringStateChanged(this) }) 41 | this.peerConnection.addEventListener('icecandidateerror', (ev) => { 42 | this.logger.error('Error while gathering ice candidates: %s', ev.errorCode) 43 | }) 44 | 45 | this.channel.addEventListener('open', () => { onDataChannelOpened(this, this.channel) }) 46 | this.channel.addEventListener('error', () => { onDataChannelError(this, this.channel) }) 47 | this.channel.addEventListener('message', (ev) => { onPacketReceived(this, ev.data) }) 48 | 49 | this.logger = loggerFactory.createLogger(`Peer(${this.id})`) 50 | } 51 | 52 | Peer.prototype.connect = function() { 53 | this.state = 'connecting' 54 | 55 | // OfferToReceiveAudio & OfferToReceiveVideo seems to be required in Chrome even if we only need data channels 56 | this.peerConnection.createOffer({ mandatory: { OfferToReceiveAudio: true, OfferToReceiveVideo: true } }).then((description) => { 57 | this.peerConnection.setLocalDescription(description).then(() => { 58 | this.logger.info('Offer created and set as local description') 59 | 60 | this.connection.send(description) 61 | }).catch((err) => { 62 | raiseError(this, `setLocalDescription: ${err}`) 63 | }) 64 | }).catch((err) => { 65 | raiseError(this, `createOffer: ${err}`) 66 | }) 67 | } 68 | 69 | Peer.prototype.close = function() { 70 | this.logger.info('Closing peer...') 71 | 72 | this.peerConnection.close() 73 | this.connection.close() 74 | } 75 | 76 | Peer.prototype.notifySignalingData = function(data) { 77 | if ('type' in data) { 78 | if (data.type === 'offer') { 79 | handleOffer(this, data) 80 | } else if (data.type === 'answer') { 81 | handleAnswer(this, data) 82 | } 83 | } else if ('candidate' in data) { 84 | handleCandidate(this, data.candidate) 85 | } else if ('signaling' in data) { 86 | if (data.signaling.ready_for_candidates) { 87 | this.logger.info('Remote peer is ready to receive our ice candidates') 88 | 89 | this.isRemotePeerReadyToReceiveRemoteIceCandidates = true 90 | } 91 | } 92 | } 93 | 94 | Peer.prototype.send = function(data) { 95 | try { 96 | this.channel.send(data) 97 | } catch(err) { 98 | this.logger.error('Failed to send: ' + err) 99 | 100 | this.close() 101 | } 102 | } 103 | 104 | function onIceCandidate(peer, candidate) { 105 | if (candidate) { 106 | peer.logger.info('Got new candidate, save it to the candidates list') 107 | 108 | peer.candidates.push(candidate) 109 | } 110 | } 111 | 112 | function onSignalingStateChange(peer) { 113 | if (peer.peerConnection.signalingState == 'closed') { 114 | peer.logger.info('Closed') 115 | 116 | peer.onClosed() 117 | } 118 | } 119 | 120 | function onIceGatheringStateChanged(peer) { 121 | peer.logger.info('Ice gathering state changed: %s', peer.peerConnection.iceGatheringState) 122 | 123 | if (peer.peerConnection.iceGatheringState === 'complete') { 124 | peer.logger.info('All candidates gathered, waiting for the remote peer to be ready to receive them') 125 | 126 | waitForRemotePeerToBeReadyToReceiveIceCandidates(peer).then(() => { 127 | if (peer.state !== 'connected') { 128 | sendCandidates(peer) 129 | } 130 | }).catch((err) => { 131 | raiseError(peer, `waitForRemotePeerToBeReadyToReceiveIceCandidates: ${err}`) 132 | }) 133 | } 134 | } 135 | 136 | function handleOffer(peer, offer) { 137 | peer.logger.info('Got offer') 138 | 139 | peer.peerConnection.setRemoteDescription(new RTCSessionDescription(offer)).then(() => { 140 | onRemoteDescriptionSet(peer) 141 | createAnswer(peer) 142 | }).catch((err) => { 143 | raiseError(peer, `setRemoteDescription: ${err}`) 144 | }) 145 | } 146 | 147 | function handleAnswer(peer, answer) { 148 | peer.logger.info('Got answer') 149 | 150 | peer.peerConnection.setRemoteDescription(answer).then(() => { 151 | onRemoteDescriptionSet(peer) 152 | }).catch((err) => { 153 | raiseError(peer, `setRemoteDescription: ${err}`) 154 | }) 155 | } 156 | 157 | function onRemoteDescriptionSet(peer) { 158 | // remote description is set so we can now add the other peer ice candidates 159 | // notify the other peer that we are ready to treat his ice candidates 160 | // addIceCandidate fail if remote description is not set 161 | 162 | peer.logger.info('Remote description set, notify remote peer that we are ready to receive his ice candidates') 163 | 164 | peer.connection.send({ signaling: { ready_for_candidates: true } }) 165 | } 166 | 167 | function handleCandidate(peer, candidate) { 168 | peer.logger.info(`Got candidate: ${JSON.stringify(candidate)}`) 169 | 170 | if (candidate.candidate) { 171 | peer.peerConnection.addIceCandidate(candidate).then(() => { 172 | peer.logger.info('Candidate added') 173 | }).catch((err) => { 174 | raiseError(peer, `addIceCandidate: ${err}`) 175 | }) 176 | } 177 | } 178 | 179 | function createAnswer(peer) { 180 | peer.peerConnection.createAnswer().then((answer) => { 181 | peer.logger.info('Answer created') 182 | 183 | peer.peerConnection.setLocalDescription(answer).then(() => { 184 | peer.logger.info('Answer set as local description, signaling it') 185 | 186 | peer.connection.send(answer) 187 | }).catch((err) => { 188 | raiseError(peer, `setLocalDescription: ${err}`) 189 | }) 190 | }).catch((err) => { 191 | raiseError(peer, `createAnswer: ${err}`) 192 | }) 193 | } 194 | 195 | function waitForRemotePeerToBeReadyToReceiveIceCandidates(peer) { 196 | return new Promise((resolve, reject) => { 197 | const timeoutId = setTimeout(() => { 198 | reject('timeout') 199 | }, 5000) 200 | 201 | const intervalId = setInterval(() => { 202 | if (peer.state === 'connected' || peer.isRemotePeerReadyToReceiveRemoteIceCandidates) { 203 | clearTimeout(timeoutId) 204 | clearInterval(intervalId) 205 | resolve() 206 | } 207 | }, 500) 208 | }) 209 | } 210 | 211 | function sendCandidates(peer) { 212 | peer.logger.info('Sending all gather candidates...') 213 | 214 | peer.candidates.forEach((candidate) => { 215 | peer.connection.send({ candidate: candidate }) 216 | }) 217 | } 218 | 219 | function onDataChannelOpened(peer, dataChannel) { 220 | peer.logger.info('%s data channel opened (id: %d)', dataChannel.label, dataChannel.id) 221 | 222 | peer.state = 'connected' 223 | 224 | peer.onConnected() 225 | } 226 | 227 | function onDataChannelError(peer, dataChannel, err) { 228 | raiseError(peer, `Data channel ${dataChannel.label} (id: ${dataChannel.id}) error: ${err}`) 229 | } 230 | 231 | function onPacketReceived(peer, packet) { 232 | peer.onPacketReceived(packet) 233 | } 234 | 235 | function raiseError(peer, err) { 236 | peer.logger.error(err) 237 | 238 | if (peer.state === 'connecting' && peer.onConnectionError) { 239 | peer.onConnectionError(this) 240 | } else if (peer.onError) { 241 | peer.onError(err) 242 | } 243 | } 244 | 245 | module.exports = Peer 246 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/standalone/connection.js: -------------------------------------------------------------------------------- 1 | function Connection(connection) { 2 | this.connection = connection 3 | this.id = connection.remoteAddress 4 | 5 | connection.on('message', (msg) => { this.onMessageReceived(msg.utf8Data) }) 6 | connection.on('close', () => { this.onClosed() }) 7 | } 8 | 9 | Connection.prototype.send = function(data) { 10 | this.connection.sendUTF(JSON.stringify(data)) 11 | } 12 | 13 | Connection.prototype.close = function() { 14 | this.connection.close() 15 | } 16 | 17 | module.exports = Connection -------------------------------------------------------------------------------- /net_drivers/webrtc/js/standalone/signaling_client.js: -------------------------------------------------------------------------------- 1 | const loggerFactory = require('../logger.js') 2 | 3 | function SignalingClient(protocol_id, options) { 4 | this.protocol = protocol_id.toString() 5 | this.logger = loggerFactory.createLogger('StandaloneSignalingClient') 6 | this.connected = false 7 | this.options = options 8 | } 9 | 10 | SignalingClient.prototype.connect = function(host, port) { 11 | return new Promise((resolve, reject) => { 12 | const uri = this.options['https'] ? `wss://${host}:${port}` : `ws://${host}:${port}` 13 | 14 | this.logger.info(this.options['https']) 15 | this.logger.info(`Connecting to ${uri}...`) 16 | 17 | const WebSocket = require('websocket').w3cwebsocket 18 | 19 | this.ws = new WebSocket(uri) 20 | 21 | this.ws.onclose = (ev) => { 22 | if (this.connected) { 23 | this.logger.error('Connection closed') 24 | 25 | this.connected = false 26 | 27 | this.onClosed() 28 | } else { 29 | this.logger.error('Connection failed') 30 | 31 | reject() 32 | } 33 | } 34 | 35 | this.ws.onopen = () => { 36 | this.logger.info('Connected') 37 | 38 | this.connected = true 39 | 40 | clearTimeout(timeoutId) 41 | resolve() 42 | } 43 | 44 | this.ws.onmessage = (ev) => { 45 | this.logger.info('Received signaling data: %s', ev.data) 46 | 47 | this.onDataReceived(JSON.parse(ev.data)) 48 | } 49 | 50 | const timeoutId = setTimeout(() => { 51 | this.logger.error('Connection timeout') 52 | 53 | reject() 54 | }, 3000) 55 | }) 56 | } 57 | 58 | SignalingClient.prototype.send = function(data) { 59 | this.logger.info('Send signaling data: %s', data) 60 | 61 | this.ws.send(JSON.stringify(data)) 62 | } 63 | 64 | SignalingClient.prototype.close = function() { 65 | this.logger.info('Closing...') 66 | 67 | const WebSocket = require('websocket').w3cwebsocket 68 | 69 | return new Promise((resolve, reject) => { 70 | if (this.ws.readyState != WebSocket.OPEN) { 71 | this.logger.warn('Not opened') 72 | 73 | reject() 74 | } else { 75 | this.ws.onclose = (_) => { 76 | this.logger.info('Closed') 77 | 78 | resolve() 79 | } 80 | 81 | this.ws.close() 82 | } 83 | }) 84 | } 85 | 86 | module.exports = SignalingClient 87 | -------------------------------------------------------------------------------- /net_drivers/webrtc/js/standalone/signaling_server.js: -------------------------------------------------------------------------------- 1 | const Connection = require('./connection.js') 2 | const loggerFactory = require('../logger.js') 3 | 4 | function SignalingServer(protocol_id, options) { 5 | this.protocol = protocol_id.toString() 6 | this.logger = loggerFactory.createLogger('StandaloneSignalingServer') 7 | this.options = options 8 | } 9 | 10 | SignalingServer.prototype.start = function(port) { 11 | return new Promise((resolve, reject) => { 12 | this.logger.info('Starting (protocol: %s)...', this.protocol) 13 | 14 | var server 15 | 16 | if (this.options['https']) { 17 | const fs = require('fs') 18 | 19 | server = createHttpsServer(this, fs.readFileSync(this.options['key']), fs.readFileSync(this.options['cert'])) 20 | } else { 21 | server = createHttpServer(this) 22 | } 23 | 24 | const WebSocketServer = require('websocket').server 25 | 26 | this.wsServer = new WebSocketServer({ 27 | httpServer: server, 28 | autoAcceptConnections: false 29 | }) 30 | 31 | this.wsServer.on('request', (request) => { 32 | this.logger.info('New connection') 33 | 34 | try { 35 | this.onConnection(new Connection(request.accept(null, request.origin))) 36 | } catch (err) { 37 | this.logger.error('Connection rejected: %s', err) 38 | } 39 | }) 40 | 41 | server.listen(port, () => { 42 | this.logger.info('Started, listening on port %d...', port); 43 | 44 | resolve() 45 | }) 46 | }) 47 | } 48 | 49 | SignalingServer.prototype.stop = function() { 50 | if (this.wsServer) { 51 | this.wsServer.shutDown() 52 | } else { 53 | this.logger.error("Not started") 54 | } 55 | } 56 | 57 | SignalingServer.prototype.isSecure = function() { 58 | return this.options['https'] 59 | } 60 | 61 | function createHttpServer(signalingServer) { 62 | return require('http').createServer((request, response) => { 63 | signalingServer.logger.info('Received request for ' + request.url) 64 | 65 | response.writeHead(404) 66 | response.end() 67 | }) 68 | } 69 | 70 | function createHttpsServer(signalingServer, key, cert) { 71 | return require('https').createServer({ key: key, cert: cert }, (request, response) => { 72 | signalingServer.logger.info('Received request for ' + request.url) 73 | 74 | response.writeHead(404) 75 | response.end() 76 | }) 77 | } 78 | 79 | module.exports = SignalingServer 80 | -------------------------------------------------------------------------------- /net_drivers/webrtc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbnet", 3 | "version": "1.0", 4 | "description": "WebRTC driver for the nbnet library", 5 | "author": "BIAGINI Nathan", 6 | "license": "MIT", 7 | "main": "js/index.js", 8 | "dependencies": { 9 | "websocket": "^1.0.31", 10 | "winston": "^3.2.1", 11 | "@roamhq/wrtc": "^0.8.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /soak/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(soak) 4 | 5 | option(CPP_COMPILE OFF) 6 | 7 | # allow to compile as cpp 8 | if (CPP_COMPILE) 9 | file(GLOB_RECURSE CFILES "${CMAKE_SOURCE_DIR}/*.c") 10 | SET_SOURCE_FILES_PROPERTIES(${CFILES} PROPERTIES LANGUAGE CXX) 11 | endif (CPP_COMPILE) 12 | 13 | unset(CPP_COMPILE) 14 | 15 | add_compile_options(-Wall) 16 | 17 | if(CMAKE_COMPILER_IS_GNUCXX) 18 | add_compile_options(-Wextra -Wpedantic) 19 | endif (CMAKE_COMPILER_IS_GNUCXX) 20 | 21 | add_executable(client client.c soak.c logging.c cargs.c) 22 | add_executable(server server.c soak.c logging.c cargs.c) 23 | 24 | target_compile_definitions(client PUBLIC NBN_DEBUG NBN_DISABLE_STALE_CONNECTION_DETECTION NBN_USE_PACKET_SIMULATOR SOAK_CLIENT) 25 | target_compile_definitions(server PUBLIC NBN_DEBUG NBN_DISABLE_STALE_CONNECTION_DETECTION NBN_USE_PACKET_SIMULATOR SOAK_SERVER) 26 | 27 | option(WEBRTC_NATIVE OFF) 28 | 29 | # compile with C WebRTC driver 30 | if (WEBRTC_NATIVE) 31 | # can't compile WebRTC native driver with emscripten 32 | if (EMSCRIPTEN) 33 | message(SEND_ERROR "Can't compile WebRTC native driver with emscripten") 34 | endif (EMSCRIPTEN) 35 | 36 | message("Compiling with WebRTC native driver") 37 | 38 | target_compile_definitions(server PUBLIC WEBRTC_NATIVE) 39 | target_compile_definitions(client PUBLIC WEBRTC_NATIVE) 40 | 41 | target_link_libraries(server ${LIBDATACHANNEL_LIBRARY_PATH} m) 42 | target_link_libraries(client ${LIBDATACHANNEL_LIBRARY_PATH} m) 43 | 44 | target_include_directories(server PUBLIC "${LIBDATACHANNEL_INCLUDE_PATH}") 45 | target_include_directories(client PUBLIC "${LIBDATACHANNEL_INCLUDE_PATH}") 46 | 47 | endif (WEBRTC_NATIVE) 48 | 49 | unset(WEBRTC_NATIVE) 50 | 51 | if(WIN32) 52 | target_link_libraries(client wsock32 ws2_32) 53 | target_link_libraries(server wsock32 ws2_32) 54 | else() 55 | # link with pthread when we are not on windows 56 | target_link_libraries(client pthread) 57 | target_link_libraries(server pthread) 58 | endif(WIN32) 59 | 60 | if (UNIX) 61 | # link with libm on unix 62 | target_link_libraries(client m) 63 | target_link_libraries(server m) 64 | endif (UNIX) 65 | 66 | if (EMSCRIPTEN) 67 | set(ASYNCIFY_IMPORTS "[\"__js_game_server_start\", \"__js_game_client_start\", \"__js_game_client_close\"]") 68 | 69 | set_target_properties(server PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../net_drivers/webrtc/js/api.js \ 70 | -s TOTAL_MEMORY=30MB \ 71 | -s USE_PTHREADS=1 \ 72 | -s PTHREAD_POOL_SIZE=4 \ 73 | -s EXIT_RUNTIME=1 \ 74 | -s ASSERTIONS=1 \ 75 | -s ASYNCIFY \ 76 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 77 | 78 | set_target_properties(client PROPERTIES LINK_FLAGS "--js-library ${CMAKE_CURRENT_SOURCE_DIR}/../net_drivers/webrtc/js/api.js \ 79 | -s TOTAL_MEMORY=30MB \ 80 | -s USE_PTHREADS=1 \ 81 | -s PTHREAD_POOL_SIZE=4 \ 82 | -s EXIT_RUNTIME=1 \ 83 | -s ASSERTIONS=1 \ 84 | -s ASYNCIFY \ 85 | -s ASYNCIFY_IMPORTS=\"${ASYNCIFY_IMPORTS}\"") 86 | endif() 87 | -------------------------------------------------------------------------------- /soak/cargs.h: -------------------------------------------------------------------------------- 1 | // cargs library: https://github.com/likle/cargs 2 | 3 | #pragma once 4 | 5 | /** 6 | * This is a simple alternative cross-platform implementation of getopt, which 7 | * is used to parse argument strings submitted to the executable (argc and argv 8 | * which are received in the main function). 9 | */ 10 | 11 | #ifndef CAG_LIBRARY_H 12 | #define CAG_LIBRARY_H 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #ifdef __cplusplus 19 | extern "C" 20 | { 21 | #endif 22 | 23 | /** 24 | * An option is used to describe a flag/argument option submitted when the 25 | * program is run. 26 | */ 27 | typedef struct cag_option 28 | { 29 | const char identifier; 30 | const char *access_letters; 31 | const char *access_name; 32 | const char *value_name; 33 | const char *description; 34 | } cag_option; 35 | 36 | /** 37 | * A context is used to iterate over all options provided. It stores the parsing 38 | * state. 39 | */ 40 | typedef struct cag_option_context 41 | { 42 | const struct cag_option *options; 43 | size_t option_count; 44 | int argc; 45 | char **argv; 46 | int index; 47 | int inner_index; 48 | bool forced_end; 49 | char identifier; 50 | char *value; 51 | } cag_option_context; 52 | 53 | /** 54 | * This is just a small macro which calculates the size of an array. 55 | */ 56 | #define CAG_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 57 | 58 | 59 | /** 60 | * @brief Prints all options to the terminal. 61 | * 62 | * This function prints all options to the terminal. This can be used to 63 | * generate the output for a "--help" option. 64 | * 65 | * @param options The options which will be printed. 66 | * @param option_count The option count which will be printed. 67 | * @param destination The destination where the output will be printed. 68 | */ 69 | void cag_option_print(const cag_option *options, size_t option_count, 70 | FILE *destination); 71 | 72 | /** 73 | * @brief Prepare argument options context for parsing. 74 | * 75 | * This function prepares the context for iteration and initializes the context 76 | * with the supplied options and arguments. After the context has been prepared, 77 | * it can be used to fetch arguments from it. 78 | * 79 | * @param context The context which will be initialized. 80 | * @param options The registered options which are available for the program. 81 | * @param option_count The amount of options which are available for the 82 | * program. 83 | * @param argc The amount of arguments the user supplied in the main function. 84 | * @param argv A pointer to the arguments of the main function. 85 | */ 86 | void cag_option_prepare(cag_option_context *context, const cag_option *options, 87 | size_t option_count, int argc, char **argv); 88 | 89 | /** 90 | * @brief Fetches an option from the argument list. 91 | * 92 | * This function fetches a single option from the argument list. The context 93 | * will be moved to that item. Information can be extracted from the context 94 | * after the item has been fetched. 95 | * The arguments will be re-ordered, which means that non-option arguments will 96 | * be moved to the end of the argument list. After all options have been 97 | * fetched, all non-option arguments will be positioned after the index of 98 | * the context. 99 | * 100 | * @param context The context from which we will fetch the option. 101 | * @return Returns true if there was another option or false if the end is 102 | * reached. 103 | */ 104 | bool cag_option_fetch(cag_option_context *context); 105 | 106 | /** 107 | * @brief Gets the identifier of the option. 108 | * 109 | * This function gets the identifier of the option, which should be unique to 110 | * this option and can be used to determine what kind of option this is. 111 | * 112 | * @param context The context from which the option was fetched. 113 | * @return Returns the identifier of the option. 114 | */ 115 | char cag_option_get(const cag_option_context *context); 116 | 117 | /** 118 | * @brief Gets the value from the option. 119 | * 120 | * This function gets the value from the option, if any. If the option does not 121 | * contain a value, this function will return NULL. 122 | * 123 | * @param context The context from which the option was fetched. 124 | * @return Returns a pointer to the value or NULL if there is no value. 125 | */ 126 | const char *cag_option_get_value(const cag_option_context *context); 127 | 128 | /** 129 | * @brief Gets the current index of the context. 130 | * 131 | * This function gets the index within the argv arguments of the context. The 132 | * context always points to the next item which it will inspect. This is 133 | * particularly useful to inspect the original argument array, or to get 134 | * non-option arguments after option fetching has finished. 135 | * 136 | * @param context The context from which the option was fetched. 137 | * @return Returns the current index of the context. 138 | */ 139 | int cag_option_get_index(const cag_option_context *context); 140 | 141 | #ifdef __cplusplus 142 | } // extern "C" 143 | #endif 144 | 145 | #endif -------------------------------------------------------------------------------- /soak/logging.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | */ 24 | 25 | #include "logging.h" 26 | 27 | /* I did not write this library: https://github.com/rxi/log.c */ 28 | 29 | /** 30 | * Copyright (c) 2017 rxi 31 | * 32 | * This library is free software; you can redistribute it and/or modify it 33 | * under the terms of the MIT license. See `log.c` for details. 34 | */ 35 | 36 | static struct 37 | { 38 | void *udata; 39 | log_LockFn lock; 40 | FILE *fp; 41 | int level; 42 | int quiet; 43 | } L; 44 | 45 | static const char *level_names[] = { 46 | "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; 47 | 48 | #ifdef LOG_USE_COLOR 49 | static const char *level_colors[] = { 50 | "\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"}; 51 | #endif 52 | 53 | static void lock(void) 54 | { 55 | if (L.lock) 56 | { 57 | L.lock(L.udata, 1); 58 | } 59 | } 60 | 61 | static void unlock(void) 62 | { 63 | if (L.lock) 64 | { 65 | L.lock(L.udata, 0); 66 | } 67 | } 68 | 69 | void log_set_udata(void *udata) 70 | { 71 | L.udata = udata; 72 | } 73 | 74 | void log_set_lock(log_LockFn fn) 75 | { 76 | L.lock = fn; 77 | } 78 | 79 | void log_set_fp(FILE *fp) 80 | { 81 | L.fp = fp; 82 | } 83 | 84 | void log_set_level(int level) 85 | { 86 | L.level = level; 87 | } 88 | 89 | void log_set_quiet(int enable) 90 | { 91 | L.quiet = enable ? 1 : 0; 92 | } 93 | 94 | void log_log(int level, const char *file, int line, const char *fmt, ...) 95 | { 96 | if (level < L.level) 97 | { 98 | return; 99 | } 100 | 101 | /* Acquire lock */ 102 | lock(); 103 | 104 | /* Get current time */ 105 | time_t t = time(NULL); 106 | struct tm *lt = localtime(&t); 107 | 108 | /* Log to stderr */ 109 | if (!L.quiet) 110 | { 111 | va_list args; 112 | char buf[16]; 113 | buf[strftime(buf, sizeof(buf), "%H:%M:%S", lt)] = '\0'; 114 | #ifdef LOG_USE_COLOR 115 | fprintf( 116 | stderr, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ", 117 | buf, level_colors[level], level_names[level], file, line); 118 | #else 119 | fprintf(stderr, "%s %-5s %s:%d: ", buf, level_names[level], file, line); 120 | #endif 121 | va_start(args, fmt); 122 | vfprintf(stderr, fmt, args); 123 | va_end(args); 124 | fprintf(stderr, "\n"); 125 | fflush(stderr); 126 | } 127 | 128 | /* Log to file */ 129 | if (L.fp) 130 | { 131 | va_list args; 132 | char buf[32]; 133 | buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt)] = '\0'; 134 | fprintf(L.fp, "%s %-5s %s:%d: ", buf, level_names[level], file, line); 135 | va_start(args, fmt); 136 | vfprintf(L.fp, fmt, args); 137 | va_end(args); 138 | fprintf(L.fp, "\n"); 139 | fflush(L.fp); 140 | } 141 | 142 | /* Release lock */ 143 | unlock(); 144 | } 145 | -------------------------------------------------------------------------------- /soak/logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | */ 24 | 25 | #ifndef SOAK_LOGGING_H 26 | #define SOAK_LOGGING_H 27 | 28 | /* I did not write this library: https://github.com/rxi/log.c */ 29 | 30 | /** 31 | * Copyright (c) 2017 rxi 32 | * 33 | * This library is free software; you can redistribute it and/or modify it 34 | * under the terms of the MIT license. See `log.c` for details. 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #define LOG_VERSION "0.1.0" 42 | 43 | typedef void (*log_LockFn)(void *udata, int lock); 44 | 45 | enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL }; 46 | 47 | #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) 48 | 49 | #define Soak_LogTrace(...) log_log(LOG_TRACE, __FILENAME__, __LINE__, __VA_ARGS__) 50 | #define Soak_LogDebug(...) log_log(LOG_DEBUG, __FILENAME__, __LINE__, __VA_ARGS__) 51 | #define Soak_LogInfo(...) log_log(LOG_INFO, __FILENAME__, __LINE__, __VA_ARGS__) 52 | #define Soak_LogWarn(...) log_log(LOG_WARN, __FILENAME__, __LINE__, __VA_ARGS__) 53 | #define Soak_LogError(...) log_log(LOG_ERROR, __FILENAME__, __LINE__, __VA_ARGS__) 54 | #define Soak_LogFatal(...) log_log(LOG_FATAL, __FILENAME__, __LINE__, __VA_ARGS__) 55 | #define Soak_SetLogLevel(level) log_set_level(level) 56 | 57 | void log_set_udata(void *udata); 58 | void log_set_lock(log_LockFn fn); 59 | void log_set_fp(FILE *fp); 60 | void log_set_level(int level); 61 | void log_set_quiet(int enable); 62 | 63 | void log_log(int level, const char *file, int line, const char *fmt, ...); 64 | 65 | #endif /* SOAK_LOGGING_H */ 66 | -------------------------------------------------------------------------------- /soak/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "server": "node --experimental-wasm-threads build_web/server.js", 4 | "client": "node --experimental-wasm-threads build_web/client.js" 5 | }, 6 | "dependencies": { 7 | "nbnet": "file:../net_drivers/webrtc" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /soak/soak.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #ifdef __EMSCRIPTEN__ 29 | #include 30 | #elif !defined(_WIN32) && !defined(_WIN64) 31 | // we are on unix or osx 32 | #include 33 | #endif 34 | 35 | #include "soak.h" 36 | #include "cargs.h" 37 | 38 | static bool running = true; 39 | static SoakOptions soak_options = {0}; 40 | static unsigned int created_outgoing_soak_message_count = 0; 41 | static unsigned int created_incoming_soak_message_count = 0; 42 | static unsigned int destroyed_outgoing_soak_message_count = 0; 43 | static unsigned int destroyed_incoming_soak_message_count = 0; 44 | 45 | static void Usage(void) 46 | { 47 | #ifdef SOAK_CLIENT 48 | 49 | #ifdef WEBRTC_NATIVE 50 | printf("Usage: client --message_count= --channel_count= [--packet_loss=] \ 51 | [--packet_duplication=] [--ping=] [--jitter=] [--webrtc]\n"); 52 | #else 53 | printf("Usage: client --message_count= --channel_count= [--packet_loss=] \ 54 | [--packet_duplication=] [--ping=] [--jitter=]\n"); 55 | #endif // WEBRTC_NATIVE 56 | 57 | #endif // SOAK_CLIENT 58 | 59 | #ifdef SOAK_SERVER 60 | printf("Usage: server --channel_count= [--packet_loss=] \ 61 | [--packet_duplication=] [--ping=] [--jitter=]\n"); 62 | #endif 63 | } 64 | 65 | int Soak_Init(int argc, char *argv[]) 66 | { 67 | (void) argc; 68 | (void) argv; 69 | 70 | srand(SOAK_SEED); 71 | 72 | SoakOptions options = Soak_GetOptions(); 73 | 74 | Soak_LogInfo("Soak test initialized (Packet loss: %f, Packet duplication: %f, Ping: %f, Jitter: %f)", 75 | options.packet_loss, options.packet_duplication, options.ping, options.jitter); 76 | 77 | #ifdef SOAK_CLIENT 78 | 79 | NBN_GameClient_RegisterMessage(SOAK_MESSAGE, 80 | (NBN_MessageBuilder)SoakMessage_CreateIncoming, 81 | (NBN_MessageDestructor)SoakMessage_Destroy, 82 | (NBN_MessageSerializer)SoakMessage_Serialize); 83 | 84 | #endif 85 | 86 | #ifdef SOAK_SERVER 87 | 88 | NBN_GameServer_RegisterMessage(SOAK_MESSAGE, 89 | (NBN_MessageBuilder)SoakMessage_CreateIncoming, 90 | (NBN_MessageDestructor)SoakMessage_Destroy, 91 | (NBN_MessageSerializer)SoakMessage_Serialize); 92 | 93 | #endif 94 | 95 | /* Packet simulator configuration */ 96 | #ifdef SOAK_CLIENT 97 | NBN_GameClient_SetPing(soak_options.ping); 98 | NBN_GameClient_SetJitter(soak_options.jitter); 99 | NBN_GameClient_SetPacketLoss(soak_options.packet_loss); 100 | NBN_GameClient_SetPacketDuplication(soak_options.packet_duplication); 101 | #endif 102 | 103 | #ifdef SOAK_SERVER 104 | NBN_GameServer_SetPing(soak_options.ping); 105 | NBN_GameServer_SetJitter(soak_options.jitter); 106 | NBN_GameServer_SetPacketLoss(soak_options.packet_loss); 107 | NBN_GameServer_SetPacketDuplication(soak_options.packet_duplication); 108 | #endif 109 | 110 | return 0; 111 | } 112 | 113 | void Soak_Deinit(void) 114 | { 115 | Soak_LogInfo("Done."); 116 | Soak_LogInfo("Memory report:\n"); 117 | // TODO 118 | } 119 | 120 | int Soak_ReadCommandLine(int argc, char *argv[]) 121 | { 122 | struct cag_option options[] = { 123 | #ifdef SOAK_CLIENT 124 | 125 | {'m', NULL, "message_count", "VALUE", "Number of messages to send"}, 126 | 127 | #ifdef WEBRTC_NATIVE 128 | 129 | {'w', NULL, "webrtc", NULL, "Use the native WebRTC driver instead of the UDP driver"}, 130 | 131 | #endif // WEBRTC_NATIVE 132 | 133 | #endif // SOAK_CLIENT 134 | 135 | {'c', NULL, "channel_count", "VALUE", "Number of channels (1-NBN_MAX_CHANNELS)"}, 136 | {'l', NULL, "packet_loss", "VALUE", "Packet loss frenquency (0-1)"}, 137 | {'d', NULL, "packet_duplication", "VALUE", "Packet duplication frequency (0-1)"}, 138 | {'p', NULL, "ping", "VALUE", "Ping in seconds"}, 139 | {'j', NULL, "jitter", "VALUE", "Jitter in seconds"} 140 | }; 141 | 142 | cag_option_context context; 143 | 144 | cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv); 145 | 146 | while (cag_option_fetch(&context)) 147 | { 148 | char option = cag_option_get(&context); 149 | 150 | #ifdef SOAK_CLIENT 151 | if (option == 'm') 152 | { 153 | const char *val = cag_option_get_value(&context); 154 | 155 | if (val) 156 | { 157 | soak_options.message_count = atoi(val); 158 | } 159 | } 160 | else if (option == 'w') 161 | { 162 | soak_options.webrtc = true; 163 | } 164 | #else 165 | if (false) {} 166 | #endif 167 | else if (option == 'c') 168 | { 169 | const char *val = cag_option_get_value(&context); 170 | 171 | if (val) 172 | { 173 | soak_options.channel_count = atoi(val); 174 | } 175 | } 176 | else if (option == 'l') 177 | { 178 | soak_options.packet_loss = atof(cag_option_get_value(&context)); 179 | } 180 | else if (option == 'd') 181 | { 182 | soak_options.packet_duplication = atof(cag_option_get_value(&context)); 183 | } 184 | else if (option == 'p') 185 | { 186 | soak_options.ping = atof(cag_option_get_value(&context)); 187 | } 188 | else if (option == 'j') 189 | { 190 | soak_options.jitter = atof(cag_option_get_value(&context)); 191 | } 192 | } 193 | 194 | if (soak_options.channel_count <= 0) 195 | { 196 | Usage(); 197 | return -1; 198 | } 199 | 200 | if (soak_options.channel_count > NBN_MAX_CHANNELS - NBN_LIBRARY_RESERVED_CHANNELS) 201 | { 202 | Soak_LogError("Channel count cannot exceed %d", NBN_MAX_CHANNELS - NBN_LIBRARY_RESERVED_CHANNELS); 203 | return -1; 204 | } 205 | 206 | #ifdef SOAK_CLIENT 207 | if (soak_options.message_count <= 0) 208 | { 209 | Usage(); 210 | return -1; 211 | } 212 | #endif 213 | 214 | if (soak_options.channel_count > SOAK_MAX_CHANNELS) 215 | { 216 | Soak_LogError("Too many channels (max: %d)", SOAK_MAX_CHANNELS); 217 | return -1; 218 | } 219 | 220 | return 0; 221 | } 222 | 223 | int Soak_MainLoop(int (*Tick)(void *), void *data) 224 | { 225 | while (running) 226 | { 227 | int ret = Tick(data); 228 | 229 | if (ret < 0) // Error 230 | return 1; 231 | 232 | if (ret == SOAK_DONE) // All soak messages have been received 233 | return 0; 234 | 235 | #ifdef __EMSCRIPTEN__ 236 | emscripten_sleep(SOAK_TICK_DT * 1000); 237 | #elif defined(_WIN32) || defined(_WIN64) 238 | Sleep(SOAK_TICK_DT * 1000); 239 | #else 240 | long nanos = SOAK_TICK_DT * 1e9; 241 | struct timespec t = { .tv_sec = nanos / 999999999, .tv_nsec = nanos % 999999999 }; 242 | 243 | nanosleep(&t, &t); 244 | #endif 245 | } 246 | 247 | return 0; 248 | } 249 | 250 | void Soak_Stop(void) 251 | { 252 | running = false; 253 | 254 | Soak_LogInfo("Soak test stopped"); 255 | } 256 | 257 | SoakOptions Soak_GetOptions(void) 258 | { 259 | return soak_options; 260 | } 261 | 262 | void Soak_Debug_PrintAddedToRecvQueue(NBN_Connection *conn, NBN_Message *msg) 263 | { 264 | (void) conn; 265 | (void) msg; 266 | // FIXME 267 | /*if (msg->header.type == NBN_MESSAGE_CHUNK_TYPE) 268 | { 269 | NBN_MessageChunk *chunk = (NBN_MessageChunk *)msg->data; 270 | 271 | Soak_LogDebug("Soak message chunk added to recv queue (chunk id: %d, chunk total: %d)", 272 | chunk->id, chunk->total); 273 | } 274 | else 275 | { 276 | SoakMessage *soak_message = (SoakMessage *)msg->data; 277 | 278 | Soak_LogDebug("Soak message added to recv queue (conn id: %d, msg id: %d, soak msg id: %d)", 279 | conn->id, msg->header.id, soak_message->id); 280 | }*/ 281 | } 282 | 283 | unsigned int Soak_GetCreatedOutgoingSoakMessageCount(void) 284 | { 285 | return created_outgoing_soak_message_count; 286 | } 287 | 288 | unsigned int Soak_GetDestroyedOutgoingSoakMessageCount(void) 289 | { 290 | return destroyed_outgoing_soak_message_count; 291 | } 292 | 293 | unsigned int Soak_GetCreatedIncomingSoakMessageCount(void) 294 | { 295 | return created_incoming_soak_message_count; 296 | } 297 | 298 | unsigned int Soak_GetDestroyedIncomingSoakMessageCount(void) 299 | { 300 | return destroyed_incoming_soak_message_count; 301 | } 302 | 303 | SoakMessage *SoakMessage_CreateIncoming(void) 304 | { 305 | SoakMessage *msg = (SoakMessage *)malloc(sizeof(SoakMessage)); 306 | 307 | msg->outgoing = false; 308 | 309 | created_incoming_soak_message_count++; 310 | 311 | return msg; 312 | } 313 | 314 | SoakMessage *SoakMessage_CreateOutgoing(void) 315 | { 316 | SoakMessage *msg = (SoakMessage *)malloc(sizeof(SoakMessage)); 317 | 318 | msg->outgoing = true; 319 | 320 | created_outgoing_soak_message_count++; 321 | 322 | return msg; 323 | } 324 | 325 | void SoakMessage_Destroy(SoakMessage *msg) 326 | { 327 | if (msg->outgoing) 328 | { 329 | destroyed_outgoing_soak_message_count++; 330 | Soak_LogDebug("Destroying outgoing soak message (destroyed count: %d, created count: %d)", 331 | destroyed_outgoing_soak_message_count, created_outgoing_soak_message_count); 332 | } 333 | else 334 | { 335 | destroyed_incoming_soak_message_count++; 336 | } 337 | 338 | free(msg); 339 | } 340 | 341 | int SoakMessage_Serialize(SoakMessage *msg, NBN_Stream *stream) 342 | { 343 | NBN_SerializeUInt(stream, msg->id, 0, UINT32_MAX); 344 | NBN_SerializeUInt(stream, msg->data_length, 1, SOAK_MESSAGE_MAX_DATA_LENGTH); 345 | NBN_SerializeBytes(stream, msg->data, msg->data_length); 346 | 347 | return 0; 348 | } 349 | -------------------------------------------------------------------------------- /soak/soak.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2024 BIAGINI Nathan 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | */ 24 | 25 | #ifndef SOAK_H_INCLUDED 26 | #define SOAK_H_INCLUDED 27 | 28 | #if defined(_WIN32) || defined(_WIN64) 29 | 30 | #include 31 | #include 32 | 33 | #endif 34 | 35 | #include 36 | #include 37 | 38 | #include "logging.h" 39 | 40 | /* nbnet logging */ 41 | #define NBN_LogInfo Soak_LogInfo 42 | #define NBN_LogTrace Soak_LogTrace 43 | #define NBN_LogDebug Soak_LogDebug 44 | #define NBN_LogError Soak_LogError 45 | #define NBN_LogWarning Soak_LogWarn 46 | 47 | #include "../nbnet.h" 48 | 49 | #define SOAK_PROTOCOL_NAME "nbnet_soak" 50 | #define SOAK_PORT 42043 51 | #define SOAK_TICK_RATE 60 52 | #define SOAK_TICK_DT (1.0 / SOAK_TICK_RATE) 53 | #define SOAK_MESSAGE_MIN_DATA_LENGTH 50 54 | #define SOAK_MESSAGE_MAX_DATA_LENGTH 4096 55 | #define SOAK_BIG_MESSAGE_PERCENTAGE 25 56 | #define SOAK_MESSAGE 42 57 | #define SOAK_SEED time(NULL) 58 | #define SOAK_DONE 1 59 | #define SOAK_MAX_CLIENTS 256 60 | #define SOAK_CLIENT_MAX_PENDING_MESSAGES 50 // max number of unacked messages at a time 61 | #define SOAK_SERVER_FULL_CODE 42 62 | #define SOAK_MAX_CHANNELS (NBN_MAX_CHANNELS - 3) 63 | 64 | typedef struct 65 | { 66 | unsigned int message_count; 67 | unsigned int channel_count; 68 | float packet_loss; /* 0 - 1 */ 69 | float packet_duplication; /* 0 - 1 */ 70 | float ping; /* in seconds */ 71 | float jitter; /* in seconds */ 72 | bool webrtc; /* use native WebRTC driver */ 73 | } SoakOptions; 74 | 75 | typedef struct 76 | { 77 | uint32_t id; 78 | unsigned int data_length; 79 | bool outgoing; 80 | uint8_t data[SOAK_MESSAGE_MAX_DATA_LENGTH]; 81 | } SoakMessage; 82 | 83 | int Soak_Init(int, char *[]); 84 | void Soak_Deinit(void); 85 | int Soak_ReadCommandLine(int, char *[]); 86 | int Soak_MainLoop(int (*Tick)(void *), void *data); 87 | void Soak_Stop(void); 88 | SoakOptions Soak_GetOptions(void); 89 | void Soak_Debug_PrintAddedToRecvQueue(NBN_Connection *, NBN_Message *); 90 | unsigned int Soak_GetCreatedOutgoingSoakMessageCount(void); 91 | unsigned int Soak_GetDestroyedOutgoingSoakMessageCount(void); 92 | unsigned int Soak_GetCreatedIncomingSoakMessageCount(void); 93 | unsigned int Soak_GetDestroyedIncomingSoakMessageCount(void); 94 | SoakMessage *SoakMessage_CreateOutgoing(void); 95 | SoakMessage *SoakMessage_CreateIncoming(void); 96 | void SoakMessage_Destroy(SoakMessage *); 97 | int SoakMessage_Serialize(SoakMessage *, NBN_Stream *); 98 | 99 | #endif // SOAK_H_INCLUDED 100 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(unit_tests C) 4 | enable_testing() 5 | 6 | add_compile_options(-Wall) 7 | 8 | if(CMAKE_COMPILER_IS_GNUCXX) 9 | add_compile_options(-Wextra -Wpedantic -Werror -Wno-unknown-pragmas) 10 | endif (CMAKE_COMPILER_IS_GNUCXX) 11 | 12 | # add_executable(message_chunks message_chunks.c CuTest.c) 13 | add_executable(string_tests string_tests.c CuTest.c) 14 | add_executable(serialization_tests serialization.c CuTest.c) 15 | 16 | target_link_libraries(string_tests -lm) 17 | target_link_libraries(serialization_tests -lm) 18 | 19 | # FIXME: adapt to latest API 20 | # add_test(message_chunks message_chunks) 21 | add_test(serialization_tests serialization_tests) 22 | add_test(string_tests string_tests) 23 | 24 | target_compile_definitions(serialization_tests PUBLIC NBN_DEBUG) 25 | -------------------------------------------------------------------------------- /tests/CuTest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "CuTest.h" 9 | 10 | /*-------------------------------------------------------------------------* 11 | * CuStr 12 | *-------------------------------------------------------------------------*/ 13 | 14 | char* CuStrAlloc(int size) 15 | { 16 | char* newStr = (char*) malloc( sizeof(char) * (size) ); 17 | return newStr; 18 | } 19 | 20 | char* CuStrCopy(const char* old) 21 | { 22 | int len = strlen(old); 23 | char* newStr = CuStrAlloc(len + 1); 24 | strcpy(newStr, old); 25 | return newStr; 26 | } 27 | 28 | /*-------------------------------------------------------------------------* 29 | * CuString 30 | *-------------------------------------------------------------------------*/ 31 | 32 | void CuStringInit(CuString* str) 33 | { 34 | str->length = 0; 35 | str->size = STRING_MAX; 36 | str->buffer = (char*) malloc(sizeof(char) * str->size); 37 | str->buffer[0] = '\0'; 38 | } 39 | 40 | CuString* CuStringNew(void) 41 | { 42 | CuString* str = (CuString*) malloc(sizeof(CuString)); 43 | str->length = 0; 44 | str->size = STRING_MAX; 45 | str->buffer = (char*) malloc(sizeof(char) * str->size); 46 | str->buffer[0] = '\0'; 47 | return str; 48 | } 49 | 50 | void CuStringDelete(CuString *str) 51 | { 52 | if (!str) return; 53 | free(str->buffer); 54 | free(str); 55 | } 56 | 57 | void CuStringResize(CuString* str, int newSize) 58 | { 59 | str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize); 60 | str->size = newSize; 61 | } 62 | 63 | void CuStringAppend(CuString* str, const char* text) 64 | { 65 | int length; 66 | 67 | if (text == NULL) { 68 | text = "NULL"; 69 | } 70 | 71 | length = strlen(text); 72 | if (str->length + length + 1 >= str->size) 73 | CuStringResize(str, str->length + length + 1 + STRING_INC); 74 | str->length += length; 75 | strcat(str->buffer, text); 76 | } 77 | 78 | void CuStringAppendChar(CuString* str, char ch) 79 | { 80 | char text[2]; 81 | text[0] = ch; 82 | text[1] = '\0'; 83 | CuStringAppend(str, text); 84 | } 85 | 86 | void CuStringAppendFormat(CuString* str, const char* format, ...) 87 | { 88 | va_list argp; 89 | char buf[HUGE_STRING_LEN]; 90 | va_start(argp, format); 91 | vsprintf(buf, format, argp); 92 | va_end(argp); 93 | CuStringAppend(str, buf); 94 | } 95 | 96 | void CuStringInsert(CuString* str, const char* text, int pos) 97 | { 98 | int length = strlen(text); 99 | if (pos > str->length) 100 | pos = str->length; 101 | if (str->length + length + 1 >= str->size) 102 | CuStringResize(str, str->length + length + 1 + STRING_INC); 103 | memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1); 104 | str->length += length; 105 | memcpy(str->buffer + pos, text, length); 106 | } 107 | 108 | /*-------------------------------------------------------------------------* 109 | * CuTest 110 | *-------------------------------------------------------------------------*/ 111 | 112 | void CuTestInit(CuTest* t, const char* name, TestFunction function) 113 | { 114 | t->name = CuStrCopy(name); 115 | t->failed = 0; 116 | t->ran = 0; 117 | t->message = NULL; 118 | t->function = function; 119 | t->jumpBuf = NULL; 120 | } 121 | 122 | CuTest* CuTestNew(const char* name, TestFunction function) 123 | { 124 | CuTest* tc = CU_ALLOC(CuTest); 125 | CuTestInit(tc, name, function); 126 | return tc; 127 | } 128 | 129 | void CuTestDelete(CuTest *t) 130 | { 131 | if (!t) return; 132 | CuStringDelete(t->message); 133 | free(t->name); 134 | free(t); 135 | } 136 | 137 | void CuTestRun(CuTest* tc) 138 | { 139 | jmp_buf buf; 140 | tc->jumpBuf = &buf; 141 | if (setjmp(buf) == 0) 142 | { 143 | tc->ran = 1; 144 | (tc->function)(tc); 145 | } 146 | tc->jumpBuf = 0; 147 | } 148 | 149 | static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string) 150 | { 151 | char buf[HUGE_STRING_LEN]; 152 | 153 | sprintf(buf, "%s:%d: ", file, line); 154 | CuStringInsert(string, buf, 0); 155 | 156 | tc->failed = 1; 157 | free(tc->message); 158 | tc->message = CuStringNew(); 159 | CuStringAppend(tc->message, string->buffer); 160 | if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0); 161 | } 162 | 163 | void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message) 164 | { 165 | CuString string; 166 | 167 | CuStringInit(&string); 168 | if (message2 != NULL) 169 | { 170 | CuStringAppend(&string, message2); 171 | CuStringAppend(&string, ": "); 172 | } 173 | CuStringAppend(&string, message); 174 | CuFailInternal(tc, file, line, &string); 175 | } 176 | 177 | void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition) 178 | { 179 | if (condition) return; 180 | CuFail_Line(tc, file, line, NULL, message); 181 | } 182 | 183 | void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, 184 | const char* expected, const char* actual) 185 | { 186 | CuString string; 187 | if ((expected == NULL && actual == NULL) || 188 | (expected != NULL && actual != NULL && 189 | strcmp(expected, actual) == 0)) 190 | { 191 | return; 192 | } 193 | 194 | CuStringInit(&string); 195 | if (message != NULL) 196 | { 197 | CuStringAppend(&string, message); 198 | CuStringAppend(&string, ": "); 199 | } 200 | CuStringAppend(&string, "expected <"); 201 | CuStringAppend(&string, expected); 202 | CuStringAppend(&string, "> but was <"); 203 | CuStringAppend(&string, actual); 204 | CuStringAppend(&string, ">"); 205 | CuFailInternal(tc, file, line, &string); 206 | } 207 | 208 | void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, 209 | int expected, int actual) 210 | { 211 | char buf[STRING_MAX]; 212 | if (expected == actual) return; 213 | sprintf(buf, "expected <%d> but was <%d>", expected, actual); 214 | CuFail_Line(tc, file, line, message, buf); 215 | } 216 | 217 | void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, 218 | double expected, double actual, double delta) 219 | { 220 | char buf[STRING_MAX]; 221 | if (fabs(expected - actual) <= delta) return; 222 | sprintf(buf, "expected <%f> but was <%f>", expected, actual); 223 | 224 | CuFail_Line(tc, file, line, message, buf); 225 | } 226 | 227 | void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, 228 | void* expected, void* actual) 229 | { 230 | char buf[STRING_MAX]; 231 | if (expected == actual) return; 232 | sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual); 233 | CuFail_Line(tc, file, line, message, buf); 234 | } 235 | 236 | 237 | /*-------------------------------------------------------------------------* 238 | * CuSuite 239 | *-------------------------------------------------------------------------*/ 240 | 241 | void CuSuiteInit(CuSuite* testSuite) 242 | { 243 | testSuite->count = 0; 244 | testSuite->failCount = 0; 245 | memset(testSuite->list, 0, sizeof(testSuite->list)); 246 | } 247 | 248 | CuSuite* CuSuiteNew(void) 249 | { 250 | CuSuite* testSuite = CU_ALLOC(CuSuite); 251 | CuSuiteInit(testSuite); 252 | return testSuite; 253 | } 254 | 255 | void CuSuiteDelete(CuSuite *testSuite) 256 | { 257 | unsigned int n; 258 | for (n=0; n < MAX_TEST_CASES; n++) 259 | { 260 | if (testSuite->list[n]) 261 | { 262 | CuTestDelete(testSuite->list[n]); 263 | } 264 | } 265 | free(testSuite); 266 | 267 | } 268 | 269 | void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase) 270 | { 271 | assert(testSuite->count < MAX_TEST_CASES); 272 | testSuite->list[testSuite->count] = testCase; 273 | testSuite->count++; 274 | } 275 | 276 | void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) 277 | { 278 | int i; 279 | for (i = 0 ; i < testSuite2->count ; ++i) 280 | { 281 | CuTest* testCase = testSuite2->list[i]; 282 | CuSuiteAdd(testSuite, testCase); 283 | } 284 | } 285 | 286 | void CuSuiteRun(CuSuite* testSuite) 287 | { 288 | int i; 289 | for (i = 0 ; i < testSuite->count ; ++i) 290 | { 291 | CuTest* testCase = testSuite->list[i]; 292 | CuTestRun(testCase); 293 | if (testCase->failed) { testSuite->failCount += 1; } 294 | } 295 | } 296 | 297 | void CuSuiteSummary(CuSuite* testSuite, CuString* summary) 298 | { 299 | int i; 300 | for (i = 0 ; i < testSuite->count ; ++i) 301 | { 302 | CuTest* testCase = testSuite->list[i]; 303 | CuStringAppend(summary, testCase->failed ? "F" : "."); 304 | } 305 | CuStringAppend(summary, "\n\n"); 306 | } 307 | 308 | void CuSuiteDetails(CuSuite* testSuite, CuString* details) 309 | { 310 | int i; 311 | int failCount = 0; 312 | 313 | if (testSuite->failCount == 0) 314 | { 315 | int passCount = testSuite->count - testSuite->failCount; 316 | const char* testWord = passCount == 1 ? "test" : "tests"; 317 | CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord); 318 | } 319 | else 320 | { 321 | if (testSuite->failCount == 1) 322 | CuStringAppend(details, "There was 1 failure:\n"); 323 | else 324 | CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount); 325 | 326 | for (i = 0 ; i < testSuite->count ; ++i) 327 | { 328 | CuTest* testCase = testSuite->list[i]; 329 | if (testCase->failed) 330 | { 331 | failCount++; 332 | CuStringAppendFormat(details, "%d) %s: %s\n", 333 | failCount, testCase->name, testCase->message->buffer); 334 | } 335 | } 336 | CuStringAppend(details, "\n!!!FAILURES!!!\n"); 337 | 338 | CuStringAppendFormat(details, "Runs: %d ", testSuite->count); 339 | CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount); 340 | CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount); 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /tests/CuTest.h: -------------------------------------------------------------------------------- 1 | #ifndef CU_TEST_H 2 | #define CU_TEST_H 3 | 4 | #include 5 | #include 6 | 7 | #define CUTEST_VERSION "CuTest 1.5b" 8 | 9 | /* CuString */ 10 | 11 | char* CuStrAlloc(int size); 12 | char* CuStrCopy(const char* old); 13 | 14 | #define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE))) 15 | 16 | #define HUGE_STRING_LEN 8192 17 | #define STRING_MAX 256 18 | #define STRING_INC 256 19 | 20 | typedef struct 21 | { 22 | int length; 23 | int size; 24 | char* buffer; 25 | } CuString; 26 | 27 | void CuStringInit(CuString* str); 28 | CuString* CuStringNew(void); 29 | void CuStringRead(CuString* str, const char* path); 30 | void CuStringAppend(CuString* str, const char* text); 31 | void CuStringAppendChar(CuString* str, char ch); 32 | void CuStringAppendFormat(CuString* str, const char* format, ...); 33 | void CuStringInsert(CuString* str, const char* text, int pos); 34 | void CuStringResize(CuString* str, int newSize); 35 | void CuStringDelete(CuString* str); 36 | 37 | /* CuTest */ 38 | 39 | typedef struct CuTest CuTest; 40 | 41 | typedef void (*TestFunction)(CuTest *); 42 | 43 | struct CuTest 44 | { 45 | char* name; 46 | TestFunction function; 47 | int failed; 48 | int ran; 49 | CuString *message; 50 | jmp_buf *jumpBuf; 51 | }; 52 | 53 | void CuTestInit(CuTest* t, const char* name, TestFunction function); 54 | CuTest* CuTestNew(const char* name, TestFunction function); 55 | void CuTestRun(CuTest* tc); 56 | void CuTestDelete(CuTest *t); 57 | 58 | /* Internal versions of assert functions -- use the public versions */ 59 | void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message); 60 | void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition); 61 | void CuAssertStrEquals_LineMsg(CuTest* tc, 62 | const char* file, int line, const char* message, 63 | const char* expected, const char* actual); 64 | void CuAssertIntEquals_LineMsg(CuTest* tc, 65 | const char* file, int line, const char* message, 66 | int expected, int actual); 67 | void CuAssertDblEquals_LineMsg(CuTest* tc, 68 | const char* file, int line, const char* message, 69 | double expected, double actual, double delta); 70 | void CuAssertPtrEquals_LineMsg(CuTest* tc, 71 | const char* file, int line, const char* message, 72 | void* expected, void* actual); 73 | 74 | /* public assert functions */ 75 | 76 | #define CuFail(tc, ms) CuFail_Line( (tc), __FILE__, __LINE__, NULL, (ms)) 77 | #define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond)) 78 | #define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond)) 79 | 80 | #define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) 81 | #define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) 82 | #define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) 83 | #define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) 84 | #define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl)) 85 | #define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl)) 86 | #define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) 87 | #define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) 88 | 89 | #define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",((p) != NULL)) 90 | #define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),((p) != NULL)) 91 | 92 | /* CuSuite */ 93 | 94 | #define MAX_TEST_CASES 1024 95 | 96 | #define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST)) 97 | 98 | typedef struct 99 | { 100 | int count; 101 | CuTest* list[MAX_TEST_CASES]; 102 | int failCount; 103 | 104 | } CuSuite; 105 | 106 | 107 | void CuSuiteInit(CuSuite* testSuite); 108 | CuSuite* CuSuiteNew(void); 109 | void CuSuiteDelete(CuSuite *testSuite); 110 | void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase); 111 | void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2); 112 | void CuSuiteRun(CuSuite* testSuite); 113 | void CuSuiteSummary(CuSuite* testSuite, CuString* summary); 114 | void CuSuiteDetails(CuSuite* testSuite, CuString* details); 115 | 116 | #endif /* CU_TEST_H */ 117 | -------------------------------------------------------------------------------- /tests/message_chunks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "CuTest.h" 5 | 6 | #define NBNET_IMPL 7 | 8 | #define NBN_LogInfo printf 9 | #define NBN_LogTrace printf 10 | #define NBN_LogDebug printf 11 | #define NBN_LogError printf 12 | 13 | #define NBN_Allocator malloc 14 | #define NBN_Deallocator free 15 | 16 | #include "../nbnet.h" 17 | 18 | typedef struct 19 | { 20 | uint8_t data[4096]; 21 | } BigMessage; 22 | 23 | #define BIG_MESSAGE_TYPE 0 24 | 25 | BigMessage *BigMessage_Create() 26 | { 27 | return malloc(sizeof(BigMessage)); 28 | } 29 | 30 | int BigMessage_Serialize(BigMessage *msg, NBN_Stream *stream) 31 | { 32 | SERIALIZE_BYTES(msg->data, 4096); 33 | 34 | return 0; 35 | } 36 | 37 | void BigMessage_Destroy(BigMessage *msg) 38 | { 39 | free(msg); 40 | } 41 | 42 | static NBN_Endpoint endpoint; 43 | 44 | NBN_Connection *Begin(NBN_Endpoint *endpoint) 45 | { 46 | NBN_Endpoint_Init(endpoint, (NBN_Config){ .protocol_name = "tests" }); 47 | NBN_Endpoint_RegisterMessageBuilder(endpoint, (NBN_MessageBuilder)BigMessage_Create, BIG_MESSAGE_TYPE); 48 | NBN_Endpoint_RegisterMessageSerializer(endpoint, (NBN_MessageSerializer)BigMessage_Serialize, BIG_MESSAGE_TYPE); 49 | NBN_Endpoint_RegisterMessageDestructor(endpoint, (NBN_MessageDestructor)BigMessage_Destroy, BIG_MESSAGE_TYPE); 50 | 51 | return NBN_Endpoint_CreateConnection(endpoint, 0); 52 | } 53 | 54 | static void End(NBN_Connection *conn, NBN_Endpoint *endpoint) 55 | { 56 | NBN_ListNode *current_node = conn->send_queue->head; 57 | 58 | for (int i = 0; current_node != NULL; i++) 59 | { 60 | NBN_Message *m = current_node->data; 61 | 62 | current_node = current_node->next; 63 | 64 | NBN_List_Remove(conn->send_queue, m); 65 | } 66 | 67 | NBN_Connection_Destroy(conn); 68 | NBN_Endpoint_Deinit(endpoint); 69 | } 70 | 71 | void Test_ChunksGeneration(CuTest *tc) 72 | { 73 | NBN_Endpoint endpoint; 74 | NBN_Connection *conn = Begin(&endpoint); 75 | BigMessage *msg = NBN_Endpoint_CreateOutgoingMessage(&endpoint, BIG_MESSAGE_TYPE, NBN_CHANNEL_RESERVED_RELIABLE); 76 | 77 | CuAssertPtrNotNull(tc, msg); 78 | 79 | /* Fill the "big message" with random bytes */ 80 | for (int i = 0; i < sizeof(msg->data); i++) 81 | msg->data[i] = rand() % 255 + 1; 82 | 83 | NBN_MeasureStream m_stream; 84 | 85 | NBN_MeasureStream_Init(&m_stream); 86 | 87 | NBN_Message message = { 88 | .header = {.type = BIG_MESSAGE_TYPE, .channel_id = NBN_CHANNEL_RESERVED_RELIABLE}, 89 | .serializer = (NBN_MessageSerializer)BigMessage_Serialize, 90 | .data = msg}; 91 | unsigned int message_size = (NBN_Message_Measure(&message, &m_stream) - 1) / 8 + 1; 92 | uint8_t buffer[4096 * 8]; 93 | NBN_WriteStream w_stream; 94 | 95 | NBN_WriteStream_Init(&w_stream, buffer, message_size); 96 | 97 | CuAssertIntEquals(tc, 0, NBN_Message_SerializeHeader( 98 | &message.header, (NBN_Stream *)&w_stream)); 99 | CuAssertIntEquals(tc, 0, BigMessage_Serialize(msg, (NBN_Stream *)&w_stream)); 100 | 101 | CuAssertIntEquals(tc, 0, NBN_Connection_EnqueueOutgoingMessage(conn)); 102 | 103 | /* Should have generated 5 chunks */ 104 | CuAssertIntEquals(tc, 5, conn->send_queue->count); 105 | 106 | /* Merging the chunks together should reconstruct the initial message */ 107 | uint8_t *r_buffer = malloc(message_size); /* used to merge chunks together */ 108 | NBN_ListNode *current_node = conn->send_queue->head; 109 | 110 | for (int i = 0; current_node != NULL; i++) 111 | { 112 | NBN_Message *chunk_msg = current_node->data; 113 | NBN_MessageChunk *chunk = ((NBN_OutgoingMessageInfo *)chunk_msg->data)->data; 114 | 115 | CuAssertIntEquals(tc, NBN_MESSAGE_CHUNK_TYPE, chunk_msg->header.type); 116 | CuAssertIntEquals(tc, i, chunk->id); 117 | CuAssertIntEquals(tc, 5, chunk->total); 118 | 119 | unsigned int cpy_size = MIN( 120 | message_size - (i * NBN_MESSAGE_CHUNK_SIZE), 121 | NBN_MESSAGE_CHUNK_SIZE); 122 | 123 | NBN_LogDebug("Read chunk %d (bytes: %d)", i, cpy_size); 124 | 125 | memcpy(r_buffer + (i * NBN_MESSAGE_CHUNK_SIZE), chunk->data, cpy_size); 126 | 127 | current_node = current_node->next; 128 | } 129 | 130 | CuAssertIntEquals(tc, 0, memcmp(r_buffer, buffer, message_size)); 131 | 132 | free(r_buffer); 133 | 134 | End(conn, &endpoint); 135 | } 136 | 137 | /* TODO: add more tests for cases like missing chunks etc. */ 138 | void Test_NBN_Channel_AddChunk(CuTest *tc) 139 | { 140 | NBN_Endpoint endpoint; 141 | NBN_Connection *conn = Begin(&endpoint); 142 | BigMessage *msg = NBN_Endpoint_CreateOutgoingMessage(&endpoint, BIG_MESSAGE_TYPE, NBN_CHANNEL_RESERVED_RELIABLE); 143 | 144 | CuAssertPtrNotNull(tc, msg); 145 | 146 | /* Fill the "big message" with random bytes */ 147 | for (int i = 0; i < sizeof(msg->data); i++) 148 | msg->data[i] = rand() % 255 + 1; 149 | 150 | NBN_Connection_EnqueueOutgoingMessage(conn); 151 | 152 | BigMessage *msg2 = NBN_Endpoint_CreateOutgoingMessage(&endpoint, BIG_MESSAGE_TYPE, NBN_CHANNEL_RESERVED_RELIABLE); 153 | 154 | CuAssertPtrNotNull(tc, msg); 155 | 156 | /* Fill the "big message" with random bytes */ 157 | for (int i = 0; i < sizeof(msg2->data); i++) 158 | msg2->data[i] = rand() % 255 + 1; 159 | 160 | NBN_Connection_EnqueueOutgoingMessage(conn); 161 | 162 | /* Should have generated 10 chunks */ 163 | CuAssertIntEquals(tc, 10, conn->send_queue->count); 164 | 165 | NBN_Message *msg_chunks[10]; 166 | 167 | for (int i = 0; i < 10; i++) 168 | { 169 | NBN_Message *m = NBN_List_GetAt(conn->send_queue, i); 170 | NBN_MessageChunk *chunk = ((NBN_OutgoingMessageInfo *)m->data)->data; 171 | 172 | msg_chunks[i] = NBN_Message_Create( 173 | NBN_MESSAGE_CHUNK_TYPE, 174 | NBN_CHANNEL_RESERVED_RELIABLE, 175 | (NBN_MessageSerializer)NBN_MessageChunk_Serialize, 176 | (NBN_MessageDestructor)NBN_MessageChunk_Destroy, 177 | false, 178 | chunk); 179 | } 180 | 181 | NBN_Channel *channel = conn->channels[NBN_CHANNEL_RESERVED_RELIABLE]; 182 | 183 | /* First message chunks */ 184 | 185 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[0])); 186 | CuAssertIntEquals(tc, 0, channel->last_received_chunk_id); 187 | CuAssertIntEquals(tc, 1, channel->chunk_count); 188 | 189 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[1])); 190 | CuAssertIntEquals(tc, 1, channel->last_received_chunk_id); 191 | CuAssertIntEquals(tc, 2, channel->chunk_count); 192 | 193 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[2])); 194 | CuAssertIntEquals(tc, 2, channel->last_received_chunk_id); 195 | CuAssertIntEquals(tc, 3, channel->chunk_count); 196 | 197 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[3])); 198 | CuAssertIntEquals(tc, 3, channel->last_received_chunk_id); 199 | CuAssertIntEquals(tc, 4, channel->chunk_count); 200 | 201 | /* This is the last chunk of the first message so it should return true */ 202 | CuAssertTrue(tc, NBN_Channel_AddChunk(channel, msg_chunks[4])); 203 | CuAssertIntEquals(tc, -1, channel->last_received_chunk_id); 204 | CuAssertIntEquals(tc, 5, channel->chunk_count); 205 | 206 | /* Reconstruct the message so the chunks buffer gets cleared */ 207 | NBN_Channel_ReconstructMessageFromChunks(channel, conn); 208 | 209 | /* Second message chunks */ 210 | 211 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[5])); 212 | CuAssertIntEquals(tc, 0, channel->last_received_chunk_id); 213 | CuAssertIntEquals(tc, 1, channel->chunk_count); 214 | 215 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[6])); 216 | CuAssertIntEquals(tc, 1, channel->last_received_chunk_id); 217 | CuAssertIntEquals(tc, 2, channel->chunk_count); 218 | 219 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[7])); 220 | CuAssertIntEquals(tc, 2, channel->last_received_chunk_id); 221 | CuAssertIntEquals(tc, 3, channel->chunk_count); 222 | 223 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[8])); 224 | CuAssertIntEquals(tc, 3, channel->last_received_chunk_id); 225 | CuAssertIntEquals(tc, 4, channel->chunk_count); 226 | 227 | /* This is the last chunk of the second message so it should return true */ 228 | CuAssertTrue(tc, NBN_Channel_AddChunk(channel, msg_chunks[9])); 229 | CuAssertIntEquals(tc, -1, channel->last_received_chunk_id); 230 | CuAssertIntEquals(tc, 5, channel->chunk_count); 231 | 232 | End(conn, &endpoint); 233 | } 234 | 235 | void Test_NBN_Channel_ReconstructMessageFromChunks(CuTest *tc) 236 | { 237 | NBN_Endpoint endpoint; 238 | NBN_Connection *conn = Begin(&endpoint); 239 | BigMessage *msg = NBN_Endpoint_CreateOutgoingMessage(&endpoint, BIG_MESSAGE_TYPE, NBN_CHANNEL_RESERVED_RELIABLE); 240 | 241 | CuAssertPtrNotNull(tc, msg); 242 | 243 | /* Fill the "big message" with random bytes */ 244 | for (int i = 0; i < sizeof(msg->data); i++) 245 | msg->data[i] = rand() % 255 + 1; 246 | 247 | CuAssertIntEquals(tc, 0, NBN_Connection_EnqueueOutgoingMessage(conn)); 248 | 249 | /* Should have generated 5 chunks */ 250 | CuAssertIntEquals(tc, 5, conn->send_queue->count); 251 | 252 | NBN_Message *msg_chunks[5]; 253 | 254 | for (int i = 0; i < 5; i++) 255 | { 256 | NBN_Message *m = NBN_List_GetAt(conn->send_queue, i); 257 | NBN_MessageChunk *chunk = ((NBN_OutgoingMessageInfo *)m->data)->data; 258 | 259 | msg_chunks[i] = NBN_Message_Create( 260 | NBN_MESSAGE_CHUNK_TYPE, 261 | NBN_CHANNEL_RESERVED_RELIABLE, 262 | (NBN_MessageSerializer)NBN_MessageChunk_Serialize, 263 | (NBN_MessageDestructor)NBN_MessageChunk_Destroy, 264 | false, 265 | chunk); 266 | } 267 | 268 | NBN_Channel *channel = conn->channels[NBN_CHANNEL_RESERVED_RELIABLE]; 269 | 270 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[0])); 271 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[1])); 272 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[2])); 273 | CuAssertTrue(tc, !NBN_Channel_AddChunk(channel, msg_chunks[3])); 274 | CuAssertTrue(tc, NBN_Channel_AddChunk(channel, msg_chunks[4])); 275 | 276 | NBN_Message *r_msg = NBN_Channel_ReconstructMessageFromChunks(channel, conn); 277 | 278 | CuAssertIntEquals(tc, 0, r_msg->header.type); 279 | CuAssertIntEquals(tc, NBN_CHANNEL_RESERVED_RELIABLE, r_msg->header.channel_id); 280 | CuAssertIntEquals(tc, 0, memcmp(r_msg->data, msg->data, 4096)); 281 | 282 | End(conn, &endpoint); 283 | } 284 | 285 | int main(int argc, char *argv[]) 286 | { 287 | CuString *output = CuStringNew(); 288 | CuSuite* suite = CuSuiteNew(); 289 | 290 | SUITE_ADD_TEST(suite, Test_ChunksGeneration); 291 | SUITE_ADD_TEST(suite, Test_NBN_Channel_AddChunk); 292 | SUITE_ADD_TEST(suite, Test_NBN_Channel_ReconstructMessageFromChunks); 293 | 294 | CuSuiteRun(suite); 295 | CuSuiteSummary(suite, output); 296 | CuSuiteDetails(suite, output); 297 | 298 | printf("%s\n", output->buffer); 299 | 300 | return suite->failCount; 301 | } 302 | -------------------------------------------------------------------------------- /tests/serialization.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "CuTest.h" 5 | 6 | #define NBNET_IMPL 7 | 8 | #define NBN_LogInfo printf 9 | #define NBN_LogTrace printf 10 | #define NBN_LogDebug printf 11 | #define NBN_LogError printf 12 | #define NBN_LogWarning printf 13 | 14 | #define NBN_Allocator malloc 15 | #define NBN_Deallocator free 16 | 17 | #include "../nbnet.h" 18 | 19 | typedef struct 20 | { 21 | float v1; 22 | float v2; 23 | float v3; 24 | } BogusMessage; 25 | 26 | int BogusMessage_Serialize(BogusMessage *msg, NBN_Stream *stream) 27 | { 28 | NBN_SerializeFloat(stream, msg->v1, -100, 100, 1); 29 | NBN_SerializeFloat(stream, msg->v2, -100, 100, 2); 30 | NBN_SerializeFloat(stream, msg->v3, -100, 100, 3); 31 | 32 | return 0; 33 | } 34 | 35 | typedef struct 36 | { 37 | uint64_t v1; 38 | uint64_t v2; 39 | uint64_t v3; 40 | uint64_t v4; 41 | } BogusMessage2; 42 | 43 | int BogusMessage2_Serialize(BogusMessage2 *msg, NBN_Stream *stream) 44 | { 45 | NBN_SerializeUInt64(stream, msg->v1); 46 | NBN_SerializeUInt64(stream, msg->v2); 47 | NBN_SerializeUInt64(stream, msg->v3); 48 | NBN_SerializeUInt64(stream, msg->v4); 49 | 50 | return 0; 51 | } 52 | 53 | void Test_SerializeFloat(CuTest *tc) 54 | { 55 | BogusMessage msg = { .v1 = 42.5, .v2 = -12.42, .v3 = -89.123 }; 56 | NBN_WriteStream w_stream; 57 | uint8_t buffer[32]; 58 | 59 | NBN_WriteStream_Init(&w_stream, buffer, sizeof(buffer)); 60 | 61 | CuAssertIntEquals(tc, 0, BogusMessage_Serialize(&msg, (NBN_Stream *)&w_stream)); 62 | 63 | NBN_WriteStream_Flush(&w_stream); 64 | 65 | BogusMessage r_msg; 66 | NBN_ReadStream r_stream; 67 | 68 | NBN_ReadStream_Init(&r_stream, buffer, sizeof(buffer)); 69 | 70 | CuAssertIntEquals(tc, 0, BogusMessage_Serialize(&r_msg, (NBN_Stream *)&r_stream)); 71 | CuAssertTrue(tc, r_msg.v1 == 42.5); 72 | CuAssertIntEquals(tc, -1242, r_msg.v2 * 100); 73 | CuAssertIntEquals(tc, -89123, r_msg.v3 * 1000); 74 | } 75 | 76 | void Test_SerializeUInt64(CuTest *tc) 77 | { 78 | BogusMessage2 msg = { 79 | .v1 = 0xFFFFFFFFFFFFFFFF, 80 | .v2 = 9223372036854775807, 81 | .v3 = 4611686018427387903, 82 | .v4 = 42000 83 | }; 84 | NBN_WriteStream w_stream; 85 | uint8_t buffer[32]; 86 | 87 | NBN_WriteStream_Init(&w_stream, buffer, sizeof(buffer)); 88 | CuAssertIntEquals(tc, 0, BogusMessage2_Serialize(&msg, (NBN_Stream *)&w_stream)); 89 | NBN_WriteStream_Flush(&w_stream); 90 | 91 | BogusMessage2 r_msg; 92 | NBN_ReadStream r_stream; 93 | 94 | NBN_ReadStream_Init(&r_stream, buffer, sizeof(buffer)); 95 | 96 | CuAssertIntEquals(tc, 0, BogusMessage2_Serialize(&r_msg, (NBN_Stream *)&r_stream)); 97 | CuAssertTrue(tc, r_msg.v1 == 0xFFFFFFFFFFFFFFFF); 98 | CuAssertTrue(tc, r_msg.v2 == 9223372036854775807); 99 | CuAssertTrue(tc, r_msg.v3 == 4611686018427387903); 100 | CuAssertTrue(tc, r_msg.v4 == 42000); 101 | } 102 | 103 | int main(int argc, char *argv[]) 104 | { 105 | (void) argc; 106 | (void) argv; 107 | 108 | CuString *output = CuStringNew(); 109 | CuSuite* suite = CuSuiteNew(); 110 | 111 | SUITE_ADD_TEST(suite, Test_SerializeFloat); 112 | SUITE_ADD_TEST(suite, Test_SerializeUInt64); 113 | 114 | CuSuiteRun(suite); 115 | CuSuiteSummary(suite, output); 116 | CuSuiteDetails(suite, output); 117 | 118 | printf("%s\n", output->buffer); 119 | 120 | return suite->failCount; 121 | } 122 | -------------------------------------------------------------------------------- /tests/string_tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "CuTest.h" 6 | 7 | static void StringReplaceAll(char *res, const char *str, const char *a, const char *b) 8 | { 9 | char *substr = strstr(str, a); 10 | size_t len_a = strlen(a); 11 | size_t len_b = strlen(b); 12 | 13 | if (substr) 14 | { 15 | int pos = substr - str; 16 | 17 | memcpy(res, str, pos); 18 | memcpy(res + pos, b, len_b); 19 | 20 | StringReplaceAll(res + pos + len_b, str + pos + len_a, a, b); 21 | } 22 | else 23 | { 24 | memcpy(res, str, strlen(str) + 1); 25 | } 26 | } 27 | 28 | void Test_StringReplaceAll(CuTest *tc) 29 | { 30 | const char *str = "foo bar plop foo plap test foo"; 31 | const char *str2 = "foo bar\nplop\n\nplap"; 32 | const char *str3 = "foo bar"; 33 | char res[128] = {0}; 34 | char res2[128] = {0}; 35 | char res3[16] = {0}; 36 | 37 | StringReplaceAll(res, str, "foo", "hello"); 38 | CuAssertStrEquals(tc, "hello bar plop hello plap test hello", res); 39 | StringReplaceAll(res2, str2, "\n", "\\n"); 40 | CuAssertStrEquals(tc, "foo bar\\nplop\\n\\nplap", res2); 41 | StringReplaceAll(res3, str3, "test", "aaaaa"); 42 | CuAssertStrEquals(tc, "foo bar", res3); 43 | } 44 | 45 | int main(int argc, char *argv[]) 46 | { 47 | CuString *output = CuStringNew(); 48 | CuSuite* suite = CuSuiteNew(); 49 | 50 | (void) argc; 51 | (void) argv; 52 | 53 | SUITE_ADD_TEST(suite, Test_StringReplaceAll); 54 | 55 | CuSuiteRun(suite); 56 | CuSuiteSummary(suite, output); 57 | CuSuiteDetails(suite, output); 58 | 59 | printf("%s\n", output->buffer); 60 | 61 | return suite->failCount; 62 | } 63 | --------------------------------------------------------------------------------