189 lines
5.7 KiB
Rust
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"
|
|
);
|
|
}
|
|
}
|