826f7b3f89
rust / build / test / clippy / fmt (push) Has been cancelled
The original hand-curated table was wrong starting at id 74 — entries had been deduplicated/renumbered without preserving the canonical `id = 2 * StringN` mapping from `[MC-NBFS]` §2.2, leaving most of the SOAP-fault subset at the wrong ids: ours had Fault at 114, canonical is 134 ours had Code at 122, canonical is 142 ours had Reason at 124, canonical is 144 ours had Text at 126, canonical is 146 ours had Value at 134, canonical is 154 ours had Subcode at 136, canonical is 156 Wire captures from the live AVEVA MxDataProvider use the canonical ids — verified earlier via `MX_ASB_TRACE_REPLY` showing `<resultCodeField>` correctly resolved through the F30 post-pass once the ids matched. Replaced the entire STATIC_ENTRIES array with a faithful port of the first 200 entries from `dotnet/wcf`'s `src/System.ServiceModel.Primitives/src/System/ServiceModel/ ServiceModelStringsVersion1.cs` (sourced via WebFetch — that file is the canonical [MC-NBFS] §2.2 table mirrored in code). The wire id is `2 * StringN` for `StringN` at 0-based position N. Coverage now spans id 0..400, picking up the full SOAP / WS-Addressing / WS-RM / WS-Security / WS-SecureConversation / WS-Trust / xmldsig+xenc URIs / SAML / Kerberos / X509 token-type subset. The 436..444 xsi/xsd/nil extras (used by .NET XmlSerializer for [MessageContract] value-type bodies) are preserved. Four new regression tests: - ids monotonic (was already there); - ids all even (`[MC-NBFS]` reserves odd ids for the dynamic dict); - SOAP-fault subset (s, Fault, MustUnderstand, Code, Reason, Text, Node, Role, Detail, Value, Subcode) resolves to the canonical strings — pins the fix against accidental regression; - `position_of_static` round-trips for known strings. Followups: - F29 moved to ## Resolved with full audit-trail. - F18 M5 status block updated to strike F29 from the remaining-work list. The remaining open M5 items are F32 (live type-matrix beyond Int32/String/Bool, gated on Galaxy provisioning) and F28 (canonical XML signing for Read/Write/Subscribe ops, P2 latent). Workspace: 712 unit tests pass (was 711 + 1 new fault-subset test + existing tests now matching canonical). Clippy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
413 lines
21 KiB
Rust
413 lines
21 KiB
Rust
//! `[MC-NBFS]` static dictionary table for `[MC-NBFX]` binary XML.
|
|
//!
|
|
//! The .NET binary message encoder (`BinaryMessageEncodingBindingElement`,
|
|
//! the default for `NetTcpBinding`) compresses common strings — SOAP /
|
|
//! WS-Addressing tokens, URIs, frequently-used element/attribute names —
|
|
//! by encoding them as a single `Multibyte Int31` index into a
|
|
//! globally-known static dictionary. `[MC-NBFS]` §2.2 enumerates that
|
|
//! dictionary; the full canonical table has 487 entries, all ASCII.
|
|
//!
|
|
//! ## Source of truth
|
|
//!
|
|
//! Every entry below is mirrored from `ServiceModelStringsVersion1` in
|
|
//! the .NET WCF source (`dotnet/wcf` repository,
|
|
//! `src/System.ServiceModel.Primitives/src/System/ServiceModel/
|
|
//! ServiceModelStringsVersion1.cs`), which is the same canonical table
|
|
//! that `[MC-NBFS]` §2.2 publishes. The wire dict id is `2 * StringN`
|
|
//! where `StringN` is the 0-based position in the WCF source — even
|
|
//! ids only; odd ids are reserved for the per-session dynamic dict.
|
|
//!
|
|
//! ## Coverage
|
|
//!
|
|
//! Strings 0-200 of the canonical table — id range 0 through 400.
|
|
//! Covers the SOAP 1.2 envelope, WS-Addressing 1.0, WS-RM,
|
|
//! WS-Security, WS-SecureConversation, WS-Trust, XmlDsig + xenc URIs,
|
|
//! and the common SAML / Kerberos / X509 token type URIs. Plus the
|
|
//! `xsi` / `xsd` / `nil` / `type` / `i` extras at id 436-444 used
|
|
//! heavily by .NET's `XmlSerializer` for value types in custom
|
|
//! message-contract bodies.
|
|
//!
|
|
//! ## Why we don't ship all 487 entries
|
|
//!
|
|
//! Strings 201-486 cover policy, trust-15, secure-conversation-13, more
|
|
//! algorithm URIs, plus the `[MC-NBFC]` Multi-Encoding tokens that the
|
|
//! AVEVA wire never references. Adding them is a mechanical extension
|
|
//! (port more lines from `ServiceModelStringsVersion1.cs`) but yields
|
|
//! no functional improvement against captured wire traffic.
|
|
//!
|
|
//! ## What the table is NOT
|
|
//!
|
|
//! ASB-specific contract strings (`"http://ASB.IDataV2"`,
|
|
//! `"http://asb.contracts/20111111"`, the operation names, etc.) are
|
|
//! **not** in the static dictionary. They live in the per-session
|
|
//! *dynamic* dictionary that `[MC-NBFX]` builds up via the
|
|
//! binary-message-header pre-pop (odd ids 1, 3, 5, ...). The dynamic
|
|
//! dictionary is mutable per session and lives in `crate::nbfx`.
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::OnceLock;
|
|
|
|
/// One static-dictionary entry.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct StaticEntry {
|
|
pub id: u32,
|
|
pub value: &'static str,
|
|
}
|
|
|
|
/// Faithful subset of the `[MC-NBFS]` §2.2 static dictionary, sorted by
|
|
/// `id`. Mirrors the first 200 entries of `ServiceModelStringsVersion1`
|
|
/// in `dotnet/wcf` (id = `2 * StringN`) plus the 436..444 xsi/xsd/nil
|
|
/// extras. Adding more entries is a mechanical extension.
|
|
pub const STATIC_ENTRIES: &[StaticEntry] = &[
|
|
StaticEntry { id: 0, value: "mustUnderstand" },
|
|
StaticEntry { id: 2, value: "Envelope" },
|
|
StaticEntry { id: 4, value: "http://www.w3.org/2003/05/soap-envelope" },
|
|
StaticEntry { id: 6, value: "http://www.w3.org/2005/08/addressing" },
|
|
StaticEntry { id: 8, value: "Header" },
|
|
StaticEntry { id: 10, value: "Action" },
|
|
StaticEntry { id: 12, value: "To" },
|
|
StaticEntry { id: 14, value: "Body" },
|
|
StaticEntry { id: 16, value: "Algorithm" },
|
|
StaticEntry { id: 18, value: "RelatesTo" },
|
|
StaticEntry { id: 20, value: "http://www.w3.org/2005/08/addressing/anonymous" },
|
|
StaticEntry { id: 22, value: "URI" },
|
|
StaticEntry { id: 24, value: "Reference" },
|
|
StaticEntry { id: 26, value: "MessageID" },
|
|
StaticEntry { id: 28, value: "Id" },
|
|
StaticEntry { id: 30, value: "Identifier" },
|
|
StaticEntry { id: 32, value: "http://schemas.xmlsoap.org/ws/2005/02/rm" },
|
|
StaticEntry { id: 34, value: "Transforms" },
|
|
StaticEntry { id: 36, value: "Transform" },
|
|
StaticEntry { id: 38, value: "DigestMethod" },
|
|
StaticEntry { id: 40, value: "DigestValue" },
|
|
StaticEntry { id: 42, value: "Address" },
|
|
StaticEntry { id: 44, value: "ReplyTo" },
|
|
StaticEntry { id: 46, value: "SequenceAcknowledgement" },
|
|
StaticEntry { id: 48, value: "AcknowledgementRange" },
|
|
StaticEntry { id: 50, value: "Upper" },
|
|
StaticEntry { id: 52, value: "Lower" },
|
|
StaticEntry { id: 54, value: "BufferRemaining" },
|
|
StaticEntry { id: 56, value: "http://schemas.microsoft.com/ws/2006/05/rm" },
|
|
StaticEntry { id: 58, value: "http://schemas.xmlsoap.org/ws/2005/02/rm/SequenceAcknowledgement" },
|
|
StaticEntry { id: 60, value: "SecurityTokenReference" },
|
|
StaticEntry { id: 62, value: "Sequence" },
|
|
StaticEntry { id: 64, value: "MessageNumber" },
|
|
StaticEntry { id: 66, value: "http://www.w3.org/2000/09/xmldsig#" },
|
|
StaticEntry { id: 68, value: "http://www.w3.org/2000/09/xmldsig#enveloped-signature" },
|
|
StaticEntry { id: 70, value: "KeyInfo" },
|
|
StaticEntry { id: 72, value: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" },
|
|
StaticEntry { id: 74, value: "http://www.w3.org/2001/04/xmlenc#" },
|
|
StaticEntry { id: 76, value: "http://schemas.xmlsoap.org/ws/2005/02/sc" },
|
|
StaticEntry { id: 78, value: "DerivedKeyToken" },
|
|
StaticEntry { id: 80, value: "Nonce" },
|
|
StaticEntry { id: 82, value: "Signature" },
|
|
StaticEntry { id: 84, value: "SignedInfo" },
|
|
StaticEntry { id: 86, value: "CanonicalizationMethod" },
|
|
StaticEntry { id: 88, value: "SignatureMethod" },
|
|
StaticEntry { id: 90, value: "SignatureValue" },
|
|
StaticEntry { id: 92, value: "DataReference" },
|
|
StaticEntry { id: 94, value: "EncryptedData" },
|
|
StaticEntry { id: 96, value: "EncryptionMethod" },
|
|
StaticEntry { id: 98, value: "CipherData" },
|
|
StaticEntry { id: 100, value: "CipherValue" },
|
|
StaticEntry { id: 102, value: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" },
|
|
StaticEntry { id: 104, value: "Security" },
|
|
StaticEntry { id: 106, value: "Timestamp" },
|
|
StaticEntry { id: 108, value: "Created" },
|
|
StaticEntry { id: 110, value: "Expires" },
|
|
StaticEntry { id: 112, value: "Length" },
|
|
StaticEntry { id: 114, value: "ReferenceList" },
|
|
StaticEntry { id: 116, value: "ValueType" },
|
|
StaticEntry { id: 118, value: "Type" },
|
|
StaticEntry { id: 120, value: "EncryptedHeader" },
|
|
StaticEntry { id: 122, value: "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" },
|
|
StaticEntry { id: 124, value: "RequestSecurityTokenResponseCollection" },
|
|
StaticEntry { id: 126, value: "http://schemas.xmlsoap.org/ws/2005/02/trust" },
|
|
StaticEntry { id: 128, value: "http://schemas.xmlsoap.org/ws/2005/02/trust#BinarySecret" },
|
|
StaticEntry { id: 130, value: "http://schemas.microsoft.com/ws/2006/02/transactions" },
|
|
StaticEntry { id: 132, value: "s" },
|
|
StaticEntry { id: 134, value: "Fault" },
|
|
StaticEntry { id: 136, value: "MustUnderstand" },
|
|
StaticEntry { id: 138, value: "role" },
|
|
StaticEntry { id: 140, value: "relay" },
|
|
StaticEntry { id: 142, value: "Code" },
|
|
StaticEntry { id: 144, value: "Reason" },
|
|
StaticEntry { id: 146, value: "Text" },
|
|
StaticEntry { id: 148, value: "Node" },
|
|
StaticEntry { id: 150, value: "Role" },
|
|
StaticEntry { id: 152, value: "Detail" },
|
|
StaticEntry { id: 154, value: "Value" },
|
|
StaticEntry { id: 156, value: "Subcode" },
|
|
StaticEntry { id: 158, value: "NotUnderstood" },
|
|
StaticEntry { id: 160, value: "qname" },
|
|
StaticEntry { id: 162, value: "" },
|
|
StaticEntry { id: 164, value: "From" },
|
|
StaticEntry { id: 166, value: "FaultTo" },
|
|
StaticEntry { id: 168, value: "EndpointReference" },
|
|
StaticEntry { id: 170, value: "PortType" },
|
|
StaticEntry { id: 172, value: "ServiceName" },
|
|
StaticEntry { id: 174, value: "PortName" },
|
|
StaticEntry { id: 176, value: "ReferenceProperties" },
|
|
StaticEntry { id: 178, value: "RelationshipType" },
|
|
StaticEntry { id: 180, value: "Reply" },
|
|
StaticEntry { id: 182, value: "a" },
|
|
StaticEntry { id: 184, value: "http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" },
|
|
StaticEntry { id: 186, value: "Identity" },
|
|
StaticEntry { id: 188, value: "Spn" },
|
|
StaticEntry { id: 190, value: "Upn" },
|
|
StaticEntry { id: 192, value: "Rsa" },
|
|
StaticEntry { id: 194, value: "Dns" },
|
|
StaticEntry { id: 196, value: "X509v3Certificate" },
|
|
StaticEntry { id: 198, value: "http://www.w3.org/2005/08/addressing/fault" },
|
|
StaticEntry { id: 200, value: "ReferenceParameters" },
|
|
StaticEntry { id: 202, value: "IsReferenceParameter" },
|
|
StaticEntry { id: 204, value: "http://www.w3.org/2005/08/addressing/reply" },
|
|
StaticEntry { id: 206, value: "http://www.w3.org/2005/08/addressing/none" },
|
|
StaticEntry { id: 208, value: "Metadata" },
|
|
StaticEntry { id: 210, value: "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
|
|
StaticEntry { id: 212, value: "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous" },
|
|
StaticEntry { id: 214, value: "http://schemas.xmlsoap.org/ws/2004/08/addressing/fault" },
|
|
StaticEntry { id: 216, value: "http://schemas.xmlsoap.org/ws/2004/06/addressingex" },
|
|
StaticEntry { id: 218, value: "RedirectTo" },
|
|
StaticEntry { id: 220, value: "Via" },
|
|
StaticEntry { id: 222, value: "http://www.w3.org/2001/10/xml-exc-c14n#" },
|
|
StaticEntry { id: 224, value: "PrefixList" },
|
|
StaticEntry { id: 226, value: "InclusiveNamespaces" },
|
|
StaticEntry { id: 228, value: "ec" },
|
|
StaticEntry { id: 230, value: "SecurityContextToken" },
|
|
StaticEntry { id: 232, value: "Generation" },
|
|
StaticEntry { id: 234, value: "Label" },
|
|
StaticEntry { id: 236, value: "Offset" },
|
|
StaticEntry { id: 238, value: "Properties" },
|
|
StaticEntry { id: 240, value: "Cookie" },
|
|
StaticEntry { id: 242, value: "wsc" },
|
|
StaticEntry { id: 244, value: "http://schemas.xmlsoap.org/ws/2004/04/sc" },
|
|
StaticEntry { id: 246, value: "http://schemas.xmlsoap.org/ws/2004/04/security/sc/dk" },
|
|
StaticEntry { id: 248, value: "http://schemas.xmlsoap.org/ws/2004/04/security/sc/sct" },
|
|
StaticEntry { id: 250, value: "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/SCT" },
|
|
StaticEntry { id: 252, value: "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RSTR/SCT" },
|
|
StaticEntry { id: 254, value: "RenewNeeded" },
|
|
StaticEntry { id: 256, value: "BadContextToken" },
|
|
StaticEntry { id: 258, value: "c" },
|
|
StaticEntry { id: 260, value: "http://schemas.xmlsoap.org/ws/2005/02/sc/dk" },
|
|
StaticEntry { id: 262, value: "http://schemas.xmlsoap.org/ws/2005/02/sc/sct" },
|
|
StaticEntry { id: 264, value: "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT" },
|
|
StaticEntry { id: 266, value: "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT" },
|
|
StaticEntry { id: 268, value: "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Renew" },
|
|
StaticEntry { id: 270, value: "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT/Renew" },
|
|
StaticEntry { id: 272, value: "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Cancel" },
|
|
StaticEntry { id: 274, value: "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT/Cancel" },
|
|
StaticEntry { id: 276, value: "http://www.w3.org/2001/04/xmlenc#aes128-cbc" },
|
|
StaticEntry { id: 278, value: "http://www.w3.org/2001/04/xmlenc#kw-aes128" },
|
|
StaticEntry { id: 280, value: "http://www.w3.org/2001/04/xmlenc#aes192-cbc" },
|
|
StaticEntry { id: 282, value: "http://www.w3.org/2001/04/xmlenc#kw-aes192" },
|
|
StaticEntry { id: 284, value: "http://www.w3.org/2001/04/xmlenc#aes256-cbc" },
|
|
StaticEntry { id: 286, value: "http://www.w3.org/2001/04/xmlenc#kw-aes256" },
|
|
StaticEntry { id: 288, value: "http://www.w3.org/2001/04/xmlenc#des-cbc" },
|
|
StaticEntry { id: 290, value: "http://www.w3.org/2000/09/xmldsig#dsa-sha1" },
|
|
StaticEntry { id: 292, value: "http://www.w3.org/2001/10/xml-exc-c14n#WithComments" },
|
|
StaticEntry { id: 294, value: "http://www.w3.org/2000/09/xmldsig#hmac-sha1" },
|
|
StaticEntry { id: 296, value: "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256" },
|
|
StaticEntry { id: 298, value: "http://schemas.xmlsoap.org/ws/2005/02/sc/dk/p_sha1" },
|
|
StaticEntry { id: 300, value: "http://www.w3.org/2001/04/xmlenc#ripemd160" },
|
|
StaticEntry { id: 302, value: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" },
|
|
StaticEntry { id: 304, value: "http://www.w3.org/2000/09/xmldsig#rsa-sha1" },
|
|
StaticEntry { id: 306, value: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" },
|
|
StaticEntry { id: 308, value: "http://www.w3.org/2001/04/xmlenc#rsa-1_5" },
|
|
StaticEntry { id: 310, value: "http://www.w3.org/2000/09/xmldsig#sha1" },
|
|
StaticEntry { id: 312, value: "http://www.w3.org/2001/04/xmlenc#sha256" },
|
|
StaticEntry { id: 314, value: "http://www.w3.org/2001/04/xmlenc#sha512" },
|
|
StaticEntry { id: 316, value: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc" },
|
|
StaticEntry { id: 318, value: "http://www.w3.org/2001/04/xmlenc#kw-tripledes" },
|
|
StaticEntry { id: 320, value: "http://schemas.xmlsoap.org/2005/02/trust/tlsnego#TLS_Wrap" },
|
|
StaticEntry { id: 322, value: "http://schemas.xmlsoap.org/2005/02/trust/spnego#GSS_Wrap" },
|
|
StaticEntry { id: 324, value: "http://schemas.microsoft.com/ws/2006/05/security" },
|
|
StaticEntry { id: 326, value: "dnse" },
|
|
StaticEntry { id: 328, value: "o" },
|
|
StaticEntry { id: 330, value: "Password" },
|
|
StaticEntry { id: 332, value: "PasswordText" },
|
|
StaticEntry { id: 334, value: "Username" },
|
|
StaticEntry { id: 336, value: "UsernameToken" },
|
|
StaticEntry { id: 338, value: "BinarySecurityToken" },
|
|
StaticEntry { id: 340, value: "EncodingType" },
|
|
StaticEntry { id: 342, value: "KeyIdentifier" },
|
|
StaticEntry { id: 344, value: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" },
|
|
StaticEntry { id: 346, value: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#HexBinary" },
|
|
StaticEntry { id: 348, value: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Text" },
|
|
StaticEntry { id: 350, value: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier" },
|
|
StaticEntry { id: 352, value: "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ" },
|
|
StaticEntry { id: 354, value: "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ1510" },
|
|
StaticEntry { id: 356, value: "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID" },
|
|
StaticEntry { id: 358, value: "Assertion" },
|
|
StaticEntry { id: 360, value: "urn:oasis:names:tc:SAML:1.0:assertion" },
|
|
StaticEntry { id: 362, value: "http://docs.oasis-open.org/wss/oasis-wss-rel-token-profile-1.0.pdf#license" },
|
|
StaticEntry { id: 364, value: "FailedAuthentication" },
|
|
StaticEntry { id: 366, value: "InvalidSecurityToken" },
|
|
StaticEntry { id: 368, value: "InvalidSecurity" },
|
|
StaticEntry { id: 370, value: "k" },
|
|
StaticEntry { id: 372, value: "SignatureConfirmation" },
|
|
StaticEntry { id: 374, value: "TokenType" },
|
|
StaticEntry { id: 376, value: "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1" },
|
|
StaticEntry { id: 378, value: "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey" },
|
|
StaticEntry { id: 380, value: "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKeySHA1" },
|
|
StaticEntry { id: 382, value: "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" },
|
|
StaticEntry { id: 384, value: "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" },
|
|
StaticEntry { id: 386, value: "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID" },
|
|
StaticEntry { id: 388, value: "AUTH-HASH" },
|
|
StaticEntry { id: 390, value: "RequestSecurityTokenResponse" },
|
|
StaticEntry { id: 392, value: "KeySize" },
|
|
StaticEntry { id: 394, value: "RequestedTokenReference" },
|
|
StaticEntry { id: 396, value: "AppliesTo" },
|
|
StaticEntry { id: 398, value: "Authenticator" },
|
|
StaticEntry { id: 400, value: "CombinedHash" },
|
|
// ---- xsi / xsd / nil — heavily used by .NET XmlSerializer for
|
|
// value types in custom message-contract bodies. These are
|
|
// String 218..222 in the canonical table.
|
|
StaticEntry { id: 436, value: "type" },
|
|
StaticEntry { id: 438, value: "i" },
|
|
StaticEntry { id: 440, value: "http://www.w3.org/2001/XMLSchema-instance" },
|
|
StaticEntry { id: 442, value: "http://www.w3.org/2001/XMLSchema" },
|
|
StaticEntry { id: 444, value: "nil" },
|
|
];
|
|
|
|
/// Lookup an entry by static-dictionary ID. Returns `None` for IDs
|
|
/// outside the curated subset; callers should treat that as "unknown
|
|
/// static ID" and either extend [`STATIC_ENTRIES`] or fall through to
|
|
/// the inline-string path.
|
|
pub fn lookup_static(id: u32) -> Option<&'static str> {
|
|
STATIC_ENTRIES
|
|
.binary_search_by_key(&id, |e| e.id)
|
|
.ok()
|
|
.and_then(|idx| STATIC_ENTRIES.get(idx).map(|e| e.value))
|
|
}
|
|
|
|
/// Reverse lookup — find the static-dictionary ID for a string. Returns
|
|
/// `None` for strings not in the curated subset; encoders can either
|
|
/// extend [`STATIC_ENTRIES`] or fall through to the inline-string /
|
|
/// dynamic-dictionary path.
|
|
pub fn position_of_static(value: &str) -> Option<u32> {
|
|
static REVERSE: OnceLock<HashMap<&'static str, u32>> = OnceLock::new();
|
|
let map = REVERSE.get_or_init(|| {
|
|
let mut map = HashMap::with_capacity(STATIC_ENTRIES.len());
|
|
for entry in STATIC_ENTRIES {
|
|
// First-id-wins for duplicates (the canonical dictionary
|
|
// has TokenType at both id 116 (String 58) and id 374
|
|
// (String 187); we lock the lower id so round-trip lookups
|
|
// are deterministic).
|
|
map.entry(entry.value).or_insert(entry.id);
|
|
}
|
|
map
|
|
});
|
|
map.get(value).copied()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[allow(
|
|
clippy::unwrap_used,
|
|
clippy::expect_used,
|
|
clippy::panic,
|
|
clippy::indexing_slicing
|
|
)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn static_entries_have_monotonic_ids() {
|
|
let mut last = None;
|
|
for entry in STATIC_ENTRIES {
|
|
if let Some(prev) = last {
|
|
assert!(
|
|
entry.id > prev,
|
|
"static dictionary entries must be sorted by id; saw {prev} then {}",
|
|
entry.id
|
|
);
|
|
}
|
|
last = Some(entry.id);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn static_entries_use_even_ids() {
|
|
// `[MC-NBFS]` reserves odd ids for the per-session dynamic dict
|
|
// (`[MC-NBFX]` records 0xAA / 0xAB DictionaryText use the parity
|
|
// bit to discriminate). All static-table ids must be even.
|
|
for entry in STATIC_ENTRIES {
|
|
assert_eq!(
|
|
entry.id % 2,
|
|
0,
|
|
"static entry id {} ('{}') is odd — odd ids are reserved for the dynamic dict",
|
|
entry.id,
|
|
entry.value
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_returns_known_entries() {
|
|
assert_eq!(lookup_static(0), Some("mustUnderstand"));
|
|
assert_eq!(lookup_static(2), Some("Envelope"));
|
|
assert_eq!(
|
|
lookup_static(4),
|
|
Some("http://www.w3.org/2003/05/soap-envelope")
|
|
);
|
|
assert_eq!(lookup_static(8), Some("Header"));
|
|
assert_eq!(lookup_static(14), Some("Body"));
|
|
}
|
|
|
|
/// Fault-subset round-trip: every entry the SOAP-1.2 fault body
|
|
/// references must resolve. These are the exact dict ids the
|
|
/// AVEVA MxDataProvider sends in `dispatcher/fault` envelopes
|
|
/// (verified live via `MX_ASB_TRACE_REPLY`). Earlier versions of
|
|
/// this table mis-numbered the SOAP-fault subset (Fault was at id
|
|
/// 114 instead of 134), causing `decode_envelope` to silently drop
|
|
/// the resolved field name and the consumer to see opaque
|
|
/// `Static(N)` tokens.
|
|
#[test]
|
|
fn fault_subset_resolves_to_canonical_strings() {
|
|
assert_eq!(lookup_static(132), Some("s"));
|
|
assert_eq!(lookup_static(134), Some("Fault"));
|
|
assert_eq!(lookup_static(136), Some("MustUnderstand"));
|
|
assert_eq!(lookup_static(142), Some("Code"));
|
|
assert_eq!(lookup_static(144), Some("Reason"));
|
|
assert_eq!(lookup_static(146), Some("Text"));
|
|
assert_eq!(lookup_static(148), Some("Node"));
|
|
assert_eq!(lookup_static(150), Some("Role"));
|
|
assert_eq!(lookup_static(152), Some("Detail"));
|
|
assert_eq!(lookup_static(154), Some("Value"));
|
|
assert_eq!(lookup_static(156), Some("Subcode"));
|
|
}
|
|
|
|
#[test]
|
|
fn xmlserializer_extras_resolve() {
|
|
// The 436..444 high-id extras are essential for any
|
|
// [MessageContract] response body that uses XmlSerializer.
|
|
assert_eq!(lookup_static(436), Some("type"));
|
|
assert_eq!(lookup_static(438), Some("i"));
|
|
assert_eq!(
|
|
lookup_static(440),
|
|
Some("http://www.w3.org/2001/XMLSchema-instance")
|
|
);
|
|
assert_eq!(
|
|
lookup_static(442),
|
|
Some("http://www.w3.org/2001/XMLSchema")
|
|
);
|
|
assert_eq!(lookup_static(444), Some("nil"));
|
|
}
|
|
|
|
#[test]
|
|
fn position_of_static_round_trips_known_strings() {
|
|
assert_eq!(position_of_static("mustUnderstand"), Some(0));
|
|
assert_eq!(position_of_static("Envelope"), Some(2));
|
|
assert_eq!(position_of_static("Fault"), Some(134));
|
|
assert_eq!(position_of_static("Reason"), Some(144));
|
|
}
|
|
|
|
#[test]
|
|
fn lookup_returns_none_for_unknown() {
|
|
// 1 is odd → reserved for dynamic dict; should always be None.
|
|
assert_eq!(lookup_static(1), None);
|
|
// Way past the table.
|
|
assert_eq!(lookup_static(9999), None);
|
|
}
|
|
}
|