Prepares RioJoy.Core for the net40 (Windows XP) target, which has no System.Memory, ValueTask, or System.Text.Json: - IRioTransport and the whole protocol/framing layer now use byte[] + Task (RioPacket.Payload, PacketParser/Builder, RioChecksum, replies, AnalogReport, RioHidReport). At 9600 baud Span bought nothing; the SerialPortTransport bridge copies disappear entirely. - ConfigStore/OverlayTemplateStore switch to Newtonsoft 13 with the same conventions (indented, PascalCase, string enums, null-skipping); verified against the real STJ-written config.json and regions.json (load + round-trip). System.Memory and System.Text.Json packages dropped. 275 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
4.0 KiB
C#
121 lines
4.0 KiB
C#
using RioJoy.Core.Protocol;
|
|
using Xunit;
|
|
|
|
namespace RioJoy.Core.Tests.Protocol;
|
|
|
|
public class PacketParserTests
|
|
{
|
|
private static List<RioRxEvent> FeedAll(PacketParser parser, byte[] data)
|
|
{
|
|
var events = new List<RioRxEvent>();
|
|
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<RioRxEvent> 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<RioRxEvent>();
|
|
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<RioRxEvent> 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<RioRxEvent>();
|
|
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);
|
|
}
|
|
}
|