Represents a reader that provides access to S101-encoded messages and their payload.
See the "Ember+ Specification"Ember+ Specification, chapter "Message Framing".
        public void MainTest()
        {
            AsyncPump.Run(
                async () =>
                {
                    var writtenBytes = new byte[this.Random.Next(3072, 10241)];
                    this.Random.NextBytes(writtenBytes);

                    using (var output = new MemoryStream())
                    {
                        using (var framer = new GlowOutput(1024, 0, (s, e) => output.Write(e.FramedPackage, 0, e.FramedPackageLength)))
                        {
                            framer.WriteBytes(writtenBytes);
                            framer.Finish();
                        }

                        output.Position = 0;
                        var reader = new S101Reader(output.ReadAsync, 1024);
                        Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                        Assert.IsInstanceOfType(reader.Message.Command, typeof(EmberData));

                        using (var input = new MemoryStream())
                        {
                            await reader.Payload.CopyToAsync(input);
                            CollectionAssert.AreEqual(writtenBytes, input.ToArray());
                        }

                        await reader.DisposeAsync(CancellationToken.None);
                    }
                });
        }
Example #2
0
        public void PayloadTest()
        {
            AsyncPump.Run(
                async() =>
            {
#pragma warning disable SA1123 // Do not place regions within elements. Necessary so that tested code snippets can be included in the documentation.
                #region Payload Test
                var writtenMessage = new S101Message(0x00, new EmberData(0x01, 0x0A, 0x02));
                var writtenPayload = new byte[8192];
                this.Random.NextBytes(writtenPayload);

                using (var encodedStream = new MemoryStream())
                {
                    // First we create a writer, which can be used to write multiple messages.
                    // We specify which methods are used to write encoded output and flush it plus the size the internal
                    // buffer should have.
                    var writer = new S101Writer(encodedStream.WriteAsync);

                    // Next we write the message. In return we get a Stream object for the payload.
                    using (var payloadStream =
                               await writer.WriteMessageAsync(writtenMessage, CancellationToken.None))
                    {
                        // Now we write the payload.
                        await payloadStream.WriteAsync(writtenPayload, 0, writtenPayload.Length);
                        await payloadStream.DisposeAsync(CancellationToken.None);
                    }

                    await writer.DisposeAsync(CancellationToken.None);

                    // Reset the encoded stream to the beginning, so that we can read from it.
                    encodedStream.Position = 0;

                    // First we create a reader, which can be used to read multiple messages.
                    // We specify which methods are used to read encoded input.
                    var reader = new S101Reader(encodedStream.ReadAsync);
                    Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));     // Read the first message
                    var readMessage = reader.Message;

                    // Assert the written and read messages are equal
                    Assert.AreEqual(writtenMessage.Slot, readMessage.Slot);
                    Assert.AreEqual(writtenMessage.Command, readMessage.Command);

                    using (var readPayload = new MemoryStream())
                    {
                        await reader.Payload.CopyToAsync(readPayload);     // Copy the payload.
                        // Assert that there is only one message
                        Assert.IsFalse(await reader.ReadAsync(CancellationToken.None));
                        CollectionAssert.AreEqual(writtenPayload, readPayload.ToArray());
                    }

                    await reader.DisposeAsync(CancellationToken.None);
                }
                #endregion
#pragma warning restore SA1123 // Do not place regions within elements
            });
        }
        private async Task <bool> ReadWithTimeoutAsync(
            IDisposable connection, S101Reader reader, Task cancellationFailed)
        {
            int timeoutCount = 0;
            var timeoutHalf  = this.timeout >= 0 ? this.timeout / 2 : this.timeout;
            var readTask     = reader.ReadAsync(this.source.Token);

            // In a perfect world, the cancellationFailed business would not be necessary, because readTask should
            // complete immediately when a cancellation is requested. In the real world however, such "rarely" used
            // classes as NetworkStream do not support cancellation, see
            // http://stackoverflow.com/questions/12421989/networkstream-readasync-with-a-cancellation-token-never-cancels
            while (await Task.WhenAny(readTask, cancellationFailed).TimeoutAsync(timeoutHalf))
            {
                switch (++timeoutCount)
                {
                case 1:
                    await this.SendMessageCoreAsync(
                        new S101Message(this.keepAliveRequestSlot, new KeepAliveRequest()), null);

                    break;

                case 2:
                    await this.EnqueueLogOperation(() => this.logger.LogEvent("TimeoutExpired"));

                    this.Dispose();
                    break;
                }
            }

            if (cancellationFailed.IsCompleted)
            {
                await this.EnqueueLogOperation(() => this.logger.LogEvent("CancellationFailed"));

                // For IO objects that do not support cancellation with CancellationToken, the recommended practice is
                // to simply dispose and then wait for the async operations to complete.
                connection?.Dispose();
            }

            try
            {
                return(await readTask);
            }
            catch (Exception ex)
            {
                if (((ex is OperationCanceledException) || (ex is ObjectDisposedException)) && (timeoutCount > 1))
                {
                    throw new S101Exception(
                              "The remote host has failed to answer a KeepAliveRequest within half the timeout period.", ex);
                }
                else
                {
                    throw;
                }
            }
        }
Example #4
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static Task <byte> WaitForOutOfFrameByte(S101Reader reader)
        {
            var resultSource = new TaskCompletionSource <byte>();
            EventHandler <OutOfFrameByteReceivedEventArgs> handler = null;

            handler = (s, e) =>
            {
                reader.OutOfFrameByteReceived -= handler;
                resultSource.SetResult(e.Value);
            };

            reader.OutOfFrameByteReceived += handler;
            return(resultSource.Task);
        }
        private async Task ProcessMessage(S101Reader reader, byte[] buffer)
        {
            var message = reader.Message;
            var payload = await GetPayload(reader, buffer, this.source.Token);

            await this.EnqueueLogOperation(() => this.logger.LogMessage(LogNames.Receive, message, payload));

            if (message.Command is EmberData)
            {
                this.OnEvent(
                    this.EmberDataReceived,
                    new MessageReceivedEventArgs(message, payload, reader.IsAnotherMessageAvailable));
            }
            else if (message.Command is KeepAliveRequest)
            {
                await this.SendMessageAsync(new S101Message(message.Slot, new KeepAliveResponse()));
            }

            // We don't need to do anything with a KeepAliveResponse, because it has already made sure that no timeout
            // exception has been thrown.
        }
Example #6
0
        public void OutOfFrameByteTest()
        {
            AsyncPump.Run(
                async() =>
            {
                var first  = this.GetRandomByteExcept(0xFE);
                var second = this.GetRandomByteExcept(0xFE);

                var prefix  = new[] { this.GetRandomByteExcept() };
                var third   = this.GetRandomByteExcept(0xFE);
                var postfix = new[] { this.GetRandomByteExcept() };

                using (var asyncStream = new MemoryStream())
                {
                    var writer = new S101Writer(asyncStream.WriteAsync);

                    try
                    {
                        await writer.WriteOutOfFrameByteAsync(first, CancellationToken.None);
                        await writer.WriteMessageAsync(KeepAliveRequestMessage, CancellationToken.None);
                        await writer.WriteOutOfFrameByteAsync(second, CancellationToken.None);

                        using (var encodingStream = await writer.WriteMessageAsync(EmberDataMessage, CancellationToken.None))
                        {
                            await encodingStream.WriteAsync(prefix, 0, prefix.Length, CancellationToken.None);
                            await writer.WriteOutOfFrameByteAsync(third, CancellationToken.None);
                            await encodingStream.WriteAsync(postfix, 0, postfix.Length, CancellationToken.None);
                            await encodingStream.DisposeAsync(CancellationToken.None);
                        }
                    }
                    finally
                    {
                        await writer.DisposeAsync(CancellationToken.None);
                    }

                    asyncStream.Position = 0;
                    var reader           = new S101Reader(asyncStream.ReadAsync);
                    var firstTask        = WaitForOutOfFrameByte(reader);
                    Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                    Assert.AreEqual(0x00, reader.Message.Slot);
                    Assert.IsInstanceOfType(reader.Message.Command, typeof(KeepAliveRequest));
                    Assert.AreEqual(first, await firstTask);
                    var secondTask = WaitForOutOfFrameByte(reader);
                    Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                    Assert.AreEqual(0x00, reader.Message.Slot);
                    Assert.IsInstanceOfType(reader.Message.Command, typeof(EmberData));
                    Assert.AreEqual(second, await secondTask);
                    var thirdTask = WaitForOutOfFrameByte(reader);

                    using (var payloadStream = new MemoryStream())
                    {
                        await reader.Payload.CopyToAsync(payloadStream);
                        var payload = payloadStream.ToArray();
                        Assert.AreEqual(2, payload.Length);
                        Assert.AreEqual(prefix.Single(), payload[0]);
                        Assert.AreEqual(postfix.Single(), payload[1]);
                    }

                    Assert.AreEqual(third, await thirdTask);
                }
            });
        }
        public void ExceptionTest()
        {
            TestStandardExceptionConstructors <S101Exception>();

            AsyncPump.Run(
                async() =>
            {
                new S101Reader((b, o, c, t) => Task.FromResult(0)).Ignore();

                AssertThrow <ArgumentNullException>(() => new S101Reader(null, 1).Ignore());
                AssertThrow <ArgumentOutOfRangeException>(() => new S101Reader((b, o, c, t) => Task.FromResult(0), 0).Ignore());

                using (var input = new MemoryStream(
                           new byte[] { 0xFE, 0x00, 0x0E, 0x01, 0x01, 0x94, 0xE4, 0xFF, 0xFE, 0x00, 0x0E, 0x02, 0x01, 0xFD, 0xDC, 0xCE, 0xFF }))
                {
                    var reader = new S101Reader(input.ReadAsync, 1);
                    AssertThrow <InvalidOperationException>(() => reader.Message.Ignore());
                    AssertThrow <InvalidOperationException>(() => reader.Payload.Ignore());
                    Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                    Assert.IsInstanceOfType(reader.Message.Command, typeof(KeepAliveRequest));
                    Assert.AreEqual(0, await reader.Payload.ReadAsync(new byte[1], 0, 1, CancellationToken.None));
                    AssertThrow <NotSupportedException>(
                        () => reader.Payload.Read(new byte[1], 0, 1),
                        () => reader.Payload.Write(new byte[1], 0, 1));
                    Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                    Assert.IsInstanceOfType(reader.Message.Command, typeof(KeepAliveResponse));
                    Assert.AreEqual(0, await reader.Payload.ReadAsync(new byte[1], 0, 1, CancellationToken.None));
                    Assert.IsFalse(await reader.ReadAsync(CancellationToken.None));
                    AssertThrow <InvalidOperationException>(() => reader.Message.Ignore());
                    AssertThrow <InvalidOperationException>(() => reader.Payload.Ignore());
                    await reader.DisposeAsync(CancellationToken.None);
                    await AssertThrowAsync <ObjectDisposedException>(() => reader.ReadAsync(CancellationToken.None));
                    AssertThrow <ObjectDisposedException>(
                        () => reader.Message.Ignore(), () => reader.Payload.Ignore());
                }

                await AssertEmpty(0xFE, 0xFF);
                await AssertEmpty(0xFE, 0xFE);

                for (byte invalid = 0xF8; invalid < 0xFD; ++invalid)
                {
                    await AssertEmpty(0xFE, invalid);
                }

                for (ushort invalid = 0xF8; invalid < 0x100; ++invalid)
                {
                    await AssertEmpty(0xFE, 0xFD, (byte)invalid);
                }

                await AssertS101Exception("Unexpected end of stream.", 0xFE, 0x00, 0x00, 0x00);

                await AssertS101Exception("Unexpected end of stream.", 0xFE, 0x00, 0x0E, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "Unexpected end of stream.",
                    0xFE, 0x00, 0x0E, 0x00, 0x01, 0x80, 0x01, 0x02, 0x0A, 0x02, 0xF5, 0x78, 0xFF);

                await AssertS101Exception(
                    "Inconsistent Slot in multi-packet message.",
                    0xFE, 0x00, 0x0E, 0x00, 0x01, 0x80, 0x01, 0x02, 0x0A, 0x02, 0xF5, 0x78, 0xFF,
                    0xFE, 0x01, 0x0E, 0x00, 0x01, 0x60, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "Unexpected Message Type.",
                    0xFE, 0x00, 0x0F, 0x00, 0x01, 0x80, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "Unexpected Command.", 0xFE, 0x00, 0x0E, 0x04, 0x01, 0x80, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "Inconsistent Command in multi-packet message.",
                    0xFE, 0x00, 0x0E, 0x00, 0x01, 0x80, 0x01, 0x02, 0x0A, 0x02, 0xF5, 0x78, 0xFF,
                    0xFE, 0x00, 0x0E, 0x01, 0x01, 0x60, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "Unexpected Version.", 0xFE, 0x00, 0x0E, 0x00, 0x00, 0x80, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "Missing FirstPacket flag in first packet.",
                    0xFE, 0x00, 0x0E, 0x00, 0x01, 0x20, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);

                await AssertS101Exception(
                    "FirstPacket flag in subsequent packet.",
                    0xFE, 0x00, 0x0E, 0x00, 0x01, 0x80, 0x01, 0x02, 0x0A, 0x02, 0xF5, 0x78, 0xFF,
                    0xFE, 0x00, 0x0E, 0x00, 0x01, 0xE0, 0x01, 0x02, 0x0A, 0x02, 0x00, 0x00, 0x00);
            });
        }
        private async void ReadLoop(IDisposable connection, S101Reader reader)
        {
            var disposed           = new TaskCompletionSource <bool>();
            var cancellationFailed = Delay(disposed.Task, 250);

            this.source.Token.Register(() => disposed.SetResult(true));

            await this.EnqueueLogOperation(() => this.logger.LogEvent("StartingReadLoop"));

            reader.OutOfFrameByteReceived += this.OnOutOfFrameByteReceived;
            Exception exception;
            bool      remote = false;

            try
            {
                var buffer = new byte[1024];

                while (await this.ReadWithTimeoutAsync(connection, reader, cancellationFailed))
                {
                    await this.ProcessMessage(reader, buffer);
                }

                remote    = true;
                exception = null;
            }
            catch (OperationCanceledException)
            {
                exception = null;
            }
            catch (ObjectDisposedException)
            {
                exception = null;
            }
            catch (Exception ex)
            {
                exception = ex;
            }
            finally
            {
                reader.OutOfFrameByteReceived -= this.OnOutOfFrameByteReceived;
                this.DisposeCore(false);
                connection?.Dispose();
                this.source.Dispose();
            }

            try
            {
                Action logAction;

                if (exception == null)
                {
                    logAction =
                        () => this.logger.LogEvent(remote ? "RemoteGracefulDisconnect" : "LocalGracefulDisconnect");
                }
                else
                {
                    logAction = () => this.logger.LogException(LogNames.Receive, exception);
                }

                // We're deliberately not awaiting this task, so that the Dispose call will be enqueued even if this
                // one fails with an exception.
                this.EnqueueLogOperation(logAction).Ignore();
                await this.EnqueueLogOperation(() => this.logger.Dispose());

                await cancellationFailed;
            }
            finally
            {
                this.OnEvent(this.ConnectionLost, new ConnectionLostEventArgs(exception));
            }
        }
Example #9
0
        public void OutOfFrameByteTest()
        {
            AsyncPump.Run(
                async () =>
                {
                    var first = this.GetRandomByteExcept(0xFE);
                    var second = this.GetRandomByteExcept(0xFE);

                    var prefix = new[] { this.GetRandomByteExcept() };
                    var third = this.GetRandomByteExcept(0xFE);
                    var postfix = new[] { this.GetRandomByteExcept() };

                    using (var asyncStream = new MemoryStream())
                    {
                        var writer = new S101Writer(asyncStream.WriteAsync);

                        try
                        {
                            await writer.WriteOutOfFrameByteAsync(first, CancellationToken.None);
                            await writer.WriteMessageAsync(KeepAliveRequestMessage, CancellationToken.None);
                            await writer.WriteOutOfFrameByteAsync(second, CancellationToken.None);

                            using (var encodingStream = await writer.WriteMessageAsync(EmberDataMessage, CancellationToken.None))
                            {
                                await encodingStream.WriteAsync(prefix, 0, prefix.Length, CancellationToken.None);
                                await writer.WriteOutOfFrameByteAsync(third, CancellationToken.None);
                                await encodingStream.WriteAsync(postfix, 0, postfix.Length, CancellationToken.None);
                                await encodingStream.DisposeAsync(CancellationToken.None);
                            }
                        }
                        finally
                        {
                            await writer.DisposeAsync(CancellationToken.None);
                        }

                        asyncStream.Position = 0;
                        var reader = new S101Reader(asyncStream.ReadAsync);
                        var firstTask = WaitForOutOfFrameByte(reader);
                        Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                        Assert.AreEqual(0x00, reader.Message.Slot);
                        Assert.IsInstanceOfType(reader.Message.Command, typeof(KeepAliveRequest));
                        Assert.AreEqual(first, await firstTask);
                        var secondTask = WaitForOutOfFrameByte(reader);
                        Assert.IsTrue(await reader.ReadAsync(CancellationToken.None));
                        Assert.AreEqual(0x00, reader.Message.Slot);
                        Assert.IsInstanceOfType(reader.Message.Command, typeof(EmberData));
                        Assert.AreEqual(second, await secondTask);
                        var thirdTask = WaitForOutOfFrameByte(reader);

                        using (var payloadStream = new MemoryStream())
                        {
                            await reader.Payload.CopyToAsync(payloadStream);
                            var payload = payloadStream.ToArray();
                            Assert.AreEqual(2, payload.Length);
                            Assert.AreEqual(prefix.Single(), payload[0]);
                            Assert.AreEqual(postfix.Single(), payload[1]);
                        }

                        Assert.AreEqual(third, await thirdTask);
                    }
                });
        }
Example #10
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static Task<byte> WaitForOutOfFrameByte(S101Reader reader)
        {
            var resultSource = new TaskCompletionSource<byte>();
            EventHandler<OutOfFrameByteReceivedEventArgs> handler = null;

            handler = (s, e) =>
            {
                reader.OutOfFrameByteReceived -= handler;
                resultSource.SetResult(e.Value);
            };

            reader.OutOfFrameByteReceived += handler;
            return resultSource.Task;
        }
Example #11
0
        public void PayloadTest()
        {
            AsyncPump.Run(
                async () =>
                {
#pragma warning disable SA1123 // Do not place regions within elements. Necessary so that tested code snippets can be included in the documentation.
                    #region Payload Test
                    var writtenMessage = new S101Message(0x00, new EmberData(0x01, 0x0A, 0x02));
                    var writtenPayload = new byte[8192];
                    this.Random.NextBytes(writtenPayload);

                    using (var encodedStream = new MemoryStream())
                    {
                        // First we create a writer, which can be used to write multiple messages.
                        // We specify which methods are used to write encoded output and flush it plus the size the internal
                        // buffer should have.
                        var writer = new S101Writer(encodedStream.WriteAsync);

                        // Next we write the message. In return we get a Stream object for the payload.
                        using (var payloadStream =
                            await writer.WriteMessageAsync(writtenMessage, CancellationToken.None))
                        {
                            // Now we write the payload.
                            await payloadStream.WriteAsync(writtenPayload, 0, writtenPayload.Length);
                            await payloadStream.DisposeAsync(CancellationToken.None);
                        }

                        await writer.DisposeAsync(CancellationToken.None);

                        // Reset the encoded stream to the beginning, so that we can read from it.
                        encodedStream.Position = 0;

                        // First we create a reader, which can be used to read multiple messages.
                        // We specify which methods are used to read encoded input.
                        var reader = new S101Reader(encodedStream.ReadAsync);
                        Assert.IsTrue(await reader.ReadAsync(CancellationToken.None)); // Read the first message
                        var readMessage = reader.Message;

                        // Assert the written and read messages are equal
                        Assert.AreEqual(writtenMessage.Slot, readMessage.Slot);
                        Assert.AreEqual(writtenMessage.Command, readMessage.Command);

                        using (var readPayload = new MemoryStream())
                        {
                            await reader.Payload.CopyToAsync(readPayload); // Copy the payload.
                            // Assert that there is only one message
                            Assert.IsFalse(await reader.ReadAsync(CancellationToken.None));
                            CollectionAssert.AreEqual(writtenPayload, readPayload.ToArray());
                        }

                        await reader.DisposeAsync(CancellationToken.None);
                    }
                    #endregion
#pragma warning restore SA1123 // Do not place regions within elements
                });
        }
        private static async Task TestS101ReaderAsync(byte[] message, int messageCount)
        {
            byte[] buffer = new byte[BlockSize];

            using (var stream = new MemoryStream(message))
            {
                var reader = new S101Reader(stream.ReadAsync, 1024);

                for (int index = 0; index < messageCount; ++index)
                {
                    await reader.ReadAsync(CancellationToken.None);

                    using (var payload = reader.Payload)
                    {
                        await payload.ReadAsync(buffer, 0, buffer.Length);
                        await payload.DisposeAsync(CancellationToken.None);
                    }

                    stream.Position = 0;
                }

                await reader.DisposeAsync(CancellationToken.None);
            }
        }