using StandardScene.Magnetic.Protocol; using StandardScene.Magnetic.Tasking; using StandardScene.Magnetic.Tests.TestHelpers; using System; using Xunit; namespace StandardScene.Magnetic.Tests.Protocol { public sealed class Fass2ProtocolGoldenTests { [Fact] public void BuildControl_Start_Car1_Param0_MatchesGoldenLayout() { var frame = Fass2Protocol.BuildControl(Fass2Protocol.CmdStart, 1, 0); Assert.Equal(Fass2Protocol.ControlFrameLength, frame.Length); Assert.Equal(Fass2Protocol.Begin, frame[0]); Assert.Equal(Fass2Protocol.CmdStart, frame[1]); Assert.Equal(0x01, frame[2]); Assert.Equal(0x00, frame[3]); Assert.Equal(0x00, frame[4]); Assert.Equal(0x00, frame[5]); Assert.Equal(Fass2Protocol.Xor(frame, 1, 47), frame[48]); Assert.Equal(Fass2Protocol.End, frame[49]); const string goldenPrefix = "BB 01 01 00 00 00"; Assert.StartsWith(goldenPrefix, Fass2TestFrameBuilder.ToHex(frame)); } [Fact] public void BuildNodes_TwoNodes_LittleEndianAndXor() { var nodes = new[] { new Fass2NodeMessage { Node = 1, Distance = 500, StartStop = 1, Speed = 120 }, new Fass2NodeMessage { Node = 2, StartStop = 2, Lift = 1 } }; var frame = Fass2Protocol.BuildNodes(1, 0x0102030405060708UL, nodes); Assert.Equal(Fass2Protocol.NodesFrameLength, frame.Length); Assert.Equal(Fass2Protocol.CmdNodes, frame[1]); Assert.Equal(0x08, frame[4]); Assert.Equal(0x01, frame[11]); Assert.Equal(0x02, frame[12]); Assert.Equal(0x00, frame[13]); Assert.Equal(Fass2Protocol.Xor(frame, 1, 297), frame[298]); Assert.Equal(Fass2Protocol.End, frame[299]); var parsedNode0 = Fass2NodeMessage.FromBytes(CopyRange(frame, 14, 25)); Assert.Equal((ushort)1, parsedNode0.Node); Assert.Equal((ushort)500, parsedNode0.Distance); Assert.Equal((byte)1, parsedNode0.StartStop); Assert.Equal((ushort)120, parsedNode0.Speed); var parsedNode1 = Fass2NodeMessage.FromBytes(CopyRange(frame, 39, 25)); Assert.Equal((ushort)2, parsedNode1.Node); Assert.Equal((byte)2, parsedNode1.StartStop); Assert.Equal((byte)1, parsedNode1.Lift); } [Fact] public void BuildAction_RoundTripsNodeBlock() { var node = new Fass2NodeMessage { Node = 3, StartStop = 22, Lift = 2, Speed = 120 }; var frame = Fass2Protocol.BuildAction(2, 999, node); Assert.Equal(Fass2Protocol.ActionFrameLength, frame.Length); Assert.Equal(Fass2Protocol.CmdAction, frame[1]); Assert.Equal(0x02, frame[2]); Assert.Equal(0x00, frame[3]); Assert.Equal(Fass2Protocol.Xor(frame, 1, 97), frame[98]); var parsed = Fass2NodeMessage.FromBytes(CopyRange(frame, 14, 25)); Assert.Equal(node.Node, parsed.Node); Assert.Equal(node.StartStop, parsed.StartStop); Assert.Equal(node.Lift, parsed.Lift); Assert.Equal(node.Speed, parsed.Speed); } [Fact] public void BuildStateResponse_FixedTimestamp_EncodesBeijingCalendar() { var frame = Fass2Protocol.BuildStateResponse(1, 1_704_067_200_000UL); Assert.Equal(Fass2Protocol.CmdStateResponse, frame[1]); Assert.Equal(0x01, frame[2]); Assert.Equal(0x00, frame[3]); Assert.Equal(Fass2Protocol.Xor(frame, 1, 47), frame[48]); Assert.Equal(Fass2Protocol.End, frame[49]); } [Fact] public void ParseState_RoundTripsMinimalFrame() { var frame = Fass2TestFrameBuilder.BuildStateFrame(5, 2, 7, 42); Assert.True(Fass2Protocol.TryExtractStateFrame(frame, frame.Length, out var extracted)); Assert.Equal(frame, extracted); var report = Fass2Protocol.ParseState(frame); Assert.Equal((ushort)5, report.Car); Assert.Equal((byte)2, report.State); Assert.Equal((ulong)42, report.Task); Assert.Equal((ushort)7, report.Node.Node); Assert.Equal((ushort)500, report.Node.Distance); } [Fact] public void TryExtractStateFrame_RejectsBadXor() { var frame = Fass2TestFrameBuilder.BuildStateFrame(5, 2, 7, 42); frame[98] ^= 0xFF; Assert.False(Fass2Protocol.TryExtractStateFrame(frame, frame.Length, out _)); } [Fact] public void ParseState_RejectsBadXor() { var frame = Fass2TestFrameBuilder.BuildStateFrame(5, 2, 7, 42); frame[98] ^= 0xFF; Assert.Throws(() => Fass2Protocol.ParseState(frame)); } [Fact] public void NodeMessage_ToBytes_FromBytes_RoundTrip() { var source = new Fass2NodeMessage { Node = 11, Distance = 1234, StartStop = 2, Direction = 3, Orientation = 64, Byroad = 2, Speed = 120, Lift = 1, Roll = 2, Shutdown = 1 }; var roundTrip = Fass2NodeMessage.FromBytes(source.ToBytes()); Assert.Equal(source.Node, roundTrip.Node); Assert.Equal(source.Distance, roundTrip.Distance); Assert.Equal(source.StartStop, roundTrip.StartStop); Assert.Equal(source.Direction, roundTrip.Direction); Assert.Equal(source.Orientation, roundTrip.Orientation); Assert.Equal(source.Byroad, roundTrip.Byroad); Assert.Equal(source.Speed, roundTrip.Speed); Assert.Equal(source.Lift, roundTrip.Lift); Assert.Equal(source.Roll, roundTrip.Roll); Assert.Equal(source.Shutdown, roundTrip.Shutdown); } [Fact] public void SpeedToProtocol_UsesMPerMinTimesTen() { Assert.Equal((ushort)120, Fass2TaskBuilder.SpeedToProtocol(12)); Assert.Equal((ushort)120, Fass2TaskBuilder.SpeedToProtocol(0.2)); } private static byte[] CopyRange(byte[] source, int offset, int length) { var copy = new byte[length]; Buffer.BlockCopy(source, offset, copy, 0, length); return copy; } } }