Skip to main content

sofie_core/encoding/
hex.rs

1//! Lowercase when written; either case when read.
2
3const 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
37/// Unlike `u8::from_str_radix`, a sign is not a digit.
38pub(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}