Skip to content

Repository files navigation

safe-decode

Crates.io Docs.rs Rust 1.75+ License: Apache-2.0 Sponsor

CI unsafe forbidden Security advisories

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 safe_decode::{
    decode_utf16le_keep_nuls, decode_utf16le_trim_end_nuls, decode_utf16le_until_nul,
    split_utf16le_on_nul,
};

// UTF-16LE 'A', NUL, 'B', NUL — the same bytes, four correct answers.
let bytes = b"A\0\0\0B\0\0\0";
assert_eq!(decode_utf16le_keep_nuls(bytes).text, "A\0B\0");      // NUL is a character
assert_eq!(decode_utf16le_until_nul(bytes).text, "A");           // NUL terminates
assert_eq!(decode_utf16le_trim_end_nuls(bytes).text, "A\0B");    // NUL is padding
let parts = split_utf16le_on_nul(bytes);                         // NUL separates
assert_eq!(parts.iter().map(|d| d.text.as_str()).collect::<Vec<_>>(), ["A", "B", ""]);

Install

[dependencies]
safe-decode = "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 safe_decode::decode_utf16le_keep_nuls;

let d = decode_utf16le_keep_nuls(&[0x41, 0x00, 0x00, 0xD8, 0x42]);
assert_eq!(d.text, "A\u{FFFD}");
assert_eq!(d.unpaired_surrogates, 1); // a lone high surrogate, replaced
assert!(d.dangling_byte);             // odd length: a byte could not form a code unit
assert!(d.is_lossy());

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-fuzz target per public function drives arbitrary byte slices; a panic is a build failure, and cargo fuzz check runs in CI.
  • Panic-free by lint and by construction. unwrap_used, expect_used and indexing_slicing are deny, and unsafe_code is forbid. Code units come from chunks_exact through a fallible array conversion rather than an index; truncation goes through Vec::truncate rather 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] plus alloc, 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

About

Panic-free, allocating byte-to-value transforms with no format knowledge: ROT13, the UTF-16 decoder family with its NUL policy named in each function, and hex rendering.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages