/// <summary> /// Receives typical data packets before dispatching them for consumption by the rest of SteamKit /// </summary> /// <param name="packet">The packet.</param> private void ReceiveData(UdpPacket packet) { // Data packets are unexpected if a valid connection has not been established if ( state != (int)State.Connected && state != (int)State.Disconnecting ) return; // If we receive a packet that we've already processed (e.g. it got resent due to a lost ack) // or that is already waiting to be processed, do nothing. if ( packet.Header.SeqThis <= inSeqHandled || inPackets.ContainsKey(packet.Header.SeqThis) ) return; inPackets.Add(packet.Header.SeqThis, packet); while ( DispatchMessage() ) ; }
/// <summary> /// Receives the challenge and responds with a Connect request /// </summary> /// <param name="packet">The packet.</param> private void ReceiveChallenge(UdpPacket packet) { if ( Interlocked.CompareExchange( ref state, (int)State.ConnectSent, (int)State.ChallengeReqSent ) != (int)State.ChallengeReqSent ) return; ChallengeData cr = new ChallengeData(); cr.Deserialize(packet.Payload); ConnectData cd = new ConnectData(); cd.ChallengeValue = cr.ChallengeValue ^ ConnectData.CHALLENGE_MASK; MemoryStream ms = new MemoryStream(); cd.Serialize(ms); ms.Seek(0, SeekOrigin.Begin); SendSequenced(new UdpPacket(EUdpPacketType.Connect, ms)); inSeqHandled = packet.Header.SeqThis; }
/// <summary> /// Receives the notification of an accepted connection and sets the connection id that will be used for the /// connection's duration. /// </summary> /// <param name="packet">The packet.</param> private void ReceiveAccept(UdpPacket packet) { if ( Interlocked.CompareExchange( ref state, (int)State.Connected, (int)State.ConnectSent ) != (int)State.ConnectSent ) return; DebugLog.WriteLine("UdpConnection", "Connection established"); remoteConnId = packet.Header.SourceConnID; inSeqHandled = packet.Header.SeqThis; OnConnected( EventArgs.Empty ); }
/// <summary> /// Processes incoming packets, maintains connection consistency, and oversees outgoing packets. /// </summary> private void NetLoop(object param) { // Variables that will be used deeper in the function; locating them here avoids recreating // them since they don't need to be. var userRequestedDisconnect = false; EndPoint packetSender = (EndPoint)new IPEndPoint(IPAddress.Any, 0); byte[] buf = new byte[2048]; var epTask = param as Task<IPEndPoint>; try { if ( epTask != null ) { remoteEndPoint = epTask.Result; } else { DebugLog.WriteLine("UdpConnection", "Invalid endpoint supplied for connection: {0}", param); } } catch ( AggregateException ae ) { foreach ( var ex in ae.Flatten().InnerExceptions ) { DebugLog.WriteLine("UdpConnection", "Endpoint task threw exception: {0}", ex); } } if ( remoteEndPoint != null ) { timeOut = DateTime.Now.AddSeconds(TIMEOUT_DELAY); nextResend = DateTime.Now.AddSeconds(RESEND_DELAY); if ( Interlocked.CompareExchange(ref state, (int)State.ChallengeReqSent, (int)State.Disconnected) != (int)State.Disconnected ) { state = (int)State.Disconnected; userRequestedDisconnect = true; } else { // Begin by sending off the challenge request SendPacket(new UdpPacket(EUdpPacketType.ChallengeReq)); } } while ( state != (int)State.Disconnected ) { try { // Wait up to 150ms for data, if none is found and the timeout is exceeded, we're done here. if ( !sock.Poll(150000, SelectMode.SelectRead) && DateTime.Now > timeOut ) { DebugLog.WriteLine("UdpConnection", "Connection timed out"); state = (int)State.Disconnected; break; } // By using a 10ms wait, we allow for multiple packets sent at the time to all be processed before moving on // to processing output and therefore Acks (the more we process at the same time, the fewer acks we have to send) while ( sock.Poll(10000, SelectMode.SelectRead) ) { int length = sock.ReceiveFrom(buf, ref packetSender); // Ignore packets that aren't sent by the server we're connected to. if ( !packetSender.Equals(remoteEndPoint) ) continue; // Data from the desired server was received; delay timeout timeOut = DateTime.Now.AddSeconds(TIMEOUT_DELAY); MemoryStream ms = new MemoryStream(buf, 0, length); UdpPacket packet = new UdpPacket(ms); ReceivePacket(packet); } } catch (IOException ex) { DebugLog.WriteLine("UdpConnection", "Exception occurred while reading packet: {0}", ex); state = (int)State.Disconnected; break; } catch ( SocketException e ) { DebugLog.WriteLine("UdpConnection", "Critical socket failure: " + e.ErrorCode); state = (int)State.Disconnected; break; } // Send or resend any sequenced packets; a call to ReceivePacket can set our state to disconnected // so don't send anything we have queued in that case if ( state != (int)State.Disconnected ) SendPendingMessages(); // If we received data but had no data to send back, we need to manually Ack (usually tags along with // outgoing data); also acks disconnections if ( inSeq != inSeqAcked ) SendAck(); // If a graceful shutdown has been requested, nothing in the outgoing queue is discarded. // Once it's empty, we exit, since the last packet was our disconnect notification. if ( state == (int)State.Disconnecting && outPackets.Count == 0 ) { DebugLog.WriteLine("UdpConnection", "Graceful disconnect completed"); state = (int)State.Disconnected; userRequestedDisconnect = true; break; } } DebugLog.WriteLine("UdpConnection", "Calling OnDisconnected"); OnDisconnected( new DisconnectedEventArgs( userRequestedDisconnect ) ); }
/// <summary> /// Receives the packet, performs all sanity checks and then passes it along as necessary. /// </summary> /// <param name="packet">The packet.</param> private void ReceivePacket(UdpPacket packet) { // Check for a malformed packet if ( !packet.IsValid ) return; else if ( remoteConnId > 0 && packet.Header.SourceConnID != remoteConnId ) return; DebugLog.WriteLine("UdpConnection", "<- Recv'd {0} Seq {1} Ack {2}; {3} bytes; Message: {4} bytes {5} packets", packet.Header.PacketType, packet.Header.SeqThis, packet.Header.SeqAck, packet.Header.PayloadSize, packet.Header.MsgSize, packet.Header.PacketsInMsg); // Throw away any duplicate messages we've already received, making sure to // re-ack it in case it got lost. if ( packet.Header.PacketType == EUdpPacketType.Data && packet.Header.SeqThis < inSeq ) { SendAck(); return; } // When we get a SeqAck, all packets with sequence numbers below that have been safely received by // the server; we are now free to remove our copies if ( outSeqAcked < packet.Header.SeqAck ) { outSeqAcked = packet.Header.SeqAck; // outSeqSent can be less than this in a very rare case involving resent packets. if ( outSeqSent < outSeqAcked ) outSeqSent = outSeqAcked; outPackets.RemoveAll( x => x.Header.SeqThis <= outSeqAcked ); nextResend = DateTime.Now.AddSeconds(RESEND_DELAY); } // inSeq should always be the latest value that we can ack, so advance it as far as is possible. if ( packet.Header.SeqThis == inSeq + 1 ) do inSeq++; while ( inPackets.ContainsKey(inSeq + 1) ); switch ( packet.Header.PacketType ) { case EUdpPacketType.Challenge: ReceiveChallenge(packet); break; case EUdpPacketType.Accept: ReceiveAccept(packet); break; case EUdpPacketType.Data: ReceiveData(packet); break; case EUdpPacketType.Disconnect: DebugLog.WriteLine("UdpConnection", "Disconnected by server"); state = (int)State.Disconnected; return; case EUdpPacketType.Datagram: break; default: DebugLog.WriteLine("UdpConnection", "Received unexpected packet type " + packet.Header.PacketType); break; } }
/// <summary> /// Sends the packets as one sequenced, reliable net message. /// </summary> /// <param name="packets">The packets that make up the single net message</param> private void SendSequenced(UdpPacket[] packets) { uint msgStart = outSeq; foreach ( UdpPacket packet in packets ) { SendSequenced(packet); // Correct for any assumptions made for the single-packet case. packet.Header.PacketsInMsg = (uint) packets.Length; packet.Header.MsgStartSeq = msgStart; } }
/// <summary> /// Sends a packet immediately. /// </summary> /// <param name="packet">The packet.</param> private void SendPacket(UdpPacket packet) { packet.Header.SourceConnID = sourceConnId; packet.Header.DestConnID = remoteConnId; packet.Header.SeqAck = inSeqAcked = inSeq; DebugLog.WriteLine("UdpConnection", "Sent -> {0} Seq {1} Ack {2}; {3} bytes; Message: {4} bytes {5} packets", packet.Header.PacketType, packet.Header.SeqThis, packet.Header.SeqAck, packet.Header.PayloadSize, packet.Header.MsgSize, packet.Header.PacketsInMsg); byte[] data = packet.GetData(); try { sock.SendTo(data, remoteEndPoint); } catch ( SocketException e ) { DebugLog.WriteLine("UdpConnection", "Critical socket failure: " + e.ErrorCode); state = (int)State.Disconnected; return; } // If we've been idle but completely acked for more than two seconds, the next sent // packet will trip the resend detection. This fixes that. if ( outSeqSent == outSeqAcked ) nextResend = DateTime.Now.AddSeconds(RESEND_DELAY); // Sending should generally carry on from the packet most recently sent, even if it was a // resend (who knows what else was lost). if ( packet.Header.SeqThis > 0 ) outSeqSent = packet.Header.SeqThis; }
/// <summary> /// Sends the packet as a sequenced, reliable packet. /// </summary> /// <param name="packet">The packet.</param> private void SendSequenced(UdpPacket packet) { packet.Header.SeqThis = outSeq; packet.Header.MsgStartSeq = outSeq; packet.Header.PacketsInMsg = 1; outPackets.Add(packet); outSeq++; }
/// <summary> /// Sends the data sequenced as a single message, splitting it into multiple parts if necessary. /// </summary> /// <param name="ms">The data to send.</param> private void SendData( MemoryStream ms ) { UdpPacket[] packets = new UdpPacket[ ( ms.Length / UdpPacket.MAX_PAYLOAD ) + 1 ]; for ( int i = 0 ; i < packets.Length ; i++ ) { long index = i * UdpPacket.MAX_PAYLOAD; long length = Math.Min( UdpPacket.MAX_PAYLOAD, ms.Length - index ); packets[ i ] = new UdpPacket( EUdpPacketType.Data, ms, length ); packets[ i ].Header.MsgSize = ( uint )ms.Length; } SendSequenced( packets ); }
/// <summary> /// Receives the notification of an accepted connection and sets the connection id that will be used for the /// connection's duration. /// </summary> /// <param name="packet">The packet.</param> private void ReceiveAccept(UdpPacket packet) { if ( state != State.ConnectSent ) return; DebugLog.WriteLine("UdpConnection", "Connection established"); state = State.Connected; remoteConnId = packet.Header.SourceConnID; inSeqHandled = packet.Header.SeqThis; OnConnected( EventArgs.Empty ); }
/// <summary> /// Receives the packet, performs all sanity checks and then passes it along as necessary. /// </summary> /// <param name="packet">The packet.</param> private void ReceivePacket(UdpPacket packet) { // Check for a malformed packet if (!packet.IsValid) return; else if (remoteConnId > 0 && packet.Header.SourceConnID != remoteConnId) return; DebugLog.WriteLine("UdpConnection", "<- Recv'd {0} Seq {1} Ack {2}; {3} bytes; Message: {4} bytes {5} packets", packet.Header.PacketType, packet.Header.SeqThis, packet.Header.SeqAck, packet.Header.PayloadSize, packet.Header.MsgSize, packet.Header.PacketsInMsg); // Throw away any duplicate messages we've already received, making sure to // re-ack it in case it got lost. if (packet.Header.PacketType == EUdpPacketType.Data && packet.Header.SeqThis < inSeq) { SendAck(); return; } // When we get a SeqAck, all packets with sequence numbers below that have been safely received by // the server; we are now free to remove our copies if (outSeqAcked < packet.Header.SeqAck) { outSeqAcked = packet.Header.SeqAck; // outSeqSent can be less than this in a very rare case involving resent packets. if (outSeqSent < outSeqAcked) outSeqSent = outSeqAcked; outPackets.RemoveAll(x => x.Header.SeqThis <= outSeqAcked); nextResend = DateTime.Now.AddSeconds(RESEND_DELAY); } // inSeq should always be the latest value that we can ack, so advance it as far as is possible. if (packet.Header.SeqThis == inSeq + 1) do inSeq++; while (inPackets.ContainsKey(inSeq + 1)); switch (packet.Header.PacketType) { case EUdpPacketType.Challenge: ReceiveChallenge(packet); break; case EUdpPacketType.Accept: ReceiveAccept(packet); break; case EUdpPacketType.Data: ReceiveData(packet); break; case EUdpPacketType.Disconnect: DebugLog.WriteLine("UdpConnection", "Disconnected by server"); state = State.Disconnected; return; case EUdpPacketType.Datagram: break; default: DebugLog.WriteLine("UdpConnection", "Received unexpected packet type " + packet.Header.PacketType); break; } }
/// <summary> /// Processes incoming packets, maintains connection consistency, and oversees outgoing packets. /// </summary> private void NetLoop() { // Variables that will be used deeper in the function; locating them here avoids recreating // them since they don't need to be. EndPoint packetSender = (EndPoint)new IPEndPoint(IPAddress.Any, 0); byte[] buf = new byte[2048]; timeOut = DateTime.Now.AddSeconds(TIMEOUT_DELAY); nextResend = DateTime.Now.AddSeconds(RESEND_DELAY); // Begin by sending off the challenge request SendPacket(new UdpPacket(EUdpPacketType.ChallengeReq)); state = State.ChallengeReqSent; while (state != State.Disconnected) { try { // Wait up to 150ms for data, if none is found and the timeout is exceeded, we're done here. if (!sock.Poll(150000, SelectMode.SelectRead) && DateTime.Now > timeOut) { DebugLog.WriteLine("UdpConnection", "Connection timed out"); state = State.Disconnected; break; } // By using a 10ms wait, we allow for multiple packets sent at the time to all be processed before moving on // to processing output and therefore Acks (the more we process at the same time, the fewer acks we have to send) while (sock.Poll(10000, SelectMode.SelectRead)) { int length = sock.ReceiveFrom(buf, ref packetSender); // Ignore packets that aren't sent by the server we're connected to. if (!packetSender.Equals(remoteEndPoint)) continue; // Data from the desired server was received; delay timeout timeOut = DateTime.Now.AddSeconds(TIMEOUT_DELAY); MemoryStream ms = new MemoryStream(buf, 0, length); UdpPacket packet = new UdpPacket(ms); ReceivePacket(packet); } } catch (SocketException e) { DebugLog.WriteLine("UdpConnection", "Critical socket failure: " + e.ErrorCode); state = State.Disconnected; break; } // Send or resend any sequenced packets; a call to ReceivePacket can set our state to disconnected // so don't send anything we have queued in that case if (state != State.Disconnected) SendPendingMessages(); // If we received data but had no data to send back, we need to manually Ack (usually tags along with // outgoing data); also acks disconnections if (inSeq != inSeqAcked) SendAck(); // If a graceful shutdown has been requested, nothing in the outgoing queue is discarded. // Once it's empty, we exit, since the last packet was our disconnect notification. if (state == State.Disconnecting && outPackets.Count == 0) { DebugLog.WriteLine("UdpConnection", "Graceful disconnect completed"); state = State.Disconnected; } } DebugLog.WriteLine("UdpConnection", "Calling OnDisconnected"); OnDisconnected(EventArgs.Empty); }