protected async Task ProcessReceivedData(ulong packetId, byte[] data, ISerializer serializer) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (serializer == null) { throw new ArgumentNullException(nameof(serializer)); } if (State == ConnectionState.Closed) { throw new DisconnectedException(); } var serializedPacket = SerializedPacket.Read(data); Log.TracePacketReceived(this, serializedPacket); { var args = new PacketReceivedEventArgs() { PacketId = packetId, Packet = serializedPacket, Connection = this }; var packetEventHandler = PacketReceived; if (packetEventHandler != null) { foreach (var handler in packetEventHandler.GetInvocationList().Cast <PacketReceived>()) { if (State == ConnectionState.Closed) { throw new DisconnectedException(); } try { await handler(this, args); } catch (Exception ex) { Log.Error($"Error in {nameof(PacketReceived)} event handler", ex); } if (args.IsHandled) { return; } } } } if (State == ConnectionState.Closed) { throw new DisconnectedException(); } Packet packet; try { packet = serializedPacket.DeserializePacket(serializer); } catch (Exception ex) { throw new InvalidDataException($"Error deserializing {serializedPacket}: {ex.Message}", data, ex); } if (State == ConnectionState.Closed) { throw new DisconnectedException(); } if (packet is HandshakeRequestPacket helloPacket) { //TODO: Move this into the Server class if (Handshaker == null) { throw new InvalidOperationException($"{this}: Can't handle {helloPacket} because there is no {nameof(Handshaker)}"); } HandshakeResult result; try { result = await Handshaker.OnHandshakeRequest(helloPacket, this, ClosedCancellationToken); } catch (Exception ex) { result = HandshakeResult.Failure("Handshake Failed"); Log.Error($"{this}: Handshake Failure: {ex}"); } if (result.Successful) { User = result.User; } var response = new ResponsePacket(result); await Respond(packetId, response, ClosedCancellationToken); TransitionState(result.Successful ? ConnectionState.Connected : ConnectionState.Closed); } else if (packet is RequestPacket requestPacket) { var requestContext = new RequestContext() { Client = Client, Server = Server, Connection = this }; var requestEventHandler = RequestReceived; if (requestEventHandler == null) { throw new InvalidOperationException($"{this}: Receiving Requests, but nothing is reading them."); } requestPacket.Context = requestContext; var args = new RequestReceivedEventArgs() { Request = requestPacket, Context = requestContext }; foreach (var handler in requestEventHandler.GetInvocationList().Cast <RequestReceived>()) { object result = null; try { result = await handler(this, args); } catch (Exception ex) { Log.Error($"{this}: Error invoking event {nameof(RequestReceived)}: {ex}"); } if (result != null) { args.Response = result; args.IsHandled = true; } if (args.IsHandled) { break; } } if (requestPacket.Flags.HasFlag(PacketFlag.AckRequired)) { var unhandledRequestResponse = new ResponsePacket(new ErrorResponseData(ErrorResponseCodes.UnhandledRequest, $"Packet {requestPacket} not expected.", false)); var response = (!args.IsHandled ? unhandledRequestResponse : new ResponsePacket(args.Response)); try { await Respond(packetId, response, ClosedCancellationToken); } catch (Exception ex) { Log.Error($"{this}: Error sending response to Request {requestPacket}: Response={response}: {ex}"); } } } else if (packet is IAck ack) { if (_awaitingAck.TryGetValue(ack.PacketId, out var tcs)) { tcs.SetResult(packet); } else { Log.Warn($"{this}: Ack: Could not find packet #{ack.PacketId}"); } } else { throw new InvalidDataException($"{this}: {packet.GetType().Name} packet not supported."); } }
/// <summary> /// Sends and configures a <see cref="IPacket"/>. /// </summary> /// <param name="packet">packet</param> /// <param name="cancellationToken">cancellation</param> /// <returns><see cref="Task"/> containing an Ack, if one was returned and required.</returns> public async Task <IAck> Send(IPacket packet, CancellationToken cancellationToken) { //TODO: This should return an IPacket, not an IAck, that way we don't have to deserialize it fully // Maybe, and IAck could share whatever required property we need so we can deserialize it // like a SerailizedPacket. So possibly merge an IAck with an IPacket if (packet == null) { throw new ArgumentNullException(nameof(packet)); } if (packet is Packet fullPacket) { fullPacket.Sent = DateTimeOffset.Now; } if (packet is IAck packetAsAck && packetAsAck.PacketId <= 0) { throw new InvalidOperationException($"Ack doesn't have response ID."); } ulong packetId; lock (_idLocker) packetId = _nextPacketId++; TaskCompletionSource <Packet> ackCompletion = null; try { if (packet.Flags.HasFlag(PacketFlag.AckRequired)) { ackCompletion = new TaskCompletionSource <Packet>(TaskCreationOptions.RunContinuationsAsynchronously); _awaitingAck.AddOrUpdate(packetId, ackCompletion, (_, __) => ackCompletion); } Log.TracePacketSending(this, packet); if (packet.Sent == null) { throw new InvalidOperationException($"{this}: Sent is null"); } byte[] packetData = null; if (packet is Packet regularPacket) { packetData = SerializedPacket.Create(regularPacket, Serializer) ?? throw new InvalidOperationException($"{this}: Packet serialization returned null"); } else if (packet is SerializedPacket serializedPacket) { packetData = serializedPacket.Data; } else { throw new InvalidOperationException($"{this}: Packet of type {packet.GetType().Name} is not supported."); } try { await SendImpl(packetId, packetData, cancellationToken); } catch (DisconnectedException ex) { if (ex.InnerException != null) { Log.Warn($"Disconnected sending packet {packet}: {ex.InnerException}"); } TransitionState(ConnectionState.Closed); throw; } // Don't log this unless the send actually happens Log.TracePacketSent(this, packet); if (ackCompletion == null) { // No ack required return(null); } else { // Wait for the ack using (var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ClosedCancellationToken)) { var cancellationTask = Task.Delay(-1, combinedCancellationTokenSource.Token); Log.TraceWaitingForAck(this, packetId); var result = await Task.WhenAny(ackCompletion.Task, Task.Delay(WaitForResponseTimeout), cancellationTask); if (result == ackCompletion.Task) { var ack = (IAck)ackCompletion.Task.Result; Log.TraceAckReceived(this, ack); return(ack); } if (result == cancellationTask) { if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); } if (ClosedCancellationToken.IsCancellationRequested) { throw new NotConnectedException($"{this}: Could not send {packet}, the connection is closed."); } } throw new TimeoutException($"{this}: Timed out waiting for Ack from {packet}"); } } } finally { if (ackCompletion != null) { ackCompletion.TrySetCanceled(); _awaitingAck.TryRemove(packetId, out _); } } }
public static SerializedPacket Read(byte[] data) { if (data.Length < HEADER_SIZE) { throw new InvalidDataLengthException($"Data size of {data.Length} is not long enough to read the headers.", data); } var header = new SerializedPacket(); var currentIndex = 0; // Packet Type header.PacketType = data[currentIndex]; currentIndex++; // Packet Flags var packetFlagsByte = data[currentIndex++]; if (!Enum.IsDefined(typeof(PacketFlag), packetFlagsByte)) { throw new InvalidDataException($"Packet flag byte {packetFlagsByte:X2} is invalid"); } header.Flags = (PacketFlag)packetFlagsByte; // Destination try { header.Destination = Encoding.UTF8.GetString(data, currentIndex, DESTINATION_SIZE); } catch (Exception ex) { throw new InvalidDataException($"Packet Destination is invalid", data, ex); } { // trim ending 0's var zeroIndex = header.Destination.IndexOf('\0'); if (zeroIndex >= 0) { header.Destination = header.Destination.Substring(0, zeroIndex); } } currentIndex += DESTINATION_SIZE; // Origin string originString; try { originString = Encoding.UTF8.GetString(data, currentIndex, ORIGIN_SIZE); } catch (Exception ex) { throw new InvalidDataException($"Packet Origin is invalid", data, ex); } { // trim ending 0's var zeroIndex = originString.IndexOf('\0'); if (zeroIndex >= 0) { originString = originString.Substring(0, zeroIndex); } } User.TryParse(originString, out var origin); header.Origin = origin; currentIndex += ORIGIN_SIZE; // Sent Date string sentDateString; try { sentDateString = Encoding.UTF8.GetString(data, currentIndex, SENT_SIZE); } catch (Exception ex) { throw new InvalidDataException($"Packet Sent Date is invalid", data, ex); } { // trim ending 0's var zeroIndex = sentDateString.IndexOf('\0'); if (zeroIndex >= 0) { sentDateString = sentDateString.Substring(0, zeroIndex); } } if (!DateTimeOffset.TryParse(sentDateString, out var packetSentDate)) { throw new InvalidDataException($"Packet Sent Date can't be parsed: {sentDateString}"); } header.Sent = packetSentDate; currentIndex += SENT_SIZE; if (currentIndex != HEADER_SIZE) { throw new InvalidOperationException($"Read different amount of data than header size."); } header.Data = data; return(header); }