See More

using System; using System.Net; using System.Net.Sockets; using System.Threading; using System.Text; using System.IO; using System.Linq; using System.Collections.Generic; using Org.BouncyCastle.Crypto.TlsExt; using NetcodeIO.NET.Utils; using NetcodeIO.NET.Utils.IO; using NetcodeIO.NET.Internal; namespace NetcodeIO.NET { ///

/// Represents a remote client connected to a server /// public class RemoteClient { /// /// The unique ID of the client as assigned by the token server /// public ulong ClientID; /// /// The index as assigned by the server /// public uint ClientIndex; /// /// The remote endpoint of the client /// public EndPoint RemoteEndpoint; /// /// 256 bytes of arbitrary user data /// public byte[] UserData; internal bool Connected; internal bool Confirmed; internal NetcodeReplayProtection replayProtection; internal Server server; internal double lastResponseTime; internal int timeoutSeconds; public RemoteClient(Server server) { this.server = server; } /// /// Send a payload to this client /// public void SendPayload(byte[] payload, int payloadSize) { server.SendPayload(this, payload, payloadSize); } internal void Touch(double time) { lastResponseTime = time; } } /// /// Event handler for when a client connects to the server /// public delegate void RemoteClientConnectedEventHandler(RemoteClient client); /// /// Event handler for when a client disconnects from the server /// public delegate void RemoteClientDisconnectedEventHandler(RemoteClient client); /// /// Event handler for when payload packets are received from a connected client /// public delegate void RemoteClientMessageReceivedEventHandler(RemoteClient sender, byte[] payload, int payloadSize); /// /// Class for starting a Netcode.IO server and accepting connections from remote clients /// public sealed class Server { #region embedded types private struct usedConnectToken { public byte[] mac; public EndPoint endpoint; public double time; } #endregion #region Public fields/properties /// /// Event triggered when a remote client connects /// public event RemoteClientConnectedEventHandler OnClientConnected; /// /// Event triggered when a remote client disconnects /// public event RemoteClientDisconnectedEventHandler OnClientDisconnected; /// /// Event triggered when a payload is received from a remote client /// public event RemoteClientMessageReceivedEventHandler OnClientMessageReceived; /// /// Log level for messages /// public NetcodeLogLevel LogLevel = NetcodeLogLevel.Error; /// /// Gets the port this server is listening on (or -1 if not listening) /// public int Port { get { if (listenSocket == null) return -1; return listenSocket.BoundPort; } } /// /// Gets or sets the internal tickrate of the server in ticks per second. Value must be between 1 and 1000. /// public int Tickrate { get { return tickrate; } set { if (value < 1 || value > 1000) throw new ArgumentOutOfRangeException(); tickrate = value; } } /// /// Gets the current number of connected clients /// public int NumConnectedClients { get { int connectedClients = 0; for (int i = 0; i < clientSlots.Length; i++) if (clientSlots[i] != null && clientSlots[i].Confirmed) connectedClients++; return connectedClients; } } #endregion #region Private fields internal bool debugIgnoreConnectionRequest = false; internal bool debugIgnoreChallengeResponse = false; private ISocketContext listenSocket; private IPEndPoint listenEndpoint; private bool isRunning = false; private ulong protocolID; private RemoteClient[] clientSlots; private int maxSlots; private usedConnectToken[] connectTokenHistory; private int maxConnectTokenEntries; private ulong nextSequenceNumber = 0; private ulong nextChallengeSequenceNumber = 0; private byte[] privateKey; private byte[] challengeKey; private EncryptionManager encryptionManager; private int tickrate; internal double time; private bool disposed = false; #endregion public Server(int maxSlots, string address, int port, ulong protocolID, byte[] privateKey) { this.tickrate = 60; this.maxSlots = maxSlots; this.maxConnectTokenEntries = this.maxSlots * 8; this.connectTokenHistory = new usedConnectToken[this.maxConnectTokenEntries]; initConnectTokenHistory(); this.clientSlots = new RemoteClient[maxSlots]; this.encryptionManager = new EncryptionManager(maxSlots); this.listenEndpoint = new IPEndPoint(IPAddress.Parse(address), port); if (this.listenEndpoint.AddressFamily == AddressFamily.InterNetwork) this.listenSocket = new UDPSocketContext(AddressFamily.InterNetwork); else this.listenSocket = new UDPSocketContext(AddressFamily.InterNetworkV6); this.protocolID = protocolID; this.privateKey = privateKey; // generate a random challenge key this.challengeKey = new byte[32]; KeyUtils.GenerateKey(this.challengeKey); } internal Server(ISocketContext socketContext, int maxSlots, string address, int port, ulong protocolID, byte[] privateKey) { this.tickrate = 60; this.maxSlots = maxSlots; this.maxConnectTokenEntries = this.maxSlots * 8; this.connectTokenHistory = new usedConnectToken[this.maxConnectTokenEntries]; initConnectTokenHistory(); this.clientSlots = new RemoteClient[maxSlots]; this.encryptionManager = new EncryptionManager(maxSlots); this.listenEndpoint = new IPEndPoint(IPAddress.Parse(address), port); this.listenSocket = socketContext; this.protocolID = protocolID; this.privateKey = privateKey; // generate a random challenge key this.challengeKey = new byte[32]; KeyUtils.GenerateKey(this.challengeKey); } #region Public Methods /// /// Start the server and listen for incoming connections /// public void Start() { Start(true); } internal void Start(bool autoTick) { if (disposed) throw new InvalidOperationException("Can't restart disposed server, please create a new server"); resetConnectTokenHistory(); this.listenSocket.Bind(this.listenEndpoint); isRunning = true; if (autoTick) { this.time = DateTime.Now.GetTotalSeconds(); ThreadPool.QueueUserWorkItem(serverTick); } } /// /// Stop the server and disconnect any clients /// public void Stop() { disposed = true; disconnectAll(); isRunning = false; this.listenSocket.Close(); if (OnClientConnected != null) { foreach (var receiver in OnClientConnected.GetInvocationList()) OnClientConnected -= (RemoteClientConnectedEventHandler)receiver; } if (OnClientDisconnected != null) { foreach (var receiver in OnClientDisconnected.GetInvocationList()) OnClientDisconnected -= (RemoteClientDisconnectedEventHandler)receiver; } if (OnClientMessageReceived != null) { foreach (var receiver in OnClientMessageReceived.GetInvocationList()) OnClientMessageReceived -= (RemoteClientMessageReceivedEventHandler)receiver; } } /// /// Send a payload to the remote client /// public void SendPayload(RemoteClient client, byte[] payload, int payloadSize) { sendPayloadToClient(client, payload, payloadSize); } /// /// Disconnect the remote client /// public void Disconnect(RemoteClient client) { disconnectClient(client); } #endregion #region Core double keepAlive = 0.0; internal void Tick(double time) { this.listenSocket.Pump(); double dt = time - this.time; this.time = time; // send keep alive to clients 10 times per second keepAlive += dt; while (keepAlive >= 0.1) { keepAlive -= 0.1; for (int i = 0; i < clientSlots.Length; i++) { if (clientSlots[i] != null) { sendKeepAlive(clientSlots[i]); } } } // disconnect any clients which have not responded for timeout seconds for (int i = 0; i < clientSlots.Length; i++) { if (clientSlots[i] == null) continue; double timeRemaining = time - clientSlots[i].lastResponseTime; // timeout < 0 disables timeouts if (clientSlots[i].timeoutSeconds >= 0 && (time - clientSlots[i].lastResponseTime) >= clientSlots[i].timeoutSeconds) { if (OnClientDisconnected != null) OnClientDisconnected(clientSlots[i]); log("Client {0} timed out", NetcodeLogLevel.Debug, clientSlots[i].RemoteEndpoint.ToString()); disconnectClient(clientSlots[i]); } } // process datagram queue Datagram packet; while (listenSocket != null && listenSocket.Read(out packet)) { processDatagram(packet.payload, packet.payloadSize, packet.sender); packet.Release(); } } private void serverTick(Object stateInfo) { while (isRunning) { Tick(DateTime.Now.GetTotalSeconds()); // sleep until next tick double tickLength = 1.0 / tickrate; Thread.Sleep((int)(tickLength * 1000)); } } // process a received datagram private void processDatagram(byte[] payload, int size, EndPoint sender) { using (var reader = ByteArrayReaderWriter.Get(payload)) { NetcodePacketHeader packetHeader = new NetcodePacketHeader(); packetHeader.Read(reader); if (packetHeader.PacketType == NetcodePacketType.ConnectionRequest) { if (!debugIgnoreConnectionRequest) processConnectionRequest(reader, size, sender); } else { switch (packetHeader.PacketType) { case NetcodePacketType.ChallengeResponse: if (!debugIgnoreChallengeResponse) processConnectionResponse(reader, packetHeader, size, sender); break; case NetcodePacketType.ConnectionKeepAlive: processConnectionKeepAlive(reader, packetHeader, size, sender); break; case NetcodePacketType.ConnectionPayload: processConnectionPayload(reader, packetHeader, size, sender); break; case NetcodePacketType.ConnectionDisconnect: processConnectionDisconnect(reader, packetHeader, size, sender); break; } } } } #endregion #region Receive Packet Methods // check the packet against the client's replay protection, returning true if packet was replayed, false otherwise private bool checkReplay(NetcodePacketHeader header, EndPoint sender) { var cryptIdx = encryptionManager.FindEncryptionMapping(sender, time); if (cryptIdx == -1) { log("Replay protection failed to find encryption mapping", NetcodeLogLevel.Debug); return true; } var clientIndex = encryptionManager.GetClientID(cryptIdx); var client = clientSlots[clientIndex]; if (client == null) { log("Replay protection failed to find client", NetcodeLogLevel.Debug); return true; } return client.replayProtection.AlreadyReceived(header.SequenceNumber); } // process an incoming disconnect message private void processConnectionDisconnect(ByteArrayReaderWriter reader, NetcodePacketHeader header, int size, EndPoint sender) { if (checkReplay(header, sender)) { return; } // encryption mapping was not registered, so don't bother int cryptIdx = encryptionManager.FindEncryptionMapping(sender, time); if (cryptIdx == -1) { log("No crytpo key for sender", NetcodeLogLevel.Debug); return; } var decryptKey = encryptionManager.GetReceiveKey(cryptIdx); var disconnectPacket = new NetcodeDisconnectPacket() { Header = header }; if (!disconnectPacket.Read(reader, size - (int)reader.ReadPosition, decryptKey, protocolID)) return; // locate the client by endpoint and free their slot var clientIndex = encryptionManager.GetClientID(cryptIdx); var client = clientSlots[clientIndex]; if (client == null) return; clientSlots[clientIndex] = null; // remove encryption mapping encryptionManager.RemoveEncryptionMapping(sender, time); // make sure all other clients still have their encryption mappings foreach (RemoteClient otherClient in clientSlots) { if (otherClient == null) continue; if (encryptionManager.FindEncryptionMapping(otherClient.RemoteEndpoint, time) == -1) log("Encryption mapping removed wrong mapping!", NetcodeLogLevel.Debug); } // trigger client disconnect callback if (OnClientDisconnected != null) OnClientDisconnected(client); log("Client {0} disconnected", NetcodeLogLevel.Info, client.RemoteEndpoint); } // process an incoming payload private void processConnectionPayload(ByteArrayReaderWriter reader, NetcodePacketHeader header, int size, EndPoint sender) { if (checkReplay(header, sender)) { return; } // encryption mapping was not registered, so don't bother int cryptIdx = encryptionManager.FindEncryptionMapping(sender, time); if (cryptIdx == -1) { log("No crytpo key for sender", NetcodeLogLevel.Debug); return; } // grab the decryption key and decrypt the packet var decryptKey = encryptionManager.GetReceiveKey(cryptIdx); var payloadPacket = new NetcodePayloadPacket() { Header = header }; if (!payloadPacket.Read(reader, size - (int)reader.ReadPosition, decryptKey, protocolID)) return; var clientIndex = encryptionManager.GetClientID(cryptIdx); var client = clientSlots[clientIndex]; // trigger callback if (OnClientMessageReceived != null) OnClientMessageReceived(client, payloadPacket.Payload, payloadPacket.Length); payloadPacket.Release(); } // process an incoming connection keep alive packet private void processConnectionKeepAlive(ByteArrayReaderWriter reader, NetcodePacketHeader header, int size, EndPoint sender) { if (checkReplay(header, sender)) { log("Detected replay in keep-alive", NetcodeLogLevel.Debug); return; } // encryption mapping was not registered, so don't bother int cryptIdx = encryptionManager.FindEncryptionMapping(sender, time); if (cryptIdx == -1) { log("No crytpo key for sender", NetcodeLogLevel.Debug); return; } // grab the decryption key and decrypt the packet var decryptKey = encryptionManager.GetReceiveKey(cryptIdx); var keepAlivePacket = new NetcodeKeepAlivePacket() { Header = header }; if (!keepAlivePacket.Read(reader, size - (int)reader.ReadPosition, decryptKey, protocolID)) { log("Failed to decrypt", NetcodeLogLevel.Debug); return; } if (keepAlivePacket.ClientIndex >= maxSlots) { log("Invalid client index", NetcodeLogLevel.Debug); return; } var client = this.clientSlots[(int)keepAlivePacket.ClientIndex]; if (client == null) { log("Failed to find client for endpoint", NetcodeLogLevel.Debug); return; } if (!client.RemoteEndpoint.Equals(sender)) { log("Client does not match sender", NetcodeLogLevel.Debug); return; } if (!client.Confirmed) { // trigger callback if (OnClientConnected != null) OnClientConnected(client); log("Client {0} connected", NetcodeLogLevel.Info, client.RemoteEndpoint); } client.Confirmed = true; client.Touch(time); int idx = encryptionManager.FindEncryptionMapping(client.RemoteEndpoint, time); encryptionManager.Touch(idx, client.RemoteEndpoint, time); } // process an incoming connection response packet private void processConnectionResponse(ByteArrayReaderWriter reader, NetcodePacketHeader header, int size, EndPoint sender) { log("Got connection response", NetcodeLogLevel.Debug); // encryption mapping was not registered, so don't bother int cryptIdx = encryptionManager.FindEncryptionMapping(sender, time); if (cryptIdx == -1) { log("No crytpo key for sender", NetcodeLogLevel.Debug); return; } // grab the decryption key and decrypt the packet var decryptKey = encryptionManager.GetReceiveKey(cryptIdx); var connectionResponsePacket = new NetcodeConnectionChallengeResponsePacket() { Header = header }; if (!connectionResponsePacket.Read(reader, size - (int)reader.ReadPosition, decryptKey, protocolID)) { log("Failed to decrypt packet", NetcodeLogLevel.Debug); return; } var challengeToken = new NetcodeChallengeToken(); if (!challengeToken.Read(connectionResponsePacket.ChallengeTokenBytes, connectionResponsePacket.ChallengeTokenSequence, challengeKey)) { log("Failed to read challenge token", NetcodeLogLevel.Debug); connectionResponsePacket.Release(); return; } // if a client from packet source IP / port is already connected, ignore the packet if (clientSlots.Any(x => x != null && x.RemoteEndpoint.Equals(sender))) { log("Client {0} already connected", NetcodeLogLevel.Debug, sender.ToString()); return; } // if a client with the same id is already connected, ignore the packet if (clientSlots.Any(x => x != null && x.ClientID == challengeToken.ClientID)) { log("Client ID {0} already connected", NetcodeLogLevel.Debug, challengeToken.ClientID); return; } // if the server is full, deny the connection int nextSlot = getFreeClientSlot(); if (nextSlot == -1) { log("Server full, denying connection", NetcodeLogLevel.Info); denyConnection(sender, encryptionManager.GetSendKey(cryptIdx)); return; } // assign the endpoint and client ID to a free client slot and set connected to true RemoteClient client = new RemoteClient(this); client.ClientID = challengeToken.ClientID; client.RemoteEndpoint = sender; client.Connected = true; client.replayProtection = new NetcodeReplayProtection(); // assign timeout to client client.timeoutSeconds = encryptionManager.GetTimeoutSeconds(cryptIdx); // assign client to a free slot client.ClientIndex = (uint)nextSlot; this.clientSlots[nextSlot] = client; encryptionManager.SetClientID(cryptIdx, client.ClientIndex); // copy user data so application can make use of it, and set confirmed to false client.UserData = challengeToken.UserData; client.Confirmed = false; client.Touch(time); // respond with a connection keep alive packet sendKeepAlive(client); } // process an incoming connection request packet private void processConnectionRequest(ByteArrayReaderWriter reader, int size, EndPoint sender) { log("Got connection request", NetcodeLogLevel.Debug); var connectionRequestPacket = new NetcodeConnectionRequestPacket(); if (!connectionRequestPacket.Read(reader, size - (int)reader.ReadPosition, protocolID)) { log("Failed to read request", NetcodeLogLevel.Debug); return; } // expiration timestamp should be greater than current timestamp if (connectionRequestPacket.Expiration <= (ulong)Math.Truncate(time)) { log("Connect token expired", NetcodeLogLevel.Debug); connectionRequestPacket.Release(); return; } var privateConnectToken = new NetcodePrivateConnectToken(); if (!privateConnectToken.Read(connectionRequestPacket.ConnectTokenBytes, privateKey, protocolID, connectionRequestPacket.Expiration, connectionRequestPacket.TokenSequenceNum)) { log("Failed to read private token", NetcodeLogLevel.Debug); connectionRequestPacket.Release(); return; } // if this server's public IP is not in the list of endpoints, packet is not valid bool serverAddressInEndpoints = privateConnectToken.ConnectServers.Any(x => x.Endpoint.CompareEndpoint(this.listenEndpoint, this.Port)); if (!serverAddressInEndpoints) { log("Server address not listen in token", NetcodeLogLevel.Debug); return; } // if a client from packet source IP / port is already connected, ignore the packet if (clientSlots.Any(x => x != null && x.RemoteEndpoint.Equals(sender))) { log("Client {0} already connected", NetcodeLogLevel.Debug, sender.ToString()); return; } // if a client with the same id as the connect token is already connected, ignore the packet if (clientSlots.Any(x => x != null && x.ClientID == privateConnectToken.ClientID)) { log("Client ID {0} already connected", NetcodeLogLevel.Debug, privateConnectToken.ClientID); return; } // if the connect token has already been used by a different endpoint, ignore the packet // otherwise, add the token hmac and endpoint to the used token history // compares the last 16 bytes (token mac) byte[] token_mac = BufferPool.GetBuffer(Defines.MAC_SIZE); System.Array.Copy(connectionRequestPacket.ConnectTokenBytes, Defines.NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - Defines.MAC_SIZE, token_mac, 0, Defines.MAC_SIZE); if (!findOrAddConnectToken(sender, token_mac, time)) { log("Token already used", NetcodeLogLevel.Debug); BufferPool.ReturnBuffer(token_mac); return; } BufferPool.ReturnBuffer(token_mac); // if we have no slots, we need to respond with a connection denied packet var nextSlot = getFreeClientSlot(); if (nextSlot == -1) { denyConnection(sender, privateConnectToken.ServerToClientKey); log("Server is full, denying connection", NetcodeLogLevel.Info); return; } // add encryption mapping for this endpoint as well as timeout // packets received from this endpoint are to be decrypted with the client-to-server key // packets sent to this endpoint are to be encrypted with the server-to-client key // if no messages are received within timeout from this endpoint, it is disconnected (unless timeout is negative) if (!encryptionManager.AddEncryptionMapping(sender, privateConnectToken.ServerToClientKey, privateConnectToken.ClientToServerKey, time, time + 30, privateConnectToken.TimeoutSeconds, 0)) { log("Failed to add encryption mapping", NetcodeLogLevel.Error); return; } // finally, send a connection challenge packet sendConnectionChallenge(privateConnectToken, sender); } #endregion #region Send Packet Methods // disconnect all clients private void disconnectAll() { for (int i = 0; i < clientSlots.Length; i++) { if (clientSlots[i] != null) disconnectClient(clientSlots[i]); } } // sends a disconnect packet to the client private void disconnectClient(RemoteClient client) { for (int i = 0; i < clientSlots.Length; i++) { if (clientSlots[i] == client) { clientSlots[i] = null; break; } } var cryptIdx = encryptionManager.FindEncryptionMapping(client.RemoteEndpoint, time); if (cryptIdx == -1) { return; } var cryptKey = encryptionManager.GetSendKey(cryptIdx); for (int i = 0; i < Defines.NUM_DISCONNECT_PACKETS; i++) { serializePacket(new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionDisconnect }, (writer) => { }, client.RemoteEndpoint, cryptKey); } } // sends a connection denied packet to the endpoint private void denyConnection(EndPoint endpoint, byte[] cryptKey) { if (cryptKey == null) { var cryptIdx = encryptionManager.FindEncryptionMapping(endpoint, time); if (cryptIdx == -1) return; cryptKey = encryptionManager.GetSendKey(cryptIdx); } serializePacket(new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionDenied }, (writer) => { }, endpoint, cryptKey); } // send a payload to a client private void sendPayloadToClient(RemoteClient client, byte[] payload, int payloadSize) { // if the client isn't confirmed, send a keep-alive packet before this packet if (!client.Confirmed) sendKeepAlive(client); var cryptIdx = encryptionManager.FindEncryptionMapping(client.RemoteEndpoint, time); if (cryptIdx == -1) return; var cryptKey = encryptionManager.GetSendKey(cryptIdx); serializePacket(new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionPayload }, (writer) => { writer.WriteBuffer(payload, payloadSize); }, client.RemoteEndpoint, cryptKey); } // send a keep-alive packet to the client private void sendKeepAlive(RemoteClient client) { var packet = new NetcodeKeepAlivePacket() { ClientIndex = client.ClientIndex, MaxSlots = (uint)this.maxSlots }; var cryptIdx = encryptionManager.FindEncryptionMapping(client.RemoteEndpoint, time); if (cryptIdx == -1) return; var cryptKey = encryptionManager.GetSendKey(cryptIdx); serializePacket(new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionKeepAlive }, (writer) => { packet.Write(writer); }, client.RemoteEndpoint, cryptKey); } // sends a connection challenge packet to the endpoint private void sendConnectionChallenge(NetcodePrivateConnectToken connectToken, EndPoint endpoint) { log("Sending connection challenge", NetcodeLogLevel.Debug); var challengeToken = new NetcodeChallengeToken(); challengeToken.ClientID = connectToken.ClientID; challengeToken.UserData = connectToken.UserData; ulong challengeSequence = nextChallengeSequenceNumber++; byte[] tokenBytes = BufferPool.GetBuffer(300); using (var tokenWriter = ByteArrayReaderWriter.Get(tokenBytes)) challengeToken.Write(tokenWriter); byte[] encryptedToken = BufferPool.GetBuffer(300); int encryptedTokenBytes; try { encryptedTokenBytes = PacketIO.EncryptChallengeToken(challengeSequence, tokenBytes, challengeKey, encryptedToken); } catch { BufferPool.ReturnBuffer(tokenBytes); BufferPool.ReturnBuffer(encryptedToken); return; } var challengePacket = new NetcodeConnectionChallengeResponsePacket(); challengePacket.ChallengeTokenSequence = challengeSequence; challengePacket.ChallengeTokenBytes = encryptedToken; var cryptIdx = encryptionManager.FindEncryptionMapping(endpoint, time); if (cryptIdx == -1) return; var cryptKey = encryptionManager.GetSendKey(cryptIdx); serializePacket(new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionChallenge }, (writer) => { challengePacket.Write(writer); }, endpoint, cryptKey); BufferPool.ReturnBuffer(tokenBytes); BufferPool.ReturnBuffer(encryptedToken); } // encrypts a packet and sends it to the endpoint private void sendPacketToClient(NetcodePacketHeader packetHeader, byte[] packetData, int packetDataLen, EndPoint endpoint, byte[] key) { // assign a sequence number to this packet packetHeader.SequenceNumber = this.nextSequenceNumber++; // encrypt packet data byte[] encryptedPacketBuffer = BufferPool.GetBuffer(2048); int encryptedBytes = PacketIO.EncryptPacketData(packetHeader, protocolID, packetData, packetDataLen, key, encryptedPacketBuffer); int packetLen = 0; // write packet to byte array var packetBuffer = BufferPool.GetBuffer(2048); using (var packetWriter = ByteArrayReaderWriter.Get(packetBuffer)) { packetHeader.Write(packetWriter); packetWriter.WriteBuffer(encryptedPacketBuffer, encryptedBytes); packetLen = (int)packetWriter.WritePosition; } // send packet listenSocket.SendTo(packetBuffer, packetLen, endpoint); BufferPool.ReturnBuffer(packetBuffer); BufferPool.ReturnBuffer(encryptedPacketBuffer); } private void serializePacket(NetcodePacketHeader packetHeader, Action write, EndPoint endpoint, byte[] key) { byte[] tempPacket = BufferPool.GetBuffer(2048); int writeLen = 0; using (var writer = ByteArrayReaderWriter.Get(tempPacket)) { write(writer); writeLen = (int)writer.WritePosition; } sendPacketToClient(packetHeader, tempPacket, writeLen, endpoint, key); BufferPool.ReturnBuffer(tempPacket); } #endregion #region Misc Util Methods // find or add a connect token entry // intentional constant time worst case search private bool findOrAddConnectToken(EndPoint address, byte[] mac, double time) { int matchingTokenIndex = -1; int oldestTokenIndex = -1; double oldestTokenTime = 0.0; for (int i = 0; i < connectTokenHistory.Length; i++) { var token = connectTokenHistory[i]; if (MiscUtils.CompareHMACConstantTime(token.mac, mac)) matchingTokenIndex = i; if (oldestTokenIndex == -1 || token.time < oldestTokenTime) { oldestTokenTime = token.time; oldestTokenIndex = i; } } // if no entry is found with the mac, this is a new connect token. replace the oldest token entry. if (matchingTokenIndex == -1) { connectTokenHistory[oldestTokenIndex].time = time; connectTokenHistory[oldestTokenIndex].endpoint = address; Buffer.BlockCopy(mac, 0, connectTokenHistory[oldestTokenIndex].mac, 0, mac.Length); return true; } // allow connect tokens we have already seen from the same address if (connectTokenHistory[matchingTokenIndex].endpoint.Equals(address)) return true; return false; } // reset connect token history private void resetConnectTokenHistory() { for (int i = 0; i < connectTokenHistory.Length; i++) { connectTokenHistory[i].endpoint = null; Array.Clear(connectTokenHistory[i].mac, 0, 16); connectTokenHistory[i].time = -1000.0; } } // initialize connect token history private void initConnectTokenHistory() { for (int i = 0; i < connectTokenHistory.Length; i++) { connectTokenHistory[i].mac = new byte[Defines.MAC_SIZE]; } resetConnectTokenHistory(); } /// /// allocate the next free client slot /// private int getFreeClientSlot() { for (int i = 0; i < maxSlots; i++) { if (clientSlots[i] == null) return i; } return -1; } private void log(string log, NetcodeLogLevel logLevel) { if (logLevel > this.LogLevel) return; Console.WriteLine(log); } private void log(string log, NetcodeLogLevel logLevel, params object[] args) { if (logLevel > this.LogLevel) return; Console.WriteLine(string.Format(log, args)); } #endregion } }