Example #1
0
        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.");
            }
        }
Example #2
0
        /// <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);
        }