[M5] mxaccess-asb-nettcp/asb: F21 short forms + EndElement fix + UniqueIdText
Three NBFX-spec corrections discovered by diffing our wire output against the .NET probe's capture: 1. **EndElement is 0x01, NOT 0x00**. Our F21 had this wrong since the first iteration. Our round-trip tests passed because encode and decode used the same wrong value, but interop with WCF's parser silently failed (TCP RST on every request). Fixed by changing `REC_END_ELEMENT` to 0x01 — all 702 tests pass on the new value. 2. **Single-letter prefix short forms**. WCF uses `PrefixDictionaryElement_<a-z>` (records 0x44-0x5D) and `PrefixDictionaryAttribute_<a-z>` (records 0x0C-0x25) for single-character prefixes. Our F21 always used the long forms (0x43 prefix-string + dict-id, etc.). The encoder now emits the short form when the prefix is a single ASCII lowercase letter; the decoder accepts both. New `prefix_letter_offset(prefix)` helper. 3. **`DictionaryXmlnsAttribute` (0x0B)** for xmlns:prefix declarations whose value is a static-dict id. The long form (0x09 + prefix-string + text-record) is still emitted when the value is an inline string, but for `xmlns:s="...soap-envelope"` (dict id 4) we now emit the short `0b 01 73 04` form WCF uses. 4. **UniqueIdText (0xAC)** added to `NbfxText` enum + encode/decode. WCF emits `<a:MessageID>` as a UniqueIdText carrying the 16 raw UUID bytes (NOT the `urn:uuid:...` text form). Updated `encode_envelope` to use this for MessageID. Combined wire-byte impact: our envelope body section now matches the .NET probe byte-for-byte through `<a:Action>`, `<h:ConnectionValidator>`, `<a:MessageID>` (UniqueId), `<a:ReplyTo>`, `<a:To>`, and `<s:Body>`. The trailing `01 01 01 01` = 4 EndElements is now the correct record byte. Tests pass (702 total). Live status: still TCP RST after the SizedEnvelope. Remaining unknown is in the body section — the .NET capture shows xmlns:xsi / xmlns:xsd declarations on the operation-specific request element (ConnectRequest etc.) that we don't emit, plus possibly different field encoding inside ConnectRequest. Next iteration will re-capture through the relay and diff our body bytes against the new .NET-byte-equivalent we now produce. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -260,13 +260,16 @@ pub fn encode_envelope(
|
||||
encode_validator(&mut tokens, v, dynamic);
|
||||
}
|
||||
|
||||
// <a:MessageID>urn:uuid:{uuid}</a:MessageID>
|
||||
let message_id = format!("urn:uuid:{}", make_random_uuid_v4());
|
||||
// <a:MessageID>{16-byte UUID via UniqueIdText}</a:MessageID>
|
||||
// WCF emits MessageID as a UniqueIdText record (0xAC) carrying the
|
||||
// 16 raw UUID bytes — NOT as Chars text. Verified against .NET
|
||||
// probe wire capture.
|
||||
let message_id_bytes = make_random_uuid_v4_bytes();
|
||||
tokens.push(NbfxToken::Element {
|
||||
prefix: Some("a".to_string()),
|
||||
name: NbfxName::Static(26),
|
||||
});
|
||||
tokens.push(NbfxToken::Text(NbfxText::Chars(message_id)));
|
||||
tokens.push(NbfxToken::Text(NbfxText::UniqueId(message_id_bytes)));
|
||||
tokens.push(NbfxToken::EndElement); // </a:MessageID>
|
||||
|
||||
// <a:ReplyTo><a:Address>{anonymous}</a:Address></a:ReplyTo>
|
||||
@@ -587,15 +590,20 @@ fn push_dc_field(out: &mut Vec<NbfxToken>, name: &str, dc_ns: &str, value: &str)
|
||||
out.push(NbfxToken::EndElement);
|
||||
}
|
||||
|
||||
/// Random RFC 4122 v4-shaped UUID (without pulling the `uuid` crate).
|
||||
/// Used by `encode_envelope` for the `<a:MessageID>urn:uuid:...`
|
||||
/// header. The output is a hyphenated lowercase 36-char string.
|
||||
fn make_random_uuid_v4() -> String {
|
||||
/// Random RFC 4122 v4 UUID raw bytes. Used by `encode_envelope` for
|
||||
/// the `<a:MessageID>` UniqueIdText record (16 raw bytes on the wire).
|
||||
fn make_random_uuid_v4_bytes() -> [u8; 16] {
|
||||
use rand::RngCore;
|
||||
let mut bytes = [0u8; 16];
|
||||
rand::thread_rng().fill_bytes(&mut bytes);
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40; // version 4
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80; // variant 1 (RFC 4122)
|
||||
bytes
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // kept for callers that need the textual form
|
||||
fn _unused_make_random_uuid_v4() -> String {
|
||||
let bytes = make_random_uuid_v4_bytes();
|
||||
format!(
|
||||
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
bytes[0],
|
||||
|
||||
Reference in New Issue
Block a user