//! 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> { 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" ); } }