use std::collections::{ HashSet };
use std::result;
#[derive(Clone, Debug, PartialEq)]
pub struct HexString(String);
#[derive(Debug)]
pub enum HexStringError {
InvalidCharacter(char),
InvalidStringLength,
InvalidNibble(u8),
}
type Result<A> = result::Result<A, HexStringError>;
pub fn hexchar_to_nibble(c: &char) -> Result<u8> {
match c {
'0' => Ok(0),
'1' => Ok(1),
'2' => Ok(2),
'3' => Ok(3),
'4' => Ok(4),
'5' => Ok(5),
'6' => Ok(6),
'7' => Ok(7),
'8' => Ok(8),
'9' => Ok(9),
'a' => Ok(10),
'b' => Ok(11),
'c' => Ok(12),
'd' => Ok(13),
'e' => Ok(14),
'f' => Ok(15),
_ => Err(HexStringError::InvalidCharacter(*c))
}
}
pub fn nibble_to_hexchar(b: &u8) -> Result<char> {
match b {
0 => Ok('0'),
1 => Ok('1'),
2 => Ok('2'),
3 => Ok('3'),
4 => Ok('4'),
5 => Ok('5'),
6 => Ok('6'),
7 => Ok('7'),
8 => Ok('8'),
9 => Ok('9'),
10 => Ok('a'),
11 => Ok('b'),
12 => Ok('c'),
13 => Ok('d'),
14 => Ok('e'),
15 => Ok('f'),
_ => Err(HexStringError::InvalidNibble(*b)),
}
}
pub fn u8_to_hex_string(b: &u8) -> [char; 2] {
fn fmt_error(b: &u8) -> String {
format!("should never have an invalid nibble here. parts: {:?}, {:?}", (b & 0xf0) >> 4, b & 0x0f)
}
let upper = nibble_to_hexchar(&((b & 0xf0) >> 4)).expect(&fmt_error(b));
let lower = nibble_to_hexchar(&(b & 0x0f)).expect(&fmt_error(b));
[upper, lower]
}
impl HexString {
pub fn from_string(s: &str) -> Result<HexString> {
if s.len() % 2 != 0 { return Err(HexStringError::InvalidStringLength) }
let mut valid_chars = HashSet::new();
valid_chars.insert('0');
valid_chars.insert('1');
valid_chars.insert('2');
valid_chars.insert('3');
valid_chars.insert('4');
valid_chars.insert('5');
valid_chars.insert('6');
valid_chars.insert('7');
valid_chars.insert('8');
valid_chars.insert('9');
valid_chars.insert('a');
valid_chars.insert('b');
valid_chars.insert('c');
valid_chars.insert('d');
valid_chars.insert('e');
valid_chars.insert('f');
for c in s.chars() {
if ! valid_chars.contains(&c) {
return Err(HexStringError::InvalidCharacter(c));
}
}
Ok(HexString(String::from(s)))
}
pub fn from_bytes(v: &Vec<u8>) -> HexString {
HexString(v.iter().map(|b| u8_to_hex_string(b)).fold(String::new(), |mut acc, s| {
acc.push(s[0]);
acc.push(s[1]);
acc
}))
}
pub fn as_string(&self) -> String {
self.0.clone()
}
pub fn as_bytes(&self) -> Vec<u8> {
let mut i = self.0.chars();
let mut octets: Vec<Vec<char>> = Vec::new();
let mut octet: Vec<char> = i.by_ref().take(2).collect();
while octet.len() != 0 {
octets.push(octet.clone());
octet = i.by_ref().take(2).collect();
}
fn to_byte(octet: Vec<char>) -> u8 {
let upper = hexchar_to_nibble(&octet[0]).expect("There should never be an invalid hexchar here");
let lower = hexchar_to_nibble(&octet[1]).expect("There should never be an invalid hexchar here");
(upper << 4) | lower
}
octets.into_iter().map(|octet| to_byte(octet)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
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] }
fn string_repr() -> String { String::from("cbbbc6e19be63efcdd78327d2df850d92375af6a03934f35e47bd02d1b496c0c") }
#[test]
fn it_converts_bytes_to_string() {
let res = HexString::from_bytes(&byte_repr());
assert_eq!(*res.as_string(), string_repr());
}
#[test]
fn it_converts_string_to_bytes() {
match HexString::from_string(&string_repr()) {
Err(err) => panic!(format!("error encoding from string: {:?}", err)),
Ok(res) => assert_eq!(res.as_bytes(), byte_repr()),
}
}
#[test]
fn it_rejects_invalid_strings() {
match HexString::from_string("abcdefg") {
Err(_err) => (),
Ok(_) => panic!("did not reject a 'g' in the string"),
}
}
}