[M5] mxaccess-asb-nettcp/asb: xmlns raw-string + xsi/xsd on Body
WIRE-FORMAT BREAKTHROUGH — our envelope is now valid NBFX/WCF. ConnectRequest reaches the server's operation handler. Direct-port-808 Connect now returns a server-side fault (operation invocation error from the placeholder DH key) rather than TCP RST. With a real DH key, asb-subscribe gets all the way to "response is missing required field ServicePublicKey/Data" — meaning our Connect request processed end-to-end, the server returned a ConnectResponse, and the only remaining issue is in our response-body decoder. Fixes: 1. **`XmlnsAttribute` (0x09) value is a RAW length-prefixed string, not a text record**. Per `[MC-NBFX]` §2.2.3, xmlns attribute values are `[length][bytes]`, NOT `[text-record-byte][value]`. Our F21 was emitting `aa <id>` for dict-static values which the receiver misparsed as a 0xAA-length string. Same fix applies to `ShortXmlnsAttribute` (0x08). Encoder now picks raw-string for `Chars` value, raw-int31 (via 0x0B) for `DictionaryStatic` value; decoder reads raw string in both code paths. 2. **xmlns:xsi + xmlns:xsd on `<s:Body>`**. WCF declares these namespaces on Body before opening the operation request element. Our envelope encoder now emits both as raw-string xmlns attrs right after `<s:Body>` opens. Required for `xsi:type` annotations that appear inside DataContract-serialised body fields. Combined wire-byte impact (verified via asb-relay side-by-side diff): * All header bytes match .NET byte-for-byte through the entire `<s:Header>` section (Action / ConnectionValidator / MessageID via UniqueIdText / ReplyTo / To). * `<s:Body>` xmlns:xsi + xmlns:xsd declarations match .NET. * `<ConnectRequest>` opens identically. * `<ConnectionId>` / `<ConsumerPublicKey>` / `<Data>` element names match. * The only known remaining diff in the request: .NET emits `xmlns="http://asb.contracts.data/20111111"` on the inner `<Data>` element (the PublicKey class's XmlType namespace); we don't. Likely an issue but apparently non-fatal — the server processed our request successfully past this point. Live status: * Direct port-808 connect with real DH key: server returns "response is missing required field ServicePublicKey/Data" — meaning we sent a valid Connect, server replied with a ConnectResponse, but our decoder can't find the field. Next iteration is response-side decode work. Workspace: 702 tests pass; clippy + fmt clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -340,24 +340,50 @@ fn encode_one(
|
||||
value,
|
||||
} => encode_attribute(prefix.as_deref(), name, value, dynamic, out),
|
||||
NbfxToken::DefaultNamespace { value } => {
|
||||
out.push(REC_SHORT_XMLNS_ATTRIBUTE);
|
||||
encode_text_string_or_dict(value, dynamic, out)
|
||||
// Per `[MC-NBFX]` §2.2.3: `ShortXmlnsAttribute` (0x08)
|
||||
// value is a RAW length-prefixed string (same convention
|
||||
// as 0x09 — see comment on NamespaceDeclaration below).
|
||||
match value {
|
||||
NbfxText::Chars(s) => {
|
||||
out.push(REC_SHORT_XMLNS_ATTRIBUTE);
|
||||
encode_string(s.as_bytes(), out)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(NbfxError::InvalidUtf8 {
|
||||
stage: "default-xmlns-must-be-Chars",
|
||||
}),
|
||||
}
|
||||
}
|
||||
NbfxToken::NamespaceDeclaration { prefix, value } => {
|
||||
// WCF emits `DictionaryXmlnsAttribute` (0x0B) when the
|
||||
// value is a static-dictionary id, and `XmlnsAttribute`
|
||||
// (0x09) when it's an inline string. Stricter parsers
|
||||
// reject the long form when a dict-id is available.
|
||||
if let NbfxText::DictionaryStatic(id) = value {
|
||||
out.push(REC_DICT_XMLNS_ATTRIBUTE);
|
||||
encode_string(prefix.as_bytes(), out)?;
|
||||
encode_multibyte_int31_to_nbfx(out, *id)?;
|
||||
Ok(())
|
||||
} else {
|
||||
out.push(REC_XMLNS_ATTRIBUTE);
|
||||
encode_string(prefix.as_bytes(), out)?;
|
||||
encode_text_string_or_dict(value, dynamic, out)
|
||||
// Per `[MC-NBFX]` §2.2.3: `XmlnsAttribute` (0x09) value is
|
||||
// a RAW length-prefixed string — NOT a text record like
|
||||
// regular Attribute values. `DictionaryXmlnsAttribute`
|
||||
// (0x0B) value is a raw multibyte-int31 dict id. Either
|
||||
// form omits the text-record byte and width-tag.
|
||||
//
|
||||
// We pick 0x0B when the value is `DictionaryStatic(id)`
|
||||
// (matches WCF's encoding for SOAP/WS-Addressing names),
|
||||
// and 0x09 + raw-string for `Chars(s)` (matches WCF's
|
||||
// encoding for namespaces not in the static dict — e.g.
|
||||
// xsi/xsd, or operation-specific URIs).
|
||||
match value {
|
||||
NbfxText::DictionaryStatic(id) => {
|
||||
out.push(REC_DICT_XMLNS_ATTRIBUTE);
|
||||
encode_string(prefix.as_bytes(), out)?;
|
||||
encode_multibyte_int31_to_nbfx(out, *id)?;
|
||||
}
|
||||
NbfxText::Chars(s) => {
|
||||
out.push(REC_XMLNS_ATTRIBUTE);
|
||||
encode_string(prefix.as_bytes(), out)?;
|
||||
encode_string(s.as_bytes(), out)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(NbfxError::InvalidUtf8 {
|
||||
stage: "xmlns-value-must-be-Chars-or-DictionaryStatic",
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
NbfxToken::Text(text) => encode_text(text, with_end, out),
|
||||
}
|
||||
@@ -672,13 +698,20 @@ pub fn decode_tokens(
|
||||
});
|
||||
}
|
||||
REC_SHORT_XMLNS_ATTRIBUTE => {
|
||||
let value = decode_text_record(input, &mut cursor)?;
|
||||
tokens.push(NbfxToken::DefaultNamespace { value });
|
||||
let value_str = decode_string(input, &mut cursor, "default-xmlns-value")?;
|
||||
tokens.push(NbfxToken::DefaultNamespace {
|
||||
value: NbfxText::Chars(value_str),
|
||||
});
|
||||
}
|
||||
REC_XMLNS_ATTRIBUTE => {
|
||||
// Per spec, value is a raw length-prefixed string,
|
||||
// NOT a text record.
|
||||
let prefix = decode_string(input, &mut cursor, "xmlns-prefix")?;
|
||||
let value = decode_text_record(input, &mut cursor)?;
|
||||
tokens.push(NbfxToken::NamespaceDeclaration { prefix, value });
|
||||
let value_str = decode_string(input, &mut cursor, "xmlns-value")?;
|
||||
tokens.push(NbfxToken::NamespaceDeclaration {
|
||||
prefix,
|
||||
value: NbfxText::Chars(value_str),
|
||||
});
|
||||
}
|
||||
REC_DICT_XMLNS_ATTRIBUTE => {
|
||||
let prefix = decode_string(input, &mut cursor, "dict-xmlns-prefix")?;
|
||||
|
||||
@@ -300,11 +300,24 @@ pub fn encode_envelope(
|
||||
|
||||
tokens.push(NbfxToken::EndElement); // </s:Header>
|
||||
|
||||
// <s:Body>
|
||||
// <s:Body xmlns:xsi="...XMLSchema-instance" xmlns:xsd="...XMLSchema">
|
||||
// WCF emits xmlns:xsi + xmlns:xsd as raw-string xmlns attributes on
|
||||
// the Body element (verified against .NET probe wire capture).
|
||||
// These declarations are required for `xsi:type` annotations that
|
||||
// appear inside DataContract-serialised body fields (e.g.
|
||||
// ConnectRequest's nested PublicKey).
|
||||
tokens.push(NbfxToken::Element {
|
||||
prefix: Some("s".to_string()),
|
||||
name: NbfxName::Static(ns::BODY),
|
||||
});
|
||||
tokens.push(NbfxToken::NamespaceDeclaration {
|
||||
prefix: "xsi".to_string(),
|
||||
value: NbfxText::Chars("http://www.w3.org/2001/XMLSchema-instance".to_string()),
|
||||
});
|
||||
tokens.push(NbfxToken::NamespaceDeclaration {
|
||||
prefix: "xsd".to_string(),
|
||||
value: NbfxText::Chars("http://www.w3.org/2001/XMLSchema".to_string()),
|
||||
});
|
||||
tokens.extend_from_slice(&envelope.body_tokens);
|
||||
tokens.push(NbfxToken::EndElement); // </s:Body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user