sofie_core/encoding/
hex.rs1const DIGITS: &[u8; 16] = b"0123456789abcdef";
4
5#[must_use]
6pub fn encode(bytes: &[u8]) -> String {
7 let mut text = String::with_capacity(bytes.len() * 2);
8 push(&mut text, bytes);
9 text
10}
11
12pub fn push(text: &mut String, bytes: &[u8]) {
13 for byte in bytes {
14 text.extend(digits(*byte).map(char::from));
15 }
16}
17
18#[must_use]
19pub fn decode(text: &str) -> Option<Vec<u8>> {
20 let (pairs, odd) = text.as_bytes().as_chunks::<2>();
21 if !odd.is_empty() {
22 return None;
23 }
24 pairs
25 .iter()
26 .map(|[high, low]| byte_of(*high, *low))
27 .collect()
28}
29
30pub(crate) fn digits(byte: u8) -> [u8; 2] {
31 [
32 DIGITS[usize::from(byte >> 4)],
33 DIGITS[usize::from(byte & 0x0f)],
34 ]
35}
36
37pub(crate) fn digit_value(digit: u8) -> Option<u8> {
39 match digit {
40 b'0'..=b'9' => Some(digit - b'0'),
41 b'a'..=b'f' => Some(digit - b'a' + 10),
42 b'A'..=b'F' => Some(digit - b'A' + 10),
43 _ => None,
44 }
45}
46
47pub(crate) fn byte_of(high: u8, low: u8) -> Option<u8> {
48 Some(digit_value(high)? << 4 | digit_value(low)?)
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn encodes_lowercase_and_appends() {
57 assert_eq!(encode(&[]), "");
58 assert_eq!(encode(&[0x00, 0x0f, 0xa0, 0xff]), "000fa0ff");
59 let mut text = String::from("x");
60 push(&mut text, &[0xab]);
61 assert_eq!(text, "xab");
62 }
63
64 #[test]
65 fn decodes_either_case_and_pairs_of_digits_only() {
66 assert_eq!(decode(""), Some(vec![]));
67 assert_eq!(decode("00ff10"), Some(vec![0, 255, 16]));
68 assert_eq!(decode("A0fF"), Some(vec![0xa0, 0xff]));
69 assert_eq!(decode("0"), None);
70 assert_eq!(decode("zz"), None);
71 assert_eq!(decode("+f"), None);
72 assert_eq!(decode("é0"), None);
73 }
74
75 #[test]
76 fn every_byte_round_trips() {
77 let bytes: Vec<u8> = (0..=u8::MAX).collect();
78 assert_eq!(decode(&encode(&bytes)), Some(bytes));
79 }
80}