using StandardScene.Magnetic.Protocol; using StandardScene.Magnetic.Tests.TestHelpers; using System; using System.Net; using System.Net.Sockets; using System.Threading; using Xunit; namespace StandardScene.Magnetic.Tests.Protocol { public sealed class Fass2UdpHubMultiCarTests : IDisposable { private readonly int _hubPort; private readonly TestUdpCar _car1; private readonly TestUdpCar _car2; public Fass2UdpHubMultiCarTests() { _hubPort = AllocateUdpPort(); _car1 = new TestUdpCar(1, AllocateUdpPort()); _car2 = new TestUdpCar(2, AllocateUdpPort()); Fass2UdpHub.Register(_car1); Fass2UdpHub.Register(_car2); Fass2UdpHub.EnsureStarted(_hubPort); Thread.Sleep(100); } [Fact] public void Hub_RoutesStateToRegisteredCar_AndRepliesWith0x10() { SendState(_car1.VehicleCode, 2, 10, 100); var ack1 = _car1.WaitForAck(); Assert.NotNull(ack1); Assert.Equal(Fass2Protocol.ControlFrameLength, ack1.Length); Assert.Equal(Fass2Protocol.CmdStateResponse, ack1[1]); Assert.Equal(0x01, ack1[2]); Assert.Single(_car1.Reports); Assert.Equal((ushort)10, _car1.Reports[0].Node.Node); Assert.Empty(_car2.Reports); } [Fact] public void Hub_DispatchesMultipleCarsIndependently() { SendState(1, 1, 11, 201); SendState(2, 2, 22, 202); Assert.NotNull(_car1.WaitForAck()); Assert.NotNull(_car2.WaitForAck()); Assert.Single(_car1.Reports); Assert.Single(_car2.Reports); Assert.Equal((ushort)11, _car1.Reports[0].Node.Node); Assert.Equal((ushort)22, _car2.Reports[0].Node.Node); Assert.Equal((ulong)201, _car1.Reports[0].Task); Assert.Equal((ulong)202, _car2.Reports[0].Task); } [Fact] public void EnsureStarted_NewPort_ReceivesOnNewPort() { var newPort = AllocateUdpPort(); Fass2UdpHub.EnsureStarted(newPort); Thread.Sleep(150); var frame = Fass2TestFrameBuilder.BuildStateFrame(1, 1, 88, 301); using var sender = new UdpClient(); sender.Send(frame, frame.Length, new IPEndPoint(IPAddress.Loopback, newPort)); Assert.NotNull(_car1.WaitForAck()); Assert.Equal((ushort)88, _car1.Reports[_car1.Reports.Count - 1].Node.Node); } [Fact] public void Hub_IgnoresUnregisteredCarCode() { SendState(99, 2, 30, 0); Thread.Sleep(300); Assert.Empty(_car1.Reports); Assert.Empty(_car2.Reports); } [Fact] public void Register_VehicleCodeZero_DoesNotReceiveState() { var zeroPort = AllocateUdpPort(); using var zero = new TestUdpCar(0, zeroPort); Assert.False(Fass2UdpHub.Register(zero)); SendState(0, 2, 40, 0); Thread.Sleep(200); Assert.Empty(zero.Reports); } [Fact] public void Register_DuplicateVehicleCode_KeepsFirstCar() { var dupPort = AllocateUdpPort(); using var dup = new TestUdpCar(1, dupPort); Assert.False(Fass2UdpHub.Register(dup)); SendState(1, 2, 41, 401); Assert.NotNull(_car1.WaitForAck()); Assert.Equal((ushort)41, _car1.Reports[_car1.Reports.Count - 1].Node.Node); Assert.Empty(dup.Reports); } public void Dispose() { Fass2UdpHub.Unregister(_car1); Fass2UdpHub.Unregister(_car2); Fass2UdpHub.Stop(); _car1.Dispose(); _car2.Dispose(); } private void SendState(ushort carCode, byte state, ushort nodeId, ulong taskId) { var frame = Fass2TestFrameBuilder.BuildStateFrame(carCode, state, nodeId, taskId); using var sender = new UdpClient(); sender.Send(frame, frame.Length, new IPEndPoint(IPAddress.Loopback, _hubPort)); Thread.Sleep(80); } private static int AllocateUdpPort() { var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; listener.Stop(); return port; } } }