CultLib/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs
Meta Vens 1590145a81 Pin the TypeScript-signed direction of the authority parity check
`contracts/cultmesh/authority-route-vectors.json` proves C# bytes verify
in TypeScript. Nothing in the repo proved the reverse; Soul's probe did it
once in a scratch directory. It is now a committed check in the same shape,
mirrored:

- `scripts/sign-cultmesh-authority-vectors.mjs` builds the route and
  session transcripts with the shared module, signs them with fixed
  TypeScript-owned test keys over awkward inputs (a quic scheme with a
  query, three unsorted protocol ids, non-ASCII generation text, priority
  13), self-verifies, and writes
  `contracts/cultmesh/authority-route-vectors.ts-signed.json`.
- `CultMeshAuthorityProofTests.AuthorityRouteVectorsSignedByTypeScriptVerifyHere`
  reads that file with keys taken from the file, asserts the protocol ids
  are unsorted, that `Validate` and `VerifySessionProof` accept, and that
  a priority-tampered route and a replayed nonce are refused.
- The TypeScript vector test runs over both files, so a transcript change
  on the TypeScript side fails here before C# could trust a stale file.

Checked negatively: one base64 character changed in the committed
signature fails the C# test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:27:06 +02:00

613 lines
30 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using GameCult.Networking;
using NUnit.Framework;
namespace GameCult.Mesh.Tests;
public sealed class CultMeshAuthorityProofTests
{
private static readonly DateTimeOffset Now = DateTimeOffset.FromUnixTimeMilliseconds(1_800_000_000_000);
[Test]
public void SignedRouteBindsEveryAuthorityFieldToConsumerTrust()
{
using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var route = SignedRoute(odin, provider);
var trust = Trust(odin);
trust.Validate("aetheria", route, Now);
var wireRoundTrip = new CultMeshVerseDescriptor(
"aetheria",
"Aetheria",
CultMeshVerseAuthorityModel.OperatorCluster,
new CultMeshVerseCompatibility("cultmesh.v0", "rules"),
authorityRoutes: new[] { route })
.ToMessage()
.ToVerseDescriptor()
.AuthorityRoutes[0];
trust.Validate("aetheria", wireRoundTrip, Now);
wireRoundTrip.Certificate!.ProviderKey.KeyId.Should().Be("provider-1");
Action mutatedEndpoint = () => trust.Validate("aetheria", new CultMeshAuthorityRoute(
route.AuthorityRuntimeId,
"wss://evil.example/mesh",
route.ProtocolIds,
route.Priority,
route.Generation,
route.Certificate), Now);
mutatedEndpoint.Should().Throw<CultMeshSessionException>()
.Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authentication);
}
[Test]
public void RemoteTrustRejectsUnknownExpiredAndUnsignedRoutes()
{
using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var stranger = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var route = SignedRoute(odin, provider);
Action unknownRoot = () => Trust(stranger).Validate("aetheria", route, Now);
Action expired = () => Trust(odin).Validate("aetheria", route, Now.AddHours(2));
Action unsigned = () => Trust(odin).Validate("aetheria", new CultMeshAuthorityRoute(
"aetheria-daemon", "wss://provider.example/mesh",
new[] { CultMeshProtocols.Documents.Value }, generation: "generation-1"), Now);
unknownRoot.Should().Throw<CultMeshSessionException>();
expired.Should().Throw<CultMeshSessionException>();
unsigned.Should().Throw<CultMeshSessionException>();
}
[Test]
public void UnsignedRouteRequiresExplicitLoopbackDevelopmentPolicy()
{
var local = new CultMeshAuthorityRoute(
"aetheria-daemon", "rudp://127.0.0.1:3076",
new[] { CultMeshProtocols.Documents.Value }, generation: "local");
var remote = new CultMeshAuthorityRoute(
"aetheria-daemon", "rudp://192.0.2.10:3076",
new[] { CultMeshProtocols.Documents.Value }, generation: "remote");
var policy = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment);
policy.Validate("aetheria", local, Now);
Action remoteAttempt = () => policy.Validate("aetheria", remote, Now);
remoteAttempt.Should().Throw<CultMeshSessionException>();
}
[Test]
public void ProviderProofRejectsCredentialFreeEchoAndNonceReplay()
{
using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var route = SignedRoute(odin, provider);
var trust = Trust(odin);
var signer = new CultMeshSessionProofSigner(route, provider);
var request = Request("nonce-a");
var signed = Accepted(request, signer.Sign(request));
CultMeshAuthorityProof.VerifySessionProof(request, signed, "aetheria", route, trust, Now).Should().BeTrue();
var echo = Accepted(request, string.Empty);
CultMeshAuthorityProof.VerifySessionProof(request, echo, "aetheria", route, trust, Now).Should().BeFalse();
var replayedRequest = Request("nonce-b");
CultMeshAuthorityProof.VerifySessionProof(replayedRequest, signed, "aetheria", route, trust, Now).Should().BeFalse();
}
[Test]
public async Task SessionManagerRejectsPeerThatOnlyEchoesCertifiedIdentityStrings()
{
using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var now = DateTimeOffset.UtcNow;
var route = CultMeshAuthorityProof.CreateSignedRoute(
"aetheria", "aetheria-daemon", "wss://provider.example/mesh",
new[] { CultMeshProtocols.Documents.Value }, 0, "generation-live",
CultMeshEcdsaP256PublicKey.From("provider-live", provider), "odin-live",
now.AddMinutes(-1), now.AddMinutes(5), odin);
var descriptor = new CultMeshVerseDescriptor(
"aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster,
new CultMeshVerseCompatibility("cultmesh.v0", "rules"),
authorityRoutes: new[] { route });
using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) });
using var manager = new CultMeshSessionManager(
discovery,
new[] { new EchoConnector() },
new CultMeshSessionManagerOptions
{
Trust = new CultMeshAuthorityTrustPolicy(
CultMeshAuthorityTrustMode.AuthenticatedRemote,
new[] { CultMeshEcdsaP256PublicKey.From("odin-live", odin) })
});
Func<Task> connect = async () => await manager.ConnectAsync(
new CultMeshSessionTarget("aetheria", "aetheria-daemon"),
CultMeshProtocols.Documents);
(await connect.Should().ThrowAsync<CultMeshSessionException>())
.Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority);
}
[Test]
public async Task SessionManagerRejectsOnlyThePoisonedRouteAndUsesCertifiedFallback()
{
using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var now = DateTimeOffset.UtcNow;
var valid = CultMeshAuthorityProof.CreateSignedRoute(
"aetheria", "aetheria-daemon", "wss://provider.example/mesh",
new[] { CultMeshProtocols.Documents.Value }, 10, "generation-valid",
CultMeshEcdsaP256PublicKey.From("provider-valid", provider), "odin-valid",
now.AddMinutes(-1), now.AddMinutes(5), odin);
var poisoned = new CultMeshAuthorityRoute(
"aetheria-daemon", "wss://poison.example/mesh",
new[] { CultMeshProtocols.Documents.Value }, priority: 0, generation: "generation-poisoned");
var descriptor = new CultMeshVerseDescriptor(
"aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster,
new CultMeshVerseCompatibility("cultmesh.v0", "rules"),
authorityRoutes: new[] { poisoned, valid });
using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) });
var signer = new CultMeshSessionProofSigner(valid, provider);
var connector = new SigningConnector(signer);
using var manager = new CultMeshSessionManager(
discovery,
new[] { connector },
new CultMeshSessionManagerOptions
{
Trust = new CultMeshAuthorityTrustPolicy(
CultMeshAuthorityTrustMode.AuthenticatedRemote,
new[] { CultMeshEcdsaP256PublicKey.From("odin-valid", odin) })
});
var session = await manager.ConnectAsync(
new CultMeshSessionTarget("aetheria", "aetheria-daemon"),
CultMeshProtocols.Documents);
session.State.Path!.Endpoint.Should().Be(valid.Endpoint);
connector.Attempts.Should().Equal(valid.Endpoint);
}
[Test]
public async Task AuthenticatedRemoteContentUsesCertifiedHttpsAndStreamsTheAdvertisedChunk()
{
using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var payload = Encoding.UTF8.GetBytes("certified-content");
var route = CultMeshAuthorityProof.CreateSignedRoute(
"aetheria", "aetheria-daemon", "https://provider.example/cultmesh/content",
new[] { CultMeshProtocols.Content.Value }, 0, "content-generation",
CultMeshEcdsaP256PublicKey.From("provider-content", provider), "odin-content",
Now.AddMinutes(-1), Now.AddMinutes(5), odin);
var descriptor = new CultMeshVerseDescriptor(
"aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster,
new CultMeshVerseCompatibility("cultmesh.v0", "rules"),
authorityRoutes: new[] { route });
using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) });
var handler = new FixedContentHandler(payload);
using var manager = new CultMeshSessionManager(
discovery,
Array.Empty<ICultMeshTransportConnector>(),
new ICultMeshContentTransportConnector[]
{
new CultMeshHttpsContentTransportConnector(() => handler)
},
new CultMeshSessionManagerOptions
{
Trust = new CultMeshAuthorityTrustPolicy(
CultMeshAuthorityTrustMode.AuthenticatedRemote,
new[] { CultMeshEcdsaP256PublicKey.From("odin-content", odin) }),
Clock = new FixedClock(Now)
});
var session = await manager.ConnectContentAsync(
new CultMeshSessionTarget("aetheria", "aetheria-daemon"));
using var destination = new MemoryStream();
await session.CopyChunkToAsync(new CultMeshCdnChunkRef
{
ChunkHash = Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(),
RecordKey = "cdn:chunk:certified",
SizeBytes = payload.Length
}, destination);
destination.ToArray().Should().Equal(payload);
handler.RequestUri.Should().NotBeNull();
handler.RequestUri!.Scheme.Should().Be(Uri.UriSchemeHttps);
handler.RequestUri.Query.Should().Contain("chunkHash=").And.Contain("recordKey=");
}
// Shared-vector check with `packages/cultnet-ts/src/cultmesh-authority.ts`.
// The C# reference signs a route and a session proof with fixed keys and
// writes them when CULTMESH_WRITE_VECTORS=1; otherwise it asserts the
// committed file still verifies here. The TypeScript test verifies the same
// file, so both runtimes are pinned to the same transcript bytes.
[Test]
public void AuthorityRouteVectorsAreSharedWithTypeScript()
{
var path = Path.Combine(RepoRoot(), "contracts", "cultmesh", "authority-route-vectors.json");
using var odin = ECDsa.Create();
odin.ImportFromPem(VectorOdinPrivateKeyPem);
using var provider = ECDsa.Create();
provider.ImportFromPem(VectorProviderPrivateKeyPem);
var odinPublic = CultMeshEcdsaP256PublicKey.From("odin-vector", odin);
var providerPublic = CultMeshEcdsaP256PublicKey.From("provider-vector", provider);
var request = new GameCult.Networking.CultMeshSessionOpenMessage
{
MessageId = "vector-message-1",
SourceRuntimeId = "vector-consumer",
VerseId = "aetheria",
AuthorityRuntimeId = "aetheria-daemon",
ProtocolId = CultMeshProtocols.Documents.Value,
RouteGeneration = "vector-generation-1",
ClientNonce = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes("vector-nonce")))
};
if (Environment.GetEnvironmentVariable("CULTMESH_WRITE_VECTORS") == "1")
{
var signedRoute = CultMeshAuthorityProof.CreateSignedRoute(
"aetheria", "aetheria-daemon", "wss://provider.example/mesh",
new[] { CultMeshProtocols.Documents.Value, CultMeshProtocols.Content.Value }, 7, "vector-generation-1",
providerPublic, odinPublic.KeyId, Now.AddMinutes(-1), Now.AddHours(1), odin);
var signer = new CultMeshSessionProofSigner(signedRoute, provider);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, JsonSerializer.Serialize(new
{
nowUnixMilliseconds = Now.ToUnixTimeMilliseconds(),
odinRoot = new { keyId = odinPublic.KeyId, x = odinPublic.X, y = odinPublic.Y },
route = new
{
verseId = "aetheria",
authorityRuntimeId = signedRoute.AuthorityRuntimeId,
endpoint = signedRoute.Endpoint,
protocolIds = signedRoute.ProtocolIds,
priority = signedRoute.Priority,
generation = signedRoute.Generation,
certificate = new
{
providerKey = new { keyId = providerPublic.KeyId, x = providerPublic.X, y = providerPublic.Y },
odinKeyId = signedRoute.Certificate!.OdinKeyId,
issuedAtUnixMilliseconds = signedRoute.Certificate.IssuedAtUnixMilliseconds,
expiresAtUnixMilliseconds = signedRoute.Certificate.ExpiresAtUnixMilliseconds,
signature = signedRoute.Certificate.Signature
}
},
sessionProof = new
{
request = new
{
schemaVersion = request.SchemaVersion,
messageId = request.MessageId,
sourceRuntimeId = request.SourceRuntimeId,
verseId = request.VerseId,
authorityRuntimeId = request.AuthorityRuntimeId,
protocolId = request.ProtocolId,
routeGeneration = request.RouteGeneration,
clientNonce = request.ClientNonce
},
providerKeyId = signer.ProviderKeyId,
providerSignature = signer.Sign(request)
}
}, new JsonSerializerOptions
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}) + "\n");
}
using var vectors = JsonDocument.Parse(File.ReadAllText(path));
var rootElement = vectors.RootElement;
var routeElement = rootElement.GetProperty("route");
var certificateElement = routeElement.GetProperty("certificate");
var route = new CultMeshAuthorityRoute(
routeElement.GetProperty("authorityRuntimeId").GetString()!,
routeElement.GetProperty("endpoint").GetString()!,
routeElement.GetProperty("protocolIds").EnumerateArray().Select(value => value.GetString()!).ToArray(),
routeElement.GetProperty("priority").GetInt32(),
routeElement.GetProperty("generation").GetString(),
new CultMeshRouteCertificate(
providerPublic,
certificateElement.GetProperty("odinKeyId").GetString()!,
certificateElement.GetProperty("issuedAtUnixMilliseconds").GetInt64(),
certificateElement.GetProperty("expiresAtUnixMilliseconds").GetInt64(),
certificateElement.GetProperty("signature").GetString()!));
var trust = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { odinPublic });
var now = DateTimeOffset.FromUnixTimeMilliseconds(rootElement.GetProperty("nowUnixMilliseconds").GetInt64());
var verseId = routeElement.GetProperty("verseId").GetString()!;
trust.Validate(verseId, route, now);
var proofElement = rootElement.GetProperty("sessionProof");
var accepted = Accepted(request, proofElement.GetProperty("providerSignature").GetString()!);
accepted.ProviderKeyId = proofElement.GetProperty("providerKeyId").GetString()!;
CultMeshAuthorityProof.VerifySessionProof(request, accepted, verseId, route, trust, now).Should().BeTrue();
}
// The other direction. `scripts/sign-cultmesh-authority-vectors.mjs` builds
// the transcripts with the TypeScript module and signs them with
// TypeScript-owned keys over awkward inputs (a quic scheme, unsorted
// protocol ids, non-ASCII generation text, a non-default priority). Only
// the reference implementation judges the file here; the keys come from
// the file, never from this side.
[Test]
public void AuthorityRouteVectorsSignedByTypeScriptVerifyHere()
{
var path = Path.Combine(RepoRoot(), "contracts", "cultmesh", "authority-route-vectors.ts-signed.json");
using var vectors = JsonDocument.Parse(File.ReadAllText(path));
var rootElement = vectors.RootElement;
var routeElement = rootElement.GetProperty("route");
var certificateElement = routeElement.GetProperty("certificate");
var odinRoot = PublicKey(rootElement.GetProperty("odinRoot"));
var providerKey = PublicKey(certificateElement.GetProperty("providerKey"));
var certificate = new CultMeshRouteCertificate(
providerKey,
certificateElement.GetProperty("odinKeyId").GetString()!,
certificateElement.GetProperty("issuedAtUnixMilliseconds").GetInt64(),
certificateElement.GetProperty("expiresAtUnixMilliseconds").GetInt64(),
certificateElement.GetProperty("signature").GetString()!);
var protocolIds = routeElement.GetProperty("protocolIds").EnumerateArray().Select(value => value.GetString()!).ToArray();
var priority = routeElement.GetProperty("priority").GetInt32();
CultMeshAuthorityRoute Route(int routePriority) => new(
routeElement.GetProperty("authorityRuntimeId").GetString()!,
routeElement.GetProperty("endpoint").GetString()!,
protocolIds,
routePriority,
routeElement.GetProperty("generation").GetString(),
certificate);
var route = Route(priority);
var trust = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { odinRoot });
var now = DateTimeOffset.FromUnixTimeMilliseconds(rootElement.GetProperty("nowUnixMilliseconds").GetInt64());
var verseId = routeElement.GetProperty("verseId").GetString()!;
protocolIds.Should().NotBeInAscendingOrder(StringComparer.Ordinal, "the vector must exercise the sort");
trust.Validate(verseId, route, now);
Action tampered = () => trust.Validate(verseId, Route(priority + 1), now);
tampered.Should().Throw<CultMeshSessionException>()
.Which.Failure.Message.Should().Be("The Odin route certificate signature is invalid.");
var proofElement = rootElement.GetProperty("sessionProof");
var requestElement = proofElement.GetProperty("request");
var request = new GameCult.Networking.CultMeshSessionOpenMessage
{
MessageId = requestElement.GetProperty("messageId").GetString(),
SourceRuntimeId = requestElement.GetProperty("sourceRuntimeId").GetString(),
VerseId = requestElement.GetProperty("verseId").GetString(),
AuthorityRuntimeId = requestElement.GetProperty("authorityRuntimeId").GetString(),
ProtocolId = requestElement.GetProperty("protocolId").GetString(),
RouteGeneration = requestElement.GetProperty("routeGeneration").GetString(),
ClientNonce = requestElement.GetProperty("clientNonce").GetString()
};
var accepted = Accepted(request, proofElement.GetProperty("providerSignature").GetString()!);
accepted.ProviderKeyId = proofElement.GetProperty("providerKeyId").GetString()!;
CultMeshAuthorityProof.VerifySessionProof(request, accepted, verseId, route, trust, now).Should().BeTrue();
var replayed = new GameCult.Networking.CultMeshSessionOpenMessage
{
MessageId = request.MessageId,
SourceRuntimeId = request.SourceRuntimeId,
VerseId = request.VerseId,
AuthorityRuntimeId = request.AuthorityRuntimeId,
ProtocolId = request.ProtocolId,
RouteGeneration = request.RouteGeneration,
ClientNonce = "replayed"
};
var replayedAccepted = Accepted(replayed, accepted.ProviderSignature!);
replayedAccepted.ProviderKeyId = accepted.ProviderKeyId;
CultMeshAuthorityProof.VerifySessionProof(replayed, replayedAccepted, verseId, route, trust, now).Should().BeFalse();
}
private static CultMeshEcdsaP256PublicKey PublicKey(JsonElement element) => new(
element.GetProperty("keyId").GetString()!,
element.GetProperty("x").GetString()!,
element.GetProperty("y").GetString()!);
private static string RepoRoot()
{
var directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
while (directory != null && !File.Exists(Path.Combine(directory.FullName, "CultLib.sln")))
directory = directory.Parent;
return directory?.FullName ?? throw new InvalidOperationException("CultLib.sln was not found above the test directory.");
}
// Fixed test keys. They sign only the committed vector and prove nothing else.
private const string VectorOdinPrivateKeyPem = @"-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgHY0//QOheSwARzXE
iX/nGqSD7XFAy530QQqKeXPNxMChRANCAASyEksa2ZGtboPztVAq/TFhB6Qsh8SR
43t1VrDCmYgpY4kehsd2lQoIc0sqO06l6q/td/ey+9ygzWbszdnGeeSB
-----END PRIVATE KEY-----";
private const string VectorProviderPrivateKeyPem = @"-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVC+9X4t+1HAiIxP4
TEuAuBAY+3ryKK8SaitB0TqsPQqhRANCAATpci+DHdbWruUkURXTJht4pY0WCyyr
ngEcG6H2ALBwf6ItRYiq2rni4nPlXpszsdfDDcBurYZmTFFnqjcOrzTp
-----END PRIVATE KEY-----";
private static CultMeshAuthorityRoute SignedRoute(ECDsa odin, ECDsa provider) =>
CultMeshAuthorityProof.CreateSignedRoute(
"aetheria",
"aetheria-daemon",
"wss://provider.example/mesh",
new[] { CultMeshProtocols.Documents.Value },
0,
"generation-1",
CultMeshEcdsaP256PublicKey.From("provider-1", provider),
"odin-root-1",
Now.AddMinutes(-1),
Now.AddHours(1),
odin);
private static CultMeshAuthorityTrustPolicy Trust(ECDsa odin) => new(
CultMeshAuthorityTrustMode.AuthenticatedRemote,
new[] { CultMeshEcdsaP256PublicKey.From("odin-root-1", odin) });
private static GameCult.Networking.CultMeshSessionOpenMessage Request(string nonceLabel)
{
using var hash = SHA256.Create();
return new GameCult.Networking.CultMeshSessionOpenMessage
{
MessageId = "message-1",
SourceRuntimeId = "browser-1",
VerseId = "aetheria",
AuthorityRuntimeId = "aetheria-daemon",
ProtocolId = CultMeshProtocols.Documents.Value,
RouteGeneration = "generation-1",
ClientNonce = Convert.ToBase64String(hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(nonceLabel)))
};
}
private static GameCult.Networking.CultMeshSessionAcceptedMessage Accepted(
GameCult.Networking.CultMeshSessionOpenMessage request,
string signature) => new()
{
MessageId = request.MessageId,
Accepted = true,
VerseId = request.VerseId,
AuthorityRuntimeId = request.AuthorityRuntimeId,
ProtocolId = request.ProtocolId,
RouteGeneration = request.RouteGeneration,
ClientNonce = request.ClientNonce,
ProviderKeyId = "provider-1",
ProviderSignature = signature
};
private sealed class SignedRouteSource : ICultMeshLookupSource
{
private readonly CultMeshVerseDescriptor _descriptor;
public SignedRouteSource(CultMeshVerseDescriptor descriptor) => _descriptor = descriptor;
public string SourceId => "signed-odin";
public Task<IReadOnlyList<CultMeshDiscoveryObservation>> LookupAsync(
CultMeshDiscoveryQuery query,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlyList<CultMeshDiscoveryObservation>>(new[]
{
new CultMeshDiscoveryObservation(
_descriptor, SourceId, DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddMinutes(1), CultMeshDiscoveryTrust.Signed)
});
}
private sealed class EchoConnector : ICultMeshTransportConnector
{
public string ConnectorId => "credential-free-echo";
public int Priority => 0;
public bool CanConnect(CultMeshTransportCandidate candidate) => true;
public Task<ICultNetSchemaClient> ConnectAsync(
CultMeshTransportCandidate candidate,
CultMeshProtocolId protocol,
CancellationToken cancellationToken = default) =>
Task.FromResult<ICultNetSchemaClient>(new EchoClient());
}
private sealed class EchoClient : ICultNetSchemaClient
{
private Action<CultMeshSessionAcceptedMessage>? _accepted;
public bool Connected => true;
public void Connect(string host, int port) { }
public void SendCultNet<T>(T message) where T : ICultNetSchemaMessage
{
if (message is not CultMeshSessionOpenMessage request) return;
_accepted?.Invoke(new CultMeshSessionAcceptedMessage
{
MessageId = request.MessageId,
Accepted = true,
VerseId = request.VerseId,
AuthorityRuntimeId = request.AuthorityRuntimeId,
ProtocolId = request.ProtocolId,
RouteGeneration = request.RouteGeneration,
ClientNonce = request.ClientNonce,
ProviderKeyId = "provider-live",
ProviderSignature = string.Empty
});
}
public void OnCultNet<T>(Action<T> callback) where T : ICultNetSchemaMessage
{
if (typeof(T) == typeof(CultMeshSessionAcceptedMessage))
_accepted = response => callback((T)(object)response);
}
public void Dispose() { }
}
private sealed class SigningConnector : ICultMeshTransportConnector
{
private readonly CultMeshSessionProofSigner _signer;
public SigningConnector(CultMeshSessionProofSigner signer) => _signer = signer;
public List<string> Attempts { get; } = new();
public string ConnectorId => "signed-provider";
public int Priority => 0;
public bool CanConnect(CultMeshTransportCandidate candidate) => true;
public Task<ICultNetSchemaClient> ConnectAsync(
CultMeshTransportCandidate candidate,
CultMeshProtocolId protocol,
CancellationToken cancellationToken = default)
{
Attempts.Add(candidate.Endpoint);
return Task.FromResult<ICultNetSchemaClient>(new SigningClient(_signer));
}
}
private sealed class SigningClient : ICultNetSchemaClient
{
private readonly CultMeshSessionProofSigner _signer;
private Action<CultMeshSessionAcceptedMessage>? _accepted;
public SigningClient(CultMeshSessionProofSigner signer) => _signer = signer;
public bool Connected => true;
public void Connect(string host, int port) { }
public void SendCultNet<T>(T message) where T : ICultNetSchemaMessage
{
if (message is not CultMeshSessionOpenMessage request) return;
_accepted?.Invoke(new CultMeshSessionAcceptedMessage
{
MessageId = request.MessageId,
Accepted = true,
VerseId = request.VerseId,
AuthorityRuntimeId = request.AuthorityRuntimeId,
ProtocolId = request.ProtocolId,
RouteGeneration = request.RouteGeneration,
ClientNonce = request.ClientNonce,
ProviderKeyId = _signer.ProviderKeyId,
ProviderSignature = _signer.Sign(request)
});
}
public void OnCultNet<T>(Action<T> callback) where T : ICultNetSchemaMessage
{
if (typeof(T) == typeof(CultMeshSessionAcceptedMessage))
_accepted = response => callback((T)(object)response);
}
public void Dispose() { }
}
private sealed class FixedContentHandler : HttpMessageHandler
{
private readonly byte[] _payload;
public FixedContentHandler(byte[] payload) => _payload = payload;
public Uri? RequestUri { get; private set; }
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
RequestUri = request.RequestUri;
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(_payload)
});
}
}
private sealed class FixedClock : ICultMeshClock
{
public FixedClock(DateTimeOffset now) => UtcNow = now;
public DateTimeOffset UtcNow { get; }
public Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken = default) =>
Task.Delay(delay, cancellationToken);
}
}