michal/tit

Blob: src/codec.rs

Raw · Blame

pub(crate) fn encode_lower_hex(bytes: &[u8]) -> String {
    const HEX: &[u8; 16] = b"0123456789abcdef";
    let mut encoded = String::with_capacity(bytes.len().saturating_mul(2));
    for byte in bytes {
        encoded.push(char::from(HEX[usize::from(byte >> 4)]));
        encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
    }
    encoded
}

pub(crate) fn decode_lower_hex(encoded: &[u8]) -> Option<Vec<u8>> {
    decode_hex(encoded, false)
}

pub(crate) fn decode_ascii_hex(encoded: &[u8]) -> Option<Vec<u8>> {
    decode_hex(encoded, true)
}

fn decode_hex(encoded: &[u8], uppercase: bool) -> Option<Vec<u8>> {
    if !encoded.len().is_multiple_of(2) {
        return None;
    }
    encoded
        .chunks_exact(2)
        .map(|pair| {
            Some((decode_nibble(pair[0], uppercase)? << 4) | decode_nibble(pair[1], uppercase)?)
        })
        .collect()
}

fn decode_nibble(byte: u8, uppercase: bool) -> Option<u8> {
    match byte {
        b'0'..=b'9' => Some(byte - b'0'),
        b'a'..=b'f' => Some(byte - b'a' + 10),
        b'A'..=b'F' if uppercase => Some(byte - b'A' + 10),
        _ => None,
    }
}