using VRio.Core.Protocol; using Xunit; namespace VRio.Core.Tests; public class ProtocolTests { [Fact] public void Checksum_is_low7_of_sum_of_low7() { // 0x88 & 0x7F = 0x08, + 0x05 = 0x0D. Assert.Equal(0x0D, RioChecksum.Compute(new byte[] { 0x88, 0x05 })); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(-1)] [InlineData(8191)] [InlineData(-8192)] [InlineData(1234)] [InlineData(-4321)] public void AnalogCodec_round_trips_and_stays_7bit(short value) { AnalogCodec.Split(value, out byte low, out byte high); Assert.Equal(0, low & 0x80); // payload bytes must keep the high bit clear Assert.Equal(0, high & 0x80); Assert.Equal(value, AnalogCodec.Combine(low, high)); } [Fact] public void Builder_output_parses_back_with_valid_checksum() { byte[] wire = PacketBuilder.AnalogReply(100, -200, 300, -8192, 8191); var parser = new PacketParser(); RioRxEvent? got = null; foreach (byte b in wire) if (parser.Feed(b, out RioRxEvent ev)) got = ev; Assert.NotNull(got); Assert.Equal(RioRxKind.Packet, got!.Value.Kind); Assert.True(got.Value.ChecksumValid); Assert.Equal(RioCommand.AnalogReply, got.Value.Packet.Command); byte[] p = got.Value.Packet.Payload; Assert.Equal(100, AnalogCodec.Combine(p[0], p[1])); Assert.Equal(-200, AnalogCodec.Combine(p[2], p[3])); Assert.Equal(300, AnalogCodec.Combine(p[4], p[5])); Assert.Equal(-8192, AnalogCodec.Combine(p[6], p[7])); Assert.Equal(8191, AnalogCodec.Combine(p[8], p[9])); } [Fact] public void Parser_reports_control_bytes_outside_framing() { var parser = new PacketParser(); Assert.True(parser.Feed((byte)RioControl.Ack, out RioRxEvent ev)); Assert.Equal(RioRxKind.ControlByte, ev.Kind); Assert.Equal((byte)RioControl.Ack, ev.Byte); } [Fact] public void Parser_resyncs_on_high_bit_byte_mid_packet() { var parser = new PacketParser(); // Start a LampRequest (2-byte payload), then interrupt with a new command byte. Assert.False(parser.Feed(0x84, out _)); Assert.True(parser.Feed(0xFF, out RioRxEvent err)); // IDLE mid-packet = framing error Assert.Equal(RioRxKind.FramingError, err.Kind); Assert.False(parser.InPacket); // A clean packet right after must still parse. byte[] wire = PacketBuilder.VersionReply(1, 2); RioRxEvent? got = null; foreach (byte b in wire) if (parser.Feed(b, out RioRxEvent ev)) got = ev; Assert.Equal(RioRxKind.Packet, got!.Value.Kind); Assert.True(got.Value.ChecksumValid); } [Theory] [InlineData(0x00, LampBrightness.Off, LampFlash.Solid)] [InlineData(0x14, LampBrightness.Dim, LampFlash.Solid)] [InlineData(0x3C, LampBrightness.Bright, LampFlash.Solid)] [InlineData(0x3D, LampBrightness.Bright, LampFlash.FlashSlow)] [InlineData(0x16, LampBrightness.Dim, LampFlash.FlashMed)] [InlineData(0x04, LampBrightness.Dim, LampFlash.Solid)] // field 1 only [InlineData(0x30, LampBrightness.Bright, LampFlash.Solid)] // field 2 only public void LampState_decodes_brightness_and_flash(byte state, LampBrightness brightness, LampFlash flash) { Assert.Equal(brightness, RioLampState.Brightness(state)); Assert.Equal(flash, RioLampState.Flash(state)); } }