packages feed

netcode-io 0.0.2 → 0.0.3

raw patch · 21 files changed

+12068/−11464 lines, 21 filessetup-changed

Files

Bindings/Netcode/IO.hsc view
@@ -1,191 +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 ()
+{-|+  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 CInt -> 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
@@ -1,29 +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.
+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
@@ -1,126 +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
+{-|++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
@@ -1,134 +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
-
+{-# 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
@@ -1,51 +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
+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
@@ -1,415 +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
+{-# 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
@@ -1,33 +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
+{-# 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
@@ -1,403 +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)
+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
@@ -1,53 +1,64 @@-# netcode-io [![Build Status](https://travis-ci.org/Mokosha/netcode-io.svg?branch=master)](https://travis-ci.org/Mokosha/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.
+# netcode-io [![Build Status](https://travis-ci.org/Mokosha/netcode-io.svg?branch=master)](https://travis-ci.org/Mokosha/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. The only major+dependency is on the `sodium` library. `netcode-io` uses at least version+`1.0.16`. Below are various ways for integrating this into your Haskell+build ecosystem.++### 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+```++### Linux++On Linux, it depends on your package manager, but if you're using Ubuntu Bionic (18.04) or later:++```+$ sudo apt-get install libsodium-dev+```++## 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
@@ -1,2 +1,2 @@-import Distribution.Simple
-main = defaultMain
+import Distribution.Simple+main = defaultMain
examples/Client.hs view
@@ -1,114 +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
+-- 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
@@ -1,142 +1,141 @@--- 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
+-- 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 serverAddr = "[::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 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
@@ -1,15 +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!"
+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
@@ -1,82 +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
+-- 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
@@ -1,293 +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 ()
-
+{-# 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 = 64++gMaxNumServers :: Int+gMaxNumServers = 2++gServerBasePort :: Int+gServerBasePort = 20000++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
@@ -1,127 +1,130 @@-cabal-version:       >= 1.10
-name:                netcode-io
-version:             0.0.2
-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
-
-  if os(linux)
-    cc-options:        -D_POSIX_C_SOURCE=199309L
-
-  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
+cabal-version:       >= 1.10+name:                netcode-io+version:             0.0.3+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/LICENCE+                  ,  netcode.io/README.md+                  ,  netcode.io/STANDARD.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++  if os(linux)+    cc-options:        -D_POSIX_C_SOURCE=199309L++  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/LICENCE view
@@ -0,0 +1,19 @@+Copyright © 2017 - 2020, 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.
+ netcode.io/README.md view
@@ -0,0 +1,85 @@+[![Travis Build Status](https://travis-ci.org/networkprotocol/netcode.io.svg?branch=master)](https://travis-ci.org/networkprotocol/netcode.io)++# netcode++**netcode** is a simple connection based client/server protocol built on top of UDP. ++It has the following features:++* Encrypted and signed packets+* Secure client connection with connect tokens+* Connection based protocol built on top of UDP++and protects your game server from the following attacks:++* Zombie clients+* Man in the middle+* DDoS amplification+* Packet replay attacks++netcode is stable and production ready.++# How does it work?++Please refer to the second half of this whitepaper: [Why can't I send UDP packets from a browser?](http://gafferongames.com/post/why_cant_i_send_udp_packets_from_a_browser/) ++For a complete technical specification, read the [netcode 1.02 standard](https://github.com/networkprotocol/netcode.io/blob/master/STANDARD.md).++# Source Code++This repository holds the reference implementation of netcode in C.++This is the primary implementation of netcode, and is always up to date with the latest features.++Other netcode.io repositories include:++* [netcode C# implementation](https://github.com/KillaMaaki/Netcode.IO.NET)+* [netcode Golang implementation](https://github.com/wirepair/netcode)+* [netcode Rust implementation](https://github.com/jaynus/netcode.io) (updated fork of [vvanders/netcode.io](https://github.com/vvanders/netcode.io))+* [netcode for Unity](https://github.com/KillaMaaki/Unity-Netcode.IO)+* [netcode for UE4](https://github.com/RedpointGames/netcode.io-UE4)+* [netcode for Typescript](https://github.com/bennychen/netcode.io-typescript)++# Contributors++These people are awesome:++* [Val Vanders](https://github.com/vvanders) - Rust Implementation+* [Walter Pearce](https://github.com/jaynus) - Rust Implementation+* [Isaac Dawson](https://github.com/wirepair) - Golang Implementation+* [Alan Stagner](https://github.com/KillaMaaki) - Unity integration, C# implementation+* [Jérôme Leclercq](https://github.com/DrLynix) - Support for random connect token nonce+* [Randy Gaul](https://github.com/RandyGaul) - Discovered vulnerability in replay protection+* [Benny Chen](https://github.com/bennychen) - Typescript Implementation++Thanks for your contributions to netcode!++# Author++The author of this library is [Glenn Fiedler](https://www.linkedin.com/in/glennfiedler).++Other open source libraries by the same author include: [yojimbo](http://libyojimbo.com) and [reliable](https://github.com/networkprotocol/reliable.io)++Glenn is now the founder and CEO of Network Next. Network Next is a radically new way to link networks together. A new internet where networks compete on performance and price to carry your traffic. Check it out at https://networknext.com++# Sponsors++**netcode** was generously sponsored by:++* **Gold Sponsors**+    * [Remedy Entertainment](http://www.remedygames.com/)+    * [Cloud Imperium Games](https://cloudimperiumgames.com)+    +* **Silver Sponsors**+    * [Moon Studios](http://www.oriblindforest.com/#!moon-3/)+    * [The Network Protocol Company](http://www.thenetworkprotocolcompany.com)+    +* **Bronze Sponsors**+    * [Kite & Lightning](http://kiteandlightning.la/)+    * [Data Realms](http://datarealms.com)+ +And by individual supporters on Patreon. Thank you. You made this possible!++# License++[BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
+ netcode.io/STANDARD.md view
@@ -0,0 +1,506 @@+# netcode.io 1.02++**netcode.io** is a simple protocol for creating secure client/server connections over UDP.++This document describes the standard for this protocol so people can create their own implementations.++## Architecture++There are three main components in a netcode.io-based architecture:++1. The web backend+2. Dedicated servers+3. Clients++The web backend is a typical web server, for example nginx, which authenticates clients and provides a REST API. Clients are endpoints running the netcode.io protocol that want to connect to dedicated server instances. Dedicated servers are instances of the server-side portion of the game or application running in data centers or the cloud.++The sequence of operations for a client connect are:++1. A client authenticates with the web backend+2. The authenticated client requests to play a game via REST call to the web backend+3. The web backend generates a _connect token_ and returns it to that client over HTTPS+4. The client uses the connect token to establish a connection with a dedicated server over UDP+5. The dedicated server runs logic to ensure that only clients with a valid connect token can connect to it+6. Once a connection is established the client and server exchange encrypted and signed UDP packets++## General Conventions++**netcode.io** is a binary protocol. ++All data is written in little-endian byte order unless otherwise specified.++This applies not only to token and packet data, but also to sequence numbers converted to byte array nonce values, and associated data passed in to AEAD encryption primitives.++## Connect Token++A _connect token_ ensures that only authenticated clients can connect to dedicated servers.++The connect token has two parts: public and private.++The private portion is encrypted and signed with a private key shared between the web backend and dedicated server instances.++Prior to encryption the private connect token data has the following binary format.++    [client id] (uint64) // globally unique identifier for an authenticated client+    [timeout seconds] (uint32) // timeout in seconds. negative values disable timeout (dev only)+    [num server addresses] (uint32) // in [1,32]+    <for each server address>+    {+        [address type] (uint8) // value of 1 = IPv4 address, 2 = IPv6 address.+        <if IPV4 address>+        {+            // for a given IPv4 address: a.b.c.d:port+            [a] (uint8)+            [b] (uint8)+            [c] (uint8)+            [d] (uint8)+            [port] (uint16)+        }+        <else IPv6 address>+        {+            // for a given IPv6 address: [a:b:c:d:e:f:g:h]:port+            [a] (uint16)+            [b] (uint16)+            [c] (uint16)+            [d] (uint16)+            [e] (uint16)+            [f] (uint16)+            [g] (uint16)+            [h] (uint16)+            [port] (uint16)+        }+    }+    [client to server key] (32 bytes)+    [server to client key] (32 bytes)+    [user data] (256 bytes) // user defined data specific to this protocol id+    <zero pad to 1024 bytes>++This data is variable size but for simplicity is written to a fixed size buffer of 1024 bytes. Unused bytes are zero padded.++Encryption of the private connect token data is performed with the libsodium AEAD primitive *crypto_aead_xchacha20poly1305_ietf_encrypt* using the following binary data as the _associated data_: ++    [version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.+    [protocol id] (uint64)          // 64 bit value unique to this particular game/application+    [expire timestamp] (uint64)     // 64 bit unix timestamp when this connect token expires++The nonce used for encryption is a 24 bytes number that is randomly generated for every token.++Encryption is performed on the first 1024 - 16 bytes in the buffer, leaving the last 16 bytes to store the HMAC:++    [encrypted private connect token] (1008 bytes)+    [hmac of encrypted private connect token] (16 bytes)++Post encryption, this is referred to as the _encrypted private connect token data_.++Together the public and private data form a _connect token_:++    [version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.+    [protocol id] (uint64)          // 64 bit value unique to this particular game/application+    [create timestamp] (uint64)     // 64 bit unix timestamp when this connect token was created+    [expire timestamp] (uint64)     // 64 bit unix timestamp when this connect token expires+    [connect token nonce] (24 bytes)+    [encrypted private connect token data] (1024 bytes)+    [timeout seconds] (uint32)      // timeout in seconds. negative values disable timeout (dev only)+    [num_server_addresses] (uint32) // in [1,32]+    <for each server address>+    {+        [address_type] (uint8) // value of 1 = IPv4 address, 2 = IPv6 address.+        <if IPV4 address>+        {+            // for a given IPv4 address: a.b.c.d:port+            [a] (uint8)+            [b] (uint8)+            [c] (uint8)+            [d] (uint8)+            [port] (uint16)+        }+        <else IPv6 address>+        {+            // for a given IPv6 address: [a:b:c:d:e:f:g:h]:port+            [a] (uint16)+            [b] (uint16)+            [c] (uint16)+            [d] (uint16)+            [e] (uint16)+            [f] (uint16)+            [g] (uint16)+            [h] (uint16)+            [port] (uint16)+        }+    }+    [client to server key] (32 bytes)+    [server to client key] (32 bytes)+    <zero pad to 2048 bytes>++This data is variable size but for simplicity is written to a fixed size buffer of 2048 bytes. Unused bytes are zero padded.++## Challenge Token++Challenge tokens stop clients with spoofed IP packet source addresses from connecting to servers.++Prior to encryption, challenge tokens have the following structure:++    [client id] (uint64)+    [user data] (256 bytes)+    <zero pad to 300 bytes>++Encryption of the challenge token data is performed with the libsodium AEAD primitive *crypto_aead_chacha20poly1305_ietf_encrypt* with no associated data, a random key generated when the dedicated server starts, and a sequence number that starts at zero and increases with each challenge token generated. The sequence number is extended by padding high bits with zero to create a 96 bit nonce.++Encryption is performed on the first 300 - 16 bytes, and the last 16 bytes store the HMAC of the encrypted buffer:++    [encrypted challenge token] (284 bytes)+    [hmac of encrypted challenge token data] (16 bytes)+    +This is referred to as the _encrypted challenge token data_.++## Packets++**netcode.io** has the following packets:++* _connection request packet_ (0)+* _connection denied packet_ (1)+* _connection challenge packet_ (2)+* _connection response packet_ (3)+* _connection keep alive packet_ (4)+* _connection payload packet_ (5)+* _connection disconnect packet_ (6)++The first packet type _connection request packet_ (0) is not encrypted and has the following format:++    0 (uint8) // prefix byte of zero+    [version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.+    [protocol id] (8 bytes)+    [connect token expire timestamp] (8 bytes)+    [connect token nonce] (24 bytes)+    [encrypted private connect token data] (1024 bytes)+    +All other packet types are encrypted. ++Prior to encryption, packet types >= 1 have the following format:++    [prefix byte] (uint8) // non-zero prefix byte+    [sequence number] (variable length 1-8 bytes)+    [per-packet type data] (variable length according to packet type)++The low 4 bits of the prefix byte contain the packet type. ++The high 4 bits contain the number of bytes for the sequence number in the range [1,8]. ++The sequence number is encoded by omitting high zero bytes. For example, a sequence number of 1000 is 0x000003E8 and requires only two bytes to send its value. Therefore, the high 4 bits of the prefix byte are set to 2 and the sequence data written to the packet is:++    0xE8,0x03+    +The sequence number bytes are _reversed_ when written to the packet like so:++    <for each sequence byte written>+    {+        write_byte( sequence_number & 0xFF )+        sequence_number >>= 8+    }+    +After the sequence number comes the per-packet type data:++_connection denied packet_:++    <no data>++_connection challenge packet_:++    [challenge token sequence] (uint64)+    [encrypted challenge token data] (300 bytes)+    +_connection response packet_:++    [challenge token sequence] (uint64)+    [encrypted challenge token data] (300 bytes)++_connection keep-alive packet_:++    [client index] (uint32)+    [max clients] (uint32)+    +_connection payload packet_:++    [payload data] (1 to 1200 bytes)+    +_connection disconnect packet_:+    +    <no data>++The per-packet type data is encrypted using the libsodium AEAD primitive *crypto_aead_chacha20poly1305_ietf_encrypt* with the following binary data as the _associated data_: ++    [version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.+    [protocol id] (uint64)          // 64 bit value unique to this particular game/application+    [prefix byte] (uint8)           // prefix byte in packet. stops an attacker from modifying packet type.++The packet sequence number is extended by padding high bits with zero to create a 96 bit nonce.++Packets sent from client to server are encrypted with the client to server key in the connect token.++Packets sent from server to client are encrypted using the server to client key in the connect token for that client.++Post encryption, packet types >= 1 have the following format:++    [prefix byte] (uint8) // non-zero prefix byte: ( (num_sequence_bytes<<4) | packet_type )+    [sequence number] (variable length 1-8 bytes)+    [encrypted per-packet type data] (variable length according to packet type)+    [hmac of encrypted per-packet type data] (16 bytes)++## Reading Encrypted Packets++The following steps are taken when reading an encrypted packet, in this exact order:++* If the packet size is less than 18 bytes then it is too small to possibly be valid, ignore the packet.++* If the low 4 bits of the prefix byte are greater than or equal to 7, the packet type is invalid, ignore the packet.++* The server ignores packets with type _connection challenge packet_. ++* The client ignores packets with type _connection request packet_ and _connection response packet_.++* If the high 4 bits of the prefix byte (sequence bytes) are outside the range [1,8], ignore the packet.++* If the packet size is less than 1 + sequence bytes + 16, it cannot possibly be valid, ignore the packet.++* If the per-packet type data size does not match the expected size for the packet type, ignore the packet.++    * 0 bytes for _connection denied packet_+    * 308 bytes for _connection challenge packet_+    * 308 bytes for _connection response packet_+    * 8 bytes for _connection keep-alive packet_+    * [1,1200] bytes for _connection payload packet_+    * 0 bytes for _connection disconnect packet_++* If the packet type fails the replay protection already received test, ignore the packet. _See the section on replay protection below for details_.++* If the per-packet type data fails to decrypt, ignore the packet.++* Advance the most recent replay protection sequence #. _See the section on replay protection below for details_.++* If all the above checks pass, the packet is processed.++## Replay Protection++Replay protection stops an attacker from recording a valid packet and replaying it back at a later time in an attempt to break the protocol.++To enable replay protection, netcode.io does the following:++* Encrypted packets are sent with 64 bit sequence numbers that start at zero and increase with each packet sent.++* The sequence number is included in the packet header and can be read by the receiver of a packet prior to decryption.++* The sequence number is used as the nonce for packet encryption, so any modification to the sequence number fails the encryption signature check.++The replay protection algorithm is as follows:++1. Any packet older than the most recent sequence number received, minus the _replay buffer size_, is discarded on the receiver side.++2. If a packet arrives that is within _replay buffer size_ of the most recent sequence number, it is accepted only if its sequence number has not already been received, otherwise it is ignored.++3. After the packet has been successfully decrypted, a) if the packet sequence # is in the replay buffer window that entry is set as received, and b) the most recent sequence number is updated if the packet sequence # is > than the previous most recent sequence number received.++Replay protection is applied to the following packet types on both client and server:++* _connection keep alive packet_+* _connection payload packet_+* _connection disconnect packet_++The replay buffer size is implementation specific, but as a guide, a few seconds worth of packets at a typical send rate (20-60HZ) should be supported. Conservatively, a replay buffer size of 256 entries per-client should be sufficient for most applications.++## Client State Machine++The client has the following states:++* _connect token expired_ (-6)+* _invalid connect token_ (-5)+* _connection timed out_ (-4)+* _connection response timed out_ (-3)+* _connection request timed out_ (-2)+* _connection denied_ (-1)+* _disconnected_ (0)+* _sending connection request_ (1)+* _sending connection response_ (2)+* _connected_ (3)++The initial state is disconnected (0). Negative states represent error states. The goal state is _connected_ (3).++### Request Connect Token++When a client wants to connect to a server, it requests a _connect token_ from the web backend. ++The following aspects are outside the scope of this standard:++1. The mechanism the client uses to request a connection token from the web backend.++2. The mechanism the web backend uses to determine the set of server addresses to include in a connect token.++Once the client has obtained a connect token, its goal is to establish connection to one of the server addresses in the connect token.++To begin this process, it transitions to _sending connection request_ with the first server address in the connect token.++Before doing this, the client checks that the connect token is valid. If the number of server addresses in the connect token are outside of the range [1,32], or if any address type values in the connect token are outside of the range [0,1], or if the create timestamp is more recent than the expire timestamp, the client transitions to _invalid connect token_.++### Sending Connection Request++While in _sending connection request_ the client sends _connection request packets_ to the server at some rate, like 10HZ. ++When the client receives a _connection challenge packet_ from the server, it stores the challenge token data and transitions to _sending challenge response_. This represents a successful transition to the next stage in the connection process.++All other transitions from _sending connection request_ are failure cases. In these cases the client attempts to connect to the next server address in the connect token (eg. transitioning to _sending connection request_ state with the next server address in the connect token). Alternatively, if there are no additional server addresses to connect to, the client transitions to the appropriate error state as described in the next paragraph.++If a _connection request denied_ packet is received while in _sending connection request_ the client transitions to _connection denied_. If neither a _connection challenge packet_ or a _connection denied packet_ are received within the timeout period specified in the connect token, the client transitions to _connection request timed out_.++### Sending Challenge Response++While in _sending challenge response_ the client sends _challenge response packets_ to the server at some rate, like 10HZ. ++When the client receives a _connection keep-alive packet_ from the server, it stores the client index and max clients in the packet, and transitions to _connected_.++Any _connection payload packets_ received prior to _connected_ are discarded.++All other transitions from _sending challenge response_ are failure cases. In these cases the client attempts to connect to the next server address in the connect token (eg. transitioning to _sending connection request_ with the next server address in the connect token). Alternatively, if there are no additional servers addresses to connect to, the client transitions to the appropriate error state as described in the next paragraph.++If a _connection request denied_ packet is received while in _sending challenge response_ the client transitions to _connection denied_. If neither a _connection keep-alive packet_ or a _connection denied packet_ are received within the timeout period specified in the connect token, the client transitions to _challenge response timed out_.++### Connect Token Expired++If the entire client connection process (potentially across multiple server addresses) takes long enough that the connect token expires before successfully connecting to a server, the client transitions to _connect token expired_.++This length of time should be determined by subtracting the create timestamp of the connect token from its expiry timestamp.++### Connected++While _connected_ the client buffers _connection payload packets_ received from the server so their payload data can be delivered to the client application as netcode.io packets.++While _connected_ the client application may send _connection payload packets_ to the server. In the absence of _connection payload packets_ sent by the client application, the client generates and sends _connection keep-alive packets_ to the server at some rate, like 10HZ.++If no _connection payload packet_ or _connection keep-alive packet_ are received from the server within the timeout period specified in the connect token, the client transitions to _connection timed out_. ++While _connected_ if the client receives a _connection disconnect_ packet from the server, it transitions to _disconnected_.++If the client wishes to disconnect from the server, it sends a number of redundant _connection disconnect packets_ before transitioning to _disconnected_.++## Server-Side Connection Process++### Server-Side Overview++The dedicated server must be on a publicly accessible IP address and port.++The server manages a set of n client slots, where each slot from [0,n-1] represents room for one connected client. ++The maximum number of client slots per-server is implementation specific. Typical uses cases are expected in the range of [2,64] but the reference implementation supports up to 256 clients per-server. ++You may support more clients per-server if your implementation is able to handle them efficiently.++### Processing Connection Requests++The server follows these strict rules when processing connection requests:++1. Clients must have a valid connect token to connect.+2. Respond to a client only when absolutely necessary. +3. Ignore any malformed request as soon as possible, with the minimum amount of work.+4. Make sure any response packet is smaller than the request packet to avoid DDoS amplification.++When a server receives a connection request packet from a client it contains the following data:++    0 (uint8) // prefix byte of zero+    [version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.+    [protocol id] (8 bytes)+    [connect token expire timestamp] (8 bytes)+    [connect token nonce] (24 bytes)+    [encrypted private connect token data] (1024 bytes)++This packet is not encrypted, however:++* Only the dedicated server instance and the web backend can read the encrypted private connect token data, because it is encrypted with a private key shared between them.++* The important aspects of the packet such as the version info, protocol id and connect token expire timestamp, are protected by the AEAD construct, and thus cannot be modified without failing the signature check.++The server takes the following steps, in this exact order, when processing a _connection request packet_:++* If the packet is not the expected size of 1078 bytes, ignore the packet.++* If the version info in the packet doesn't match "NETCODE 1.02" (13 bytes, with null terminator), ignore the packet.++* If the protocol id in the packet doesn't match the expected protocol id of the dedicated server, ignore the packet.++* If the connect token expire timestamp is <= the current timestamp, ignore the packet.++* If the encrypted private connect token data doesn't decrypt with the private key, using the associated data constructed from: version info, protocol id and expire timestamp, ignore the packet.++* If the decrypted private connect token fails to be read for any reason, for example, having a number of server addresses outside of the expected range of [1,32], or having an address type value outside of range [0,1], ignore the packet.++* If the dedicated server public address is not in the list of server addresses in the private connect token, ignore the packet.++* If a client from the packet IP source address and port is already connected, ignore the packet.++* If a client with the client id contained in the private connect token data is already connected, ignore the packet.++* If the connect token has already been used by a different packet source IP address and port, ignore the packet. ++* Otherwise, add the private connect token hmac + packet source IP address and port to the history of connect tokens already used.++* If no client slots are available, then the server is full. Respond with a _connection denied packet_.++* Add an encryption mapping for the packet source IP address and port so that packets read from that address and port are decrypted with the client to server key in the private connect token, and packets sent to that address and port are encrypted with the server to client key in the private connect token. This encryption mapping expires in _timeout_ seconds of no packets being sent to or received from that address and port, or if a client fails to establish a connection with the server within _timeout_ seconds.++* If for some reason this encryption mapping cannot be added, ignore the packet.++* Otherwise, respond with a _connection challenge packet_ and increment the _connection challenge sequence number_.++### Processing Connection Response Packets++When the client receives a _connection challenge packet_ from the server it responds with a _connection response packet_.++The _connection response packet_ contains the following data:++    [prefix byte] (uint8) // non-zero prefix byte: ( (num_sequence_bytes<<4) | packet_type )+    [sequence number] (variable length 1-8 bytes)+    [challenge token sequence] (uint64)+    [encrypted challenge token data] (360 bytes)++The server takes these steps, in this exact order, when processing a _connection response packet_:++* If the _encrypted challenge token data_ fails to decrypt, ignore the packet.++* If a client from the packet source address and port is already connected, ignore the packet.++* If a client with the client id contained in the encrypted challenge token data is already connected, ignore the packet.++* If no client slots are available, then the server is full. Respond with a _connection denied packet_.++* Assign the packet IP address + port and client id to a free client slot and mark that client as connected.++* Copy across the user data from the challenge token into the client slot so it is accessible to the server application.++* Set the _confirmed_ flag for that client slot to false.++* Respond with a _connection keep-alive_ packet.++### Connected Clients++Once a client is asigned to a slot on the server, it is logically connected. ++The index of this slot is used to identify clients on the server and is called the _client index_.++Packets received by the server from that client's address and port are mapped to that _client index_ and processed in the context of that client.++These packets include:++* _connection keep-alive packet_+* _connection payload packet_+* _connection disconnect packet_++The server buffers _connection payload packets_ received from connected clients client so their payload data can be delivered to the server application as netcode.io packets.++The server application may also send _connection payload packets_ to connected clients.++In the absence of _connection payload packets_ sent to a client, the server generates and sends _connection keep-alive packets_ to that client at some rate, like 10HZ.++While the _confirmed_ flag for a client slot is false, each _connection payload packet_ sent to that client has a _connection keep-alive packet_ sent before it. This communicates the _client index_ and the _max clients_ to that client, which it needs to transition to a fully connected state.++When the server receives a _connection payload packet_ or a _connection keep-alive packet_ from an unconfirmed client, it sets the _confirmed_ flag for that client slot to true, and stops prefixing _connection payload packets_ with _connection keep-alive packets_.++If the server wishes to disconnect a client, it sends a number of redundant _connection disconnect packets_ to that client before resetting that client slot.++If no _connection payload packet_ or _connection keep-alive packet_ are received from a client within the timeout period specified in the connect token, or the server receives a _connection disconnect_ packet from a client, the client slot is reset and becomes available for other clients to connect to.
netcode.io/netcode.c view
@@ -1,8977 +1,8948 @@-/*
-    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 )
-{
-    struct timespec ts;
-    ts.tv_sec = (time_t) time;
-    ts.tv_nsec = (long) ((time - (double) ( ts.tv_sec )) * 1000000000.0);
-    nanosleep( &ts, NULL );
-}
-
-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 )
-{
-    struct timespec ts;
-    ts.tv_sec = (time_t) time;
-    ts.tv_nsec = (long) ((time - (double) ( ts.tv_sec )) * 1000000000.0);
-    nanosleep( &ts, NULL );
-}
-
-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 reference implementation++    Copyright © 2017 - 2020, 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_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;++    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] == UINT64_MAX )+        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;+    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 )+{+    struct timespec ts;+    ts.tv_sec = (time_t) time;+    ts.tv_nsec = (long) ((time - (double) ( ts.tv_sec )) * 1000000000.0);+    nanosleep( &ts, NULL );+}++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 )+{+    struct timespec ts;+    ts.tv_sec = (time_t) time;+    ts.tv_nsec = (long) ((time - (double) ( ts.tv_sec )) * 1000000000.0);+    nanosleep( &ts, NULL );+}++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 );+#ifdef NETCODE_DEBUG+    #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
@@ -1,282 +1,292 @@-/*
-    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
+/*+    netcode.io reference implementation++    Copyright © 2017 - 2020, 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(NETCODE_DEBUG) && !defined(NETCODE_RELEASE)+#if defined(NDEBUG)+#define NETCODE_RELEASE+#else+#define NETCODE_DEBUG+#endif+#elif defined(NETCODE_DEBUG) && defined(NETCODE_RELEASE)+#error Can only define one of debug & release+#endif++#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