/* static double _noneTime = 0.0f; static int _noneSize = 0; static int _encodeSize = 0; static double _encodeTime = 0.0f; static int _encodeCompressSize = 0; static double _encodeCompressTime = 0.0f; static int _encodeCompressEncryptSize = 0; static double _encodeCompressEncryptTime = 0.0f; static int _compressSize = 0; static double _compressTime = 0.0f; static int _compressEncryptSize = 0; static double _compressEncryptTime = 0.0f; static int _encryptSize = 0; static double _encryptTime = 0.0f; static int _encryptEncodeSize = 0; static double _encryptEncodeTime = 0.0f; static int _packetCounter = 0; */ /// <summary> /// Serializes this packet to a byte buffer than can be recreated by calling FromBuffer. /// </summary> /// <param name="inReplyTo">If not null this packet is marked as a reply to another packet.</param> /// <param name="cryptor">The encryption class to encrypt the buffer with.</param> /// <param name="compressor">Compressor used to compress the packet.</param> /// <param name="encoder">Encoder used for delta-encoding of packets.</param> /// <returns>Byte buffer conversion of packet.</returns> public byte[] ToBuffer(Packet inReplyTo, StreamEncryptor cryptor, StreamCompressor compressor, StreamDeltaEncoder encoder) { byte[] data = CreatePayload(); // Data cannot be larger than a ushort. if (data.Length >= ushort.MaxValue) { throw new InvalidDataException("Packets cannot be larger than a ushort's max value."); } // Work out checksum of unmodified payload. PearsonHash hash = new PearsonHash(); hash.AddBuffer(data, 0, data.Length); byte dataChecksum = hash.Calculate(); // Mangle our data based ready for transport. data = MangleData(data, encoder, compressor, cryptor); // DEBUG TIMING /* HighPerformanceTimer timer = new HighPerformanceTimer(); StreamEncryptor tmp_cryptor = cryptor == null ? null : cryptor.Clone(); StreamCompressor tmp_compressor = new StreamCompressor(); StreamDeltaEncoder tmp_encoder = encoder == null ? null : encoder.Clone(); byte[] tmp = CreatePayload(); timer.Start(); _noneSize += MangleData(tmp, null, null, null).Length; _noneTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _encodeSize += MangleData(tmp, tmp_encoder, null, null).Length; _encodeTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _encodeCompressSize += MangleData(tmp, tmp_encoder, tmp_compressor, null).Length; _encodeCompressTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _encodeCompressEncryptSize += MangleData(tmp, tmp_encoder, tmp_compressor, tmp_cryptor).Length; _encodeCompressEncryptTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _compressSize += MangleData(tmp, null, tmp_compressor, null).Length; _compressTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _compressEncryptSize += MangleData(tmp, null, tmp_compressor, tmp_cryptor).Length; _compressEncryptTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _encryptSize += MangleData(tmp, null, null, tmp_cryptor).Length; _encryptTime += timer.Stop(); tmp = CreatePayload(); timer.Start(); _encryptEncodeSize += MangleData(tmp, tmp_encoder, null, tmp_cryptor).Length; _encryptEncodeTime += timer.Stop(); _packetCounter++; if (_packetCounter >= 100000) { System.Console.WriteLine("DONE!"); } * */ // DEBUG TIMING // Write in data checksum. byte[] buffer = new byte[HEADER_SIZE + data.Length]; buffer[0] = dataChecksum; // Write in payload size. byte[] bytes = BitConverter.GetBytes((ushort)data.Length); buffer[1] = bytes[0]; buffer[2] = bytes[1]; // Write in class hash. bytes = BitConverter.GetBytes(this.GetType().FullName.GetHashCode()); buffer[3] = bytes[0]; buffer[4] = bytes[1]; buffer[5] = bytes[2]; buffer[6] = bytes[3]; // Write in a reply-to-packet-id. bytes = BitConverter.GetBytes(inReplyTo == null ? 0 : inReplyTo.PacketID); buffer[7] = bytes[0]; buffer[8] = bytes[1]; // Write in payload. for (int i = 0; i < data.Length; i++) { buffer[HEADER_SIZE + i] = data[i]; } return buffer; }
/// <summary> /// Mangles the given buffer of data. /// </summary> /// <param name="data">data to mangle.</param> /// <param name="cryptor">The encryption class to encrypt the buffer with.</param> /// <param name="compressor">Compressor used to compress the packet.</param> /// <param name="encoder">Encoder used for delta-encoding of packets.</param> private byte[] MangleData(byte[] data, StreamDeltaEncoder encoder, StreamCompressor compressor, StreamEncryptor cryptor) { // Delta encoder data. if (encoder != null) { encoder.EncodeInPlace(data, GetType().FullName.GetHashCode()); } // Compress payload. if (compressor != null) { data = compressor.Compress(data); } // Encrypt output. if (cryptor != null) { cryptor.EncryptInPlace(data); } return data; }
/// <summary> /// Finishes the job that FromHeader starts, decrypts and parses the packet payload. /// /// Be aware that for the sake of speed, decryption of the data will be done /// in-place - the value of buffer will be in its decrypted form when this /// function returns! /// </summary> /// <returns>True if successful, or false if payload is invalid (and thus packet is too).</returns> /// <param name="cryptor">The encryption class to decrypt the buffer with.</param> /// <param name="compressor">Compressor used to decompress the packet.</param> /// <param name="encoder">Encoder used for delta-deencoding of packets.</param> /// <param name="buffer">Buffer to construct packet from.</param> public bool RecievePayload(byte[] buffer, StreamEncryptor cryptor, StreamCompressor compressor, StreamDeltaEncoder encoder) { try { // Decrypt the payload. cryptor.DecryptInPlace(buffer); // Decompress payload. buffer = compressor.Decompress(buffer); // Delta un-encode. encoder.DecodeInPlace(buffer, GetType().FullName.GetHashCode()); } catch (Exception) { Logger.Error("Recieved packet with invalid data. Dropping packet.", LoggerVerboseLevel.Normal); return false; } // Check that the checksum is correct. PearsonHash hash = new PearsonHash(); hash.AddBuffer(buffer, 0, buffer.Length); int bufferChecksum = hash.Calculate(); if (bufferChecksum != m_checksum) { Logger.Error("Recieved packet with invalid checksum, got {0}, expected {1}. Dropping packet.", LoggerVerboseLevel.Normal, bufferChecksum, m_checksum); return false; } // Parse the payload! return ParsePayload(buffer); }
/// <summary> /// Constructs a packet from the given packet header. /// RecievePayload should be called on the packet instance after data is recieved. /// /// Be aware that for the sake of speed, decryption of the header will be done /// in-place - the value of buffer will be in its decrypted form when this /// function returns! /// </summary> /// <returns>Skeleton packet instance holding header information. Or null if header is invalid.</returns> /// <param name="cryptor">The encryption class to decrypt the buffer with.</param> /// <param name="compressor">Compressor used to decompress the packet.</param> /// <param name="encoder">Encoder used for delta-deencoding of packets.</param> /// <param name="buffer">Buffer to construct packet from.</param> public static Packet FromHeader(byte[] buffer, StreamEncryptor cryptor, StreamCompressor compressor, StreamDeltaEncoder encoder) { // Make sure we have been passed a header, and not some other kind of buffer. if (buffer.Length != HEADER_SIZE) { Logger.Error("Recieved packet with invalid header size, got {0} bytes, expected {1}. Dropping packet.", LoggerVerboseLevel.Normal, buffer.Length, HEADER_SIZE); return null; } // Read header. byte checksum = buffer[0]; int payloadSize = (int)BitConverter.ToUInt16(buffer, 1); int classHash = (int)BitConverter.ToInt32(buffer, 3); int replyToPacketID = (int)BitConverter.ToUInt16(buffer, 7); // Verify class hash is correct. Type classType = null; lock (m_hashToTypeTable_lock) { if (m_hashToTypeTable.ContainsKey(classHash) == true) { classType = m_hashToTypeTable[classHash]; } else { var subclasses = typeof(Packet).Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Packet))); foreach (Type type in subclasses) { int code = type.FullName.GetHashCode(); if (code == classHash) { m_hashToTypeTable.Add(code, type); classType = type; break; } } } } // No class available for hash? if (classType == null) { Logger.Error("Recieved packet with invalid class hash '{0}'. Dropping packet.", LoggerVerboseLevel.Normal, classHash); return null; } // Load the packet. Packet packet = null; try { packet = (Packet)Activator.CreateInstance(classType); packet.m_payloadSize = payloadSize; packet.m_checksum = checksum; packet.m_replyToPacketID = replyToPacketID; } catch (Exception) { Logger.Error("Failed to create packet of type '{0}'. Dropping packet.", LoggerVerboseLevel.Normal, classType.Name); return null; } return packet; }
/// <summary> /// Disposes of the socket if it exists. /// </summary> /// <param name="byError">If this disconnect was caused by an error, then we will try and reconnect.</param> /// <param name="timeout">Amount of time in milliseconds to wait until socket is closed. 0 closes socket instantly.</param> /// <param name="reconnectDelay">Used to determine at what point we reconnect.</param> private void DisposeSocket(bool byError, int timeout = 1000) { // Disconnect from the socket. lock (m_socket_lock) { if (m_socket != null) { try { if (m_socket.Connected == true) { m_socket.Disconnect(false); } m_socket.Close(timeout); m_socket = null; } catch (SocketException) { Logger.Warning("Recieved socket exception whilst disposing of old socket.", LoggerVerboseLevel.Highest); } catch (ObjectDisposedException) { Logger.Warning("Recieved socket exception whilst disposing of old socket.", LoggerVerboseLevel.Highest); } catch (NullReferenceException) { Logger.Warning("Recieved socket exception whilst disposing of old socket.", LoggerVerboseLevel.Highest); } } } // Disconnect all peers. lock (m_peers_lock) { try { foreach (Connection connection in m_peers) { connection.DisposeSocket(false, timeout); // BUG: Lock loop, this calls DisposeSocket, which in turn calls listen_socket (this connection) .PeerDisconnect, which in turn calls this function! m_disconnected_peers.Add(connection); } m_peers.Clear(); } catch (InvalidOperationException ex) { // Hack: This needs fixing, its just to prevent a bug with the lock loop that causes // a "collection modified" error. } } // Reset state. lock (m_socket_lock) { m_connected = false; m_connecting = false; m_listening = false; m_readingMessagePayload = false; m_hardwareFingerprint = new byte[0]; m_computerName = ""; m_computerUserName = ""; m_connectionEstablished = false; m_sentBytes = 0; m_recievedBytes = 0; m_queuedSendBytes = 0; m_sendPacketIDCounter = 0; m_recvPacketIDCounter = 0; m_timeSinceLastPing = 0; m_timeSinceLastPong = 0; m_waitingForPong = false; m_ping = 0; m_encryptor = null; m_deltaEncoder = null; m_compressor = null; } lock (m_messageQueue_lock) { m_messageQueue.Clear(); } // If we have a listen socket, remove us from their peer list. if (m_listenConnection != null) { m_listenConnection.PeerDisconnected(this); } // Clear out the packet wait list. lock (m_replyWaitContexts_lock) { foreach (ConnectionReplyWaitContext context in m_replyWaitContexts) { context.Event.Set(); } m_replyWaitContexts.Clear(); } // Reconnect to the server as it appears // that we have been disconnected! //if (byError == true) //{ // PerformReconnect(); //} }
/// <summary> /// Sets up this connection as a peer of a listen connection. /// </summary> private void SetupAsPeer(Connection listenConnection, Socket socket) { lock (m_socket_lock) { m_listenConnection = listenConnection; m_socket = socket; m_connected = true; m_endPoint = (System.Net.IPEndPoint)socket.RemoteEndPoint; m_encryptor = new StreamEncryptor(""); m_compressor = new StreamCompressor(); m_deltaEncoder = new StreamDeltaEncoder(); } // Begin reading from the server. StartRead(); // Start polling connection. StartPoll(); }
/// <summary> /// Connects to the service and updates over time. /// </summary> /// <param name="ip">IP address of the server to connect to.</param> /// <param name="port">Port number that service is running on.</param> public bool ConnectOverTime(string ip, UInt16 port) { // Close down old socket. DisposeSocket(false); m_connecting = true; m_connectionEstablished = false; m_encryptor = new StreamEncryptor(""); m_compressor = new StreamCompressor(); m_deltaEncoder = new StreamDeltaEncoder(); // Local address? if (ip == "127.0.0.1" || ip == "::1" || ip == HardwareHelper.GetLocalIPAddress()) { ip = "localhost"; } // Create new socket! System.Net.IPAddress[] hosts = null; try { hosts = Dns.GetHostAddresses(ip); } catch (System.Net.Sockets.SocketException) { hosts = null; } if (hosts == null || hosts.Length == 0) { Logger.Error("Failed to connect to host - could not find address for {0}.", LoggerVerboseLevel.Normal, ip); m_connecting = false; return false; } System.Net.IPAddress host = hosts[0]; try { bool result = false; SocketAsyncEventArgs args; lock (m_socket_lock) { m_endPoint = new IPEndPoint(host, port); m_socket = new Socket(host.AddressFamily, SocketType.Stream, ProtocolType.Tcp); m_socket.SendTimeout = Settings.CONNECTION_SEND_TIMEOUT; m_socket.ReceiveTimeout = Settings.CONNECTION_RECIEVE_TIMEOUT; // Get connecting! args = new SocketAsyncEventArgs();//g_socketAsyncEventArgsPool.NewObject();//new SocketAsyncEventArgs(); args.RemoteEndPoint = m_endPoint; args.UserToken = this; args.Completed += SocketConnect_Completed; Logger.Info("Attempting to connect to {0}:{1}.", LoggerVerboseLevel.High, ip, port); result = m_socket.ConnectAsync(args); } if (result == false) { SocketConnect_Completed(this, args); } } catch (SocketException) { Logger.Error("Encountered socket exception while attempting to connect to host.", LoggerVerboseLevel.Normal); DisposeSocket(false); } // And we are done! return m_connected; }