├── .ci ├── linux-build.yml └── windows-build.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── Build ├── MakeGMakeProjects.sh ├── MakeVS2015Projects.bat ├── MakeVS2017Projects.bat ├── MakeVS2019Projects.bat ├── module.lua ├── premake5 ├── premake5.exe └── premake5.lua ├── CODE_GUIDELINES.md ├── Code ├── network │ ├── include │ │ ├── Connection.hpp │ │ ├── ConnectionManager.hpp │ │ ├── Endpoint.hpp │ │ ├── Network.hpp │ │ ├── Resolver.hpp │ │ ├── Selector.hpp │ │ ├── Server.hpp │ │ └── Socket.hpp │ └── src │ │ ├── Connection.cpp │ │ ├── ConnectionManager.cpp │ │ ├── Endpoint.cpp │ │ ├── Network.cpp │ │ ├── Resolver.cpp │ │ ├── Selector.cpp │ │ ├── Server.cpp │ │ └── Socket.cpp ├── protocol │ ├── include │ │ ├── Channel.hpp │ │ ├── DHChachaFilter.hpp │ │ └── Message.hpp │ └── src │ │ ├── Channel.cpp │ │ └── DHChachaFilter.cpp └── tests │ └── src │ ├── main.cpp │ ├── network.cpp │ └── protocol.cpp ├── LICENSE ├── README.md ├── ThirdParty ├── catch.hpp └── endian.hpp └── azure-pipelines.yml /.ci/linux-build.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - checkout: self 3 | lfs: boolean 4 | submodules: recursive 5 | 6 | - script: | 7 | git clone --recurse-submodules https://github.com/tiltedphoques/TiltedCore.git ../TiltedCore 8 | displayName: Clone TiltedCore 9 | 10 | - script: | 11 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 12 | sudo apt-get update 13 | sudo apt-get install g++-9 14 | 15 | sudo update-alternatives --remove-all gcc 16 | sudo update-alternatives --remove-all g++ 17 | 18 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 19 | sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc 100 20 | sudo update-alternatives --set cc /usr/bin/gcc 21 | 22 | sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100 23 | sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++ 100 24 | sudo update-alternatives --set c++ /usr/bin/g++ 25 | displayName: Install 26 | 27 | - script: | 28 | cd Build 29 | chmod +x ./MakeGMakeProjects.sh ./premake5 30 | ./MakeGMakeProjects.sh 31 | displayName: Generate 32 | 33 | - script: | 34 | cd Build/projects 35 | make config=release_$(platform) -j 4 36 | displayName: Build 37 | 38 | - script: | 39 | cd Build/bin/$(platform) 40 | ./Tests_r 41 | displayName: Test 42 | -------------------------------------------------------------------------------- /.ci/windows-build.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - checkout: self 3 | lfs: boolean 4 | submodules: recursive 5 | 6 | - script: | 7 | git clone --recurse-submodules https://github.com/tiltedphoques/TiltedCore.git ../TiltedCore 8 | displayName: Clone TiltedCore 9 | 10 | - script: | 11 | cd Build 12 | ./MakeVS2019Projects.bat 13 | displayName: Generate 14 | 15 | - task: VSBuild@1 16 | displayName: Building 17 | inputs: 18 | solution: Build/projects/*.sln 19 | msbuildArgs: -m 20 | Configuration: Release 21 | Platform: $(platform) 22 | 23 | - script: | 24 | SET real_platform="x64" 25 | if "%platform%" == "Win32" SET real_platform="x32" 26 | 27 | cd Build/bin/%real_platform% 28 | Tests_r.exe 29 | displayName: Test 30 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{c,h,cpp,hpp}] 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | *.sh text eol=lf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dmp 2 | *.ilk 3 | Build/*.log 4 | Build/projects 5 | Build/obj 6 | Build/lib 7 | Build/bin 8 | *__pycache__* 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ThirdParty/cryptopp"] 2 | path = ThirdParty/cryptopp 3 | url = https://github.com/weidai11/cryptopp 4 | [submodule "Libraries/TiltedCore"] 5 | path = Libraries/TiltedCore 6 | url = https://github.com/tiltedphoques/TiltedCore 7 | -------------------------------------------------------------------------------- /Build/MakeGMakeProjects.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./premake5 gmake2 4 | -------------------------------------------------------------------------------- /Build/MakeVS2015Projects.bat: -------------------------------------------------------------------------------- 1 | premake5.exe vs2015 2 | -------------------------------------------------------------------------------- /Build/MakeVS2017Projects.bat: -------------------------------------------------------------------------------- 1 | premake5.exe vs2017 2 | -------------------------------------------------------------------------------- /Build/MakeVS2019Projects.bat: -------------------------------------------------------------------------------- 1 | premake5.exe vs2019 2 | -------------------------------------------------------------------------------- /Build/module.lua: -------------------------------------------------------------------------------- 1 | premake.extensions.network = {} 2 | 3 | function network_parent_path() 4 | local str = debug.getinfo(2, "S").source:sub(2) 5 | local dir = str:match("(.*/)"):sub(0,-2) 6 | local index = string.find(dir, "/[^/]*$") 7 | return dir:sub(0, index) 8 | end 9 | 10 | function network_protocol_generate() 11 | 12 | if premake.extensions.network.protocol_generated == true then 13 | return 14 | end 15 | 16 | local basePath = premake.extensions.network.path 17 | local coreBasePath = premake.extensions.core.path 18 | 19 | project ("Protocol") 20 | kind ("StaticLib") 21 | language ("C++") 22 | 23 | includedirs 24 | { 25 | basePath .. "/Code/protocol/include/", 26 | coreBasePath .. "/Code/core/include/", 27 | basePath .. "/ThirdParty/cryptopp/", 28 | } 29 | 30 | files 31 | { 32 | basePath .. "/Code/protocol/include/**.hpp", 33 | basePath .. "/Code/protocol/src/**.cpp", 34 | } 35 | 36 | links 37 | { 38 | "Core", 39 | "cryptopp" 40 | } 41 | 42 | premake.extensions.network.protocol_generated = true 43 | 44 | end 45 | 46 | function network_crypto_generate() 47 | 48 | if premake.extensions.network.crypto_generated == true then 49 | return 50 | end 51 | 52 | local basePath = premake.extensions.network.path 53 | 54 | project "cryptopp" 55 | language "C++" 56 | kind "StaticLib" 57 | targetname "cryptopp" 58 | 59 | 60 | defines 61 | { 62 | "_WINSOCK_DEPRECATED_NO_WARNINGS", 63 | "_LIB", 64 | "CRYPTOPP_DISABLE_SSSE3" 65 | } 66 | 67 | includedirs 68 | { 69 | basePath .. "/ThirdParty/cryptopp/", 70 | } 71 | 72 | files 73 | { 74 | basePath .. "/ThirdParty/cryptopp/*.h", 75 | basePath .. "/ThirdParty/cryptopp/3way.cpp", 76 | basePath .. "/ThirdParty/cryptopp/adler32.cpp", 77 | basePath .. "/ThirdParty/cryptopp/algebra.cpp", 78 | basePath .. "/ThirdParty/cryptopp/algparam.cpp", 79 | basePath .. "/ThirdParty/cryptopp/arc4.cpp", 80 | basePath .. "/ThirdParty/cryptopp/asn.cpp", 81 | basePath .. "/ThirdParty/cryptopp/authenc.cpp", 82 | basePath .. "/ThirdParty/cryptopp/base32.cpp", 83 | basePath .. "/ThirdParty/cryptopp/base64.cpp", 84 | basePath .. "/ThirdParty/cryptopp/basecode.cpp", 85 | basePath .. "/ThirdParty/cryptopp/bfinit.cpp", 86 | basePath .. "/ThirdParty/cryptopp/blake2.cpp", 87 | basePath .. "/ThirdParty/cryptopp/blake2b_simd.cpp", 88 | basePath .. "/ThirdParty/cryptopp/blake2s_simd.cpp", 89 | basePath .. "/ThirdParty/cryptopp/blowfish.cpp", 90 | basePath .. "/ThirdParty/cryptopp/blumshub.cpp", 91 | basePath .. "/ThirdParty/cryptopp/cast.cpp", 92 | basePath .. "/ThirdParty/cryptopp/casts.cpp", 93 | basePath .. "/ThirdParty/cryptopp/cbcmac.cpp", 94 | basePath .. "/ThirdParty/cryptopp/ccm.cpp", 95 | basePath .. "/ThirdParty/cryptopp/chacha.cpp", 96 | basePath .. "/ThirdParty/cryptopp/chacha_avx.cpp", 97 | basePath .. "/ThirdParty/cryptopp/chacha_simd.cpp", 98 | basePath .. "/ThirdParty/cryptopp/channels.cpp", 99 | basePath .. "/ThirdParty/cryptopp/cmac.cpp", 100 | basePath .. "/ThirdParty/cryptopp/cpu.cpp", 101 | basePath .. "/ThirdParty/cryptopp/crc.cpp", 102 | basePath .. "/ThirdParty/cryptopp/cryptlib.cpp", 103 | basePath .. "/ThirdParty/cryptopp/default.cpp", 104 | basePath .. "/ThirdParty/cryptopp/des.cpp", 105 | basePath .. "/ThirdParty/cryptopp/dessp.cpp", 106 | basePath .. "/ThirdParty/cryptopp/dh.cpp", 107 | basePath .. "/ThirdParty/cryptopp/dh2.cpp", 108 | basePath .. "/ThirdParty/cryptopp/dll.cpp", 109 | basePath .. "/ThirdParty/cryptopp/dsa.cpp", 110 | basePath .. "/ThirdParty/cryptopp/eax.cpp", 111 | basePath .. "/ThirdParty/cryptopp/ec2n.cpp", 112 | basePath .. "/ThirdParty/cryptopp/eccrypto.cpp", 113 | basePath .. "/ThirdParty/cryptopp/ecp.cpp", 114 | basePath .. "/ThirdParty/cryptopp/elgamal.cpp", 115 | basePath .. "/ThirdParty/cryptopp/emsa2.cpp", 116 | basePath .. "/ThirdParty/cryptopp/eprecomp.cpp", 117 | basePath .. "/ThirdParty/cryptopp/esign.cpp", 118 | basePath .. "/ThirdParty/cryptopp/files.cpp", 119 | basePath .. "/ThirdParty/cryptopp/filters.cpp", 120 | basePath .. "/ThirdParty/cryptopp/fips140.cpp", 121 | basePath .. "/ThirdParty/cryptopp/gcm.cpp", 122 | basePath .. "/ThirdParty/cryptopp/gf256.cpp", 123 | basePath .. "/ThirdParty/cryptopp/gf2n.cpp", 124 | basePath .. "/ThirdParty/cryptopp/gf2_32.cpp", 125 | basePath .. "/ThirdParty/cryptopp/gfpcrypt.cpp", 126 | basePath .. "/ThirdParty/cryptopp/gost.cpp", 127 | basePath .. "/ThirdParty/cryptopp/gzip.cpp", 128 | basePath .. "/ThirdParty/cryptopp/hex.cpp", 129 | basePath .. "/ThirdParty/cryptopp/hmac.cpp", 130 | basePath .. "/ThirdParty/cryptopp/hrtimer.cpp", 131 | basePath .. "/ThirdParty/cryptopp/ida.cpp", 132 | basePath .. "/ThirdParty/cryptopp/idea.cpp", 133 | basePath .. "/ThirdParty/cryptopp/integer.cpp", 134 | basePath .. "/ThirdParty/cryptopp/iterhash.cpp", 135 | basePath .. "/ThirdParty/cryptopp/luc.cpp", 136 | basePath .. "/ThirdParty/cryptopp/mars.cpp", 137 | basePath .. "/ThirdParty/cryptopp/marss.cpp", 138 | basePath .. "/ThirdParty/cryptopp/md2.cpp", 139 | basePath .. "/ThirdParty/cryptopp/md4.cpp", 140 | basePath .. "/ThirdParty/cryptopp/md5.cpp", 141 | basePath .. "/ThirdParty/cryptopp/misc.cpp", 142 | basePath .. "/ThirdParty/cryptopp/modes.cpp", 143 | basePath .. "/ThirdParty/cryptopp/mqueue.cpp", 144 | basePath .. "/ThirdParty/cryptopp/mqv.cpp", 145 | basePath .. "/ThirdParty/cryptopp/nbtheory.cpp", 146 | basePath .. "/ThirdParty/cryptopp/oaep.cpp", 147 | basePath .. "/ThirdParty/cryptopp/osrng.cpp", 148 | basePath .. "/ThirdParty/cryptopp/panama.cpp", 149 | basePath .. "/ThirdParty/cryptopp/pch.cpp", 150 | basePath .. "/ThirdParty/cryptopp/pkcspad.cpp", 151 | basePath .. "/ThirdParty/cryptopp/polynomi.cpp", 152 | basePath .. "/ThirdParty/cryptopp/pssr.cpp", 153 | basePath .. "/ThirdParty/cryptopp/pubkey.cpp", 154 | basePath .. "/ThirdParty/cryptopp/queue.cpp", 155 | basePath .. "/ThirdParty/cryptopp/rabin.cpp", 156 | basePath .. "/ThirdParty/cryptopp/randpool.cpp", 157 | basePath .. "/ThirdParty/cryptopp/rc2.cpp", 158 | basePath .. "/ThirdParty/cryptopp/rc5.cpp", 159 | basePath .. "/ThirdParty/cryptopp/rc6.cpp", 160 | basePath .. "/ThirdParty/cryptopp/rdtables.cpp", 161 | basePath .. "/ThirdParty/cryptopp/rijndael.cpp", 162 | basePath .. "/ThirdParty/cryptopp/ripemd.cpp", 163 | basePath .. "/ThirdParty/cryptopp/rng.cpp", 164 | basePath .. "/ThirdParty/cryptopp/rsa.cpp", 165 | basePath .. "/ThirdParty/cryptopp/rw.cpp", 166 | basePath .. "/ThirdParty/cryptopp/safer.cpp", 167 | basePath .. "/ThirdParty/cryptopp/salsa.cpp", 168 | basePath .. "/ThirdParty/cryptopp/seal.cpp", 169 | basePath .. "/ThirdParty/cryptopp/sse_simd.cpp", 170 | basePath .. "/ThirdParty/cryptopp/seed.cpp", 171 | basePath .. "/ThirdParty/cryptopp/serpent.cpp", 172 | basePath .. "/ThirdParty/cryptopp/sha.cpp", 173 | basePath .. "/ThirdParty/cryptopp/sha3.cpp", 174 | basePath .. "/ThirdParty/cryptopp/shacal2.cpp", 175 | basePath .. "/ThirdParty/cryptopp/shark.cpp", 176 | basePath .. "/ThirdParty/cryptopp/sharkbox.cpp", 177 | basePath .. "/ThirdParty/cryptopp/simple.cpp", 178 | basePath .. "/ThirdParty/cryptopp/skipjack.cpp", 179 | basePath .. "/ThirdParty/cryptopp/sosemanuk.cpp", 180 | basePath .. "/ThirdParty/cryptopp/square.cpp", 181 | basePath .. "/ThirdParty/cryptopp/squaretb.cpp", 182 | basePath .. "/ThirdParty/cryptopp/strciphr.cpp", 183 | basePath .. "/ThirdParty/cryptopp/tea.cpp", 184 | basePath .. "/ThirdParty/cryptopp/tftables.cpp", 185 | basePath .. "/ThirdParty/cryptopp/tiger.cpp", 186 | basePath .. "/ThirdParty/cryptopp/tigertab.cpp", 187 | basePath .. "/ThirdParty/cryptopp/ttmac.cpp", 188 | basePath .. "/ThirdParty/cryptopp/twofish.cpp", 189 | basePath .. "/ThirdParty/cryptopp/vmac.cpp", 190 | basePath .. "/ThirdParty/cryptopp/wake.cpp", 191 | basePath .. "/ThirdParty/cryptopp/whrlpool.cpp", 192 | basePath .. "/ThirdParty/cryptopp/xtr.cpp", 193 | basePath .. "/ThirdParty/cryptopp/xtrcrypt.cpp", 194 | basePath .. "/ThirdParty/cryptopp/zdeflate.cpp", 195 | basePath .. "/ThirdParty/cryptopp/zinflate.cpp", 196 | basePath .. "/ThirdParty/cryptopp/zlib.cpp", 197 | basePath .. "/ThirdParty/cryptopp/keccak_core.cpp" 198 | } 199 | 200 | filter "platforms:x64" 201 | files { 202 | basePath .. "/ThirdParty/cryptopp/x64dll.asm", 203 | basePath .. "/ThirdParty/cryptopp/x64masm.asm" 204 | } 205 | 206 | filter {"system:windows"} 207 | linkoptions { "/ignore:4221" } 208 | disablewarnings { "4005" } 209 | 210 | filter {"system:windows", "toolset:*_xp*"} 211 | defines { "USE_MS_CRYPTOAPI", "_WIN32_WINNT=0x502", "NTDDI_VERSION=0x05020300" } 212 | 213 | filter {} 214 | 215 | end 216 | -------------------------------------------------------------------------------- /Build/premake5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiltedphoques/TiltedNetwork/b35f2b4eaedd2dc14b797fd2a42169c939c1a4e6/Build/premake5 -------------------------------------------------------------------------------- /Build/premake5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiltedphoques/TiltedNetwork/b35f2b4eaedd2dc14b797fd2a42169c939c1a4e6/Build/premake5.exe -------------------------------------------------------------------------------- /Build/premake5.lua: -------------------------------------------------------------------------------- 1 | require("premake", ">=5.0.0-alpha10") 2 | 3 | include "module.lua" 4 | include "../../TiltedCore/Build/module.lua" 5 | 6 | workspace ("Tilted Network") 7 | 8 | ------------------------------------------------------------------ 9 | -- setup common settings 10 | ------------------------------------------------------------------ 11 | configurations { "Debug", "Release" } 12 | defines { "_CRT_SECURE_NO_WARNINGS" } 13 | 14 | location ("projects") 15 | startproject ("Tests") 16 | 17 | staticruntime "On" 18 | floatingpoint "Fast" 19 | vectorextensions "SSE2" 20 | warnings "Extra" 21 | 22 | cppdialect "C++17" 23 | 24 | platforms { "x32", "x64" } 25 | 26 | includedirs 27 | { 28 | "../ThirdParty/", 29 | "../Code/" 30 | } 31 | 32 | 33 | filter { "action:vs*"} 34 | buildoptions { "/wd4512", "/wd4996", "/wd4018", "/Zm500" } 35 | 36 | filter { "action:gmake2", "language:C++" } 37 | buildoptions { "-g -fpermissive" } 38 | linkoptions ("-lm -lpthread -pthread -Wl,--no-as-needed -lrt -g -fPIC") 39 | 40 | filter { "configurations:Release" } 41 | defines { "NDEBUG"} 42 | optimize ("On") 43 | targetsuffix ("_r") 44 | 45 | filter { "configurations:Debug" } 46 | defines { "DEBUG" } 47 | optimize ("Off") 48 | symbols ( "On" ) 49 | 50 | filter { "architecture:*86" } 51 | libdirs { "lib/x32" } 52 | targetdir ("lib/x32") 53 | 54 | filter { "architecture:*64" } 55 | libdirs { "lib/x64" } 56 | targetdir ("lib/x64") 57 | 58 | filter {} 59 | 60 | group ("Applications") 61 | project ("Tests") 62 | kind ("ConsoleApp") 63 | language ("C++") 64 | 65 | 66 | includedirs 67 | { 68 | "../Code/tests/include/", 69 | "../Code/network/include/", 70 | "../Code/protocol/include/", 71 | "../../TiltedCore/Code/core/include/" 72 | } 73 | 74 | files 75 | { 76 | "../Code/tests/include/**.h", 77 | "../Code/tests/src/**.cpp", 78 | } 79 | 80 | links 81 | { 82 | "Network", 83 | "Protocol", 84 | "Core", 85 | "cryptopp" 86 | } 87 | 88 | 89 | filter { "architecture:*86" } 90 | libdirs { "lib/x32" } 91 | targetdir ("bin/x32") 92 | 93 | filter { "architecture:*64" } 94 | libdirs { "lib/x64" } 95 | targetdir ("bin/x64") 96 | 97 | premake.extensions.network.generate() 98 | -------------------------------------------------------------------------------- /CODE_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Code guidelines 2 | 3 | ## Language 4 | 5 | We are using C++17, any C++17 feature supported by vs2017 is allowed. 6 | 7 | Please try to use templates responsibly, we don't want compilation times to explode and to deal with bloated binaries. 8 | 9 | Try to follow SRP as much as possible, a huge class containing tons of functionnalities is not better than many small components, it's easier to re-use them and to extend. 10 | 11 | ## Naming 12 | 13 | ### Variables 14 | 15 | The first letter is lower case, other words must start with an upper case : ``someVariableName``. 16 | 17 | Function arguments must be prefixed with an ``a`` for 'argument' : ``aFunctionArgument``. 18 | 19 | Const variables must be prefixed with a ``c`` for 'const' : ``const int cSomeInt;`` 20 | 21 | Pointers must be prefixed with a ``p`` for 'pointer' : ``int* pSomePointer``. 22 | 23 | The rules above must be used together for example ``void SomeFunc(const int* acpSomeArgument)``. 24 | 25 | Static variables must be prefixed with ``s_`` : ``static int s_someInt;``. 26 | 27 | Global variables must be prefixed with ``g_`` : ``extern int g_someGlobalInt;``. 28 | 29 | ### Classes 30 | 31 | Class names must start with an upper case : ``class SomeClass``. 32 | 33 | Class attributes must be prefixed with ``m_`` and must use the same rules as variables : ``int* m_pSomeMemberPointer;``. 34 | 35 | ### Functions 36 | 37 | All functions must start with an upper case : ``void SomeFunc();``. 38 | 39 | ## Generalities 40 | 41 | Names must be self explanatory, ``size_t a;`` is not acceptable, ``size_t incomingPacketCount;`` is good. 42 | 43 | ``auto`` is allowed when dealing with long names, it is not accepted for primitive types as we don't want the compiler to give us a signed int when we are using it as unsigned. 44 | 45 | Don't use java style blocks, a ``{`` needs to be on a new line. 46 | 47 | Don't use exceptions, don't use STL code that can throw, use the nothrow version if available or the unsafe version. Use the Outcome class to return the value or an error. 48 | -------------------------------------------------------------------------------- /Code/network/include/Connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Buffer.hpp" 4 | #include "Outcome.hpp" 5 | #include "Endpoint.hpp" 6 | #include "DHChachaFilter.hpp" 7 | 8 | namespace TiltedPhoques 9 | { 10 | struct Socket; 11 | struct Connection 12 | { 13 | struct Header 14 | { 15 | enum 16 | { 17 | kNegotiation, 18 | kConnection, 19 | kCount 20 | }; 21 | 22 | char Signature[2]; 23 | uint64_t Version; 24 | uint64_t Type; 25 | uint64_t Length; 26 | }; 27 | 28 | enum State 29 | { 30 | kNone, 31 | kNegociating, 32 | kConnected 33 | }; 34 | 35 | enum HeaderErrors 36 | { 37 | kBadSignature, 38 | kBadVersion, 39 | kBadPacketType, 40 | kTooLarge, 41 | kUnknownChannel 42 | }; 43 | 44 | struct ICommunication 45 | { 46 | virtual bool Send(const Endpoint& acRemote, Buffer aBuffer) = 0; 47 | }; 48 | 49 | Connection(ICommunication& aCommunicationInterface, const Endpoint& acRemoteEndpoint); 50 | Connection(const Connection& acRhs) = delete; 51 | Connection(Connection&& aRhs) noexcept; 52 | 53 | ~Connection(); 54 | 55 | Connection& operator=(Connection&& aRhs) noexcept; 56 | Connection& operator=(const Connection& aRhs) = delete; 57 | 58 | bool ProcessPacket(Buffer* apBuffer); 59 | bool ProcessNegociation(Buffer* apBuffer); 60 | 61 | bool IsNegotiating() const; 62 | bool IsConnected() const; 63 | 64 | State GetState() const; 65 | const Endpoint& GetRemoteEndpoint() const; 66 | 67 | void Update(uint64_t aElapsedMilliseconds); 68 | 69 | protected: 70 | 71 | void SendNegotiation(); 72 | 73 | Outcome ProcessHeader(Buffer::Reader& aReader); 74 | 75 | private: 76 | 77 | ICommunication& m_communication; 78 | State m_state; 79 | uint64_t m_timeSinceLastEvent; 80 | Endpoint m_remoteEndpoint; 81 | DHChachaFilter m_filter; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /Code/network/include/ConnectionManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Socket.hpp" 4 | #include "Connection.hpp" 5 | #include 6 | 7 | namespace TiltedPhoques 8 | { 9 | struct ConnectionManager : public AllocatorCompatible 10 | { 11 | ConnectionManager(size_t aMaxConnections); 12 | 13 | Connection* Find(const Endpoint& acEndpoint); 14 | const Connection* Find(const Endpoint& acEndpoint) const; 15 | 16 | void Add(Connection aConnection); 17 | 18 | bool IsFull() const; 19 | 20 | void Update(uint64_t aElapsedMilliSeconds); 21 | 22 | private: 23 | 24 | std::unordered_map m_connections; 25 | size_t m_maxConnections; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /Code/network/include/Endpoint.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Network.hpp" 4 | #include "Outcome.hpp" 5 | #include "Meta.hpp" 6 | 7 | namespace TiltedPhoques 8 | { 9 | struct Endpoint 10 | { 11 | enum Type : uint8_t 12 | { 13 | kNone, 14 | kIPv4, 15 | kIPv6 16 | }; 17 | 18 | Endpoint() noexcept; 19 | Endpoint(Endpoint&& aRhs) noexcept; 20 | Endpoint(const Endpoint& acRhs) noexcept; 21 | Endpoint(uint32_t aNetIPv4, uint16_t aPort) noexcept; 22 | Endpoint(const uint16_t* acpNetIPv6, uint16_t aPort) noexcept; 23 | 24 | bool IsIPv6() const noexcept; 25 | bool IsIPv4() const noexcept; 26 | bool IsValid() const noexcept; 27 | Type GetType() const noexcept; 28 | 29 | void SetPort(uint16_t aPort) noexcept; 30 | uint16_t GetPort() const noexcept; 31 | const uint8_t* GetIPv4() const noexcept; 32 | uint8_t* GetIPv4() noexcept; 33 | const uint16_t* GetIPv6() const noexcept; 34 | uint16_t* GetIPv6() noexcept; 35 | 36 | bool ToNetIPv4(uint32_t& aDestination) const noexcept; 37 | bool ToNetIPv6(in6_addr& aDestination) const noexcept; 38 | 39 | Endpoint& operator=(const Endpoint& acRhs) noexcept; 40 | Endpoint& operator=(Endpoint&& aRhs) noexcept; 41 | bool operator==(const Endpoint& acRhs) const noexcept; 42 | bool operator!=(const Endpoint& acRhs) const noexcept; 43 | 44 | private: 45 | 46 | union 47 | { 48 | uint8_t m_ipv4[4]; 49 | uint16_t m_ipv6[8]; 50 | }; 51 | 52 | friend struct std::hash; 53 | 54 | Type m_type; 55 | uint16_t m_port; 56 | }; 57 | } 58 | 59 | namespace std 60 | { 61 | template<> 62 | struct hash 63 | { 64 | typedef TiltedPhoques::Endpoint argument_type; 65 | typedef std::size_t result_type; 66 | 67 | result_type operator()(argument_type const& s) const noexcept 68 | { 69 | result_type result = 0; 70 | TiltedPhoques::hash_combine(result, (uint8_t)s.m_type); 71 | TiltedPhoques::hash_combine(result, s.m_port); 72 | 73 | result_type tmp = 0; 74 | if (s.IsIPv6()) 75 | { 76 | for (auto c : s.m_ipv6) 77 | { 78 | tmp = tmp * 31 + std::hash()(c); 79 | } 80 | } 81 | else if (s.IsIPv4()) 82 | { 83 | for (auto c : s.m_ipv4) 84 | { 85 | tmp = tmp * 31 + std::hash()(c); 86 | } 87 | } 88 | TiltedPhoques::hash_combine(result, tmp); 89 | 90 | return result; 91 | } 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /Code/network/include/Network.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | #include 7 | #pragma comment(lib, "ws2_32") 8 | #elif __linux__ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #else 19 | 20 | #endif 21 | 22 | namespace TiltedPhoques 23 | { 24 | void InitializeNetwork(); 25 | void ShutdownNetwork(); 26 | 27 | using Socket_t = decltype(socket(AF_INET, SOCK_DGRAM, 0)); 28 | } 29 | -------------------------------------------------------------------------------- /Code/network/include/Resolver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Network.hpp" 4 | #include "Endpoint.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include "Stl.hpp" 10 | 11 | namespace TiltedPhoques 12 | { 13 | struct Resolver 14 | { 15 | struct Iterator 16 | { 17 | using difference_type = ptrdiff_t; 18 | using value_type = Endpoint; 19 | using pointer = const Endpoint*; 20 | using reference = const Endpoint &; 21 | using iterator_category = std::forward_iterator_tag; 22 | 23 | Iterator() 24 | { 25 | } 26 | Iterator(Vector::const_iterator aIterator) : m_iterator(aIterator) 27 | { 28 | } 29 | Iterator& operator++() 30 | { 31 | ++m_iterator; return *this; 32 | } 33 | bool operator==(const Iterator& acIterator) const 34 | { 35 | return m_iterator == acIterator.m_iterator; 36 | } 37 | bool operator!=(const Iterator& acIterator) const 38 | { 39 | return m_iterator != acIterator.m_iterator; 40 | } 41 | reference operator*() const 42 | { 43 | return *m_iterator; 44 | } 45 | pointer operator->() const 46 | { 47 | return m_iterator.operator->(); 48 | } 49 | 50 | private: 51 | Vector::const_iterator m_iterator; 52 | }; 53 | 54 | Resolver(const String& acAddress); 55 | Resolver(Resolver&& aRhs) noexcept; 56 | Resolver(const Resolver& acRhs) = delete; 57 | [[nodiscard]] size_t GetSize() noexcept; 58 | [[nodiscard]] bool IsEmpty() noexcept; 59 | [[nodiscard]] Endpoint GetEndpoint(const size_t& acIndex) noexcept; 60 | [[nodiscard]] Endpoint operator[](const size_t& acIndex) noexcept; 61 | Resolver& operator=(Resolver&& aRhs) noexcept; 62 | Resolver& operator=(const Resolver& acRhs) = delete; 63 | Iterator begin() noexcept; 64 | Iterator end() noexcept; 65 | 66 | protected: 67 | [[nodiscard]] Vector& GetEndpoints() noexcept; 68 | void Parse(String aAddress) noexcept; 69 | [[nodiscard]] Vector ResolveHostname(const String& acHostname, const uint16_t& acPort) noexcept; 70 | 71 | private: 72 | std::future> m_futureEndpoints; 73 | Vector m_endpoints; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /Code/network/include/Selector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Socket.hpp" 4 | 5 | namespace TiltedPhoques 6 | { 7 | struct Selector 8 | { 9 | Selector(Socket& aSocket); 10 | 11 | bool IsReady() const; 12 | 13 | private: 14 | 15 | Socket_t m_sock; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /Code/network/include/Server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Socket.hpp" 4 | #include "ConnectionManager.hpp" 5 | 6 | namespace TiltedPhoques 7 | { 8 | struct Server : Connection::ICommunication 9 | { 10 | Server(); 11 | ~Server(); 12 | 13 | TP_NOCOPYMOVE(Server); 14 | TP_ALLOCATOR; 15 | 16 | bool Start(uint16_t aPort); 17 | uint32_t Update(uint64_t aElapsedMilliSeconds); 18 | [[nodiscard]] uint16_t GetPort() const; 19 | 20 | bool Send(const Endpoint& acRemoteEndpoint, Buffer aBuffer) override; 21 | 22 | protected: 23 | 24 | bool ProcessPacket(Socket::Packet& aPacket); 25 | 26 | private: 27 | 28 | uint32_t Work(); 29 | 30 | Socket m_v4Listener; 31 | Socket m_v6Listener; 32 | ConnectionManager m_connectionManager; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /Code/network/include/Socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Network.hpp" 4 | #include "Outcome.hpp" 5 | #include "Buffer.hpp" 6 | #include "Endpoint.hpp" 7 | 8 | namespace TiltedPhoques 9 | { 10 | struct Socket 11 | { 12 | enum Error 13 | { 14 | kInvalidSocket, 15 | kDiscardError, 16 | kCallFailure 17 | }; 18 | 19 | struct Packet 20 | { 21 | Endpoint Remote; 22 | Buffer Payload; 23 | }; 24 | 25 | Socket(Endpoint::Type aEndpointType = Endpoint::kIPv6, bool aBlocking = true); 26 | ~Socket(); 27 | 28 | Outcome Receive(); 29 | bool Send(const Packet& aBuffer); 30 | bool Bind(uint16_t aPort = 0); 31 | 32 | [[nodiscard]] uint16_t GetPort() const; 33 | 34 | protected: 35 | 36 | bool Bindv6(uint16_t aPort); 37 | bool Bindv4(uint16_t aPort); 38 | 39 | private: 40 | 41 | friend struct Selector; 42 | 43 | static constexpr size_t MaxPacketSize = 1200; 44 | 45 | Socket_t m_sock; 46 | uint16_t m_port; 47 | Endpoint::Type m_type; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /Code/network/src/Connection.cpp: -------------------------------------------------------------------------------- 1 | #include "Connection.hpp" 2 | #include "StackAllocator.hpp" 3 | 4 | namespace TiltedPhoques 5 | { 6 | struct NullCommunicationInterface : Connection::ICommunication 7 | { 8 | bool Send(const Endpoint& acRemote, Buffer aBuffer) override 9 | { 10 | (void)acRemote; 11 | (void)aBuffer; 12 | 13 | return false; 14 | } 15 | }; 16 | 17 | static NullCommunicationInterface s_dummyInterface; 18 | 19 | static const char* s_headerSignature = "MG"; 20 | 21 | Connection::Connection(ICommunication& aCommunicationInterface, const Endpoint& acRemoteEndpoint) 22 | : m_communication{ aCommunicationInterface } 23 | , m_state{ kNegociating } 24 | , m_timeSinceLastEvent{ 0 } 25 | , m_remoteEndpoint{ acRemoteEndpoint } 26 | { 27 | 28 | } 29 | 30 | Connection::Connection(Connection&& aRhs) noexcept 31 | : m_communication{ aRhs.m_communication } 32 | , m_state{ std::move(aRhs.m_state) } 33 | , m_timeSinceLastEvent{ std::move(aRhs.m_timeSinceLastEvent) } 34 | , m_remoteEndpoint{ std::move(aRhs.m_remoteEndpoint) } 35 | { 36 | aRhs.m_communication = s_dummyInterface; 37 | aRhs.m_state = kNone; 38 | aRhs.m_timeSinceLastEvent = 0; 39 | } 40 | 41 | Connection::~Connection() 42 | { 43 | } 44 | 45 | Connection& Connection::operator=(Connection&& aRhs) noexcept 46 | { 47 | m_communication = aRhs.m_communication; 48 | m_state = aRhs.m_state; 49 | m_timeSinceLastEvent = aRhs.m_timeSinceLastEvent; 50 | m_remoteEndpoint = std::move(aRhs.m_remoteEndpoint); 51 | 52 | aRhs.m_communication = s_dummyInterface; 53 | aRhs.m_state = kNone; 54 | aRhs.m_timeSinceLastEvent = 0; 55 | 56 | return *this; 57 | } 58 | 59 | bool Connection::ProcessPacket(Buffer* apBuffer) 60 | { 61 | Buffer::Reader reader(apBuffer); 62 | 63 | auto header = ProcessHeader(reader); 64 | if (header.HasError()) 65 | return false; 66 | 67 | m_timeSinceLastEvent = 0; 68 | 69 | return true; 70 | } 71 | 72 | bool Connection::ProcessNegociation(Buffer* apBuffer) 73 | { 74 | Buffer::Reader reader(apBuffer); 75 | 76 | auto header = ProcessHeader(reader); 77 | if (header.HasError()) 78 | return false; 79 | 80 | if (m_filter.ReceiveConnect(&reader)) 81 | m_state = kConnected; 82 | 83 | return IsNegotiating() || IsConnected(); 84 | } 85 | 86 | bool Connection::IsNegotiating() const 87 | { 88 | return m_state == kNegociating; 89 | } 90 | 91 | bool Connection::IsConnected() const 92 | { 93 | return m_state == kConnected; 94 | } 95 | 96 | Connection::State Connection::GetState() const 97 | { 98 | return m_state; 99 | } 100 | 101 | const Endpoint& Connection::GetRemoteEndpoint() const 102 | { 103 | return m_remoteEndpoint; 104 | } 105 | 106 | void Connection::Update(uint64_t aElapsedMilliseconds) 107 | { 108 | m_timeSinceLastEvent += aElapsedMilliseconds; 109 | 110 | // Connection is considered timed out if no data is received in 15s (TODO: make this configurable) 111 | if (m_timeSinceLastEvent > 15 * 1000) 112 | { 113 | m_state = kNone; 114 | return; 115 | } 116 | 117 | switch (m_state) 118 | { 119 | case Connection::kNone: 120 | break; 121 | case Connection::kNegociating: 122 | SendNegotiation(); 123 | break; 124 | case Connection::kConnected: 125 | break; 126 | default: 127 | break; 128 | } 129 | } 130 | 131 | void Connection::SendNegotiation() 132 | { 133 | Header header; 134 | header.Signature[0] = s_headerSignature[0]; 135 | header.Signature[1] = s_headerSignature[1]; 136 | header.Version = 1; 137 | header.Type = Header::kNegotiation; 138 | header.Length = 0; 139 | 140 | StackAllocator<1 << 13> allocator; 141 | auto* pBuffer = allocator.New(1200); 142 | 143 | Buffer::Writer writer(pBuffer); 144 | writer.WriteBytes((const uint8_t*)header.Signature, 2); 145 | writer.WriteBits(header.Version, 6); 146 | writer.WriteBits(header.Type, 3); 147 | writer.WriteBits(header.Length, 11); 148 | 149 | m_filter.PreConnect(&writer); 150 | 151 | m_communication.Send(m_remoteEndpoint, *pBuffer); 152 | 153 | allocator.Delete(pBuffer); 154 | } 155 | 156 | Outcome Connection::ProcessHeader(Buffer::Reader& aReader) 157 | { 158 | Header header; 159 | 160 | aReader.ReadBytes((uint8_t*)& header.Signature, 2); 161 | 162 | if (header.Signature[0] != 'M' || header.Signature[1] != 'G') 163 | return kBadSignature; 164 | 165 | aReader.ReadBits(header.Version, 6); 166 | 167 | if (header.Version != 1) 168 | return kBadVersion; 169 | 170 | aReader.ReadBits(header.Type, 3); 171 | 172 | if (header.Type >= Header::kCount) 173 | return kBadPacketType; 174 | 175 | aReader.ReadBits(header.Length, 11); 176 | 177 | if (header.Length > 1200) 178 | return kTooLarge; 179 | 180 | return header; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Code/network/src/ConnectionManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionManager.hpp" 2 | 3 | namespace TiltedPhoques 4 | { 5 | ConnectionManager::ConnectionManager(size_t aMaxConnections) 6 | : m_maxConnections(aMaxConnections) 7 | { 8 | 9 | } 10 | 11 | Connection* ConnectionManager::Find(const Endpoint& acEndpoint) 12 | { 13 | auto itor = m_connections.find(acEndpoint); 14 | if (itor != std::end(m_connections)) 15 | { 16 | return &itor->second; 17 | } 18 | 19 | return nullptr; 20 | } 21 | 22 | const Connection* ConnectionManager::Find(const Endpoint& acEndpoint) const 23 | { 24 | auto itor = m_connections.find(acEndpoint); 25 | if (itor != std::end(m_connections)) 26 | { 27 | return &itor->second; 28 | } 29 | 30 | return nullptr; 31 | } 32 | 33 | bool ConnectionManager::IsFull() const 34 | { 35 | return m_connections.size() >= m_maxConnections; 36 | } 37 | 38 | void ConnectionManager::Update(uint64_t aElapsedMilliSeconds) 39 | { 40 | for (auto& kvp : m_connections) 41 | { 42 | auto& connection = kvp.second; 43 | 44 | connection.Update(aElapsedMilliSeconds); 45 | } 46 | } 47 | 48 | void ConnectionManager::Add(Connection aConnection) 49 | { 50 | m_connections.emplace(aConnection.GetRemoteEndpoint(), std::move(aConnection)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Code/network/src/Endpoint.cpp: -------------------------------------------------------------------------------- 1 | #include "Endpoint.hpp" 2 | #include "Network.hpp" 3 | 4 | #include 5 | 6 | namespace TiltedPhoques 7 | { 8 | Endpoint::Endpoint() noexcept 9 | { 10 | m_port = 0; 11 | m_type = kNone; 12 | } 13 | 14 | Endpoint::Endpoint(Endpoint&& aRhs) noexcept 15 | { 16 | this->operator=(std::move(aRhs)); 17 | } 18 | 19 | Endpoint::Endpoint(const Endpoint& acRhs) noexcept 20 | { 21 | m_port = acRhs.m_port; 22 | m_type = acRhs.m_type; 23 | std::copy(std::begin(acRhs.m_ipv6), std::end(acRhs.m_ipv6), std::begin(m_ipv6)); 24 | } 25 | 26 | Endpoint::Endpoint(uint32_t aNetIPv4, uint16_t aPort) noexcept 27 | : m_type(kIPv4) 28 | , m_port(aPort) 29 | { 30 | m_ipv4[0] = (uint8_t)(aNetIPv4 & 0xFF); aNetIPv4 >>= 8; 31 | m_ipv4[1] = (uint8_t)(aNetIPv4 & 0xFF); aNetIPv4 >>= 8; 32 | m_ipv4[2] = (uint8_t)(aNetIPv4 & 0xFF); aNetIPv4 >>= 8; 33 | m_ipv4[3] = (uint8_t)(aNetIPv4 & 0xFF); aNetIPv4 >>= 8; 34 | } 35 | 36 | Endpoint::Endpoint(const uint16_t* acpNetIPv6, uint16_t aPort) noexcept 37 | : m_type(kIPv6) 38 | , m_port(aPort) 39 | { 40 | m_ipv6[0] = ntohs(acpNetIPv6[0]); 41 | m_ipv6[1] = ntohs(acpNetIPv6[1]); 42 | m_ipv6[2] = ntohs(acpNetIPv6[2]); 43 | m_ipv6[3] = ntohs(acpNetIPv6[3]); 44 | m_ipv6[4] = ntohs(acpNetIPv6[4]); 45 | m_ipv6[5] = ntohs(acpNetIPv6[5]); 46 | m_ipv6[6] = ntohs(acpNetIPv6[6]); 47 | m_ipv6[7] = ntohs(acpNetIPv6[7]); 48 | } 49 | 50 | bool Endpoint::IsIPv4() const noexcept 51 | { 52 | return m_type == kIPv4; 53 | } 54 | 55 | bool Endpoint::IsIPv6() const noexcept 56 | { 57 | return m_type == kIPv6; 58 | } 59 | 60 | bool Endpoint::IsValid() const noexcept 61 | { 62 | return m_type != kNone; 63 | } 64 | 65 | Endpoint::Type Endpoint::GetType() const noexcept 66 | { 67 | return m_type; 68 | } 69 | 70 | void Endpoint::SetPort(uint16_t aPort) noexcept 71 | { 72 | m_port = aPort; 73 | } 74 | 75 | uint16_t Endpoint::GetPort() const noexcept 76 | { 77 | return m_port; 78 | } 79 | 80 | const uint8_t* Endpoint::GetIPv4() const noexcept 81 | { 82 | return m_ipv4; 83 | } 84 | 85 | uint8_t* Endpoint::GetIPv4() noexcept 86 | { 87 | return m_ipv4; 88 | } 89 | 90 | const uint16_t* Endpoint::GetIPv6() const noexcept 91 | { 92 | return m_ipv6; 93 | } 94 | 95 | uint16_t* Endpoint::GetIPv6() noexcept 96 | { 97 | return m_ipv6; 98 | } 99 | 100 | bool Endpoint::ToNetIPv4(uint32_t& aDestination) const noexcept 101 | { 102 | if (IsIPv4() == false) return false; 103 | 104 | aDestination |= uint32_t(m_ipv4[0]); 105 | aDestination |= uint32_t(m_ipv4[1]) << 8; 106 | aDestination |= uint32_t(m_ipv4[2]) << 16; 107 | aDestination |= uint32_t(m_ipv4[3]) << 24; 108 | 109 | return true; 110 | } 111 | 112 | bool Endpoint::ToNetIPv6(in6_addr& aDestination) const noexcept 113 | { 114 | if (IsIPv6() == false) return false; 115 | 116 | auto pDest = (uint16_t*)& aDestination; 117 | 118 | pDest[0] = htons(m_ipv6[0]); 119 | pDest[1] = htons(m_ipv6[1]); 120 | pDest[2] = htons(m_ipv6[2]); 121 | pDest[3] = htons(m_ipv6[3]); 122 | pDest[4] = htons(m_ipv6[4]); 123 | pDest[5] = htons(m_ipv6[5]); 124 | pDest[6] = htons(m_ipv6[6]); 125 | pDest[7] = htons(m_ipv6[7]); 126 | 127 | return true; 128 | } 129 | 130 | Endpoint& Endpoint::operator=(const Endpoint& acRhs) noexcept 131 | { 132 | m_type = acRhs.m_type; 133 | m_port = acRhs.m_port; 134 | std::copy(std::begin(acRhs.m_ipv6), std::end(acRhs.m_ipv6), std::begin(m_ipv6)); 135 | 136 | return *this; 137 | } 138 | 139 | Endpoint& Endpoint::operator=(Endpoint&& aRhs) noexcept 140 | { 141 | this->operator=((const Endpoint&)aRhs); 142 | 143 | aRhs.m_type = kNone; 144 | aRhs.m_port = 0; 145 | 146 | return *this; 147 | } 148 | 149 | bool Endpoint::operator==(const Endpoint& acRhs) const noexcept 150 | { 151 | if (m_type != acRhs.m_type || m_port != acRhs.m_port) 152 | return false; 153 | 154 | return std::memcmp(m_ipv6, acRhs.m_ipv6, IsIPv6() ? 16 : 4) == 0; 155 | } 156 | 157 | bool Endpoint::operator!=(const Endpoint& acRhs) const noexcept 158 | { 159 | return !this->operator==(acRhs); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Code/network/src/Network.cpp: -------------------------------------------------------------------------------- 1 | #include "Network.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace TiltedPhoques 7 | { 8 | void InitializeNetwork() 9 | { 10 | static std::once_flag s_flag; 11 | std::call_once(s_flag, []() 12 | { 13 | #ifdef _WIN32 14 | WSADATA wsadata; 15 | if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) 16 | { 17 | } 18 | #endif 19 | }); 20 | } 21 | 22 | void ShutdownNetwork() 23 | { 24 | static std::once_flag s_flag; 25 | std::call_once(s_flag, []() 26 | { 27 | #ifdef _WIN32 28 | WSACleanup(); 29 | #endif 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Code/network/src/Resolver.cpp: -------------------------------------------------------------------------------- 1 | #include "Resolver.hpp" 2 | 3 | #include 4 | 5 | namespace TiltedPhoques 6 | { 7 | Resolver::Resolver(const String& acAddress) 8 | { 9 | Parse(acAddress); 10 | } 11 | 12 | Resolver::Resolver(Resolver&& aRhs) noexcept 13 | : m_futureEndpoints{ std::move(aRhs.m_futureEndpoints) } 14 | , m_endpoints{ std::move(aRhs.m_endpoints) } 15 | { 16 | } 17 | 18 | size_t Resolver::GetSize() noexcept 19 | { 20 | return GetEndpoints().size(); 21 | } 22 | 23 | bool Resolver::IsEmpty() noexcept 24 | { 25 | return GetSize() == 0; 26 | } 27 | 28 | Endpoint Resolver::GetEndpoint(const size_t& acIndex) noexcept 29 | { 30 | return GetEndpoints()[acIndex]; 31 | } 32 | 33 | Endpoint Resolver::operator[](const size_t& acIndex) noexcept 34 | { 35 | return GetEndpoint(acIndex); 36 | } 37 | 38 | Resolver& Resolver::operator=(Resolver&& aRhs) noexcept 39 | { 40 | m_endpoints = std::move(aRhs.m_endpoints); 41 | m_futureEndpoints = std::move(aRhs.m_futureEndpoints); 42 | 43 | return *this; 44 | } 45 | 46 | Resolver::Iterator Resolver::begin() noexcept 47 | { 48 | return Iterator(GetEndpoints().cbegin()); 49 | } 50 | 51 | Resolver::Iterator Resolver::end() noexcept 52 | { 53 | return Iterator(GetEndpoints().cend()); 54 | } 55 | 56 | Vector& Resolver::GetEndpoints() noexcept 57 | { 58 | if (m_futureEndpoints.valid()) 59 | { 60 | // std::future::get can only be called once in its lifetime, so we have to keep them all in m_endpoints 61 | const Vector& newEndpoints = m_futureEndpoints.get(); 62 | m_endpoints.insert(std::end(m_endpoints), std::begin(newEndpoints), std::end(newEndpoints)); 63 | } 64 | 65 | return m_endpoints; 66 | } 67 | 68 | void Resolver::Parse(String aAddress) noexcept 69 | { 70 | uint16_t port = 0; 71 | 72 | if (aAddress.empty()) 73 | { 74 | return; 75 | } 76 | 77 | // If we see an IPv6 start character 78 | if (aAddress[0] == '[') 79 | { 80 | auto endChar = aAddress.rfind(']'); 81 | if (endChar != std::string::npos) 82 | { 83 | if (endChar + 3 <= aAddress.size() && aAddress[endChar + 1] == ':') 84 | port = std::atoi(&aAddress[endChar + 2]); 85 | 86 | aAddress[endChar] = '\0'; 87 | } 88 | 89 | in6_addr sockaddr6; 90 | if (inet_pton(AF_INET6, &aAddress[1], &sockaddr6) == 1) 91 | { 92 | m_endpoints.push_back(Endpoint((uint16_t*)& sockaddr6, port)); 93 | } 94 | } 95 | else 96 | { 97 | auto endChar = aAddress.rfind(':'); 98 | if (endChar != std::string::npos && endChar + 2 <= aAddress.size()) 99 | { 100 | port = std::atoi(&aAddress[endChar + 1]); 101 | aAddress[endChar] = '\0'; 102 | } 103 | 104 | sockaddr_in sockaddr; 105 | if (inet_pton(AF_INET, &aAddress[0], &sockaddr.sin_addr) == 1) 106 | { 107 | m_endpoints.push_back(Endpoint(sockaddr.sin_addr.s_addr, port)); 108 | } 109 | else 110 | { 111 | // this call is safe as std::async const & args anyway 112 | m_futureEndpoints = std::async(std::launch::async, &Resolver::ResolveHostname, this, aAddress, port); 113 | } 114 | } 115 | } 116 | 117 | Vector Resolver::ResolveHostname(const String& acHostname, const uint16_t& port) noexcept 118 | { 119 | Vector endpoints; 120 | struct addrinfo hints = {}; 121 | struct addrinfo* pResolvedAddresses; 122 | hints.ai_family = AF_UNSPEC; 123 | hints.ai_socktype = SOCK_DGRAM; 124 | 125 | const int err = getaddrinfo(acHostname.c_str(), nullptr, &hints, &pResolvedAddresses); 126 | 127 | if (err != 0) 128 | { 129 | // printf("%s\n", gai_strerror(err)); 130 | return endpoints; 131 | } 132 | 133 | for (struct addrinfo* r = pResolvedAddresses; r != nullptr; r = r->ai_next) 134 | { 135 | if (r->ai_family == AF_INET) 136 | { 137 | endpoints.push_back(Endpoint(((struct sockaddr_in*)r->ai_addr)->sin_addr.s_addr, port)); 138 | } 139 | else if (r->ai_family == AF_INET6) 140 | { 141 | endpoints.push_back(Endpoint((uint16_t*) & ((struct sockaddr_in6*)r->ai_addr)->sin6_addr, port)); 142 | } 143 | } 144 | 145 | freeaddrinfo(pResolvedAddresses); 146 | 147 | return endpoints; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Code/network/src/Selector.cpp: -------------------------------------------------------------------------------- 1 | #include "Selector.hpp" 2 | 3 | namespace TiltedPhoques 4 | { 5 | Selector::Selector(Socket& aSocket) 6 | : m_sock{ aSocket.m_sock } 7 | { 8 | } 9 | 10 | bool Selector::IsReady() const 11 | { 12 | fd_set set; 13 | #ifdef _WIN32 14 | set.fd_count = 1; 15 | set.fd_array[0] = m_sock; 16 | #else 17 | FD_ZERO(&set); 18 | FD_SET(m_sock, &set); 19 | #endif 20 | 21 | timeval tm; 22 | tm.tv_sec = 0; 23 | tm.tv_usec = 0; 24 | 25 | #ifdef _WIN32 26 | return select(set.fd_count, &set, nullptr, nullptr, &tm) == 1; 27 | #else 28 | return select(m_sock + 1, &set, nullptr, nullptr, &tm) == 1; 29 | #endif 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Code/network/src/Server.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.hpp" 2 | #include "Selector.hpp" 3 | 4 | namespace TiltedPhoques 5 | { 6 | Server::Server() 7 | : m_v4Listener(Endpoint::kIPv4) 8 | , m_v6Listener(Endpoint::kIPv6) 9 | , m_connectionManager(64) 10 | { 11 | 12 | } 13 | 14 | Server::~Server() 15 | { 16 | } 17 | 18 | bool Server::Start(uint16_t aPort) 19 | { 20 | if (m_v4Listener.Bind(aPort) == false) 21 | { 22 | return false; 23 | } 24 | return m_v6Listener.Bind(m_v4Listener.GetPort()); 25 | } 26 | 27 | uint32_t Server::Update(uint64_t aElapsedMilliSeconds) 28 | { 29 | m_connectionManager.Update(aElapsedMilliSeconds); 30 | 31 | return Work(); 32 | } 33 | 34 | uint16_t Server::GetPort() const 35 | { 36 | return m_v4Listener.GetPort(); 37 | } 38 | 39 | bool Server::Send(const Endpoint& acRemoteEndpoint, Buffer aBuffer) 40 | { 41 | if (acRemoteEndpoint.IsIPv6()) 42 | { 43 | Socket::Packet packet{ acRemoteEndpoint, std::move(aBuffer) }; 44 | 45 | m_v6Listener.Send(packet); 46 | } 47 | 48 | return false; 49 | } 50 | 51 | bool Server::ProcessPacket(Socket::Packet& aPacket) 52 | { 53 | auto pConnection = m_connectionManager.Find(aPacket.Remote); 54 | if (!pConnection) 55 | { 56 | Connection connection(*this, aPacket.Remote); 57 | 58 | m_connectionManager.Add(std::move(connection)); 59 | 60 | return true; 61 | } 62 | else if (m_connectionManager.IsFull() == false) 63 | { 64 | // New connection 65 | 66 | 67 | return true; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | uint32_t Server::Work() 74 | { 75 | uint32_t processedPackets = 0; 76 | 77 | Selector selector(m_v4Listener); 78 | while (selector.IsReady()) 79 | { 80 | auto result = m_v4Listener.Receive(); 81 | if (result.HasError()) 82 | { 83 | // do some error handling 84 | continue; 85 | } 86 | else 87 | { 88 | // Route packet to a connection 89 | if (ProcessPacket(result.GetResult())) 90 | ++processedPackets; 91 | } 92 | } 93 | 94 | Selector selectorv6(m_v6Listener); 95 | while (selectorv6.IsReady()) 96 | { 97 | auto result = m_v6Listener.Receive(); 98 | if (result.HasError()) 99 | { 100 | // do some error handling 101 | continue; 102 | } 103 | else 104 | { 105 | // Route packet to a connection 106 | if (ProcessPacket(result.GetResult())) 107 | ++processedPackets; 108 | } 109 | } 110 | 111 | return processedPackets; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Code/network/src/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.hpp" 2 | #include 3 | 4 | namespace TiltedPhoques 5 | { 6 | Socket::Socket(Endpoint::Type aEndpointType, bool aBlocking) 7 | : m_type{ aEndpointType } 8 | { 9 | m_port = 0; 10 | m_sock = socket(aEndpointType == Endpoint::kIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); 11 | if (m_sock < 0) 12 | return; // error handling 13 | 14 | int on = 1; 15 | #ifdef _WIN32 16 | if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)& on, sizeof(on)) != 0) 17 | #else 18 | if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) 19 | #endif 20 | return; 21 | 22 | if (aBlocking == false) 23 | { 24 | #ifdef _WIN32 25 | if (ioctlsocket(m_sock, FIONBIO, (u_long*)& on) != 0) 26 | #else 27 | if (ioctl(m_sock, FIONBIO, (char*)& on) != 0) 28 | #endif 29 | return; 30 | } 31 | } 32 | 33 | Socket::~Socket() 34 | { 35 | #ifdef _WIN32 36 | closesocket(m_sock); 37 | #else 38 | close(m_sock); 39 | #endif 40 | } 41 | 42 | Outcome Socket::Receive() 43 | { 44 | Buffer buffer(MaxPacketSize); 45 | 46 | sockaddr_storage from; 47 | #ifdef _WIN32 48 | using socklen_t = int; 49 | #endif 50 | socklen_t len = sizeof(sockaddr_storage); 51 | 52 | auto result = recvfrom(m_sock, (char*)buffer.GetWriteData(), MaxPacketSize, 0, (sockaddr*)& from, &len); 53 | #ifdef _WIN32 54 | if (result == SOCKET_ERROR) 55 | { 56 | auto error = WSAGetLastError(); 57 | 58 | if (error == WSAEWOULDBLOCK || error == WSAECONNRESET) 59 | return kDiscardError; 60 | 61 | return kCallFailure; 62 | } 63 | #else 64 | if (result <= 0) 65 | { 66 | if (errno == EAGAIN) 67 | return kDiscardError; 68 | 69 | return kCallFailure; 70 | } 71 | #endif 72 | 73 | Packet packet{ Endpoint{}, std::move(buffer) }; 74 | if (from.ss_family == AF_INET) 75 | { 76 | auto* pAddr = (sockaddr_in*)& from; 77 | new (&packet.Remote) Endpoint(pAddr->sin_addr.s_addr, ntohs(pAddr->sin_port)); 78 | } 79 | else 80 | { 81 | auto* pAddr = (sockaddr_in6*)& from; 82 | new (&packet.Remote) Endpoint((uint16_t*)& pAddr->sin6_addr, ntohs(pAddr->sin6_port)); 83 | } 84 | 85 | return std::move(packet); 86 | } 87 | 88 | bool Socket::Send(const Socket::Packet& acPacket) 89 | { 90 | if (acPacket.Remote.GetType() != m_type) 91 | return false; 92 | 93 | if (m_type == Endpoint::kIPv6) 94 | { 95 | sockaddr_in6 ipv6; 96 | std::memset(&ipv6, 0, sizeof(ipv6)); 97 | ipv6.sin6_port = htons(acPacket.Remote.GetPort()); 98 | ipv6.sin6_family = AF_INET6; 99 | acPacket.Remote.ToNetIPv6(ipv6.sin6_addr); 100 | 101 | if (sendto(m_sock, (const char*)acPacket.Payload.GetData(), acPacket.Payload.GetSize(), 0, (sockaddr*)& ipv6, sizeof(ipv6)) < 0) 102 | return false; 103 | } 104 | else 105 | { 106 | sockaddr_in ipv4; 107 | std::memset(&ipv4, 0, sizeof(ipv4)); 108 | ipv4.sin_port = htons(acPacket.Remote.GetPort()); 109 | ipv4.sin_family = AF_INET; 110 | acPacket.Remote.ToNetIPv4((uint32_t&)ipv4.sin_addr.s_addr); 111 | 112 | if (sendto(m_sock, (const char*)acPacket.Payload.GetData(), acPacket.Payload.GetSize(), 0, (sockaddr*)& ipv4, sizeof(sockaddr_in)) < 0) 113 | return false; 114 | } 115 | 116 | return true; 117 | } 118 | 119 | bool Socket::Bind(uint16_t aPort) 120 | { 121 | if (m_type == Endpoint::kIPv6) 122 | return Bindv6(aPort); 123 | 124 | return Bindv4(aPort); 125 | } 126 | 127 | bool Socket::Bindv6(uint16_t aPort) 128 | { 129 | sockaddr_in6 saddr; 130 | std::memset(&saddr, 0, sizeof(saddr)); 131 | saddr.sin6_family = AF_INET6; 132 | saddr.sin6_addr = in6addr_any; 133 | saddr.sin6_port = htons(aPort); 134 | 135 | int v6only = 1; 136 | #ifdef _WIN32 137 | if (setsockopt(m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)& v6only, sizeof(v6only)) != 0) 138 | #else 139 | if (setsockopt(m_sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) 140 | #endif 141 | return false; 142 | 143 | 144 | if (bind(m_sock, (sockaddr*)& saddr, sizeof(saddr)) < 0) 145 | return false; 146 | 147 | #ifdef _WIN32 148 | using socklen_t = int; 149 | #endif 150 | socklen_t len = sizeof(saddr); 151 | getsockname(m_sock, (sockaddr*)& saddr, &len); 152 | 153 | m_port = ntohs(saddr.sin6_port); 154 | 155 | return true; 156 | } 157 | 158 | bool Socket::Bindv4(uint16_t aPort) 159 | { 160 | sockaddr_in saddr; 161 | std::memset(&saddr, 0, sizeof(saddr)); 162 | saddr.sin_family = AF_INET; 163 | saddr.sin_addr.s_addr = htonl(INADDR_ANY); 164 | saddr.sin_port = htons(aPort); 165 | 166 | if (bind(m_sock, (sockaddr*)& saddr, sizeof(saddr)) < 0) 167 | return false; 168 | 169 | #ifdef _WIN32 170 | using socklen_t = int; 171 | #endif 172 | socklen_t len = sizeof(saddr); 173 | getsockname(m_sock, (sockaddr*)& saddr, &len); 174 | 175 | m_port = ntohs(saddr.sin_port); 176 | 177 | return true; 178 | } 179 | 180 | uint16_t Socket::GetPort() const 181 | { 182 | return m_port; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Code/protocol/include/Channel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace TiltedPhoques 7 | { 8 | struct Channel 9 | { 10 | enum Type 11 | { 12 | kReliable, 13 | kUnreliable, 14 | kSequenced 15 | }; 16 | 17 | Channel(Type aType, std::function aMessageReceivedCallback); 18 | ~Channel(); 19 | 20 | TP_NOCOPYMOVE(Channel); 21 | TP_ALLOCATOR; 22 | 23 | void ProcessPacket(Buffer::Reader* apReader); 24 | 25 | protected: 26 | 27 | void ProcessUnreliablePacket(Buffer::Reader* apReader); 28 | void ProcessReliablePacket(Buffer::Reader* apReader); 29 | void ProcessSequencedPacket(Buffer::Reader* apReader); 30 | 31 | private: 32 | 33 | Type m_type; 34 | std::function m_messageReceivedCallback; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /Code/protocol/include/DHChachaFilter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Buffer.hpp" 4 | #include "Allocator.hpp" 5 | #include 6 | 7 | namespace TiltedPhoques 8 | { 9 | struct DHChachaFilterPimpl; 10 | struct DHChachaFilter 11 | { 12 | DHChachaFilter(); 13 | ~DHChachaFilter(); 14 | 15 | TP_NOCOPYMOVE(DHChachaFilter); 16 | TP_ALLOCATOR; 17 | 18 | bool PreConnect(Buffer::Writer* apBuffer); 19 | bool ReceiveConnect(Buffer::Reader* apBuffer); 20 | 21 | // Called before the packet gets sent 22 | bool PreSend(Buffer::Writer* apBuffer, uint32_t aSequenceNumber); 23 | // Called after the payload is generated 24 | bool PostSend(uint8_t* apPayload, size_t aLength, uint32_t aSequenceNumber); 25 | // Called with the raw payload 26 | bool PreReceive(uint8_t* apPayload, size_t aLength, uint32_t aSequenceNumber); 27 | 28 | private: 29 | 30 | void GenerateKeys(); 31 | 32 | DHChachaFilterPimpl* m_pPimpl; 33 | std::array m_iv; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /Code/protocol/include/Message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace TiltedPhoques 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /Code/protocol/src/Channel.cpp: -------------------------------------------------------------------------------- 1 | #include "Channel.hpp" 2 | #include 3 | 4 | namespace TiltedPhoques 5 | { 6 | Channel::Channel(Type aType, std::function aMessageReceivedCallback) 7 | : m_type(aType) 8 | , m_messageReceivedCallback(std::move(aMessageReceivedCallback)) 9 | { 10 | 11 | } 12 | 13 | Channel::~Channel() 14 | { 15 | 16 | } 17 | 18 | void Channel::ProcessPacket(Buffer::Reader* apReader) 19 | { 20 | switch (m_type) 21 | { 22 | case kUnreliable: 23 | ProcessUnreliablePacket(apReader); 24 | break; 25 | case kReliable: 26 | ProcessReliablePacket(apReader); 27 | break; 28 | case kSequenced: 29 | ProcessSequencedPacket(apReader); 30 | break; 31 | } 32 | } 33 | 34 | void Channel::ProcessUnreliablePacket(Buffer::Reader* apReader) 35 | { 36 | 37 | 38 | 39 | } 40 | 41 | void Channel::ProcessReliablePacket(Buffer::Reader* apReader) 42 | { 43 | (void)apReader; 44 | assert(false); 45 | } 46 | 47 | void Channel::ProcessSequencedPacket(Buffer::Reader* apReader) 48 | { 49 | (void)apReader; 50 | assert(false); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Code/protocol/src/DHChachaFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "DHChachaFilter.hpp" 2 | 3 | #include "cryptlib.h" 4 | #include "chacha.h" 5 | #include "integer.h" 6 | #include "dh.h" 7 | #include "eccrypto.h" 8 | #include "oids.h" 9 | #include "osrng.h" 10 | #include "secblock.h" 11 | #include "sha.h" 12 | #include "blake2.h" 13 | 14 | 15 | using namespace CryptoPP::ASN1; 16 | 17 | namespace TiltedPhoques 18 | { 19 | struct DHChachaFilterPimpl 20 | { 21 | DHChachaFilterPimpl() 22 | : m_dh(secp256r1()) 23 | {} 24 | 25 | CryptoPP::ECDH::Domain m_dh; 26 | CryptoPP::XChaCha20::Encryption m_cipher; 27 | CryptoPP::SecByteBlock m_pubKey; 28 | CryptoPP::SecByteBlock m_priKey; 29 | }; 30 | 31 | DHChachaFilter::DHChachaFilter() 32 | : m_pPimpl{ GetAllocator()->New() } 33 | , m_iv{ 0 } 34 | { 35 | m_pPimpl->m_priKey.resize(m_pPimpl->m_dh.PrivateKeyLength()); 36 | m_pPimpl->m_pubKey.resize(m_pPimpl->m_dh.PublicKeyLength()); 37 | 38 | GenerateKeys(); 39 | } 40 | 41 | DHChachaFilter::~DHChachaFilter() 42 | { 43 | GetAllocator()->Delete(m_pPimpl); 44 | } 45 | 46 | bool DHChachaFilter::PreConnect(Buffer::Writer* apBuffer) 47 | { 48 | return apBuffer->WriteBytes(m_pPimpl->m_pubKey.BytePtr(), m_pPimpl->m_pubKey.SizeInBytes()); 49 | } 50 | 51 | bool DHChachaFilter::ReceiveConnect(Buffer::Reader* apBuffer) 52 | { 53 | CryptoPP::SecByteBlock sharedSecret(m_pPimpl->m_dh.AgreedValueLength()); 54 | CryptoPP::SecByteBlock pubKey(m_pPimpl->m_dh.PublicKeyLength()); 55 | 56 | if (!apBuffer->ReadBytes((CryptoPP::byte*)pubKey, pubKey.SizeInBytes())) 57 | return false; 58 | 59 | if (!m_pPimpl->m_dh.Agree(sharedSecret, m_pPimpl->m_priKey, pubKey)) 60 | return false; 61 | 62 | CryptoPP::SecByteBlock key(CryptoPP::SHA256::DIGESTSIZE); 63 | CryptoPP::SecByteBlock iv(CryptoPP::BLAKE2b::DIGESTSIZE); 64 | 65 | CryptoPP::SHA256().CalculateDigest(key, sharedSecret, sharedSecret.SizeInBytes()); 66 | CryptoPP::BLAKE2b().CalculateDigest(iv, sharedSecret, sharedSecret.SizeInBytes()); 67 | 68 | std::copy(iv.BytePtr(), iv.BytePtr() + std::size(m_iv), std::begin(m_iv)); 69 | 70 | m_pPimpl->m_cipher.SetKeyWithIV(key.BytePtr(), key.SizeInBytes(), iv.BytePtr()); 71 | 72 | return true; 73 | } 74 | 75 | bool DHChachaFilter::PreSend(Buffer::Writer* apBuffer, uint32_t aSequenceNumber) 76 | { 77 | (void)apBuffer; 78 | (void)aSequenceNumber; 79 | 80 | return true; 81 | } 82 | 83 | bool DHChachaFilter::PostSend(uint8_t* apPayload, size_t aLength, uint32_t aSequenceNumber) 84 | { 85 | // Operation is reversible 86 | return PreReceive(apPayload, aLength, aSequenceNumber); 87 | } 88 | 89 | bool DHChachaFilter::PreReceive(uint8_t* apPayload, size_t aLength, uint32_t aSequenceNumber) 90 | { 91 | std::array iv; 92 | 93 | std::copy(std::begin(m_iv), std::end(m_iv), std::begin(iv)); 94 | 95 | uint8_t* pSequenceAsBytes = (uint8_t*)& aSequenceNumber; 96 | std::copy(pSequenceAsBytes, pSequenceAsBytes + 4, std::begin(iv) + std::size(m_iv)); 97 | 98 | m_pPimpl->m_cipher.Resynchronize(iv.data(), std::size(iv)); 99 | m_pPimpl->m_cipher.ProcessData(apPayload, apPayload, aLength); 100 | 101 | return true; 102 | } 103 | 104 | void DHChachaFilter::GenerateKeys() 105 | { 106 | CryptoPP::AutoSeededX917RNG rng; 107 | 108 | m_pPimpl->m_dh.GenerateKeyPair(rng, m_pPimpl->m_priKey, m_pPimpl->m_pubKey); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Code/tests/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /Code/tests/src/network.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | #include "Socket.hpp" 4 | #include "Resolver.hpp" 5 | #include "Server.hpp" 6 | #include "Selector.hpp" 7 | 8 | #include 9 | #include 10 | 11 | using namespace TiltedPhoques; 12 | 13 | 14 | TEST_CASE("Endpoint", "[network.endpoint]") 15 | { 16 | InitializeNetwork(); 17 | 18 | GIVEN("An empty endpoint") 19 | { 20 | Endpoint endpoint; 21 | REQUIRE(endpoint.IsValid() == false); 22 | } 23 | } 24 | 25 | TEST_CASE("Resolver", "[network.resolver]") 26 | { 27 | GIVEN("An empty address") 28 | { 29 | Resolver resolver(""); 30 | REQUIRE(resolver.IsEmpty() == true); 31 | } 32 | 33 | GIVEN("An IPv4") 34 | { 35 | Resolver resolver("127.0.0.1"); 36 | REQUIRE(resolver.GetSize() == 1); 37 | Endpoint endpoint = resolver[0]; 38 | REQUIRE(endpoint.IsIPv4() == true); 39 | REQUIRE(endpoint.GetPort() == 0); 40 | REQUIRE(endpoint.GetIPv4()[0] == 127); 41 | REQUIRE(endpoint.GetIPv4()[1] == 0); 42 | REQUIRE(endpoint.GetIPv4()[2] == 0); 43 | REQUIRE(endpoint.GetIPv4()[3] == 1); 44 | } 45 | GIVEN("An IPv4 with a port") 46 | { 47 | Resolver resolver("127.0.0.1:12345"); 48 | REQUIRE(resolver.GetSize() == 1); 49 | Endpoint endpoint = resolver[0]; 50 | REQUIRE(endpoint.IsIPv4() == true); 51 | REQUIRE(endpoint.GetPort() == 12345); 52 | REQUIRE(endpoint.GetIPv4()[0] == 127); 53 | REQUIRE(endpoint.GetIPv4()[1] == 0); 54 | REQUIRE(endpoint.GetIPv4()[2] == 0); 55 | REQUIRE(endpoint.GetIPv4()[3] == 1); 56 | } 57 | GIVEN("A bad IPv4") 58 | { 59 | Resolver resolver("127.0.0.0.1"); 60 | REQUIRE(resolver.IsEmpty() == true); 61 | } 62 | GIVEN("A bad hostname") 63 | { 64 | Resolver resolver("lolcalhost777"); 65 | REQUIRE(resolver.IsEmpty() == true); 66 | } 67 | GIVEN("A hostname") 68 | { 69 | Resolver resolver("localhost"); 70 | REQUIRE(resolver.IsEmpty() == false); 71 | 72 | for (const Endpoint& endpoint : resolver) { 73 | REQUIRE(endpoint.IsValid() == true); 74 | REQUIRE(endpoint.GetPort() == 0); 75 | 76 | if (endpoint.IsIPv6()) 77 | { 78 | REQUIRE(endpoint.GetIPv6()[0] == 0); 79 | REQUIRE(endpoint.GetIPv6()[1] == 0); 80 | REQUIRE(endpoint.GetIPv6()[2] == 0); 81 | REQUIRE(endpoint.GetIPv6()[3] == 0); 82 | REQUIRE(endpoint.GetIPv6()[4] == 0); 83 | REQUIRE(endpoint.GetIPv6()[5] == 0); 84 | REQUIRE(endpoint.GetIPv6()[6] == 0); 85 | REQUIRE(endpoint.GetIPv6()[7] == 1); 86 | } 87 | else 88 | { 89 | REQUIRE(endpoint.GetIPv4()[0] == 127); 90 | REQUIRE(endpoint.GetIPv4()[1] == 0); 91 | REQUIRE(endpoint.GetIPv4()[2] == 0); 92 | REQUIRE(endpoint.GetIPv4()[3] == 1); 93 | } 94 | } 95 | } 96 | GIVEN("A hostname with a port") 97 | { 98 | Resolver resolver_original("localhost:12345"); 99 | Resolver resolver(std::move(resolver_original)); 100 | REQUIRE(resolver.IsEmpty() == false); 101 | 102 | for (const Endpoint& endpoint : resolver) { 103 | REQUIRE(endpoint.IsValid() == true); 104 | REQUIRE(endpoint.GetPort() == 12345); 105 | 106 | if (endpoint.IsIPv6()) 107 | { 108 | REQUIRE(endpoint.GetIPv6()[0] == 0); 109 | REQUIRE(endpoint.GetIPv6()[1] == 0); 110 | REQUIRE(endpoint.GetIPv6()[2] == 0); 111 | REQUIRE(endpoint.GetIPv6()[3] == 0); 112 | REQUIRE(endpoint.GetIPv6()[4] == 0); 113 | REQUIRE(endpoint.GetIPv6()[5] == 0); 114 | REQUIRE(endpoint.GetIPv6()[6] == 0); 115 | REQUIRE(endpoint.GetIPv6()[7] == 1); 116 | } 117 | else 118 | { 119 | REQUIRE(endpoint.GetIPv4()[0] == 127); 120 | REQUIRE(endpoint.GetIPv4()[1] == 0); 121 | REQUIRE(endpoint.GetIPv4()[2] == 0); 122 | REQUIRE(endpoint.GetIPv4()[3] == 1); 123 | } 124 | } 125 | } 126 | GIVEN("An IPv6") 127 | { 128 | Resolver resolver("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); 129 | REQUIRE(resolver.GetSize() == 1); 130 | Endpoint endpoint = resolver[0]; 131 | REQUIRE(endpoint.IsIPv6() == true); 132 | REQUIRE(endpoint.GetPort() == 0); 133 | REQUIRE(endpoint.GetIPv6()[0] == 0x2001); 134 | REQUIRE(endpoint.GetIPv6()[1] == 0x0db8); 135 | REQUIRE(endpoint.GetIPv6()[2] == 0x85a3); 136 | REQUIRE(endpoint.GetIPv6()[3] == 0x0000); 137 | REQUIRE(endpoint.GetIPv6()[4] == 0x0000); 138 | REQUIRE(endpoint.GetIPv6()[5] == 0x8a2e); 139 | REQUIRE(endpoint.GetIPv6()[6] == 0x0370); 140 | REQUIRE(endpoint.GetIPv6()[7] == 0x7334); 141 | } 142 | GIVEN("An IPv6 with a port") 143 | { 144 | Resolver resolver("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:12345"); 145 | REQUIRE(resolver.GetSize() == 1); 146 | Endpoint endpoint = resolver[0]; 147 | REQUIRE(endpoint.IsIPv6() == true); 148 | REQUIRE(endpoint.GetPort() == 12345); 149 | REQUIRE(endpoint.GetIPv6()[0] == 0x2001); 150 | REQUIRE(endpoint.GetIPv6()[1] == 0x0db8); 151 | REQUIRE(endpoint.GetIPv6()[2] == 0x85a3); 152 | REQUIRE(endpoint.GetIPv6()[3] == 0x0000); 153 | REQUIRE(endpoint.GetIPv6()[4] == 0x0000); 154 | REQUIRE(endpoint.GetIPv6()[5] == 0x8a2e); 155 | REQUIRE(endpoint.GetIPv6()[6] == 0x0370); 156 | REQUIRE(endpoint.GetIPv6()[7] == 0x7334); 157 | } 158 | } 159 | 160 | TEST_CASE("Networking", "[network]") 161 | { 162 | GIVEN("A basic socket") 163 | { 164 | Socket sock; 165 | REQUIRE(sock.GetPort() == 0); 166 | REQUIRE(sock.Bind() == true); 167 | REQUIRE(sock.GetPort() != 0); 168 | } 169 | GIVEN("Two sockets v4") 170 | { 171 | static const std::string testString = "abcdef"; 172 | 173 | Buffer buffer(100); 174 | 175 | Socket client(Endpoint::kIPv4), server(Endpoint::kIPv4); 176 | REQUIRE(client.Bind()); 177 | REQUIRE(server.Bind()); 178 | 179 | Selector clientSelector(client); 180 | Selector serverSelector(server); 181 | 182 | REQUIRE(clientSelector.IsReady() == false); 183 | REQUIRE(serverSelector.IsReady() == false); 184 | 185 | Resolver localhostResolver("127.0.0.1"); 186 | Endpoint clientEndpoint = localhostResolver[0]; 187 | Endpoint serverEndpoint = localhostResolver[0]; 188 | clientEndpoint.SetPort(client.GetPort()); 189 | serverEndpoint.SetPort(server.GetPort()); 190 | 191 | Buffer::Writer writer(&buffer); 192 | writer.WriteBytes((const uint8_t*)testString.data(), testString.size()); 193 | 194 | Socket::Packet packet{ serverEndpoint, buffer }; 195 | 196 | REQUIRE(serverSelector.IsReady() == false); 197 | 198 | REQUIRE(client.Send(packet)); 199 | 200 | // Now the server should have available data 201 | REQUIRE(serverSelector.IsReady()); 202 | 203 | auto result = server.Receive(); 204 | REQUIRE(result.HasError() == false); 205 | auto data = result.GetResult(); 206 | REQUIRE(std::memcmp(data.Payload.GetData(), buffer.GetData(), buffer.GetSize()) == 0); 207 | REQUIRE(data.Remote.IsIPv4()); 208 | 209 | // Reply exactly what we got to the client 210 | REQUIRE(server.Send(data)); 211 | result = client.Receive(); 212 | REQUIRE(result.HasError() == false); 213 | data = result.GetResult(); 214 | REQUIRE(std::memcmp(data.Payload.GetData(), buffer.GetData(), buffer.GetSize()) == 0); 215 | REQUIRE(data.Remote.IsIPv4()); 216 | } 217 | GIVEN("Two sockets v6") 218 | { 219 | static const std::string testString = "abcdef"; 220 | 221 | Buffer buffer(100); 222 | 223 | Socket client, server; 224 | REQUIRE(client.Bind()); 225 | REQUIRE(server.Bind()); 226 | 227 | Selector clientSelector(client); 228 | Selector serverSelector(server); 229 | 230 | REQUIRE(clientSelector.IsReady() == false); 231 | REQUIRE(serverSelector.IsReady() == false); 232 | 233 | Resolver localhostResolver("[::1]"); 234 | Endpoint clientEndpoint = localhostResolver[0]; 235 | Endpoint serverEndpoint = localhostResolver[0]; 236 | clientEndpoint.SetPort(client.GetPort()); 237 | serverEndpoint.SetPort(server.GetPort()); 238 | 239 | Buffer::Writer writer(&buffer); 240 | writer.WriteBytes((const uint8_t*)testString.data(), testString.size()); 241 | 242 | Socket::Packet packet{ serverEndpoint, buffer }; 243 | 244 | REQUIRE(serverSelector.IsReady() == false); 245 | 246 | REQUIRE(client.Send(packet)); 247 | 248 | // Now the server should have available data 249 | REQUIRE(serverSelector.IsReady()); 250 | 251 | auto result = server.Receive(); 252 | REQUIRE(result.HasError() == false); 253 | auto data = result.GetResult(); 254 | REQUIRE(std::memcmp(data.Payload.GetData(), buffer.GetData(), buffer.GetSize()) == 0); 255 | REQUIRE(data.Remote.IsIPv6()); 256 | 257 | // Reply exactly what we got to the client 258 | REQUIRE(server.Send(data)); 259 | result = client.Receive(); 260 | REQUIRE(result.HasError() == false); 261 | data = result.GetResult(); 262 | REQUIRE(std::memcmp(data.Payload.GetData(), buffer.GetData(), buffer.GetSize()) == 0); 263 | REQUIRE(data.Remote.IsIPv6()); 264 | } 265 | 266 | GIVEN("A client server model") 267 | { 268 | Buffer buffer(100); 269 | 270 | Server server; 271 | REQUIRE(server.Start(0)); 272 | 273 | REQUIRE(server.GetPort() != 0); 274 | 275 | Resolver v4Resolver("127.0.0.1"); 276 | Resolver v6Resolver("[::1]"); 277 | Endpoint serverEndpointv4(v4Resolver[0]); 278 | Endpoint serverEndpointv6(v6Resolver[0]); 279 | 280 | serverEndpointv6.SetPort(server.GetPort()); 281 | serverEndpointv4.SetPort(server.GetPort()); 282 | 283 | Socket clientv6(Endpoint::kIPv6); 284 | Socket clientv4(Endpoint::kIPv4); 285 | 286 | clientv6.Bind(); 287 | clientv4.Bind(); 288 | 289 | Socket::Packet packetv6{ serverEndpointv6, buffer }; 290 | Socket::Packet packetv4{ serverEndpointv4, buffer }; 291 | 292 | REQUIRE(clientv6.Send(packetv6)); 293 | 294 | REQUIRE(server.Update(1) == 1); 295 | 296 | REQUIRE(clientv4.Send(packetv4)); 297 | 298 | REQUIRE(server.Update(1) == 1); 299 | 300 | REQUIRE(clientv6.Send(packetv6)); 301 | REQUIRE(clientv4.Send(packetv4)); 302 | 303 | REQUIRE(server.Update(1) == 2); 304 | } 305 | } 306 | 307 | TEST_CASE("Connection", "[network.connection]") 308 | { 309 | GIVEN("A connection with a dummy interface") 310 | { 311 | static uint32_t s_count{0}; 312 | Resolver localhostResolver("127.0.0.1"); 313 | static Endpoint remoteEndpoint = localhostResolver[0]; 314 | static Buffer buffer; 315 | 316 | remoteEndpoint.SetPort(12345); 317 | 318 | struct DummyCommunication : Connection::ICommunication 319 | { 320 | bool Send(const Endpoint& acRemote, Buffer aBuffer) override 321 | { 322 | REQUIRE(acRemote == remoteEndpoint); 323 | buffer = aBuffer; 324 | ++s_count; 325 | return true; 326 | } 327 | }; 328 | 329 | DummyCommunication comm; 330 | 331 | Connection connection(comm, remoteEndpoint); 332 | Connection connection2(comm, remoteEndpoint); 333 | REQUIRE(connection.IsNegotiating()); 334 | 335 | connection.Update(1); 336 | 337 | REQUIRE(s_count == 1); 338 | REQUIRE(buffer.GetData()[0] == 'M'); 339 | REQUIRE(buffer.GetData()[1] == 'G'); 340 | 341 | REQUIRE(connection2.ProcessNegociation(&buffer)); 342 | } 343 | } 344 | 345 | TEST_CASE("Server", "[network.server]") 346 | { 347 | GIVEN("A client server model") 348 | { 349 | Buffer buffer(100); 350 | 351 | Server server; 352 | REQUIRE(server.Start(0)); 353 | REQUIRE(server.GetPort() != 0); 354 | 355 | Resolver localhostResolver("127.0.0.1"); 356 | Endpoint serverEndpoint = localhostResolver[0]; 357 | 358 | serverEndpoint.SetPort(server.GetPort()); 359 | 360 | Socket client(Endpoint::kIPv4); 361 | client.Bind(); 362 | 363 | Socket::Packet packet{ serverEndpoint, buffer }; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /Code/tests/src/protocol.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | #include "DHChachaFilter.hpp" 4 | #include 5 | 6 | using namespace TiltedPhoques; 7 | 8 | TEST_CASE("Protocol DHChaCha", "[protocol.dhchacha]") 9 | { 10 | GIVEN("Two DHChaCha filters") 11 | { 12 | DHChachaFilter clientFilter; 13 | DHChachaFilter serverFilter; 14 | 15 | static std::string data{ "abcdefhijklmnopqrstuvwxyz" }; 16 | 17 | WHEN("Using key exchange") 18 | { 19 | Buffer keyExchangePacketClient(1024); 20 | Buffer keyExchangePacketServer(1024); 21 | 22 | Buffer::Writer writerClient(&keyExchangePacketClient); 23 | Buffer::Reader readerClient(&keyExchangePacketClient); 24 | 25 | Buffer::Writer writerServer(&keyExchangePacketServer); 26 | Buffer::Reader readerServer(&keyExchangePacketServer); 27 | 28 | // Client generates connection packet 29 | REQUIRE(clientFilter.PreConnect(&writerClient) == true); 30 | // Server reads generated packet 31 | REQUIRE(serverFilter.ReceiveConnect(&readerClient) == true); 32 | // Server generates connection response packet 33 | REQUIRE(serverFilter.PreConnect(&writerServer) == true); 34 | // Client reads generated packet 35 | REQUIRE(clientFilter.ReceiveConnect(&readerServer) == true); 36 | 37 | AND_THEN("Using symmetric encryption") 38 | { 39 | Buffer buffer(100); 40 | 41 | for (uint32_t i = 0; i < 16; ++i) 42 | { 43 | Buffer::Writer writer(&buffer); 44 | REQUIRE(writer.WriteBytes((uint8_t*)data.data(), data.length()) == true); 45 | 46 | REQUIRE(clientFilter.PostSend(buffer.GetWriteData(), buffer.GetSize(), i) == true); 47 | REQUIRE(std::memcmp(buffer.GetData(), data.data(), data.length()) != 0); 48 | 49 | REQUIRE(serverFilter.PreReceive(buffer.GetWriteData(), buffer.GetSize(), i) == true); 50 | REQUIRE(std::memcmp(buffer.GetData(), data.data(), data.length()) == 0); 51 | } 52 | } 53 | } 54 | 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Tilted Phoques. All Rights Reserved. 2 | 3 | All materials are made available on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. Do not remove or modify any license notices. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tilted Network 2 | 3 | [![Build Status](https://dev.azure.com/TiltedPhoques/TiltedNetwork/_apis/build/status/tiltedphoques.TiltedNetwork?branchName=master)](https://dev.azure.com/TiltedPhoques/TiltedNetwork/_build/latest?definitionId=1&branchName=master) 4 | 5 | This library is yet another attempt to get a decent networking library designed with games in mind. 6 | -------------------------------------------------------------------------------- /ThirdParty/endian.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // from https://stackoverflow.com/questions/1583791/constexpr-and-endianness 4 | #define IS_LITTLE_ENDIAN ('ABCD'==0x41424344UL) //41 42 43 44 = 'ABCD' hex ASCII code 5 | #define IS_BIG_ENDIAN ('ABCD'==0x44434241UL) //44 43 42 41 = 'DCBA' hex ASCII code 6 | #define IS_UNKNOWN_ENDIAN (IS_LITTLE_ENDIAN == IS_BIG_ENDIAN) 7 | 8 | // from http://stackoverflow.com/a/4956493/238609 9 | template 10 | T swap_endian(T u) 11 | { 12 | union 13 | { 14 | T u; 15 | unsigned char u8[sizeof(T)]; 16 | } source, dest; 17 | 18 | source.u = u; 19 | 20 | for (size_t k = 0; k < sizeof(T); k++) 21 | dest.u8[k] = source.u8[sizeof(T) - k - 1]; 22 | 23 | return dest.u; 24 | } 25 | 26 | template 27 | T to_big_endian(T u) 28 | { 29 | #if IS_BIG_ENDIAN 30 | return u; 31 | #else 32 | return swap_endian(u); 33 | #endif 34 | } 35 | 36 | template 37 | T to_little_endian(T u) 38 | { 39 | #if IS_LITTLE_ENDIAN 40 | return u; 41 | #else 42 | return swap_endian(u); 43 | #endif 44 | } 45 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: Linux 3 | displayName: Linux 4 | pool: 5 | vmImage: ubuntu-latest 6 | 7 | variables: 8 | platform: x64 9 | 10 | steps: 11 | - template: .ci/linux-build.yml 12 | 13 | - job: Windows_x32 14 | displayName: Windows x32 15 | pool: 16 | vmImage: windows-2019 17 | 18 | variables: 19 | platform: Win32 20 | 21 | steps: 22 | - template: .ci/windows-build.yml 23 | 24 | - job: Windows_x64 25 | displayName: Windows x64 26 | pool: 27 | vmImage: windows-2019 28 | 29 | variables: 30 | platform: x64 31 | 32 | steps: 33 | - template: .ci/windows-build.yml 34 | --------------------------------------------------------------------------------