Files
TeslaSuite/Console/tests/TeslaConsole.DiffTests/VPodProvisioningTests.cs
T
CydandClaude Fable 5 1daaf4af78 vPOD: virtual launcher — test Site Management / product deploys without a cockpit
vPOD now also impersonates the pod's TeslaLauncher service, so the console's
Manage Site works against it unmodified:

- LauncherRpcServer: ILauncherService over OFB + framed JSON on TCP 53290,
  mirroring TeslaLauncherService (concurrent sessions, out-of-band install
  zip on a second connection, the 99%-not-100 completion convention).
  Packages extract to %LocalAppData%\vPOD\Games; postinstall.bat is logged
  but never executed.
- PodProvisioning: pod side of SecureConfig (RQST beacon, RPLY decrypt, RSA
  session-key exchange), display-only — never touches the NIC/registry. The
  console's Configure flow mints the key exactly as for a real pod; console
  Reconfigure (ClearStore) drops the key and re-enters beacon mode.
- VirtualLauncher: installed-app registry (persisted), simulated launch PIDs,
  volume, install progress; console Shutdown/Restart power-cycles the pod.
- Form gets a Launcher/Site Management column (passcode display, RPC status,
  install progress, app list, Reprovision); Power Off darkens the launcher
  side too; new -nomanage flag disables it.

vPOD references the shared Tesla.Contract/Tesla.SecureConfig projects (server
side of the existing contract only, no new RPCs). Loopback tests drive the
real PodManagerConnection and PodConfigurationServer against the new code
(VPodLauncherServerTests, VPodProvisioningTests) — suite now 99 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 20:45:33 -05:00

84 lines
3.2 KiB
C#

using System;
using System.Net;
using System.Threading;
using Tesla;
using VPod;
using Xunit;
namespace TeslaConsole.DiffTests
{
/// <summary>
/// End-to-end loopback exercise of vPOD's pod-side SecureConfig provisioning
/// against the REAL console-side implementation (Tesla.PodConfigurationServer,
/// the exact code the console runs behind Manage Site's "Configure" button):
/// RQST beacon reception, the AES "RPLY" config broadcast, and the OFB+RSA
/// session-key exchange must all interoperate.
///
/// Uses the protocol's fixed ports (UDP 53291/53292, TCP 53292) and real UDP
/// broadcasts on loopback — do not run while a console or another vPOD is
/// provisioning on this machine.
/// </summary>
public class VPodProvisioningTests
{
[Fact]
public void Console_Provisions_VPod_And_Both_Hold_The_Same_Session_Key()
{
byte[] mac = PodProvisioning.MacForHost(7);
var provisioning = new PodProvisioning(mac);
byte[] beaconMac = null;
string beaconRequestId = null;
using (var beaconSeen = new ManualResetEventSlim())
using (var podProvisioned = new ManualResetEventSlim())
{
byte[] podKey = null;
provisioning.ConfigReceived += _ => { };
provisioning.Provisioned += key =>
{
podKey = key;
podProvisioned.Set();
};
// The console side: listens on UDP 53291 for RQST beacons; the
// delegate fires on vPOD's first beacon (sent at Start).
var consoleServer = new PodConfigurationServer(53291, 53292, (m, id) =>
{
beaconMac = m;
beaconRequestId = id;
beaconSeen.Set();
});
try
{
provisioning.Start();
Assert.True(beaconSeen.Wait(TimeSpan.FromSeconds(15)),
"console never received vPOD's RQST beacon");
Assert.Equal(mac, beaconMac);
Assert.Equal(provisioning.RequestId, beaconRequestId);
// The console side of Configure: broadcast the AES-encrypted
// network config and run the RSA key exchange against the pod.
byte[] consoleKey = consoleServer.SendEncryptionKey(
provisioning.Passphrase,
IPAddress.Loopback,
IPAddress.Parse("255.255.255.0"),
IPAddress.Any,
IPAddress.Any,
"vpod-test",
TimeSpan.FromSeconds(30));
Assert.True(podProvisioned.Wait(TimeSpan.FromSeconds(15)),
"vPOD never completed provisioning");
Assert.NotNull(consoleKey);
Assert.Equal(32, consoleKey.Length);
Assert.Equal(consoleKey, podKey);
}
finally
{
provisioning.Stop();
}
}
}
}
}