preserves/implementations/rust/preserves/src/hex.rs

189 lines
5.7 KiB
Rust

//! Utilities for producing and flexibly parsing strings containing hexadecimal binary data.
/// Utility for parsing hex binary data from strings.
pub enum HexParser {
/// "Liberal" parsing simply ignores characters that are not (case-insensitive) hex digits.
Liberal,
/// "Whitespace allowed" parsing ignores whitespace, but fails a parse on anything other
/// than hex or whitespace.
WhitespaceAllowed,
/// "Strict" parsing accepts only (case-insensitive) hex digits; no whitespace, no other
/// characters.
Strict,
}
/// Utility for formatting binary data as hex.
pub enum HexFormatter {
/// Produces LF-separated lines with a maximum of `usize` hex digits in each line.
Lines(usize),
/// Simply packs hex digits in as tightly as possible.
Packed,
}
/// Convert a number 0..15 to a hex digit [char].
///
/// # Panics
///
/// Panics if given `v` outside the range 0..15 inclusive.
///
pub fn hexdigit(v: u8) -> char {
char::from_digit(v as u32, 16).expect("hexadecimal digit value")
}
impl HexParser {
/// Decode `s` according to the given rules for `self`; see [HexParser].
/// If the parse fails, yield `None`.
pub fn decode(&self, s: &str) -> Option<Vec<u8>> {
let mut result = Vec::new();
let mut buf: u8 = 0;
let mut buf_full = false;
for c in s.chars() {
match c.to_digit(16) {
None => match self {
HexParser::Liberal => (),
HexParser::WhitespaceAllowed => {
if !c.is_whitespace() {
return None;
}
}
HexParser::Strict => return None,
},
Some(nibble) => {
if buf_full {
result.push(buf << 4 | (nibble as u8));
buf_full = false;
} else {
buf = nibble as u8;
buf_full = true;
}
}
}
}
if buf_full {
None // odd number of hexits
} else {
Some(result)
}
}
}
impl HexFormatter {
/// Encode `bs` according to the given rules for `self; see [HexFormatter].
pub fn encode(&self, bs: &[u8]) -> String {
match self {
HexFormatter::Lines(max_line_length) => {
let mut lines = Vec::new();
let mut line = String::new();
for b in bs {
if line.len() + 2 > *max_line_length {
lines.push(std::mem::take(&mut line));
}
line.push(hexdigit(b >> 4));
line.push(hexdigit(b & 15));
}
lines.push(std::mem::take(&mut line));
lines.join("\n")
}
HexFormatter::Packed => {
let mut result = String::new();
for b in bs {
result.push(hexdigit(b >> 4));
result.push(hexdigit(b & 15));
}
result
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_decode_packed() {
let s = "01ab00ff";
assert_eq!(HexParser::Strict.decode(s), Some(vec![1, 171, 0, 255]));
assert_eq!(
HexParser::WhitespaceAllowed.decode(s),
Some(vec![1, 171, 0, 255])
);
assert_eq!(HexParser::Liberal.decode(s), Some(vec![1, 171, 0, 255]));
}
#[test]
fn test_decode_whitespace() {
let s = "01ab 00ff";
assert_eq!(HexParser::Strict.decode(s), None);
assert_eq!(
HexParser::WhitespaceAllowed.decode(s),
Some(vec![1, 171, 0, 255])
);
assert_eq!(HexParser::Liberal.decode(s), Some(vec![1, 171, 0, 255]));
}
#[test]
fn test_decode_liberal() {
let s = "01ab zz 00ff";
assert_eq!(HexParser::Strict.decode(s), None);
assert_eq!(HexParser::WhitespaceAllowed.decode(s), None);
assert_eq!(HexParser::Liberal.decode(s), Some(vec![1, 171, 0, 255]));
}
#[test]
fn test_encode_lines() {
assert_eq!(
HexFormatter::Lines(10).encode(&vec![0x5a; 11]),
"5a5a5a5a5a\n5a5a5a5a5a\n5a"
);
assert_eq!(
HexFormatter::Lines(10).encode(&vec![0x5a; 10]),
"5a5a5a5a5a\n5a5a5a5a5a"
);
assert_eq!(
HexFormatter::Lines(10).encode(&vec![0x5a; 9]),
"5a5a5a5a5a\n5a5a5a5a"
);
assert_eq!(
HexFormatter::Lines(9).encode(&vec![0x5a; 11]),
"5a5a5a5a\n5a5a5a5a\n5a5a5a"
);
assert_eq!(
HexFormatter::Lines(9).encode(&vec![0x5a; 10]),
"5a5a5a5a\n5a5a5a5a\n5a5a"
);
assert_eq!(
HexFormatter::Lines(9).encode(&vec![0x5a; 9]),
"5a5a5a5a\n5a5a5a5a\n5a"
);
assert_eq!(
HexFormatter::Lines(8).encode(&vec![0x5a; 11]),
"5a5a5a5a\n5a5a5a5a\n5a5a5a"
);
assert_eq!(
HexFormatter::Lines(8).encode(&vec![0x5a; 10]),
"5a5a5a5a\n5a5a5a5a\n5a5a"
);
assert_eq!(
HexFormatter::Lines(8).encode(&vec![0x5a; 9]),
"5a5a5a5a\n5a5a5a5a\n5a"
);
}
#[test]
fn test_encode_packed() {
assert_eq!(
HexFormatter::Packed.encode(&vec![0x5a; 11]),
"5a5a5a5a5a5a5a5a5a5a5a"
);
assert_eq!(
HexFormatter::Packed.encode(&vec![0x5a; 10]),
"5a5a5a5a5a5a5a5a5a5a"
);
assert_eq!(
HexFormatter::Packed.encode(&vec![0x5a; 9]),
"5a5a5a5a5a5a5a5a5a"
);
}
}