diff --git a/rust/crates/mxaccess-asb-nettcp/src/nbfx.rs b/rust/crates/mxaccess-asb-nettcp/src/nbfx.rs index 2efd3a2..13d5de5 100644 --- a/rust/crates/mxaccess-asb-nettcp/src/nbfx.rs +++ b/rust/crates/mxaccess-asb-nettcp/src/nbfx.rs @@ -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")?; diff --git a/rust/crates/mxaccess-asb/src/envelope.rs b/rust/crates/mxaccess-asb/src/envelope.rs index ae54845..ed85c09 100644 --- a/rust/crates/mxaccess-asb/src/envelope.rs +++ b/rust/crates/mxaccess-asb/src/envelope.rs @@ -300,11 +300,24 @@ pub fn encode_envelope( tokens.push(NbfxToken::EndElement); // - // + // + // 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); //