0002 — MultiSz and MRUListEx are registry conventions, not encodings¶
Status: Accepted
Context¶
ADR-0019 of the fleet constitution
moves algorithms out of forensicnomicon — a knowledge crate — into primitive
crates, and lists MultiSz and MruListEx among the candidates for this one. It
also states the membership test each candidate must pass: panic-free ·
allocating · format-agnostic · no domain knowledge.
Both candidates are describable structurally, which is what makes the call non-obvious:
MultiSz(REG_MULTI_SZ) — UTF-16LE strings separated by NUL code units, the sequence terminated by an empty string, i.e. a double NUL.MRUListEx— little-endianu32values, the list terminated by0xFFFFFFFF.
Decision¶
MultiSz is split. The structural half ships; the convention does not.
split_utf16le_on_nul (and its big-endian twin) performs the part that is a
property of the encoding: split a UTF-16 buffer on NUL code units and decode
every segment. It is total — n NULs yield n + 1 segments, empty ones
included — so it describes the bytes and asserts nothing about them.
What it deliberately omits is everything specific to REG_MULTI_SZ: that the
sequence ends at a double NUL, that trailing empty segments are terminator
padding rather than data, and that empty strings in the middle should or should
not be dropped. Those are Windows registry facts. A caller composes them in one
line, in the crate where the fact already lives:
let values: Vec<String> = split_utf16le_on_nul(raw)
.into_iter()
.take_while(|s| !s.text.is_empty()) // REG_MULTI_SZ: the double NUL ends it
.map(|s| s.text)
.collect();
The dividing question is not "can this be described without naming Windows?" Almost anything can. It is "would this still be correct for a format I have never seen that shares the structure?" Splitting on NUL: yes. Stopping at the double NUL and discarding the tail: only for formats that happen to agree with the registry.
MRUListEx is excluded entirely.
The sentinel 0xFFFFFFFF is not a property of a u32 array — it is the registry
MRU convention. A different format terminates with a different sentinel, or a
count, or the field length. Generalising it to
read_u32le_list_until(bytes, sentinel) would produce a function whose only
content is three iterator combinators over safe_read::le_u32, with no bounds
risk to centralise and no behaviour to converge; there is no defect class it
prevents. The knowledge stays with Decoder::MruListEx in forensicnomicon,
which is where ADR-0019 says facts belong, and the read it performs already goes
through safe-read.
Consequences¶
forensicnomiconkeeps theMultiSzandMruListExtaxonomy entries, as ADR-0019 intends; itsMultiSzdecoding delegates tosplit_utf16le_on_nuland applies the terminator rule itself. ItsMruListExloop stays where it is.- The membership test stays decidable. Admitting
MRUListEx— a named constant from one vendor's registry format — would have made "no domain knowledge" negotiable, and a primitive crate whose membership test is negotiable becomes the junk drawer ADR-0019 §3 exists to prevent.