using RioJoy.Core.Protocol; using Xunit; namespace RioJoy.Core.Tests.Protocol; public class PacketParserTests { private static List FeedAll(PacketParser parser, byte[] data) { var events = new List(); foreach (byte b in data) { if (parser.Feed(b, out RioRxEvent ev)) events.Add(ev); } return events; } [Fact] public void FramesValidPacket_WithChecksumValid() { var parser = new PacketParser(); byte[] frame = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x05 }); List events = FeedAll(parser, frame); RioRxEvent ev = Assert.Single(events); Assert.Equal(RioRxEventKind.Packet, ev.Kind); Assert.True(ev.ChecksumValid); Assert.Equal(RioCommand.ButtonPressed, ev.Packet.Command); Assert.Equal(new byte[] { 0x05 }, ev.Packet.Payload.ToArray()); Assert.False(parser.InPacket); } [Fact] public void ReportsChecksumInvalid_ButStillFramesPacket() { var parser = new PacketParser(); byte[] frame = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x05 }); frame[^1] ^= 0x01; // corrupt checksum (stays high-bit clear) RioRxEvent ev = Assert.Single(FeedAll(parser, frame)); Assert.Equal(RioRxEventKind.Packet, ev.Kind); Assert.False(ev.ChecksumValid); } [Fact] public void ControlByte_OutsideFraming_IsReported() { var parser = new PacketParser(); RioRxEvent ev = Assert.Single(FeedAll(parser, new[] { (byte)RioControl.Ack })); Assert.Equal(RioRxEventKind.ControlByte, ev.Kind); Assert.True(ev.IsControl(RioControl.Ack)); } [Fact] public void HighBitMidPacket_AbortsAndResyncs() { var parser = new PacketParser(); // Start a ButtonPressed (expects 1 payload + 1 checksum), feed one payload // byte, then a high-bit byte mid-packet → framing error + resync. var events = new List(); foreach (byte b in new byte[] { 0x88, 0x05, 0x82 }) { if (parser.Feed(b, out RioRxEvent ev)) events.Add(ev); } RioRxEvent framing = Assert.Single(events); Assert.Equal(RioRxEventKind.FramingError, framing.Kind); Assert.False(parser.InPacket); // A subsequent clean frame parses normally (the parser resynced). byte[] next = PacketBuilder.Build(RioCommand.ButtonReleased, new byte[] { 0x07 }); RioRxEvent ev2 = Assert.Single(FeedAll(parser, next)); Assert.Equal(RioCommand.ButtonReleased, ev2.Packet.Command); } [Fact] public void ParsesMultiplePackets_InOneStream() { var parser = new PacketParser(); byte[] a = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x01 }); byte[] b = PacketBuilder.Build(RioCommand.ButtonReleased, new byte[] { 0x02 }); byte[] stream = new byte[a.Length + b.Length]; a.CopyTo(stream, 0); b.CopyTo(stream, a.Length); List events = FeedAll(parser, stream); Assert.Equal(2, events.Count); Assert.Equal(RioCommand.ButtonPressed, events[0].Packet.Command); Assert.Equal(RioCommand.ButtonReleased, events[1].Packet.Command); } [Fact] public void HandlesPacket_SplitAcrossFeeds() { var parser = new PacketParser(); byte[] frame = PacketBuilder.Build(RioCommand.AnalogReply, new byte[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0 }); // Feed byte-by-byte: only the final byte should yield the packet event. var events = new List(); for (int i = 0; i < frame.Length; i++) { bool produced = parser.Feed(frame[i], out RioRxEvent ev); if (produced) events.Add(ev); Assert.Equal(i == frame.Length - 1, produced); } RioRxEvent done = Assert.Single(events); Assert.Equal(RioCommand.AnalogReply, done.Packet.Command); Assert.True(done.ChecksumValid); } }