Canonicalize Eve invocation digests
This commit is contained in:
parent
4167fc32ca
commit
0d75021c8b
3 changed files with 101 additions and 0 deletions
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace GameCult.Eve.Surface
|
||||
{
|
||||
/// <summary>Canonical digest for one immutable Eve command invocation.</summary>
|
||||
public static class EveCommandInvocationHash
|
||||
{
|
||||
public static string Compute(EveSurfaceCommandRequest request)
|
||||
{
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
var canonical = new StringBuilder();
|
||||
Append(request.Schema);
|
||||
Append(request.ProviderId);
|
||||
Append(request.SurfaceId);
|
||||
Append(request.OperationRecord.OperationId);
|
||||
Append(request.OperationRecord.SchemaId);
|
||||
Append(request.OperationRecord.RouteKind);
|
||||
Append(request.OperationRecord.RouteDescription);
|
||||
Append(request.OperationRecord.IdempotencyKey);
|
||||
foreach (var field in request.PayloadFields.OrderBy(pair => pair.Key, StringComparer.Ordinal))
|
||||
{
|
||||
Append(field.Key);
|
||||
Append(field.Value);
|
||||
}
|
||||
Append(request.IssuedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture));
|
||||
Append(request.ClientId);
|
||||
Append(request.CommandBoundary);
|
||||
Append(request.ReceiptSchema);
|
||||
using var sha256 = SHA256.Create();
|
||||
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(canonical.ToString()));
|
||||
var digest = new StringBuilder(bytes.Length * 2);
|
||||
foreach (var value in bytes) digest.Append(value.ToString("x2", CultureInfo.InvariantCulture));
|
||||
return digest.ToString();
|
||||
|
||||
void Append(string? value)
|
||||
{
|
||||
value ??= "";
|
||||
canonical.Append(value.Length.ToString(CultureInfo.InvariantCulture));
|
||||
canonical.Append(':');
|
||||
canonical.Append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7d5f81f817c44d1ab25d76166354364d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -68,6 +68,46 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
|||
|
||||
private static readonly MessagePackSerializerOptions Options = MessagePackSerializerOptions.Standard;
|
||||
|
||||
[Test]
|
||||
public void CommandInvocationHashBindsEveryImmutableEnvelopeField()
|
||||
{
|
||||
var request = new EveSurfaceCommandRequest(
|
||||
"provider",
|
||||
"surface",
|
||||
new CultMeshOperationInvocationDescriptor(
|
||||
"launch",
|
||||
"launch.v1",
|
||||
new CultMeshRouteHint(CultMeshLocalityKind.Network, "commands"),
|
||||
"command-1"),
|
||||
CultMesh.OperationPayload(("shipId", "ship:one")),
|
||||
DateTimeOffset.Parse("2026-08-19T00:00:00Z"),
|
||||
"pilot:one",
|
||||
"eve:commands",
|
||||
EveCommandReceiptDocument.SchemaId);
|
||||
var same = new EveSurfaceCommandRequest(
|
||||
request.Schema,
|
||||
request.ProviderId,
|
||||
request.SurfaceId,
|
||||
request.OperationRecord,
|
||||
new Dictionary<string, string>(request.PayloadFields, StringComparer.Ordinal),
|
||||
request.IssuedAt,
|
||||
request.ClientId,
|
||||
request.CommandBoundary,
|
||||
request.ReceiptSchema);
|
||||
var changedPayload = new EveSurfaceCommandRequest(
|
||||
request.ProviderId,
|
||||
request.SurfaceId,
|
||||
request.Operation,
|
||||
CultMesh.OperationPayload(("shipId", "ship:two")),
|
||||
request.IssuedAt,
|
||||
request.ClientId,
|
||||
request.CommandBoundary,
|
||||
request.ReceiptSchema);
|
||||
|
||||
Assert.That(EveCommandInvocationHash.Compute(same), Is.EqualTo(EveCommandInvocationHash.Compute(request)));
|
||||
Assert.That(EveCommandInvocationHash.Compute(changedPayload), Is.Not.EqualTo(EveCommandInvocationHash.Compute(request)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LegacySevenFieldSurfaceDocumentDeserializes()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue