1use std::collections::{ HashSet };
9use std::result;
10
11#[derive(Clone, Debug, PartialEq)]
14pub struct HexString(String);
15
16#[derive(Debug)]
17pub enum HexStringError {
18 InvalidCharacter(char),
20
21 InvalidStringLength,
24
25 InvalidNibble(u8),
29}
30
31type Result<A> = result::Result<A, HexStringError>;
32
33
34pub 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
64pub 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
91pub 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 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 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 pub fn as_string(&self) -> String {
149 self.0.clone()
150 }
151
152 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}