netcode-io (empty) → 0.0.1
raw patch · 18 files changed
+11460/−0 lines, 18 filesdep +basedep +bindings-DSLdep +netcode-iosetup-changed
Dependencies added: base, bindings-DSL, netcode-io
Files
- Bindings/Netcode/IO.hsc +191/−0
- LICENSE +29/−0
- Netcode/IO.hs +126/−0
- Netcode/IO/Address.hs +134/−0
- Netcode/IO/Callbacks.hs +51/−0
- Netcode/IO/Client.hs +415/−0
- Netcode/IO/Packet.hs +33/−0
- Netcode/IO/Server.hs +403/−0
- README.md +53/−0
- Setup.hs +2/−0
- examples/Client.hs +114/−0
- examples/ClientServer.hs +142/−0
- examples/RunCUnitTests.hs +15/−0
- examples/Server.hs +82/−0
- examples/Soak.hs +293/−0
- netcode-io.cabal +124/−0
- netcode.io/netcode.c +8971/−0
- netcode.io/netcode.h +282/−0
+ Bindings/Netcode/IO.hsc view
@@ -0,0 +1,191 @@+{-| + Module : Bindings.Netcode.IO + Description : Low-level bindings to the netcode.io library. + Copyright : (c) Pavel Krajcevski, 2020 + License : BSD-3 + Maintainer : krajcevski@gmail.com + Stability : experimental + Portability : Portable + + This module contains the low-level bindings that represent the direct + interface between Haskell and the C library + <https://github.com/networkprotocol/netcode.io netcode.io>. + + The bindings here are meant for advanced usage, as they are not particularly + idiomatic Haskell, and largely represent the types that we get from the + <https://hackage.haskell.org/package/bindings-DSL bindings-DSL> library. For + high level bindings (recommended), please refer to the "Netcode.IO" module. +-} + +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE StandaloneDeriving #-} +-------------------------------------------------------------------------------- + +#include <netcode.h> +#include <bindings.dsl.h> + +-------------------------------------------------------------------------------- + +module Bindings.Netcode.IO where + +import Data.Word (Word8, Word16, Word64) +import Foreign.C.String (CString) +import Foreign.C.Types (CInt(..), CDouble(..)) +import Foreign.Marshal.Array (peekArray, pokeArray) +import Foreign.Ptr (Ptr, FunPtr, plusPtr) +import Foreign.Storable (Storable(..)) +import Prelude ( IO, Eq, Show, Num + , ($) + , div, undefined, return, take + ) + +-------------------------------------------------------------------------------- + +#num NETCODE_CONNECT_TOKEN_BYTES +#num NETCODE_KEY_BYTES +#num NETCODE_MAC_BYTES +#num NETCODE_USER_DATA_BYTES +#num NETCODE_MAX_SERVERS_PER_CONNECT + +#num NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED +#num NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN +#num NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT +#num NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT +#num NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT +#num NETCODE_CLIENT_STATE_CONNECTION_DENIED +#num NETCODE_CLIENT_STATE_DISCONNECTED +#num NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST +#num NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE +#num NETCODE_CLIENT_STATE_CONNECTED + +#num NETCODE_MAX_CLIENTS +#num NETCODE_MAX_PACKET_SIZE + +#num NETCODE_LOG_LEVEL_NONE +#num NETCODE_LOG_LEVEL_ERROR +#num NETCODE_LOG_LEVEL_INFO +#num NETCODE_LOG_LEVEL_DEBUG + +#num NETCODE_OK +#num NETCODE_ERROR + +#num NETCODE_ADDRESS_NONE +#num NETCODE_ADDRESS_IPV4 +#num NETCODE_ADDRESS_IPV6 + +#ccall netcode_init, IO CInt +#ccall netcode_term, IO () + +#starttype struct netcode_address_t +#array_field data.ipv4, Word8 +#array_field data.ipv6, Word16 +#field port, Word16 +#field type, Word8 +#stoptype + +#ccall netcode_parse_address, CString -> Ptr <netcode_address_t> -> IO CInt +#ccall netcode_address_to_string, Ptr <netcode_address_t> -> CString -> IO CString +#ccall netcode_address_equal, Ptr <netcode_address_t> -> Ptr <netcode_address_t> -> IO CInt + +#opaque_t netcode_network_simulator_t +#opaque_t netcode_client_t +#opaque_t netcode_server_t + +#callback_t allocate_function_t, Ptr () -> Word64 -> IO (Ptr ()) +#callback_t free_function_t, Ptr () -> Ptr () -> IO () +#callback_t state_change_callback_t, Ptr () -> CInt -> CInt -> IO () +#callback_t connect_disconnect_callback_t, Ptr () -> CInt -> CInt -> IO () +#callback_t send_loopback_packet_callback_t, Ptr () -> CInt -> Ptr Word8 -> CInt -> Word64 -> IO () +#callback_t send_packet_override_t, Ptr () -> Ptr <netcode_address_t> -> Ptr Word8 -> CInt -> IO () +#callback_t receive_packet_override_t, Ptr () -> Ptr <netcode_address_t> -> Ptr Word8 -> CInt -> IO CInt + +#starttype struct netcode_client_config_t +#field allocator_context, Ptr () +#field allocate_function, <allocate_function_t> +#field free_function, <free_function_t> +#field network_simulator, Ptr <netcode_network_simulator_t> +#field callback_context, Ptr () +#field state_change_callback, <state_change_callback_t> +#field send_loopback_packet_callback, <send_loopback_packet_callback_t> +#field override_send_and_receive, CInt +#field send_packet_override, <send_packet_override_t> +#field receive_packet_override, <receive_packet_override_t> +#stoptype + +#ccall netcode_default_client_config, Ptr <netcode_client_config_t> -> IO () +#ccall netcode_client_create, CString -> Ptr <netcode_client_config_t> -> CDouble -> IO (Ptr <netcode_client_t>) +#ccall netcode_client_destroy, Ptr <netcode_client_t> -> IO () +#ccall netcode_client_connect, Ptr <netcode_client_t> -> Ptr Word8 -> IO () +#ccall netcode_client_update, Ptr <netcode_client_t> -> CDouble -> IO () +#ccall netcode_client_next_packet_sequence, Ptr <netcode_client_t> -> IO Word64 +#ccall netcode_client_send_packet, Ptr <netcode_client_t> -> Ptr Word8 -> CInt -> IO () +#ccall netcode_client_receive_packet, Ptr <netcode_client_t> -> Ptr CInt -> Ptr Word64 -> IO (Ptr Word8) +#ccall netcode_client_free_packet, Ptr <netcode_client_t> -> Ptr () -> IO () +#ccall netcode_client_disconnect, Ptr <netcode_client_t> -> IO () +#ccall netcode_client_state, Ptr <netcode_client_t> -> IO CInt +#ccall netcode_client_index, Ptr <netcode_client_t> -> IO CInt +#ccall netcode_client_max_clients, Ptr <netcode_client_t> -> IO CInt +#ccall netcode_client_connect_loopback, Ptr <netcode_client_t> -> CInt -> CInt -> IO () +#ccall netcode_client_disconnect_loopback, Ptr <netcode_client_t> -> IO () +#ccall netcode_client_process_packet, Ptr <netcode_client_t> -> Ptr <netcode_address_t> -> Ptr Word8 -> CInt -> IO () +#ccall netcode_client_loopback, Ptr <netcode_client_t> -> IO CInt +#ccall netcode_client_process_loopback_packet, Ptr <netcode_client_t> -> Ptr Word8 -> CInt -> Word64 -> IO () +#ccall netcode_client_get_port, Ptr <netcode_client_t> -> IO Word16 +#ccall netcode_client_server_address, Ptr <netcode_client_t> -> IO (Ptr <netcode_address_t>) +#ccall netcode_generate_connect_token, CInt -> Ptr CString -> Ptr CString -> CInt -> CInt -> Word64 -> Word64 -> Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt + +#starttype struct netcode_server_config_t +#field protocol_id, Word64 +#array_field private_key, Word8 +#field allocator_context, Ptr () +#field allocate_function, <allocate_function_t> +#field free_function, <free_function_t> +#field network_simulator, Ptr <netcode_network_simulator_t> +#field callback_context, Ptr () +#field connect_disconnect_callback, <connect_disconnect_callback_t> +#field send_loopback_packet_callback, <send_loopback_packet_callback_t> +#field override_send_and_receive, CInt +#field send_packet_override, <send_packet_override_t> +#field receive_packet_override, <receive_packet_override_t> +#stoptype + +#ccall netcode_default_server_config, Ptr <netcode_server_config_t> -> IO () +#ccall netcode_server_create, CString -> Ptr <netcode_server_config_t> -> CDouble -> IO (Ptr <netcode_server_t>) +#ccall netcode_server_destroy, Ptr <netcode_server_t> -> IO () +#ccall netcode_server_start, Ptr <netcode_server_t> -> CInt -> IO () +#ccall netcode_server_stop, Ptr <netcode_server_t> -> IO () +#ccall netcode_server_running, Ptr <netcode_server_t> -> IO CInt +#ccall netcode_server_max_clients, Ptr <netcode_server_t> -> IO CInt +#ccall netcode_server_update, Ptr <netcode_server_t> -> CDouble -> IO () +#ccall netcode_server_client_connected, Ptr <netcode_server_t> -> CInt -> IO CInt +#ccall netcode_server_client_id, Ptr <netcode_server_t> -> CInt -> IO Word64 +#ccall netcode_server_client_address, Ptr <netcode_server_t> -> CInt -> IO (Ptr <netcode_address_t>) +#ccall netcode_server_disconnect_client, Ptr <netcode_server_t> -> CInt -> IO () +#ccall netcode_server_disconnect_all_clients, Ptr <netcode_server_t> -> IO () +#ccall netcode_server_next_packet_sequence, Ptr <netcode_server_t> -> CInt -> IO Word64 +#ccall netcode_server_send_packet, Ptr <netcode_server_t> -> CInt -> Ptr Word8 -> CInt -> IO () +#ccall netcode_server_receive_packet, Ptr <netcode_server_t> -> CInt -> Ptr Word8 -> Ptr Word64 -> IO (Ptr Word8) +#ccall netcode_server_free_packet, Ptr <netcode_server_t> -> Ptr () -> IO () +#ccall netcode_server_num_connected_clients, Ptr <netcode_server_t> -> IO CInt +#ccall netcode_server_client_user_data, Ptr <netcode_server_t> -> CInt -> IO (Ptr ()) +#ccall netcode_server_process_packet, Ptr <netcode_server_t> -> Ptr <netcode_address_t> -> Ptr Word8 -> CInt -> IO () +#ccall netcode_server_connect_loopback_client, Ptr <netcode_server_t> -> CInt -> Word64 -> Ptr Word8 -> IO () +#ccall netcode_server_disconnect_loopback_client, Ptr <netcode_server_t> -> CInt -> IO () +#ccall netcode_server_client_loopback, Ptr <netcode_server_t> -> CInt -> IO CInt +#ccall netcode_server_process_loopback_packet, Ptr <netcode_server_t> -> CInt -> Ptr Word8 -> CInt -> Word64 -> IO () +#ccall netcode_server_get_port, Ptr <netcode_server_t> -> IO Word16 + +#ccall netcode_log_level, CInt -> IO () + +#callback_t netcode_assert_function_ty, CString -> CString -> CString -> CInt -> IO () +#ccall netcode_set_assert_function, <netcode_assert_function_ty> -> IO () + +-- void netcode_set_printf_function( int (*function)( NETCODE_CONST char *, ... ) ); + +#ccall netcode_random_bytes, Ptr Word8 -> CInt -> IO () +#ccall netcode_sleep, CDouble -> IO () +#ccall netcode_time, IO CDouble + +-- | For testing only. +#ccall netcode_test, IO ()
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License + +Copyright (c) 2020, Pavel Krajcevski +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Netcode/IO.hs view
@@ -0,0 +1,126 @@+{-| + +Module : Netcode.IO +Description : High-level bindings to the netcode.io library. +Copyright : (c) Pavel Krajcevski, 2020 +License : BSD-3 +Maintainer : krajcevski@gmail.com +Stability : experimental +Portability : Portable + +This module contains the high-level bindings on top of the module +"Bindings.Netcode.IO". These provide a cleaner interface to the +<https://github.com/networkprotocol/netcode.io netcode.io> C library and are +the recommended interface for application developers. + +These bindings have some limitations. Namely, they are not as performant as +the "close to the metal" bindings provided in "Bindings.Netcode.IO". In the +event that you need more performance, that module is available for use. + +The general architecture of a @netcode.io@ application is outlined in the C +library +<https://github.com/networkprotocol/netcode.io/blob/master/STANDARD.md#architecture documentation>. The jist is that we need three main entities: + + * A server to connect to + * An authentication server to dispatch connection tokens + * A client that wants to connect to a server + +In this case, the client will request a connection token from the authentication +server. The authentication server will know a-priori what the available servers +are for the client to connect to. These servers (and other information) are +stored in an encrypted 'ConnectToken', based on the 64-bit unique client ID. The +authentication server will send a 'ConnectToken' to a client when that client +looks for servers to connect to. + +Once a connection between server and client is established, they may exchange +packets of information. These packets are sent over UDP and therefore are not +guaranteed to arrive in order or even arrive at all. In order to create a +reliable information channel between client and server, it is recommended to +use this library in conjunction with the +<https://github.com/networkprotocol/reliable.io reliable.io> library. +-} + +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +module Netcode.IO ( + -- * Initialization + initialize + , terminate + + -- * Addresses + , Address + , AddressMode(..) + , addressMode + , addressPort + , addressValues + , constructAddress + , parseAddress + , addressToString + , addressEqual + + -- * Common callbacks + , SendPacketOverride + , ReceivePacketOverride + + -- * Packets + , module Netcode.IO.Packet + , module Netcode.IO.Client + , module Netcode.IO.Server + + -- * Utilities + , sleep + + , LogLevel(..), logLevel +) where +-------------------------------------------------------------------------------- + +import Control.Monad (unless) +import Data.Data (Data) +import Data.Typeable (Typeable) +import Foreign.C.Types (CDouble(..)) +import GHC.Generics (Generic) + +import Bindings.Netcode.IO +import Netcode.IO.Address +import Netcode.IO.Callbacks +import Netcode.IO.Client +import Netcode.IO.Packet +import Netcode.IO.Server + +-------------------------------------------------------------------------------- + +-- | Initializes the @netcode.io@ library runtime. This should be called before +-- any additional functions in this library. Throws an +-- t'Control.Exception.IOException' on failure. +initialize :: IO () +initialize = do + result <- c'netcode_init + unless (result == c'NETCODE_OK) $ + fail $ "Failed to initialize netcode.io. Result: " <> show result + return () + +-- | Terminates the @netcode.io@ library runtime. This should be called only +-- after all other library functions terminate. +terminate :: IO () +terminate = c'netcode_term + +-- | Specifies the logging behavior of @netcode.io@. Note, this logging behavior +-- is called from C calls to @printf@ and therefore might interfere with the +-- Haskell runtime (such as 'putStrLn'). +data LogLevel = LogLevel'None + | LogLevel'Info + | LogLevel'Error + | LogLevel'Debug + deriving (Eq, Ord, Show, Read, Bounded, Enum, Generic, Data, Typeable) + +-- | Set the @netcode.io@ 'LogLevel'. The default is 'LogLevel'None'. +logLevel :: LogLevel -> IO () +logLevel LogLevel'None = c'netcode_log_level c'NETCODE_LOG_LEVEL_NONE +logLevel LogLevel'Info = c'netcode_log_level c'NETCODE_LOG_LEVEL_INFO +logLevel LogLevel'Error = c'netcode_log_level c'NETCODE_LOG_LEVEL_ERROR +logLevel LogLevel'Debug = c'netcode_log_level c'NETCODE_LOG_LEVEL_DEBUG + +-- | Sleep the current thread. This is usually only used in example programs. +-- It's probably safer to use the built-in 'Control.Concurrent.threadDelay'. +sleep :: Double -> IO () +sleep = c'netcode_sleep . CDouble
+ Netcode/IO/Address.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +module Netcode.IO.Address where + +-------------------------------------------------------------------------------- + +import Data.Data (Data) +import Data.Typeable (Typeable) +import Data.Word (Word8, Word16) +import Control.Monad (unless) +import Foreign.C.String (withCString, peekCString) +import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, mallocForeignPtr) +import Foreign.Marshal.Alloc (allocaBytes) +import Foreign.Storable (peek, poke) +import GHC.Generics (Generic) + +import Bindings.Netcode.IO + +-------------------------------------------------------------------------------- + +-- | An opaque type that encapsulates an address that can be used with +-- @netcode.io@. The address may be stored in memory in different ways (for +-- example with encryption), and therefore needs the IO monad to interact with +-- it. +-- +-- <https://github.com/Mokosha/netcode-io/issues/1 TODO>: Use a high-level +-- representation here to enable a more pure interface. +newtype Address = Address (ForeignPtr C'netcode_address_t) + deriving (Show) + +-- | The address mode based on how the address is represented. Usually a +-- consequence of how it was parsed. +data AddressMode + = AddressMode'Unknown -- ^ Usually when address is stored encrypted. + | AddressMode'IPv4 + | AddressMode'IPv6 + deriving (Eq, Ord, Show, Read, Enum, Bounded, Data, Typeable, Generic) + +typedAddressMode :: Word8 -> AddressMode +typedAddressMode m + | m == c'NETCODE_ADDRESS_NONE = AddressMode'Unknown + | m == c'NETCODE_ADDRESS_IPV4 = AddressMode'IPv4 + | m == c'NETCODE_ADDRESS_IPV6 = AddressMode'IPv6 + | otherwise = error "Unknown address mode!" + +rawAddressMode :: AddressMode -> Word8 +rawAddressMode AddressMode'Unknown = c'NETCODE_ADDRESS_NONE +rawAddressMode AddressMode'IPv4 = c'NETCODE_ADDRESS_IPV4 +rawAddressMode AddressMode'IPv6 = c'NETCODE_ADDRESS_IPV6 + +-- | Returns the address mode for the given address. Note, this address may be +-- stored in memory in different ways, so we must inspect the memory contents +-- in order to retreive the addressing mode. +addressMode :: Address -> IO AddressMode +addressMode (Address addr) = withForeignPtr addr $ + fmap (typedAddressMode . c'netcode_address_t'type) . peek + +-- | Returns the port associated with this address. +addressPort :: Address -> IO Word16 +addressPort (Address addr) = withForeignPtr addr $ + fmap c'netcode_address_t'port . peek + +-- | Returns the address as a sequence of values in its human readable format. +-- For example: +-- +-- >>> parseAddress "123.231.132.213" >>= addressValues +-- [123, 231, 132, 213] +-- +-- The length of the list is either 4 or 8 depending on the address mode. If +-- the address mode is unknown, 'addressValues' returns the empty list. +addressValues :: Address -> IO [Word16] +addressValues (Address fptr) = withForeignPtr fptr $ \aptr -> do + addr <- peek aptr + case (typedAddressMode $ c'netcode_address_t'type addr) of + AddressMode'IPv4 -> + return $ map fromIntegral $ c'netcode_address_t'data'ipv4 addr + AddressMode'IPv6 -> return $ c'netcode_address_t'data'ipv6 addr + _ -> return [] + +-- | Returns an address with the given values interpreted using the given mode. +-- For IPv4 addresses, only the bottom 8 bits of each 16-bit word will be used. +-- The list will be zero padded to contain enough values to fill the address as +-- needed. +constructAddress :: AddressMode + -> Word16 -- ^ Port + -> [Word16] + -> IO Address +constructAddress mode port vals = + let ipv4Vals = take 4 $ map fromIntegral $ vals <> repeat 0 + ipv6Vals = take 8 $ vals <> repeat 0 + in do + address <- mallocForeignPtr + withForeignPtr address $ \ptr -> do + addrVal <- peek ptr + case mode of + AddressMode'IPv4 -> poke ptr $ addrVal { + c'netcode_address_t'data'ipv4 = ipv4Vals + , c'netcode_address_t'port = port + , c'netcode_address_t'type = c'NETCODE_ADDRESS_IPV4 + } + AddressMode'IPv6 -> poke ptr $ addrVal { + c'netcode_address_t'data'ipv6 = ipv6Vals + , c'netcode_address_t'port = port + , c'netcode_address_t'type = c'NETCODE_ADDRESS_IPV6 + } + _ -> fail "Cannot construct address with unknown address mode" + return $ Address address + +-- | Takes a 'String' and parses it to create an 'Address'. The string should +-- be formatted as either a valid IPv4 or IPv6 address. It does not, however, +-- support dual address modes. +parseAddress :: String -> IO Address +parseAddress addrStr = do + address <- mallocForeignPtr + retVal <- withForeignPtr address $ \addressPtr -> + withCString addrStr (`c'netcode_parse_address` addressPtr) + unless (retVal == c'NETCODE_OK) $ fail "Unable to parse address" + return $ Address address + +-- | Returns a string that represents the given 'Address'. +addressToString :: Address -> IO String +addressToString (Address addrPtr) = + let maxAddrStringLen = 256 + in allocaBytes maxAddrStringLen $ \addrStr -> do + _ <- withForeignPtr addrPtr (`c'netcode_address_to_string` addrStr) + peekCString addrStr + +-- | Returns True if two addresses are equal by examining their memory contents. +addressEqual :: Address -> Address -> IO Bool +addressEqual (Address addrPtrA) (Address addrPtrB) = + withForeignPtr addrPtrA $ \ptrA -> + withForeignPtr addrPtrB $ \ptrB -> + (/= 0) <$> c'netcode_address_equal ptrA ptrB +
+ Netcode/IO/Callbacks.hs view
@@ -0,0 +1,51 @@+module Netcode.IO.Callbacks where + +------------------------------------------------------------------------------- +import Data.Word (Word8) +import Foreign.C.Types (CInt) +import Foreign.ForeignPtr (newForeignPtr_) +import Foreign.Ptr (Ptr) + +import Bindings.Netcode.IO +import Netcode.IO.Address + +------------------------------------------------------------------------------- + +-- | Override that sends a packet to the given address. This can be used by +-- both clients and servers. This is invoked after @netcode.io@ processes and +-- encrypts the packet. +type SendPacketOverride + = Address -- ^ 'Address' to send the packet to + -> Ptr Word8 -- ^ A pointer to the memory that holds the packet data + -> CInt -- ^ The size of the packet + -> IO () + +mkSendPacketOverride :: SendPacketOverride -> IO C'send_packet_override_t +mkSendPacketOverride sendFn = + mk'send_packet_override_t $ \_ aptr pkt pktSize -> do + addr <- Address <$> newForeignPtr_ aptr + sendFn addr pkt pktSize + +-- | Override that receives a packet from the given address. This can be used +-- by both clients and servers. This is invoked before @netcode.io@ processes +-- and decrypts the packet data. +-- +-- Implementations of this callback are meant to fill the memory at the given +-- pointer with the data from a packet received from the 'Address'. The maximum +-- size of the buffer pointed to is also passed and the implementation is +-- expected to return the actual size of the packet. In the event that there is +-- no packet (or equivalently, no packet data), then the implementation should +-- return zero. +type ReceivePacketOverride + = Address -- ^ Address from which to receive a packet + -> Ptr Word8 -- ^ Pointer to the buffer where to write packet data + -> CInt -- ^ Maximum size of destination buffer in bytes + -> IO CInt -- ^ Return value: should be size of packet data + +mkReceivePacketOverride :: ReceivePacketOverride + -> IO C'receive_packet_override_t +mkReceivePacketOverride recvFn = + mk'receive_packet_override_t $ \_ aptr pkt pktSize -> do + addr <- Address <$> newForeignPtr_ aptr + recvFn addr pkt pktSize +
+ Netcode/IO/Client.hs view
@@ -0,0 +1,415 @@+{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +module Netcode.IO.Client ( + -- * Clients + + -- ** Client-specific callbacks + ClientStateChangeCallback + + -- ** Client configs + , ClientConfig + , defaultClientConfig + , setClientStateChangeCallback, clearClientStateChangeCallback + , setClientSendReceiveOverrides, clearClientSendReceiveOverrides + + -- ** Client objects + , Client + , createClient + , destroyClient + , generateClientID + , connectClient + , disconnectClient + , updateClient + , sendPacketFromClient + , receivePacketFromServer + , nextClientPacketSequence + , getClientPort + , withClientServerAddress + + -- ** Client state + , ClientState(..) + , getClientState + , isClientDisconnected + + -- ** Connect Tokens + , ConnectToken + , maximumServersPerConnect + , maximumUserDataSize + , privateKeySize + , generateConnectToken +) where + +-------------------------------------------------------------------------------- + +import Control.Monad (when) +import Data.Data (Data) +import Data.Typeable (Typeable) +import Data.Word (Word8, Word16, Word64) +import Foreign.C.String (withCString) +import Foreign.C.Types (CDouble(..), CInt) +import Foreign.Concurrent (newForeignPtr) +import Foreign.ForeignPtr ( ForeignPtr, newForeignPtr_, mallocForeignPtrBytes + , withForeignPtr + ) +import Foreign.Marshal.Alloc (alloca) +import Foreign.Marshal.Array (allocaArray, pokeArray) +import Foreign.Ptr ( Ptr, nullPtr, castPtr + , FunPtr, nullFunPtr, freeHaskellFunPtr + ) +import Foreign.Storable (peek, poke, sizeOf, pokeElemOff) +import GHC.Generics (Generic) + +import Bindings.Netcode.IO +import Netcode.IO.Address +import Netcode.IO.Callbacks +import Netcode.IO.Packet + +-------------------------------------------------------------------------------- + +freeNullFunPtr :: FunPtr a -> IO () +freeNullFunPtr x + | x == nullFunPtr = return () + | otherwise = freeHaskellFunPtr x + +-------------------------------------------------------------------------------- + +-- | The possible connection states of a 'Client'. The default state is +-- 'ClientState'Disconnected'. +data ClientState + = ClientState'ConnectTokenExpired + | ClientState'InvalidConnectToken + | ClientState'ConnectionTimedOut + | ClientState'ConnectionResponseTimedOut + | ClientState'ConnectionRequestTimedOut + | ClientState'ConnectionDenied + | ClientState'Disconnected + | ClientState'SendingConnectionRequest + | ClientState'SendingConnectionResponse + | ClientState'Connected + deriving (Eq, Ord, Show, Enum, Bounded, Data, Typeable, Generic) + +_rawClientState :: ClientState -> CInt +_rawClientState ClientState'ConnectTokenExpired = c'NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED +_rawClientState ClientState'InvalidConnectToken = c'NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN +_rawClientState ClientState'ConnectionTimedOut = c'NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT +_rawClientState ClientState'ConnectionResponseTimedOut = c'NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT +_rawClientState ClientState'ConnectionRequestTimedOut = c'NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT +_rawClientState ClientState'ConnectionDenied = c'NETCODE_CLIENT_STATE_CONNECTION_DENIED +_rawClientState ClientState'Disconnected = c'NETCODE_CLIENT_STATE_DISCONNECTED +_rawClientState ClientState'SendingConnectionRequest = c'NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST +_rawClientState ClientState'SendingConnectionResponse = c'NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE +_rawClientState ClientState'Connected = c'NETCODE_CLIENT_STATE_CONNECTED + +typedClientState :: CInt -> ClientState +typedClientState raw + | raw == c'NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED = ClientState'ConnectTokenExpired + | raw == c'NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN = ClientState'InvalidConnectToken + | raw == c'NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT = ClientState'ConnectionTimedOut + | raw == c'NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT = ClientState'ConnectionResponseTimedOut + | raw == c'NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT = ClientState'ConnectionRequestTimedOut + | raw == c'NETCODE_CLIENT_STATE_CONNECTION_DENIED = ClientState'ConnectionDenied + | raw == c'NETCODE_CLIENT_STATE_DISCONNECTED = ClientState'Disconnected + | raw == c'NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST = ClientState'SendingConnectionRequest + | raw == c'NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE = ClientState'SendingConnectionResponse + | raw == c'NETCODE_CLIENT_STATE_CONNECTED = ClientState'Connected + | otherwise = error "Unrecognized client state value" + +-- | A client object. This is an opaque type meant to be used in conjunction +-- with this library. +-- +-- A 'Client' is generally meant to connect to one of potentially many servers +-- through a 'ConnectToken'. The main loop of the application that manages the +-- lifetime of the client is expected to maintain a running timer with a +-- resolution of at least seconds. This main loop is also expected to call +-- 'updateClient' on a regular basis to allow the library to process incoming +-- packets and send outgoing packets. +data Client = Client + { clientPtr :: Ptr C'netcode_client_t + , clientCallbacks :: ClientCallbacks + } deriving (Show) + +data ClientCallbacks = ClientCallbacks + { clientStateChange :: C'state_change_callback_t + , clientSendPacketOverride :: C'send_packet_override_t + , clientReceivePacketOverride :: C'receive_packet_override_t + } deriving (Show) + +defaultClientCallbacks :: ClientCallbacks +defaultClientCallbacks = ClientCallbacks nullFunPtr nullFunPtr nullFunPtr + +-- | A 'ClientConfig' is a type that specifies the behavior of a 'Client'. +-- Client configs are pretty spartan: the only options available at this time +-- are setting callbacks. +newtype ClientConfig = ClientConfig + ( Ptr C'netcode_client_config_t + -> ClientCallbacks + -> IO (Ptr C'netcode_client_config_t, ClientCallbacks) + ) + +-- | A 'ClientConfig' with no callback overrides. +defaultClientConfig :: ClientConfig +defaultClientConfig = ClientConfig $ \clientConfig cbs -> do + c'netcode_default_client_config clientConfig + return (clientConfig, cbs) + +-- | A client-specific callback that gets invoked each time the underlying +-- state of the client changes. +type ClientStateChangeCallback + = ClientState -- ^ Old state + -> ClientState -- ^ New state + -> IO () + +mkClientStateChangeCallback :: ClientStateChangeCallback + -> IO C'state_change_callback_t +mkClientStateChangeCallback cb = mk'state_change_callback_t $ \_ oldSt newSt -> + cb (typedClientState oldSt) (typedClientState newSt) + +-- | Creates a config that removes the existing 'ClientStateChangeCallback' and +-- instead uses the given callback. +setClientStateChangeCallback :: ClientStateChangeCallback + -> ClientConfig -> ClientConfig +setClientStateChangeCallback cb (ClientConfig mkConfig) = + ClientConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ clientStateChange callbacks + fPtr <- mkClientStateChangeCallback cb + config <- peek configPtr + poke configPtr $ + config { c'netcode_client_config_t'state_change_callback = fPtr } + return (configPtr, callbacks { clientStateChange = fPtr }) + +-- | Clears the 'ClientStateChangeCallback' for the given config. +clearClientStateChangeCallback :: ClientConfig -> ClientConfig +clearClientStateChangeCallback (ClientConfig mkConfig) = + ClientConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ clientStateChange callbacks + config <- peek configPtr + poke configPtr $ + config { c'netcode_client_config_t'state_change_callback = nullFunPtr } + return (configPtr, callbacks { clientStateChange = nullFunPtr }) + +-- | Removes the existing send and receive overrides for the given config, if +-- set, and instead uses the ones given. +setClientSendReceiveOverrides :: SendPacketOverride + -> ReceivePacketOverride + -> ClientConfig -> ClientConfig +setClientSendReceiveOverrides sendFn recvFn (ClientConfig mkConfig) = + ClientConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ clientSendPacketOverride callbacks + freeNullFunPtr $ clientReceivePacketOverride callbacks + config <- peek configPtr + sendOverride <- mkSendPacketOverride sendFn + recvOverride <- mkReceivePacketOverride recvFn + poke configPtr $ config + { c'netcode_client_config_t'send_packet_override = sendOverride + , c'netcode_client_config_t'receive_packet_override = recvOverride + , c'netcode_client_config_t'override_send_and_receive = 1 + } + let newcbs = callbacks + { clientSendPacketOverride = sendOverride + , clientReceivePacketOverride = recvOverride + } + return (configPtr, newcbs) + +-- | Changes the config to use the default send and receive packet functions. +clearClientSendReceiveOverrides :: ClientConfig -> ClientConfig +clearClientSendReceiveOverrides (ClientConfig mkConfig) = + ClientConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ clientSendPacketOverride callbacks + freeNullFunPtr $ clientReceivePacketOverride callbacks + config <- peek configPtr + poke configPtr $ config + { c'netcode_client_config_t'send_packet_override = nullFunPtr + , c'netcode_client_config_t'receive_packet_override = nullFunPtr + , c'netcode_client_config_t'override_send_and_receive = 0 + } + let newcbs = callbacks + { clientSendPacketOverride = nullFunPtr + , clientReceivePacketOverride = nullFunPtr + } + return (configPtr, newcbs) + +-- | Creates a client at the given address using the provided config. Throws an +-- IOException on failure. +-- +-- Note, the address used here can be either formatted as an IPv4 address or an +-- IPv6 address, similar to the arguments passed to 'parseAddress'. In the +-- common case, you will likely want to use INADDR_ANY to bind to the +-- underlying socket, which is represented by the address "0.0.0.0" +-- +-- The time passed to this create function should be a measurement in seconds, +-- such that when connecting in the future using 'updateClient', the same +-- resolution timer is being passed. That allows the library to properly +-- timeout in cases where connections are taking too long to establish. +createClient :: String -> ClientConfig -> Double -> IO Client +createClient s (ClientConfig mkConfig) time = alloca $ \clientConfig -> do + (config, callbacks) <- mkConfig clientConfig defaultClientCallbacks + ptr <- withCString s $ \cs -> + c'netcode_client_create cs config (CDouble time) + when (ptr == nullPtr) $ fail "Failed to create client!" + return (Client ptr callbacks) + +-- | Destroys the client and frees all of the Haskell-side function pointers +-- that were registered as callbacks. +destroyClient :: Client -> IO () +destroyClient (Client c cbs) = do + c'netcode_client_destroy c + freeNullFunPtr $ clientStateChange cbs + freeNullFunPtr $ clientSendPacketOverride cbs + freeNullFunPtr $ clientReceivePacketOverride cbs + +-- | Generates a random 64-bit client ID to be used with 'generateConnectToken' +generateClientID :: IO Word64 +generateClientID = alloca $ \idPtr -> do + c'netcode_random_bytes (castPtr idPtr) $ + fromIntegral $ sizeOf (undefined :: Word64) + peek idPtr + +-- | Begin the process to connect the client to a server stored in the given +-- 'ConnectToken'. This does not connect the client immediately, but rather +-- resets the client object and sets the state to +-- 'ClientState'SendingConnectionRequest'. The client will attempt to connect +-- on the next call to 'updateClient'. +connectClient :: Client -> ConnectToken -> IO () +connectClient (Client c _) (ConnectToken ctPtr) = + withForeignPtr ctPtr (c'netcode_client_connect c) + +-- | Disconnects the client from anything it might be connected to. +disconnectClient :: Client -> IO () +disconnectClient (Client c _) = c'netcode_client_disconnect c + +-- | Main processing call for clients with the current time in seconds (in the +-- same domain as the time passed to 'createClient'). This flushes packet +-- queues at the appropriate rate and updates connection statuses among other +-- things. It is expected to be called in the main loop of the application. +updateClient :: Client -> Double -> IO () +updateClient (Client c _) = c'netcode_client_update c . CDouble + +-- | Returns the current state of the 'Client'. +getClientState :: Client -> IO ClientState +getClientState (Client c _) = typedClientState <$> c'netcode_client_state c + +-- | Returns true if the 'Client' is in a state considered to be disconnected, +-- as opposed to connected or connecting. +isClientDisconnected :: Client -> IO Bool +isClientDisconnected (Client c _) = + (<= c'NETCODE_CLIENT_STATE_DISCONNECTED) <$> c'netcode_client_state c + +-- | Returns the sequence number of the next packet that the 'Client' will +-- send. +nextClientPacketSequence :: Client -> IO Word64 +nextClientPacketSequence (Client c _) = c'netcode_client_next_packet_sequence c + +-- | Enqueues a packet to be sent during the next call to 'updateClient'. +sendPacketFromClient :: Client -> Int -> Ptr Word8 -> IO () +sendPacketFromClient (Client c _) pktSz pktMem = do + let pktSize = min c'NETCODE_MAX_PACKET_SIZE (fromIntegral pktSz) + when (pktSz > c'NETCODE_MAX_PACKET_SIZE) $ putStrLn $ + "WARNING: Sending packet that's too large: " <> show pktSz + c'netcode_client_send_packet c pktMem pktSize + +-- | Dequeues a received packet from the t'Netcode.IO.Server'. This function +-- returns a @Just@ until the queue is empty, upon which it will return +-- @Nothing@. +receivePacketFromServer :: Client -> IO (Maybe Packet) +receivePacketFromServer (Client c _) = + alloca $ \sequenceNumPtr -> + alloca $ \pktSzPtr -> do + packetMem <- c'netcode_client_receive_packet c pktSzPtr sequenceNumPtr + let finalizer = c'netcode_client_free_packet c (castPtr packetMem) + if packetMem == nullPtr + then return Nothing + else fmap Just $ + Packet <$> peek sequenceNumPtr + <*> (fromIntegral <$> peek pktSzPtr) + <*> newForeignPtr packetMem finalizer + +-- | Returns the port assigned to this 'Client'. +getClientPort :: Client -> IO Word16 +getClientPort (Client c _) = c'netcode_client_get_port c + +-- | Performs an action with the address of the server to which the given +-- 'Client' is connected to. This is meant to minimize the chances that the +-- 'Address' value will be used in a manner that outlives the given 'Client'. +-- Callers should avoid storing the 'Address' value or returning it as a result +-- of this function. +-- +-- In the event that the client is not connected to a server, the address +-- passed to the action will be @0.0.0.0@. +withClientServerAddress :: Client -> (Address -> IO a) -> IO a +withClientServerAddress (Client c _) fn = do + aptr <- c'netcode_client_server_address c + if aptr == nullPtr + then parseAddress "0.0.0.0" >>= fn + else Address <$> newForeignPtr_ aptr >>= fn + +-- | A 'ConnectToken' represents an encrypted set of data fields that describe +-- both the client requesting to make a connection and the available servers to +-- which that connection can be made. It is generated solely via the +newtype ConnectToken = ConnectToken (ForeignPtr Word8) + +-- | Gives the maximum size, in bytes, of user data stored in a 'ConnectToken'. +maximumUserDataSize :: Num a => a +maximumUserDataSize = c'NETCODE_USER_DATA_BYTES + +-- | Returns the maximum number of servers that can be stored in a +-- 'ConnectToken'. +maximumServersPerConnect :: Num a => a +maximumServersPerConnect = c'NETCODE_MAX_SERVERS_PER_CONNECT + +-- | Returns the number of bytes expected in the private key used to generate a +-- 'ConnectToken' +privateKeySize :: Num a => a +privateKeySize = c'NETCODE_KEY_BYTES + +-- | Creates a connect token for the given client (by clientID) with the list +-- of associated addresses. User data may be at most 'maximumUserDataSize' +-- values, otherwise is truncated or zero-padded to fill. The list of public +-- and internal servers must not be empty and may contain at most +-- 'maximumServersPerConnect' values, otherwise is truncated. Throws an +-- IOException on failure. +generateConnectToken :: [(String, String)] -- ^ Public and internal servers + -> Int -- ^ Token expiration in seconds + -> Int -- ^ Token timeout in seconds + -> Word64 -- ^ Unique Client ID + -> Word64 -- ^ Protocol ID + -> [Word8] -- ^ Private key + -> [Word8] -- ^ User data + -> IO ConnectToken +generateConnectToken [] _ _ _ _ _ _ = fail "Connect token server list is empty." +generateConnectToken addrs expiry timeout clientID protocolID privateKey userData = + allocaArray (length addrs) $ \externalAddrs -> + allocaArray (length addrs) $ \internalAddrs -> + let checkServers = + when (length addrs > maximumServersPerConnect) $ putStrLn $ + "Warning: Too many servers passed to connect token: " <> show (length addrs) + + writeAddrsAndGo i ((s1, s2) : rest) = withCString s1 $ \cs1 -> do + pokeElemOff externalAddrs i cs1 + withCString s2 $ \cs2 -> do + pokeElemOff internalAddrs i cs2 + writeAddrsAndGo (i + 1) rest + writeAddrsAndGo _ [] = -- go.... + allocaArray c'NETCODE_USER_DATA_BYTES $ \userDataBytes -> + allocaArray c'NETCODE_KEY_BYTES $ \privateKeyBytes -> do + connectTokenPtr <- mallocForeignPtrBytes c'NETCODE_CONNECT_TOKEN_BYTES + pokeArray privateKeyBytes (take c'NETCODE_KEY_BYTES $ privateKey <> repeat 0) + pokeArray userDataBytes (take c'NETCODE_USER_DATA_BYTES $ userData <> repeat 0) + result <- withForeignPtr connectTokenPtr $ \connectTokenBytes -> + c'netcode_generate_connect_token (fromIntegral $ length addrs) + externalAddrs + internalAddrs + (fromIntegral expiry) + (fromIntegral timeout) + clientID + protocolID + privateKeyBytes + userDataBytes + connectTokenBytes + when (result == c'NETCODE_ERROR) $ fail "Error generating connect token" + return $ ConnectToken connectTokenPtr + in checkServers >> writeAddrsAndGo 0 addrs +
+ Netcode/IO/Packet.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DeriveGeneric #-} +module Netcode.IO.Packet where + +-------------------------------------------------------------------------------- + +import Data.Word (Word8, Word64) +import Foreign.ForeignPtr (ForeignPtr) +import GHC.Generics (Generic) + +import Bindings.Netcode.IO + +-------------------------------------------------------------------------------- + +-- | A packet is a basic unit of data that is transferred between client and +-- server. Sequence numbers indicate the order in which the packets were sent, +-- and this library contains no guarantee that they will be received in a +-- monotonically increasing order. +data Packet = Packet { + -- | The sequence number for this packet. + packetSequenceNumber :: Word64, + -- | The size, in bytes, of the data stored at 'packetDataPtr' + packetSize :: Int, + -- | A pointer to the bytes that are contained in this packet. This can be + -- cast to any pointer type for the purposes of deseralizing, but this + -- pointer must outlive the amount of time that this library has been + -- initialized. + packetDataPtr :: ForeignPtr Word8 +} deriving (Show, Generic) + +-- | The maximum size, in bytes, of a packet. In other words, this is the +-- maximum value that 'packetSize' can take. +maximumPacketSize :: Num a => a +maximumPacketSize = c'NETCODE_MAX_PACKET_SIZE
+ Netcode/IO/Server.hs view
@@ -0,0 +1,403 @@+module Netcode.IO.Server ( + -- * Servers + + -- ** Server-specific callbacks + ServerConnectDisconnectCallback + + -- ** Server configs + , ServerConfig + , defaultServerConfig + , setProtocolID + , setPrivateKey + , setServerConnectDisconnectCallback, clearServerConnectDisconnectCallback + , setServerSendReceiveOverrides, clearServerSendReceiveOverrides + + -- ** Server objects + , Server + , createServer + , destroyServer + , startServer + , maxNumClients + , stopServer + , updateServer + , clientConnectedAtIndex + , clientIdAtIndex + , withClientAddressAtIndex + , withClientUserDataAtIndex + , clientUserDataAtIndex + , maxClientsForServer + , numConnectedClients + , isServerRunning + , isServerFull + , getServerPort + , sendPacketFromServer + , broadcastPacketFromServer + , disconnectClientFromServer + , disconnectAllClientsFromServer + , receivePacketFromClient + , nextServerPacketSequence +) where + +-------------------------------------------------------------------------------- + +import Control.Applicative (liftA2) +import Control.Monad (when, forM_) +import Data.Word (Word8, Word16, Word64) +import Foreign.C.String (withCString) +import Foreign.C.Types (CDouble(..)) +import Foreign.Concurrent (newForeignPtr) +import Foreign.ForeignPtr (newForeignPtr_) +import Foreign.Marshal.Alloc (alloca) +import Foreign.Marshal.Array (peekArray) +import Foreign.Ptr ( Ptr, nullPtr, castPtr + , FunPtr, nullFunPtr, freeHaskellFunPtr + ) +import Foreign.Storable (peek, poke) + +import Bindings.Netcode.IO +import Netcode.IO.Address +import Netcode.IO.Callbacks +import Netcode.IO.Packet + +-------------------------------------------------------------------------------- + +freeNullFunPtr :: FunPtr a -> IO () +freeNullFunPtr x + | x == nullFunPtr = return () + | otherwise = freeHaskellFunPtr x + +-------------------------------------------------------------------------------- + +-- | A server object. This is an opaque type meant to be used in conjunction +-- with this library. +-- +-- A server is generally meant to represent and endpoint for one or more +-- t'Network.IO.Client's to connect to. The server application is similar to +-- that of the client in that it is expected to have a running timer with a +-- resolution of at least seconds. The main loop of the server application is +-- meant to call 'updateServer' to allow the library to process incoming +-- packets and send outgoing packets to the clients. +data Server = Server + { serverPtr :: Ptr C'netcode_server_t + , serverCallbacks :: ServerCallbacks + } deriving (Show) + +data ServerCallbacks = ServerCallbacks + { serverConnectDisconnect :: C'connect_disconnect_callback_t + , serverSendPacketOverride :: C'send_packet_override_t + , serverReceivePacketOverride :: C'receive_packet_override_t + } deriving (Show) + +defaultServerCallbacks :: ServerCallbacks +defaultServerCallbacks = ServerCallbacks nullFunPtr nullFunPtr nullFunPtr + +-- | A 'ServerConfig' is a type that specifies the behavior of a 'Server' and +-- contains associated metadata. +-- +-- At a minimum, the connection protocol ID and the private key for the +-- application should be set for each server (via 'setProtocolID' and +-- 'setPrivateKey', respectively). +newtype ServerConfig = ServerConfig + ( Ptr C'netcode_server_config_t + -> ServerCallbacks + -> IO (Ptr C'netcode_server_config_t, ServerCallbacks) + ) + +-- | The default 'ServerConfig' contains no callbacks or overrides, and +-- contains empty values for the required fields needed to properly have +-- a server respond to a connecting t'Netcode.IO.Client'. +defaultServerConfig :: ServerConfig +defaultServerConfig = ServerConfig $ \serverConfig cbs -> do + c'netcode_default_server_config serverConfig + return (serverConfig, cbs) + +-- | Sets the connection protocol ID used by this 'Server'. This is a unique +-- ID that must match the protocol ID used in +-- 'Netcode.IO.generateConnectToken' +setProtocolID :: Word64 -> ServerConfig -> ServerConfig +setProtocolID protocolID (ServerConfig mkServerPtr) = + ServerConfig $ \serverConfig cbs' -> do + (configPtr, cbs) <- mkServerPtr serverConfig cbs' + config <- peek configPtr + poke configPtr $ + config { c'netcode_server_config_t'protocol_id = protocolID } + return (configPtr, cbs) + +-- | Sets the private key used by this 'Server'. This key must match the +-- private key used in 'Netcode.IO.generateConnectToken' +setPrivateKey :: [Word8] -> ServerConfig -> ServerConfig +setPrivateKey key (ServerConfig mkServerPtr) = + ServerConfig $ \serverConfig cbs' -> do + (configPtr, cbs) <- mkServerPtr serverConfig cbs' + config <- peek configPtr + poke configPtr $ + config { + c'netcode_server_config_t'private_key = + take c'NETCODE_KEY_BYTES (key <> repeat 0) + } + return (configPtr, cbs) + +-- | A server-specific callback that gets invoked each time a client either +-- connects to, or disconnects from, the server. +type ServerConnectDisconnectCallback + = Int -- ^ Client index connected + -> Bool -- ^ True if the client connected + -> IO () + +mkServerConnectDisconnectCallback :: ServerConnectDisconnectCallback + -> IO C'connect_disconnect_callback_t +mkServerConnectDisconnectCallback cb = mk'connect_disconnect_callback_t $ + \_ clientIdx connected -> cb (fromIntegral clientIdx) (connected /= 0) + +-- | Replaces the existing 'ServerConnectDisconnectCallback' with the given one +-- and frees any associated memory that may be allocated for the the existing +-- callback. +setServerConnectDisconnectCallback :: ServerConnectDisconnectCallback + -> ServerConfig -> ServerConfig +setServerConnectDisconnectCallback cb (ServerConfig mkConfig) = + ServerConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ serverConnectDisconnect callbacks + fPtr <- mkServerConnectDisconnectCallback cb + config <- peek configPtr + poke configPtr $ config + { c'netcode_server_config_t'connect_disconnect_callback = fPtr + } + return (configPtr, callbacks { serverConnectDisconnect = fPtr }) + +-- | Removes the existing 'ServerConnectDisconnectCallback' and frees any +-- associated memory that may be allocated for the the existing callback. +clearServerConnectDisconnectCallback :: ServerConfig -> ServerConfig +clearServerConnectDisconnectCallback (ServerConfig mkConfig) = + ServerConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ serverConnectDisconnect callbacks + config <- peek configPtr + poke configPtr $ config + { c'netcode_server_config_t'connect_disconnect_callback = nullFunPtr + } + return (configPtr, callbacks { serverConnectDisconnect = nullFunPtr }) + +-- | Removes the existing send and receive overrides for the given config, if +-- set, and instead uses the ones given. +setServerSendReceiveOverrides :: SendPacketOverride + -> ReceivePacketOverride + -> ServerConfig -> ServerConfig +setServerSendReceiveOverrides sendFn recvFn (ServerConfig mkConfig) = + ServerConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ serverSendPacketOverride callbacks + freeNullFunPtr $ serverReceivePacketOverride callbacks + config <- peek configPtr + sendOverride <- mkSendPacketOverride sendFn + recvOverride <- mkReceivePacketOverride recvFn + poke configPtr $ config + { c'netcode_server_config_t'send_packet_override = sendOverride + , c'netcode_server_config_t'receive_packet_override = recvOverride + , c'netcode_server_config_t'override_send_and_receive = 1 + } + let newcbs = callbacks + { serverSendPacketOverride = sendOverride + , serverReceivePacketOverride = recvOverride + } + return (configPtr, newcbs) + +-- | Changes the config to use the default send and receive packet functions. +clearServerSendReceiveOverrides :: ServerConfig -> ServerConfig +clearServerSendReceiveOverrides (ServerConfig mkConfig) = + ServerConfig $ \configPtr' callbacks' -> do + (configPtr, callbacks) <- mkConfig configPtr' callbacks' + freeNullFunPtr $ serverSendPacketOverride callbacks + freeNullFunPtr $ serverReceivePacketOverride callbacks + config <- peek configPtr + poke configPtr $ config + { c'netcode_server_config_t'send_packet_override = nullFunPtr + , c'netcode_server_config_t'receive_packet_override = nullFunPtr + , c'netcode_server_config_t'override_send_and_receive = 0 + } + let newcbs = callbacks + { serverSendPacketOverride = nullFunPtr + , serverReceivePacketOverride = nullFunPtr + } + return (configPtr, newcbs) + +-- | Creates a server at the given address using the provided config. Throws an +-- IOException on failure. +-- +-- Note, the address used here can be either formatted as an IPv4 address or an +-- IPv6 address, similar to the arguments passed to 'parseAddress'. In the +-- common case, you will likely want to use INADDR_ANY to bind to the +-- underlying socket, which is represented by the address "0.0.0.0" +-- +-- The time passed to this create function should be a measurement in seconds, +-- such that when connecting in the future using 'updateServer', the same +-- resolution timer is being passed. That allows the library to properly +-- timeout in cases where connections are taking too long to establish. +createServer :: String -> ServerConfig -> Double -> IO Server +createServer s (ServerConfig mkConfig) time = alloca $ \serverConfig -> do + (config, cbs) <- mkConfig serverConfig defaultServerCallbacks + ptr <- withCString s (\cs -> c'netcode_server_create cs config (CDouble time)) + when (ptr == nullPtr) $ fail "Failed to create server!" + return (Server ptr cbs) + +-- | Starts the server and specifies the maximum number of clients that can +-- connect. Emits a warning when the maximum number of clients is more than +-- 'maxNumClients'. +startServer :: Server -> Int -> IO () +startServer (Server s _) n = do + when (n > maxNumClients) $ putStrLn $ concat [ + "Warning: Can only start server with at most " + , show (maxNumClients :: Int), " clients. Requested: ", show n + ] + c'netcode_server_start s (min maxNumClients $ fromIntegral n) + +-- | Returns the maximum number of clients that a server can support. +maxNumClients :: Num a => a +maxNumClients = c'NETCODE_MAX_CLIENTS + +-- | Stops the server. +stopServer :: Server -> IO () +stopServer (Server s _) = c'netcode_server_stop s + +-- | Destroys the server object and frees the associated Haskell-side callbacks +-- and overrides +destroyServer :: Server -> IO () +destroyServer (Server s cbs) = do + c'netcode_server_destroy s + freeNullFunPtr $ serverConnectDisconnect cbs + freeNullFunPtr $ serverSendPacketOverride cbs + freeNullFunPtr $ serverReceivePacketOverride cbs + +-- | Main processing call for a 'Server' with the current time in seconds (in +-- the same domain as the time passed to 'createServer'). This flushes packet +-- queues at the appropriate rate and updates connection statuses among other +-- things. It is expected to be called in the main loop of the application. +updateServer :: Server -> Double -> IO () +updateServer (Server s _) = c'netcode_server_update s . CDouble + +-- | Returns @True@ if the client at the given client index is connected to +-- the server. Returns @False@ if not connected, if the server is not running, +-- or if the client index is out of bounds. +clientConnectedAtIndex :: Server -> Int -> IO Bool +clientConnectedAtIndex (Server s _) = + fmap (/= 0) . c'netcode_server_client_connected s . fromIntegral + +-- | Returns the client ID of the client at the given client index. Returns @0@ +-- if not connected, the server is not running, or if the client index is out +-- of bounds. +clientIdAtIndex :: Server -> Int -> IO Word64 +clientIdAtIndex (Server s _) = c'netcode_server_client_id s . fromIntegral + +-- | Performs an action with the address of the client to which the given +-- 'Server' is connected to. This is meant to minimize the chances that the +-- 'Address' value will be used in a manner that outlives the given 'Server' or +-- the connection lifetime of the client. Callers should avoid storing the +-- 'Address' value or returning it as a result of this function. +-- +-- In the event that the client index is out of bounds, or the client is not +-- connected at that slot, the address passed to the action will be @0.0.0.0@. +withClientAddressAtIndex :: Server -> Int -> (Address -> IO a) -> IO a +withClientAddressAtIndex (Server s _) cidx fn = do + aptr <- c'netcode_server_client_address s (fromIntegral cidx) + if aptr == nullPtr + then parseAddress "0.0.0.0" >>= fn + else Address <$> newForeignPtr_ aptr >>= fn + +-- | Performs an action with the user data of the client to which the given +-- 'Server' is connected to. This is meant to minimize the chances that the +-- memory buffer will be used in a manner that outlives the given 'Server' or +-- the connection lifetime of the client. Callers should avoid storing the +-- @Ptr@ value or returning it as a result of this function. +-- +-- In the event that the client index is out of bounds, or the client is not +-- connected at that slot, the given action will receive @nullPtr@. +withClientUserDataAtIndex :: Server -> Int -> (Ptr () -> IO a) -> IO a +withClientUserDataAtIndex (Server s _) cidx fn = + c'netcode_server_client_user_data s (fromIntegral cidx) >>= fn + +-- | Returns the user data for the client connected at the given client index. +-- +-- In the event that the client index is out of bounds, or the client is not +-- connected at that slot, the result will be the empty list. +clientUserDataAtIndex :: Server -> Int -> IO [Word8] +clientUserDataAtIndex s i = withClientUserDataAtIndex s i $ \ptr -> + if ptr == nullPtr + then return [] + else peekArray c'NETCODE_USER_DATA_BYTES (castPtr ptr) + +-- | Returns the maximum number of clients that can connect to this server, or +-- zero if the server has not been started yet (via a call to 'startServer'). +maxClientsForServer :: Server -> IO Int +maxClientsForServer (Server s _) = + fromIntegral <$> c'netcode_server_max_clients s + +-- | Returns the number of currently connected clients. +numConnectedClients :: Server -> IO Int +numConnectedClients (Server s _) = + fromIntegral <$> c'netcode_server_num_connected_clients s + +-- | Returns True if the server has been started, and is ready to accept +-- incoming connections from clients. +isServerRunning :: Server -> IO Bool +isServerRunning (Server s _) = (/= 0) <$> c'netcode_server_running s + +-- | Returns true if the number of connected clients matches the maximum number +-- of possibly connected clients. +isServerFull :: Server -> IO Bool +isServerFull (Server s _) = + liftA2 (==) (c'netcode_server_num_connected_clients s) + (c'netcode_server_max_clients s) + +-- | Returns the port assigned to the server's IP address. +getServerPort :: Server -> IO Word16 +getServerPort (Server s _) = c'netcode_server_get_port s + +-- | Disconnects the client at the given index from the server. +disconnectClientFromServer :: Server -> Int -> IO () +disconnectClientFromServer (Server s _) = + c'netcode_server_disconnect_client s . fromIntegral + +-- | Disconnects all clients from the server. +disconnectAllClientsFromServer :: Server -> IO () +disconnectAllClientsFromServer (Server s _) = + c'netcode_server_disconnect_all_clients s + +-- | Returns the next sequence number of a packet destined for the client at +-- the given client index. +nextServerPacketSequence :: Server -> Int -> IO Word64 +nextServerPacketSequence (Server s _) = + c'netcode_server_next_packet_sequence s . fromIntegral + +-- | Enqueues a packet to be sent to the client at the given index during the +-- next call to 'updateServer'. +sendPacketFromServer :: Server + -> Int -- ^ Client index + -> Int -- ^ Size in bytes of packet data + -> Ptr Word8 -- ^ Packet data buffer + -> IO () +sendPacketFromServer (Server s _) clientIdx pktSz pktMem = do + let pktSize = min c'NETCODE_MAX_PACKET_SIZE (fromIntegral pktSz) + when (pktSz > c'NETCODE_MAX_PACKET_SIZE) $ putStrLn $ + "WARNING: Sending packet that's too large: " <> show pktSz + c'netcode_server_send_packet s (fromIntegral clientIdx) pktMem pktSize + +-- | Enqueues a packet with the given size and data to all connected clients. +broadcastPacketFromServer :: Server -> Int -> Ptr Word8 -> IO () +broadcastPacketFromServer s pktSz pktMem = do + numClients <- numConnectedClients s + forM_ [0..(numClients - 1)] $ \i -> sendPacketFromServer s i pktSz pktMem + +-- | Dequeues a received packet from the t'Netcode.IO.Client' at the given +-- client index. This function returns a @Just@ until the queue is empty, upon +-- which it will return a @Nothing@. +receivePacketFromClient :: Server -> Int -> IO (Maybe Packet) +receivePacketFromClient (Server s _) clientIdx = + alloca $ \sequenceNumPtr -> + alloca $ \pktSzPtr -> do + packetMem <- c'netcode_server_receive_packet s (fromIntegral clientIdx) pktSzPtr sequenceNumPtr + if packetMem == nullPtr + then return Nothing + else fmap Just $ + Packet <$> peek sequenceNumPtr + <*> (fromIntegral <$> peek pktSzPtr) + <*> newForeignPtr packetMem (c'netcode_server_free_packet s (castPtr packetMem))
+ README.md view
@@ -0,0 +1,53 @@+# netcode-io +Haskell bindings to the +[netcode.io](https://github.com/networkprotocol/netcode.io) library + +## Development + +These bindings were developed using `stack` in the "usual" way. We expect +that anyone building from source to use the same workflow. + +### Windows + +The cabal file specifies that the `sodium` library is required for building +this package. Unfortunately, this is a bit tricky on Windows since there's no +native package manager. To use this with Windows, we recommend using +[`MSYS2`](https://www.msys2.org/). `msys2.exe` has a built-in shell for using +the `gcc` toolchain on Windows. This toolchain is compatible with the one `ghc` +uses. Within this environment, you can install `sodium` using the `pacman` +package manager: + +``` +$ pacman -S mingw64/mingw-w64-x86_64-libsodium +``` + +Once installed, you can add the following lines to your global `config.yaml`: + +``` +extra-include-dirs: + - D:\msys64\mingw64\include + +extra-lib-dirs: + - D:\msys64\mingw64\lib +``` + +Your `config.yaml` is located at your `%STACK_ROOT%` directory: + +``` +C:\> stack path --stack-root +C:\path\to\stack\root +``` + +### OS X + +On MacOS, we can install `sodium` simply by using homebrew: + +``` +$ brew install libsodium +``` + +## LICENSING + +IANAL, but these bindings use +[the same license](https://github.com/networkprotocol/netcode.io/blob/master/LICENCE) +that the original library uses, so you must comply with both.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple +main = defaultMain
+ examples/Client.hs view
@@ -0,0 +1,114 @@+-- This example serves as a counterpart to the example program of the same name +-- in the C library. +module Main (main) where + +import qualified Netcode.IO + +import Control.Exception (try, throw, AsyncException(..)) +import Control.Monad (forM_, when) +import Data.Word (Word64, Word8) +import Foreign.ForeignPtr (withForeignPtr) +import Foreign.Marshal.Array (peekArray, withArrayLen) +import System.Environment (getArgs) +import System.Exit (ExitCode(..), exitSuccess) + +gProtocolID :: Word64 +gProtocolID = 0x1122334455667788 + +gPrivateKey :: [Word8] +gPrivateKey = + [ 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea + , 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4 + , 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26 + , 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1 + ] + +gConnectTokenExpiry :: Int +gConnectTokenExpiry = 30 + +gConnectTokenTimeout :: Int +gConnectTokenTimeout = 5 + +main :: IO () +main = do + Netcode.IO.initialize + + Netcode.IO.logLevel Netcode.IO.LogLevel'Info + + putStrLn "[client]" + let stateChangeCallback old new = + putStrLn $ "Old state: " <> show old <> " new state: " <> show new + clientConfig = Netcode.IO.setClientStateChangeCallback stateChangeCallback + $ Netcode.IO.defaultClientConfig + client <- Netcode.IO.createClient "0.0.0.0" clientConfig 0.0 + + args <- getArgs + let serverAddr = case length args of + 1 -> head args + _ -> "127.0.0.1:40000" + + -- This is expected to be performed on an HTTP server + clientID <- Netcode.IO.generateClientID + putStrLn $ "client id is " <> show clientID + + connectToken <- Netcode.IO.generateConnectToken [(serverAddr, serverAddr)] + gConnectTokenExpiry + gConnectTokenTimeout + clientID + gProtocolID + gPrivateKey + [] + + Netcode.IO.connectClient client connectToken + + let untilM :: IO Bool -> IO () + untilM cond = do + tf <- cond + if tf then return () else untilM cond + + catchUserInterrupt :: IO a -> IO a + catchUserInterrupt prg = do + interruptResult <- try prg + case interruptResult of + (Left UserInterrupt) -> putStrLn "\nshutting down" >> exitSuccess + (Left e) -> throw e + (Right x) -> return x + + quitResult <- try $ catchUserInterrupt $ forM_ [0.0, 0.016667 ..] $ \time -> do + Netcode.IO.updateClient client time + + clientState <- Netcode.IO.getClientState client + when (clientState == Netcode.IO.ClientState'Connected) $ + withArrayLen [0..(Netcode.IO.maximumPacketSize - 1)] $ + Netcode.IO.sendPacketFromClient client + + untilM $ do + mpkt <- Netcode.IO.receivePacketFromServer client + case mpkt of + Nothing -> return True + Just pkt -> do + withForeignPtr (Netcode.IO.packetDataPtr pkt) $ \pktMem -> do + pktData <- peekArray (Netcode.IO.packetSize pkt) pktMem + case (and $ zipWith (==) pktData [0,1..]) of + True -> return False + False -> fail "Received garbled packet!" + + clientState' <- Netcode.IO.getClientState client + when (any (== clientState') [ Netcode.IO.ClientState'ConnectTokenExpired + , Netcode.IO.ClientState'InvalidConnectToken + , Netcode.IO.ClientState'ConnectionTimedOut + , Netcode.IO.ClientState'ConnectionResponseTimedOut + , Netcode.IO.ClientState'ConnectionRequestTimedOut + , Netcode.IO.ClientState'ConnectionDenied + , Netcode.IO.ClientState'Disconnected + ]) exitSuccess + + Netcode.IO.sleep 0.016667 + + case quitResult of + (Left ExitSuccess) -> return () + (Right _) -> fail "forM_ with infinite list terminated?" + (Left e) -> fail $ show e + + Netcode.IO.destroyClient client + Netcode.IO.terminate
+ examples/ClientServer.hs view
@@ -0,0 +1,142 @@+-- This example serves as a counterpart to the example program of the same name +-- in the C library. +module Main (main) where + +import qualified Netcode.IO + +import Control.Exception (try, throw, AsyncException(..)) +import Control.Monad (forM_, when) +import Data.IORef (newIORef, readIORef, modifyIORef) +import Data.Word (Word64, Word8) +import Foreign.ForeignPtr (withForeignPtr) +import Foreign.Marshal.Array (peekArray, withArrayLen) +import System.Exit (ExitCode(..), exitSuccess) + +gProtocolID :: Word64 +gProtocolID = 0x1122334455667788 + +gPrivateKey :: [Word8] +gPrivateKey = + [ 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea + , 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4 + , 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26 + , 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1 + ] + +gConnectTokenExpiry :: Int +gConnectTokenExpiry = 30 + +gConnectTokenTimeout :: Int +gConnectTokenTimeout = 5 + +main :: IO () +main = do + Netcode.IO.initialize + + Netcode.IO.logLevel Netcode.IO.LogLevel'Info + + putStrLn "[client/server]" + client <- Netcode.IO.createClient "::" Netcode.IO.defaultClientConfig 0.0 + + let serverAddrNoPort = "[::1]:00000" + serverConfig = + Netcode.IO.setPrivateKey gPrivateKey $ + Netcode.IO.setProtocolID gProtocolID $ + Netcode.IO.defaultServerConfig + server <- Netcode.IO.createServer serverAddrNoPort serverConfig 0.0 + + serverAddr <- ("[::1]:" <>) . show <$> Netcode.IO.getServerPort server + Netcode.IO.startServer server 1 + + -- This is expected to be performed on an HTTP server + clientID <- Netcode.IO.generateClientID + putStrLn $ "client id is " <> show clientID + + connectToken <- Netcode.IO.generateConnectToken [(serverAddr, serverAddr)] + gConnectTokenExpiry + gConnectTokenTimeout + clientID + gProtocolID + gPrivateKey + [] + + Netcode.IO.connectClient client connectToken + + let untilM :: IO Bool -> IO () + untilM cond = do + tf <- cond + if tf then return () else untilM cond + + catchUserInterrupt :: IO a -> IO a + catchUserInterrupt prg = do + interruptResult <- try prg + case interruptResult of + (Left UserInterrupt) -> putStrLn "\nshutting down" >> exitSuccess + (Left e) -> throw e + (Right x) -> return x + + numServerPacketsReceived <- newIORef (0 :: Int) + numClientPacketsReceived <- newIORef (0 :: Int) + + quitResult <- try $ catchUserInterrupt $ forM_ [0.0, 0.016667 ..] $ \time -> do + Netcode.IO.updateClient client time + Netcode.IO.updateServer server time + + clientState <- Netcode.IO.getClientState client + when (clientState == Netcode.IO.ClientState'Connected) $ + withArrayLen [0..(Netcode.IO.maximumPacketSize - 1)] $ + Netcode.IO.sendPacketFromClient client + + clientConnected <- Netcode.IO.clientConnectedAtIndex server 0 + when clientConnected $ + withArrayLen [0..(Netcode.IO.maximumPacketSize - 1)] $ + Netcode.IO.sendPacketFromServer server 0 + + untilM $ do + mpkt <- Netcode.IO.receivePacketFromServer client + case mpkt of + Nothing -> return True + Just pkt -> do + modifyIORef numClientPacketsReceived (+ 1) + withForeignPtr (Netcode.IO.packetDataPtr pkt) $ \pktMem -> do + pktData <- peekArray (Netcode.IO.packetSize pkt) pktMem + case (and $ zipWith (==) pktData [0,1..]) of + True -> return False + False -> fail "Received garbled packet!" + + untilM $ do + mpkt <- Netcode.IO.receivePacketFromClient server 0 + case mpkt of + Nothing -> return True + Just pkt -> do + modifyIORef numServerPacketsReceived (+ 1) + withForeignPtr (Netcode.IO.packetDataPtr pkt) $ \pktMem -> do + pktData <- peekArray (Netcode.IO.packetSize pkt) pktMem + case (and $ zipWith (==) pktData [0,1..]) of + True -> return False + False -> fail "Received garbled packet!" + + numClientPackets <- readIORef numClientPacketsReceived + numServerPackets <- readIORef numServerPacketsReceived + if (numClientPackets >= 10 && numServerPackets >= 10) + then do + conn <- Netcode.IO.clientConnectedAtIndex server 0 + when conn $ do + putStrLn "client and server successfully exchanged packets" + Netcode.IO.disconnectClientFromServer server 0 + exitSuccess + else return () + + disconnected <- Netcode.IO.isClientDisconnected client + when disconnected exitSuccess + + Netcode.IO.sleep 0.016667 + + case quitResult of + (Left ExitSuccess) -> return () + (Right _) -> fail "forM_ with infinite list terminated?" + (Left e) -> fail $ show e + + Netcode.IO.destroyServer server + Netcode.IO.destroyClient client + Netcode.IO.terminate
+ examples/RunCUnitTests.hs view
@@ -0,0 +1,15 @@+module Main (main) where + +import Bindings.Netcode.IO ( c'NETCODE_OK + , c'netcode_init, c'netcode_test, c'netcode_term + ) + +main :: IO () +main = do + initResult <- c'netcode_init + if initResult == c'NETCODE_OK + then return () + else fail "Failed to initialize netcode.io" + c'netcode_test + c'netcode_term + putStrLn "Success!"
+ examples/Server.hs view
@@ -0,0 +1,82 @@+-- This example serves as a counterpart to the example program of the same name +-- in the C library. +module Main (main) where + +import qualified Netcode.IO + +import Control.Exception (try, throw, AsyncException(..)) +import Control.Monad (forM_, when) +import Data.Word (Word64, Word8) +import Foreign.ForeignPtr (withForeignPtr) +import Foreign.Marshal.Array (peekArray, withArrayLen) +import System.Environment (getArgs) +import System.Exit (ExitCode(..), exitSuccess) + +gProtocolID :: Word64 +gProtocolID = 0x1122334455667788 + +gPrivateKey :: [Word8] +gPrivateKey = + [ 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea + , 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4 + , 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26 + , 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1 + ] + +main :: IO () +main = do + Netcode.IO.initialize + + Netcode.IO.logLevel Netcode.IO.LogLevel'Info + + putStrLn "[server]" + args <- getArgs + let serverAddr = if length args == 1 then head args else "127.0.0.1:40000" + serverConfig = + Netcode.IO.setPrivateKey gPrivateKey $ + Netcode.IO.setProtocolID gProtocolID $ + Netcode.IO.defaultServerConfig + server <- Netcode.IO.createServer serverAddr serverConfig 0.0 + + Netcode.IO.startServer server Netcode.IO.maxNumClients + + let untilM :: IO Bool -> IO () + untilM cond = do + tf <- cond + if tf then return () else untilM cond + + catchUserInterrupt :: IO a -> IO a + catchUserInterrupt prg = do + interruptResult <- try prg + case interruptResult of + (Left UserInterrupt) -> putStrLn "\nshutting down" >> exitSuccess + (Left e) -> throw e + (Right x) -> return x + + quitResult <- try $ catchUserInterrupt $ forM_ [0.0, 0.016667 ..] $ \time -> do + Netcode.IO.updateServer server time + + clientConnected <- Netcode.IO.clientConnectedAtIndex server 0 + when clientConnected $ + withArrayLen [0..(Netcode.IO.maximumPacketSize - 1)] $ + Netcode.IO.sendPacketFromServer server 0 + + forM_ [0..(Netcode.IO.maxNumClients - 1)] $ \client -> untilM $ do + mpkt <- Netcode.IO.receivePacketFromClient server client + case mpkt of + Nothing -> return True + Just pkt -> withForeignPtr (Netcode.IO.packetDataPtr pkt) $ \pktMem -> do + pktData <- peekArray (Netcode.IO.packetSize pkt) pktMem + case (and $ zipWith (==) pktData [0,1..]) of + True -> return False + False -> fail "Received garbled packet!" + + Netcode.IO.sleep 0.016667 + + case quitResult of + (Left ExitSuccess) -> return () + (Right _) -> fail "forM_ with infinite list terminated?" + (Left e) -> fail $ show e + + Netcode.IO.destroyServer server + Netcode.IO.terminate
+ examples/Soak.hs view
@@ -0,0 +1,293 @@+{-# LANGUAGE TupleSections #-} +module Main (main) where + +-------------------------------------------------------------------------------- + +import Control.Exception (try, AsyncException(..)) +import Control.Monad (forM_, foldM, filterM, when) +import Data.Bifunctor (second) +import Data.Bool (bool) +import Data.Function (on) +import Data.List (delete, deleteBy) +import Data.Maybe (fromJust) +import Data.IORef (IORef, newIORef, readIORef, writeIORef) +import Data.Word (Word8, Word64) +import Foreign.ForeignPtr (withForeignPtr) +import Foreign.Marshal.Array (peekArray, withArrayLen) +import System.Environment (getArgs) +import System.IO (hSetBuffering, stdin, BufferMode(..)) + +import qualified Netcode.IO as Netcode + +-------------------------------------------------------------------------------- + +data Activated a = Activated + { inactive :: [Int] + , active :: [(Int, a)] + } deriving (Eq, Show, Ord) + +instance Functor Activated where + fmap f (Activated i xs) = Activated i (fmap (second f) xs) + +instance Foldable Activated where + foldMap f (Activated _ xs) = foldMap (f . snd) xs + +instance Traversable Activated where + traverse f (Activated i' xs) = + Activated i' <$> traverse (\(i, x) -> (i,) <$> f x) xs + +mkActivated :: Int -> Activated a +mkActivated n = Activated (take n [0..]) [] + +activate :: Int -> a -> Activated a -> Maybe (Activated a) +activate _ _ (Activated [] _) = Nothing +activate i x (Activated is xs) + | i `notElem` is = Nothing + | otherwise = Just (Activated (delete i is) ((i, x):xs)) + +deactivate :: Int -> Activated a -> Maybe (Activated a) +deactivate i a@(Activated is xs) + | isActive i a = + Just $ Activated (i:is) $ deleteBy ((==) `on` fst) (i, undefined) xs + | otherwise = + Nothing + +isActive :: Int -> Activated a -> Bool +isActive i (Activated _ xs) = i `elem` (map fst xs) + +-------------------------------------------------------------------------------- + +newtype RandomInts = RandomInts (IORef [Int]) + +mkRandomInts :: IO RandomInts +mkRandomInts = do + let randInts = 4 : map ((`mod` 134456) . (+ 28411) . (* 8121)) randInts + RandomInts <$> newIORef randInts + +getNextInt :: RandomInts -> IO Int +getNextInt (RandomInts ints) = do + (i:is) <- readIORef ints + writeIORef ints is + return i + +filterRand :: RandomInts -> Int -> [a] -> IO [a] +filterRand ints modVal = (map snd <$>) + . filterM (fmap ((== 0) . (`mod` modVal)) . fst) + . zip (repeat $ getNextInt ints) + +whenRandMod :: RandomInts -> Int -> IO () -> IO () +whenRandMod ints modVal = whenM ((== 0) . (`mod` modVal) <$> getNextInt ints) + +-------------------------------------------------------------------------------- + +gPrivateKey :: [Word8] +gPrivateKey = + [ 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea + , 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4 + , 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26 + , 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1 + ] + +gProtocolID :: Word64 +gProtocolID = 0x1122334455667788 + +gPacketData :: [Word8] +gPacketData = [0..(Netcode.maximumPacketSize - 1)] + +gMaxNumClients :: Int +gMaxNumClients = 1024 + +gMaxNumServers :: Int +gMaxNumServers = 64 + +gServerBasePort :: Int +gServerBasePort = 40000 + +gConnectTokenExpiry :: Int +gConnectTokenExpiry = 30 + +gConnectTokenTimeout :: Int +gConnectTokenTimeout = 5 + +------------------------------------------------------------------------------- +whenM :: Monad m => m Bool -> m () -> m () +whenM c p = do { b <- c; when b p } + +untilM :: Monad m => m Bool -> m () +untilM p = p >>= bool (untilM p) (return ()) +------------------------------------------------------------------------------- + +data Soak = Soak (Activated Netcode.Client) (Activated Netcode.Server) + +initializeSoak :: IO Soak +initializeSoak = do + putStrLn "initializing" + Netcode.initialize + Netcode.logLevel Netcode.LogLevel'Info + + return $ Soak (mkActivated gMaxNumClients) (mkActivated gMaxNumServers) + +shutdownSoak :: Soak -> IO () +shutdownSoak (Soak clients servers) = do + putStrLn "shutdown" + + forM_ (map snd $ active clients) Netcode.destroyClient + forM_ (map snd $ active servers) Netcode.destroyServer + + Netcode.terminate + +decodePacket :: Maybe Netcode.Packet -> IO Bool +decodePacket Nothing = return True +decodePacket (Just pkt) = + withForeignPtr (Netcode.packetDataPtr pkt) $ \pktMem -> do + pktData <- peekArray (Netcode.packetSize pkt) pktMem + case (and $ zipWith (==) pktData gPacketData) of + True -> return False + False -> fail "Received garbled packet!" + +iterateSoak :: RandomInts -> IORef Soak -> Double -> IO () +iterateSoak ints sr t = do + (Soak clients servers) <- readIORef sr + let serverConfig = + Netcode.setPrivateKey gPrivateKey $ + Netcode.setProtocolID gProtocolID $ + Netcode.defaultServerConfig + + mkServer ss serverID = do + let addr = "127.0.0.1:" ++ show (gServerBasePort + serverID) + s <- Netcode.createServer addr serverConfig t + putStrLn $ "created server " ++ show s + return $ fromJust $ activate serverID s ss + + servers' <- filterRand ints 10 (inactive servers) + >>= foldM mkServer servers + + let killServer ss (serverID, s) = do + putStrLn $ "destroy server " ++ show s + Netcode.destroyServer s + return $ fromJust $ deactivate serverID ss + + servers'' <- filterM (Netcode.isServerFull . snd) (active servers) + >>= filterRand ints 10000 + >>= foldM killServer servers' + + let mkClient cs clientID = do + c <- Netcode.createClient "0.0.0.0" Netcode.defaultClientConfig t + putStrLn $ "created client " ++ show c + return $ fromJust $ activate clientID c cs + + clients' <- filterRand ints 10 (inactive clients) + >>= foldM mkClient clients + + let killClient cs (clientID, c) = do + putStrLn $ "destroy client " ++ show c + Netcode.destroyClient c + return $ fromJust $ deactivate clientID cs + + clients'' <- filterRand ints 1000 (active clients) + >>= foldM killClient clients' + + forM_ servers'' $ \s -> do + _ <- getNextInt ints + whenRandMod ints 10 $ + whenM (not <$> Netcode.isServerRunning s) $ do + numClients <- (`mod` Netcode.maxNumClients) <$> getNextInt ints + Netcode.startServer s (1 + numClients) + + maxClients <- Netcode.maxClientsForServer s + whenRandMod ints 1000 $ + whenM ((== maxClients) <$> Netcode.numConnectedClients s) $ + whenM (Netcode.isServerRunning s) $ + Netcode.stopServer s + + whenM (Netcode.isServerRunning s) $ do + forM_ [0..(maxClients - 1)] $ \clientIndex -> + whenM (Netcode.clientConnectedAtIndex s clientIndex) $ do + pktSz <- + ((+ 1) . (`mod` Netcode.maximumPacketSize)) <$> + getNextInt ints + withArrayLen (take pktSz gPacketData) $ + Netcode.sendPacketFromServer s clientIndex + + forM_ [0..(maxClients - 1)] $ \clientIndex -> + whenM (Netcode.clientConnectedAtIndex s clientIndex) $ + untilM $ Netcode.receivePacketFromClient s clientIndex + >>= decodePacket + + Netcode.updateServer s t + + forM_ clients'' $ \c -> do + _ <- getNextInt ints + whenRandMod ints 10 $ whenM (Netcode.isClientDisconnected c) $ do + clientID <- Netcode.generateClientID + userData <- + mapM (fmap fromIntegral) + (replicate Netcode.maximumUserDataSize $ getNextInt ints) + + connectServers <- + filterM (Netcode.isServerRunning . snd) (active servers'') + + let maxServers = Netcode.maximumServersPerConnect + mkAddrPair i = + let addr = "127.0.0.1:" <> show (gServerBasePort + i) + in (addr, addr) + serverAddrs = take maxServers $ + map (mkAddrPair . fst) connectServers + + when (not $ null serverAddrs) $ + Netcode.connectClient c =<< + Netcode.generateConnectToken serverAddrs + gConnectTokenExpiry + gConnectTokenTimeout + clientID + gProtocolID + gPrivateKey + userData + + let isConnected = (== Netcode.ClientState'Connected) + whenRandMod ints 100 $ + whenM (isConnected <$> Netcode.getClientState c) $ + Netcode.disconnectClient c + + whenM (isConnected <$> Netcode.getClientState c) $ do + pktSz <- + ((+ 1) . (`mod` Netcode.maximumPacketSize)) <$> + getNextInt ints + withArrayLen (take pktSz gPacketData) + (Netcode.sendPacketFromClient c) + + untilM $ Netcode.receivePacketFromServer c >>= decodePacket + + Netcode.updateClient c t + + writeIORef sr $ Soak clients'' servers'' + +main :: IO () +main = do + hSetBuffering stdin LineBuffering + args <- getArgs + let numIters :: Maybe Int + numIters = case length args of + 1 -> Just $ read (head args) + _ -> Nothing + + times = case numIters of + Just x -> take x [0.0,0.1..] + Nothing -> [0.0,0.1..] + + putStrLn "[soak]" + putStrLn $ "num_iterations = " <> show numIters + + soak <- initializeSoak + soakRef <- newIORef soak + + ints <- mkRandomInts + + soakResult <- try $ forM_ times $ iterateSoak ints soakRef + + case soakResult of + (Left UserInterrupt) -> return () + (Left e) -> fail (show e) + (Right ()) -> return () + + readIORef soakRef >>= shutdownSoak
+ netcode-io.cabal view
@@ -0,0 +1,124 @@+cabal-version: >= 1.10 +name: netcode-io +version: 0.0.1 +synopsis: Bindings to the low-level netcode.io library. +description: Bindings to the low-level netcode.io library, which come + in two flavors: c-level bindings and a high-level + interface to the library. + + For the low level interface, refer to the + Bindings.Netcode.IO module. + + For the high-level interface, which is a bit closer to + idiomatic Haskell, refer to the Netcode.IO module. +homepage: http://www.github.com/Mokosha/netcode-io +bug-reports: http://www.github.com/Mokosha/netcode-io +license: BSD3 +license-file: LICENSE +author: Pavel Krajcevski +maintainer: krajcevski@gmail.com +copyright: Pavel Krajcevski, 2020 +category: Network +build-type: Simple +extra-source-files: README.md + , netcode.io/netcode.h + , netcode.io/netcode.c + +source-repository head + type: git + location: https://www.github.com/Mokosha/netcode-io.git + +library + ghc-options: -Wall -fPIC + include-dirs: netcode.io + c-sources: netcode.io/netcode.c + cc-options: -std=c99 -DNDEBUG -DNETCODE_ENABLE_TESTS=1 + extra-libraries: sodium + + if os(mingw32) + -- To make life easy on windows, we encourage using static libraries where + -- possible. That way we don't need to go around copying .dll files. + ld-options: -static + extra-libraries: ws2_32 + + exposed-modules: Bindings.Netcode.IO + , Netcode.IO + other-modules: Netcode.IO.Server + , Netcode.IO.Client + , Netcode.IO.Address + , Netcode.IO.Packet + , Netcode.IO.Callbacks + -- other-extensions: + build-tools: hsc2hs + build-depends: base >= 4.12 && < 5 + , bindings-DSL + default-language: Haskell2010 + +flag examples + description: If true, build the examples + default: False + +executable netcode-io-c-unit-tests + default-language: Haskell2010 + main-is: RunCUnitTests.hs + hs-source-dirs: examples + ghc-options: -Wall -static -fPIC + build-depends: base > 4 + , netcode-io + + if flag(examples) + buildable: True + else + buildable: False + +executable netcode-io-client + default-language: Haskell2010 + main-is: Client.hs + hs-source-dirs: examples + ghc-options: -Wall -static -fPIC + build-depends: base > 4 + , netcode-io + + if flag(examples) + buildable: True + else + buildable: False + +executable netcode-io-server + default-language: Haskell2010 + main-is: Server.hs + hs-source-dirs: examples + ghc-options: -Wall -static -fPIC + build-depends: base > 4 + , netcode-io + + if flag(examples) + buildable: True + else + buildable: False + +executable netcode-io-clientserver + default-language: Haskell2010 + main-is: ClientServer.hs + hs-source-dirs: examples + ghc-options: -Wall -static -fPIC + build-depends: base > 4 + , netcode-io + + if flag(examples) + buildable: True + else + buildable: False + +executable netcode-io-soak + default-language: Haskell2010 + main-is: Soak.hs + hs-source-dirs: examples + ghc-options: -Wall -static -fPIC + build-depends: base > 4 + , netcode-io + + if flag(examples) + buildable: True + else + buildable: False
+ netcode.io/netcode.c view
@@ -0,0 +1,8971 @@+/* + netcode.io reference implementation + + Copyright © 2017 - 2019, The Network Protocol Company, Inc. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "netcode.h" +#include <stdlib.h> +#include <memory.h> +#include <stdio.h> +#include <stdarg.h> +#include <inttypes.h> +#include <math.h> +#include <time.h> + +#ifdef _MSC_VER +#define SODIUM_STATIC +#pragma warning(disable:4996) +#endif // #ifdef _MSC_VER + +#include <sodium.h> + +#define NETCODE_SOCKET_IPV6 1 +#define NETCODE_SOCKET_IPV4 2 + +#define NETCODE_CONNECT_TOKEN_NONCE_BYTES 24 +#define NETCODE_CONNECT_TOKEN_PRIVATE_BYTES 1024 +#define NETCODE_CHALLENGE_TOKEN_BYTES 300 +#define NETCODE_VERSION_INFO_BYTES 13 +#define NETCODE_MAX_PACKET_BYTES 1300 +#define NETCODE_MAX_PAYLOAD_BYTES 1200 +#define NETCODE_MAX_ADDRESS_STRING_LENGTH 256 +#define NETCODE_PACKET_QUEUE_SIZE 256 +#define NETCODE_REPLAY_PROTECTION_BUFFER_SIZE 256 +#define NETCODE_CLIENT_MAX_RECEIVE_PACKETS 64 +#define NETCODE_SERVER_MAX_RECEIVE_PACKETS ( 64 * NETCODE_MAX_CLIENTS ) +#define NETCODE_CLIENT_SOCKET_SNDBUF_SIZE ( 256 * 1024 ) +#define NETCODE_CLIENT_SOCKET_RCVBUF_SIZE ( 256 * 1024 ) +#define NETCODE_SERVER_SOCKET_SNDBUF_SIZE ( 4 * 1024 * 1024 ) +#define NETCODE_SERVER_SOCKET_RCVBUF_SIZE ( 4 * 1024 * 1024 ) + +#define NETCODE_VERSION_INFO ( (uint8_t*) "NETCODE 1.02" ) +#define NETCODE_PACKET_SEND_RATE 10.0 +#define NETCODE_NUM_DISCONNECT_PACKETS 10 + +#ifndef NETCODE_ENABLE_TESTS +#define NETCODE_ENABLE_TESTS 0 +#endif // #ifndef NETCODE_ENABLE_TESTS + +#ifndef NETCODE_ENABLE_LOGGING +#define NETCODE_ENABLE_LOGGING 1 +#endif // #ifndef NETCODE_ENABLE_LOGGING + +// ------------------------------------------------------------------ + +static void netcode_default_assert_handler( NETCODE_CONST char * condition, NETCODE_CONST char * function, NETCODE_CONST char * file, int line ) +{ + printf( "assert failed: ( %s ), function %s, file %s, line %d\n", condition, function, file, line ); + #if defined( __GNUC__ ) + __builtin_trap(); + #elif defined( _MSC_VER ) + __debugbreak(); + #endif + exit( 1 ); +} + +static int log_level = 0; +static int (*printf_function)( NETCODE_CONST char *, ... ) = ( int (*)( NETCODE_CONST char *, ... ) ) printf; +void (*netcode_assert_function)( NETCODE_CONST char *, NETCODE_CONST char *, NETCODE_CONST char * file, int line ) = netcode_default_assert_handler; + +void netcode_log_level( int level ) +{ + log_level = level; +} + +void netcode_set_printf_function( int (*function)( NETCODE_CONST char *, ... ) ) +{ + netcode_assert( function ); + printf_function = function; +} + +void netcode_set_assert_function( void (*function)( NETCODE_CONST char *, NETCODE_CONST char *, NETCODE_CONST char * file, int line ) ) +{ + netcode_assert_function = function; +} + +#if NETCODE_ENABLE_LOGGING + +void netcode_printf( int level, NETCODE_CONST char * format, ... ) +{ + if ( level > log_level ) + return; + va_list args; + va_start( args, format ); + char buffer[4*1024]; + vsprintf( buffer, format, args ); + printf_function( "%s", buffer ); + va_end( args ); +} + +#else // #if NETCODE_ENABLE_LOGGING + +void netcode_printf( int level, NETCODE_CONST char * format, ... ) +{ + (void) level; + (void) format; +} + +#endif // #if NETCODE_ENABLE_LOGGING + +void * netcode_default_allocate_function( void * context, uint64_t bytes ) +{ + (void) context; + return malloc( bytes ); +} + +void netcode_default_free_function( void * context, void * pointer ) +{ + (void) context; + free( pointer ); +} + +// ------------------------------------------------------------------ + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + + #define NOMINMAX + #define _WINSOCK_DEPRECATED_NO_WARNINGS + #include <winsock2.h> + #include <ws2tcpip.h> + #include <ws2ipdef.h> + #include <iphlpapi.h> + #pragma comment( lib, "WS2_32.lib" ) + #pragma comment( lib, "IPHLPAPI.lib" ) + + #ifdef SetPort + #undef SetPort + #endif // #ifdef SetPort + + #include <iphlpapi.h> + #pragma comment( lib, "IPHLPAPI.lib" ) + +#elif NETCODE_PLATFORM == NETCODE_PLATFORM_MAC || NETCODE_PLATFORM == NETCODE_PLATFORM_UNIX + + #include <netdb.h> + #include <sys/types.h> + #include <sys/socket.h> + #include <netinet/in.h> + #include <ifaddrs.h> + #include <net/if.h> + #include <fcntl.h> + #include <netdb.h> + #include <arpa/inet.h> + #include <unistd.h> + #include <errno.h> + +#else + + #error netcode.io - unknown platform! + +#endif + +// ---------------------------------------------------------------- + +#ifdef __MINGW32__ + const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) + { + if ( af == AF_INET ) + { + struct sockaddr_in in; + memset(&in, 0, sizeof(in)); + in.sin_family = AF_INET; + memcpy(&in.sin_addr, src, sizeof(struct in_addr)); + getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), dst, cnt, NULL, 0, NI_NUMERICHOST); + return dst; + } + else if ( af == AF_INET6 ) + { + struct sockaddr_in6 in; + memset(&in, 0, sizeof(in)); + in.sin6_family = AF_INET6; + memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); + getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in6), dst, cnt, NULL, 0, NI_NUMERICHOST); + return dst; + } + + return NULL; + } + + #define NS_INADDRSZ 4 + #define NS_IN6ADDRSZ 16 + #define NS_INT16SZ 2 + + int inet_pton4(const char *src, char *dst) + { + uint8_t tmp[NS_INADDRSZ], *tp; + int saw_digit = 0; + int octets = 0; + *(tp = tmp) = 0; + int ch; + + while ((ch = *src++) != '\0') + { + if ( ch >= '0' && ch <= '9' ) + { + uint32_t n = *tp * 10 + (ch - '0'); + + if ( saw_digit && *tp == 0 ) + return 0; + if ( n > 255 ) + return 0; + *tp = n; + if ( !saw_digit ) + { + if ( ++octets > 4 ) + return 0; + saw_digit = 1; + } + } + else if ( ch == '.' && saw_digit ) + { + if ( octets == 4 ) + return 0; + *++tp = 0; + saw_digit = 0; + } + else + return 0; + } + if ( octets < 4 ) + return 0; + + memcpy(dst, tmp, NS_INADDRSZ); + + return 1; + } + + int inet_pton6(const char *src, char *dst) + { + static const char xdigits[] = "0123456789abcdef"; + uint8_t tmp[NS_IN6ADDRSZ]; + uint8_t *tp = (uint8_t*) memset(tmp, '\0', NS_IN6ADDRSZ); + uint8_t *endp = tp + NS_IN6ADDRSZ; + uint8_t *colonp = NULL; + + // Leading :: requires some special handling + if ( *src == ':' ) + { + if ( *++src != ':' ) + return 0; + } + + const char *curtok = src; + int saw_xdigit = 0; + uint32_t val = 0; + int ch; + + while ( (ch = tolower(*src++)) != '\0' ) + { + const char *pch = strchr(xdigits, ch); + if ( pch != NULL ) + { + val <<= 4; + val |= (pch - xdigits); + if ( val > 0xffff ) + return 0; + saw_xdigit = 1; + continue; + } + if ( ch == ':' ) + { + curtok = src; + if ( !saw_xdigit ) + { + if ( colonp ) + return 0; + colonp = tp; + continue; + } + else if ( *src == '\0' ) + { + return 0; + } + if ( tp + NS_INT16SZ > endp ) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if ( ch == '.' && ((tp + NS_INADDRSZ) <= endp) && inet_pton4(curtok, (char*) tp) > 0 ) + { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; + } + return 0; + } + + if ( saw_xdigit ) + { + if ( tp + NS_INT16SZ > endp ) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + } + + if ( colonp != NULL ) + { + const int n = tp - colonp; + if ( tp == endp ) + return 0; + for (int i = 1; i <= n; i++) + { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if ( tp != endp ) + return 0; + + memcpy(dst, tmp, NS_IN6ADDRSZ); + + return 1; + } + + int inet_pton(int af, const char *src, void *dst) + { + switch ( af ) + { + case AF_INET: + return inet_pton4(src, (char *) dst); + + case AF_INET6: + return inet_pton6(src, (char *) dst); + + default: + return -1; + } + } +#endif + +// ---------------------------------------------------------------- + +int netcode_parse_address( NETCODE_CONST char * address_string_in, struct netcode_address_t * address ) +{ + netcode_assert( address_string_in ); + netcode_assert( address ); + + memset( address, 0, sizeof( struct netcode_address_t ) ); + + // first try to parse the string as an IPv6 address: + // 1. if the first character is '[' then it's probably an ipv6 in form "[addr6]:portnum" + // 2. otherwise try to parse as a raw IPv6 address using inet_pton + + #define NETCODE_ADDRESS_BUFFER_SAFETY 32 + + char buffer[NETCODE_MAX_ADDRESS_STRING_LENGTH + NETCODE_ADDRESS_BUFFER_SAFETY*2]; + + char * address_string = buffer + NETCODE_ADDRESS_BUFFER_SAFETY; + strncpy( address_string, address_string_in, NETCODE_MAX_ADDRESS_STRING_LENGTH - 1 ); + address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH-1] = '\0'; + + int address_string_length = (int) strlen( address_string ); + + if ( address_string[0] == '[' ) + { + int base_index = address_string_length - 1; + + int i; + for ( i = 0; i < 6; ++i ) // note: no need to search past 6 characters as ":65535" is longest possible port value + { + int index = base_index - i; + if ( index < 3 ) + return NETCODE_ERROR; + if ( address_string[index] == ':' ) + { + address->port = (uint16_t) ( atoi( &address_string[index + 1] ) ); + address_string[index-1] = '\0'; + } + } + address_string += 1; + } + + struct in6_addr sockaddr6; + if ( inet_pton( AF_INET6, address_string, &sockaddr6 ) == 1 ) + { + address->type = NETCODE_ADDRESS_IPV6; + int i; + for ( i = 0; i < 8; ++i ) + { + address->data.ipv6[i] = ntohs( ( (uint16_t*) &sockaddr6 ) [i] ); + } + return NETCODE_OK; + } + + // otherwise it's probably an IPv4 address: + // 1. look for ":portnum", if found save the portnum and strip it out + // 2. parse remaining ipv4 address via inet_pton + + address_string_length = (int) strlen( address_string ); + int base_index = address_string_length - 1; + int i; + for ( i = 0; i < 6; ++i ) + { + int index = base_index - i; + if ( index < 0 ) + break; + if ( address_string[index] == ':' ) + { + address->port = (uint16_t) atoi( &address_string[index+1] ); + address_string[index] = '\0'; + } + } + + struct sockaddr_in sockaddr4; + if ( inet_pton( AF_INET, address_string, &sockaddr4.sin_addr ) == 1 ) + { + address->type = NETCODE_ADDRESS_IPV4; + address->data.ipv4[3] = (uint8_t) ( ( sockaddr4.sin_addr.s_addr & 0xFF000000 ) >> 24 ); + address->data.ipv4[2] = (uint8_t) ( ( sockaddr4.sin_addr.s_addr & 0x00FF0000 ) >> 16 ); + address->data.ipv4[1] = (uint8_t) ( ( sockaddr4.sin_addr.s_addr & 0x0000FF00 ) >> 8 ); + address->data.ipv4[0] = (uint8_t) ( ( sockaddr4.sin_addr.s_addr & 0x000000FF ) ); + return NETCODE_OK; + } + + return NETCODE_ERROR; +} + +char * netcode_address_to_string( struct netcode_address_t * address, char * buffer ) +{ + netcode_assert( address ); + netcode_assert( buffer ); + + if ( address->type == NETCODE_ADDRESS_IPV6 ) + { + if ( address->port == 0 ) + { + uint16_t ipv6_network_order[8]; + int i; + for ( i = 0; i < 8; ++i ) + ipv6_network_order[i] = htons( address->data.ipv6[i] ); + inet_ntop( AF_INET6, (void*) ipv6_network_order, buffer, NETCODE_MAX_ADDRESS_STRING_LENGTH ); + return buffer; + } + else + { + char address_string[INET6_ADDRSTRLEN]; + uint16_t ipv6_network_order[8]; + int i; + for ( i = 0; i < 8; ++i ) + ipv6_network_order[i] = htons( address->data.ipv6[i] ); + inet_ntop( AF_INET6, (void*) ipv6_network_order, address_string, INET6_ADDRSTRLEN ); + snprintf( buffer, NETCODE_MAX_ADDRESS_STRING_LENGTH, "[%s]:%d", address_string, address->port ); + return buffer; + } + } + else if ( address->type == NETCODE_ADDRESS_IPV4 ) + { + if ( address->port != 0 ) + { + snprintf( buffer, NETCODE_MAX_ADDRESS_STRING_LENGTH, "%d.%d.%d.%d:%d", + address->data.ipv4[0], + address->data.ipv4[1], + address->data.ipv4[2], + address->data.ipv4[3], + address->port ); + } + else + { + snprintf( buffer, NETCODE_MAX_ADDRESS_STRING_LENGTH, "%d.%d.%d.%d", + address->data.ipv4[0], + address->data.ipv4[1], + address->data.ipv4[2], + address->data.ipv4[3] ); + } + return buffer; + } + else + { + snprintf( buffer, NETCODE_MAX_ADDRESS_STRING_LENGTH, "%s", "NONE" ); + return buffer; + } +} + +int netcode_address_is_any_address(struct netcode_address_t *a) { + if (a->type == NETCODE_ADDRESS_IPV4) { + // 0.0.0.0 + if (a->data.ipv4[0] == 0 && + a->data.ipv4[1] == 0 && + a->data.ipv4[2] == 0 && + a->data.ipv4[3] == 0) + return 1; + + } else if ( a->type == NETCODE_ADDRESS_IPV6 ) { + // [::] aka [0:0:0:0:0:0:0:0] + if (a->data.ipv6[0] == 0 && + a->data.ipv6[1] == 0 && + a->data.ipv6[2] == 0 && + a->data.ipv6[3] == 0 && + a->data.ipv6[4] == 0 && + a->data.ipv6[5] == 0 && + a->data.ipv6[6] == 0 && + a->data.ipv6[7] == 0) + return 1; + } + + return 0; +} + +int netcode_address_equal( struct netcode_address_t * a, struct netcode_address_t * b ) +{ + netcode_assert( a ); + netcode_assert( b ); + + if ( a->type != b->type ) + return 0; + + if ( a->port != b->port ) + return 0; + + // At this point, ports match. If either address is 0.0.0.0 or [::], then mark them as matching. These addresses are never routed, they're only used when listening. + if (netcode_address_is_any_address(a) || netcode_address_is_any_address(b)) + return 1; + + if ( a->type == NETCODE_ADDRESS_IPV4 ) + { + int i; + for ( i = 0; i < 4; ++i ) + { + if ( a->data.ipv4[i] != b->data.ipv4[i] ) + return 0; + } + } + else if ( a->type == NETCODE_ADDRESS_IPV6 ) + { + int i; + for ( i = 0; i < 8; ++i ) + { + if ( a->data.ipv6[i] != b->data.ipv6[i] ) + return 0; + } + } + else + { + return 0; + } + + return 1; +} + +// ---------------------------------------------------------------- + +struct netcode_t +{ + int initialized; +}; + +static struct netcode_t netcode; + +int netcode_init() +{ + netcode_assert( !netcode.initialized ); + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + WSADATA WsaData; + if ( WSAStartup( MAKEWORD(2,2), &WsaData ) != NO_ERROR ) + return NETCODE_ERROR; +#endif // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + + if ( sodium_init() == -1 ) + return NETCODE_ERROR; + + netcode.initialized = 1; + + return NETCODE_OK; +} + +void netcode_term() +{ + netcode_assert( netcode.initialized ); + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + WSACleanup(); +#endif // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + + netcode.initialized = 0; +} + +// ---------------------------------------------------------------- + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS +typedef uint64_t netcode_socket_handle_t; +#else // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS +typedef int netcode_socket_handle_t; +#endif // #if NETCODE_PLATFORM == NETCODe_PLATFORM_WINDOWS + +struct netcode_socket_t +{ + struct netcode_address_t address; + netcode_socket_handle_t handle; +}; + + +struct netcode_socket_holder_t +{ + struct netcode_socket_t ipv4; + struct netcode_socket_t ipv6; +}; + +#define NETCODE_SOCKET_ERROR_NONE 0 +#define NETCODE_SOCKET_ERROR_CREATE_FAILED 1 +#define NETCODE_SOCKET_ERROR_SET_NON_BLOCKING_FAILED 2 +#define NETCODE_SOCKET_ERROR_SOCKOPT_IPV6_ONLY_FAILED 3 +#define NETCODE_SOCKET_ERROR_SOCKOPT_RCVBUF_FAILED 4 +#define NETCODE_SOCKET_ERROR_SOCKOPT_SNDBUF_FAILED 5 +#define NETCODE_SOCKET_ERROR_BIND_IPV4_FAILED 6 +#define NETCODE_SOCKET_ERROR_BIND_IPV6_FAILED 7 +#define NETCODE_SOCKET_ERROR_GET_SOCKNAME_IPV4_FAILED 8 +#define NETCODE_SOCKET_ERROR_GET_SOCKNAME_IPV6_FAILED 7 + +void netcode_socket_destroy( struct netcode_socket_t * socket ) +{ + netcode_assert( socket ); + netcode_assert( netcode.initialized ); + + if ( socket->handle != 0 ) + { + #if NETCODE_PLATFORM == NETCODE_PLATFORM_MAC || NETCODE_PLATFORM == NETCODE_PLATFORM_UNIX + close( socket->handle ); + #elif NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + closesocket( socket->handle ); + #else + #error unsupported platform + #endif + socket->handle = 0; + } +} + +int netcode_socket_create( struct netcode_socket_t * s, struct netcode_address_t * address, int send_buffer_size, int receive_buffer_size ) +{ + netcode_assert( s ); + netcode_assert( address ); + netcode_assert( netcode.initialized ); + + netcode_assert( address->type != NETCODE_ADDRESS_NONE ); + + s->address = *address; + + // create socket + + s->handle = socket( ( address->type == NETCODE_ADDRESS_IPV6 ) ? AF_INET6 : AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + if ( s->handle == INVALID_SOCKET ) +#else // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + if ( s->handle <= 0 ) +#endif // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to create socket\n" ); + return NETCODE_SOCKET_ERROR_CREATE_FAILED; + } + + // force IPv6 only if necessary + + if ( address->type == NETCODE_ADDRESS_IPV6 ) + { + int yes = 1; + if ( setsockopt( s->handle, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&yes, sizeof(yes) ) != 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to set socket ipv6 only\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_SOCKOPT_IPV6_ONLY_FAILED; + } + } + + // increase socket send and receive buffer sizes + + if ( setsockopt( s->handle, SOL_SOCKET, SO_SNDBUF, (char*)&send_buffer_size, sizeof(int) ) != 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to set socket send buffer size\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_SOCKOPT_SNDBUF_FAILED; + } + + if ( setsockopt( s->handle, SOL_SOCKET, SO_RCVBUF, (char*)&receive_buffer_size, sizeof(int) ) != 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to set socket receive buffer size\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_SOCKOPT_RCVBUF_FAILED; + } + + // bind to port + + if ( address->type == NETCODE_ADDRESS_IPV6 ) + { + struct sockaddr_in6 socket_address; + memset( &socket_address, 0, sizeof( struct sockaddr_in6 ) ); + socket_address.sin6_family = AF_INET6; + int i; + for ( i = 0; i < 8; ++i ) + { + ( (uint16_t*) &socket_address.sin6_addr ) [i] = htons( address->data.ipv6[i] ); + } + socket_address.sin6_port = htons( address->port ); + + if ( bind( s->handle, (struct sockaddr*) &socket_address, sizeof( socket_address ) ) < 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to bind socket (ipv6)\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_BIND_IPV6_FAILED; + } + } + else + { + struct sockaddr_in socket_address; + memset( &socket_address, 0, sizeof( socket_address ) ); + socket_address.sin_family = AF_INET; + socket_address.sin_addr.s_addr = ( ( (uint32_t) address->data.ipv4[0] ) ) | + ( ( (uint32_t) address->data.ipv4[1] ) << 8 ) | + ( ( (uint32_t) address->data.ipv4[2] ) << 16 ) | + ( ( (uint32_t) address->data.ipv4[3] ) << 24 ); + socket_address.sin_port = htons( address->port ); + + if ( bind( s->handle, (struct sockaddr*) &socket_address, sizeof( socket_address ) ) < 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to bind socket (ipv4)\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_BIND_IPV4_FAILED; + } + } + + // if bound to port 0 find the actual port we got + + if ( address->port == 0 ) + { + if ( address->type == NETCODE_ADDRESS_IPV6 ) + { + struct sockaddr_in6 sin; + socklen_t len = sizeof( sin ); + if ( getsockname( s->handle, (struct sockaddr*)&sin, &len ) == -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to get socket port (ipv6)\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_GET_SOCKNAME_IPV6_FAILED; + } + s->address.port = ntohs( sin.sin6_port ); + } + else + { + struct sockaddr_in sin; + socklen_t len = sizeof( sin ); + if ( getsockname( s->handle, (struct sockaddr*)&sin, &len ) == -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to get socket port (ipv4)\n" ); + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_GET_SOCKNAME_IPV4_FAILED; + } + s->address.port = ntohs( sin.sin_port ); + } + } + + // set non-blocking io + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_MAC || NETCODE_PLATFORM == NETCODE_PLATFORM_UNIX + + int non_blocking = 1; + if ( fcntl( s->handle, F_SETFL, O_NONBLOCK, non_blocking ) == -1 ) + { + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_SET_NON_BLOCKING_FAILED; + } + +#elif NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + + DWORD nonBlocking = 1; + if ( ioctlsocket( s->handle, FIONBIO, &nonBlocking ) != 0 ) + { + netcode_socket_destroy( s ); + return NETCODE_SOCKET_ERROR_SET_NON_BLOCKING_FAILED; + } + +#else + + #error unsupported platform + +#endif + + return NETCODE_SOCKET_ERROR_NONE; +} + +void netcode_socket_send_packet( struct netcode_socket_t * socket, struct netcode_address_t * to, void * packet_data, int packet_bytes ) +{ + netcode_assert( socket ); + netcode_assert( socket->handle != 0 ); + netcode_assert( to ); + netcode_assert( to->type == NETCODE_ADDRESS_IPV6 || to->type == NETCODE_ADDRESS_IPV4 ); + netcode_assert( packet_data ); + netcode_assert( packet_bytes > 0 ); + + if ( to->type == NETCODE_ADDRESS_IPV6 ) + { + struct sockaddr_in6 socket_address; + memset( &socket_address, 0, sizeof( socket_address ) ); + socket_address.sin6_family = AF_INET6; + int i; + for ( i = 0; i < 8; ++i ) + { + ( (uint16_t*) &socket_address.sin6_addr ) [i] = htons( to->data.ipv6[i] ); + } + socket_address.sin6_port = htons( to->port ); + int result = sendto( socket->handle, (char*) packet_data, packet_bytes, 0, (struct sockaddr*) &socket_address, sizeof( struct sockaddr_in6 ) ); + (void) result; + } + else if ( to->type == NETCODE_ADDRESS_IPV4 ) + { + struct sockaddr_in socket_address; + memset( &socket_address, 0, sizeof( socket_address ) ); + socket_address.sin_family = AF_INET; + socket_address.sin_addr.s_addr = ( ( (uint32_t) to->data.ipv4[0] ) ) | + ( ( (uint32_t) to->data.ipv4[1] ) << 8 ) | + ( ( (uint32_t) to->data.ipv4[2] ) << 16 ) | + ( ( (uint32_t) to->data.ipv4[3] ) << 24 ); + socket_address.sin_port = htons( to->port ); + int result = sendto( socket->handle, (NETCODE_CONST char*) packet_data, packet_bytes, 0, (struct sockaddr*) &socket_address, sizeof( struct sockaddr_in ) ); + (void) result; + } +} + +int netcode_socket_receive_packet( struct netcode_socket_t * socket, struct netcode_address_t * from, void * packet_data, int max_packet_size ) +{ + netcode_assert( socket ); + netcode_assert( socket->handle != 0 ); + netcode_assert( from ); + netcode_assert( packet_data ); + netcode_assert( max_packet_size > 0 ); + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + typedef int socklen_t; +#endif // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + + struct sockaddr_storage sockaddr_from; + socklen_t from_length = sizeof( sockaddr_from ); + + int result = recvfrom( socket->handle, (char*) packet_data, max_packet_size, 0, (struct sockaddr*) &sockaddr_from, &from_length ); + +#if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + if ( result == SOCKET_ERROR ) + { + int error = WSAGetLastError(); + + if ( error == WSAEWOULDBLOCK || error == WSAECONNRESET ) + return 0; + + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: recvfrom failed with error %d\n", error ); + + return 0; + } +#else // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + if ( result <= 0 ) + { + if ( errno == EAGAIN ) + return 0; + + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: recvfrom failed with error %d\n", errno ); + + return 0; + } +#endif // #if NETCODE_PLATFORM == NETCODE_PLATFORM_WINDOWS + + if ( sockaddr_from.ss_family == AF_INET6 ) + { + struct sockaddr_in6 * addr_ipv6 = (struct sockaddr_in6*) &sockaddr_from; + from->type = NETCODE_ADDRESS_IPV6; + int i; + for ( i = 0; i < 8; ++i ) + { + from->data.ipv6[i] = ntohs( ( (uint16_t*) &addr_ipv6->sin6_addr ) [i] ); + } + from->port = ntohs( addr_ipv6->sin6_port ); + } + else if ( sockaddr_from.ss_family == AF_INET ) + { + struct sockaddr_in * addr_ipv4 = (struct sockaddr_in*) &sockaddr_from; + from->type = NETCODE_ADDRESS_IPV4; + from->data.ipv4[0] = (uint8_t) ( ( addr_ipv4->sin_addr.s_addr & 0x000000FF ) ); + from->data.ipv4[1] = (uint8_t) ( ( addr_ipv4->sin_addr.s_addr & 0x0000FF00 ) >> 8 ); + from->data.ipv4[2] = (uint8_t) ( ( addr_ipv4->sin_addr.s_addr & 0x00FF0000 ) >> 16 ); + from->data.ipv4[3] = (uint8_t) ( ( addr_ipv4->sin_addr.s_addr & 0xFF000000 ) >> 24 ); + from->port = ntohs( addr_ipv4->sin_port ); + } + else + { + netcode_assert( 0 ); + return 0; + } + + netcode_assert( result >= 0 ); + + int bytes_read = result; + + return bytes_read; +} + +// ---------------------------------------------------------------- + +void netcode_write_uint8( uint8_t ** p, uint8_t value ) +{ + **p = value; + ++(*p); +} + +void netcode_write_uint16( uint8_t ** p, uint16_t value ) +{ + (*p)[0] = value & 0xFF; + (*p)[1] = value >> 8; + *p += 2; +} + +void netcode_write_uint32( uint8_t ** p, uint32_t value ) +{ + (*p)[0] = value & 0xFF; + (*p)[1] = ( value >> 8 ) & 0xFF; + (*p)[2] = ( value >> 16 ) & 0xFF; + (*p)[3] = value >> 24; + *p += 4; +} + +void netcode_write_uint64( uint8_t ** p, uint64_t value ) +{ + (*p)[0] = value & 0xFF; + (*p)[1] = ( value >> 8 ) & 0xFF; + (*p)[2] = ( value >> 16 ) & 0xFF; + (*p)[3] = ( value >> 24 ) & 0xFF; + (*p)[4] = ( value >> 32 ) & 0xFF; + (*p)[5] = ( value >> 40 ) & 0xFF; + (*p)[6] = ( value >> 48 ) & 0xFF; + (*p)[7] = value >> 56; + *p += 8; +} + +void netcode_write_bytes( uint8_t ** p, uint8_t * byte_array, int num_bytes ) +{ + int i; + for ( i = 0; i < num_bytes; ++i ) + { + netcode_write_uint8( p, byte_array[i] ); + } +} + +uint8_t netcode_read_uint8( uint8_t ** p ) +{ + uint8_t value = **p; + ++(*p); + return value; +} + +uint16_t netcode_read_uint16( uint8_t ** p ) +{ + uint16_t value; + value = (*p)[0]; + value |= ( ( (uint16_t)( (*p)[1] ) ) << 8 ); + *p += 2; + return value; +} + +uint32_t netcode_read_uint32( uint8_t ** p ) +{ + uint32_t value; + value = (*p)[0]; + value |= ( ( (uint32_t)( (*p)[1] ) ) << 8 ); + value |= ( ( (uint32_t)( (*p)[2] ) ) << 16 ); + value |= ( ( (uint32_t)( (*p)[3] ) ) << 24 ); + *p += 4; + return value; +} + +uint64_t netcode_read_uint64( uint8_t ** p ) +{ + uint64_t value; + value = (*p)[0]; + value |= ( ( (uint64_t)( (*p)[1] ) ) << 8 ); + value |= ( ( (uint64_t)( (*p)[2] ) ) << 16 ); + value |= ( ( (uint64_t)( (*p)[3] ) ) << 24 ); + value |= ( ( (uint64_t)( (*p)[4] ) ) << 32 ); + value |= ( ( (uint64_t)( (*p)[5] ) ) << 40 ); + value |= ( ( (uint64_t)( (*p)[6] ) ) << 48 ); + value |= ( ( (uint64_t)( (*p)[7] ) ) << 56 ); + *p += 8; + return value; +} + +void netcode_read_bytes( uint8_t ** p, uint8_t * byte_array, int num_bytes ) +{ + int i; + for ( i = 0; i < num_bytes; ++i ) + { + byte_array[i] = netcode_read_uint8( p ); + } +} + +// ---------------------------------------------------------------- + +void netcode_generate_key( uint8_t * key ) +{ + netcode_assert( key ); + randombytes_buf( key, NETCODE_KEY_BYTES ); +} + +void netcode_generate_nonce( uint8_t * nonce ) +{ + netcode_assert( nonce ); + randombytes_buf( nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); +} + +void netcode_random_bytes( uint8_t * data, int bytes ) +{ + netcode_assert( data ); + netcode_assert( bytes > 0 ); + randombytes_buf( data, bytes ); +} + +int netcode_encrypt_aead_bignonce( uint8_t * message, uint64_t message_length, + uint8_t * additional, uint64_t additional_length, + NETCODE_CONST uint8_t * nonce, + NETCODE_CONST uint8_t * key ) +{ + unsigned long long encrypted_length; + + int result = crypto_aead_xchacha20poly1305_ietf_encrypt( message, &encrypted_length, + message, (unsigned long long) message_length, + additional, (unsigned long long) additional_length, + NULL, nonce, key ); + + if ( result != 0 ) + return NETCODE_ERROR; + + netcode_assert( encrypted_length == message_length + NETCODE_MAC_BYTES ); + + return NETCODE_OK; +} + +int netcode_decrypt_aead_bignonce( uint8_t * message, uint64_t message_length, + uint8_t * additional, uint64_t additional_length, + uint8_t * nonce, + uint8_t * key ) +{ + unsigned long long decrypted_length; + + int result = crypto_aead_xchacha20poly1305_ietf_decrypt( message, &decrypted_length, + NULL, + message, (unsigned long long) message_length, + additional, (unsigned long long) additional_length, + nonce, key ); + + if ( result != 0 ) + return NETCODE_ERROR; + + netcode_assert( decrypted_length == message_length - NETCODE_MAC_BYTES ); + + return NETCODE_OK; +} + +int netcode_encrypt_aead( uint8_t * message, uint64_t message_length, + uint8_t * additional, uint64_t additional_length, + NETCODE_CONST uint8_t * nonce, + NETCODE_CONST uint8_t * key ) +{ + unsigned long long encrypted_length; + + int result = crypto_aead_chacha20poly1305_ietf_encrypt( message, &encrypted_length, + message, (unsigned long long) message_length, + additional, (unsigned long long) additional_length, + NULL, nonce, key ); + + if ( result != 0 ) + return NETCODE_ERROR; + + netcode_assert( encrypted_length == message_length + NETCODE_MAC_BYTES ); + + return NETCODE_OK; +} + +int netcode_decrypt_aead( uint8_t * message, uint64_t message_length, + uint8_t * additional, uint64_t additional_length, + uint8_t * nonce, + uint8_t * key ) +{ + unsigned long long decrypted_length; + + int result = crypto_aead_chacha20poly1305_ietf_decrypt( message, &decrypted_length, + NULL, + message, (unsigned long long) message_length, + additional, (unsigned long long) additional_length, + nonce, key ); + + if ( result != 0 ) + return NETCODE_ERROR; + + netcode_assert( decrypted_length == message_length - NETCODE_MAC_BYTES ); + + return NETCODE_OK; +} + +// ---------------------------------------------------------------- + +struct netcode_connect_token_private_t +{ + uint64_t client_id; + int timeout_seconds; + int num_server_addresses; + struct netcode_address_t server_addresses[NETCODE_MAX_SERVERS_PER_CONNECT]; + uint8_t client_to_server_key[NETCODE_KEY_BYTES]; + uint8_t server_to_client_key[NETCODE_KEY_BYTES]; + uint8_t user_data[NETCODE_USER_DATA_BYTES]; +}; + +void netcode_generate_connect_token_private( struct netcode_connect_token_private_t * connect_token, + uint64_t client_id, + int timeout_seconds, + int num_server_addresses, + struct netcode_address_t * server_addresses, + uint8_t * user_data ) +{ + netcode_assert( connect_token ); + netcode_assert( num_server_addresses > 0 ); + netcode_assert( num_server_addresses <= NETCODE_MAX_SERVERS_PER_CONNECT ); + netcode_assert( server_addresses ); + netcode_assert( user_data ); + + connect_token->client_id = client_id; + connect_token->timeout_seconds = timeout_seconds; + connect_token->num_server_addresses = num_server_addresses; + + int i; + for ( i = 0; i < num_server_addresses; ++i ) + { + memcpy( &connect_token->server_addresses[i], &server_addresses[i], sizeof( struct netcode_address_t ) ); + } + + netcode_generate_key( connect_token->client_to_server_key ); + netcode_generate_key( connect_token->server_to_client_key ); + + if ( user_data != NULL ) + { + memcpy( connect_token->user_data, user_data, NETCODE_USER_DATA_BYTES ); + } + else + { + memset( connect_token->user_data, 0, NETCODE_USER_DATA_BYTES ); + } +} + +void netcode_write_connect_token_private( struct netcode_connect_token_private_t * connect_token, uint8_t * buffer, int buffer_length ) +{ + (void) buffer_length; + + netcode_assert( connect_token ); + netcode_assert( connect_token->num_server_addresses > 0 ); + netcode_assert( connect_token->num_server_addresses <= NETCODE_MAX_SERVERS_PER_CONNECT ); + netcode_assert( buffer ); + netcode_assert( buffer_length >= NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + uint8_t * start = buffer; + + (void) start; + + netcode_write_uint64( &buffer, connect_token->client_id ); + + netcode_write_uint32( &buffer, connect_token->timeout_seconds ); + + netcode_write_uint32( &buffer, connect_token->num_server_addresses ); + + int i,j; + + for ( i = 0; i < connect_token->num_server_addresses; ++i ) + { + // todo: should really have a function to write an address + if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV4 ) + { + netcode_write_uint8( &buffer, NETCODE_ADDRESS_IPV4 ); + for ( j = 0; j < 4; ++j ) + { + netcode_write_uint8( &buffer, connect_token->server_addresses[i].data.ipv4[j] ); + } + netcode_write_uint16( &buffer, connect_token->server_addresses[i].port ); + } + else if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV6 ) + { + netcode_write_uint8( &buffer, NETCODE_ADDRESS_IPV6 ); + for ( j = 0; j < 8; ++j ) + { + netcode_write_uint16( &buffer, connect_token->server_addresses[i].data.ipv6[j] ); + } + netcode_write_uint16( &buffer, connect_token->server_addresses[i].port ); + } + else + { + netcode_assert( 0 ); + } + } + + netcode_write_bytes( &buffer, connect_token->client_to_server_key, NETCODE_KEY_BYTES ); + + netcode_write_bytes( &buffer, connect_token->server_to_client_key, NETCODE_KEY_BYTES ); + + netcode_write_bytes( &buffer, connect_token->user_data, NETCODE_USER_DATA_BYTES ); + + netcode_assert( buffer - start <= NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - NETCODE_MAC_BYTES ); + + memset( buffer, 0, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - ( buffer - start ) ); +} + +int netcode_encrypt_connect_token_private( uint8_t * buffer, + int buffer_length, + uint8_t * version_info, + uint64_t protocol_id, + uint64_t expire_timestamp, + NETCODE_CONST uint8_t * nonce, + NETCODE_CONST uint8_t * key ) +{ + netcode_assert( buffer ); + netcode_assert( buffer_length == NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + netcode_assert( key ); + + (void) buffer_length; + + uint8_t additional_data[NETCODE_VERSION_INFO_BYTES+8+8]; + { + uint8_t * p = additional_data; + netcode_write_bytes( &p, version_info, NETCODE_VERSION_INFO_BYTES ); + netcode_write_uint64( &p, protocol_id ); + netcode_write_uint64( &p, expire_timestamp ); + } + + return netcode_encrypt_aead_bignonce( buffer, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - NETCODE_MAC_BYTES, additional_data, sizeof( additional_data ), nonce, key ); +} + +int netcode_decrypt_connect_token_private( uint8_t * buffer, + int buffer_length, + uint8_t * version_info, + uint64_t protocol_id, + uint64_t expire_timestamp, + uint8_t * nonce, + uint8_t * key ) +{ + netcode_assert( buffer ); + netcode_assert( buffer_length == NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + netcode_assert( key ); + + (void) buffer_length; + + uint8_t additional_data[NETCODE_VERSION_INFO_BYTES+8+8]; + { + uint8_t * p = additional_data; + netcode_write_bytes( &p, version_info, NETCODE_VERSION_INFO_BYTES ); + netcode_write_uint64( &p, protocol_id ); + netcode_write_uint64( &p, expire_timestamp ); + } + return netcode_decrypt_aead_bignonce( buffer, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, additional_data, sizeof( additional_data ), nonce, key ); +} + +int netcode_read_connect_token_private( uint8_t * buffer, int buffer_length, struct netcode_connect_token_private_t * connect_token ) +{ + netcode_assert( buffer ); + netcode_assert( connect_token ); + + if ( buffer_length < NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ) + return NETCODE_ERROR; + + connect_token->client_id = netcode_read_uint64( &buffer ); + + connect_token->timeout_seconds = (int) netcode_read_uint32( &buffer ); + + connect_token->num_server_addresses = netcode_read_uint32( &buffer ); + + if ( connect_token->num_server_addresses <= 0 ) + return NETCODE_ERROR; + + if ( connect_token->num_server_addresses > NETCODE_MAX_SERVERS_PER_CONNECT ) + return NETCODE_ERROR; + + int i,j; + + for ( i = 0; i < connect_token->num_server_addresses; ++i ) + { + // todo: should really have a function to read an address + connect_token->server_addresses[i].type = netcode_read_uint8( &buffer ); + + if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV4 ) + { + for ( j = 0; j < 4; ++j ) + { + connect_token->server_addresses[i].data.ipv4[j] = netcode_read_uint8( &buffer ); + } + connect_token->server_addresses[i].port = netcode_read_uint16( &buffer ); + } + else if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV6 ) + { + for ( j = 0; j < 8; ++j ) + { + connect_token->server_addresses[i].data.ipv6[j] = netcode_read_uint16( &buffer ); + } + connect_token->server_addresses[i].port = netcode_read_uint16( &buffer ); + } + else + { + return NETCODE_ERROR; + } + } + + netcode_read_bytes( &buffer, connect_token->client_to_server_key, NETCODE_KEY_BYTES ); + + netcode_read_bytes( &buffer, connect_token->server_to_client_key, NETCODE_KEY_BYTES ); + + netcode_read_bytes( &buffer, connect_token->user_data, NETCODE_USER_DATA_BYTES ); + + return NETCODE_OK; +} + +// ----------------------------------------------- + +struct netcode_challenge_token_t +{ + uint64_t client_id; + uint8_t user_data[NETCODE_USER_DATA_BYTES]; +}; + +void netcode_write_challenge_token( struct netcode_challenge_token_t * challenge_token, uint8_t * buffer, int buffer_length ) +{ + (void) buffer_length; + + netcode_assert( challenge_token ); + netcode_assert( buffer ); + netcode_assert( buffer_length >= NETCODE_CHALLENGE_TOKEN_BYTES ); + + memset( buffer, 0, NETCODE_CHALLENGE_TOKEN_BYTES ); + + uint8_t * start = buffer; + + (void) start; + + netcode_write_uint64( &buffer, challenge_token->client_id ); + + netcode_write_bytes( &buffer, challenge_token->user_data, NETCODE_USER_DATA_BYTES ); + + netcode_assert( buffer - start <= NETCODE_CHALLENGE_TOKEN_BYTES - NETCODE_MAC_BYTES ); +} + +int netcode_encrypt_challenge_token( uint8_t * buffer, int buffer_length, uint64_t sequence, uint8_t * key ) +{ + netcode_assert( buffer ); + netcode_assert( buffer_length >= NETCODE_CHALLENGE_TOKEN_BYTES ); + netcode_assert( key ); + + (void) buffer_length; + + uint8_t nonce[12]; + { + uint8_t * p = nonce; + netcode_write_uint32( &p, 0 ); + netcode_write_uint64( &p, sequence ); + } + + return netcode_encrypt_aead( buffer, NETCODE_CHALLENGE_TOKEN_BYTES - NETCODE_MAC_BYTES, NULL, 0, nonce, key ); +} + +int netcode_decrypt_challenge_token( uint8_t * buffer, int buffer_length, uint64_t sequence, uint8_t * key ) +{ + netcode_assert( buffer ); + netcode_assert( buffer_length >= NETCODE_CHALLENGE_TOKEN_BYTES ); + netcode_assert( key ); + + (void) buffer_length; + + uint8_t nonce[12]; + { + uint8_t * p = nonce; + netcode_write_uint32( &p, 0 ); + netcode_write_uint64( &p, sequence ); + } + + return netcode_decrypt_aead( buffer, NETCODE_CHALLENGE_TOKEN_BYTES, NULL, 0, nonce, key ); +} + +int netcode_read_challenge_token( uint8_t * buffer, int buffer_length, struct netcode_challenge_token_t * challenge_token ) +{ + netcode_assert( buffer ); + netcode_assert( challenge_token ); + + if ( buffer_length < NETCODE_CHALLENGE_TOKEN_BYTES ) + return NETCODE_ERROR; + + uint8_t * start = buffer; + + (void) start; + + challenge_token->client_id = netcode_read_uint64( &buffer ); + + netcode_read_bytes( &buffer, challenge_token->user_data, NETCODE_USER_DATA_BYTES ); + + netcode_assert( buffer - start == 8 + NETCODE_USER_DATA_BYTES ); + + return NETCODE_OK; +} + +// ---------------------------------------------------------------- + +#define NETCODE_CONNECTION_REQUEST_PACKET 0 +#define NETCODE_CONNECTION_DENIED_PACKET 1 +#define NETCODE_CONNECTION_CHALLENGE_PACKET 2 +#define NETCODE_CONNECTION_RESPONSE_PACKET 3 +#define NETCODE_CONNECTION_KEEP_ALIVE_PACKET 4 +#define NETCODE_CONNECTION_PAYLOAD_PACKET 5 +#define NETCODE_CONNECTION_DISCONNECT_PACKET 6 +#define NETCODE_CONNECTION_NUM_PACKETS 7 + +struct netcode_connection_request_packet_t +{ + uint8_t packet_type; + uint8_t version_info[NETCODE_VERSION_INFO_BYTES]; + uint64_t protocol_id; + uint64_t connect_token_expire_timestamp; + uint8_t connect_token_nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + uint8_t connect_token_data[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; +}; + +struct netcode_connection_denied_packet_t +{ + uint8_t packet_type; +}; + +struct netcode_connection_challenge_packet_t +{ + uint8_t packet_type; + uint64_t challenge_token_sequence; + uint8_t challenge_token_data[NETCODE_CHALLENGE_TOKEN_BYTES]; +}; + +struct netcode_connection_response_packet_t +{ + uint8_t packet_type; + uint64_t challenge_token_sequence; + uint8_t challenge_token_data[NETCODE_CHALLENGE_TOKEN_BYTES]; +}; + +struct netcode_connection_keep_alive_packet_t +{ + uint8_t packet_type; + int client_index; + int max_clients; +}; + +struct netcode_connection_payload_packet_t +{ + uint8_t packet_type; + uint32_t payload_bytes; + uint8_t payload_data[1]; +}; + +struct netcode_connection_disconnect_packet_t +{ + uint8_t packet_type; +}; + +struct netcode_connection_payload_packet_t * netcode_create_payload_packet( int payload_bytes, void * allocator_context, void* (*allocate_function)(void*,uint64_t) ) +{ + netcode_assert( payload_bytes >= 0 ); + netcode_assert( payload_bytes <= NETCODE_MAX_PAYLOAD_BYTES ); + + if ( allocate_function == NULL ) + { + allocate_function = netcode_default_allocate_function; + } + + struct netcode_connection_payload_packet_t * packet = (struct netcode_connection_payload_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_payload_packet_t ) + payload_bytes ); + + if ( !packet ) + return NULL; + + packet->packet_type = NETCODE_CONNECTION_PAYLOAD_PACKET; + packet->payload_bytes = payload_bytes; + + return packet; +} + +struct netcode_context_t +{ + uint8_t write_packet_key[NETCODE_KEY_BYTES]; + uint8_t read_packet_key[NETCODE_KEY_BYTES]; +}; + +int netcode_sequence_number_bytes_required( uint64_t sequence ) +{ + int i; + uint64_t mask = 0xFF00000000000000UL; + for ( i = 0; i < 7; ++i ) + { + if ( sequence & mask ) + break; + mask >>= 8; + } + return 8 - i; +} + +int netcode_write_packet( void * packet, uint8_t * buffer, int buffer_length, uint64_t sequence, uint8_t * write_packet_key, uint64_t protocol_id ) +{ + netcode_assert( packet ); + netcode_assert( buffer ); + netcode_assert( write_packet_key ); + + (void) buffer_length; + + uint8_t packet_type = ((uint8_t*)packet)[0]; + + if ( packet_type == NETCODE_CONNECTION_REQUEST_PACKET ) + { + // connection request packet: first byte is zero + + netcode_assert( buffer_length >= 1 + 13 + 8 + 8 + NETCODE_CONNECT_TOKEN_NONCE_BYTES + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + struct netcode_connection_request_packet_t * p = (struct netcode_connection_request_packet_t*) packet; + + uint8_t * start = buffer; + + netcode_write_uint8( &buffer, NETCODE_CONNECTION_REQUEST_PACKET ); + netcode_write_bytes( &buffer, p->version_info, NETCODE_VERSION_INFO_BYTES ); + netcode_write_uint64( &buffer, p->protocol_id ); + netcode_write_uint64( &buffer, p->connect_token_expire_timestamp ); + netcode_write_bytes( &buffer, p->connect_token_nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + netcode_write_bytes( &buffer, p->connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + netcode_assert( buffer - start == 1 + 13 + 8 + 8 + NETCODE_CONNECT_TOKEN_NONCE_BYTES + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + return (int) ( buffer - start ); + } + else + { + // *** encrypted packets *** + + // write the prefix byte (this is a combination of the packet type and number of sequence bytes) + + uint8_t * start = buffer; + + uint8_t sequence_bytes = (uint8_t) netcode_sequence_number_bytes_required( sequence ); + + netcode_assert( sequence_bytes >= 1 ); + netcode_assert( sequence_bytes <= 8 ); + + netcode_assert( packet_type <= 0xF ); + + uint8_t prefix_byte = packet_type | ( sequence_bytes << 4 ); + + netcode_write_uint8( &buffer, prefix_byte ); + + // write the variable length sequence number [1,8] bytes. + + uint64_t sequence_temp = sequence; + + int i; + for ( i = 0; i < sequence_bytes; ++i ) + { + netcode_write_uint8( &buffer, (uint8_t) ( sequence_temp & 0xFF ) ); + sequence_temp >>= 8; + } + + // write packet data according to type. this data will be encrypted. + + uint8_t * encrypted_start = buffer; + + switch ( packet_type ) + { + case NETCODE_CONNECTION_DENIED_PACKET: + { + // ... + } + break; + + case NETCODE_CONNECTION_CHALLENGE_PACKET: + { + struct netcode_connection_challenge_packet_t * p = (struct netcode_connection_challenge_packet_t*) packet; + netcode_write_uint64( &buffer, p->challenge_token_sequence ); + netcode_write_bytes( &buffer, p->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + } + break; + + case NETCODE_CONNECTION_RESPONSE_PACKET: + { + struct netcode_connection_response_packet_t * p = (struct netcode_connection_response_packet_t*) packet; + netcode_write_uint64( &buffer, p->challenge_token_sequence ); + netcode_write_bytes( &buffer, p->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + } + break; + + case NETCODE_CONNECTION_KEEP_ALIVE_PACKET: + { + struct netcode_connection_keep_alive_packet_t * p = (struct netcode_connection_keep_alive_packet_t*) packet; + netcode_write_uint32( &buffer, p->client_index ); + netcode_write_uint32( &buffer, p->max_clients ); + } + break; + + case NETCODE_CONNECTION_PAYLOAD_PACKET: + { + struct netcode_connection_payload_packet_t * p = (struct netcode_connection_payload_packet_t*) packet; + + netcode_assert( p->payload_bytes <= NETCODE_MAX_PAYLOAD_BYTES ); + + netcode_write_bytes( &buffer, p->payload_data, p->payload_bytes ); + } + break; + + case NETCODE_CONNECTION_DISCONNECT_PACKET: + { + // ... + } + break; + + default: + netcode_assert( 0 ); + } + + netcode_assert( buffer - start <= buffer_length - NETCODE_MAC_BYTES ); + + uint8_t * encrypted_finish = buffer; + + // encrypt the per-packet packet written with the prefix byte, protocol id and version as the associated data. this must match to decrypt. + + uint8_t additional_data[NETCODE_VERSION_INFO_BYTES+8+1]; + { + uint8_t * p = additional_data; + netcode_write_bytes( &p, NETCODE_VERSION_INFO, NETCODE_VERSION_INFO_BYTES ); + netcode_write_uint64( &p, protocol_id ); + netcode_write_uint8( &p, prefix_byte ); + } + + uint8_t nonce[12]; + { + uint8_t * p = nonce; + netcode_write_uint32( &p, 0 ); + netcode_write_uint64( &p, sequence ); + } + + if ( netcode_encrypt_aead( encrypted_start, + encrypted_finish - encrypted_start, + additional_data, sizeof( additional_data ), + nonce, write_packet_key ) != NETCODE_OK ) + { + return NETCODE_ERROR; + } + + buffer += NETCODE_MAC_BYTES; + + netcode_assert( buffer - start <= buffer_length ); + + return (int) ( buffer - start ); + } +} + +struct netcode_replay_protection_t +{ + uint64_t most_recent_sequence; + uint64_t received_packet[NETCODE_REPLAY_PROTECTION_BUFFER_SIZE]; +}; + +void netcode_replay_protection_reset( struct netcode_replay_protection_t * replay_protection ) +{ + netcode_assert( replay_protection ); + replay_protection->most_recent_sequence = 0; + memset( replay_protection->received_packet, 0xFF, sizeof( replay_protection->received_packet ) ); +} + +int netcode_replay_protection_already_received( struct netcode_replay_protection_t * replay_protection, uint64_t sequence ) +{ + netcode_assert( replay_protection ); + + if ( sequence + NETCODE_REPLAY_PROTECTION_BUFFER_SIZE <= replay_protection->most_recent_sequence ) + return 1; + + int index = (int) ( sequence % NETCODE_REPLAY_PROTECTION_BUFFER_SIZE ); + + if ( replay_protection->received_packet[index] == 0xFFFFFFFFFFFFFFFFLL ) + return 0; + + if ( replay_protection->received_packet[index] >= sequence ) + return 1; + + return 0; +} + +void netcode_replay_protection_advance_sequence( struct netcode_replay_protection_t * replay_protection, uint64_t sequence ) +{ + netcode_assert( replay_protection ); + + if ( sequence > replay_protection->most_recent_sequence ) + replay_protection->most_recent_sequence = sequence; + + int index = (int) ( sequence % NETCODE_REPLAY_PROTECTION_BUFFER_SIZE ); + + replay_protection->received_packet[index] = sequence; +} + +void * netcode_read_packet( uint8_t * buffer, + int buffer_length, + uint64_t * sequence, + uint8_t * read_packet_key, + uint64_t protocol_id, + uint64_t current_timestamp, + uint8_t * private_key, + uint8_t * allowed_packets, + struct netcode_replay_protection_t * replay_protection, + void * allocator_context, + void* (*allocate_function)(void*,uint64_t) ) +{ + netcode_assert( sequence ); + netcode_assert( allowed_packets ); + + *sequence = 0; + + if ( allocate_function == NULL ) + { + allocate_function = netcode_default_allocate_function; + } + + if ( buffer_length < 1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored packet. buffer length is less than 1\n" ); + return NULL; + } + + uint8_t * start = buffer; + + uint8_t prefix_byte = netcode_read_uint8( &buffer ); + + if ( prefix_byte == NETCODE_CONNECTION_REQUEST_PACKET ) + { + // connection request packet: first byte is zero + + if ( !allowed_packets[NETCODE_CONNECTION_REQUEST_PACKET] ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. packet type is not allowed\n" ); + return NULL; + } + + if ( buffer_length != 1 + NETCODE_VERSION_INFO_BYTES + 8 + 8 + NETCODE_CONNECT_TOKEN_NONCE_BYTES + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. bad packet length (expected %d, got %d)\n", 1 + NETCODE_VERSION_INFO_BYTES + 8 + 8 + 8 + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, buffer_length ); + return NULL; + } + + if ( !private_key ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. no private key\n" ); + return NULL; + } + + uint8_t version_info[NETCODE_VERSION_INFO_BYTES]; + netcode_read_bytes( &buffer, version_info, NETCODE_VERSION_INFO_BYTES ); + if ( version_info[0] != 'N' || + version_info[1] != 'E' || + version_info[2] != 'T' || + version_info[3] != 'C' || + version_info[4] != 'O' || + version_info[5] != 'D' || + version_info[6] != 'E' || + version_info[7] != ' ' || + version_info[8] != '1' || + version_info[9] != '.' || + version_info[10] != '0' || + version_info[11] != '2' || + version_info[12] != '\0' ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. bad version info\n" ); + return NULL; + } + + uint64_t packet_protocol_id = netcode_read_uint64( &buffer ); + if ( packet_protocol_id != protocol_id ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. wrong protocol id. expected %.16" PRIx64 ", got %.16" PRIx64 "\n", + protocol_id, packet_protocol_id ); + return NULL; + } + + uint64_t packet_connect_token_expire_timestamp = netcode_read_uint64( &buffer ); + if ( packet_connect_token_expire_timestamp <= current_timestamp ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. connect token expired\n" ); + return NULL; + } + + uint8_t packet_connect_token_nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + netcode_read_bytes(&buffer, packet_connect_token_nonce, sizeof(packet_connect_token_nonce)); + + netcode_assert( buffer - start == 1 + NETCODE_VERSION_INFO_BYTES + 8 + 8 + NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + + if ( netcode_decrypt_connect_token_private( buffer, + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, + version_info, + protocol_id, + packet_connect_token_expire_timestamp, + packet_connect_token_nonce, + private_key ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. connect token failed to decrypt\n" ); + return NULL; + } + + struct netcode_connection_request_packet_t * packet = (struct netcode_connection_request_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_request_packet_t ) ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection request packet. failed to allocate packet\n" ); + return NULL; + } + + packet->packet_type = NETCODE_CONNECTION_REQUEST_PACKET; + memcpy( packet->version_info, version_info, NETCODE_VERSION_INFO_BYTES ); + packet->protocol_id = packet_protocol_id; + packet->connect_token_expire_timestamp = packet_connect_token_expire_timestamp; + memcpy( packet->connect_token_nonce, packet_connect_token_nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + netcode_read_bytes( &buffer, packet->connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + netcode_assert( buffer - start == 1 + NETCODE_VERSION_INFO_BYTES + 8 + 8 + NETCODE_CONNECT_TOKEN_NONCE_BYTES + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + return packet; + } + else + { + // *** encrypted packets *** + + if ( !read_packet_key ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. no read packet key for this address\n" ); + return NULL; + } + + if ( buffer_length < 1 + 1 + NETCODE_MAC_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. packet is too small to be valid (%d bytes)\n", buffer_length ); + return NULL; + } + + // extract the packet type and number of sequence bytes from the prefix byte + + int packet_type = prefix_byte & 0xF; + + if ( packet_type >= NETCODE_CONNECTION_NUM_PACKETS ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. packet type %d is invalid\n", packet_type ); + return NULL; + } + + if ( !allowed_packets[packet_type] ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. packet type %d is not allowed\n", packet_type ); + return NULL; + } + + int sequence_bytes = prefix_byte >> 4; + + if ( sequence_bytes < 1 || sequence_bytes > 8 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. sequence bytes %d is out of range [1,8]\n", sequence_bytes ); + return NULL; + } + + if ( buffer_length < 1 + sequence_bytes + NETCODE_MAC_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. buffer is too small for sequence bytes + encryption mac\n" ); + return NULL; + } + + // read variable length sequence number [1,8] + + int i; + for ( i = 0; i < sequence_bytes; ++i ) + { + uint8_t value = netcode_read_uint8( &buffer ); + (*sequence) |= ( uint64_t) ( value ) << ( 8 * i ); + } + + // ignore the packet if it has already been received + + if ( replay_protection && packet_type >= NETCODE_CONNECTION_KEEP_ALIVE_PACKET ) + { + if ( netcode_replay_protection_already_received( replay_protection, *sequence ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored packet. sequence %.16" PRIx64 " already received (replay protection)\n", *sequence ); + return NULL; + } + } + + // decrypt the per-packet type data + + uint8_t additional_data[NETCODE_VERSION_INFO_BYTES+8+1]; + { + uint8_t * p = additional_data; + netcode_write_bytes( &p, NETCODE_VERSION_INFO, NETCODE_VERSION_INFO_BYTES ); + netcode_write_uint64( &p, protocol_id ); + netcode_write_uint8( &p, prefix_byte ); + } + + uint8_t nonce[12]; + { + uint8_t * p = nonce; + netcode_write_uint32( &p, 0 ); + netcode_write_uint64( &p, *sequence ); + } + + int encrypted_bytes = (int) ( buffer_length - ( buffer - start ) ); + + if ( encrypted_bytes < NETCODE_MAC_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. encrypted payload is too small\n" ); + return NULL; + } + + if ( netcode_decrypt_aead( buffer, encrypted_bytes, additional_data, sizeof( additional_data ), nonce, read_packet_key ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored encrypted packet. failed to decrypt\n" ); + return NULL; + } + + int decrypted_bytes = encrypted_bytes - NETCODE_MAC_BYTES; + + // update the latest replay protection sequence # + + if ( replay_protection && packet_type >= NETCODE_CONNECTION_KEEP_ALIVE_PACKET ) + { + netcode_replay_protection_advance_sequence( replay_protection, *sequence ); + } + + // process the per-packet type data that was just decrypted + + switch ( packet_type ) + { + case NETCODE_CONNECTION_DENIED_PACKET: + { + if ( decrypted_bytes != 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection denied packet. decrypted packet data is wrong size\n" ); + return NULL; + } + + struct netcode_connection_denied_packet_t * packet = (struct netcode_connection_denied_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_denied_packet_t ) ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection denied packet. could not allocate packet struct\n" ); + return NULL; + } + + packet->packet_type = NETCODE_CONNECTION_DENIED_PACKET; + + return packet; + } + break; + + case NETCODE_CONNECTION_CHALLENGE_PACKET: + { + if ( decrypted_bytes != 8 + NETCODE_CHALLENGE_TOKEN_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection challenge packet. decrypted packet data is wrong size\n" ); + return NULL; + } + + struct netcode_connection_challenge_packet_t * packet = (struct netcode_connection_challenge_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_challenge_packet_t ) ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection challenge packet. could not allocate packet struct\n" ); + return NULL; + } + + packet->packet_type = NETCODE_CONNECTION_CHALLENGE_PACKET; + packet->challenge_token_sequence = netcode_read_uint64( &buffer ); + netcode_read_bytes( &buffer, packet->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + + return packet; + } + break; + + case NETCODE_CONNECTION_RESPONSE_PACKET: + { + if ( decrypted_bytes != 8 + NETCODE_CHALLENGE_TOKEN_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection response packet. decrypted packet data is wrong size\n" ); + return NULL; + } + + struct netcode_connection_response_packet_t * packet = (struct netcode_connection_response_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_response_packet_t ) ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection response packet. could not allocate packet struct\n" ); + return NULL; + } + + packet->packet_type = NETCODE_CONNECTION_RESPONSE_PACKET; + packet->challenge_token_sequence = netcode_read_uint64( &buffer ); + netcode_read_bytes( &buffer, packet->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + + return packet; + } + break; + + case NETCODE_CONNECTION_KEEP_ALIVE_PACKET: + { + if ( decrypted_bytes != 8 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection keep alive packet. decrypted packet data is wrong size\n" ); + return NULL; + } + + struct netcode_connection_keep_alive_packet_t * packet = (struct netcode_connection_keep_alive_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_keep_alive_packet_t ) ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection keep alive packet. could not allocate packet struct\n" ); + return NULL; + } + + packet->packet_type = NETCODE_CONNECTION_KEEP_ALIVE_PACKET; + packet->client_index = netcode_read_uint32( &buffer ); + packet->max_clients = netcode_read_uint32( &buffer ); + + return packet; + } + break; + + case NETCODE_CONNECTION_PAYLOAD_PACKET: + { + if ( decrypted_bytes < 1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection payload packet. payload is too small\n" ); + return NULL; + } + + if ( decrypted_bytes > NETCODE_MAX_PAYLOAD_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection payload packet. payload is too large\n" ); + return NULL; + } + + struct netcode_connection_payload_packet_t * packet = netcode_create_payload_packet( decrypted_bytes, allocator_context, allocate_function ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection payload packet. could not allocate packet struct\n" ); + return NULL; + } + + memcpy( packet->payload_data, buffer, decrypted_bytes ); + + return packet; + } + break; + + case NETCODE_CONNECTION_DISCONNECT_PACKET: + { + if ( decrypted_bytes != 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection disconnect packet. decrypted packet data is wrong size\n" ); + return NULL; + } + + struct netcode_connection_disconnect_packet_t * packet = (struct netcode_connection_disconnect_packet_t*) + allocate_function( allocator_context, sizeof( struct netcode_connection_disconnect_packet_t ) ); + + if ( !packet ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "ignored connection disconnect packet. could not allocate packet struct\n" ); + return NULL; + } + + packet->packet_type = NETCODE_CONNECTION_DISCONNECT_PACKET; + + return packet; + } + break; + + default: + return NULL; + } + } +} + +// ---------------------------------------------------------------- + +struct netcode_connect_token_t +{ + uint8_t version_info[NETCODE_VERSION_INFO_BYTES]; + uint64_t protocol_id; + uint64_t create_timestamp; + uint64_t expire_timestamp; + uint8_t nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + uint8_t private_data[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; + int timeout_seconds; + int num_server_addresses; + struct netcode_address_t server_addresses[NETCODE_MAX_SERVERS_PER_CONNECT]; + uint8_t client_to_server_key[NETCODE_KEY_BYTES]; + uint8_t server_to_client_key[NETCODE_KEY_BYTES]; +}; + +void netcode_write_connect_token( struct netcode_connect_token_t * connect_token, uint8_t * buffer, int buffer_length ) +{ + netcode_assert( connect_token ); + netcode_assert( buffer ); + netcode_assert( buffer_length >= NETCODE_CONNECT_TOKEN_BYTES ); + + uint8_t * start = buffer; + + (void) start; + (void) buffer_length; + + netcode_write_bytes( &buffer, connect_token->version_info, NETCODE_VERSION_INFO_BYTES ); + + netcode_write_uint64( &buffer, connect_token->protocol_id ); + + netcode_write_uint64( &buffer, connect_token->create_timestamp ); + + netcode_write_uint64( &buffer, connect_token->expire_timestamp ); + + netcode_write_bytes( &buffer, connect_token->nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + + netcode_write_bytes( &buffer, connect_token->private_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + int i,j; + + netcode_write_uint32( &buffer, connect_token->timeout_seconds ); + + netcode_write_uint32( &buffer, connect_token->num_server_addresses ); + + for ( i = 0; i < connect_token->num_server_addresses; ++i ) + { + // todo: really just need a function to write an address. too much cut & paste here + if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV4 ) + { + netcode_write_uint8( &buffer, NETCODE_ADDRESS_IPV4 ); + for ( j = 0; j < 4; ++j ) + { + netcode_write_uint8( &buffer, connect_token->server_addresses[i].data.ipv4[j] ); + } + netcode_write_uint16( &buffer, connect_token->server_addresses[i].port ); + } + else if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV6 ) + { + netcode_write_uint8( &buffer, NETCODE_ADDRESS_IPV6 ); + for ( j = 0; j < 8; ++j ) + { + netcode_write_uint16( &buffer, connect_token->server_addresses[i].data.ipv6[j] ); + } + netcode_write_uint16( &buffer, connect_token->server_addresses[i].port ); + } + else + { + netcode_assert( 0 ); + } + } + + netcode_write_bytes( &buffer, connect_token->client_to_server_key, NETCODE_KEY_BYTES ); + + netcode_write_bytes( &buffer, connect_token->server_to_client_key, NETCODE_KEY_BYTES ); + + netcode_assert( buffer - start <= NETCODE_CONNECT_TOKEN_BYTES ); + + memset( buffer, 0, NETCODE_CONNECT_TOKEN_BYTES - ( buffer - start ) ); +} + +int netcode_read_connect_token( uint8_t * buffer, int buffer_length, struct netcode_connect_token_t * connect_token ) +{ + netcode_assert( buffer ); + netcode_assert( connect_token ); + + if ( buffer_length != NETCODE_CONNECT_TOKEN_BYTES ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: read connect data has bad buffer length (%d)\n", buffer_length ); + return NETCODE_ERROR; + } + + netcode_read_bytes( &buffer, connect_token->version_info, NETCODE_VERSION_INFO_BYTES ); + if ( connect_token->version_info[0] != 'N' || + connect_token->version_info[1] != 'E' || + connect_token->version_info[2] != 'T' || + connect_token->version_info[3] != 'C' || + connect_token->version_info[4] != 'O' || + connect_token->version_info[5] != 'D' || + connect_token->version_info[6] != 'E' || + connect_token->version_info[7] != ' ' || + connect_token->version_info[8] != '1' || + connect_token->version_info[9] != '.' || + connect_token->version_info[10] != '0' || + connect_token->version_info[11] != '2' || + connect_token->version_info[12] != '\0' ) + { + connect_token->version_info[12] = '\0'; + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: read connect data has bad version info (got %s, expected %s)\n", connect_token->version_info, NETCODE_VERSION_INFO ); + return NETCODE_ERROR; + } + + connect_token->protocol_id = netcode_read_uint64( &buffer ); + + connect_token->create_timestamp = netcode_read_uint64( &buffer ); + + connect_token->expire_timestamp = netcode_read_uint64( &buffer ); + + if ( connect_token->create_timestamp > connect_token->expire_timestamp ) + return NETCODE_ERROR; + + netcode_read_bytes( &buffer, connect_token->nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + + netcode_read_bytes( &buffer, connect_token->private_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + connect_token->timeout_seconds = (int) netcode_read_uint32( &buffer ); + + connect_token->num_server_addresses = netcode_read_uint32( &buffer ); + + if ( connect_token->num_server_addresses <= 0 || connect_token->num_server_addresses > NETCODE_MAX_SERVERS_PER_CONNECT ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: read connect data has bad number of server addresses (%d)\n", connect_token->num_server_addresses ); + return NETCODE_ERROR; + } + + int i,j; + + for ( i = 0; i < connect_token->num_server_addresses; ++i ) + { + // todo: really need a function to read an address + connect_token->server_addresses[i].type = netcode_read_uint8( &buffer ); + + if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV4 ) + { + for ( j = 0; j < 4; ++j ) + { + connect_token->server_addresses[i].data.ipv4[j] = netcode_read_uint8( &buffer ); + } + connect_token->server_addresses[i].port = netcode_read_uint16( &buffer ); + } + else if ( connect_token->server_addresses[i].type == NETCODE_ADDRESS_IPV6 ) + { + for ( j = 0; j < 8; ++j ) + { + connect_token->server_addresses[i].data.ipv6[j] = netcode_read_uint16( &buffer ); + } + connect_token->server_addresses[i].port = netcode_read_uint16( &buffer ); + } + else + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: read connect data has bad address type (%d)\n", connect_token->server_addresses[i].type ); + return NETCODE_ERROR; + } + } + + netcode_read_bytes( &buffer, connect_token->client_to_server_key, NETCODE_KEY_BYTES ); + + netcode_read_bytes( &buffer, connect_token->server_to_client_key, NETCODE_KEY_BYTES ); + + return NETCODE_OK; +} + +// ---------------------------------------------------------------- + +struct netcode_packet_queue_t +{ + void * allocator_context; + void * (*allocate_function)(void*,uint64_t); + void (*free_function)(void*,void*); + int num_packets; + int start_index; + void * packet_data[NETCODE_PACKET_QUEUE_SIZE]; + uint64_t packet_sequence[NETCODE_PACKET_QUEUE_SIZE]; +}; + +void netcode_packet_queue_init( struct netcode_packet_queue_t * queue, + void * allocator_context, + void * (*allocate_function)(void*,uint64_t), + void (*free_function)(void*,void*) ) +{ + if ( allocate_function == NULL ) + { + allocate_function = netcode_default_allocate_function; + } + + if ( free_function == NULL ) + { + free_function = netcode_default_free_function; + } + + netcode_assert( queue ); + + queue->allocator_context = allocator_context; + queue->allocate_function = allocate_function; + queue->free_function = free_function; + queue->num_packets = 0; + queue->start_index = 0; + memset( queue->packet_data, 0, sizeof( queue->packet_data ) ); + memset( queue->packet_sequence, 0, sizeof( queue->packet_sequence ) ); +} + +void netcode_packet_queue_clear( struct netcode_packet_queue_t * queue ) +{ + int i; + for ( i = 0; i < queue->num_packets; ++i ) + { + queue->free_function( queue->allocator_context, queue->packet_data[i] ); + } + queue->num_packets = 0; + queue->start_index = 0; + memset( queue->packet_data, 0, sizeof( queue->packet_data ) ); + memset( queue->packet_sequence, 0, sizeof( queue->packet_sequence ) ); +} + +int netcode_packet_queue_push( struct netcode_packet_queue_t * queue, void * packet_data, uint64_t packet_sequence ) +{ + netcode_assert( queue ); + netcode_assert( packet_data ); + if ( queue->num_packets == NETCODE_PACKET_QUEUE_SIZE ) + { + queue->free_function( queue->allocator_context, packet_data ); + return 0; + } + int index = ( queue->start_index + queue->num_packets ) % NETCODE_PACKET_QUEUE_SIZE; + queue->packet_data[index] = packet_data; + queue->packet_sequence[index] = packet_sequence; + queue->num_packets++; + return 1; +} + +void * netcode_packet_queue_pop( struct netcode_packet_queue_t * queue, uint64_t * packet_sequence ) +{ + if ( queue->num_packets == 0 ) + return NULL; + void * packet = queue->packet_data[queue->start_index]; + if ( packet_sequence ) + *packet_sequence = queue->packet_sequence[queue->start_index]; + queue->start_index = ( queue->start_index + 1 ) % NETCODE_PACKET_QUEUE_SIZE; + queue->num_packets--; + return packet; +} + +// ---------------------------------------------------------------- + +#define NETCODE_NETWORK_SIMULATOR_NUM_PACKET_ENTRIES ( NETCODE_MAX_CLIENTS * 256 ) +#define NETCODE_NETWORK_SIMULATOR_NUM_PENDING_RECEIVE_PACKETS ( NETCODE_MAX_CLIENTS * 64 ) + +struct netcode_network_simulator_packet_entry_t +{ + struct netcode_address_t from; + struct netcode_address_t to; + double delivery_time; + uint8_t * packet_data; + int packet_bytes; +}; + +struct netcode_network_simulator_t +{ + void * allocator_context; + void * (*allocate_function)(void*,uint64_t); + void (*free_function)(void*,void*); + float latency_milliseconds; + float jitter_milliseconds; + float packet_loss_percent; + float duplicate_packet_percent; + double time; + int current_index; + int num_pending_receive_packets; + struct netcode_network_simulator_packet_entry_t packet_entries[NETCODE_NETWORK_SIMULATOR_NUM_PACKET_ENTRIES]; + struct netcode_network_simulator_packet_entry_t pending_receive_packets[NETCODE_NETWORK_SIMULATOR_NUM_PENDING_RECEIVE_PACKETS]; +}; + +struct netcode_network_simulator_t * netcode_network_simulator_create( void * allocator_context, + void * (*allocate_function)(void*,uint64_t), + void (*free_function)(void*,void*) ) +{ + if ( allocate_function == NULL ) + { + allocate_function = netcode_default_allocate_function; + } + + if ( free_function == NULL ) + { + free_function = netcode_default_free_function; + } + + struct netcode_network_simulator_t * network_simulator = (struct netcode_network_simulator_t*) + allocate_function( allocator_context, sizeof( struct netcode_network_simulator_t ) ); + + netcode_assert( network_simulator ); + + memset( network_simulator, 0, sizeof( struct netcode_network_simulator_t ) ); + + network_simulator->allocator_context = allocator_context; + network_simulator->allocate_function = allocate_function; + network_simulator->free_function = free_function; + + return network_simulator; +} + +void netcode_network_simulator_reset( struct netcode_network_simulator_t * network_simulator ) +{ + netcode_assert( network_simulator ); + + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "network simulator reset\n" ); + + int i; + for ( i = 0; i < NETCODE_NETWORK_SIMULATOR_NUM_PACKET_ENTRIES; ++i ) + { + network_simulator->free_function( network_simulator->allocator_context, network_simulator->packet_entries[i].packet_data ); + memset( &network_simulator->packet_entries[i], 0, sizeof( struct netcode_network_simulator_packet_entry_t ) ); + } + + for ( i = 0; i < network_simulator->num_pending_receive_packets; ++i ) + { + network_simulator->free_function( network_simulator->allocator_context, network_simulator->pending_receive_packets[i].packet_data ); + memset( &network_simulator->pending_receive_packets[i], 0, sizeof( struct netcode_network_simulator_packet_entry_t ) ); + } + + network_simulator->current_index = 0; + network_simulator->num_pending_receive_packets = 0; +} + +void netcode_network_simulator_destroy( struct netcode_network_simulator_t * network_simulator ) +{ + netcode_assert( network_simulator ); + netcode_network_simulator_reset( network_simulator ); + network_simulator->free_function( network_simulator->allocator_context, network_simulator ); +} + +float netcode_random_float( float a, float b ) +{ + netcode_assert( a < b ); + float random = ( (float) rand() ) / (float) RAND_MAX; + float diff = b - a; + float r = random * diff; + return a + r; +} + +void netcode_network_simulator_queue_packet( struct netcode_network_simulator_t * network_simulator, + struct netcode_address_t * from, + struct netcode_address_t * to, + uint8_t * packet_data, + int packet_bytes, + float delay ) +{ + network_simulator->packet_entries[network_simulator->current_index].from = *from; + network_simulator->packet_entries[network_simulator->current_index].to = *to; + network_simulator->packet_entries[network_simulator->current_index].packet_data = + (uint8_t*) network_simulator->allocate_function( network_simulator->allocator_context, packet_bytes ); + memcpy( network_simulator->packet_entries[network_simulator->current_index].packet_data, packet_data, packet_bytes ); + network_simulator->packet_entries[network_simulator->current_index].packet_bytes = packet_bytes; + network_simulator->packet_entries[network_simulator->current_index].delivery_time = network_simulator->time + delay; + network_simulator->current_index++; + network_simulator->current_index %= NETCODE_NETWORK_SIMULATOR_NUM_PACKET_ENTRIES; +} + +void netcode_network_simulator_send_packet( struct netcode_network_simulator_t * network_simulator, + struct netcode_address_t * from, + struct netcode_address_t * to, + uint8_t * packet_data, + int packet_bytes ) +{ + netcode_assert( network_simulator ); + netcode_assert( from ); + netcode_assert( from->type != 0 ); + netcode_assert( to ); + netcode_assert( to->type != 0 ); + netcode_assert( packet_data ); + netcode_assert( packet_bytes > 0 ); + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_BYTES ); + + if ( netcode_random_float( 0.0f, 100.0f ) <= network_simulator->packet_loss_percent ) + return; + + if ( network_simulator->packet_entries[network_simulator->current_index].packet_data ) + { + network_simulator->free_function( network_simulator->allocator_context, network_simulator->packet_entries[network_simulator->current_index].packet_data ); + network_simulator->packet_entries[network_simulator->current_index].packet_data = NULL; + } + + float delay = network_simulator->latency_milliseconds / 1000.0f; + + if ( network_simulator->jitter_milliseconds > 0.0 ) + delay += netcode_random_float( -network_simulator->jitter_milliseconds, +network_simulator->jitter_milliseconds ) / 1000.0f; + + netcode_network_simulator_queue_packet( network_simulator, from, to, packet_data, packet_bytes, delay ); + + if ( netcode_random_float( 0.0f, 100.0f ) <= network_simulator->duplicate_packet_percent ) + { + netcode_network_simulator_queue_packet( network_simulator, from, to, packet_data, packet_bytes, delay + netcode_random_float( 0, 1.0 ) ); + } +} + +int netcode_network_simulator_receive_packets( struct netcode_network_simulator_t * network_simulator, + struct netcode_address_t * to, + int max_packets, + uint8_t ** packet_data, + int * packet_bytes, + struct netcode_address_t * from ) +{ + netcode_assert( network_simulator ); + netcode_assert( max_packets >= 0 ); + netcode_assert( packet_data ); + netcode_assert( packet_bytes ); + netcode_assert( from ); + netcode_assert( to ); + + int num_packets = 0; + + int i; + for ( i = 0; i < network_simulator->num_pending_receive_packets; ++i ) + { + if ( num_packets == max_packets ) + break; + + if ( !network_simulator->pending_receive_packets[i].packet_data ) + continue; + + if ( !netcode_address_equal( &network_simulator->pending_receive_packets[i].to, to ) ) + continue; + + packet_data[num_packets] = network_simulator->pending_receive_packets[i].packet_data; + packet_bytes[num_packets] = network_simulator->pending_receive_packets[i].packet_bytes; + from[num_packets] = network_simulator->pending_receive_packets[i].from; + + network_simulator->pending_receive_packets[i].packet_data = NULL; + + num_packets++; + } + + netcode_assert( num_packets <= max_packets ); + + return num_packets; +} + +void netcode_network_simulator_update( struct netcode_network_simulator_t * network_simulator, double time ) +{ + netcode_assert( network_simulator ); + + network_simulator->time = time; + + // discard any pending receive packets that are still in the buffer + + int i; + for ( i = 0; i < network_simulator->num_pending_receive_packets; ++i ) + { + if ( network_simulator->pending_receive_packets[i].packet_data ) + { + network_simulator->free_function( network_simulator->allocator_context, network_simulator->pending_receive_packets[i].packet_data ); + network_simulator->pending_receive_packets[i].packet_data = NULL; + } + } + + network_simulator->num_pending_receive_packets = 0; + + // walk across packet entries and move any that are ready to be received into the pending receive buffer + + for ( i = 0; i < NETCODE_NETWORK_SIMULATOR_NUM_PACKET_ENTRIES; ++i ) + { + if ( !network_simulator->packet_entries[i].packet_data ) + continue; + + if ( network_simulator->num_pending_receive_packets == NETCODE_NETWORK_SIMULATOR_NUM_PENDING_RECEIVE_PACKETS ) + break; + + if ( network_simulator->packet_entries[i].packet_data && network_simulator->packet_entries[i].delivery_time <= time ) + { + network_simulator->pending_receive_packets[network_simulator->num_pending_receive_packets] = network_simulator->packet_entries[i]; + network_simulator->num_pending_receive_packets++; + network_simulator->packet_entries[i].packet_data = NULL; + } + } +} + +// ---------------------------------------------------------------- + +NETCODE_CONST char * netcode_client_state_name( int client_state ) +{ + switch ( client_state ) + { + case NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED: return "connect token expired"; + case NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN: return "invalid connect token"; + case NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT: return "connection timed out"; + case NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT: return "connection request timed out"; + case NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT: return "connection response timed out"; + case NETCODE_CLIENT_STATE_CONNECTION_DENIED: return "connection denied"; + case NETCODE_CLIENT_STATE_DISCONNECTED: return "disconnected"; + case NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST: return "sending connection request"; + case NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE: return "sending connection response"; + case NETCODE_CLIENT_STATE_CONNECTED: return "connected"; + default: + netcode_assert( 0 ); + return "???"; + } +} + +void netcode_default_client_config( struct netcode_client_config_t * config ) +{ + netcode_assert( config ); + config->allocator_context = NULL; + config->allocate_function = netcode_default_allocate_function; + config->free_function = netcode_default_free_function; + config->network_simulator = NULL; + config->callback_context = NULL; + config->state_change_callback = NULL; + config->send_loopback_packet_callback = NULL; + config->override_send_and_receive = 0; + config->send_packet_override = NULL; + config->receive_packet_override = NULL; +}; + +struct netcode_client_t +{ + struct netcode_client_config_t config; + int state; + double time; + double connect_start_time; + double last_packet_send_time; + double last_packet_receive_time; + int should_disconnect; + int should_disconnect_state; + uint64_t sequence; + int client_index; + int max_clients; + int server_address_index; + struct netcode_address_t address; + struct netcode_address_t server_address; + struct netcode_connect_token_t connect_token; + struct netcode_socket_holder_t socket_holder; + struct netcode_context_t context; + struct netcode_replay_protection_t replay_protection; + struct netcode_packet_queue_t packet_receive_queue; + uint64_t challenge_token_sequence; + uint8_t challenge_token_data[NETCODE_CHALLENGE_TOKEN_BYTES]; + uint8_t * receive_packet_data[NETCODE_CLIENT_MAX_RECEIVE_PACKETS]; + int receive_packet_bytes[NETCODE_CLIENT_MAX_RECEIVE_PACKETS]; + struct netcode_address_t receive_from[NETCODE_CLIENT_MAX_RECEIVE_PACKETS]; + int loopback; +}; + +int netcode_client_socket_create( struct netcode_socket_t * socket, + struct netcode_address_t * address, + int send_buffer_size, + int receive_buffer_size, + NETCODE_CONST struct netcode_client_config_t * config ) +{ + netcode_assert( socket ); + netcode_assert( address ); + netcode_assert( config ); + + if ( !config->network_simulator ) + { + if ( !config->override_send_and_receive ) + { + if ( netcode_socket_create( socket, address, send_buffer_size, receive_buffer_size ) != NETCODE_SOCKET_ERROR_NONE ) + { + return 0; + } + } + } + else + { + if ( address->port == 0 ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: must bind to a specific port when using network simulator\n" ); + return 0; + } + } + + return 1; +} + +struct netcode_client_t * netcode_client_create_overload( NETCODE_CONST char * address1_string, + NETCODE_CONST char * address2_string, + NETCODE_CONST struct netcode_client_config_t * config, + double time ) +{ + netcode_assert( config ); + netcode_assert( netcode.initialized ); + + struct netcode_address_t address1; + struct netcode_address_t address2; + + memset( &address1, 0, sizeof( address1 ) ); + memset( &address2, 0, sizeof( address2 ) ); + + if ( netcode_parse_address( address1_string, &address1 ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to parse client address\n" ); + return NULL; + } + + if ( address2_string != NULL && netcode_parse_address( address2_string, &address2 ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to parse client address2\n" ); + return NULL; + } + + + struct netcode_socket_t socket_ipv4; + struct netcode_socket_t socket_ipv6; + + memset( &socket_ipv4, 0, sizeof( socket_ipv4 ) ); + memset( &socket_ipv6, 0, sizeof( socket_ipv6 ) ); + + if ( address1.type == NETCODE_ADDRESS_IPV4 || address2.type == NETCODE_ADDRESS_IPV4 ) + { + if ( !netcode_client_socket_create( &socket_ipv4, address1.type == NETCODE_ADDRESS_IPV4 ? &address1 : &address2, NETCODE_CLIENT_SOCKET_SNDBUF_SIZE, NETCODE_CLIENT_SOCKET_RCVBUF_SIZE, config ) ) + { + return NULL; + } + } + + if ( address1.type == NETCODE_ADDRESS_IPV6 || address2.type == NETCODE_ADDRESS_IPV6 ) + { + if ( !netcode_client_socket_create( &socket_ipv6, address1.type == NETCODE_ADDRESS_IPV6 ? &address1 : &address2, NETCODE_CLIENT_SOCKET_SNDBUF_SIZE, NETCODE_CLIENT_SOCKET_RCVBUF_SIZE, config ) ) + { + return NULL; + } + } + + struct netcode_client_t * client = (struct netcode_client_t*) config->allocate_function( config->allocator_context, sizeof( struct netcode_client_t ) ); + + if ( !client ) + { + netcode_socket_destroy( &socket_ipv4 ); + netcode_socket_destroy( &socket_ipv6 ); + return NULL; + } + + struct netcode_address_t socket_address = address1.type == NETCODE_ADDRESS_IPV4 ? socket_ipv4.address : socket_ipv6.address; + + if ( !config->network_simulator ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client started on port %d\n", socket_address.port ); + } + else + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client started on port %d (network simulator)\n", socket_address.port ); + } + + client->config = *config; + client->socket_holder.ipv4 = socket_ipv4; + client->socket_holder.ipv6 = socket_ipv6; + client->address = config->network_simulator ? address1 : socket_address; + client->state = NETCODE_CLIENT_STATE_DISCONNECTED; + client->time = time; + client->connect_start_time = 0.0; + client->last_packet_send_time = -1000.0; + client->last_packet_receive_time = -1000.0; + client->should_disconnect = 0; + client->should_disconnect_state = NETCODE_CLIENT_STATE_DISCONNECTED; + client->sequence = 0; + client->client_index = 0; + client->max_clients = 0; + client->server_address_index = 0; + client->challenge_token_sequence = 0; + client->loopback = 0; + memset( &client->server_address, 0, sizeof( struct netcode_address_t ) ); + memset( &client->connect_token, 0, sizeof( struct netcode_connect_token_t ) ); + memset( &client->context, 0, sizeof( struct netcode_context_t ) ); + memset( client->challenge_token_data, 0, NETCODE_CHALLENGE_TOKEN_BYTES ); + + netcode_packet_queue_init( &client->packet_receive_queue, config->allocator_context, config->allocate_function, config->free_function ); + + netcode_replay_protection_reset( &client->replay_protection ); + + return client; +} + +struct netcode_client_t * netcode_client_create( NETCODE_CONST char * address, + NETCODE_CONST struct netcode_client_config_t * config, + double time ) +{ + return netcode_client_create_overload( address, NULL, config, time ); +} + +void netcode_client_destroy( struct netcode_client_t * client ) +{ + netcode_assert( client ); + if ( !client->loopback ) + netcode_client_disconnect( client ); + else + netcode_client_disconnect_loopback( client ); + netcode_socket_destroy( &client->socket_holder.ipv4 ); + netcode_socket_destroy( &client->socket_holder.ipv6 ); + netcode_packet_queue_clear( &client->packet_receive_queue ); + client->config.free_function( client->config.allocator_context, client ); +} + +void netcode_client_set_state( struct netcode_client_t * client, int client_state ) +{ + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client changed state from '%s' to '%s'\n", + netcode_client_state_name( client->state ), netcode_client_state_name( client_state ) ); + + if ( client->config.state_change_callback ) + { + client->config.state_change_callback( client->config.callback_context, client->state, client_state ); + } + + client->state = client_state; +} + +void netcode_client_reset_before_next_connect( struct netcode_client_t * client ) +{ + client->connect_start_time = client->time; + client->last_packet_send_time = client->time - 1.0f; + client->last_packet_receive_time = client->time; + client->should_disconnect = 0; + client->should_disconnect_state = NETCODE_CLIENT_STATE_DISCONNECTED; + client->challenge_token_sequence = 0; + + memset( client->challenge_token_data, 0, NETCODE_CHALLENGE_TOKEN_BYTES ); + + netcode_replay_protection_reset( &client->replay_protection ); +} + +void netcode_client_reset_connection_data( struct netcode_client_t * client, int client_state ) +{ + netcode_assert( client ); + + client->sequence = 0; + client->loopback = 0; + client->client_index = 0; + client->max_clients = 0; + client->connect_start_time = 0.0; + client->server_address_index = 0; + memset( &client->server_address, 0, sizeof( struct netcode_address_t ) ); + memset( &client->connect_token, 0, sizeof( struct netcode_connect_token_t ) ); + memset( &client->context, 0, sizeof( struct netcode_context_t ) ); + + netcode_client_set_state( client, client_state ); + + netcode_client_reset_before_next_connect( client ); + + while ( 1 ) + { + void * packet = netcode_packet_queue_pop( &client->packet_receive_queue, NULL ); + if ( !packet ) + break; + client->config.free_function( client->config.allocator_context, packet ); + } + + netcode_packet_queue_clear( &client->packet_receive_queue ); +} + +void netcode_client_disconnect_internal( struct netcode_client_t * client, int destination_state, int send_disconnect_packets ); + +void netcode_client_connect( struct netcode_client_t * client, uint8_t * connect_token ) +{ + netcode_assert( client ); + netcode_assert( connect_token ); + + netcode_client_disconnect( client ); + + if ( netcode_read_connect_token( connect_token, NETCODE_CONNECT_TOKEN_BYTES, &client->connect_token ) != NETCODE_OK ) + { + netcode_client_set_state( client, NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN ); + return; + } + + client->server_address_index = 0; + client->server_address = client->connect_token.server_addresses[0]; + + char server_address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connecting to server %s [%d/%d]\n", + netcode_address_to_string( &client->server_address, server_address_string ), client->server_address_index + 1, client->connect_token.num_server_addresses ); + + memcpy( client->context.read_packet_key, client->connect_token.server_to_client_key, NETCODE_KEY_BYTES ); + memcpy( client->context.write_packet_key, client->connect_token.client_to_server_key, NETCODE_KEY_BYTES ); + + netcode_client_reset_before_next_connect( client ); + + netcode_client_set_state( client, NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST ); +} + +void netcode_client_process_packet_internal( struct netcode_client_t * client, struct netcode_address_t * from, uint8_t * packet, uint64_t sequence ) +{ + netcode_assert( client ); + netcode_assert( packet ); + + uint8_t packet_type = ( (uint8_t*) packet ) [0]; + + switch ( packet_type ) + { + case NETCODE_CONNECTION_DENIED_PACKET: + { + if ( ( client->state == NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST || + client->state == NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE ) + && + netcode_address_equal( from, &client->server_address ) ) + { + client->should_disconnect = 1; + client->should_disconnect_state = NETCODE_CLIENT_STATE_CONNECTION_DENIED; + client->last_packet_receive_time = client->time; + } + } + break; + + case NETCODE_CONNECTION_CHALLENGE_PACKET: + { + if ( client->state == NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST && netcode_address_equal( from, &client->server_address ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client received connection challenge packet from server\n" ); + + struct netcode_connection_challenge_packet_t * p = (struct netcode_connection_challenge_packet_t*) packet; + client->challenge_token_sequence = p->challenge_token_sequence; + memcpy( client->challenge_token_data, p->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + client->last_packet_receive_time = client->time; + + netcode_client_set_state( client, NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE ); + } + } + break; + + case NETCODE_CONNECTION_KEEP_ALIVE_PACKET: + { + if ( netcode_address_equal( from, &client->server_address ) ) + { + struct netcode_connection_keep_alive_packet_t * p = (struct netcode_connection_keep_alive_packet_t*) packet; + + if ( client->state == NETCODE_CLIENT_STATE_CONNECTED ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client received connection keep alive packet from server\n" ); + + client->last_packet_receive_time = client->time; + } + else if ( client->state == NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client received connection keep alive packet from server\n" ); + + client->last_packet_receive_time = client->time; + client->client_index = p->client_index; + client->max_clients = p->max_clients; + + netcode_client_set_state( client, NETCODE_CLIENT_STATE_CONNECTED ); + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connected to server\n" ); + } + } + } + break; + + case NETCODE_CONNECTION_PAYLOAD_PACKET: + { + if ( client->state == NETCODE_CLIENT_STATE_CONNECTED && netcode_address_equal( from, &client->server_address ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client received connection payload packet from server\n" ); + + netcode_packet_queue_push( &client->packet_receive_queue, packet, sequence ); + + client->last_packet_receive_time = client->time; + + return; + } + } + break; + + case NETCODE_CONNECTION_DISCONNECT_PACKET: + { + if ( client->state == NETCODE_CLIENT_STATE_CONNECTED && netcode_address_equal( from, &client->server_address ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client received disconnect packet from server\n" ); + + client->should_disconnect = 1; + client->should_disconnect_state = NETCODE_CLIENT_STATE_DISCONNECTED; + client->last_packet_receive_time = client->time; + } + } + break; + + default: + break; + } + + client->config.free_function( client->config.allocator_context, packet ); +} + +void netcode_client_process_packet( struct netcode_client_t * client, struct netcode_address_t * from, uint8_t * packet_data, int packet_bytes ) +{ + (void) client; + (void) from; + (void) packet_data; + (void) packet_bytes; + + uint8_t allowed_packets[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packets, 0, sizeof( allowed_packets ) ); + allowed_packets[NETCODE_CONNECTION_DENIED_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_CHALLENGE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_KEEP_ALIVE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_PAYLOAD_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_DISCONNECT_PACKET] = 1; + + uint64_t current_timestamp = (uint64_t) time( NULL ); + + uint64_t sequence; + + void * packet = netcode_read_packet( packet_data, + packet_bytes, + &sequence, + client->context.read_packet_key, + client->connect_token.protocol_id, + current_timestamp, + NULL, + allowed_packets, + &client->replay_protection, + client->config.allocator_context, + client->config.allocate_function ); + + if ( !packet ) + return; + + netcode_client_process_packet_internal( client, from, (uint8_t*)packet, sequence ); +} + +void netcode_client_receive_packets( struct netcode_client_t * client ) +{ + netcode_assert( client ); + netcode_assert( !client->loopback ); + + uint8_t allowed_packets[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packets, 0, sizeof( allowed_packets ) ); + allowed_packets[NETCODE_CONNECTION_DENIED_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_CHALLENGE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_KEEP_ALIVE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_PAYLOAD_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_DISCONNECT_PACKET] = 1; + + uint64_t current_timestamp = (uint64_t) time( NULL ); + + if ( !client->config.network_simulator ) + { + // process packets received from socket + + while ( 1 ) + { + struct netcode_address_t from; + uint8_t packet_data[NETCODE_MAX_PACKET_BYTES]; + int packet_bytes = 0; + + if ( client->config.override_send_and_receive ) + { + packet_bytes = client->config.receive_packet_override( client->config.callback_context, &from, packet_data, NETCODE_MAX_PACKET_BYTES ); + } + else if ( client->server_address.type == NETCODE_ADDRESS_IPV4 ) + { + packet_bytes = netcode_socket_receive_packet( &client->socket_holder.ipv4, &from, packet_data, NETCODE_MAX_PACKET_BYTES ); + } + else if ( client->server_address.type == NETCODE_ADDRESS_IPV6 ) + { + packet_bytes = netcode_socket_receive_packet( &client->socket_holder.ipv6, &from, packet_data, NETCODE_MAX_PACKET_BYTES ); + } + + if ( packet_bytes == 0 ) + break; + + uint64_t sequence; + void * packet = netcode_read_packet( packet_data, + packet_bytes, + &sequence, + client->context.read_packet_key, + client->connect_token.protocol_id, + current_timestamp, + NULL, + allowed_packets, + &client->replay_protection, + client->config.allocator_context, + client->config.allocate_function ); + + if ( !packet ) + continue; + + netcode_client_process_packet_internal( client, &from, (uint8_t*)packet, sequence ); + } + } + else + { + // process packets received from network simulator + + int num_packets_received = netcode_network_simulator_receive_packets( client->config.network_simulator, + &client->address, + NETCODE_CLIENT_MAX_RECEIVE_PACKETS, + client->receive_packet_data, + client->receive_packet_bytes, + client->receive_from ); + + int i; + for ( i = 0; i < num_packets_received; ++i ) + { + uint64_t sequence; + + void * packet = netcode_read_packet( client->receive_packet_data[i], + client->receive_packet_bytes[i], + &sequence, + client->context.read_packet_key, + client->connect_token.protocol_id, + current_timestamp, + NULL, + allowed_packets, + &client->replay_protection, + client->config.allocator_context, + client->config.allocate_function ); + + client->config.free_function( client->config.allocator_context, client->receive_packet_data[i] ); + + if ( !packet ) + continue; + + netcode_client_process_packet_internal( client, &client->receive_from[i], (uint8_t*)packet, sequence ); + } + } +} + +void netcode_client_send_packet_to_server_internal( struct netcode_client_t * client, void * packet ) +{ + netcode_assert( client ); + netcode_assert( !client->loopback ); + + uint8_t packet_data[NETCODE_MAX_PACKET_BYTES]; + + int packet_bytes = netcode_write_packet( packet, + packet_data, + NETCODE_MAX_PACKET_BYTES, + client->sequence++, + client->context.write_packet_key, + client->connect_token.protocol_id ); + + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_BYTES ); + + if ( client->config.network_simulator ) + { + netcode_network_simulator_send_packet( client->config.network_simulator, &client->address, &client->server_address, packet_data, packet_bytes ); + } + else + { + if ( client->config.override_send_and_receive ) + { + client->config.send_packet_override( client->config.callback_context, &client->server_address, packet_data, packet_bytes ); + } + else if ( client->server_address.type == NETCODE_ADDRESS_IPV4 ) + { + netcode_socket_send_packet( &client->socket_holder.ipv4, &client->server_address, packet_data, packet_bytes ); + } + else if ( client->server_address.type == NETCODE_ADDRESS_IPV6 ) + { + netcode_socket_send_packet( &client->socket_holder.ipv6, &client->server_address, packet_data, packet_bytes ); + } + } + + client->last_packet_send_time = client->time; +} + +void netcode_client_send_packets( struct netcode_client_t * client ) +{ + netcode_assert( client ); + netcode_assert( !client->loopback ); + + switch ( client->state ) + { + case NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST: + { + if ( client->last_packet_send_time + ( 1.0 / NETCODE_PACKET_SEND_RATE ) >= client->time ) + return; + + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client sent connection request packet to server\n" ); + + struct netcode_connection_request_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_REQUEST_PACKET; + memcpy( packet.version_info, NETCODE_VERSION_INFO, NETCODE_VERSION_INFO_BYTES ); + packet.protocol_id = client->connect_token.protocol_id; + packet.connect_token_expire_timestamp = client->connect_token.expire_timestamp; + memcpy( packet.connect_token_nonce, client->connect_token.nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + memcpy( packet.connect_token_data, client->connect_token.private_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + netcode_client_send_packet_to_server_internal( client, &packet ); + } + break; + + case NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE: + { + if ( client->last_packet_send_time + ( 1.0 / NETCODE_PACKET_SEND_RATE ) >= client->time ) + return; + + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client sent connection response packet to server\n" ); + + struct netcode_connection_response_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_RESPONSE_PACKET; + packet.challenge_token_sequence = client->challenge_token_sequence; + memcpy( packet.challenge_token_data, client->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + + netcode_client_send_packet_to_server_internal( client, &packet ); + } + break; + + case NETCODE_CLIENT_STATE_CONNECTED: + { + if ( client->last_packet_send_time + ( 1.0 / NETCODE_PACKET_SEND_RATE ) >= client->time ) + return; + + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client sent connection keep-alive packet to server\n" ); + + struct netcode_connection_keep_alive_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_KEEP_ALIVE_PACKET; + packet.client_index = 0; + packet.max_clients = 0; + + netcode_client_send_packet_to_server_internal( client, &packet ); + } + break; + + default: + break; + } +} + +int netcode_client_connect_to_next_server( struct netcode_client_t * client ) +{ + netcode_assert( client ); + + if ( client->server_address_index + 1 >= client->connect_token.num_server_addresses ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client has no more servers to connect to\n" ); + return 0; + } + + client->server_address_index++; + client->server_address = client->connect_token.server_addresses[client->server_address_index]; + + netcode_client_reset_before_next_connect( client ); + + char server_address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connecting to next server %s [%d/%d]\n", + netcode_address_to_string( &client->server_address, server_address_string ), + client->server_address_index + 1, + client->connect_token.num_server_addresses ); + + netcode_client_set_state( client, NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST ); + + return 1; +} + +void netcode_client_update( struct netcode_client_t * client, double time ) +{ + netcode_assert( client ); + + client->time = time; + + if ( client->loopback ) + return; + + netcode_client_receive_packets( client ); + + netcode_client_send_packets( client ); + + if ( client->state > NETCODE_CLIENT_STATE_DISCONNECTED && client->state < NETCODE_CLIENT_STATE_CONNECTED ) + { + uint64_t connect_token_expire_seconds = ( client->connect_token.expire_timestamp - client->connect_token.create_timestamp ); + if ( client->time - client->connect_start_time >= connect_token_expire_seconds ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connect failed. connect token expired\n" ); + netcode_client_disconnect_internal( client, NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED, 0 ); + return; + } + } + + if ( client->should_disconnect ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client should disconnect -> %s\n", netcode_client_state_name( client->should_disconnect_state ) ); + if ( netcode_client_connect_to_next_server( client ) ) + return; + netcode_client_disconnect_internal( client, client->should_disconnect_state, 0 ); + return; + } + + switch ( client->state ) + { + case NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST: + { + if ( client->connect_token.timeout_seconds > 0 && client->last_packet_receive_time + client->connect_token.timeout_seconds < time ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connect failed. connection request timed out\n" ); + if ( netcode_client_connect_to_next_server( client ) ) + return; + netcode_client_disconnect_internal( client, NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT, 0 ); + return; + } + } + break; + + case NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE: + { + if ( client->connect_token.timeout_seconds > 0 && client->last_packet_receive_time + client->connect_token.timeout_seconds < time ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connect failed. connection response timed out\n" ); + if ( netcode_client_connect_to_next_server( client ) ) + return; + netcode_client_disconnect_internal( client, NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT, 0 ); + return; + } + } + break; + + case NETCODE_CLIENT_STATE_CONNECTED: + { + if ( client->connect_token.timeout_seconds > 0 && client->last_packet_receive_time + client->connect_token.timeout_seconds < time ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connection timed out\n" ); + netcode_client_disconnect_internal( client, NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT, 0 ); + return; + } + } + break; + + default: + break; + } +} + +uint64_t netcode_client_next_packet_sequence( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return client->sequence; +} + +void netcode_client_send_packet( struct netcode_client_t * client, NETCODE_CONST uint8_t * packet_data, int packet_bytes ) +{ + netcode_assert( client ); + netcode_assert( packet_data ); + netcode_assert( packet_bytes >= 0 ); + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_SIZE ); + + if ( client->state != NETCODE_CLIENT_STATE_CONNECTED ) + return; + + if ( !client->loopback ) + { + uint8_t buffer[NETCODE_MAX_PAYLOAD_BYTES*2]; + + struct netcode_connection_payload_packet_t * packet = (struct netcode_connection_payload_packet_t*) buffer; + + packet->packet_type = NETCODE_CONNECTION_PAYLOAD_PACKET; + packet->payload_bytes = packet_bytes; + memcpy( packet->payload_data, packet_data, packet_bytes ); + + netcode_client_send_packet_to_server_internal( client, packet ); + } + else + { + client->config.send_loopback_packet_callback( client->config.callback_context, + client->client_index, + packet_data, + packet_bytes, + client->sequence++ ); + } +} + +uint8_t * netcode_client_receive_packet( struct netcode_client_t * client, int * packet_bytes, uint64_t * packet_sequence ) +{ + netcode_assert( client ); + netcode_assert( packet_bytes ); + + struct netcode_connection_payload_packet_t * packet = (struct netcode_connection_payload_packet_t*) + netcode_packet_queue_pop( &client->packet_receive_queue, packet_sequence ); + + if ( packet ) + { + netcode_assert( packet->packet_type == NETCODE_CONNECTION_PAYLOAD_PACKET ); + *packet_bytes = packet->payload_bytes; + netcode_assert( *packet_bytes >= 0 ); + netcode_assert( *packet_bytes <= NETCODE_MAX_PAYLOAD_BYTES ); + return (uint8_t*) &packet->payload_data; + } + else + { + return NULL; + } +} + +void netcode_client_free_packet( struct netcode_client_t * client, void * packet ) +{ + netcode_assert( client ); + netcode_assert( packet ); + uint8_t * packet_data = (uint8_t*) packet; + int offset = offsetof( struct netcode_connection_payload_packet_t, payload_data ); + client->config.free_function( client->config.allocator_context, packet_data - offset ); +} + +void netcode_client_disconnect( struct netcode_client_t * client ) +{ + netcode_assert( client ); + netcode_assert( !client->loopback ); + netcode_client_disconnect_internal( client, NETCODE_CLIENT_STATE_DISCONNECTED, 1 ); +} + +void netcode_client_disconnect_internal( struct netcode_client_t * client, int destination_state, int send_disconnect_packets ) +{ + netcode_assert( !client->loopback ); + netcode_assert( destination_state <= NETCODE_CLIENT_STATE_DISCONNECTED ); + + if ( client->state <= NETCODE_CLIENT_STATE_DISCONNECTED || client->state == destination_state ) + return; + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client disconnected\n" ); + + if ( !client->loopback && send_disconnect_packets && client->state > NETCODE_CLIENT_STATE_DISCONNECTED ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client sent disconnect packets to server\n" ); + + int i; + for ( i = 0; i < NETCODE_NUM_DISCONNECT_PACKETS; ++i ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client sent disconnect packet %d\n", i ); + + struct netcode_connection_disconnect_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_DISCONNECT_PACKET; + + netcode_client_send_packet_to_server_internal( client, &packet ); + } + } + + netcode_client_reset_connection_data( client, destination_state ); +} + +int netcode_client_state( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return client->state; +} + +int netcode_client_index( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return client->client_index; +} + +int netcode_client_max_clients( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return client->max_clients; +} + +void netcode_client_connect_loopback( struct netcode_client_t * client, int client_index, int max_clients ) +{ + netcode_assert( client ); + netcode_assert( client->state <= NETCODE_CLIENT_STATE_DISCONNECTED ); + netcode_printf( NETCODE_LOG_LEVEL_INFO, "client connected to server via loopback as client %d\n", client_index ); + client->state = NETCODE_CLIENT_STATE_CONNECTED; + client->client_index = client_index; + client->max_clients = max_clients; + client->loopback = 1; +} + +void netcode_client_disconnect_loopback( struct netcode_client_t * client ) +{ + netcode_assert( client ); + netcode_assert( client->loopback ); + netcode_client_reset_connection_data( client, NETCODE_CLIENT_STATE_DISCONNECTED ); +} + +int netcode_client_loopback( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return client->loopback; +} + +void netcode_client_process_loopback_packet( struct netcode_client_t * client, NETCODE_CONST uint8_t * packet_data, int packet_bytes, uint64_t packet_sequence ) +{ + netcode_assert( client ); + netcode_assert( client->loopback ); + struct netcode_connection_payload_packet_t * packet = netcode_create_payload_packet( packet_bytes, client->config.allocator_context, client->config.allocate_function ); + if ( !packet ) + return; + memcpy( packet->payload_data, packet_data, packet_bytes ); + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "client processing loopback packet from server\n" ); + netcode_packet_queue_push( &client->packet_receive_queue, packet, packet_sequence ); +} + +uint16_t netcode_client_get_port( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return client->address.type == NETCODE_ADDRESS_IPV4 ? client->socket_holder.ipv4.address.port : client->socket_holder.ipv6.address.port; +} + +struct netcode_address_t * netcode_client_server_address( struct netcode_client_t * client ) +{ + netcode_assert( client ); + return &client->server_address; +} + +// ---------------------------------------------------------------- + +#define NETCODE_MAX_ENCRYPTION_MAPPINGS ( NETCODE_MAX_CLIENTS * 4 ) + +struct netcode_encryption_manager_t +{ + int num_encryption_mappings; + int timeout[NETCODE_MAX_ENCRYPTION_MAPPINGS]; + double expire_time[NETCODE_MAX_ENCRYPTION_MAPPINGS]; + double last_access_time[NETCODE_MAX_ENCRYPTION_MAPPINGS]; + struct netcode_address_t address[NETCODE_MAX_ENCRYPTION_MAPPINGS]; + uint8_t send_key[NETCODE_KEY_BYTES*NETCODE_MAX_ENCRYPTION_MAPPINGS]; + uint8_t receive_key[NETCODE_KEY_BYTES*NETCODE_MAX_ENCRYPTION_MAPPINGS]; +}; + +void netcode_encryption_manager_reset( struct netcode_encryption_manager_t * encryption_manager ) +{ + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "reset encryption manager\n" ); + + netcode_assert( encryption_manager ); + + encryption_manager->num_encryption_mappings = 0; + + int i; + for ( i = 0; i < NETCODE_MAX_ENCRYPTION_MAPPINGS; ++i ) + { + encryption_manager->expire_time[i] = -1.0; + encryption_manager->last_access_time[i] = -1000.0; + memset( &encryption_manager->address[i], 0, sizeof( struct netcode_address_t ) ); + } + + memset( encryption_manager->timeout, 0, sizeof( encryption_manager->timeout ) ); + memset( encryption_manager->send_key, 0, sizeof( encryption_manager->send_key ) ); + memset( encryption_manager->receive_key, 0, sizeof( encryption_manager->receive_key ) ); +} + +int netcode_encryption_manager_entry_expired( struct netcode_encryption_manager_t * encryption_manager, int index, double time ) +{ + return ( encryption_manager->timeout[index] > 0 && ( encryption_manager->last_access_time[index] + encryption_manager->timeout[index] ) < time ) || + ( encryption_manager->expire_time[index] >= 0.0 && encryption_manager->expire_time[index] < time ); +} + +int netcode_encryption_manager_add_encryption_mapping( struct netcode_encryption_manager_t * encryption_manager, + struct netcode_address_t * address, + uint8_t * send_key, + uint8_t * receive_key, + double time, + double expire_time, + int timeout ) +{ + int i; + for ( i = 0; i < encryption_manager->num_encryption_mappings; ++i ) + { + if ( netcode_address_equal( &encryption_manager->address[i], address ) && !netcode_encryption_manager_entry_expired( encryption_manager, i, time ) ) + { + encryption_manager->timeout[i] = timeout; + encryption_manager->expire_time[i] = expire_time; + encryption_manager->last_access_time[i] = time; + memcpy( encryption_manager->send_key + i * NETCODE_KEY_BYTES, send_key, NETCODE_KEY_BYTES ); + memcpy( encryption_manager->receive_key + i * NETCODE_KEY_BYTES, receive_key, NETCODE_KEY_BYTES ); + return 1; + } + } + + for ( i = 0; i < NETCODE_MAX_ENCRYPTION_MAPPINGS; ++i ) + { + if ( encryption_manager->address[i].type == NETCODE_ADDRESS_NONE || netcode_encryption_manager_entry_expired( encryption_manager, i, time ) ) + { + encryption_manager->timeout[i] = timeout; + encryption_manager->address[i] = *address; + encryption_manager->expire_time[i] = expire_time; + encryption_manager->last_access_time[i] = time; + memcpy( encryption_manager->send_key + i * NETCODE_KEY_BYTES, send_key, NETCODE_KEY_BYTES ); + memcpy( encryption_manager->receive_key + i * NETCODE_KEY_BYTES, receive_key, NETCODE_KEY_BYTES ); + if ( i + 1 > encryption_manager->num_encryption_mappings ) + encryption_manager->num_encryption_mappings = i + 1; + return 1; + } + } + + return 0; +} + +int netcode_encryption_manager_remove_encryption_mapping( struct netcode_encryption_manager_t * encryption_manager, struct netcode_address_t * address, double time ) +{ + netcode_assert( encryption_manager ); + netcode_assert( address ); + + int i; + for ( i = 0; i < encryption_manager->num_encryption_mappings; ++i ) + { + if ( netcode_address_equal( &encryption_manager->address[i], address ) ) + { + encryption_manager->expire_time[i] = -1.0; + encryption_manager->last_access_time[i] = -1000.0; + memset( &encryption_manager->address[i], 0, sizeof( struct netcode_address_t ) ); + memset( encryption_manager->send_key + i * NETCODE_KEY_BYTES, 0, NETCODE_KEY_BYTES ); + memset( encryption_manager->receive_key + i * NETCODE_KEY_BYTES, 0, NETCODE_KEY_BYTES ); + + if ( i + 1 == encryption_manager->num_encryption_mappings ) + { + int index = i - 1; + while ( index >= 0 ) + { + if ( !netcode_encryption_manager_entry_expired( encryption_manager, index, time ) ) + { + break; + } + encryption_manager->address[index].type = NETCODE_ADDRESS_NONE; + index--; + } + encryption_manager->num_encryption_mappings = index + 1; + } + + return 1; + } + } + + return 0; +} + +int netcode_encryption_manager_find_encryption_mapping( struct netcode_encryption_manager_t * encryption_manager, struct netcode_address_t * address, double time ) +{ + int i; + for ( i = 0; i < encryption_manager->num_encryption_mappings; ++i ) + { + if ( netcode_address_equal( &encryption_manager->address[i], address ) && !netcode_encryption_manager_entry_expired( encryption_manager, i, time ) ) + { + encryption_manager->last_access_time[i] = time; + return i; + } + } + return -1; +} + +int netcode_encryption_manager_touch( struct netcode_encryption_manager_t * encryption_manager, int index, struct netcode_address_t * address, double time ) +{ + netcode_assert( index >= 0 ); + netcode_assert( index < encryption_manager->num_encryption_mappings ); + if ( !netcode_address_equal( &encryption_manager->address[index], address ) ) + return 0; + encryption_manager->last_access_time[index] = time; + return 1; +} + +void netcode_encryption_manager_set_expire_time( struct netcode_encryption_manager_t * encryption_manager, int index, double expire_time ) +{ + netcode_assert( index >= 0 ); + netcode_assert( index < encryption_manager->num_encryption_mappings ); + encryption_manager->expire_time[index] = expire_time; +} + + +uint8_t * netcode_encryption_manager_get_send_key( struct netcode_encryption_manager_t * encryption_manager, int index ) +{ + netcode_assert( encryption_manager ); + if ( index == -1 ) + return NULL; + netcode_assert( index >= 0 ); + netcode_assert( index < encryption_manager->num_encryption_mappings ); + return encryption_manager->send_key + index * NETCODE_KEY_BYTES; +} + +uint8_t * netcode_encryption_manager_get_receive_key( struct netcode_encryption_manager_t * encryption_manager, int index ) +{ + netcode_assert( encryption_manager ); + if ( index == -1 ) + return NULL; + netcode_assert( index >= 0 ); + netcode_assert( index < encryption_manager->num_encryption_mappings ); + return encryption_manager->receive_key + index * NETCODE_KEY_BYTES; +} + +int netcode_encryption_manager_get_timeout( struct netcode_encryption_manager_t * encryption_manager, int index ) +{ + netcode_assert( encryption_manager ); + if ( index == -1 ) + return 0; + netcode_assert( index >= 0 ); + netcode_assert( index < encryption_manager->num_encryption_mappings ); + return encryption_manager->timeout[index]; +} + +// ---------------------------------------------------------------- + +#define NETCODE_MAX_CONNECT_TOKEN_ENTRIES ( NETCODE_MAX_CLIENTS * 8 ) + +struct netcode_connect_token_entry_t +{ + double time; + uint8_t mac[NETCODE_MAC_BYTES]; + struct netcode_address_t address; +}; + +void netcode_connect_token_entries_reset( struct netcode_connect_token_entry_t * connect_token_entries ) +{ + int i; + for ( i = 0; i < NETCODE_MAX_CONNECT_TOKEN_ENTRIES; ++i ) + { + connect_token_entries[i].time = -1000.0; + memset( connect_token_entries[i].mac, 0, NETCODE_MAC_BYTES ); + memset( &connect_token_entries[i].address, 0, sizeof( struct netcode_address_t ) ); + } +} + +int netcode_connect_token_entries_find_or_add( struct netcode_connect_token_entry_t * connect_token_entries, + struct netcode_address_t * address, + uint8_t * mac, + double time ) +{ + netcode_assert( connect_token_entries ); + netcode_assert( address ); + netcode_assert( mac ); + + // find the matching entry for the token mac and the oldest token entry. constant time worst case. This is intentional! + + int matching_token_index = -1; + int oldest_token_index = -1; + double oldest_token_time = 0.0; + + int i; + for ( i = 0; i < NETCODE_MAX_CONNECT_TOKEN_ENTRIES; ++i ) + { + if ( memcmp( mac, connect_token_entries[i].mac, NETCODE_MAC_BYTES ) == 0 ) + matching_token_index = i; + + if ( oldest_token_index == -1 || connect_token_entries[i].time < oldest_token_time ) + { + oldest_token_time = connect_token_entries[i].time; + oldest_token_index = i; + } + } + + // if no entry is found with the mac, this is a new connect token. replace the oldest token entry. + + netcode_assert( oldest_token_index != -1 ); + + if ( matching_token_index == -1 ) + { + connect_token_entries[oldest_token_index].time = time; + connect_token_entries[oldest_token_index].address = *address; + memcpy( connect_token_entries[oldest_token_index].mac, mac, NETCODE_MAC_BYTES ); + return 1; + } + + // allow connect tokens we have already seen from the same address + + netcode_assert( matching_token_index >= 0 ); + netcode_assert( matching_token_index < NETCODE_MAX_CONNECT_TOKEN_ENTRIES ); + if ( netcode_address_equal( &connect_token_entries[matching_token_index].address, address ) ) + return 1; + + return 0; +} + +// ---------------------------------------------------------------- + +#define NETCODE_SERVER_FLAG_IGNORE_CONNECTION_REQUEST_PACKETS 1 +#define NETCODE_SERVER_FLAG_IGNORE_CONNECTION_RESPONSE_PACKETS (1<<1) + +void netcode_default_server_config( struct netcode_server_config_t * config ) +{ + netcode_assert( config ); + config->allocator_context = NULL; + config->allocate_function = netcode_default_allocate_function; + config->free_function = netcode_default_free_function; + config->network_simulator = NULL; + config->callback_context = NULL; + config->connect_disconnect_callback = NULL; + config->send_loopback_packet_callback = NULL; + config->override_send_and_receive = 0; + config->send_packet_override = NULL; + config->receive_packet_override = NULL; +}; + +struct netcode_server_t +{ + struct netcode_server_config_t config; + struct netcode_socket_holder_t socket_holder; + struct netcode_address_t address; + uint32_t flags; + double time; + int running; + int max_clients; + int num_connected_clients; + uint64_t global_sequence; + uint64_t challenge_sequence; + uint8_t challenge_key[NETCODE_KEY_BYTES]; + int client_connected[NETCODE_MAX_CLIENTS]; + int client_timeout[NETCODE_MAX_CLIENTS]; + int client_loopback[NETCODE_MAX_CLIENTS]; + int client_confirmed[NETCODE_MAX_CLIENTS]; + int client_encryption_index[NETCODE_MAX_CLIENTS]; + uint64_t client_id[NETCODE_MAX_CLIENTS]; + uint64_t client_sequence[NETCODE_MAX_CLIENTS]; + double client_last_packet_send_time[NETCODE_MAX_CLIENTS]; + double client_last_packet_receive_time[NETCODE_MAX_CLIENTS]; + uint8_t client_user_data[NETCODE_MAX_CLIENTS][NETCODE_USER_DATA_BYTES]; + struct netcode_replay_protection_t client_replay_protection[NETCODE_MAX_CLIENTS]; + struct netcode_packet_queue_t client_packet_queue[NETCODE_MAX_CLIENTS]; + struct netcode_address_t client_address[NETCODE_MAX_CLIENTS]; + struct netcode_connect_token_entry_t connect_token_entries[NETCODE_MAX_CONNECT_TOKEN_ENTRIES]; + struct netcode_encryption_manager_t encryption_manager; + uint8_t * receive_packet_data[NETCODE_SERVER_MAX_RECEIVE_PACKETS]; + int receive_packet_bytes[NETCODE_SERVER_MAX_RECEIVE_PACKETS]; + struct netcode_address_t receive_from[NETCODE_SERVER_MAX_RECEIVE_PACKETS]; +}; + +int netcode_server_socket_create( struct netcode_socket_t * socket, + struct netcode_address_t * address, + int send_buffer_size, + int receive_buffer_size, + NETCODE_CONST struct netcode_server_config_t * config ) +{ + netcode_assert( socket ); + netcode_assert( address ); + netcode_assert( config ); + + if ( !config->network_simulator ) + { + if ( !config->override_send_and_receive ) + { + if ( netcode_socket_create( socket, address, send_buffer_size, receive_buffer_size ) != NETCODE_SOCKET_ERROR_NONE ) + { + return 0; + } + } + } + + return 1; +} + +struct netcode_server_t * netcode_server_create_overload( NETCODE_CONST char * server_address1_string, NETCODE_CONST char * server_address2_string, NETCODE_CONST struct netcode_server_config_t * config, double time ) +{ + netcode_assert( config ); + netcode_assert( netcode.initialized ); + + struct netcode_address_t server_address1; + struct netcode_address_t server_address2; + + memset( &server_address1, 0, sizeof( server_address1 ) ); + memset( &server_address2, 0, sizeof( server_address2 ) ); + + if ( netcode_parse_address( server_address1_string, &server_address1 ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to parse server public address\n" ); + return NULL; + } + + if ( server_address2_string != NULL && netcode_parse_address( server_address2_string, &server_address2 ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: failed to parse server public address2\n" ); + return NULL; + } + + struct netcode_address_t bind_address_ipv4; + struct netcode_address_t bind_address_ipv6; + + memset( &bind_address_ipv4, 0, sizeof( bind_address_ipv4 ) ); + memset( &bind_address_ipv6, 0, sizeof( bind_address_ipv6 ) ); + + struct netcode_socket_t socket_ipv4; + struct netcode_socket_t socket_ipv6; + + memset( &socket_ipv4, 0, sizeof( socket_ipv4 ) ); + memset( &socket_ipv6, 0, sizeof( socket_ipv6 ) ); + + if ( server_address1.type == NETCODE_ADDRESS_IPV4 || server_address2.type == NETCODE_ADDRESS_IPV4 ) + { + bind_address_ipv4.type = NETCODE_ADDRESS_IPV4; + bind_address_ipv4.port = server_address1.type == NETCODE_ADDRESS_IPV4 ? server_address1.port : server_address2.port; + + if ( !netcode_server_socket_create( &socket_ipv4, &bind_address_ipv4, NETCODE_SERVER_SOCKET_SNDBUF_SIZE, NETCODE_SERVER_SOCKET_RCVBUF_SIZE, config ) ) + { + return NULL; + } + } + + if ( server_address1.type == NETCODE_ADDRESS_IPV6 || server_address2.type == NETCODE_ADDRESS_IPV6 ) + { + bind_address_ipv6.type = NETCODE_ADDRESS_IPV6; + bind_address_ipv6.port = server_address1.type == NETCODE_ADDRESS_IPV6 ? server_address1.port : server_address2.port; + + if ( !netcode_server_socket_create( &socket_ipv6, &bind_address_ipv6, NETCODE_SERVER_SOCKET_SNDBUF_SIZE, NETCODE_SERVER_SOCKET_RCVBUF_SIZE, config ) ) + { + return NULL; + } + } + + struct netcode_server_t * server = (struct netcode_server_t*) config->allocate_function( config->allocator_context, sizeof( struct netcode_server_t ) ); + if ( !server ) + { + netcode_socket_destroy( &socket_ipv4 ); + netcode_socket_destroy( &socket_ipv6 ); + return NULL; + } + + if ( !config->network_simulator ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server listening on %s\n", server_address1_string ); + } + else + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server listening on %s (network simulator)\n", server_address1_string ); + } + + server->config = *config; + server->socket_holder.ipv4 = socket_ipv4; + server->socket_holder.ipv6 = socket_ipv6; + server->address = server_address1.type == NETCODE_ADDRESS_IPV4 ? server->socket_holder.ipv4.address : server->socket_holder.ipv6.address; + server->flags = 0; + server->time = time; + server->running = 0; + server->max_clients = 0; + server->num_connected_clients = 0; + server->global_sequence = 1ULL << 63; + + memset( server->client_connected, 0, sizeof( server->client_connected ) ); + memset( server->client_loopback, 0, sizeof( server->client_loopback ) ); + memset( server->client_confirmed, 0, sizeof( server->client_confirmed ) ); + memset( server->client_id, 0, sizeof( server->client_id ) ); + memset( server->client_sequence, 0, sizeof( server->client_sequence ) ); + memset( server->client_last_packet_send_time, 0, sizeof( server->client_last_packet_send_time ) ); + memset( server->client_last_packet_receive_time, 0, sizeof( server->client_last_packet_receive_time ) ); + memset( server->client_address, 0, sizeof( server->client_address ) ); + memset( server->client_user_data, 0, sizeof( server->client_user_data ) ); + + int i; + for ( i = 0; i < NETCODE_MAX_CLIENTS; ++i ) + server->client_encryption_index[i] = -1; + + netcode_connect_token_entries_reset( server->connect_token_entries ); + + netcode_encryption_manager_reset( &server->encryption_manager ); + + for ( i = 0; i < NETCODE_MAX_CLIENTS; ++i ) + netcode_replay_protection_reset( &server->client_replay_protection[i] ); + + memset( &server->client_packet_queue, 0, sizeof( server->client_packet_queue ) ); + + return server; +} + +struct netcode_server_t * netcode_server_create( NETCODE_CONST char * server_address_string, NETCODE_CONST struct netcode_server_config_t * config, double time ) +{ + return netcode_server_create_overload( server_address_string, NULL, config, time ); +} + +void netcode_server_stop( struct netcode_server_t * server ); + +void netcode_server_destroy( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + netcode_server_stop( server ); + + netcode_socket_destroy( &server->socket_holder.ipv4 ); + netcode_socket_destroy( &server->socket_holder.ipv6 ); + + server->config.free_function( server->config.allocator_context, server ); +} + +void netcode_server_start( struct netcode_server_t * server, int max_clients ) +{ + netcode_assert( server ); + netcode_assert( max_clients > 0 ); + netcode_assert( max_clients <= NETCODE_MAX_CLIENTS ); + + if ( server->running ) + netcode_server_stop( server ); + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server started with %d client slots\n", max_clients ); + + server->running = 1; + server->max_clients = max_clients; + server->num_connected_clients = 0; + server->challenge_sequence = 0; + netcode_generate_key( server->challenge_key ); + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + netcode_packet_queue_init( &server->client_packet_queue[i], server->config.allocator_context, server->config.allocate_function, server->config.free_function ); + } +} + +void netcode_server_send_global_packet( struct netcode_server_t * server, void * packet, struct netcode_address_t * to, uint8_t * packet_key ) +{ + netcode_assert( server ); + netcode_assert( packet ); + netcode_assert( to ); + netcode_assert( packet_key ); + + uint8_t packet_data[NETCODE_MAX_PACKET_BYTES]; + + int packet_bytes = netcode_write_packet( packet, packet_data, NETCODE_MAX_PACKET_BYTES, server->global_sequence, packet_key, server->config.protocol_id ); + + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_BYTES ); + + if ( server->config.network_simulator ) + { + netcode_network_simulator_send_packet( server->config.network_simulator, &server->address, to, packet_data, packet_bytes ); + } + else + { + if ( server->config.override_send_and_receive ) + { + server->config.send_packet_override( server->config.callback_context, to, packet_data, packet_bytes ); + } + else if ( to->type == NETCODE_ADDRESS_IPV4 ) + { + netcode_socket_send_packet( &server->socket_holder.ipv4, to, packet_data, packet_bytes ); + } + else if ( to->type == NETCODE_ADDRESS_IPV6 ) + { + netcode_socket_send_packet( &server->socket_holder.ipv6, to, packet_data, packet_bytes ); + } + } + + server->global_sequence++; +} + +void netcode_server_send_client_packet( struct netcode_server_t * server, void * packet, int client_index ) +{ + netcode_assert( server ); + netcode_assert( packet ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( server->client_connected[client_index] ); + netcode_assert( !server->client_loopback[client_index] ); + + uint8_t packet_data[NETCODE_MAX_PACKET_BYTES]; + + if ( !netcode_encryption_manager_touch( &server->encryption_manager, + server->client_encryption_index[client_index], + &server->client_address[client_index], + server->time ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_ERROR, "error: encryption mapping is out of date for client %d\n", client_index ); + return; + } + + uint8_t * packet_key = netcode_encryption_manager_get_send_key( &server->encryption_manager, server->client_encryption_index[client_index] ); + + int packet_bytes = netcode_write_packet( packet, packet_data, NETCODE_MAX_PACKET_BYTES, server->client_sequence[client_index], packet_key, server->config.protocol_id ); + + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_BYTES ); + + if ( server->config.network_simulator ) + { + netcode_network_simulator_send_packet( server->config.network_simulator, &server->address, &server->client_address[client_index], packet_data, packet_bytes ); + } + else + { + if ( server->config.override_send_and_receive ) + { + server->config.send_packet_override( server->config.callback_context, &server->client_address[client_index], packet_data, packet_bytes ); + } + else + { + if ( server->client_address[client_index].type == NETCODE_ADDRESS_IPV4 ) + { + netcode_socket_send_packet( &server->socket_holder.ipv4, &server->client_address[client_index], packet_data, packet_bytes ); + } + else if ( server->client_address[client_index].type == NETCODE_ADDRESS_IPV6 ) + { + netcode_socket_send_packet( &server->socket_holder.ipv6, &server->client_address[client_index], packet_data, packet_bytes ); + } + } + } + + server->client_sequence[client_index]++; + + server->client_last_packet_send_time[client_index] = server->time; +} + +void netcode_server_disconnect_client_internal( struct netcode_server_t * server, int client_index, int send_disconnect_packets ) +{ + netcode_assert( server ); + netcode_assert( server->running ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( server->client_connected[client_index] ); + netcode_assert( !server->client_loopback[client_index] ); + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server disconnected client %d\n", client_index ); + + if ( server->config.connect_disconnect_callback ) + { + server->config.connect_disconnect_callback( server->config.callback_context, client_index, 0 ); + } + + if ( send_disconnect_packets ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server sent disconnect packets to client %d\n", client_index ); + + int i; + for ( i = 0; i < NETCODE_NUM_DISCONNECT_PACKETS; ++i ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server sent disconnect packet %d\n", i ); + + struct netcode_connection_disconnect_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_DISCONNECT_PACKET; + + netcode_server_send_client_packet( server, &packet, client_index ); + } + } + + while ( 1 ) + { + void * packet = netcode_packet_queue_pop( &server->client_packet_queue[client_index], NULL ); + if ( !packet ) + break; + server->config.free_function( server->config.allocator_context, packet ); + } + + netcode_packet_queue_clear( &server->client_packet_queue[client_index] ); + + netcode_replay_protection_reset( &server->client_replay_protection[client_index] ); + + netcode_encryption_manager_remove_encryption_mapping( &server->encryption_manager, &server->client_address[client_index], server->time ); + + server->client_connected[client_index] = 0; + server->client_confirmed[client_index] = 0; + server->client_id[client_index] = 0; + server->client_sequence[client_index] = 0; + server->client_last_packet_send_time[client_index] = 0.0; + server->client_last_packet_receive_time[client_index] = 0.0; + memset( &server->client_address[client_index], 0, sizeof( struct netcode_address_t ) ); + server->client_encryption_index[client_index] = -1; + memset( server->client_user_data[client_index], 0, NETCODE_USER_DATA_BYTES ); + + server->num_connected_clients--; + + netcode_assert( server->num_connected_clients >= 0 ); +} + +void netcode_server_disconnect_client( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + + if ( !server->running ) + return; + + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( server->client_loopback[client_index] == 0 ); + + if ( !server->client_connected[client_index] ) + return; + + if ( server->client_loopback[client_index] ) + return; + + netcode_server_disconnect_client_internal( server, client_index, 1 ); +} + +void netcode_server_disconnect_all_clients( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + if ( !server->running ) + return; + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + if ( server->client_connected[i] && !server->client_loopback[i] ) + { + netcode_server_disconnect_client_internal( server, i, 1 ); + } + } +} + +void netcode_server_stop( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + if ( !server->running ) + return; + + netcode_server_disconnect_all_clients( server ); + + server->running = 0; + server->max_clients = 0; + server->num_connected_clients = 0; + + server->global_sequence = 0; + server->challenge_sequence = 0; + memset( server->challenge_key, 0, NETCODE_KEY_BYTES ); + + netcode_connect_token_entries_reset( server->connect_token_entries ); + + netcode_encryption_manager_reset( &server->encryption_manager ); + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server stopped\n" ); +} + +int netcode_server_find_client_index_by_id( struct netcode_server_t * server, uint64_t client_id ) +{ + netcode_assert( server ); + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + if ( server->client_connected[i] && server->client_id[i] == client_id ) + return i; + } + + return -1; +} + +int netcode_server_find_client_index_by_address( struct netcode_server_t * server, struct netcode_address_t * address ) +{ + netcode_assert( server ); + netcode_assert( address ); + + if ( address->type == 0 ) + return -1; + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + if ( server->client_connected[i] && netcode_address_equal( &server->client_address[i], address ) ) + return i; + } + + return -1; +} + +void netcode_server_process_connection_request_packet( struct netcode_server_t * server, + struct netcode_address_t * from, + struct netcode_connection_request_packet_t * packet ) +{ + netcode_assert( server ); + + (void) from; + + struct netcode_connect_token_private_t connect_token_private; + if ( netcode_read_connect_token_private( packet->connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, &connect_token_private ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. failed to read connect token\n" ); + return; + } + + int found_server_address = 0; + int i; + for ( i = 0; i < connect_token_private.num_server_addresses; ++i ) + { + if ( netcode_address_equal( &server->address, &connect_token_private.server_addresses[i] ) ) + { + found_server_address = 1; + } + } + if ( !found_server_address ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. server address not in connect token whitelist\n" ); + return; + } + + if ( netcode_server_find_client_index_by_address( server, from ) != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. a client with this address is already connected\n" ); + return; + } + + if ( netcode_server_find_client_index_by_id( server, connect_token_private.client_id ) != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. a client with this id is already connected\n" ); + return; + } + + if ( !netcode_connect_token_entries_find_or_add( server->connect_token_entries, + from, + packet->connect_token_data + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - NETCODE_MAC_BYTES, + server->time ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. connect token has already been used\n" ); + return; + } + + if ( server->num_connected_clients == server->max_clients ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server denied connection request. server is full\n" ); + + struct netcode_connection_denied_packet_t p; + p.packet_type = NETCODE_CONNECTION_DENIED_PACKET; + + netcode_server_send_global_packet( server, &p, from, connect_token_private.server_to_client_key ); + + return; + } + + double expire_time = ( connect_token_private.timeout_seconds >= 0 ) ? server->time + connect_token_private.timeout_seconds : -1.0; + + if ( !netcode_encryption_manager_add_encryption_mapping( &server->encryption_manager, + from, + connect_token_private.server_to_client_key, + connect_token_private.client_to_server_key, + server->time, + expire_time, + connect_token_private.timeout_seconds ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. failed to add encryption mapping\n" ); + return; + } + + struct netcode_challenge_token_t challenge_token; + challenge_token.client_id = connect_token_private.client_id; + memcpy( challenge_token.user_data, connect_token_private.user_data, NETCODE_USER_DATA_BYTES ); + + struct netcode_connection_challenge_packet_t challenge_packet; + challenge_packet.packet_type = NETCODE_CONNECTION_CHALLENGE_PACKET; + challenge_packet.challenge_token_sequence = server->challenge_sequence; + netcode_write_challenge_token( &challenge_token, challenge_packet.challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + if ( netcode_encrypt_challenge_token( challenge_packet.challenge_token_data, + NETCODE_CHALLENGE_TOKEN_BYTES, + server->challenge_sequence, + server->challenge_key ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection request. failed to encrypt challenge token\n" ); + return; + } + + server->challenge_sequence++; + + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server sent connection challenge packet\n" ); + + netcode_server_send_global_packet( server, &challenge_packet, from, connect_token_private.server_to_client_key ); +} + +int netcode_server_find_free_client_index( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + if ( !server->client_connected[i] ) + return i; + } + + return -1; +} + +void netcode_server_connect_client( struct netcode_server_t * server, + int client_index, + struct netcode_address_t * address, + uint64_t client_id, + int encryption_index, + int timeout_seconds, + void * user_data ) +{ + netcode_assert( server ); + netcode_assert( server->running ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( address ); + netcode_assert( encryption_index != -1 ); + netcode_assert( user_data ); + + server->num_connected_clients++; + + netcode_assert( server->num_connected_clients <= server->max_clients ); + + netcode_assert( server->client_connected[client_index] == 0 ); + + netcode_encryption_manager_set_expire_time( &server->encryption_manager, encryption_index, -1.0 ); + + server->client_connected[client_index] = 1; + server->client_timeout[client_index] = timeout_seconds; + server->client_encryption_index[client_index] = encryption_index; + server->client_id[client_index] = client_id; + server->client_sequence[client_index] = 0; + server->client_address[client_index] = *address; + server->client_last_packet_send_time[client_index] = server->time; + server->client_last_packet_receive_time[client_index] = server->time; + memcpy( server->client_user_data[client_index], user_data, NETCODE_USER_DATA_BYTES ); + + char address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server accepted client %s %.16" PRIx64 " in slot %d\n", + netcode_address_to_string( address, address_string ), client_id, client_index ); + + struct netcode_connection_keep_alive_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_KEEP_ALIVE_PACKET; + packet.client_index = client_index; + packet.max_clients = server->max_clients; + + netcode_server_send_client_packet( server, &packet, client_index ); + + if ( server->config.connect_disconnect_callback ) + { + server->config.connect_disconnect_callback( server->config.callback_context, client_index, 1 ); + } +} + +void netcode_server_process_connection_response_packet( struct netcode_server_t * server, + struct netcode_address_t * from, + struct netcode_connection_response_packet_t * packet, + int encryption_index ) +{ + netcode_assert( server ); + + if ( netcode_decrypt_challenge_token( packet->challenge_token_data, + NETCODE_CHALLENGE_TOKEN_BYTES, + packet->challenge_token_sequence, + server->challenge_key ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection response. failed to decrypt challenge token\n" ); + return; + } + + struct netcode_challenge_token_t challenge_token; + if ( netcode_read_challenge_token( packet->challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES, &challenge_token ) != NETCODE_OK ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection response. failed to read challenge token\n" ); + return; + } + + uint8_t * packet_send_key = netcode_encryption_manager_get_send_key( &server->encryption_manager, encryption_index ); + + if ( !packet_send_key ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection response. no packet send key\n" ); + return; + } + + if ( netcode_server_find_client_index_by_address( server, from ) != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection response. a client with this address is already connected\n" ); + return; + } + + if ( netcode_server_find_client_index_by_id( server, challenge_token.client_id ) != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server ignored connection response. a client with this id is already connected\n" ); + return; + } + + if ( server->num_connected_clients == server->max_clients ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server denied connection response. server is full\n" ); + + struct netcode_connection_denied_packet_t p; + p.packet_type = NETCODE_CONNECTION_DENIED_PACKET; + + netcode_server_send_global_packet( server, &p, from, packet_send_key ); + + return; + } + + int client_index = netcode_server_find_free_client_index( server ); + + netcode_assert( client_index != -1 ); + + int timeout_seconds = netcode_encryption_manager_get_timeout( &server->encryption_manager, encryption_index ); + + netcode_server_connect_client( server, client_index, from, challenge_token.client_id, encryption_index, timeout_seconds, challenge_token.user_data ); +} + +void netcode_server_process_packet_internal( struct netcode_server_t * server, + struct netcode_address_t * from, + void * packet, + uint64_t sequence, + int encryption_index, + int client_index ) +{ + netcode_assert( server ); + netcode_assert( packet ); + + (void) from; + (void) sequence; + + uint8_t packet_type = ( (uint8_t*) packet ) [0]; + + switch ( packet_type ) + { + case NETCODE_CONNECTION_REQUEST_PACKET: + { + if ( ( server->flags & NETCODE_SERVER_FLAG_IGNORE_CONNECTION_REQUEST_PACKETS ) == 0 ) + { + char from_address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server received connection request from %s\n", netcode_address_to_string( from, from_address_string ) ); + netcode_server_process_connection_request_packet( server, from, (struct netcode_connection_request_packet_t*) packet ); + } + } + break; + + case NETCODE_CONNECTION_RESPONSE_PACKET: + { + if ( ( server->flags & NETCODE_SERVER_FLAG_IGNORE_CONNECTION_RESPONSE_PACKETS ) == 0 ) + { + char from_address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server received connection response from %s\n", netcode_address_to_string( from, from_address_string ) ); + netcode_server_process_connection_response_packet( server, from, (struct netcode_connection_response_packet_t*) packet, encryption_index ); + } + } + break; + + case NETCODE_CONNECTION_KEEP_ALIVE_PACKET: + { + if ( client_index != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server received connection keep alive packet from client %d\n", client_index ); + server->client_last_packet_receive_time[client_index] = server->time; + if ( !server->client_confirmed[client_index] ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server confirmed connection with client %d\n", client_index ); + server->client_confirmed[client_index] = 1; + } + } + } + break; + + case NETCODE_CONNECTION_PAYLOAD_PACKET: + { + if ( client_index != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server received connection payload packet from client %d\n", client_index ); + server->client_last_packet_receive_time[client_index] = server->time; + if ( !server->client_confirmed[client_index] ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server confirmed connection with client %d\n", client_index ); + server->client_confirmed[client_index] = 1; + } + netcode_packet_queue_push( &server->client_packet_queue[client_index], packet, sequence ); + return; + } + } + break; + + case NETCODE_CONNECTION_DISCONNECT_PACKET: + { + if ( client_index != -1 ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server received disconnect packet from client %d\n", client_index ); + netcode_server_disconnect_client_internal( server, client_index, 0 ); + } + } + break; + + default: + break; + } + + server->config.free_function( server->config.allocator_context, packet ); +} + +void netcode_server_process_packet( struct netcode_server_t * server, struct netcode_address_t * from, uint8_t * packet_data, int packet_bytes ) +{ + uint8_t allowed_packets[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packets, 0, sizeof( allowed_packets ) ); + allowed_packets[NETCODE_CONNECTION_REQUEST_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_RESPONSE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_KEEP_ALIVE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_PAYLOAD_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_DISCONNECT_PACKET] = 1; + + uint64_t current_timestamp = (uint64_t) time( NULL ); + + uint64_t sequence; + + int encryption_index = -1; + int client_index = netcode_server_find_client_index_by_address( server, from ); + if ( client_index != -1 ) + { + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + encryption_index = server->client_encryption_index[client_index]; + } + else + { + encryption_index = netcode_encryption_manager_find_encryption_mapping( &server->encryption_manager, from, server->time ); + } + + uint8_t * read_packet_key = netcode_encryption_manager_get_receive_key( &server->encryption_manager, encryption_index ); + + if ( !read_packet_key && packet_data[0] != 0 ) + { + char address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server could not process packet because no encryption mapping exists for %s\n", netcode_address_to_string( from, address_string ) ); + return; + } + + void * packet = netcode_read_packet( packet_data, + packet_bytes, + &sequence, + read_packet_key, + server->config.protocol_id, + current_timestamp, + server->config.private_key, + allowed_packets, + ( client_index != -1 ) ? &server->client_replay_protection[client_index] : NULL, + server->config.allocator_context, + server->config.allocate_function ); + + if ( !packet ) + return; + + netcode_server_process_packet_internal( server, from, packet, sequence, encryption_index, client_index ); +} + +void netcode_server_read_and_process_packet( struct netcode_server_t * server, + struct netcode_address_t * from, + uint8_t * packet_data, + int packet_bytes, + uint64_t current_timestamp, + uint8_t * allowed_packets ) +{ + if ( !server->running ) + return; + + if ( packet_bytes <= 1 ) + return; + + uint64_t sequence; + + int encryption_index = -1; + int client_index = netcode_server_find_client_index_by_address( server, from ); + if ( client_index != -1 ) + { + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + encryption_index = server->client_encryption_index[client_index]; + } + else + { + encryption_index = netcode_encryption_manager_find_encryption_mapping( &server->encryption_manager, from, server->time ); + } + + uint8_t * read_packet_key = netcode_encryption_manager_get_receive_key( &server->encryption_manager, encryption_index ); + + if ( !read_packet_key && packet_data[0] != 0 ) + { + char address_string[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server could not process packet because no encryption mapping exists for %s\n", netcode_address_to_string( from, address_string ) ); + return; + } + + void * packet = netcode_read_packet( packet_data, + packet_bytes, + &sequence, + read_packet_key, + server->config.protocol_id, + current_timestamp, + server->config.private_key, + allowed_packets, + ( client_index != -1 ) ? &server->client_replay_protection[client_index] : NULL, + server->config.allocator_context, + server->config.allocate_function ); + + if ( !packet ) + return; + + netcode_server_process_packet_internal( server, from, packet, sequence, encryption_index, client_index ); +} + +void netcode_server_receive_packets( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + uint8_t allowed_packets[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packets, 0, sizeof( allowed_packets ) ); + allowed_packets[NETCODE_CONNECTION_REQUEST_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_RESPONSE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_KEEP_ALIVE_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_PAYLOAD_PACKET] = 1; + allowed_packets[NETCODE_CONNECTION_DISCONNECT_PACKET] = 1; + + uint64_t current_timestamp = (uint64_t) time( NULL ); + + if ( !server->config.network_simulator ) + { + // process packets received from socket + + while ( 1 ) + { + struct netcode_address_t from; + + uint8_t packet_data[NETCODE_MAX_PACKET_BYTES]; + + int packet_bytes = 0; + + if ( server->config.override_send_and_receive ) + { + packet_bytes = server->config.receive_packet_override( server->config.callback_context, &from, packet_data, NETCODE_MAX_PACKET_BYTES ); + } + else + { + if (server->socket_holder.ipv4.handle != 0) + packet_bytes = netcode_socket_receive_packet( &server->socket_holder.ipv4, &from, packet_data, NETCODE_MAX_PACKET_BYTES ); + + if ( packet_bytes == 0 && server->socket_holder.ipv6.handle != 0) + packet_bytes = netcode_socket_receive_packet( &server->socket_holder.ipv6, &from, packet_data, NETCODE_MAX_PACKET_BYTES ); + } + + if ( packet_bytes == 0 ) + break; + + netcode_server_read_and_process_packet( server, &from, packet_data, packet_bytes, current_timestamp, allowed_packets ); + } + } + else + { + // process packets received from network simulator + + int num_packets_received = netcode_network_simulator_receive_packets( server->config.network_simulator, + &server->address, + NETCODE_SERVER_MAX_RECEIVE_PACKETS, + server->receive_packet_data, + server->receive_packet_bytes, + server->receive_from ); + + int i; + for ( i = 0; i < num_packets_received; ++i ) + { + netcode_server_read_and_process_packet( server, + &server->receive_from[i], + server->receive_packet_data[i], + server->receive_packet_bytes[i], + current_timestamp, + allowed_packets ); + + server->config.free_function( server->config.allocator_context, server->receive_packet_data[i] ); + } + } +} + +void netcode_server_send_packets( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + if ( !server->running ) + return; + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + if ( server->client_connected[i] && !server->client_loopback[i] && + ( server->client_last_packet_send_time[i] + ( 1.0 / NETCODE_PACKET_SEND_RATE ) <= server->time ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server sent connection keep alive packet to client %d\n", i ); + struct netcode_connection_keep_alive_packet_t packet; + packet.packet_type = NETCODE_CONNECTION_KEEP_ALIVE_PACKET; + packet.client_index = i; + packet.max_clients = server->max_clients; + netcode_server_send_client_packet( server, &packet, i ); + } + } +} + +void netcode_server_check_for_timeouts( struct netcode_server_t * server ) +{ + netcode_assert( server ); + + if ( !server->running ) + return; + + int i; + for ( i = 0; i < server->max_clients; ++i ) + { + if ( server->client_connected[i] && server->client_timeout[i] > 0 && !server->client_loopback[i] && + ( server->client_last_packet_receive_time[i] + server->client_timeout[i] <= server->time ) ) + { + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server timed out client %d\n", i ); + netcode_server_disconnect_client_internal( server, i, 0 ); + return; + } + } +} + +int netcode_server_client_connected( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + + if ( !server->running ) + return 0; + + if ( client_index < 0 || client_index >= server->max_clients ) + return 0; + + return server->client_connected[client_index]; +} + +uint64_t netcode_server_client_id( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + + if ( !server->running ) + return 0; + + if ( client_index < 0 || client_index >= server->max_clients ) + return 0; + + return server->client_id[client_index]; +} + +struct netcode_address_t * netcode_server_client_address( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + + if (!server->running) + return NULL; + + if (client_index < 0 || client_index >= server->max_clients) + return NULL; + + return &server->client_address[client_index]; +} + +uint64_t netcode_server_next_packet_sequence( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + if ( !server->client_connected[client_index] ) + return 0; + return server->client_sequence[client_index]; +} + +void netcode_server_send_packet( struct netcode_server_t * server, int client_index, NETCODE_CONST uint8_t * packet_data, int packet_bytes ) +{ + netcode_assert( server ); + netcode_assert( packet_data ); + netcode_assert( packet_bytes >= 0 ); + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_SIZE ); + + if ( !server->running ) + return; + + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + if ( !server->client_connected[client_index] ) + return; + + if ( !server->client_loopback[client_index] ) + { + uint8_t buffer[NETCODE_MAX_PAYLOAD_BYTES*2]; + + struct netcode_connection_payload_packet_t * packet = (struct netcode_connection_payload_packet_t*) buffer; + + packet->packet_type = NETCODE_CONNECTION_PAYLOAD_PACKET; + packet->payload_bytes = packet_bytes; + memcpy( packet->payload_data, packet_data, packet_bytes ); + + if ( !server->client_confirmed[client_index] ) + { + struct netcode_connection_keep_alive_packet_t keep_alive_packet; + keep_alive_packet.packet_type = NETCODE_CONNECTION_KEEP_ALIVE_PACKET; + keep_alive_packet.client_index = client_index; + keep_alive_packet.max_clients = server->max_clients; + netcode_server_send_client_packet( server, &keep_alive_packet, client_index ); + } + + netcode_server_send_client_packet( server, packet, client_index ); + } + else + { + netcode_assert( server->config.send_loopback_packet_callback ); + + server->config.send_loopback_packet_callback( server->config.callback_context, + client_index, + packet_data, + packet_bytes, + server->client_sequence[client_index]++ ); + + server->client_last_packet_send_time[client_index] = server->time; + } +} + +uint8_t * netcode_server_receive_packet( struct netcode_server_t * server, int client_index, int * packet_bytes, uint64_t * packet_sequence ) +{ + netcode_assert( server ); + netcode_assert( packet_bytes ); + + if ( !server->running ) + return NULL; + + if ( !server->client_connected[client_index] ) + return NULL; + + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + + struct netcode_connection_payload_packet_t * packet = (struct netcode_connection_payload_packet_t*) + netcode_packet_queue_pop( &server->client_packet_queue[client_index], packet_sequence ); + + if ( packet ) + { + netcode_assert( packet->packet_type == NETCODE_CONNECTION_PAYLOAD_PACKET ); + *packet_bytes = packet->payload_bytes; + netcode_assert( *packet_bytes >= 0 ); + netcode_assert( *packet_bytes <= NETCODE_MAX_PAYLOAD_BYTES ); + return (uint8_t*) &packet->payload_data; + } + else + { + return NULL; + } +} + +void netcode_server_free_packet( struct netcode_server_t * server, void * packet ) +{ + netcode_assert( server ); + netcode_assert( packet ); + (void) server; + int offset = offsetof( struct netcode_connection_payload_packet_t, payload_data ); + server->config.free_function( server->config.allocator_context, ( (uint8_t*) packet ) - offset ); +} + +int netcode_server_num_connected_clients( struct netcode_server_t * server ) +{ + netcode_assert( server ); + return server->num_connected_clients; +} + +void * netcode_server_client_user_data( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + return server->client_user_data[client_index]; +} + +int netcode_server_running( struct netcode_server_t * server ) +{ + netcode_assert( server ); + return server->running; +} + +int netcode_server_max_clients( struct netcode_server_t * server ) +{ + return server->max_clients; +} + +void netcode_server_update( struct netcode_server_t * server, double time ) +{ + netcode_assert( server ); + server->time = time; + netcode_server_receive_packets( server ); + netcode_server_send_packets( server ); + netcode_server_check_for_timeouts( server ); +} + +void netcode_server_connect_loopback_client( struct netcode_server_t * server, int client_index, uint64_t client_id, NETCODE_CONST uint8_t * user_data ) +{ + netcode_assert( server ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( server->running ); + netcode_assert( !server->client_connected[client_index] ); + + server->num_connected_clients++; + + netcode_assert( server->num_connected_clients <= server->max_clients ); + + server->client_loopback[client_index] = 1; + server->client_connected[client_index] = 1; + server->client_confirmed[client_index] = 1; + server->client_encryption_index[client_index] = -1; + server->client_id[client_index] = client_id; + server->client_sequence[client_index] = 0; + memset( &server->client_address[client_index], 0, sizeof( struct netcode_address_t ) ); + server->client_last_packet_send_time[client_index] = server->time; + server->client_last_packet_receive_time[client_index] = server->time; + + if ( user_data ) + { + memcpy( server->client_user_data[client_index], user_data, NETCODE_USER_DATA_BYTES ); + } + else + { + memset( server->client_user_data[client_index], 0, NETCODE_USER_DATA_BYTES ); + } + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server connected loopback client %.16" PRIx64 " in slot %d\n", client_id, client_index ); + + if ( server->config.connect_disconnect_callback ) + { + server->config.connect_disconnect_callback( server->config.callback_context, client_index, 1 ); + } +} + +void netcode_server_disconnect_loopback_client( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( server->running ); + netcode_assert( server->client_connected[client_index] ); + netcode_assert( server->client_loopback[client_index] ); + + netcode_printf( NETCODE_LOG_LEVEL_INFO, "server disconnected loopback client %d\n", client_index ); + + if ( server->config.connect_disconnect_callback ) + { + server->config.connect_disconnect_callback( server->config.callback_context, client_index, 0 ); + } + + while ( 1 ) + { + void * packet = netcode_packet_queue_pop( &server->client_packet_queue[client_index], NULL ); + if ( !packet ) + break; + server->config.free_function( server->config.allocator_context, packet ); + } + + netcode_packet_queue_clear( &server->client_packet_queue[client_index] ); + + server->client_connected[client_index] = 0; + server->client_loopback[client_index] = 0; + server->client_confirmed[client_index] = 0; + server->client_id[client_index] = 0; + server->client_sequence[client_index] = 0; + server->client_last_packet_send_time[client_index] = 0.0; + server->client_last_packet_receive_time[client_index] = 0.0; + memset( &server->client_address[client_index], 0, sizeof( struct netcode_address_t ) ); + server->client_encryption_index[client_index] = -1; + memset( server->client_user_data[client_index], 0, NETCODE_USER_DATA_BYTES ); + + server->num_connected_clients--; + + netcode_assert( server->num_connected_clients >= 0 ); +} + +int netcode_server_client_loopback( struct netcode_server_t * server, int client_index ) +{ + netcode_assert( server ); + netcode_assert( server->running ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + return server->client_loopback[client_index]; +} + +void netcode_server_process_loopback_packet( struct netcode_server_t * server, int client_index, NETCODE_CONST uint8_t * packet_data, int packet_bytes, uint64_t packet_sequence ) +{ + netcode_assert( server ); + netcode_assert( client_index >= 0 ); + netcode_assert( client_index < server->max_clients ); + netcode_assert( packet_data ); + netcode_assert( packet_bytes >= 0 ); + netcode_assert( packet_bytes <= NETCODE_MAX_PACKET_SIZE ); + netcode_assert( server->client_connected[client_index] ); + netcode_assert( server->client_loopback[client_index] ); + netcode_assert( server->running ); + + struct netcode_connection_payload_packet_t * packet = netcode_create_payload_packet( packet_bytes, server->config.allocator_context, server->config.allocate_function ); + if ( !packet ) + return; + + memcpy( packet->payload_data, packet_data, packet_bytes ); + + netcode_printf( NETCODE_LOG_LEVEL_DEBUG, "server processing loopback packet from client %d\n", client_index ); + + server->client_last_packet_receive_time[client_index] = server->time; + + netcode_packet_queue_push( &server->client_packet_queue[client_index], packet, packet_sequence ); +} + +uint16_t netcode_server_get_port( struct netcode_server_t * server ) +{ + netcode_assert( server ); + return server->address.type == NETCODE_ADDRESS_IPV4 ? server->socket_holder.ipv4.address.port : server->socket_holder.ipv6.address.port; +} + +// ---------------------------------------------------------------- + +int netcode_generate_connect_token( int num_server_addresses, + NETCODE_CONST char ** public_server_addresses, + NETCODE_CONST char ** internal_server_addresses, + int expire_seconds, + int timeout_seconds, + uint64_t client_id, + uint64_t protocol_id, + NETCODE_CONST uint8_t * private_key, + uint8_t * user_data, + uint8_t * output_buffer ) +{ + netcode_assert( num_server_addresses > 0 ); + netcode_assert( num_server_addresses <= NETCODE_MAX_SERVERS_PER_CONNECT ); + netcode_assert( public_server_addresses ); + netcode_assert( internal_server_addresses ); + netcode_assert( private_key ); + netcode_assert( user_data ); + netcode_assert( output_buffer ); + + // parse public server addresses + + struct netcode_address_t parsed_public_server_addresses[NETCODE_MAX_SERVERS_PER_CONNECT]; + int i; + for ( i = 0; i < num_server_addresses; ++i ) + { + if ( netcode_parse_address( public_server_addresses[i], &parsed_public_server_addresses[i] ) != NETCODE_OK ) + { + return NETCODE_ERROR; + } + } + + // parse internal server addresses + + struct netcode_address_t parsed_internal_server_addresses[NETCODE_MAX_SERVERS_PER_CONNECT]; + for ( i = 0; i < num_server_addresses; ++i ) + { + if ( netcode_parse_address( internal_server_addresses[i], &parsed_internal_server_addresses[i] ) != NETCODE_OK ) + { + return NETCODE_ERROR; + } + } + + // generate a connect token + + uint8_t nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + netcode_generate_nonce(nonce); + + struct netcode_connect_token_private_t connect_token_private; + netcode_generate_connect_token_private( &connect_token_private, client_id, timeout_seconds, num_server_addresses, parsed_internal_server_addresses, user_data ); + + // write it to a buffer + + uint8_t connect_token_data[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; + netcode_write_connect_token_private( &connect_token_private, connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + // encrypt the buffer + + uint64_t create_timestamp = time( NULL ); + uint64_t expire_timestamp = ( expire_seconds >= 0 ) ? ( create_timestamp + expire_seconds ) : 0xFFFFFFFFFFFFFFFFULL; + if ( netcode_encrypt_connect_token_private( connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, NETCODE_VERSION_INFO, protocol_id, expire_timestamp, nonce, private_key ) != NETCODE_OK ) + return NETCODE_ERROR; + + // wrap a connect token around the private connect token data + + struct netcode_connect_token_t connect_token; + memcpy( connect_token.version_info, NETCODE_VERSION_INFO, NETCODE_VERSION_INFO_BYTES ); + connect_token.protocol_id = protocol_id; + connect_token.create_timestamp = create_timestamp; + connect_token.expire_timestamp = expire_timestamp; + memcpy( connect_token.nonce, nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + memcpy( connect_token.private_data, connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + connect_token.num_server_addresses = num_server_addresses; + for ( i = 0; i < num_server_addresses; ++i ) + connect_token.server_addresses[i] = parsed_public_server_addresses[i]; + memcpy( connect_token.client_to_server_key, connect_token_private.client_to_server_key, NETCODE_KEY_BYTES ); + memcpy( connect_token.server_to_client_key, connect_token_private.server_to_client_key, NETCODE_KEY_BYTES ); + connect_token.timeout_seconds = timeout_seconds; + + // write the connect token to the output buffer + + netcode_write_connect_token( &connect_token, output_buffer, NETCODE_CONNECT_TOKEN_BYTES ); + + return NETCODE_OK; +} + +// --------------------------------------------------------------- + +#if __APPLE__ + +// MacOS + +#include <unistd.h> +#include <mach/mach.h> +#include <mach/mach_time.h> + +void netcode_sleep( double time ) +{ + usleep( (int) ( time * 1000000 ) ); +} + +static uint64_t start = 0; +static mach_timebase_info_data_t timebase_info; + +double netcode_time() +{ + if ( start == 0 ) + { + mach_timebase_info( &timebase_info ); + start = mach_absolute_time(); + return 0.0; + } + uint64_t current = mach_absolute_time(); + return ( (double) ( current - start ) ) * ( (double) timebase_info.numer ) / ( (double) timebase_info.denom ) / 1000000000.0; +} + +#elif __linux + +// linux + +#include <unistd.h> + +void netcode_sleep( double time ) +{ + usleep( (int) ( time * 1000000 ) ); +} + +double netcode_time() +{ + static double start = -1; + if ( start == -1 ) + { + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC_RAW, &ts ); + start = ts.tv_sec + ( (double) ( ts.tv_nsec ) ) / 1000000000.0; + return 0.0; + } + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC_RAW, &ts ); + double current = ts.tv_sec + ( (double) ( ts.tv_nsec ) ) / 1000000000.0; + return current - start; +} + +#elif defined( _WIN32 ) + +// windows + +#define NOMINMAX +#include <windows.h> + +void netcode_sleep( double time ) +{ + int milliseconds = (int) ( time * 1000 ); + Sleep( milliseconds ); +} + +static int timer_initialized = 0; +static LARGE_INTEGER timer_frequency; +static LARGE_INTEGER timer_start; + +double netcode_time() +{ + if ( !timer_initialized ) + { + QueryPerformanceFrequency( &timer_frequency ); + QueryPerformanceCounter( &timer_start ); + timer_initialized = 1; + } + LARGE_INTEGER now; + QueryPerformanceCounter( &now ); + return ( (double) ( now.QuadPart - timer_start.QuadPart ) ) / ( (double) ( timer_frequency.QuadPart ) ); +} + +#else + +#error unsupported platform! + +#endif + +// --------------------------------------------------------------- + +#if NETCODE_ENABLE_TESTS + +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <time.h> + +static void check_handler( NETCODE_CONST char * condition, + NETCODE_CONST char * function, + NETCODE_CONST char * file, + int line ) +{ + printf( "check failed: ( %s ), function %s, file %s, line %d\n", condition, function, file, line ); +#ifndef NDEBUG + #if defined( __GNUC__ ) + __builtin_trap(); + #elif defined( _MSC_VER ) + __debugbreak(); + #endif +#endif + exit( 1 ); +} + +#define check( condition ) \ +do \ +{ \ + if ( !(condition) ) \ + { \ + check_handler( #condition, (NETCODE_CONST char*) __FUNCTION__, (char*) __FILE__, __LINE__ ); \ + } \ +} while(0) + +static void test_queue() +{ + struct netcode_packet_queue_t queue; + + netcode_packet_queue_init( &queue, NULL, NULL, NULL ); + + check( queue.num_packets == 0 ); + check( queue.start_index == 0 ); + + // attempting to pop a packet off an empty queue should return NULL + + check( netcode_packet_queue_pop( &queue, NULL ) == NULL ); + + // add some packets to the queue and make sure they pop off in the correct order + { + #define NUM_PACKETS 100 + + void * packets[NUM_PACKETS]; + + int i; + for ( i = 0; i < NUM_PACKETS; ++i ) + { + packets[i] = malloc( (i+1) * 256 ); + check( netcode_packet_queue_push( &queue, packets[i], (uint64_t) i ) == 1 ); + } + + check( queue.num_packets == NUM_PACKETS ); + + for ( i = 0; i < NUM_PACKETS; ++i ) + { + uint64_t sequence = 0; + void * packet = netcode_packet_queue_pop( &queue, &sequence ); + check( sequence == (uint64_t) i ) ; + check( packet == packets[i] ); + free( packet ); + } + } + + // after all entries are popped off, the queue is empty, so calls to pop should return NULL + + check( queue.num_packets == 0 ); + + check( netcode_packet_queue_pop( &queue, NULL ) == NULL ); + + // test that the packet queue can be filled to max capacity + + void * packets[NETCODE_PACKET_QUEUE_SIZE]; + + int i; + for ( i = 0; i < NETCODE_PACKET_QUEUE_SIZE; ++i ) + { + packets[i] = malloc( i * 256 ); + check( netcode_packet_queue_push( &queue, packets[i], (uint64_t) i ) == 1 ); + } + + check( queue.num_packets == NETCODE_PACKET_QUEUE_SIZE ); + + // when the queue is full, attempting to push a packet should fail and return 0 + + check( netcode_packet_queue_push( &queue, malloc( 100 ), 0 ) == 0 ); + + // make sure all packets pop off in the correct order + + for ( i = 0; i < NETCODE_PACKET_QUEUE_SIZE; ++i ) + { + uint64_t sequence = 0; + void * packet = netcode_packet_queue_pop( &queue, &sequence ); + check( sequence == (uint64_t) i ); + check( packet == packets[i] ); + free( packet ); + } + + // add some packets again + + for ( i = 0; i < NETCODE_PACKET_QUEUE_SIZE; ++i ) + { + packets[i] = malloc( i * 256 ); + check( netcode_packet_queue_push( &queue, packets[i], (uint64_t) i ) == 1 ); + } + + // clear the queue and make sure that all packets are freed + + netcode_packet_queue_clear( &queue ); + + check( queue.start_index == 0 ); + check( queue.num_packets == 0 ); + for ( i = 0; i < NETCODE_PACKET_QUEUE_SIZE; ++i ) + check( queue.packet_data[i] == NULL ); +} + +static void test_endian() +{ + uint32_t value = 0x11223344; + + char * bytes = (char*) &value; + +#if NETCODE_LITTLE_ENDIAN + + check( bytes[0] == 0x44 ); + check( bytes[1] == 0x33 ); + check( bytes[2] == 0x22 ); + check( bytes[3] == 0x11 ); + +#else // #if NETCODE_LITTLE_ENDIAN + + check( bytes[3] == 0x44 ); + check( bytes[2] == 0x33 ); + check( bytes[1] == 0x22 ); + check( bytes[0] == 0x11 ); + +#endif // #if NETCODE_LITTLE_ENDIAN +} + +static void test_sequence() +{ + check( netcode_sequence_number_bytes_required( 0 ) == 1 ); + check( netcode_sequence_number_bytes_required( 0x11 ) == 1 ); + check( netcode_sequence_number_bytes_required( 0x1122 ) == 2 ); + check( netcode_sequence_number_bytes_required( 0x112233 ) == 3 ); + check( netcode_sequence_number_bytes_required( 0x11223344 ) == 4 ); + check( netcode_sequence_number_bytes_required( 0x1122334455 ) == 5 ); + check( netcode_sequence_number_bytes_required( 0x112233445566 ) == 6 ); + check( netcode_sequence_number_bytes_required( 0x11223344556677 ) == 7 ); + check( netcode_sequence_number_bytes_required( 0x1122334455667788 ) == 8 ); +} + +static void test_address() +{ + { + struct netcode_address_t address; + check( netcode_parse_address( "", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "[", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "[]", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "[]:", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( ":", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "1", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "12", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "123", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "1234", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "1234.0.12313.0000", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "1234.0.12313.0000.0.0.0.0.0", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "1312313:123131:1312313:123131:1312313:123131:1312313:123131:1312313:123131:1312313:123131", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( ".", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "..", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "...", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( "....", &address ) == NETCODE_ERROR ); + check( netcode_parse_address( ".....", &address ) == NETCODE_ERROR ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "107.77.207.77", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV4 ); + check( address.port == 0 ); + check( address.data.ipv4[0] == 107 ); + check( address.data.ipv4[1] == 77 ); + check( address.data.ipv4[2] == 207 ); + check( address.data.ipv4[3] == 77 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "127.0.0.1", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV4 ); + check( address.port == 0 ); + check( address.data.ipv4[0] == 127 ); + check( address.data.ipv4[1] == 0 ); + check( address.data.ipv4[2] == 0 ); + check( address.data.ipv4[3] == 1 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "107.77.207.77:40000", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV4 ); + check( address.port == 40000 ); + check( address.data.ipv4[0] == 107 ); + check( address.data.ipv4[1] == 77 ); + check( address.data.ipv4[2] == 207 ); + check( address.data.ipv4[3] == 77 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "127.0.0.1:40000", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV4 ); + check( address.port == 40000 ); + check( address.data.ipv4[0] == 127 ); + check( address.data.ipv4[1] == 0 ); + check( address.data.ipv4[2] == 0 ); + check( address.data.ipv4[3] == 1 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "fe80::202:b3ff:fe1e:8329", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV6 ); + check( address.port == 0 ); + check( address.data.ipv6[0] == 0xfe80 ); + check( address.data.ipv6[1] == 0x0000 ); + check( address.data.ipv6[2] == 0x0000 ); + check( address.data.ipv6[3] == 0x0000 ); + check( address.data.ipv6[4] == 0x0202 ); + check( address.data.ipv6[5] == 0xb3ff ); + check( address.data.ipv6[6] == 0xfe1e ); + check( address.data.ipv6[7] == 0x8329 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "::", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV6 ); + check( address.port == 0 ); + check( address.data.ipv6[0] == 0x0000 ); + check( address.data.ipv6[1] == 0x0000 ); + check( address.data.ipv6[2] == 0x0000 ); + check( address.data.ipv6[3] == 0x0000 ); + check( address.data.ipv6[4] == 0x0000 ); + check( address.data.ipv6[5] == 0x0000 ); + check( address.data.ipv6[6] == 0x0000 ); + check( address.data.ipv6[7] == 0x0000 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "::1", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV6 ); + check( address.port == 0 ); + check( address.data.ipv6[0] == 0x0000 ); + check( address.data.ipv6[1] == 0x0000 ); + check( address.data.ipv6[2] == 0x0000 ); + check( address.data.ipv6[3] == 0x0000 ); + check( address.data.ipv6[4] == 0x0000 ); + check( address.data.ipv6[5] == 0x0000 ); + check( address.data.ipv6[6] == 0x0000 ); + check( address.data.ipv6[7] == 0x0001 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "[fe80::202:b3ff:fe1e:8329]:40000", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV6 ); + check( address.port == 40000 ); + check( address.data.ipv6[0] == 0xfe80 ); + check( address.data.ipv6[1] == 0x0000 ); + check( address.data.ipv6[2] == 0x0000 ); + check( address.data.ipv6[3] == 0x0000 ); + check( address.data.ipv6[4] == 0x0202 ); + check( address.data.ipv6[5] == 0xb3ff ); + check( address.data.ipv6[6] == 0xfe1e ); + check( address.data.ipv6[7] == 0x8329 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "[::]:40000", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV6 ); + check( address.port == 40000 ); + check( address.data.ipv6[0] == 0x0000 ); + check( address.data.ipv6[1] == 0x0000 ); + check( address.data.ipv6[2] == 0x0000 ); + check( address.data.ipv6[3] == 0x0000 ); + check( address.data.ipv6[4] == 0x0000 ); + check( address.data.ipv6[5] == 0x0000 ); + check( address.data.ipv6[6] == 0x0000 ); + check( address.data.ipv6[7] == 0x0000 ); + } + + { + struct netcode_address_t address; + check( netcode_parse_address( "[::1]:40000", &address ) == NETCODE_OK ); + check( address.type == NETCODE_ADDRESS_IPV6 ); + check( address.port == 40000 ); + check( address.data.ipv6[0] == 0x0000 ); + check( address.data.ipv6[1] == 0x0000 ); + check( address.data.ipv6[2] == 0x0000 ); + check( address.data.ipv6[3] == 0x0000 ); + check( address.data.ipv6[4] == 0x0000 ); + check( address.data.ipv6[5] == 0x0000 ); + check( address.data.ipv6[6] == 0x0000 ); + check( address.data.ipv6[7] == 0x0001 ); + } +} + +#define TEST_PROTOCOL_ID 0x1122334455667788ULL +#define TEST_CLIENT_ID 0x1ULL +#define TEST_SERVER_PORT 40000 +#define TEST_CONNECT_TOKEN_EXPIRY 30 +#define TEST_TIMEOUT_SECONDS 15 + +static void test_connect_token() +{ + // generate a connect token + + struct netcode_address_t server_address; + server_address.type = NETCODE_ADDRESS_IPV4; + server_address.data.ipv4[0] = 127; + server_address.data.ipv4[1] = 0; + server_address.data.ipv4[2] = 0; + server_address.data.ipv4[3] = 1; + server_address.port = TEST_SERVER_PORT; + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes( user_data, NETCODE_USER_DATA_BYTES ); + + struct netcode_connect_token_private_t input_token; + + netcode_generate_connect_token_private( &input_token, TEST_CLIENT_ID, TEST_TIMEOUT_SECONDS, 1, &server_address, user_data ); + + check( input_token.client_id == TEST_CLIENT_ID ); + check( input_token.num_server_addresses == 1 ); + check( memcmp( input_token.user_data, user_data, NETCODE_USER_DATA_BYTES ) == 0 ); + check( netcode_address_equal( &input_token.server_addresses[0], &server_address ) ); + + // write it to a buffer + + uint8_t buffer[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; + + netcode_write_connect_token_private( &input_token, buffer, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + // encrypt the buffer + + uint64_t expire_timestamp = time( NULL ) + 30; + uint8_t nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + netcode_generate_nonce(nonce); + uint8_t key[NETCODE_KEY_BYTES]; + netcode_generate_key( key ); + + check( netcode_encrypt_connect_token_private( buffer, + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, + NETCODE_VERSION_INFO, + TEST_PROTOCOL_ID, + expire_timestamp, + nonce, + key ) == NETCODE_OK ); + + // decrypt the buffer + + check( netcode_decrypt_connect_token_private( buffer, + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, + NETCODE_VERSION_INFO, + TEST_PROTOCOL_ID, + expire_timestamp, + nonce, + key ) == NETCODE_OK ); + + // read the connect token back in + + struct netcode_connect_token_private_t output_token; + + check( netcode_read_connect_token_private( buffer, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, &output_token ) == NETCODE_OK ); + + // make sure that everything matches the original connect token + + check( output_token.client_id == input_token.client_id ); + check( output_token.timeout_seconds == input_token.timeout_seconds ); + check( output_token.num_server_addresses == input_token.num_server_addresses ); + check( netcode_address_equal( &output_token.server_addresses[0], &input_token.server_addresses[0] ) ); + check( memcmp( output_token.client_to_server_key, input_token.client_to_server_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( output_token.server_to_client_key, input_token.server_to_client_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( output_token.user_data, input_token.user_data, NETCODE_USER_DATA_BYTES ) == 0 ); +} + +static void test_challenge_token() +{ + // generate a challenge token + + struct netcode_challenge_token_t input_token; + + input_token.client_id = TEST_CLIENT_ID; + netcode_random_bytes( input_token.user_data, NETCODE_USER_DATA_BYTES ); + + // write it to a buffer + + uint8_t buffer[NETCODE_CHALLENGE_TOKEN_BYTES]; + + netcode_write_challenge_token( &input_token, buffer, NETCODE_CHALLENGE_TOKEN_BYTES ); + + // encrypt the buffer + + uint64_t sequence = 1000; + uint8_t key[NETCODE_KEY_BYTES]; + netcode_generate_key( key ); + + check( netcode_encrypt_challenge_token( buffer, NETCODE_CHALLENGE_TOKEN_BYTES, sequence, key ) == NETCODE_OK ); + + // decrypt the buffer + + check( netcode_decrypt_challenge_token( buffer, NETCODE_CHALLENGE_TOKEN_BYTES, sequence, key ) == NETCODE_OK ); + + // read the challenge token back in + + struct netcode_challenge_token_t output_token; + + check( netcode_read_challenge_token( buffer, NETCODE_CHALLENGE_TOKEN_BYTES, &output_token ) == NETCODE_OK ); + + // make sure that everything matches the original challenge token + + check( output_token.client_id == input_token.client_id ); + check( memcmp( output_token.user_data, input_token.user_data, NETCODE_USER_DATA_BYTES ) == 0 ); +} + +static void test_connection_request_packet() +{ + // generate a connect token + + struct netcode_address_t server_address; + server_address.type = NETCODE_ADDRESS_IPV4; + server_address.data.ipv4[0] = 127; + server_address.data.ipv4[1] = 0; + server_address.data.ipv4[2] = 0; + server_address.data.ipv4[3] = 1; + server_address.port = TEST_SERVER_PORT; + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes( user_data, NETCODE_USER_DATA_BYTES ); + + struct netcode_connect_token_private_t input_token; + + netcode_generate_connect_token_private( &input_token, TEST_CLIENT_ID, TEST_TIMEOUT_SECONDS, 1, &server_address, user_data ); + + check( input_token.client_id == TEST_CLIENT_ID ); + check( input_token.num_server_addresses == 1 ); + check( memcmp( input_token.user_data, user_data, NETCODE_USER_DATA_BYTES ) == 0 ); + check( netcode_address_equal( &input_token.server_addresses[0], &server_address ) ); + + // write the conect token to a buffer (non-encrypted) + + uint8_t connect_token_data[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; + + netcode_write_connect_token_private( &input_token, connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + // copy to a second buffer then encrypt it in place (we need the unencrypted token for verification later on) + + uint8_t encrypted_connect_token_data[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; + + memcpy( encrypted_connect_token_data, connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + uint64_t connect_token_expire_timestamp = time( NULL ) + 30; + uint8_t connect_token_nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + netcode_generate_nonce(connect_token_nonce); + uint8_t connect_token_key[NETCODE_KEY_BYTES]; + netcode_generate_key( connect_token_key ); + + check( netcode_encrypt_connect_token_private( encrypted_connect_token_data, + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, + NETCODE_VERSION_INFO, + TEST_PROTOCOL_ID, + connect_token_expire_timestamp, + connect_token_nonce, + connect_token_key ) == NETCODE_OK ); + + // setup a connection request packet wrapping the encrypted connect token + + struct netcode_connection_request_packet_t input_packet; + + input_packet.packet_type = NETCODE_CONNECTION_REQUEST_PACKET; + memcpy( input_packet.version_info, NETCODE_VERSION_INFO, NETCODE_VERSION_INFO_BYTES ); + input_packet.protocol_id = TEST_PROTOCOL_ID; + input_packet.connect_token_expire_timestamp = connect_token_expire_timestamp; + memcpy( input_packet.connect_token_nonce, connect_token_nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + memcpy( input_packet.connect_token_data, encrypted_connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + // write the connection request packet to a buffer + + uint8_t buffer[2048]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( &input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the connection request packet back in from the buffer (the connect token data is decrypted as part of the read packet validation) + + uint64_t sequence = 1000; + + uint8_t allowed_packets[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packets, 1, sizeof( allowed_packets ) ); + + struct netcode_connection_request_packet_t * output_packet = (struct netcode_connection_request_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), connect_token_key, allowed_packets, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_REQUEST_PACKET ); + check( memcmp( output_packet->version_info, input_packet.version_info, NETCODE_VERSION_INFO_BYTES ) == 0 ); + check( output_packet->protocol_id == input_packet.protocol_id ); + check( output_packet->connect_token_expire_timestamp == input_packet.connect_token_expire_timestamp ); + check( memcmp( output_packet->connect_token_nonce, input_packet.connect_token_nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ) == 0 ); + check( memcmp( output_packet->connect_token_data, connect_token_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - NETCODE_MAC_BYTES ) == 0 ); + + free( output_packet ); +} + +void test_connection_denied_packet() +{ + // setup a connection denied packet + + struct netcode_connection_denied_packet_t input_packet; + + input_packet.packet_type = NETCODE_CONNECTION_DENIED_PACKET; + + // write the packet to a buffer + + uint8_t buffer[NETCODE_MAX_PACKET_BYTES]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( &input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the packet back in from the buffer + + uint64_t sequence; + + uint8_t allowed_packet_types[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packet_types, 1, sizeof( allowed_packet_types ) ); + + struct netcode_connection_denied_packet_t * output_packet = (struct netcode_connection_denied_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), NULL, allowed_packet_types, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_DENIED_PACKET ); + + free( output_packet ); +} + +void test_connection_challenge_packet() +{ + // setup a connection challenge packet + + struct netcode_connection_challenge_packet_t input_packet; + + input_packet.packet_type = NETCODE_CONNECTION_CHALLENGE_PACKET; + input_packet.challenge_token_sequence = 0; + netcode_random_bytes( input_packet.challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + + // write the packet to a buffer + + uint8_t buffer[NETCODE_MAX_PACKET_BYTES]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( &input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the packet back in from the buffer + + uint64_t sequence; + + uint8_t allowed_packet_types[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packet_types, 1, sizeof( allowed_packet_types ) ); + + struct netcode_connection_challenge_packet_t * output_packet = (struct netcode_connection_challenge_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), NULL, allowed_packet_types, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_CHALLENGE_PACKET ); + check( output_packet->challenge_token_sequence == input_packet.challenge_token_sequence ); + check( memcmp( output_packet->challenge_token_data, input_packet.challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ) == 0 ); + + free( output_packet ); +} + +void test_connection_response_packet() +{ + // setup a connection response packet + + struct netcode_connection_response_packet_t input_packet; + + input_packet.packet_type = NETCODE_CONNECTION_RESPONSE_PACKET; + input_packet.challenge_token_sequence = 0; + netcode_random_bytes( input_packet.challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ); + + // write the packet to a buffer + + uint8_t buffer[NETCODE_MAX_PACKET_BYTES]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( &input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the packet back in from the buffer + + uint64_t sequence; + + uint8_t allowed_packet_types[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packet_types, 1, sizeof( allowed_packet_types ) ); + + struct netcode_connection_response_packet_t * output_packet = (struct netcode_connection_response_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), NULL, allowed_packet_types, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_RESPONSE_PACKET ); + check( output_packet->challenge_token_sequence == input_packet.challenge_token_sequence ); + check( memcmp( output_packet->challenge_token_data, input_packet.challenge_token_data, NETCODE_CHALLENGE_TOKEN_BYTES ) == 0 ); + + free( output_packet ); +} + +void test_connection_keep_alive_packet() +{ + // setup a connection keep alive packet + + struct netcode_connection_keep_alive_packet_t input_packet; + + input_packet.packet_type = NETCODE_CONNECTION_KEEP_ALIVE_PACKET; + input_packet.client_index = 10; + input_packet.max_clients = 16; + + // write the packet to a buffer + + uint8_t buffer[NETCODE_MAX_PACKET_BYTES]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( &input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the packet back in from the buffer + + uint64_t sequence; + + uint8_t allowed_packet_types[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packet_types, 1, sizeof( allowed_packet_types ) ); + + struct netcode_connection_keep_alive_packet_t * output_packet = (struct netcode_connection_keep_alive_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), NULL, allowed_packet_types, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_KEEP_ALIVE_PACKET ); + check( output_packet->client_index == input_packet.client_index ); + check( output_packet->max_clients == input_packet.max_clients ); + + free( output_packet ); +} + +void test_connection_payload_packet() +{ + // setup a connection payload packet + + struct netcode_connection_payload_packet_t * input_packet = netcode_create_payload_packet( NETCODE_MAX_PAYLOAD_BYTES, NULL, NULL ); + + check( input_packet->packet_type == NETCODE_CONNECTION_PAYLOAD_PACKET ); + check( input_packet->payload_bytes == NETCODE_MAX_PAYLOAD_BYTES ); + + netcode_random_bytes( input_packet->payload_data, NETCODE_MAX_PAYLOAD_BYTES ); + + // write the packet to a buffer + + uint8_t buffer[NETCODE_MAX_PACKET_BYTES]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the packet back in from the buffer + + uint64_t sequence; + + uint8_t allowed_packet_types[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packet_types, 1, sizeof( allowed_packet_types ) ); + + struct netcode_connection_payload_packet_t * output_packet = (struct netcode_connection_payload_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), NULL, allowed_packet_types, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_PAYLOAD_PACKET ); + check( output_packet->payload_bytes == input_packet->payload_bytes ); + check( memcmp( output_packet->payload_data, input_packet->payload_data, NETCODE_MAX_PAYLOAD_BYTES ) == 0 ); + + free( input_packet ); + free( output_packet ); +} + +void test_connection_disconnect_packet() +{ + // setup a connection disconnect packet + + struct netcode_connection_disconnect_packet_t input_packet; + + input_packet.packet_type = NETCODE_CONNECTION_DISCONNECT_PACKET; + + // write the packet to a buffer + + uint8_t buffer[NETCODE_MAX_PACKET_BYTES]; + + uint8_t packet_key[NETCODE_KEY_BYTES]; + + netcode_generate_key( packet_key ); + + int bytes_written = netcode_write_packet( &input_packet, buffer, sizeof( buffer ), 1000, packet_key, TEST_PROTOCOL_ID ); + + check( bytes_written > 0 ); + + // read the packet back in from the buffer + + uint64_t sequence; + + uint8_t allowed_packet_types[NETCODE_CONNECTION_NUM_PACKETS]; + memset( allowed_packet_types, 1, sizeof( allowed_packet_types ) ); + + struct netcode_connection_disconnect_packet_t * output_packet = (struct netcode_connection_disconnect_packet_t*) + netcode_read_packet( buffer, bytes_written, &sequence, packet_key, TEST_PROTOCOL_ID, time( NULL ), NULL, allowed_packet_types, NULL, NULL, NULL ); + + check( output_packet ); + + // make sure the read packet matches what was written + + check( output_packet->packet_type == NETCODE_CONNECTION_DISCONNECT_PACKET ); + + free( output_packet ); +} + +void test_connect_token_public() +{ + // generate a private connect token + + struct netcode_address_t server_address; + server_address.type = NETCODE_ADDRESS_IPV4; + server_address.data.ipv4[0] = 127; + server_address.data.ipv4[1] = 0; + server_address.data.ipv4[2] = 0; + server_address.data.ipv4[3] = 1; + server_address.port = TEST_SERVER_PORT; + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes( user_data, NETCODE_USER_DATA_BYTES ); + + struct netcode_connect_token_private_t connect_token_private; + + netcode_generate_connect_token_private( &connect_token_private, TEST_CLIENT_ID, TEST_TIMEOUT_SECONDS, 1, &server_address, user_data ); + + check( connect_token_private.client_id == TEST_CLIENT_ID ); + check( connect_token_private.num_server_addresses == 1 ); + check( memcmp( connect_token_private.user_data, user_data, NETCODE_USER_DATA_BYTES ) == 0 ); + check( netcode_address_equal( &connect_token_private.server_addresses[0], &server_address ) ); + + // write it to a buffer + + uint8_t connect_token_private_data[NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; + netcode_write_connect_token_private( &connect_token_private, connect_token_private_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + + // encrypt the buffer + + uint64_t create_timestamp = time( NULL ); + uint64_t expire_timestamp = create_timestamp + 30; + uint8_t connect_token_nonce[NETCODE_CONNECT_TOKEN_NONCE_BYTES]; + netcode_generate_nonce( connect_token_nonce ); + uint8_t key[NETCODE_KEY_BYTES]; + netcode_generate_key( key ); + check( netcode_encrypt_connect_token_private( connect_token_private_data, + NETCODE_CONNECT_TOKEN_PRIVATE_BYTES, + NETCODE_VERSION_INFO, + TEST_PROTOCOL_ID, + expire_timestamp, + connect_token_nonce, + key ) == 1 ); + + // wrap a public connect token around the private connect token data + + struct netcode_connect_token_t input_connect_token; + memset( &input_connect_token, 0, sizeof( struct netcode_connect_token_t ) ); + memcpy( input_connect_token.version_info, NETCODE_VERSION_INFO, NETCODE_VERSION_INFO_BYTES ); + input_connect_token.protocol_id = TEST_PROTOCOL_ID; + input_connect_token.create_timestamp = create_timestamp; + input_connect_token.expire_timestamp = expire_timestamp; + memcpy( input_connect_token.nonce, connect_token_nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ); + memcpy( input_connect_token.private_data, connect_token_private_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ); + input_connect_token.num_server_addresses = 1; + input_connect_token.server_addresses[0] = server_address; + memcpy( input_connect_token.client_to_server_key, connect_token_private.client_to_server_key, NETCODE_KEY_BYTES ); + memcpy( input_connect_token.server_to_client_key, connect_token_private.server_to_client_key, NETCODE_KEY_BYTES ); + input_connect_token.timeout_seconds = (int) TEST_TIMEOUT_SECONDS; + + // write the connect token to a buffer + + uint8_t buffer[NETCODE_CONNECT_TOKEN_BYTES]; + netcode_write_connect_token( &input_connect_token, buffer, NETCODE_CONNECT_TOKEN_BYTES ); + + // read the buffer back in + + struct netcode_connect_token_t output_connect_token; + memset( &output_connect_token, 0, sizeof( struct netcode_connect_token_t ) ); + check( netcode_read_connect_token( buffer, NETCODE_CONNECT_TOKEN_BYTES, &output_connect_token ) == 1 ); + + // make sure the public connect token matches what was written + + check( memcmp( output_connect_token.version_info, input_connect_token.version_info, NETCODE_VERSION_INFO_BYTES ) == 0 ); + check( output_connect_token.protocol_id == input_connect_token.protocol_id ); + check( output_connect_token.create_timestamp == input_connect_token.create_timestamp ); + check( output_connect_token.expire_timestamp == input_connect_token.expire_timestamp ); + check( memcmp( output_connect_token.nonce, input_connect_token.nonce, NETCODE_CONNECT_TOKEN_NONCE_BYTES ) == 0 ); + check( memcmp( output_connect_token.private_data, input_connect_token.private_data, NETCODE_CONNECT_TOKEN_PRIVATE_BYTES ) == 0 ); + check( output_connect_token.num_server_addresses == input_connect_token.num_server_addresses ); + check( netcode_address_equal( &output_connect_token.server_addresses[0], &input_connect_token.server_addresses[0] ) ); + check( memcmp( output_connect_token.client_to_server_key, input_connect_token.client_to_server_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( output_connect_token.server_to_client_key, input_connect_token.server_to_client_key, NETCODE_KEY_BYTES ) == 0 ); + check( output_connect_token.timeout_seconds == input_connect_token.timeout_seconds ); +} + +void test_encryption_manager() +{ + struct netcode_encryption_manager_t encryption_manager; + + netcode_encryption_manager_reset( &encryption_manager ); + + double time = 100.0; + + // generate some test encryption mappings + + struct encryption_mapping_t + { + struct netcode_address_t address; + uint8_t send_key[NETCODE_KEY_BYTES]; + uint8_t receive_key[NETCODE_KEY_BYTES]; + }; + + #define NUM_ENCRYPTION_MAPPINGS 5 + + struct encryption_mapping_t encryption_mapping[NUM_ENCRYPTION_MAPPINGS]; + memset( encryption_mapping, 0, sizeof( encryption_mapping ) ); + int i; + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + encryption_mapping[i].address.type = NETCODE_ADDRESS_IPV6; + encryption_mapping[i].address.data.ipv6[7] = 1; + encryption_mapping[i].address.port = ( uint16_t) ( 20000 + i ); + netcode_generate_key( encryption_mapping[i].send_key ); + netcode_generate_key( encryption_mapping[i].receive_key ); + } + + // add the encryption mappings to the manager and make sure they can be looked up by address + + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + check( encryption_index == -1 ); + + check( netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ) == NULL ); + check( netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ) == NULL ); + + check( netcode_encryption_manager_add_encryption_mapping( &encryption_manager, + &encryption_mapping[i].address, + encryption_mapping[i].send_key, + encryption_mapping[i].receive_key, + time, + -1.0, + TEST_TIMEOUT_SECONDS ) ); + + encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + uint8_t * send_key = netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ); + uint8_t * receive_key = netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ); + + check( send_key ); + check( receive_key ); + + check( memcmp( send_key, encryption_mapping[i].send_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( receive_key, encryption_mapping[i].receive_key, NETCODE_KEY_BYTES ) == 0 ); + } + + // removing an encryption mapping that doesn't exist should return 0 + { + struct netcode_address_t address; + address.type = NETCODE_ADDRESS_IPV6; + address.data.ipv6[7] = 1; + address.port = 50000; + + check( netcode_encryption_manager_remove_encryption_mapping( &encryption_manager, &address, time ) == 0 ); + } + + // remove the first and last encryption mappings + + check( netcode_encryption_manager_remove_encryption_mapping( &encryption_manager, &encryption_mapping[0].address, time ) == 1 ); + + check( netcode_encryption_manager_remove_encryption_mapping( &encryption_manager, &encryption_mapping[NUM_ENCRYPTION_MAPPINGS-1].address, time ) == 1 ); + + // make sure the encryption mappings that were removed can no longer be looked up by address + + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + uint8_t * send_key = netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ); + uint8_t * receive_key = netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ); + + if ( i != 0 && i != NUM_ENCRYPTION_MAPPINGS - 1 ) + { + check( send_key ); + check( receive_key ); + + check( memcmp( send_key, encryption_mapping[i].send_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( receive_key, encryption_mapping[i].receive_key, NETCODE_KEY_BYTES ) == 0 ); + } + else + { + check( !send_key ); + check( !receive_key ); + } + } + + // add the encryption mappings back in + + check( netcode_encryption_manager_add_encryption_mapping( &encryption_manager, + &encryption_mapping[0].address, + encryption_mapping[0].send_key, + encryption_mapping[0].receive_key, + time, + -1.0, + TEST_TIMEOUT_SECONDS ) ); + + check( netcode_encryption_manager_add_encryption_mapping( &encryption_manager, + &encryption_mapping[NUM_ENCRYPTION_MAPPINGS-1].address, + encryption_mapping[NUM_ENCRYPTION_MAPPINGS-1].send_key, + encryption_mapping[NUM_ENCRYPTION_MAPPINGS-1].receive_key, + time, + -1.0, + TEST_TIMEOUT_SECONDS ) ); + + // all encryption mappings should be able to be looked up by address again + + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + uint8_t * send_key = netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ); + uint8_t * receive_key = netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ); + + check( send_key ); + check( receive_key ); + + check( memcmp( send_key, encryption_mapping[i].send_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( receive_key, encryption_mapping[i].receive_key, NETCODE_KEY_BYTES ) == 0 ); + } + + // check that encryption mappings time out properly + + time += TEST_TIMEOUT_SECONDS * 2; + + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + uint8_t * send_key = netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ); + uint8_t * receive_key = netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ); + + check( !send_key ); + check( !receive_key ); + } + + // add the same encryption mappings after timeout + + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + check( encryption_index == -1 ); + + check( netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ) == NULL ); + check( netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ) == NULL ); + + check( netcode_encryption_manager_add_encryption_mapping( &encryption_manager, + &encryption_mapping[i].address, + encryption_mapping[i].send_key, + encryption_mapping[i].receive_key, + time, + -1.0, + TEST_TIMEOUT_SECONDS ) ); + + encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + uint8_t * send_key = netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ); + uint8_t * receive_key = netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ); + + check( send_key ); + check( receive_key ); + + check( memcmp( send_key, encryption_mapping[i].send_key, NETCODE_KEY_BYTES ) == 0 ); + check( memcmp( receive_key, encryption_mapping[i].receive_key, NETCODE_KEY_BYTES ) == 0 ); + } + + // reset the encryption mapping and verify that all encryption mappings have been removed + + netcode_encryption_manager_reset( &encryption_manager ); + + for ( i = 0; i < NUM_ENCRYPTION_MAPPINGS; ++i ) + { + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[i].address, time ); + + uint8_t * send_key = netcode_encryption_manager_get_send_key( &encryption_manager, encryption_index ); + uint8_t * receive_key = netcode_encryption_manager_get_receive_key( &encryption_manager, encryption_index ); + + check( !send_key ); + check( !receive_key ); + } + + // test the expire time for encryption mapping works as expected + + check( netcode_encryption_manager_add_encryption_mapping( &encryption_manager, + &encryption_mapping[0].address, + encryption_mapping[0].send_key, + encryption_mapping[0].receive_key, + time, + time + 1.0, + TEST_TIMEOUT_SECONDS ) ); + + int encryption_index = netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[0].address, time ); + + check( encryption_index != -1 ); + + check( netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[0].address, time + 1.1f ) == -1 ); + + netcode_encryption_manager_set_expire_time( &encryption_manager, encryption_index, -1.0 ); + + check( netcode_encryption_manager_find_encryption_mapping( &encryption_manager, &encryption_mapping[0].address, time ) == encryption_index ); +} + +void test_replay_protection() +{ + struct netcode_replay_protection_t replay_protection; + + int i; + for ( i = 0; i < 2; ++i ) + { + netcode_replay_protection_reset( &replay_protection ); + + check( replay_protection.most_recent_sequence == 0 ); + + // the first time we receive packets, they should not be already received + + #define MAX_SEQUENCE ( NETCODE_REPLAY_PROTECTION_BUFFER_SIZE * 4 ) + + uint64_t sequence; + for ( sequence = 0; sequence < MAX_SEQUENCE; ++sequence ) + { + check( netcode_replay_protection_already_received( &replay_protection, sequence ) == 0 ); + netcode_replay_protection_advance_sequence( &replay_protection, sequence ); + } + + // old packets outside buffer should be considered already received + + check( netcode_replay_protection_already_received( &replay_protection, 0 ) == 1 ); + + // packets received a second time should be flagged already received + + for ( sequence = MAX_SEQUENCE - 10; sequence < MAX_SEQUENCE; ++sequence ) + { + check( netcode_replay_protection_already_received( &replay_protection, sequence ) == 1 ); + } + + // jumping ahead to a much higher sequence should be considered not already received + + check( netcode_replay_protection_already_received( &replay_protection, MAX_SEQUENCE + NETCODE_REPLAY_PROTECTION_BUFFER_SIZE ) == 0 ); + + // old packets should be considered already received + + for ( sequence = 0; sequence < MAX_SEQUENCE; ++sequence ) + { + check( netcode_replay_protection_already_received( &replay_protection, sequence ) == 1 ); + } + } +} + +void test_client_create() +{ + { + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create( "127.0.0.1:40000", &client_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "127.0.0.1:40000", &test_address ); + + check( client ); + check( client->socket_holder.ipv4.handle != 0 ); + check( client->socket_holder.ipv6.handle == 0 ); + check( netcode_address_equal( &client->address, &test_address ) ); + + netcode_client_destroy( client ); + } + + { + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "[::]:50000", &test_address ); + + check( client ); + check( client->socket_holder.ipv4.handle == 0 ); + check( client->socket_holder.ipv6.handle != 0 ); + check( netcode_address_equal( &client->address, &test_address ) ); + + netcode_client_destroy( client ); + } + + { + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create_overload( "127.0.0.1:40000", "[::]:50000", &client_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "127.0.0.1:40000", &test_address ); + + check( client ); + check( client->socket_holder.ipv4.handle != 0 ); + check( client->socket_holder.ipv6.handle != 0 ); + check( netcode_address_equal( &client->address, &test_address ) ); + + netcode_client_destroy( client ); + } + + { + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create_overload( "[::]:50000", "127.0.0.1:40000", &client_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "[::]:50000", &test_address ); + + check( client ); + check( client->socket_holder.ipv4.handle != 0 ); + check( client->socket_holder.ipv6.handle != 0 ); + check( netcode_address_equal( &client->address, &test_address ) ); + + netcode_client_destroy( client ); + } +} + +void test_server_create() +{ + { + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + + struct netcode_server_t * server = netcode_server_create( "127.0.0.1:40000", &server_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "127.0.0.1:40000", &test_address ); + + check( server ); + check( server->socket_holder.ipv4.handle != 0 ); + check( server->socket_holder.ipv6.handle == 0 ); + check( netcode_address_equal( &server->address, &test_address ) ); + + netcode_server_destroy( server ); + } + + { + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:50000", &server_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "[::1]:50000", &test_address ); + + check( server ); + check( server->socket_holder.ipv4.handle == 0 ); + check( server->socket_holder.ipv6.handle != 0 ); + check( netcode_address_equal( &server->address, &test_address ) ); + + netcode_server_destroy( server ); + } + + { + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + + struct netcode_server_t * server = netcode_server_create_overload( "127.0.0.1:40000", "[::1]:50000", &server_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "127.0.0.1:40000", &test_address ); + + check( server ); + check( server->socket_holder.ipv4.handle != 0 ); + check( server->socket_holder.ipv6.handle != 0 ); + check( netcode_address_equal( &server->address, &test_address ) ); + + netcode_server_destroy( server ); + } + + { + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + + struct netcode_server_t * server = netcode_server_create_overload( "[::1]:50000", "127.0.0.1:40000", &server_config, 0.0 ); + + struct netcode_address_t test_address; + netcode_parse_address( "[::1]:50000", &test_address ); + + check( server ); + check( server->socket_holder.ipv4.handle != 0 ); + check( server->socket_holder.ipv6.handle != 0 ); + check( netcode_address_equal( &server->address, &test_address ) ); + + netcode_server_destroy( server ); + } +} + +static uint8_t private_key[NETCODE_KEY_BYTES] = { 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea, + 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4, + 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26, + 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1 }; + +void test_client_server_connect() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + int server_num_packets_received = 0; + int client_num_packets_received = 0; + + uint8_t packet_data[NETCODE_MAX_PACKET_SIZE]; + int i; + for ( i = 0; i < NETCODE_MAX_PACKET_SIZE; ++i ) + packet_data[i] = (uint8_t) i; + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + netcode_client_send_packet( client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 0, packet_data, NETCODE_MAX_PACKET_SIZE ); + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + client_num_packets_received++; + netcode_client_free_packet( client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 0, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + if ( client_num_packets_received >= 10 && server_num_packets_received >= 10 ) + { + if ( netcode_server_client_connected( server, 0 ) ) + { + netcode_server_disconnect_client( server, 0 ); + } + } + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( client_num_packets_received >= 10 && server_num_packets_received >= 10 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_server_ipv4_socket_connect() +{ + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create( "0.0.0.0:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "127.0.0.1:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "127.0.0.1:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } + + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create( "0.0.0.0:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create_overload( "127.0.0.1:40000", "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "127.0.0.1:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } + + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create_overload( "0.0.0.0:50000", "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "127.0.0.1:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "127.0.0.1:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } + + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create_overload( "0.0.0.0:50000", "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create_overload( "127.0.0.1:40000", "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "127.0.0.1:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } +} + +void test_client_server_ipv6_socket_connect() +{ + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } + + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create_overload( "127.0.0.1:40000", "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } + + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create_overload( "0.0.0.0:50000", "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } + + { + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + + struct netcode_client_t * client = netcode_client_create_overload( "0.0.0.0:50000", "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create_overload( "127.0.0.1:40000", "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + } +} + +void test_client_server_keep_alive() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + // connect client to server + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // pump the client and server long enough that they would timeout without keep alive packets + + int num_iterations = (int) ceil( 1.25f * TEST_TIMEOUT_SECONDS / delta_time ); + + int i; + for ( i = 0; i < num_iterations; ++i ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_server_multiple_clients() +{ + #define NUM_START_STOP_ITERATIONS 3 + + int max_clients[NUM_START_STOP_ITERATIONS] = { 2, 32, 5 }; + + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + int i; + for ( i = 0; i < NUM_START_STOP_ITERATIONS; ++i ) + { + // start the server with max # of clients for this iteration + + netcode_server_start( server, max_clients[i] ); + + // create # of client objects for this iteration and connect to server + + struct netcode_client_t ** client = (struct netcode_client_t **) malloc( sizeof( struct netcode_client_t* ) * max_clients[i] ); + + check( client ); + + int j; + for ( j = 0; j < max_clients[i]; ++j ) + { + char client_address[NETCODE_MAX_ADDRESS_STRING_LENGTH]; + sprintf( client_address, "[::]:%d", 50000 + j ); + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + client[j] = netcode_client_create( client_address, &client_config, time ); + + check( client[j] ); + + uint64_t client_id = j; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, + &server_address, + &server_address, + TEST_CONNECT_TOKEN_EXPIRY, + TEST_TIMEOUT_SECONDS, + client_id, + TEST_PROTOCOL_ID, + private_key, + user_data, + connect_token ) ); + + netcode_client_connect( client[j], connect_token ); + } + + // make sure all clients can connect + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + for ( j = 0; j < max_clients[i]; ++j ) + { + netcode_client_update( client[j], time ); + } + + netcode_server_update( server, time ); + + int num_connected_clients = 0; + + for ( j = 0; j < max_clients[i]; ++j ) + { + if ( netcode_client_state( client[j] ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client[j] ) == NETCODE_CLIENT_STATE_CONNECTED ) + num_connected_clients++; + } + + if ( num_connected_clients == max_clients[i] ) + break; + + time += delta_time; + } + + check( netcode_server_num_connected_clients( server ) == max_clients[i] ); + + for ( j = 0; j < max_clients[i]; ++j ) + { + check( netcode_client_state( client[j] ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_server_client_connected( server, j ) == 1 ); + } + + // make sure all clients can exchange packets with the server + + int * server_num_packets_received = (int*) malloc( sizeof(int) * max_clients[i] ); + int * client_num_packets_received = (int*) malloc( sizeof(int) * max_clients[i] ); + + memset( server_num_packets_received, 0, sizeof(int) * max_clients[i] ); + memset( client_num_packets_received, 0, sizeof(int) * max_clients[i] ); + + uint8_t packet_data[NETCODE_MAX_PACKET_SIZE]; + for ( j = 0; j < NETCODE_MAX_PACKET_SIZE; ++j ) + packet_data[j] = (uint8_t) j; + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + for ( j = 0; j < max_clients[i]; ++j ) + { + netcode_client_update( client[j], time ); + } + + netcode_server_update( server, time ); + + for ( j = 0; j < max_clients[i]; ++j ) + { + netcode_client_send_packet( client[j], packet_data, NETCODE_MAX_PACKET_SIZE ); + } + + for ( j = 0; j < max_clients[i]; ++j ) + { + netcode_server_send_packet( server, j, packet_data, NETCODE_MAX_PACKET_SIZE ); + } + + for ( j = 0; j < max_clients[i]; ++j ) + { + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( client[j], &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + client_num_packets_received[j]++; + netcode_client_free_packet( client[j], packet ); + } + } + + for ( j = 0; j < max_clients[i]; ++j ) + { + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, j, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + server_num_packets_received[j]++; + netcode_server_free_packet( server, packet ); + } + } + + int num_clients_ready = 0; + + for ( j = 0; j < max_clients[i]; ++j ) + { + if ( client_num_packets_received[j] >= 1 && server_num_packets_received[j] >= 1 ) + { + num_clients_ready++; + } + } + + if ( num_clients_ready == max_clients[i] ) + break; + + for ( j = 0; j < max_clients[i]; ++j ) + { + if ( netcode_client_state( client[j] ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + } + + time += delta_time; + } + + int num_clients_ready = 0; + + for ( j = 0; j < max_clients[i]; ++j ) + { + if ( client_num_packets_received[j] >= 1 && server_num_packets_received[j] >= 1 ) + { + num_clients_ready++; + } + } + + check( num_clients_ready == max_clients[i] ); + + free( server_num_packets_received ); + free( client_num_packets_received ); + + netcode_network_simulator_reset( network_simulator ); + + for ( j = 0; j < max_clients[i]; ++j ) + { + netcode_client_destroy( client[j] ); + } + + free( client ); + + netcode_server_stop( server ); + } + + netcode_server_destroy( server ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_server_multiple_servers() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address[] = { "10.10.10.10:1000", "100.100.100.100:50000", "[::1]:40000" }; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 3, server_address, server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + int server_num_packets_received = 0; + int client_num_packets_received = 0; + + uint8_t packet_data[NETCODE_MAX_PACKET_SIZE]; + int i; + for ( i = 0; i < NETCODE_MAX_PACKET_SIZE; ++i ) + packet_data[i] = (uint8_t) i; + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + netcode_client_send_packet( client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 0, packet_data, NETCODE_MAX_PACKET_SIZE ); + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + client_num_packets_received++; + netcode_client_free_packet( client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 0, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + if ( client_num_packets_received >= 10 && server_num_packets_received >= 10 ) + { + if ( netcode_server_client_connected( server, 0 ) ) + { + netcode_server_disconnect_client( server, 0 ); + } + } + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( client_num_packets_received >= 10 && server_num_packets_received >= 10 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_error_connect_token_expired() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, 0, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + netcode_client_update( client, time ); + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_error_invalid_connect_token() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + netcode_random_bytes( connect_token, NETCODE_CONNECT_TOKEN_BYTES ); + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + netcode_client_connect( client, connect_token ); + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_error_connection_timed_out() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + // connect a client to the server + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // now disable updating the server and verify that the client times out + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_error_connection_response_timeout() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + server->flags = NETCODE_SERVER_FLAG_IGNORE_CONNECTION_RESPONSE_PACKETS; + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_error_connection_request_timeout() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 60.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + server->flags = NETCODE_SERVER_FLAG_IGNORE_CONNECTION_REQUEST_PACKETS; + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_error_connection_denied() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + // start a server and connect one client + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // now attempt to connect a second client. the connection should be denied. + + struct netcode_client_t * client2 = netcode_client_create( "[::]:50001", &client_config, time ); + + check( client2 ); + + uint8_t connect_token2[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id2 = 0; + netcode_random_bytes( (uint8_t*) &client_id2, 8 ); + + uint8_t user_data2[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data2, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id2, TEST_PROTOCOL_ID, private_key, user_data2, connect_token2 ) ); + + netcode_client_connect( client2, connect_token2 ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_client_update( client2, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client2 ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_state( client2 ) == NETCODE_CLIENT_STATE_CONNECTION_DENIED ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_client_destroy( client2 ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_side_disconnect() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + // start a server and connect one client + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // disconnect client side and verify that the server sees that client disconnect cleanly, rather than timing out. + + netcode_client_disconnect( client ); + + int i; + for ( i = 0; i < 10; ++i ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_server_client_connected( server, 0 ) == 0 ) + break; + + time += delta_time; + } + + check( netcode_server_client_connected( server, 0 ) == 0 ); + check( netcode_server_num_connected_clients( server ) == 0 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_server_side_disconnect() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + // start a server and connect one client + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // disconnect server side and verify that the client disconnects cleanly, rather than timing out. + + netcode_server_disconnect_client( server, 0 ); + + int i; + for ( i = 0; i < 10; ++i ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_DISCONNECTED ); + check( netcode_server_client_connected( server, 0 ) == 0 ); + check( netcode_server_num_connected_clients( server ) == 0 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_client_reconnect() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + // start a server and connect one client + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // disconnect client on the server-side and wait until client sees the disconnect + + netcode_network_simulator_reset( network_simulator ); + + netcode_server_disconnect_client( server, 0 ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_DISCONNECTED ); + check( netcode_server_client_connected( server, 0 ) == 0 ); + check( netcode_server_num_connected_clients( server ) == 0 ); + + // now reconnect the client and verify they connect + + netcode_network_simulator_reset( network_simulator ); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +struct test_loopback_context_t +{ + struct netcode_client_t * client; + struct netcode_server_t * server; + int num_loopback_packets_sent_to_client; + int num_loopback_packets_sent_to_server; +}; + +void client_send_loopback_packet_callback( void * _context, int client_index, NETCODE_CONST uint8_t * packet_data, int packet_bytes, uint64_t packet_sequence ) +{ + (void) packet_sequence; + check( _context ); + check( client_index == 0 ); + check( packet_data ); + check( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + int i; + for ( i = 0; i < packet_bytes; ++i ) + { + check( packet_data[i] == (uint8_t) i ); + } + struct test_loopback_context_t * context = (struct test_loopback_context_t*) _context; + context->num_loopback_packets_sent_to_server++; + netcode_server_process_loopback_packet( context->server, client_index, packet_data, packet_bytes, packet_sequence ); +} + +void server_send_loopback_packet_callback( void * _context, int client_index, NETCODE_CONST uint8_t * packet_data, int packet_bytes, uint64_t packet_sequence ) +{ + (void) packet_sequence; + check( _context ); + check( client_index == 0 ); + check( packet_data ); + check( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + int i; + for ( i = 0; i < packet_bytes; ++i ) + { + check( packet_data[i] == (uint8_t) i ); + } + struct test_loopback_context_t * context = (struct test_loopback_context_t*) _context; + context->num_loopback_packets_sent_to_client++; + netcode_client_process_loopback_packet( context->client, packet_data, packet_bytes, packet_sequence ); +} + +void test_disable_timeout() +{ + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.network_simulator = network_simulator; + + struct netcode_client_t * client = netcode_client_create( "[::]:50000", &client_config, time ); + + check( client ); + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + netcode_server_start( server, 1 ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, -1, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( client ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + int server_num_packets_received = 0; + int client_num_packets_received = 0; + + uint8_t packet_data[NETCODE_MAX_PACKET_SIZE]; + int i; + for ( i = 0; i < NETCODE_MAX_PACKET_SIZE; ++i ) + packet_data[i] = (uint8_t) i; + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( client, time ); + + netcode_server_update( server, time ); + + netcode_client_send_packet( client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 0, packet_data, NETCODE_MAX_PACKET_SIZE ); + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + client_num_packets_received++; + netcode_client_free_packet( client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 0, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + if ( client_num_packets_received >= 10 && server_num_packets_received >= 10 ) + { + if ( netcode_server_client_connected( server, 0 ) ) + { + netcode_server_disconnect_client( server, 0 ); + } + } + + if ( netcode_client_state( client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += 1000.0f; // normally this would timeout the client + } + + check( client_num_packets_received >= 10 && server_num_packets_received >= 10 ); + + netcode_server_destroy( server ); + + netcode_client_destroy( client ); + + netcode_network_simulator_destroy( network_simulator ); +} + +void test_loopback() +{ + struct test_loopback_context_t context; + memset( &context, 0, sizeof( context ) ); + + struct netcode_network_simulator_t * network_simulator = netcode_network_simulator_create( NULL, NULL, NULL ); + + network_simulator->latency_milliseconds = 250; + network_simulator->jitter_milliseconds = 250; + network_simulator->packet_loss_percent = 5; + network_simulator->duplicate_packet_percent = 10; + + double time = 0.0; + double delta_time = 1.0 / 10.0; + + // start the server + + struct netcode_server_config_t server_config; + netcode_default_server_config( &server_config ); + server_config.protocol_id = TEST_PROTOCOL_ID; + server_config.network_simulator = network_simulator; + server_config.callback_context = &context; + server_config.send_loopback_packet_callback = server_send_loopback_packet_callback; + memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES ); + + struct netcode_server_t * server = netcode_server_create( "[::1]:40000", &server_config, time ); + + check( server ); + + int max_clients = 2; + + netcode_server_start( server, max_clients ); + + context.server = server; + + // connect a loopback client in slot 0 + + struct netcode_client_config_t client_config; + netcode_default_client_config( &client_config ); + client_config.callback_context = &context; + client_config.send_loopback_packet_callback = client_send_loopback_packet_callback; + client_config.network_simulator = network_simulator; + + struct netcode_client_t * loopback_client = netcode_client_create( "[::]:50000", &client_config, time ); + check( loopback_client ); + netcode_client_connect_loopback( loopback_client, 0, max_clients ); + context.client = loopback_client; + + check( netcode_client_index( loopback_client ) == 0 ); + check( netcode_client_loopback( loopback_client ) == 1 ); + check( netcode_client_max_clients( loopback_client ) == max_clients ); + check( netcode_client_state( loopback_client ) == NETCODE_CLIENT_STATE_CONNECTED ); + + uint64_t client_id = 0; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + netcode_server_connect_loopback_client( server, 0, client_id, NULL ); + + check( netcode_server_client_loopback( server, 0 ) == 1 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + // connect a regular client in the other slot + + struct netcode_client_t * regular_client = netcode_client_create( "[::]:50001", &client_config, time ); + + check( regular_client ); + + NETCODE_CONST char * server_address = "[::1]:40000"; + + uint8_t connect_token[NETCODE_CONNECT_TOKEN_BYTES]; + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + + uint8_t user_data[NETCODE_USER_DATA_BYTES]; + netcode_random_bytes(user_data, NETCODE_USER_DATA_BYTES); + + check( netcode_generate_connect_token( 1, &server_address, &server_address, TEST_CONNECT_TOKEN_EXPIRY, TEST_TIMEOUT_SECONDS, client_id, TEST_PROTOCOL_ID, private_key, user_data, connect_token ) ); + + netcode_client_connect( regular_client, connect_token ); + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( regular_client, time ); + + netcode_server_update( server, time ); + + if ( netcode_client_state( regular_client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + if ( netcode_client_state( regular_client ) == NETCODE_CLIENT_STATE_CONNECTED ) + break; + + time += delta_time; + } + + check( netcode_client_state( regular_client ) == NETCODE_CLIENT_STATE_CONNECTED ); + check( netcode_client_index( regular_client ) == 1 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_client_connected( server, 1 ) == 1 ); + check( netcode_server_client_loopback( server, 0 ) == 1 ); + check( netcode_server_client_loopback( server, 1 ) == 0 ); + check( netcode_server_num_connected_clients( server ) == 2 ); + + // test that we can exchange packets for the regular client and the loopback client + + int loopback_client_num_packets_received = 0; + int loopback_server_num_packets_received = 0; + int regular_server_num_packets_received = 0; + int regular_client_num_packets_received = 0; + + uint8_t packet_data[NETCODE_MAX_PACKET_SIZE]; + int i; + for ( i = 0; i < NETCODE_MAX_PACKET_SIZE; ++i ) + packet_data[i] = (uint8_t) i; + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( regular_client, time ); + + netcode_server_update( server, time ); + + netcode_client_send_packet( loopback_client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_client_send_packet( regular_client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 0, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 1, packet_data, NETCODE_MAX_PACKET_SIZE ); + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( loopback_client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + loopback_client_num_packets_received++; + netcode_client_free_packet( loopback_client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( regular_client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + regular_client_num_packets_received++; + netcode_client_free_packet( regular_client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 0, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + loopback_server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 1, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + regular_server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + if ( loopback_client_num_packets_received >= 10 && loopback_server_num_packets_received >= 10 && + regular_client_num_packets_received >= 10 && regular_server_num_packets_received >= 10 ) + break; + + if ( netcode_client_state( regular_client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( loopback_client_num_packets_received >= 10 ); + check( loopback_server_num_packets_received >= 10 ); + check( regular_client_num_packets_received >= 10 ); + check( regular_server_num_packets_received >= 10 ); + check( context.num_loopback_packets_sent_to_client >= 10 ); + check( context.num_loopback_packets_sent_to_server >= 10 ); + + // verify that we can disconnect the loopback client + + check( netcode_server_client_loopback( server, 0 ) == 1 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 2 ); + + netcode_server_disconnect_loopback_client( server, 0 ); + + check( netcode_server_client_loopback( server, 0 ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 0 ); + check( netcode_server_num_connected_clients( server ) == 1 ); + + netcode_client_disconnect_loopback( loopback_client ); + + check( netcode_client_state( loopback_client ) == NETCODE_CLIENT_STATE_DISCONNECTED ); + + // verify that we can reconnect the loopback client + + netcode_random_bytes( (uint8_t*) &client_id, 8 ); + netcode_server_connect_loopback_client( server, 0, client_id, NULL ); + + check( netcode_server_client_loopback( server, 0 ) == 1 ); + check( netcode_server_client_loopback( server, 1 ) == 0 ); + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_client_connected( server, 1 ) == 1 ); + check( netcode_server_num_connected_clients( server ) == 2 ); + + netcode_client_connect_loopback( loopback_client, 0, max_clients ); + + check( netcode_client_index( loopback_client ) == 0 ); + check( netcode_client_loopback( loopback_client ) == 1 ); + check( netcode_client_max_clients( loopback_client ) == max_clients ); + check( netcode_client_state( loopback_client ) == NETCODE_CLIENT_STATE_CONNECTED ); + + // verify that we can exchange packets for both regular and loopback client post reconnect + + loopback_server_num_packets_received = 0; + loopback_client_num_packets_received = 0; + regular_server_num_packets_received = 0; + regular_client_num_packets_received = 0; + context.num_loopback_packets_sent_to_client = 0; + context.num_loopback_packets_sent_to_server = 0; + + while ( 1 ) + { + netcode_network_simulator_update( network_simulator, time ); + + netcode_client_update( regular_client, time ); + + netcode_server_update( server, time ); + + netcode_client_send_packet( loopback_client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_client_send_packet( regular_client, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 0, packet_data, NETCODE_MAX_PACKET_SIZE ); + + netcode_server_send_packet( server, 1, packet_data, NETCODE_MAX_PACKET_SIZE ); + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( loopback_client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + loopback_client_num_packets_received++; + netcode_client_free_packet( loopback_client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + uint8_t * packet = netcode_client_receive_packet( regular_client, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + regular_client_num_packets_received++; + netcode_client_free_packet( regular_client, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 0, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + loopback_server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + while ( 1 ) + { + int packet_bytes; + uint64_t packet_sequence; + void * packet = netcode_server_receive_packet( server, 1, &packet_bytes, &packet_sequence ); + if ( !packet ) + break; + (void) packet_sequence; + netcode_assert( packet_bytes == NETCODE_MAX_PACKET_SIZE ); + netcode_assert( memcmp( packet, packet_data, NETCODE_MAX_PACKET_SIZE ) == 0 ); + regular_server_num_packets_received++; + netcode_server_free_packet( server, packet ); + } + + if ( loopback_client_num_packets_received >= 10 && loopback_server_num_packets_received >= 10 && + regular_client_num_packets_received >= 10 && regular_server_num_packets_received >= 10 ) + break; + + if ( netcode_client_state( regular_client ) <= NETCODE_CLIENT_STATE_DISCONNECTED ) + break; + + time += delta_time; + } + + check( loopback_client_num_packets_received >= 10 ); + check( loopback_server_num_packets_received >= 10 ); + check( regular_client_num_packets_received >= 10 ); + check( regular_server_num_packets_received >= 10 ); + check( context.num_loopback_packets_sent_to_client >= 10 ); + check( context.num_loopback_packets_sent_to_server >= 10 ); + + // verify the regular client times out but loopback client doesn't + + time += 100000.0; + + netcode_server_update( server, time ); + + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_client_connected( server, 1 ) == 0 ); + + netcode_client_update( loopback_client, time ); + + check( netcode_client_state( loopback_client ) == NETCODE_CLIENT_STATE_CONNECTED ); + + // verify that disconnect all clients leaves loopback clients alone + + netcode_server_disconnect_all_clients( server ); + + check( netcode_server_client_connected( server, 0 ) == 1 ); + check( netcode_server_client_connected( server, 1 ) == 0 ); + check( netcode_server_client_loopback( server, 0 ) == 1 ); + + // clean up + + netcode_client_destroy( regular_client ); + + netcode_client_destroy( loopback_client ); + + netcode_server_destroy( server ); + + netcode_network_simulator_destroy( network_simulator ); +} + +#define RUN_TEST( test_function ) \ + do \ + { \ + printf( #test_function "\n" ); \ + test_function(); \ + } \ + while (0) + +void netcode_test() +{ + //while ( 1 ) + { + RUN_TEST( test_queue ); + RUN_TEST( test_endian ); + RUN_TEST( test_address ); + RUN_TEST( test_sequence ); + RUN_TEST( test_connect_token ); + RUN_TEST( test_challenge_token ); + RUN_TEST( test_connection_request_packet ); + RUN_TEST( test_connection_denied_packet ); + RUN_TEST( test_connection_challenge_packet ); + RUN_TEST( test_connection_response_packet ); + RUN_TEST( test_connection_payload_packet ); + RUN_TEST( test_connection_disconnect_packet ); + RUN_TEST( test_connect_token_public ); + RUN_TEST( test_encryption_manager ); + RUN_TEST( test_replay_protection ); + RUN_TEST( test_client_create ); + RUN_TEST( test_server_create ); + RUN_TEST( test_client_server_connect ); + RUN_TEST( test_client_server_ipv4_socket_connect ); + RUN_TEST( test_client_server_ipv6_socket_connect ); + RUN_TEST( test_client_server_keep_alive ); + RUN_TEST( test_client_server_multiple_clients ); + RUN_TEST( test_client_server_multiple_servers ); + RUN_TEST( test_client_error_connect_token_expired ); + RUN_TEST( test_client_error_invalid_connect_token ); + RUN_TEST( test_client_error_connection_timed_out ); + RUN_TEST( test_client_error_connection_response_timeout ); + RUN_TEST( test_client_error_connection_request_timeout ); + RUN_TEST( test_client_error_connection_denied ); + RUN_TEST( test_client_side_disconnect ); + RUN_TEST( test_server_side_disconnect ); + RUN_TEST( test_client_reconnect ); + RUN_TEST( test_disable_timeout ); + RUN_TEST( test_loopback ); + } +} + +#endif // #if NETCODE_ENABLE_TESTS
+ netcode.io/netcode.h view
@@ -0,0 +1,282 @@+/* + netcode.io reference implementation + + Copyright © 2017 - 2019, The Network Protocol Company, Inc. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef NETCODE_H +#define NETCODE_H + +#include <stdint.h> + +#if defined(__386__) || defined(i386) || defined(__i386__) \ + || defined(__X86) || defined(_M_IX86) \ + || defined(_M_X64) || defined(__x86_64__) \ + || defined(alpha) || defined(__alpha) || defined(__alpha__) \ + || defined(_M_ALPHA) \ + || defined(ARM) || defined(_ARM) || defined(__arm__) \ + || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ + || defined(_WIN32_WCE) || defined(__NT__) \ + || defined(__MIPSEL__) + #define NETCODE_LITTLE_ENDIAN 1 +#else + #define NETCODE_BIG_ENDIAN 1 +#endif + +#define NETCODE_PLATFORM_WINDOWS 1 +#define NETCODE_PLATFORM_MAC 2 +#define NETCODE_PLATFORM_UNIX 3 + +#if defined(_WIN32) +#define NETCODE_PLATFORM NETCODE_PLATFORM_WINDOWS +#elif defined(__APPLE__) +#define NETCODE_PLATFORM NETCODE_PLATFORM_MAC +#else +#define NETCODE_PLATFORM NETCODE_PLATFORM_UNIX +#endif + +#define NETCODE_CONNECT_TOKEN_BYTES 2048 +#define NETCODE_KEY_BYTES 32 +#define NETCODE_MAC_BYTES 16 +#define NETCODE_USER_DATA_BYTES 256 +#define NETCODE_MAX_SERVERS_PER_CONNECT 32 + +#define NETCODE_CLIENT_STATE_CONNECT_TOKEN_EXPIRED -6 +#define NETCODE_CLIENT_STATE_INVALID_CONNECT_TOKEN -5 +#define NETCODE_CLIENT_STATE_CONNECTION_TIMED_OUT -4 +#define NETCODE_CLIENT_STATE_CONNECTION_RESPONSE_TIMED_OUT -3 +#define NETCODE_CLIENT_STATE_CONNECTION_REQUEST_TIMED_OUT -2 +#define NETCODE_CLIENT_STATE_CONNECTION_DENIED -1 +#define NETCODE_CLIENT_STATE_DISCONNECTED 0 +#define NETCODE_CLIENT_STATE_SENDING_CONNECTION_REQUEST 1 +#define NETCODE_CLIENT_STATE_SENDING_CONNECTION_RESPONSE 2 +#define NETCODE_CLIENT_STATE_CONNECTED 3 + +#define NETCODE_MAX_CLIENTS 256 +#define NETCODE_MAX_PACKET_SIZE 1200 + +#define NETCODE_LOG_LEVEL_NONE 0 +#define NETCODE_LOG_LEVEL_ERROR 1 +#define NETCODE_LOG_LEVEL_INFO 2 +#define NETCODE_LOG_LEVEL_DEBUG 3 + +#define NETCODE_OK 1 +#define NETCODE_ERROR 0 + +#define NETCODE_ADDRESS_NONE 0 +#define NETCODE_ADDRESS_IPV4 1 +#define NETCODE_ADDRESS_IPV6 2 + +#ifdef __cplusplus +#define NETCODE_CONST const +extern "C" { +#else +#if defined(__STDC__) +#define NETCODE_CONST const +#else +#define NETCODE_CONST +#endif +#endif + +int netcode_init(); + +void netcode_term(); + +struct netcode_address_t +{ + union { uint8_t ipv4[4]; uint16_t ipv6[8]; } data; + uint16_t port; + uint8_t type; +}; + +int netcode_parse_address( NETCODE_CONST char * address_string_in, struct netcode_address_t * address ); + +char * netcode_address_to_string( struct netcode_address_t * address, char * buffer ); + +int netcode_address_equal( struct netcode_address_t * a, struct netcode_address_t * b ); + +struct netcode_client_config_t +{ + void * allocator_context; + void * (*allocate_function)(void*,uint64_t); + void (*free_function)(void*,void*); + struct netcode_network_simulator_t * network_simulator; + void * callback_context; + void (*state_change_callback)(void*,int,int); + void (*send_loopback_packet_callback)(void*,int,NETCODE_CONST uint8_t*,int,uint64_t); + int override_send_and_receive; + void (*send_packet_override)(void*,struct netcode_address_t*,NETCODE_CONST uint8_t*,int); + int (*receive_packet_override)(void*,struct netcode_address_t*,uint8_t*,int); +}; + +void netcode_default_client_config( struct netcode_client_config_t * config ); + +struct netcode_client_t * netcode_client_create( NETCODE_CONST char * address, NETCODE_CONST struct netcode_client_config_t * config, double time ); + +void netcode_client_destroy( struct netcode_client_t * client ); + +void netcode_client_connect( struct netcode_client_t * client, uint8_t * connect_token ); + +void netcode_client_update( struct netcode_client_t * client, double time ); + +uint64_t netcode_client_next_packet_sequence( struct netcode_client_t * client ); + +void netcode_client_send_packet( struct netcode_client_t * client, NETCODE_CONST uint8_t * packet_data, int packet_bytes ); + +uint8_t * netcode_client_receive_packet( struct netcode_client_t * client, int * packet_bytes, uint64_t * packet_sequence ); + +void netcode_client_free_packet( struct netcode_client_t * client, void * packet ); + +void netcode_client_disconnect( struct netcode_client_t * client ); + +int netcode_client_state( struct netcode_client_t * client ); + +int netcode_client_index( struct netcode_client_t * client ); + +int netcode_client_max_clients( struct netcode_client_t * client ); + +void netcode_client_connect_loopback( struct netcode_client_t * client, int client_index, int max_clients ); + +void netcode_client_disconnect_loopback( struct netcode_client_t * client ); + +void netcode_client_process_packet( struct netcode_client_t * client, struct netcode_address_t * from, uint8_t * packet_data, int packet_bytes ); + +int netcode_client_loopback( struct netcode_client_t * client ); + +void netcode_client_process_loopback_packet( struct netcode_client_t * client, NETCODE_CONST uint8_t * packet_data, int packet_bytes, uint64_t packet_sequence ); + +uint16_t netcode_client_get_port( struct netcode_client_t * client ); + +struct netcode_address_t * netcode_client_server_address( struct netcode_client_t * client ); + +int netcode_generate_connect_token( int num_server_addresses, + NETCODE_CONST char ** public_server_addresses, + NETCODE_CONST char ** internal_server_addresses, + int expire_seconds, + int timeout_seconds, + uint64_t client_id, + uint64_t protocol_id, + NETCODE_CONST uint8_t * private_key, + uint8_t * user_data, + uint8_t * connect_token ); + +struct netcode_server_config_t +{ + uint64_t protocol_id; + uint8_t private_key[NETCODE_KEY_BYTES]; + void * allocator_context; + void * (*allocate_function)(void*,uint64_t); + void (*free_function)(void*,void*); + struct netcode_network_simulator_t * network_simulator; + void * callback_context; + void (*connect_disconnect_callback)(void*,int,int); + void (*send_loopback_packet_callback)(void*,int,NETCODE_CONST uint8_t*,int,uint64_t); + int override_send_and_receive; + void (*send_packet_override)(void*,struct netcode_address_t*,NETCODE_CONST uint8_t*,int); + int (*receive_packet_override)(void*,struct netcode_address_t*,uint8_t*,int); +}; + +void netcode_default_server_config( struct netcode_server_config_t * config ); + +struct netcode_server_t * netcode_server_create( NETCODE_CONST char * server_address, NETCODE_CONST struct netcode_server_config_t * config, double time ); + +void netcode_server_destroy( struct netcode_server_t * server ); + +void netcode_server_start( struct netcode_server_t * server, int max_clients ); + +void netcode_server_stop( struct netcode_server_t * server ); + +int netcode_server_running( struct netcode_server_t * server ); + +int netcode_server_max_clients( struct netcode_server_t * server ); + +void netcode_server_update( struct netcode_server_t * server, double time ); + +int netcode_server_client_connected( struct netcode_server_t * server, int client_index ); + +uint64_t netcode_server_client_id( struct netcode_server_t * server, int client_index ); + +struct netcode_address_t * netcode_server_client_address( struct netcode_server_t * server, int client_index ); + +void netcode_server_disconnect_client( struct netcode_server_t * server, int client_index ); + +void netcode_server_disconnect_all_clients( struct netcode_server_t * server ); + +uint64_t netcode_server_next_packet_sequence( struct netcode_server_t * server, int client_index ); + +void netcode_server_send_packet( struct netcode_server_t * server, int client_index, NETCODE_CONST uint8_t * packet_data, int packet_bytes ); + +uint8_t * netcode_server_receive_packet( struct netcode_server_t * server, int client_index, int * packet_bytes, uint64_t * packet_sequence ); + +void netcode_server_free_packet( struct netcode_server_t * server, void * packet ); + +int netcode_server_num_connected_clients( struct netcode_server_t * server ); + +void * netcode_server_client_user_data( struct netcode_server_t * server, int client_index ); + +void netcode_server_process_packet( struct netcode_server_t * server, struct netcode_address_t * from, uint8_t * packet_data, int packet_bytes ); + +void netcode_server_connect_loopback_client( struct netcode_server_t * server, int client_index, uint64_t client_id, NETCODE_CONST uint8_t * user_data ); + +void netcode_server_disconnect_loopback_client( struct netcode_server_t * server, int client_index ); + +int netcode_server_client_loopback( struct netcode_server_t * server, int client_index ); + +void netcode_server_process_loopback_packet( struct netcode_server_t * server, int client_index, NETCODE_CONST uint8_t * packet_data, int packet_bytes, uint64_t packet_sequence ); + +uint16_t netcode_server_get_port( struct netcode_server_t * server ); + +void netcode_log_level( int level ); + +void netcode_set_printf_function( int (*function)( NETCODE_CONST char *, ... ) ); + +extern void (*netcode_assert_function)( NETCODE_CONST char *, NETCODE_CONST char *, NETCODE_CONST char * file, int line ); + +#ifndef NDEBUG +#define netcode_assert( condition ) \ +do \ +{ \ + if ( !(condition) ) \ + { \ + netcode_assert_function( #condition, __FUNCTION__, __FILE__, __LINE__ ); \ + exit(1); \ + } \ +} while(0) +#else +#define netcode_assert( ignore ) ((void)0) +#endif + +void netcode_set_assert_function( void (*function)( NETCODE_CONST char * /*condition*/, + NETCODE_CONST char * /*function*/, + NETCODE_CONST char * /*file*/, + int /*line*/ ) ); + +void netcode_random_bytes( uint8_t * data, int bytes ); + +void netcode_sleep( double seconds ); + +double netcode_time(); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef NETCODE_H