[M5] tools+fixtures: F28 canonical-XML signing target captured from .NET
Adds `MxAsbClient.Probe --dump-signed-xml` flag that builds five ConnectedRequest shapes (AuthenticateMe, Disconnect, KeepAlive, RegisterItemsRequest, UnregisterItemsRequest) with deterministic field values and prints `AsbSerialization.ToXml(...)` output. The output is exactly what `AsbSystemAuthenticator.Sign` HMACs (`AsbSystemAuthenticator.cs:79`), so the Rust port's canonical-XML emitter must produce byte-identical bytes for HMAC parity. Captured fixtures land under `rust/crates/mxaccess-asb/tests/fixtures/signed-xml/`: - `authenticate-me.xml` — 1000 bytes - `disconnect.xml` — 980 bytes - `keep-alive.xml` — 705 bytes - `register-items.xml` — 1068 bytes - `unregister-items.xml` — 1072 bytes Plus a `README.md` documenting 10 inferred XmlSerializer rules (element name = class name not WrapperName, field order = declaration order not [MessageBodyMember.Order], `[XmlType.Namespace]` on field type causes per-child xmlns redeclaration on the children not the wrapper, `*Specified` pattern controls Xxx emission, CRLF + 2-space indent + utf-16 declaration but UTF-8 bytes fed to HMAC). `.gitattributes` marks the XML fixtures as binary (`*.xml -text`) so neither `core.autocrlf` nor `text` filters can rewrite the byte content — CRLF is part of the canonical form and must survive round-trip through Git untouched. `MxAsbClient.csproj` gains `<InternalsVisibleTo Include="MxAsbClient .Probe" />` so the probe can reach the internal `AsbSerialization` helper without making it public. Workspace: 702 tests pass (no Rust changes — fixtures only). F28 follow-up updated with the captured fixtures + the inferred rules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,93 @@ if (args.Any(arg => arg.Equals("--dump-register-payload", StringComparison.Ordin
|
||||
return;
|
||||
}
|
||||
|
||||
// `--dump-signed-xml` produces deterministic .NET `XmlSerializer` output
|
||||
// for each ConnectedRequest type that goes through `AsbSystemAuthenticator
|
||||
// .Sign` (`AsbSystemAuthenticator.cs:79`). The output is exactly what
|
||||
// the .NET HMAC computation runs over, so the Rust port's canonical-XML
|
||||
// emitter (F28) needs to produce byte-identical bytes for every type
|
||||
// listed here. Connection IDs, MACs, IVs, and message numbers are pinned
|
||||
// to deterministic values so the dump is reproducible.
|
||||
if (args.Any(arg => arg.Equals("--dump-signed-xml", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
Guid connectionId = Guid.Parse("8cba964a-74c1-ef74-f6aa-761b3540191b");
|
||||
byte[] mac = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw==");
|
||||
byte[] sigIv = Convert.FromBase64String("EBESExQVFhcYGRobHB0eHw==");
|
||||
|
||||
void Dump(string label, object request)
|
||||
{
|
||||
string xml = AsbSerialization.ToXml(request);
|
||||
byte[] xmlBytes = System.Text.Encoding.UTF8.GetBytes(xml);
|
||||
Console.WriteLine($"--- {label} ({xmlBytes.Length} UTF-8 bytes) ---");
|
||||
Console.WriteLine(xml);
|
||||
Console.WriteLine($"--- {label} (base64) ---");
|
||||
Console.WriteLine(Convert.ToBase64String(xmlBytes));
|
||||
}
|
||||
|
||||
ConnectionValidator validator = new()
|
||||
{
|
||||
ConnectionId = connectionId,
|
||||
MessageNumber = 42,
|
||||
MessageAuthenticationCode = mac,
|
||||
SignatureInitializationVector = sigIv,
|
||||
};
|
||||
|
||||
AuthenticateMe authMe = new()
|
||||
{
|
||||
ConnectionValidator = validator,
|
||||
ConsumerAuthenticationData = new AuthenticationData
|
||||
{
|
||||
Data = Convert.FromBase64String("ZGV0ZXJtaW5pc3RpYy1jaXBoZXJ0ZXh0LWJ5dGVz"),
|
||||
InitializationVector = Convert.FromBase64String("MDEyMzQ1Njc4OWFiY2RlZg=="),
|
||||
},
|
||||
};
|
||||
Dump("AuthenticateMe", authMe);
|
||||
|
||||
Disconnect disconnect = new()
|
||||
{
|
||||
ConnectionValidator = validator,
|
||||
ConsumerAuthenticationData = new AuthenticationData
|
||||
{
|
||||
Data = Convert.FromBase64String("ZGlzY29ubmVjdC1jaXBoZXJ0ZXh0"),
|
||||
InitializationVector = Convert.FromBase64String("MDEyMzQ1Njc4OWFiY2RlZg=="),
|
||||
},
|
||||
};
|
||||
Dump("Disconnect", disconnect);
|
||||
|
||||
KeepAlive keepAlive = new() { ConnectionValidator = validator };
|
||||
Dump("KeepAlive", keepAlive);
|
||||
|
||||
RegisterItemsRequest registerDump = new()
|
||||
{
|
||||
ConnectionValidator = validator,
|
||||
Items = [new ItemIdentity
|
||||
{
|
||||
Type = (ushort)ItemIdentityType.Name,
|
||||
ReferenceType = (ushort)ItemReferenceType.Absolute,
|
||||
Name = "TestChildObject.TestInt",
|
||||
ContextName = string.Empty,
|
||||
}],
|
||||
RequireId = true,
|
||||
RegisterOnly = false,
|
||||
};
|
||||
Dump("RegisterItemsRequest", registerDump);
|
||||
|
||||
UnregisterItemsRequest unregisterDump = new()
|
||||
{
|
||||
ConnectionValidator = validator,
|
||||
Items = [new ItemIdentity
|
||||
{
|
||||
Type = (ushort)ItemIdentityType.Id,
|
||||
ReferenceType = (ushort)ItemReferenceType.Absolute,
|
||||
Id = 0xCAFE_BABE_DEAD_BEEFul,
|
||||
IdSpecified = true,
|
||||
}],
|
||||
};
|
||||
Dump("UnregisterItemsRequest", unregisterDump);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (probeConnectFailure)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -14,4 +14,9 @@
|
||||
<PackageReference Include="System.ServiceModel.Primitives" Version="10.0.652802" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MxAsbClient.Probe" />
|
||||
<InternalsVisibleTo Include="MxAsbClient.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user