[M5] mxaccess-asb: F25 step 1 — SOAP envelope codec

First slice of F25. Provides the building blocks the per-operation
request/response codecs and the network loop will compose:

* `actions` module — IASBIDataV2 action strings (all 14 operations,
  verbatim from `AsbContracts.cs:14-58`).
* `ConnectionValidator` — SOAP header struct mirroring
  `AsbContracts.cs:65-117`. `from_signed(&SignedValidator)` converts
  F23's MAC + IV to base64 for the wire, matching .NET's
  `BinaryWriter`-via-`XmlSerializer` shape.
* `SoapEnvelope` + `encode_envelope` — assembles the NBFX token
  stream: `s:Envelope` → `s:Header` → `a:Action s:mustUnderstand="1"`
  → optional `h:ConnectionValidator` → `s:Body` → caller-supplied
  body tokens. Uses static-dictionary IDs for the SOAP/WS-Addressing
  tokens via F22's `lookup_static`.
* `decode_envelope` — pulls action + validator + body tokens back
  out of received bytes. Tolerant of header ordering.
* Mixed-endian GUID format/parse (`format_uuid` / `parse_uuid`) that
  mirrors .NET's `Guid.ToString("D")` byte order so connection-id
  round-trip matches the wire exactly.

9 new unit tests cover:
* Round-trip with and without validator.
* `from_signed` base64 encoding of MAC + IV.
* `format_uuid` produces the correct .NET-mixed-endian hex string.
* GUID round-trip through string formatter.
* Action string presence in the encoded byte stream.
* Decoder tolerance of envelopes without an Action header.
* Validator round-trip through full encode → decode.
* Lint-style guard that all 14 action constants are URIs ending `In`.

Stubbed for next F25 iteration: per-operation request/response
struct codecs (`ConnectRequest`, `RegisterItemsRequest`, etc.) +
`AsbClient` network loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 11:16:22 -04:00
parent 5f985588f7
commit 25dbd8d3bd
5 changed files with 792 additions and 2 deletions
+5 -1
View File
@@ -46,7 +46,11 @@ move to `## Resolved` with a date + commit hash.
**Resolves when:** F19-F26 are all closed and the four DoD bullets above pass.
**Cumulative execution log.** F19 + F23 (`ed17c07`); F24 (`7611d9e`); F20 (`9dfd193`); F22 (`43c10a1`); F21 landed in this commit:
**Cumulative execution log.** F19 + F23 (`ed17c07`); F24 (`7611d9e`); F20 (`9dfd193`); F22 (`43c10a1`); F21 (`5f98558`); F25 step 1 landed in this commit:
- F25 step 1: `mxaccess-asb::envelope` — SOAP-1.2-over-NBFX envelope assembly + parsing for the `IASBIDataV2` contract. Provides `actions::*` constants for all 14 operations (verbatim from `AsbContracts.cs:14-58`), a `ConnectionValidator` header struct that converts F23's `SignedValidator` (`mac` + `iv` get base64-encoded for the wire), `SoapEnvelope` builder, `encode_envelope` (NBFX-token assembly: `s:Envelope``s:Header``a:Action s:mustUnderstand="1"` → optional `h:ConnectionValidator``s:Body``body_tokens`), and `decode_envelope` (tolerant of header ordering — looks for Action and ConnectionValidator anywhere inside `<s:Header>`). Includes a `format_uuid`/`parse_uuid` pair that mirrors .NET's `Guid.ToString("D")` mixed-endian byte order so connection-id round-trip matches the wire. 9 unit tests cover round-trip with/without validator, validator-from-SignedValidator base64 encoding, .NET-mixed-endian GUID format, action-string presence in encoded bytes, missing-Action tolerance, and full validator round-trip through encode→decode. **Stubbed for next F25 iteration:** per-operation request/response struct codecs (`ConnectRequest`, `RegisterItemsRequest`, etc. with the `IAsbCustomSerializableType` binary fast-path that .NET uses for `Variant`/`AsbStatus`/`RuntimeValue`), and `AsbClient` (TCP + NMF preamble + sized-envelope read/write loop + auth handshake).
**Earlier slices:**
- F21 (commit `5f98558`):
- F21: `mxaccess-asb-nettcp::nbfx` ports the `[MC-NBFX]` `.NET Binary XML Format` token codec — the proven subset for ASB. Token model: `Element { prefix, name }` / `EndElement` / `Attribute { prefix, name, value }` / `DefaultNamespace` / `NamespaceDeclaration` / `Text`. Name forms: inline UTF-8, `[MC-NBFS]` static-dictionary id, per-session `DynamicDictionary` id. Text forms: Empty, Zero, One, Bool, Int8/16/32/64, Chars (Chars8/16/32 width variants chosen automatically), and `DictionaryText` static/dynamic refs. The `*WithEndElement` text variants are collapsed automatically: `Text → EndElement` pairs encode as the `+1` record byte (e.g. `EmptyTextWithEndElement = 0xA9`); decoder splits them back out so consumers see the same token stream. 15 unit tests cover the dynamic-dictionary semantics, all element/attribute/xmlns/dict-text record forms, the collapse behavior with explicit byte pinning (`0x87` TrueTextWithEndElement, `0xA9` EmptyTextWithEndElement), Chars width-variant selection (Chars8 / Chars16 / Chars32 by length), unknown-record rejection, and truncated payloads. Records left for follow-up: Decimal, UniqueId, TimeSpan, Float/Double text, DateTime text, Bytes8/16/32, QNameDictionary, the `0x0C-0x25`/`0x26-0x3F` prefix-attribute and `0x44-0x77` prefix-element families.
**Earlier slices:**