[M5] mxaccess-asb: MX_ASB_TRACE_REPLY trace + F30/F31 followups

Adds env-gated diagnostic trace `MX_ASB_TRACE_REPLY` that, on every
incoming SizedEnvelope, prints the raw reply bytes + decoded body
tokens (capped at 64) before any consumer-level decode runs. Used to
isolate the next blocker after F28's wire-format fixes landed: with
canonical XML signing, registry-driven DH params, dynamic-dict id
management, ConnectionValidator wire-format-per-action, chunked
ASBIData decode, and 0x0A `ShortDictionaryXmlnsAttribute` all in
place, AuthenticateMe is accepted by the server and a real
RegisterItemsResponse comes back — but it decodes to an opaque token
stream of `<b:Static(43)>false</b:Static(43)>` etc. because every
field name is dict-encoded against the response's own binary header
pre-pop and we never resolve those ids on the read side.

Two new follow-ups capture the remaining work:
- **F30**: resolve dict-id element/attribute names on the read side.
  Mirror the F28 write-side fix: read-side dynamic dict accumulates
  session strings via `intern`, and `decode_tokens` (or a post-pass)
  needs to substitute `NbfxName::Static(id)` with the resolved
  `NbfxName::Inline(name)` so downstream `find_element_named` /
  `collect_asbidata_payloads` match.
- **F31**: server response indicates `successField=false` with an
  empty Status array on Register. Hypotheses (in order): (a) silent
  HMAC mismatch despite F23 deterministic parity; (b) request-side
  wire-byte delta the server tolerates but interprets as 0 items;
  (c) tag does not resolve in the live Galaxy state. Resolution
  needs F30 first to read the actual Status array + error codes.

Workspace: 710 unit tests pass. Clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 20:35:29 -04:00
parent cf97eab396
commit 703c540bdc
2 changed files with 33 additions and 0 deletions
+19
View File
@@ -170,7 +170,18 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
let record = read_record(&mut self.stream).await?;
match record {
NmfRecord::SizedEnvelope(reply_bytes) => {
let trace_reply = std::env::var("MX_ASB_TRACE_REPLY").ok().is_some();
if trace_reply {
eprintln!("asb.reply.bytes_len={}", reply_bytes.len());
eprintln!("asb.reply.bytes_hex={}", hex_dump(&reply_bytes));
}
let decoded = decode_envelope(&reply_bytes, &mut self.read_dictionary)?;
if trace_reply {
eprintln!("asb.reply.body_tokens.len={}", decoded.body_tokens.len());
for (i, tok) in decoded.body_tokens.iter().enumerate().take(64) {
eprintln!("asb.reply.body[{i}]={tok:?}");
}
}
if let Some(fault) = detect_soap_fault(&decoded) {
return Err(fault);
}
@@ -737,6 +748,14 @@ fn detect_soap_fault(decoded: &crate::DecodedEnvelope) -> Option<ClientError> {
})
}
/// Hex dump for diagnostic traces. First 256 bytes only to keep
/// MX_ASB_TRACE_REPLY output bounded.
fn hex_dump(bytes: &[u8]) -> String {
let cap = bytes.len().min(256);
let slice = bytes.get(..cap).unwrap_or(&[]);
slice.iter().map(|b| format!("{b:02x}")).collect()
}
// ---- error type ----------------------------------------------------------
#[derive(Debug, thiserror::Error)]