Skip to main content

clawft_kernel/workload_pkg/
codec.rs

1//! Small, strict hex and base64 codecs (the kernel carries no hex/base64
2//! crates, and these inputs are all short, bounded key material).
3
4/// Lower-case hex encoding.
5pub fn hex_encode(bytes: &[u8]) -> String {
6    const DIGITS: &[u8; 16] = b"0123456789abcdef";
7    let mut s = String::with_capacity(bytes.len() * 2);
8    for b in bytes {
9        s.push(DIGITS[(b >> 4) as usize] as char);
10        s.push(DIGITS[(b & 0x0f) as usize] as char);
11    }
12    s
13}
14
15/// Decode lower-case hex of exactly `N` bytes. Upper-case is refused so each
16/// value has one spelling.
17pub fn hex_decode_exact<const N: usize>(s: &str) -> Option<[u8; N]> {
18    if s.len() != N * 2 {
19        return None;
20    }
21    let mut out = [0u8; N];
22    let bytes = s.as_bytes();
23    for (i, slot) in out.iter_mut().enumerate() {
24        let hi = hex_val(bytes[2 * i])?;
25        let lo = hex_val(bytes[2 * i + 1])?;
26        *slot = (hi << 4) | lo;
27    }
28    Some(out)
29}
30
31/// True when `s` is exactly `len` lower-case hex characters.
32pub fn is_lower_hex(s: &str, len: usize) -> bool {
33    s.len() == len && s.bytes().all(|b| hex_val(b).is_some())
34}
35
36fn hex_val(b: u8) -> Option<u8> {
37    match b {
38        b'0'..=b'9' => Some(b - b'0'),
39        b'a'..=b'f' => Some(b - b'a' + 10),
40        _ => None,
41    }
42}
43
44/// Decode base64 in either the standard or the URL-safe alphabet, with or
45/// without `=` padding. Whitespace is refused. Non-canonical trailing bits
46/// are refused so an encoding cannot be malleated.
47pub fn base64_decode(s: &str) -> Option<Vec<u8>> {
48    let trimmed = s.trim_end_matches('=');
49    if s.len() - trimmed.len() > 2 || trimmed.len() % 4 == 1 {
50        return None;
51    }
52    let mut out = Vec::with_capacity(trimmed.len() * 3 / 4);
53    let mut acc: u32 = 0;
54    let mut bits = 0u32;
55    for c in trimmed.bytes() {
56        let v = match c {
57            b'A'..=b'Z' => c - b'A',
58            b'a'..=b'z' => c - b'a' + 26,
59            b'0'..=b'9' => c - b'0' + 52,
60            b'+' | b'-' => 62,
61            b'/' | b'_' => 63,
62            _ => return None,
63        } as u32;
64        acc = (acc << 6) | v;
65        bits += 6;
66        if bits >= 8 {
67            bits -= 8;
68            out.push((acc >> bits) as u8);
69            acc &= (1 << bits) - 1;
70        }
71    }
72    if acc != 0 {
73        return None;
74    }
75    Some(out)
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn hex_round_trip_and_strictness() {
84        let bytes = [0u8, 1, 0xab, 0xff];
85        let s = hex_encode(&bytes);
86        assert_eq!(s, "0001abff");
87        assert_eq!(hex_decode_exact::<4>(&s), Some(bytes));
88        assert_eq!(hex_decode_exact::<4>("0001ABFF"), None);
89        assert_eq!(hex_decode_exact::<4>("0001ab"), None);
90        assert!(is_lower_hex("ab01", 4));
91        assert!(!is_lower_hex("ab0g", 4));
92    }
93
94    #[test]
95    fn base64_both_alphabets() {
96        assert_eq!(base64_decode("aGVsbG8").unwrap(), b"hello");
97        assert_eq!(base64_decode("aGVsbG8=").unwrap(), b"hello");
98        assert_eq!(base64_decode("-_8").unwrap(), vec![0xfb, 0xff]);
99        assert_eq!(base64_decode("+/8").unwrap(), vec![0xfb, 0xff]);
100        assert!(base64_decode("a").is_none());
101        assert!(base64_decode("aGVsbG8===").is_none());
102        assert!(base64_decode("aGVs bG8").is_none());
103        // Non-canonical trailing bits ("aGVsbG9" ends with a stray bit).
104        assert!(base64_decode("aGVsbG9=").is_none());
105    }
106}