[M5] mxaccess-asb: F25 step 2 — per-operation request body codecs

Adds the IAsbCustomSerializableType binary fast-path + per-operation
request-body NBFX-token builders. RegisterItems and UnregisterItems
now compose end-to-end through SoapEnvelope + encode_envelope to a
byte stream that round-trips back to the original ItemIdentity array.

Three pieces:

1. F21 NBFX gains `Bytes8/16/32` text records (records 0x9E/0xA0/0xA2
   plus +1 WithEndElement variants). WCF's `XmlDictionaryWriter.
   WriteBase64` emits these in binary form — not actual base64 text —
   so they're required for the `<ASBIData>` content.

2. `mxaccess-asb::contracts::ItemIdentity` ports `AsbContracts.cs:533-633`:
   * Wire layout: u16 kind + u16 reference_type +
     AsbBinary.WriteUnicodeString(Name) + AsbBinary.WriteUnicodeString
     (ContextName) + u64 Id + u8 IdSpecified.
   * `AsbBinary.WriteUnicodeString` per cs:1622-1633: u32 byte-length
     + UTF-16LE bytes; null/empty collapse to a 4-byte zero header.
   * `encode_item_identity_array` / `decode_item_identity_array`
     mirror `WriteArrayToStream` — 4-byte int32 count + each
     element's `WriteToStream` output. Per `AsbDataCustomSerializer`
     at cs:1583-1591.
   * `absolute_by_name(...)` convenience constructor matching
     `MxAsbDataClient.CreateAbsoluteItem` at cs:172-194.

3. `mxaccess-asb::operations` builds SOAP body NBFX token streams:
   * `build_register_items_request_body(items, require_id, register_only)`
     — RegisterItems contract per cs:119-143.
   * `build_unregister_items_request_body(items)` — UnregisterItems
     per cs:145-159.
   * Internal `BodyField` helper assembles the wire shape:
     `<RegisterItemsRequest xmlns="urn:msg.data.asb.iom:2">
        <Items><ASBIData>{Bytes(payload)}</ASBIData></Items>
        <RequireId>true|false</RequireId>
        <RegisterOnly>true|false</RegisterOnly>
      </RegisterItemsRequest>`

15 new tests cover:
* ItemIdentity round-trip (default, with id, unicode name).
* AsbBinary unicode-string null/empty/value semantics.
* Byte-layout pinning (21 bytes for default ItemIdentity, le-int32
  array count).
* ItemIdentity array round-trip.
* `<ASBIData>` Bytes record round-trip across NBFX widths
  (Bytes8/16/32 selected by length).
* RegisterItems body → SoapEnvelope → encode → decode → recover the
  ItemIdentity array end-to-end.
* RequireId / RegisterOnly Bool wire form.
* UnregisterItems body uses correct outer element name and omits
  the RegisterItems-only fields.

Stubbed for next F25 iteration: per-operation Read / Write /
PublishWriteComplete / CreateSubscription / AddMonitoredItems /
DeleteMonitoredItems / Publish builders, response decoders, and the
`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:24:19 -04:00
parent 25dbd8d3bd
commit a2b8989cbf
5 changed files with 784 additions and 3 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 (`5f98558`); F25 step 1 landed in this commit:
**Cumulative execution log.** F19 + F23 (`ed17c07`); F24 (`7611d9e`); F20 (`9dfd193`); F22 (`43c10a1`); F21 (`5f98558`); F25 step 1 (`25dbd8d`); F25 step 2 landed in this commit:
- F25 step 2: per-operation request-body builders + `IAsbCustomSerializableType` binary fast-path. F21 NBFX gains `Bytes8/16/32` text records (used by `XmlDictionaryWriter.WriteBase64` for the `<ASBIData>` content). New `mxaccess-asb::contracts::ItemIdentity` ports the binary `WriteToStream` shape from `AsbContracts.cs:594-611`: u16 kind + u16 reference_type + `AsbBinary.WriteUnicodeString` Name + ContextName + u64 Id + u8 IdSpecified. Plus `encode_item_identity_array` / `decode_item_identity_array` mirroring `WriteArrayToStream` (4-byte int32 count + items). New `mxaccess-asb::operations` builds the SOAP body NBFX token streams: `build_register_items_request_body(items, require_id, register_only)` and `build_unregister_items_request_body(items)`. The `<ASBIData>` element is wrapped with raw NBFX `Bytes` records (the binary form of WCF's `WriteBase64`). 14 new tests cover ItemIdentity round-trip (default, with id, unicode), ItemIdentity array round-trip, AsbBinary unicode-string null/empty/value semantics, byte-layout pinning (21-byte minimum for default ItemIdentity, le-int32 array count), and the full RegisterItems → SoapEnvelope → encode → decode → recover-ItemIdentity-array round-trip through the entire stack.
**Earlier slices:**
- F25 step 1 (commit `25dbd8d`):
- 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:**