using System.IO.Pipes; using System.Text.Json; using System.Text.Json.Nodes; using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class NativeCruiseThermalControlTests { private const string Hash="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; private static async Task ReadProof(Action? alter=null,int heatAgeMilliseconds=0) { var pid=Random.Shared.Next(200000000,290000000);using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var now=DateTimeOffset.UtcNow; var result=JsonSerializer.SerializeToNode(new{schema=1,processId=pid,service=NativeCruiseControlChannel.Service,gameSha256=Hash, enabled=true,owningThread=8,overchargeControl=true,originalFuelRangeObservations=true,nativeStopReceipts=true,expiringOriginalControl=true, componentThermalModel=true,controlTelemetrySchema=1,sampleReady=true, sample=new{verified=true,entityId="12",referenceWorldAddress="24",observedAtUnixMilliseconds=now.ToUnixTimeMilliseconds(), inSupercruise=true,stableFsd=true,rawFsdState=2,state=3,nativeCanEngage=false,heatPercent=95, thermal=new{verified=true,entityId="12",referenceWorldAddress="24",sourceObservedAtUnixMilliseconds=now.ToUnixTimeMilliseconds(), heatObservedAtUnixMilliseconds=now.ToUnixTimeMilliseconds()-heatAgeMilliseconds, heatCapacity=100,heatDisplayScale=1.5,scoInputUpper=30,otherInputUpper=3,coolingPassive=5,coolingMaximum=20,coolingScale=1, coolingHeatLower=.2,coolingHeatUpper=1,responseDelaySeconds=.02}}})!.AsObject(); alter?.Invoke(result); await using var pipe=new NamedPipeServerStream($"XFEEDToolBox.{NativeCruiseControlChannel.Service}.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous); var serve=Task.Run(async()=> { await pipe.WaitForConnectionAsync(timeout.Token);var buffer=new byte[1024];var count=await pipe.ReadAsync(buffer,timeout.Token); using var request=JsonDocument.Parse(buffer.AsMemory(0,count));Check.Equal("read",request.RootElement.GetProperty("op").GetString()!); await pipe.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),timeout.Token);await pipe.WriteAsync(new byte[]{10},timeout.Token); if(OperatingSystem.IsWindows())pipe.WaitForPipeDrain(); }); var channel=new NativeCruiseControlChannel(pid,Hash);var notifications=0;channel.Observed+=_=>notifications++; var frame=new SystemFlightFrame{ShipEntity=12,ReferenceWorld=24,GameSha256=Hash,InGame=true,Mode=LocalFlightMode.Supercruise,HeatPercent=95,ObservedAt=now}; try { var expiry=await channel.ReadThermalControlExpiryAsync(frame,timeout.Token); Check.That(channel.LastObservation is null&&channel.LastStatus is null&¬ifications==0,"The heat-envelope proof must not consume command observations or publish command events."); return expiry is {} end&&end>=DateTimeOffset.UtcNow&&end<=now.AddMilliseconds(200-heatAgeMilliseconds); } finally{await serve;} } [Test]public async Task VerifiedOriginalCoolingStillControlsTheEnvelopeDuringNativeCooldown() =>Check.That(await ReadProof()); [Test]public async Task OtherSourcesThatKeepHeatingCannotBypassTheConventionalHeatLimit() =>Check.That(!await ReadProof(value=>value["sample"]!["thermal"]!["otherInputUpper"]=30)); [Test]public async Task IndependentOlderHeatLimitsTheCoolingProofLease() =>Check.That(await ReadProof(heatAgeMilliseconds:100)); [Test]public async Task UnknownFutureAndExpiredHeatCannotAuthorizeTheCoolingProof() { foreach(var age in new[]{-1000,201})Check.That(!await ReadProof(heatAgeMilliseconds:age)); Check.That(!await ReadProof(value=>value["sample"]!["thermal"]!.AsObject().Remove("heatObservedAtUnixMilliseconds"))); foreach(var unknown in new JsonNode?[]{null,JsonValue.Create("unknown"),JsonValue.Create(0L)}) Check.That(!await ReadProof(value=>value["sample"]!["thermal"]!["heatObservedAtUnixMilliseconds"]=unknown)); } [Test]public async Task UnknownForeignAndStaleSourcesCannotAuthorizeTheEnvelope() { foreach(var field in new[]{"verified","entityId","referenceWorldAddress","sourceObservedAtUnixMilliseconds"}) Check.That(!await ReadProof(value=> { var model=value["sample"]!["thermal"]!; if(field=="verified")model[field]=false; else if(field=="sourceObservedAtUnixMilliseconds")model[field]=DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeMilliseconds(); else model[field]="999"; })); } [Test]public async Task OldModulesStaleFramesAndWrongFlightModesStayWithinTheConventionalLimit() { foreach(var mode in new[]{0,1,2,3,4})Check.That(!await ReadProof(value=> { if(mode==0)value["componentThermalModel"]=false; else if(mode==1)value["service"]="CruiseControl0514f"; else if(mode==2)value["sample"]!["observedAtUnixMilliseconds"]=DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeMilliseconds(); else if(mode==3)value["sample"]!["entityId"]="13"; else value["sample"]!["rawFsdState"]=1; })); } }