[M5] mxaccess-asb: collect_asbidata_payloads concatenates chunked Bytes records

.NET's `XmlBinaryWriter.WriteBase64` chunks the byte array into
multiple consecutive NBFX `Bytes8/16/32` records when the total
exceeds the per-record budget. Live capture of a successful .NET
RegisterItemsResponse showed the Status ASBIData payload split into
`Bytes8(78) + Bytes8WithEndElement(1)` — total 79 bytes. Our decoder
walked tokens looking for a single `Text(Bytes(...))` after each
`<ASBIData>` element and stopped at the first chunk, returning a
truncated payload that hit `Codec(ShortRead)` when the consumer
tried to decode an ItemStatus from the partial bytes.

Fix: walk **all** consecutive `Text(Bytes)` tokens after `<ASBIData>`
and concatenate into a single payload before pushing to the result
vector. Mirrors WCF's reader behaviour, which reassembles the
chunks into one byte array via `XmlReader.ReadElementContentAsBase64`.

Workspace: 710 unit tests pass. Live state: AuthenticateMe is
accepted, RegisterItemsResponse decodes structurally — the remaining
"MissingField Status" error reflects a server-side semantic outcome
(server returned empty Status array) rather than a protocol bug,
likely tag-resolution related and outside F28's scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 19:36:38 -04:00
parent 104efc4e9b
commit cf97eab396
+24 -2
View File
@@ -1157,8 +1157,30 @@ pub fn collect_asbidata_payloads(tokens: &[NbfxToken]) -> Vec<Vec<u8>> {
) {
inner += 1;
}
if let Some(NbfxToken::Text(NbfxText::Bytes(payload))) = tokens.get(inner) {
out.push(payload.clone());
// CONCATENATE all consecutive `Bytes` text records.
// .NET's `XmlBinaryWriter.WriteBase64` chunks the byte
// array into multiple NBFX `Bytes8/16/32` records when
// the total exceeds the per-record budget — captured
// live response showed an ASBIData payload split into
// a Bytes8(78) + Bytes8WithEndElement(1) pair, total
// 79 bytes. Earlier we only returned the first chunk
// and the consumer hit a `ShortRead` decoding the
// truncated ItemStatus. The decoder collapses adjacent
// Bytes-followed-by-Bytes pairs into a single text
// token, but a `Bytes`-then-`EndElement` (from the
// `WithEndElement` variant) leaves a sequence of
// `Bytes` tokens we walk here.
let mut combined: Option<Vec<u8>> = None;
while let Some(NbfxToken::Text(NbfxText::Bytes(payload))) = tokens.get(inner)
{
match combined.as_mut() {
Some(buf) => buf.extend_from_slice(payload),
None => combined = Some(payload.clone()),
}
inner += 1;
}
if let Some(buf) = combined {
out.push(buf);
}
}
}