safe-decode
Byte-to-text transforms that name what they do to NUL. Fourteen hand-rolled UTF-16 decoders across one codebase, four different NUL policies, and every one of them called decode_utf16le. This crate ends that: the policy is in the function name, so choosing the wrong one is a decision rather than an accident.
use ;
// UTF-16LE 'A', NUL, 'B', NUL — the same bytes, four correct answers.
let bytes = b"A\0\0\0B\0\0\0";
assert_eq!; // NUL is a character
assert_eq!; // NUL terminates
assert_eq!; // NUL is padding
let parts = split_utf16le_on_nul; // NUL separates
assert_eq!;
Install
[]
= "0.1"
A decode that lost something says so
Every decode returns a DecodedUtf16, not a bare String. text is always well-formed UTF-8, so a caller may ignore the rest — but the rest is what a forensic report needs, because "the field decoded" and "the field decoded faithfully" are different claims.
use decode_utf16le_keep_nuls;
let d = decode_utf16le_keep_nuls;
assert_eq!;
assert_eq!; // a lone high surrogate, replaced
assert!; // odd length: a byte could not form a code unit
assert!;
What's here
| Function | Behaviour |
|---|---|
rot13 |
Rotate ASCII letters by thirteen; everything else, including non-ASCII, unchanged. |
decode_utf16le_keep_nuls · decode_utf16be_keep_nuls |
Decode the whole slice; NUL code units become U+0000 characters. |
decode_utf16le_until_nul · decode_utf16be_until_nul |
Stop at the first NUL; discard it and everything after. |
decode_utf16le_trim_end_nuls · decode_utf16be_trim_end_nuls |
Decode all, then strip trailing NULs only; interior NULs kept. |
split_utf16le_on_nul · split_utf16be_on_nul |
Split on NUL and decode every segment, empty ones included. |
to_hex_lower · to_hex_upper |
Two hex digits per byte, no separator. |
Trust, but verify
- Fuzzed. A
cargo-fuzztarget per public function drives arbitrary byte slices; a panic is a build failure, andcargo fuzz checkruns in CI. - Panic-free by lint and by construction.
unwrap_used,expect_usedandindexing_slicingaredeny, andunsafe_codeisforbid. Code units come fromchunks_exactthrough a fallible array conversion rather than an index; truncation goes throughVec::truncaterather than a range slice; the hex digit is masked to a nibble. There is no construct in the crate that can panic on any input. - 100% line and function coverage, gated in CI.
- Zero dependencies,
#![no_std]plusalloc, MSRV 1.75 verified in CI — nothing here raises a consumer's floor.
Scope
A function belongs in this crate only if it is panic-free, allocating, format-agnostic, and free of domain knowledge. alloc is the line between this crate and safe-read, its no_std-without-allocator sibling for fixed-width integer reads.
Deliberately absent: anything that needs to know what the bytes mean. REG_MULTI_SZ's double-NUL terminator and MRUListEx's 0xFFFFFFFF sentinel are Windows registry conventions, not properties of an encoding, so they live with the crate that owns that knowledge. split_utf16le_on_nul gives that caller the structural half; the convention stays where the fact is. See docs/decisions/.
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd