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<bool> ReadProof(Action<JsonObject>? 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;
}));
}
}
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<bool> ReadProof(Action<JsonObject>? 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;
}));
}
}