using System; using System.Collections.Generic; using System.Linq; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class WireRebindTests { sealed class Delivery { public P.Packet Packet; public long Address; } sealed class Rig { public readonly P Program=TestRig.Program(); public readonly Queue Incoming=new Queue(); public readonly List Sent=new List(); public readonly List Received=new List(); public Rig() { Program.Config=new P.Settings("[System]\nId=base\nFleetId=mining\nBaseId=base","fleet","base"); var broadcast=new Stub(); var unicast=new Stub().Method("get_HasPendingMessage",a=>Incoming.Count>0).Method("AcceptMessage",a=>Incoming.Dequeue()); var igc=new Stub().Set("UnicastListener",unicast.Value) .Method("RegisterBroadcastListener",a=>broadcast.Value) .Method("SendBroadcastMessage",a=>Capture(0,(string)a[1])) .Method("SendUnicastMessage",a=>{Capture((long)a[0],(string)a[2]);return true;}); TestRig.SetBase(Program,"IGC",igc.Value);Program.Bus=new P.Wire(Program); Add("old",1);Drain(); } object Capture(long address,string value) { P.Packet packet;Assert.True(P.Packet.TryDecode(value,"mining",out packet));Sent.Add(new Delivery {Address=address,Packet=packet});return null; } public void Add(string session,long sequence,long address=123,string kind="HELLO",bool reliable=false,long epoch=0,MyIni body=null) { var packet=new P.Packet {From="miner-3",Target="base",Session=session,Seq=sequence,Id=session+"-"+kind+"-"+sequence,Kind=kind,Reliable=reliable,Epoch=epoch,Body=body??new MyIni()}; Incoming.Enqueue(new MyIGCMessage(packet.Encode("mining"),"AMS2/mining",address)); } public void Drain() {Program.Bus.Drain(p=>Received.Add(p));} public long SendAddress() { Program.Bus.Send("COMMAND","miner-3",new MyIni());return Sent.Last().Address; } } [Fact] public void RejectedReplacementKeepsTheOriginalAddressAndDoesNotAcknowledgeIt() { var r=new Rig();int decisions=0;r.Program.Bus.AllowRebind=p=>{decisions++;return false;}; r.Add("new",1,456,reliable:true);r.Drain(); Assert.Equal(1,decisions);Assert.Single(r.Received);Assert.Empty(r.Sent);Assert.Equal(123,r.SendAddress()); r.Add("old",2);r.Drain();Assert.Equal(2,r.Received.Count); } [Fact] public void ApprovedReplacementMigratesProtectionDropsOldRetriesAndDeliversOnce() { var r=new Rig();r.Program.Bus.ProtectPeer("miner-3",123); r.Program.Bus.Send("JOB","miner-3",new MyIni(),true); r.Program.Bus.Send("JOB","unrelated",new MyIni(),true,789); int decisions=0;r.Program.Bus.AllowRebind=p=>{decisions++;return p.Source==456;};r.Sent.Clear(); r.Add("new",1,456,reliable:true);r.Drain(); Assert.Equal(1,decisions);Assert.Equal(2,r.Received.Count);Assert.Equal(456,r.Received.Last().Source); var ack=Assert.Single(r.Sent);Assert.Equal("ACK",ack.Packet.Kind);Assert.Equal(456,ack.Address); Assert.Equal(1,r.Program.Bus.PendingCount);Assert.Equal(456,r.SendAddress()); r.Add("new",1,456,reliable:true);r.Drain();Assert.Equal(2,r.Received.Count);Assert.Equal(1,decisions); r.Sent.Clear();r.Program.Now=2;r.Program.Bus.Tick(); var retry=Assert.Single(r.Sent);Assert.Equal(789,retry.Address);Assert.Equal("unrelated",retry.Packet.Target); // The new protected address works without another authorization callback. r.Program.Bus.AllowRebind=null;r.Add("new",2,456,kind:"TELEMETRY");r.Drain();Assert.Equal(3,r.Received.Count); } [Fact] public void RetiredSessionReplayCannotAskToMoveTheAddressBack() { var r=new Rig();int decisions=0;r.Program.Bus.AllowRebind=p=>{decisions++;return true;}; r.Add("new",1,456);r.Drain();Assert.Equal(1,decisions); r.Add("old",999,123,reliable:true);r.Drain(); Assert.Equal(1,decisions);Assert.Equal(2,r.Received.Count);Assert.Empty(r.Sent);Assert.Equal(456,r.SendAddress()); } [Fact] public void CurrentSessionReplayAndWrongEpochNeverReachTheAuthorizationCallback() { var r=new Rig();int decisions=0;r.Program.Bus.AllowRebind=p=>{decisions++;return true;}; r.Add("old",1,456,reliable:true);r.Add("new",1,456,reliable:true,epoch:1);r.Drain(); Assert.Equal(0,decisions);Assert.Single(r.Received);Assert.Empty(r.Sent);Assert.Equal(123,r.SendAddress()); } [Theory] [InlineData("ACK")] [InlineData("RESULT")] [InlineData("COMMAND")] public void NonRegistrationPacketsCannotAuthorizeAddressTakeover(string kind) { var r=new Rig();int decisions=0;r.Program.Bus.AllowRebind=p=>{decisions++;return true;}; r.Add("new",1,456,kind,true);r.Drain(); Assert.Equal(0,decisions);Assert.Single(r.Received);Assert.Empty(r.Sent);Assert.Equal(123,r.SendAddress()); } [Theory] [InlineData("HELLO")] [InlineData("TELEMETRY")] [InlineData("DOCK_REQUEST")] [InlineData("DEPART_REQUEST")] public void OnlyAnExplicitApprovalAllowsFreshRegistrationKindsToRebind(string kind) { var r=new Rig();r.Program.Bus.AllowRebind=p=>p.Kind==kind&&p.Source==456; r.Add("new",1,456,kind);r.Drain();Assert.Equal(2,r.Received.Count);Assert.Equal(456,r.SendAddress()); } [Fact] public void RejectedProtectedIdentityRemainsProtectedAfterItsAddressSlotWasForgotten() { var r=new Rig();r.Program.Bus.ProtectPeer("miner-3",123);r.Program.Bus.ForgetPeer("miner-3"); int decisions=0;r.Program.Bus.AllowRebind=p=>{decisions++;return false;}; r.Add("new",1,456,reliable:true);r.Drain();Assert.Equal(1,decisions);Assert.Single(r.Received);Assert.Empty(r.Sent); r.Add("old",2,123);r.Drain();Assert.Equal(2,r.Received.Count);Assert.Equal(123,r.SendAddress()); } [Fact] public void InvalidAuthorizationPayloadCannotPartiallyCancelOrMoveTheBinding() { var r=new Rig();r.Program.Bus.Send("JOB","miner-3",new MyIni(),true); r.Program.Bus.AllowRebind=p=>{throw new ArgumentException("invalid replacement telemetry");}; r.Add("new",1,456);r.Drain(); Assert.Single(r.Received);Assert.Equal(1,r.Program.Bus.PendingCount);Assert.Equal(123,r.SendAddress()); } } }