Skip to main content

netbat/transport/
hex.rs

1use super::error::NetbatError;
2
3/// Decode a lowercase or uppercase hexadecimal byte string with a decoded-size limit.
4///
5/// # Errors
6/// Returns [`NetbatError`] when the hex string has odd length, contains a
7/// non-hex byte, or decodes past `max_input_bytes`.
8pub fn decode_hex(input: &[u8], max_input_bytes: usize) -> Result<Vec<u8>, NetbatError> {
9    if !input.len().is_multiple_of(2) {
10        return Err(NetbatError::MalformedRequest {
11            reason: "hex input has odd length",
12        });
13    }
14    if input.len() / 2 > max_input_bytes {
15        return Err(NetbatError::InputTooLarge {
16            max: max_input_bytes,
17        });
18    }
19
20    let mut output = Vec::with_capacity(input.len() / 2);
21    for pair in input.chunks_exact(2) {
22        let high = hex_value(pair[0])?;
23        let low = hex_value(pair[1])?;
24        output.push((high << 4) | low);
25    }
26    Ok(output)
27}
28
29fn hex_value(byte: u8) -> Result<u8, NetbatError> {
30    match byte {
31        b'0'..=b'9' => Ok(byte - b'0'),
32        b'a'..=b'f' => Ok(byte - b'a' + 10),
33        b'A'..=b'F' => Ok(byte - b'A' + 10),
34        _ => Err(NetbatError::MalformedRequest {
35            reason: "input is not hex",
36        }),
37    }
38}
39
40/// Append lowercase hexadecimal encoding of `bytes` into `output`.
41pub fn encode_hex_into(bytes: &[u8], output: &mut Vec<u8>) {
42    const HEX: &[u8; 16] = b"0123456789abcdef";
43    output.reserve(bytes.len() * 2);
44    for byte in bytes {
45        output.push(HEX[(byte >> 4) as usize]);
46        output.push(HEX[(byte & 0x0f) as usize]);
47    }
48}
49
50/// Encode `bytes` as a lowercase hexadecimal byte string and return the
51/// owned buffer.
52#[must_use]
53pub fn encode_hex(bytes: &[u8]) -> Vec<u8> {
54    let mut output = Vec::with_capacity(bytes.len() * 2);
55    encode_hex_into(bytes, &mut output);
56    output
57}
58
59/// Encode `bytes` as a lowercase hexadecimal [`String`].
60///
61/// Convenience wrapper around [`encode_hex`] for callers (such as
62/// substrate operations that carry hex on the wire as msgpack strings)
63/// that need an owned `String` rather than `Vec<u8>`. The conversion
64/// is allocation-free past [`encode_hex`] because every byte produced
65/// by the lowercase-hex encoder is ASCII.
66#[must_use]
67pub fn encode_hex_str(bytes: &[u8]) -> String {
68    let buf = encode_hex(bytes);
69    // SAFETY: encode_hex_into emits only ASCII bytes from the
70    // 0123456789abcdef alphabet.
71    String::from_utf8(buf).expect("lowercase-hex encoder produces ASCII")
72}
73
74/// Decode a lowercase or uppercase hexadecimal `&str`.
75///
76/// Convenience wrapper around [`decode_hex`] without the input-size
77/// guard, for callers that already trust the source. Use [`decode_hex`]
78/// directly when receiving bytes from an untrusted transport that
79/// needs a bound.
80///
81/// # Errors
82/// Returns [`NetbatError::MalformedRequest`] when the input has odd
83/// length or contains a non-hex byte.
84pub fn decode_hex_str(input: &str) -> Result<Vec<u8>, NetbatError> {
85    decode_hex(input.as_bytes(), usize::MAX)
86}