From 3b2af7b79a31fd48f949571089e11db84b74fb13 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 11 Jul 2026 20:36:19 -0500 Subject: [PATCH] Phase 8A (1/2): de-Span the serial layer, swap JSON to Newtonsoft 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 --- src/RioJoy.Core/Hid/RioHidReport.cs | 5 +---- .../Overlay/OverlayTemplateStore.cs | 18 +++++++-------- src/RioJoy.Core/Plasma/PlasmaDisplay.cs | 2 +- src/RioJoy.Core/Profiles/ConfigStore.cs | 22 +++++++++++-------- src/RioJoy.Core/Protocol/AnalogReport.cs | 3 ++- src/RioJoy.Core/Protocol/PacketBuilder.cs | 15 ++++++++----- src/RioJoy.Core/Protocol/PacketParser.cs | 17 ++++++++------ src/RioJoy.Core/Protocol/RioChecksum.cs | 17 +++++++++----- src/RioJoy.Core/Protocol/RioPacket.cs | 13 ++++++----- src/RioJoy.Core/Protocol/RioReplies.cs | 7 +++--- src/RioJoy.Core/RioJoy.Core.csproj | 3 +-- src/RioJoy.Core/RioRuntime.cs | 4 ++-- src/RioJoy.Core/Serial/IRioTransport.cs | 9 ++++++-- src/RioJoy.Core/Serial/RioSerialLink.cs | 8 +++---- src/RioJoy.Core/Serial/SerialPortTransport.cs | 18 ++++----------- .../Hid/RioHidReportTests.cs | 16 +++++++------- .../Protocol/PacketBuilderTests.cs | 4 ++-- .../Protocol/PacketParserTests.cs | 2 +- .../Protocol/RioChecksumTests.cs | 2 +- .../RioJoy.Core.Tests/Serial/FakeTransport.cs | 10 ++++----- tools/RioSerialMonitor/Program.cs | 2 +- 21 files changed, 104 insertions(+), 93 deletions(-) diff --git a/src/RioJoy.Core/Hid/RioHidReport.cs b/src/RioJoy.Core/Hid/RioHidReport.cs index b0df9fe..1794208 100644 --- a/src/RioJoy.Core/Hid/RioHidReport.cs +++ b/src/RioJoy.Core/Hid/RioHidReport.cs @@ -43,10 +43,7 @@ public sealed class RioHidReport SetHat(RioHat.Centered); } - /// The current report bytes (length ). - public ReadOnlySpan Bytes => _buffer; - - /// Copy of the current report bytes. + /// Copy of the current report bytes (length ). public byte[] ToArray() => (byte[])_buffer.Clone(); /// Set an axis value (clamped to 0..), little-endian. diff --git a/src/RioJoy.Core/Overlay/OverlayTemplateStore.cs b/src/RioJoy.Core/Overlay/OverlayTemplateStore.cs index 720e0ab..1558b5a 100644 --- a/src/RioJoy.Core/Overlay/OverlayTemplateStore.cs +++ b/src/RioJoy.Core/Overlay/OverlayTemplateStore.cs @@ -1,5 +1,5 @@ -using System.Text.Json; -using System.Text.Json.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace RioJoy.Core.Overlay; @@ -11,24 +11,24 @@ namespace RioJoy.Core.Overlay; /// public static class OverlayTemplateStore { - private static readonly JsonSerializerOptions Options = new() + private static readonly JsonSerializerSettings Options = new() { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { new JsonStringEnumConverter() }, + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + Converters = { new StringEnumConverter() }, }; public static string Serialize(OverlayTemplate template) { if (template is null) throw new ArgumentNullException(nameof(template)); - return JsonSerializer.Serialize(template, Options); + return JsonConvert.SerializeObject(template, Options); } public static OverlayTemplate Deserialize(string json) { if (string.IsNullOrWhiteSpace(json)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(json)); - return JsonSerializer.Deserialize(json, Options) - ?? throw new JsonException("Overlay template JSON deserialized to null."); + return JsonConvert.DeserializeObject(json, Options) + ?? throw new JsonSerializationException("Overlay template JSON deserialized to null."); } public static void Save(OverlayTemplate template, string path) diff --git a/src/RioJoy.Core/Plasma/PlasmaDisplay.cs b/src/RioJoy.Core/Plasma/PlasmaDisplay.cs index 3e8fe69..99eed11 100644 --- a/src/RioJoy.Core/Plasma/PlasmaDisplay.cs +++ b/src/RioJoy.Core/Plasma/PlasmaDisplay.cs @@ -48,5 +48,5 @@ public sealed class PlasmaDisplay } private Task WriteAsync(byte[] data, CancellationToken ct) => - _transport.WriteAsync(data, ct).AsTask(); + _transport.WriteAsync(data, ct); } diff --git a/src/RioJoy.Core/Profiles/ConfigStore.cs b/src/RioJoy.Core/Profiles/ConfigStore.cs index a3f903b..54cabb7 100644 --- a/src/RioJoy.Core/Profiles/ConfigStore.cs +++ b/src/RioJoy.Core/Profiles/ConfigStore.cs @@ -1,32 +1,36 @@ -using System.Text.Json; -using System.Text.Json.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace RioJoy.Core.Profiles; /// /// Loads and saves as JSON. Replaces the legacy /// SimpleIni single-file config with a profile library (see docs/PLAN.md). +/// Newtonsoft rather than System.Text.Json so the net40 (Windows XP) flavor +/// shares one serializer — and one on-disk format — with net48 (PLAN.md §8A). +/// Conventions match the original STJ settings: indented, PascalCase names, +/// enums as strings, nulls skipped — existing config.json files parse as-is. /// public static class ConfigStore { - private static readonly JsonSerializerOptions Options = new() + private static readonly JsonSerializerSettings Options = new() { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { new JsonStringEnumConverter() }, + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + Converters = { new StringEnumConverter() }, }; public static string Serialize(AppConfig config) { if (config is null) throw new ArgumentNullException(nameof(config)); - return JsonSerializer.Serialize(config, Options); + return JsonConvert.SerializeObject(config, Options); } public static AppConfig Deserialize(string json) { if (string.IsNullOrWhiteSpace(json)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(json)); - return JsonSerializer.Deserialize(json, Options) - ?? throw new JsonException("Config JSON deserialized to null."); + return JsonConvert.DeserializeObject(json, Options) + ?? throw new JsonSerializationException("Config JSON deserialized to null."); } /// Save the config to (creating directories). diff --git a/src/RioJoy.Core/Protocol/AnalogReport.cs b/src/RioJoy.Core/Protocol/AnalogReport.cs index 9013957..77934d3 100644 --- a/src/RioJoy.Core/Protocol/AnalogReport.cs +++ b/src/RioJoy.Core/Protocol/AnalogReport.cs @@ -44,8 +44,9 @@ public readonly struct AnalogReport /// 0xFE (), which the RIO uses as an /// "invalid sample" sentinel — the legacy code ignores such replies. /// - public static bool TryParse(ReadOnlySpan payload, out AnalogReport report) + public static bool TryParse(byte[] payload, out AnalogReport report) { + if (payload is null) throw new ArgumentNullException(nameof(payload)); if (payload.Length != RioCommandTable.PayloadLength(RioCommand.AnalogReply)) throw new ArgumentException( $"AnalogReply payload must be {RioCommandTable.PayloadLength(RioCommand.AnalogReply)} bytes.", diff --git a/src/RioJoy.Core/Protocol/PacketBuilder.cs b/src/RioJoy.Core/Protocol/PacketBuilder.cs index e8d915b..902e093 100644 --- a/src/RioJoy.Core/Protocol/PacketBuilder.cs +++ b/src/RioJoy.Core/Protocol/PacketBuilder.cs @@ -13,8 +13,9 @@ public static class PacketBuilder /// . The payload length must match the command's /// entry in the length table. /// - public static byte[] Build(RioCommand command, ReadOnlySpan payload) + public static byte[] Build(RioCommand command, byte[] payload) { + if (payload is null) throw new ArgumentNullException(nameof(payload)); int expected = RioCommandTable.PayloadLength(command); if (payload.Length != expected) throw new ArgumentException( @@ -23,13 +24,15 @@ public static class PacketBuilder var packet = new byte[1 + expected + 1]; packet[0] = (byte)command; - payload.CopyTo(packet.AsSpan(1)); - packet[^1] = RioChecksum.Compute(packet.AsSpan(0, 1 + expected)); + Array.Copy(payload, 0, packet, 1, expected); + packet[packet.Length - 1] = RioChecksum.Compute(packet, 0, 1 + expected); return packet; } /// Build a zero-payload command packet (CheckRequest etc.). - public static byte[] Build(RioCommand command) => Build(command, ReadOnlySpan.Empty); + public static byte[] Build(RioCommand command) => Build(command, EmptyPayload); + + private static readonly byte[] EmptyPayload = new byte[0]; // --- Convenience builders for the PC → RIO commands --------------------- @@ -40,12 +43,12 @@ public static class PacketBuilder public static byte[] AnalogRequest() => Build(RioCommand.AnalogRequest); public static byte[] ResetRequest(RioResetTarget target) => - Build(RioCommand.ResetRequest, stackalloc byte[] { (byte)target }); + Build(RioCommand.ResetRequest, new[] { (byte)target }); /// /// LampRequest: payload is [lamp#, state]. The state byte is a /// 7-bit lamp-state value (see ). /// public static byte[] LampRequest(byte lampNumber, byte state) => - Build(RioCommand.LampRequest, stackalloc byte[] { lampNumber, state }); + Build(RioCommand.LampRequest, new[] { lampNumber, state }); } diff --git a/src/RioJoy.Core/Protocol/PacketParser.cs b/src/RioJoy.Core/Protocol/PacketParser.cs index 9dbc7ed..10ea04c 100644 --- a/src/RioJoy.Core/Protocol/PacketParser.cs +++ b/src/RioJoy.Core/Protocol/PacketParser.cs @@ -92,15 +92,17 @@ public sealed class PacketParser } /// - /// Feed a chunk of received bytes, invoking for - /// each event produced, in order. + /// Feed the first received bytes of + /// , invoking for each + /// event produced, in order. /// - public void Feed(ReadOnlySpan data, Action onEvent) + public void Feed(byte[] data, int count, Action onEvent) { + if (data is null) throw new ArgumentNullException(nameof(data)); if (onEvent is null) throw new ArgumentNullException(nameof(onEvent)); - foreach (byte ch in data) + for (int i = 0; i < count; i++) { - if (Feed(ch, out RioRxEvent ev)) + if (Feed(data[i], out RioRxEvent ev)) onEvent(ev); } } @@ -110,11 +112,12 @@ public sealed class PacketParser // _buffer = [command][payload…][checksum]; _count includes all of them. int bodyLen = _count - 1; // command + payload (exclude checksum) byte receivedChecksum = _buffer[bodyLen]; - byte computed = RioChecksum.Compute(_buffer.AsSpan(0, bodyLen)); + byte computed = RioChecksum.Compute(_buffer, 0, bodyLen); bool checksumValid = computed == receivedChecksum; var command = (RioCommand)_buffer[0]; - var payload = _buffer.AsSpan(1, bodyLen - 1).ToArray(); // copy out; buffer is reused + var payload = new byte[bodyLen - 1]; // copy out; buffer is reused + Array.Copy(_buffer, 1, payload, 0, payload.Length); var packet = new RioPacket(command, payload); Reset(); diff --git a/src/RioJoy.Core/Protocol/RioChecksum.cs b/src/RioJoy.Core/Protocol/RioChecksum.cs index 2664cf4..8c46f0a 100644 --- a/src/RioJoy.Core/Protocol/RioChecksum.cs +++ b/src/RioJoy.Core/Protocol/RioChecksum.cs @@ -9,15 +9,22 @@ namespace RioJoy.Core.Protocol; public static class RioChecksum { /// - /// Compute the 7-bit checksum over - /// (the command byte followed by its payload bytes — not the checksum byte). + /// Compute the 7-bit checksum over the command byte followed by its payload + /// bytes (not the checksum byte): bytes of + /// starting at . /// - public static byte Compute(ReadOnlySpan commandAndPayload) + public static byte Compute(byte[] commandAndPayload, int offset, int count) { + if (commandAndPayload is null) throw new ArgumentNullException(nameof(commandAndPayload)); + byte sum = 0; - foreach (byte b in commandAndPayload) - sum += (byte)(b & 0x7F); + for (int i = offset; i < offset + count; i++) + sum += (byte)(commandAndPayload[i] & 0x7F); return (byte)(sum & 0x7F); } + + /// Compute the checksum over all of . + public static byte Compute(byte[] commandAndPayload) => + Compute(commandAndPayload, 0, commandAndPayload?.Length ?? 0); } diff --git a/src/RioJoy.Core/Protocol/RioPacket.cs b/src/RioJoy.Core/Protocol/RioPacket.cs index 8be56fd..c211ed9 100644 --- a/src/RioJoy.Core/Protocol/RioPacket.cs +++ b/src/RioJoy.Core/Protocol/RioPacket.cs @@ -11,13 +11,14 @@ public readonly struct RioPacket public RioCommand Command { get; } /// - /// The payload bytes (high bit always clear). Length matches - /// . + /// The payload bytes (high bit always clear; treat as read-only). Length + /// matches . /// - public ReadOnlyMemory Payload { get; } + public byte[] Payload { get; } - public RioPacket(RioCommand command, ReadOnlyMemory payload) + public RioPacket(RioCommand command, byte[] payload) { + if (payload is null) throw new ArgumentNullException(nameof(payload)); int expected = RioCommandTable.PayloadLength(command); if (payload.Length != expected) throw new ArgumentException( @@ -30,7 +31,7 @@ public readonly struct RioPacket public override string ToString() { - var hex = BitConverter.ToString(Payload.ToArray()).Replace("-", string.Empty); - return Payload.IsEmpty ? Command.ToString() : $"{Command} [{hex}]"; + var hex = BitConverter.ToString(Payload).Replace("-", string.Empty); + return Payload.Length == 0 ? Command.ToString() : $"{Command} [{hex}]"; } } diff --git a/src/RioJoy.Core/Protocol/RioReplies.cs b/src/RioJoy.Core/Protocol/RioReplies.cs index 2589f65..5a6224a 100644 --- a/src/RioJoy.Core/Protocol/RioReplies.cs +++ b/src/RioJoy.Core/Protocol/RioReplies.cs @@ -16,7 +16,7 @@ public readonly struct VersionInfo } /// Decode a payload (2 bytes). - public static VersionInfo Parse(ReadOnlySpan payload) + public static VersionInfo Parse(byte[] payload) { Require(payload, RioCommand.VersionReply); return new VersionInfo(payload[0], payload[1]); @@ -24,8 +24,9 @@ public readonly struct VersionInfo public override string ToString() => $"{Major}.{Minor}"; - internal static void Require(ReadOnlySpan payload, RioCommand command) + internal static void Require(byte[] payload, RioCommand command) { + if (payload is null) throw new ArgumentNullException(nameof(payload)); int expected = RioCommandTable.PayloadLength(command); if (payload.Length != expected) throw new ArgumentException( @@ -51,7 +52,7 @@ public readonly struct CheckStatus } /// Decode a payload (2 bytes). - public static CheckStatus Parse(ReadOnlySpan payload) + public static CheckStatus Parse(byte[] payload) { VersionInfo.Require(payload, RioCommand.CheckReply); return new CheckStatus((RioStatusType)payload[0], payload[1]); diff --git a/src/RioJoy.Core/RioJoy.Core.csproj b/src/RioJoy.Core/RioJoy.Core.csproj index ffb09f8..b050b9b 100644 --- a/src/RioJoy.Core/RioJoy.Core.csproj +++ b/src/RioJoy.Core/RioJoy.Core.csproj @@ -15,8 +15,7 @@ - - + all diff --git a/src/RioJoy.Core/RioRuntime.cs b/src/RioJoy.Core/RioRuntime.cs index d57b5d3..9323fc8 100644 --- a/src/RioJoy.Core/RioRuntime.cs +++ b/src/RioJoy.Core/RioRuntime.cs @@ -144,7 +144,7 @@ public sealed class RioRuntime : IRioCommandSink, IDisposable private void OnPacket(RioPacket packet) { EnsureLampsInitialized(); - ReadOnlySpan p = packet.Payload.Span; + byte[] p = packet.Payload; switch (packet.Command) { case RioCommand.ButtonPressed when p[0] < RioAddress.ButtonCount: @@ -185,7 +185,7 @@ public sealed class RioRuntime : IRioCommandSink, IDisposable ButtonActivity?.Invoke(address, pressed); } - private static bool IsKeypad(ReadOnlySpan p) => p[0] is 0 or 1 && p[1] <= 0x0F; + private static bool IsKeypad(byte[] p) => p[0] is 0 or 1 && p[1] <= 0x0F; // IRioCommandSink: RIO commands routed from a button (RIOcmd port). void IRioCommandSink.Execute(RioCommandCode command) diff --git a/src/RioJoy.Core/Serial/IRioTransport.cs b/src/RioJoy.Core/Serial/IRioTransport.cs index 9692281..bb8e113 100644 --- a/src/RioJoy.Core/Serial/IRioTransport.cs +++ b/src/RioJoy.Core/Serial/IRioTransport.cs @@ -15,8 +15,13 @@ public interface IRioTransport : IDisposable /// Read available bytes into . Returns the number of /// bytes read; a return of 0 indicates the transport has closed. /// - ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken); + /// + /// Plain byte[]/ rather than Memory/ValueTask: the + /// link runs at 9600 baud, and the net40 (Windows XP) flavor has neither + /// System.Memory nor ValueTask (see docs/PLAN.md §Phase 8). + /// + Task ReadAsync(byte[] buffer, CancellationToken cancellationToken); /// Write all of to the transport. - ValueTask WriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken); + Task WriteAsync(byte[] data, CancellationToken cancellationToken); } diff --git a/src/RioJoy.Core/Serial/RioSerialLink.cs b/src/RioJoy.Core/Serial/RioSerialLink.cs index 1104e73..4ad4b73 100644 --- a/src/RioJoy.Core/Serial/RioSerialLink.cs +++ b/src/RioJoy.Core/Serial/RioSerialLink.cs @@ -79,7 +79,7 @@ public sealed class RioSerialLink } /// Send a pre-built packet (see ) to the RIO. - public async Task SendAsync(ReadOnlyMemory packet, CancellationToken cancellationToken = default) + public async Task SendAsync(byte[] packet, CancellationToken cancellationToken = default) { await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -155,7 +155,7 @@ public sealed class RioSerialLink switch (packet.Command) { case RioCommand.AnalogReply: - if (AnalogReport.TryParse(packet.Payload.Span, out AnalogReport report)) + if (AnalogReport.TryParse(packet.Payload, out AnalogReport report)) { _sinceAnalog.Restart(); AnalogReceived?.Invoke(report); @@ -163,11 +163,11 @@ public sealed class RioSerialLink break; case RioCommand.VersionReply: - VersionReceived?.Invoke(VersionInfo.Parse(packet.Payload.Span)); + VersionReceived?.Invoke(VersionInfo.Parse(packet.Payload)); break; case RioCommand.CheckReply: - CheckReceived?.Invoke(CheckStatus.Parse(packet.Payload.Span)); + CheckReceived?.Invoke(CheckStatus.Parse(packet.Payload)); break; } } diff --git a/src/RioJoy.Core/Serial/SerialPortTransport.cs b/src/RioJoy.Core/Serial/SerialPortTransport.cs index 20f8dc2..cce6bd9 100644 --- a/src/RioJoy.Core/Serial/SerialPortTransport.cs +++ b/src/RioJoy.Core/Serial/SerialPortTransport.cs @@ -47,21 +47,11 @@ public sealed class SerialPortTransport : IRioTransport public string Description => $"{_port.PortName} @ {BaudRate} 8N1"; - // net48's Stream has no Memory-based ReadAsync/WriteAsync overloads, so bridge - // through a pooled array and copy into/out of the caller's Memory. - public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) - { - byte[] tmp = new byte[buffer.Length]; - int read = await _stream.ReadAsync(tmp, 0, tmp.Length, cancellationToken).ConfigureAwait(false); - new ReadOnlySpan(tmp, 0, read).CopyTo(buffer.Span); - return read; - } + public Task ReadAsync(byte[] buffer, CancellationToken cancellationToken) => + _stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); - public async ValueTask WriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) - { - byte[] tmp = data.ToArray(); - await _stream.WriteAsync(tmp, 0, tmp.Length, cancellationToken).ConfigureAwait(false); - } + public Task WriteAsync(byte[] data, CancellationToken cancellationToken) => + _stream.WriteAsync(data, 0, data.Length, cancellationToken); public void Dispose() { diff --git a/tests/RioJoy.Core.Tests/Hid/RioHidReportTests.cs b/tests/RioJoy.Core.Tests/Hid/RioHidReportTests.cs index 9caf07b..2e4aec3 100644 --- a/tests/RioJoy.Core.Tests/Hid/RioHidReportTests.cs +++ b/tests/RioJoy.Core.Tests/Hid/RioHidReportTests.cs @@ -29,12 +29,12 @@ public class RioHidReportTests { var report = new RioHidReport(); report.SetAxis(JoyAxis.Z, 0x1234); - Assert.Equal(0x34, report.Bytes[4]); - Assert.Equal(0x12, report.Bytes[5]); + Assert.Equal(0x34, report.ToArray()[4]); + Assert.Equal(0x12, report.ToArray()[5]); report.SetAxis(JoyAxis.Z, 70000); // over max → clamped to 32767 - Assert.Equal(0xFF, report.Bytes[4]); - Assert.Equal(0x7F, report.Bytes[5]); + Assert.Equal(0xFF, report.ToArray()[4]); + Assert.Equal(0x7F, report.ToArray()[5]); } [Theory] @@ -47,7 +47,7 @@ public class RioHidReportTests { var report = new RioHidReport(); report.SetHat(hat); - Assert.Equal(expected, report.Bytes[12] & 0x0F); + Assert.Equal(expected, report.ToArray()[12] & 0x0F); } [Theory] @@ -59,10 +59,10 @@ public class RioHidReportTests { var report = new RioHidReport(); report.SetButton(button, pressed: true); - Assert.Equal(mask, report.Bytes[byteIndex]); + Assert.Equal(mask, report.ToArray()[byteIndex]); report.SetButton(button, pressed: false); - Assert.Equal(0, report.Bytes[byteIndex]); + Assert.Equal(0, report.ToArray()[byteIndex]); } [Fact] @@ -80,6 +80,6 @@ public class RioHidReportTests report.SetButton(1, true); report.SetButton(2, true); report.SetButton(1, false); - Assert.Equal(0x02, report.Bytes[13]); // only button 2 remains + Assert.Equal(0x02, report.ToArray()[13]); // only button 2 remains } } diff --git a/tests/RioJoy.Core.Tests/Protocol/PacketBuilderTests.cs b/tests/RioJoy.Core.Tests/Protocol/PacketBuilderTests.cs index 709a583..b42755e 100644 --- a/tests/RioJoy.Core.Tests/Protocol/PacketBuilderTests.cs +++ b/tests/RioJoy.Core.Tests/Protocol/PacketBuilderTests.cs @@ -41,7 +41,7 @@ public class PacketBuilderTests byte[] packet = PacketBuilder.Build(RioCommand.AnalogReply, payload); Assert.Equal((byte)RioCommand.AnalogReply, packet[0]); - Assert.Equal(payload, packet.AsSpan(1, packet.Length - 2).ToArray()); - Assert.Equal(RioChecksum.Compute(packet.AsSpan(0, packet.Length - 1)), packet[^1]); + Assert.Equal(payload, packet.Skip(1).Take(packet.Length - 2).ToArray()); + Assert.Equal(RioChecksum.Compute(packet, 0, packet.Length - 1), packet[packet.Length - 1]); } } diff --git a/tests/RioJoy.Core.Tests/Protocol/PacketParserTests.cs b/tests/RioJoy.Core.Tests/Protocol/PacketParserTests.cs index eaa57be..9c585a4 100644 --- a/tests/RioJoy.Core.Tests/Protocol/PacketParserTests.cs +++ b/tests/RioJoy.Core.Tests/Protocol/PacketParserTests.cs @@ -5,7 +5,7 @@ namespace RioJoy.Core.Tests.Protocol; public class PacketParserTests { - private static List FeedAll(PacketParser parser, ReadOnlySpan data) + private static List FeedAll(PacketParser parser, byte[] data) { var events = new List(); foreach (byte b in data) diff --git a/tests/RioJoy.Core.Tests/Protocol/RioChecksumTests.cs b/tests/RioJoy.Core.Tests/Protocol/RioChecksumTests.cs index cb4691a..0a3ba96 100644 --- a/tests/RioJoy.Core.Tests/Protocol/RioChecksumTests.cs +++ b/tests/RioJoy.Core.Tests/Protocol/RioChecksumTests.cs @@ -8,7 +8,7 @@ public class RioChecksumTests [Fact] public void Empty_IsZero() { - Assert.Equal(0, RioChecksum.Compute(ReadOnlySpan.Empty)); + Assert.Equal(0, RioChecksum.Compute(new byte[0])); } [Fact] diff --git a/tests/RioJoy.Core.Tests/Serial/FakeTransport.cs b/tests/RioJoy.Core.Tests/Serial/FakeTransport.cs index c86865e..fdd915a 100644 --- a/tests/RioJoy.Core.Tests/Serial/FakeTransport.cs +++ b/tests/RioJoy.Core.Tests/Serial/FakeTransport.cs @@ -23,13 +23,13 @@ internal sealed class FakeTransport : IRioTransport /// Signal that no more inbound data will arrive (transport closed). public void CompleteIncoming() => _incoming.Writer.TryComplete(); - public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) + public async Task ReadAsync(byte[] buffer, CancellationToken cancellationToken) { while (await _incoming.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { if (_incoming.Reader.TryRead(out byte[]? chunk)) { - chunk.AsSpan().CopyTo(buffer.Span); + Array.Copy(chunk, buffer, chunk.Length); return chunk.Length; } } @@ -37,10 +37,10 @@ internal sealed class FakeTransport : IRioTransport return 0; // completed } - public ValueTask WriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) + public Task WriteAsync(byte[] data, CancellationToken cancellationToken) { - _writes.Writer.TryWrite(data.ToArray()); - return default; // net48 has no ValueTask.CompletedTask; default(ValueTask) is the completed task + _writes.Writer.TryWrite((byte[])data.Clone()); + return Task.CompletedTask; } /// Read the next outbound write, failing if none arrives in time. diff --git a/tools/RioSerialMonitor/Program.cs b/tools/RioSerialMonitor/Program.cs index 41c3200..287a5df 100644 --- a/tools/RioSerialMonitor/Program.cs +++ b/tools/RioSerialMonitor/Program.cs @@ -68,7 +68,7 @@ bool Changed(AnalogReport a, AnalogReport b) => link.PacketReceived += p => { packets++; - ReadOnlySpan pl = p.Payload.Span; + byte[] pl = p.Payload; switch (p.Command) { case RioCommand.ButtonPressed when pl[0] < RioAddress.ButtonCount: