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>
4.0 KiB
Signed-request XML fixtures
Canonical XmlSerializer output for every ConnectedRequest shape that
the .NET reference HMACs in AsbSystemAuthenticator.Sign
(src/MxAsbClient/AsbSystemAuthenticator.cs:79). The Rust port's
canonical-XML emitter (F28) must produce these exact UTF-8 bytes for
the HMAC to match the server's recomputation.
Capture procedure
dotnet run --project src\MxAsbClient.Probe -c Release -- --dump-signed-xml > capture.txt
The probe's --dump-signed-xml flag (added 2026-05-05) builds each
shape with deterministic field values and prints the output of
AsbSerialization.ToXml(...) (src/MxAsbClient/AsbSerialization.cs:12).
Pinned values
All shapes use the same ConnectionValidator:
ConnectionId = 8cba964a-74c1-ef74-f6aa-761b3540191bMessageNumber = 42MessageAuthenticationCode = AAECAwQFBgcICQoLDA0ODw==(base64 of bytes 0..15)SignatureInitializationVector = EBESExQVFhcYGRobHB0eHw==(base64 of bytes 16..31)
AuthenticateMe and Disconnect use AuthenticationData with:
Data = "deterministic-ciphertext-bytes"(base64-encoded)InitializationVector = "0123456789abcdef"(base64-encoded)
RegisterItemsRequest uses one ItemIdentity with
Type = Name (0), ReferenceType = Absolute (1),
Name = "TestChildObject.TestInt", ContextName = "".
UnregisterItemsRequest uses one ItemIdentity with
Type = Id (1), ReferenceType = Absolute (1), Name = null,
ContextName = null, Id = 0xCAFEBABEDEADBEEF (14627333968688430831),
IdSpecified = true.
Observed serialiser behaviour
These rules were inferred from the captured output and from the .NET
source for XmlSerializer:
-
Element name = class name, NOT
[MessageContract.WrapperName].XmlSerializerdoes not honour WCF's MessageContract attributes. -
Top-element xmlns ordering (after
<?xml ... ?>):xmlns:xsi, thenxmlns:xsd, then defaultxmlns. TheAsbSerialization.ToXmlpost-process (AsbSerialization.cs:36-47) reparses withXDocument.Loadand reorders to putxsibeforexsd—XmlSerializer's native order is the opposite. -
Field order = C# declaration order (with inherited fields first), NOT
[MessageBodyMember.Order]. -
[XmlType(Namespace = ...)]on a field's type triggers anxmlns="..."redeclaration on EACH child element of that type's instance, NOT on the wrapper element itself. e.g. inside<ConnectionValidator>, every direct child getsxmlns="http://asb.contracts.data/20111111". -
byte[]fields serialise as base64 text content.Guidas canonical lowercase D-format (8cba964a-74c1-...).ulongas decimal.boolas"true"/"false". -
Null reference-type fields with
[XmlElement(IsNullable = true)]produce<Name xsi:nil="true" xmlns="..." />. Empty string fields produce a self-closing<ContextName xmlns="..." />. -
*Specifiedpattern: a public bool field namedXxxSpecified=truecauses XmlSerializer to emit the corresponding<Xxx>element.IdSpecified = false(default) →<Id>omitted.IdSpecified = true→<Id>emitted with the int value. The*Specifiedfield itself is[XmlIgnore]and never emitted. -
Self-closing elements use
/>(space before/>). -
Indentation: 2 spaces,
\r\nline endings, no trailing newline after the closing tag. -
XML declaration:
<?xml version="1.0" encoding="utf-16"?>— noteutf-16even thoughAsbSystemAuthenticator.SignHMACsEncoding.UTF8.GetBytes(...)of this string. The declaration is a static .NET StringWriter default; the actual byte encoding fed to HMAC is UTF-8.
Files
authenticate-me.xml—AuthenticateMedisconnect.xml—Disconnectkeep-alive.xml—KeepAliveregister-items.xml—RegisterItemsRequestunregister-items.xml—UnregisterItemsRequest
Each file is the verbatim UTF-8 representation of request.ToXml(),
with literal \r\n line endings preserved. Treat as binary (don't
let your editor reformat).