| 1 | //! Lowercase hex, for object ids and tokens. |
| 2 | |
| 3 | const DIGITS: &[u8; 16] = b"0123456789abcdef"; |
| 4 | |
| 5 | pub fn encode(bytes: &[u8]) -> String { |
| 6 | let mut s = String::with_capacity(bytes.len() * 2); |
| 7 | for b in bytes { |
| 8 | s.push(DIGITS[(b >> 4) as usize] as char); |
| 9 | s.push(DIGITS[(b & 0xf) as usize] as char); |
| 10 | } |
| 11 | s |
| 12 | } |
| 13 | |
| 14 | pub fn decode(s: &str) -> Option<Vec<u8>> { |
| 15 | if s.len() % 2 != 0 { |
| 16 | return None; |
| 17 | } |
| 18 | let nibble = |c: u8| match c { |
| 19 | b'0'..=b'9' => Some(c - b'0'), |
| 20 | b'a'..=b'f' => Some(c - b'a' + 10), |
| 21 | _ => None, |
| 22 | }; |
| 23 | s.as_bytes().chunks(2).map(|p| Some(nibble(p[0])? << 4 | nibble(p[1])?)).collect() |
| 24 | } |
| 25 | |
| 26 | #[cfg(test)] |
| 27 | mod tests { |
| 28 | #[test] |
| 29 | fn round_trip() { |
| 30 | let bytes = [0u8, 1, 0x7f, 0x80, 0xff]; |
| 31 | assert_eq!(super::encode(&bytes), "00017f80ff"); |
| 32 | assert_eq!(super::decode("00017f80ff").unwrap(), bytes); |
| 33 | assert!(super::decode("0g").is_none()); |
| 34 | assert!(super::decode("abc").is_none()); |
| 35 | assert!(super::decode("AB").is_none()); |
| 36 | } |
| 37 | } |