hex_string/
lib.rs

1//! A utilty library for handling Hex strings
2//!
3//! The digest operations in sha2 return the result as u8 vectors. But a lot of command line
4//! applicaions, like sha256sum, return byte strings. I was unable to find an obvious way to handle
5//! this in rust, so this module provides a clear well-defined HexString, loaders from a regular
6//! string of hex values and from a vector of bytes, and output representations in both forms.
7
8use std::collections::{ HashSet };
9use std::result;
10
11/// HexString provides a structured representation of a hex string. It is guaranteed to be a valid
12/// string, whether initialized from a string or from a byte vector.
13#[derive(Clone, Debug, PartialEq)]
14pub struct HexString(String);
15
16#[derive(Debug)]
17pub enum HexStringError {
18    /// There was an invalid character in the hex string
19    InvalidCharacter(char),
20
21    /// All hex strings must be an even length in order to represent bytes because each two
22    /// characters represents one byte
23    InvalidStringLength,
24
25    /// Somehow the conversion function tried to convert a value outside the range of 0-15
26    /// (inclusive) into a hex value. This should only be raised from a direct call to
27    /// `nibble_to_hexchar`, or in the case of a bug in this module.
28    InvalidNibble(u8),
29}
30
31type Result<A> = result::Result<A, HexStringError>;
32
33
34/// Given a character, convert it into a u8 in the range 0-15 (inclusive).
35///
36/// Note that Rust does not have an obvious nibble data type, so we approximate with the lower 4
37/// bits of a u8.
38///
39/// This will raise InvalidCharacte if the provided character is not in the range 0-9 or a-f
40/// (lower-case only).
41pub fn hexchar_to_nibble(c: &char) -> Result<u8> {
42    match c {
43        '0' => Ok(0),
44        '1' => Ok(1),
45        '2' => Ok(2),
46        '3' => Ok(3),
47        '4' => Ok(4),
48        '5' => Ok(5),
49        '6' => Ok(6),
50        '7' => Ok(7),
51        '8' => Ok(8),
52        '9' => Ok(9),
53        'a' => Ok(10),
54        'b' => Ok(11),
55        'c' => Ok(12),
56        'd' => Ok(13),
57        'e' => Ok(14),
58        'f' => Ok(15),
59        _ => Err(HexStringError::InvalidCharacter(*c))
60    }
61}
62
63
64/// Given a nibble (a u8 value in the range 0-15), convert it to its corresponding character
65/// representation.
66///
67/// This will raise InvalidNibble if the value provided is outside the range 0-15.
68pub fn nibble_to_hexchar(b: &u8) -> Result<char>  {
69    match b {
70        0 => Ok('0'),
71        1 => Ok('1'),
72        2 => Ok('2'),
73        3 => Ok('3'),
74        4 => Ok('4'),
75        5 => Ok('5'),
76        6 => Ok('6'),
77        7 => Ok('7'),
78        8 => Ok('8'),
79        9 => Ok('9'),
80        10 => Ok('a'),
81        11 => Ok('b'),
82        12 => Ok('c'),
83        13 => Ok('d'),
84        14 => Ok('e'),
85        15 => Ok('f'),
86        _ => Err(HexStringError::InvalidNibble(*b)),
87    }
88}
89
90
91/// Convert a byte to its two-character hex string representation
92pub fn u8_to_hex_string(b: &u8) -> [char; 2] {
93    fn fmt_error(b: &u8) -> String {
94        format!("should never have an invalid nibble here. parts: {:?}, {:?}", (b & 0xf0) >> 4, b & 0x0f)
95    }
96    let upper = nibble_to_hexchar(&((b & 0xf0) >> 4)).expect(&fmt_error(b));
97    let lower = nibble_to_hexchar(&(b & 0x0f)).expect(&fmt_error(b));
98    [upper, lower]
99}
100
101
102impl HexString {
103    /// Initialize a HexString from an actual hex string. The input string must be of an even
104    /// length (since it takes two hex characters to represent a byte) and must contain only
105    /// characters in the range 0-9 and a-f.
106    ///
107    /// This will return an InvalidStringLength error if the length is not even, and
108    /// InvalidCharacter if any non-hex character is detected.
109    pub fn from_string(s: &str) -> Result<HexString> {
110        if s.len() % 2 != 0 { return Err(HexStringError::InvalidStringLength) }
111
112        let mut valid_chars = HashSet::new();
113        valid_chars.insert('0');
114        valid_chars.insert('1');
115        valid_chars.insert('2');
116        valid_chars.insert('3');
117        valid_chars.insert('4');
118        valid_chars.insert('5');
119        valid_chars.insert('6');
120        valid_chars.insert('7');
121        valid_chars.insert('8');
122        valid_chars.insert('9');
123        valid_chars.insert('a');
124        valid_chars.insert('b');
125        valid_chars.insert('c');
126        valid_chars.insert('d');
127        valid_chars.insert('e');
128        valid_chars.insert('f');
129
130        for c in s.chars() {
131            if ! valid_chars.contains(&c) {
132                return Err(HexStringError::InvalidCharacter(c));
133            }
134        }
135        Ok(HexString(String::from(s)))
136    }
137
138    /// Initialize a hex strign from a binary vector. This function cannot fail.
139    pub fn from_bytes(v: &Vec<u8>) -> HexString {
140        HexString(v.iter().map(|b| u8_to_hex_string(b)).fold(String::new(), |mut acc, s| {
141            acc.push(s[0]);
142            acc.push(s[1]);
143            acc
144        }))
145    }
146
147    /// Return a String representation
148    pub fn as_string(&self) -> String {
149        self.0.clone()
150    }
151
152    /// Return a byte representation
153    pub fn as_bytes(&self) -> Vec<u8> {
154        let mut i = self.0.chars();
155        let mut octets: Vec<Vec<char>> = Vec::new();
156
157        let mut octet: Vec<char> = i.by_ref().take(2).collect();
158        while octet.len() != 0 {
159            octets.push(octet.clone());
160            octet = i.by_ref().take(2).collect();
161        }
162
163        fn to_byte(octet: Vec<char>) -> u8 {
164            let upper = hexchar_to_nibble(&octet[0]).expect("There should never be an invalid hexchar here");
165            let lower = hexchar_to_nibble(&octet[1]).expect("There should never be an invalid hexchar here");
166            (upper << 4) | lower
167        }
168
169        octets.into_iter().map(|octet| to_byte(octet)).collect()
170    }
171}
172
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    fn byte_repr() -> Vec<u8> { vec![203, 187, 198, 225, 155, 230, 62, 252, 221, 120, 50, 125, 45, 248, 80, 217, 35, 117, 175, 106, 3, 147, 79, 53, 228, 123, 208, 45, 27, 73, 108, 12] }
179    fn string_repr() -> String { String::from("cbbbc6e19be63efcdd78327d2df850d92375af6a03934f35e47bd02d1b496c0c") }
180
181    #[test]
182    fn it_converts_bytes_to_string() {
183        let res = HexString::from_bytes(&byte_repr());
184        assert_eq!(*res.as_string(), string_repr());
185    }
186
187    #[test]
188    fn it_converts_string_to_bytes() {
189        match HexString::from_string(&string_repr()) {
190            Err(err) => panic!(format!("error encoding from string: {:?}", err)),
191            Ok(res) => assert_eq!(res.as_bytes(), byte_repr()),
192        }
193    }
194
195    #[test]
196    fn it_rejects_invalid_strings() {
197        match HexString::from_string("abcdefg") {
198            Err(_err) => (),
199            Ok(_) => panic!("did not reject a 'g' in the string"),
200        }
201    }
202}