/// <summary> /// Processes a raw command packet that has been received and identified as being /// sent to this connection. /// </summary> /// <param name="command_packet">Command packet data</param> public void ProcessCommandPacket(string command_packet) { try { int pcmd = 0; Command new_cmd = new Command(); new_cmd.OPCode = command_packet.Substring(pcmd, 2); pcmd += 2; new_cmd.SequenceNum = Util.BytesToUint(command_packet.Substring(pcmd, 4)); pcmd += 4; new_cmd.Flags = (byte)command_packet[pcmd]; pcmd++; new_cmd.NumFields = Util.BytesToShort(command_packet.Substring(pcmd, 2)); pcmd += 2; new_cmd.FieldSizes = new short[new_cmd.NumFields]; for (short i = 0; i < new_cmd.NumFields; i++) { new_cmd.FieldSizes[i] = Util.BytesToShort(command_packet.Substring(pcmd, 2)); pcmd += 2; } //Is a reliable packet? Send an acknowledgement back if ((new_cmd.Flags & UdpConsts.FLAGS_RELIABLE) > 0) { m_Parent.DebugDump("Got a reliable packet (" + new_cmd.SequenceNum.ToString() + ") - sent acknowledgement to sender."); SendUnreliableCommand(0, UdpConsts.OPCODE_RELIABLEACK, new string[] { new_cmd.SequenceNum.ToString() }); //Repeat reliable packet - sender must not have received our acknowledgement if (LastReceivedPacketR == new_cmd.SequenceNum) { m_Parent.DebugDump("Repeated reliable packet - not processed."); return; } //Update last received reliable command number LastReceivedPacketR = new_cmd.SequenceNum; } else { LastReceivedPacket = new_cmd.SequenceNum; //Update unreliable command number if ((new_cmd.Flags & UdpConsts.FLAGS_SEQUENCED) > 0) { //If this sequenced packet has arrived too late (a newer one got there first) then don't process it if (LastReceivedPacketSeq > new_cmd.SequenceNum) { m_Parent.DebugDump("Unreliable sequenced packet arrived out of order - ignored."); return; } //Update the last sequenced packet number if we should LastReceivedPacketSeq = new_cmd.SequenceNum; } } //Encryption check if ((new_cmd.Flags & UdpConsts.FLAGS_ENCRYPTED) > 0) { if (EncryptionKey != "") if (Util.XORCrypt(command_packet.Substring(pcmd, 2), EncryptionKey) != UdpConsts.ENCRYPT_CHECK_STRING) { //!!BAD ENCRYPTION KEY m_Parent.DebugDump("Received an encrypted packet but the stored encryption key failed to decrypt it!"); return; } } pcmd += 2; //Populate the AllFields property of the command new_cmd.AllFields = command_packet.Substring(pcmd); if ((new_cmd.Flags & UdpConsts.FLAGS_RELIABLE) > 0) { //Is this reliable packet a compound piece? if ((new_cmd.Flags & UdpConsts.FLAGS_COMPOUNDPIECE) > 0) { //Assume this is the first piece of the split command if (CompoundCommand == null) { m_Parent.DebugDump("Got first part of a compound command, stored."); CompoundCommand = new Command(); CompoundCommand.AllFields = new_cmd.AllFields; CompoundCommand.FieldSizes = new_cmd.FieldSizes; CompoundCommand.Flags = new_cmd.Flags; //Remove the compound piece flag (will be treated as a single packet) CompoundCommand.Flags &= (byte)(~UdpConsts.FLAGS_COMPOUNDPIECE); CompoundCommand.NumFields = new_cmd.NumFields; CompoundCommand.SequenceNum = new_cmd.SequenceNum; CompoundCommand.OPCode = new_cmd.OPCode; return; } else { //Additional pieces have FLAGS_COMPOUNDPIECE set also.... m_Parent.DebugDump("Got an additional compound piece."); //Is not the first nor last part so just addon the fields CompoundCommand.AllFields += new_cmd.AllFields; return; } } //The last compound piece has FLAGS_COMPOUNDEND set. if ((new_cmd.Flags & UdpConsts.FLAGS_COMPOUNDEND) > 0) { m_Parent.DebugDump("Got last compound piece - sending to command processing."); //Add the fields on CompoundCommand.AllFields += new_cmd.AllFields; //Swap the incomplete part with the whole command new_cmd = CompoundCommand; CompoundCommand = null; } } new_cmd.Initialize(); //If the packet is encrypted decrypt the fields now... if ((new_cmd.Flags & UdpConsts.FLAGS_ENCRYPTED) > 0) { new_cmd.AllFields = ""; //Decrypt the fields for (int i = 0; i < new_cmd.NumFields; i++) { new_cmd.Fields[i] = Util.XORCrypt(new_cmd.Fields[i], EncryptionKey); //Rebuild the allfields property with the decrypted field data new_cmd.AllFields += new_cmd.Fields[i]; } } //Send to command processing ProcessCompletedCommand(new_cmd); } catch { m_Parent.DebugDump("Exception whilst parsing input from " + RemoteEP.ToString() + " as command, probably not a command. Ignoring."); } }
/// <summary> /// This function is called internally when a command has been parsed and is ready to be processed /// by the UDP engine and possibly the host application. /// </summary> /// <param name="cmd">Object containing data about the command to be processed.</param> public void ProcessCompletedCommand(Command cmd) { //Client sent login details if (!Authed) { if (cmd.OPCode == UdpConsts.OPCODE_LOGINDETAILS) { m_Parent.ConnectionAuthing(this, cmd); return; } } //Remote host sent us a ping if (cmd.OPCode == UdpConsts.OPCODE_PING) { if(Authed) { m_Parent.DebugDump("Received ping from " + this.RemoteEP.ToString()); UpdateTimeout(); if (!Server) //If this is not a connection to a server SendUnreliableCommand(0, UdpConsts.OPCODE_PING, null); } return; } //Server sent acknowledgement of our connection if (cmd.OPCode == UdpConsts.OPCODE_LOGINACK) { if (cmd.Fields[0] == "OK") { Authed = true; m_Parent.DebugDump("Authenticated with " + this.RemoteEP.ToString() + " OK. Connected!"); //Send the authenticated event to the third party application m_Parent.AuthenticatedWithConnection(this, true, ""); } else { m_Parent.DebugDump("Authentication to " + this.RemoteEP.ToString() + " Failed!"); //Send the not authed event m_Parent.AuthenticatedWithConnection(this, false, cmd.Fields[1]); //Disconnect the connection (but don't send a disconnection packet back) m_Parent.RemoveConnection(this, false, cmd.Fields[1]); } return; } //Remote host disconnected from us if (cmd.OPCode == UdpConsts.OPCODE_DISCONNECT) { m_Parent.RemoveConnection(this, false, cmd.Fields[0]); return; } //Reliable packet acknowledgement if (cmd.OPCode == UdpConsts.OPCODE_RELIABLEACK) { m_Parent.DebugDump("Received reliable ACK for packet " + cmd.Fields[0] + "."); //Get the current reliable command ReliableEntry rcmd = null; RQueue.GetCurrentReliableCommand(out rcmd); //If the sequence number from the one stored in the first field //of the ACK is the same as the one in the queue, remove it. if (rcmd != null) { try { if (rcmd.SequenceNum == Convert.ToUInt32(cmd.Fields[0])) { RQueue.NextReliableCommand(); ReliableEntry next_rel = null; RQueue.GetCurrentReliableCommand(out next_rel); if (next_rel != null) { m_Parent.DebugDump("Moving reliable packet queue - next packet is " + next_rel.SequenceNum.ToString()); //Send the next reliable packet m_Parent.SendData(RemoteEP.Address.ToString(), RemoteEP.Port, next_rel.CommandPacket); } else m_Parent.DebugDump("Moving reliable packet queue - no more packets on reliable queue."); } } catch (Exception e) { m_Parent.DebugDump("Exception: " + e.Message); } } } //Give unrecognised commands to the application if(Authed) m_Parent.CommandReceived(this, cmd); }
/// <summary> /// Initialises the connection object, setting variables to defaults. /// </summary> /// <param name="udp">Reference to the main core engine that created the connection.</param> public Connection(CommonUdp udp) { EncryptionKey = ""; RemoteEP = null; LastSentPacket = 0; LastReceivedPacket = 0; LastSentPacketR = 1; LastReceivedPacketR = 0; LastReceivedPacketSeq = 0; Authed = false; //Either (server) client is authed or (client) is authed with server Server = false; //Connection is a server connection m_Parent = udp; TimeoutTime = DateTime.Now.AddSeconds(UdpConsts.CONNECTION_TIMEOUT); CompoundCommand = null; m_RequestID = ""; RQueue = new ReliableQueue(); }
/// <summary> /// Called when a connection is attempting to authenticate /// </summary> /// <param name="cn">Connection</param> /// <param name="cmd">Command</param> /// <returns>UDP_OK or error code</returns> internal int ConnectionAuthing(Connection cn, Command cmd) { //Is the connection already authed? if(cn.Authed) { DebugDump("Connection " + cn.RemoteEP.ToString() + " sent an auth packet but is already authed? Command ignored."); return UdpConsts.UDP_OK; } ConnectionAuthEventArgs ea = new ConnectionAuthEventArgs(cn, cmd); DebugDump("Connection " + cn.RemoteEP.ToString() + " sent login data..."); //Have the third party client app process the login data if (OnConnectionAuth != null) OnConnectionAuth(null, ea); //Clamp the disallow reason to 200 characters if (ea.DisallowReason.Length > 200) ea.DisallowReason = ea.DisallowReason.Substring(0, 200); if (!ea.AllowConnection) { DebugDump("Login data is bad, rejecting connection."); cn.SendUnreliableCommand(0, UdpConsts.OPCODE_LOGINACK, new string[] { "FAIL", ea.DisallowReason }); m_Clients.RemoveConnection(cn, true, ea.DisallowReason); } else { DebugDump("Login data is ok, connection authed."); cn.SendUnreliableCommand(0, UdpConsts.OPCODE_LOGINACK, new string[] { "OK" }); cn.Authed = true; } return UdpConsts.UDP_OK; }
/// <summary> /// Called by a connection object when a command comes in /// </summary> /// <param name="cn">Connection that sent the command</param> /// <param name="cmd">Command received</param> /// <returns>UDP_OK or error code</returns> internal int CommandReceived(Connection cn, Command cmd) { if (OnCommandReceived != null) OnCommandReceived(null, new CommandEventArgs(cmd, cn, null)); return UdpConsts.UDP_OK; }
/// <summary> /// Processes a connectionless command. /// </summary> /// <param name="c">Command to process</param> /// <returns>UDP_OK or error code.</returns> private int ProcessConnectionlessComand(IPEndPoint remote_ep, Command c) { int retval = UdpConsts.UDP_OK; try { //======================================================================== //Connection request from a remote host if (c.OPCode == UdpConsts.OPCODE_CONNECTIONREQUEST) { Connection temp; bool cant_connect = false; m_Servers.ConnectionByIPPort(remote_ep.Address.ToString(), remote_ep.Port, out temp); if (temp != null) { DebugDump("Unable to accept connection, connection exists in server list."); retval = UdpConsts.UDP_ALREADYCONNECTED; cant_connect = true; } m_Clients.ConnectionByIPPort(remote_ep.Address.ToString(), remote_ep.Port, out temp); if (temp != null) { DebugDump("Unable to accept connection, connection exists in client list."); retval = UdpConsts.UDP_ALREADYCONNECTED; cant_connect = true; } //Can't accept this connection as we already have it in the list //Client will just have to wait until it times out. if (cant_connect) { SendConnectionlessCommand(remote_ep.Address.ToString(), remote_ep.Port, UdpConsts.OPCODE_CONNECTIONACK, new string[] { c.Fields[0], "FAIL", "Connection from this client already exists." }); return retval; } string encryption_key = ""; //Is encryption on? If so generate an encryption key. if(m_bEncrypt) encryption_key = Util.GenerateEncryptionKey(); //Create a new connection object and add it to the clients list... Connection new_conn = new Connection(this); new_conn.EncryptionKey = encryption_key; new_conn.RemoteEP = remote_ep; DebugDump("Remote host at " + remote_ep.ToString() + " requested a connection."); DebugDump("Sending acknowledgement packet."); SendConnectionlessCommand(remote_ep.Address.ToString(), remote_ep.Port, UdpConsts.OPCODE_CONNECTIONACK, new string[] { c.Fields[0], "OK", encryption_key }); DebugDump("Adding remote host to clients list."); m_Clients.NewConnection(new_conn); return UdpConsts.UDP_OK; } //======================================================================== //Connection acknowledge from a remote host if (c.OPCode == UdpConsts.OPCODE_CONNECTIONACK) { DebugDump("Got connection acknowledgement from " + remote_ep.ToString() + "."); if(!m_ConnTimeouts.EntryExists(c.Fields[0])) { //Make sure we actually _asked_ for this connection ack. DebugDump("Connection accept packet sent by " + remote_ep.ToString() + " but was unrequested - ignored."); } else if (c.Fields[1] == "OK") { //Server accepted our connection. DebugDump("Connection " + c.Fields[0] + " accepted by " + remote_ep.ToString()); //Create and store a connection object for the server that accepted us. Connection new_con = new Connection(this); new_con.EncryptionKey = c.Fields[2]; new_con.RemoteEP = remote_ep; new_con.Server = true; //Is a connection to a server new_con.RequestID = c.Fields[0]; m_Servers.NewConnection(new_con); //Remove from the timeouts list m_ConnTimeouts.RemoveConnectionEntry(c.Fields[0]); //Sending login data is to be handled in the actual application //Basically it needs to send an command packet to the host containing the login data. DebugDump("Server is requesting login information, sending."); if (OnLoginRequested != null) OnLoginRequested(null, new LoginSendEventArgs(new_con, true, "")); else //Application did nothing, so send a blank login packet now. SendCommand(remote_ep.Address.ToString(), remote_ep.Port, new_con.EncryptionKey, 0, 0, UdpConsts.OPCODE_LOGINDETAILS, null); } else { //Server did not accept our connection. //Make a temporary connection object Connection new_con = new Connection(this); new_con.EncryptionKey = c.Fields[1]; new_con.RemoteEP = remote_ep; new_con.Server = true; //Is a connection to a server DebugDump("Connection not accepted - " + c.Fields[2] + "."); if (OnLoginRequested != null) OnLoginRequested(null, new LoginSendEventArgs(new_con, false, c.Fields[1])); } return UdpConsts.UDP_OK; } } catch (Exception e) { DebugDump("Exception whilst processing connectionless command (" + e.Message + ")"); return UdpConsts.UDP_FAIL; } //======================================================================== //Call the event (so it can be handled in the application) if (OnConnectionlessCommand != null) OnConnectionlessCommand(null, new CommandEventArgs(c, null, remote_ep)); return UdpConsts.UDP_OK; }
/// <summary> /// Receive loop continually receives data. /// </summary> private void ReceiveLoop() { int iExCount = 0; if(m_CurrentIP == "") m_LocalEndPoint = new IPEndPoint(IPAddress.Any, m_CurrentPort); else m_LocalEndPoint = new IPEndPoint(IPAddress.Parse(m_CurrentIP), m_CurrentPort); DebugDump("Created the local IP endpoint at " + m_LocalEndPoint.ToString( )); try { m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); DebugDump("Created the socket."); m_Socket.Bind(m_LocalEndPoint); DebugDump("Bound the socket to the local end point, now entering receive loop..."); m_iState = UdpConsts.UDP_STATE_LISTENING; if (OnListenStateChanged != null) OnListenStateChanged(null, new ListenEventArgs(true)); while (true) { try { //Make space to store the data from the socket Byte[] received = new Byte[UdpConsts.MAX_COMMAND_LEN]; //Create an end point, just give it temporary values EndPoint remoteEP = new IPEndPoint(m_LocalEndPoint.Address, m_LocalEndPoint.Port); //Read bytes from the socket int bytesReceived = m_Socket.ReceiveFrom(received, ref remoteEP); IPEndPoint remoteIPEP = (IPEndPoint)remoteEP; //string str_received = Encoding.UTF8.GetString(received); string str_received = Util.BytesToString(received); str_received = str_received.Substring(0, bytesReceived); //Fire the received event if it is being used (allowing raw data to be caught) if(OnDataReceived != null) OnDataReceived(null, new ReceivedEventArgs((IPEndPoint)remoteEP, str_received)); //Handle connectionless packets try { int pcmd = 0; //Allocate a temporary command object Command new_cmd = new Command(); //Parse the start of the command new_cmd.OPCode = str_received.Substring(pcmd, 2); pcmd += 2; new_cmd.SequenceNum = Util.BytesToUint(str_received.Substring(pcmd, 4)); pcmd += 4; new_cmd.Flags = (byte)str_received[pcmd]; pcmd++; //If the command packet is connectionless then parse it now. if ((new_cmd.Flags & UdpConsts.FLAGS_CONNECTIONLESS) > 0) { new_cmd.NumFields = Util.BytesToShort(str_received.Substring(pcmd, 2)); pcmd += 2; new_cmd.FieldSizes = new short[new_cmd.NumFields]; for (short i = 0; i < new_cmd.NumFields; i++) { new_cmd.FieldSizes[i] = Util.BytesToShort(str_received.Substring(pcmd, 2)); pcmd += 2; } pcmd += 2; new_cmd.AllFields = str_received.Substring(pcmd); new_cmd.Initialize(); //Process the connectionless command ProcessConnectionlessComand(remoteIPEP, new_cmd); } else { //Locate connection this command belongs to and process it there. Connection conn; //Check both the client and server lists m_Clients.ConnectionByRemoteEndpoint(remoteIPEP, out conn); if(conn == null) m_Servers.ConnectionByRemoteEndpoint(remoteIPEP, out conn); if (conn == null) DebugDump("Connection-related packet with no matching connection arrived - ignored."); else conn.ProcessCommandPacket(str_received); } } catch { DebugDump("Exception whilst parsing input from " + remoteIPEP.ToString() + " as command, probably not a command. Ignoring."); } //Reset the exception count to 0 iExCount = 0; } catch (SocketException se) { if ((se.ErrorCode != 10061) && (se.ErrorCode != 10054)) { //Fire the socket error event. if (se.ErrorCode != 10004) { if (OnSocketError != null) OnSocketError(null, new SocketEventArgs(se.ErrorCode, se.Message)); } DebugDump("Socket Exception in receive loop (" + se.ErrorCode.ToString() + "): " + se.Message); if (m_iState != UdpConsts.UDP_STATE_LISTENING) break; iExCount++; } } catch (Exception e) { DebugDump("Exception in receive loop: " + e.Message); if (m_iState != UdpConsts.UDP_STATE_LISTENING) break; iExCount++; } if (iExCount == UdpConsts.MAX_EXCEPTIONS) { DebugDump("Got too many consecutive exceptions in the receive loop, terminating."); break; } } } catch (SocketException se) { //Fire the socket error event. if (OnSocketError != null) OnSocketError(null, new SocketEventArgs(se.ErrorCode, se.Message)); DebugDump("Socket Exception (" + se.ErrorCode.ToString() + "): " + se.Message); } catch (Exception e) { DebugDump("Exception: " + e.Message); } //We are out of the loop but the server thinks we are not? make sure it does. if((m_iState != UdpConsts.UDP_STATE_IDLE) && (m_iState != UdpConsts.UDP_STATE_CLOSING)) { StopListen(); } DebugDump("Set the system state to idle."); //Set the state to idle. m_iState = UdpConsts.UDP_STATE_IDLE; DebugDump("Exited the receive loop."); }