diff --git a/design/followups.md b/design/followups.md index 975ef4c..33f50a1 100644 --- a/design/followups.md +++ b/design/followups.md @@ -90,7 +90,12 @@ For the per-step body of every line listed in the cumulative execution log, see For ops where the body is purely `IAsbCustomSerializableType` arrays (Read, Register, Unregister), no DataContract names appear — every payload is wrapped as `{bytes}` (binary fast-path) and our builders are correct. The DataContract schema only matters for ops carrying non-`IAsbCustomSerializable` types like `MonitoredItem` and `WriteValue`. -**Resolves when:** `build_add_monitored_items_request_body` and `build_delete_monitored_items_request_body` rewrite each `MonitoredItem` child as the DataContract field-suffix names (`activeField` instead of `Active`, etc.), with the `*Specified` siblings emitted as their own elements (`activeFieldSpecified`, `idFieldSpecified`, etc.). The dictionary-id pre-population that .NET's WCF binary writer uses to compress these long strings is a perf optimisation; an inline-string emit will work for correctness. Likely the same fix applies to `WriteBasicRequest`'s `WriteValue[]? Values` field (also non-`IAsbCustomSerializable`) — that's a future capture-and-verify pass. +**Resolves when:** Two prerequisites: + +1. **F30 dynamic-dict resolution bug** — captured `tests/fixtures/add-monitored-items-request-wire.bin` (the .NET probe's verbatim 695-byte AddMonitoredItems request via `examples/asb-relay.rs`), decoded via `decode_envelope` at `tests/add_monitored_items_request_capture.rs`. The trace shows `DefaultNamespace { value: Chars("nameField") }` and `NamespaceDeclaration { prefix: "i", value: Chars("activeField") }` — namespace URL slots resolved to field-name strings, plus most element names left as `Static(NN)` instead of resolving to inline names. The F30 cumulative dynamic-dict post-pass at `envelope.rs::resolve_dict_names_in_tokens` mis-maps per-session dynamic dict ids; the fix needs reproducing exactly which dict each id refers to (per-message header vs cumulative dynamic vs `[MC-NBFS]` static) and resolving in the right order. +2. **Builder rewrite** — once (1) lands and we can read the captured request structurally, rewrite `build_add_monitored_items_request_body` and `build_delete_monitored_items_request_body` to emit each `MonitoredItem` child as the DataContract field-suffix names (`activeField` / `activeFieldSpecified` / `bufferedField` / `itemField` / `sampleIntervalField` / `timeDeadbandField` / `timeDeadbandFieldSpecified` / `userDataField` / `valueDeadbandField`) under a `b` namespace prefix that maps to `http://schemas.datacontract.org/2004/07/ArchestrAServices.ASBIDataV2Contract`. The nested `` carries an ItemIdentity serialized via DataContract (NOT the binary `` fast-path — that only kicks in at the outer body-member level) with children `contextNameField` / `idField` / `idFieldSpecified` / `nameField` / `referenceTypeField` / `typeField` under a different `b` prefix mapping to `http://schemas.datacontract.org/2004/07/ArchestrAServices.ASBContract`. The Variant fields (`userDataField` / `valueDeadbandField`) carry `lengthField` / `payloadField` / `typeField` children. Same fix likely applies to `WriteBasicRequest`'s `WriteValue[]? Values` field (also non-`IAsbCustomSerializable`); needs its own capture-and-verify pass. + +The dictionary-id pre-population that .NET's WCF binary writer uses is a perf optimisation; an inline-string emit will work for correctness once the structure is right. **Bonus context discovered while debugging F34:** - `MinimalMonitoredItem` gained an `active: Option` field with a `with_active(item, interval, active)` constructor. Without `true` on the wire, MxDataProvider treats the subscription as inactive even when AddMonitoredItems "succeeds" — F26 stream then never sees values. (Once the field-name fix lands, this becomes the determining factor for whether values flow.) diff --git a/rust/crates/mxaccess-asb/tests/add_monitored_items_request_capture.rs b/rust/crates/mxaccess-asb/tests/add_monitored_items_request_capture.rs new file mode 100644 index 0000000..165b92e --- /dev/null +++ b/rust/crates/mxaccess-asb/tests/add_monitored_items_request_capture.rs @@ -0,0 +1,47 @@ +//! F34 — wire-byte trace of a captured `AddMonitoredItemsRequest`. +//! +//! `tests/fixtures/add-monitored-items-request-wire.bin` is the +//! verbatim C→S bytes the .NET probe (`MxAsbClient.Probe --subscribe +//! --via=net.tcp://127.0.0.1:8088/...`) sent to MxDataProvider on +//! 2026-05-06. The exchange led to a working subscription that +//! delivered values; this is the request shape MxDataProvider +//! actually accepts. +//! +//! Test goal: dump every NBFX token in the body so we can read off +//! the exact element-name shape (DataContract field-suffix names per +//! `[DataMember(Name=...)]`, NOT XmlSerializer property names) and +//! re-implement `build_add_monitored_items_request_body` against it. +//! +//! Frame layout: 3-byte NMF SizedEnvelope header (`06 b4 05`, +//! varint length = 692) + 692-byte SOAP envelope. + +#![allow( + clippy::unwrap_used, + clippy::expect_used, + clippy::indexing_slicing, + clippy::panic +)] + +use mxaccess_asb::decode_envelope; +use mxaccess_asb_nettcp::nbfx::DynamicDictionary; + +#[test] +fn add_monitored_items_request_capture_decoder_trace() { + let raw = std::fs::read( + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/add-monitored-items-request-wire.bin"), + ) + .expect("read fixture"); + assert_eq!(raw.len(), 695, "frame length sanity check"); + + // Strip 3-byte NMF SizedEnvelope header. + let envelope = &raw[3..]; + assert_eq!(envelope.len(), 692); + + let mut dict = DynamicDictionary::new(); + let decoded = decode_envelope(envelope, &mut dict).expect("decode_envelope succeeds"); + eprintln!("=== body tokens ({} total) ===", decoded.body_tokens.len()); + for (i, tok) in decoded.body_tokens.iter().enumerate() { + eprintln!(" body[{i}]={tok:?}"); + } +} diff --git a/rust/crates/mxaccess-asb/tests/fixtures/add-monitored-items-request-wire.bin b/rust/crates/mxaccess-asb/tests/fixtures/add-monitored-items-request-wire.bin new file mode 100644 index 0000000..47c51c2 Binary files /dev/null and b/rust/crates/mxaccess-asb/tests/fixtures/add-monitored-items-request-wire.bin differ