ct_codecs/
hex.rs

1use crate::error::*;
2use crate::{Decoder, Encoder};
3
4/// Hexadecimal encoder and decoder implementation.
5///
6/// Provides constant-time encoding and decoding of binary data to and from
7/// hexadecimal representation. The implementation uses only lowercase
8/// hexadecimal characters (0-9, a-f) for encoding.
9///
10/// # Security
11///
12/// All operations run in constant time relative to input length, making
13/// this implementation suitable for handling sensitive cryptographic data.
14///
15/// # Examples
16///
17/// ```
18/// use ct_codecs::{Hex, Encoder, Decoder};
19///
20/// let data = b"Hello";
21/// # let result = 
22/// // Encode binary data to hex
23/// let encoded = Hex::encode_to_string(data)?;
24/// assert_eq!(encoded, "48656c6c6f");
25///
26/// // Decode hex back to binary
27/// let decoded = Hex::decode_to_vec(&encoded, None)?;
28/// assert_eq!(decoded, data);
29///
30/// // Working with preallocated buffers (useful for no_std)
31/// let mut hex_buf = [0u8; 10];
32/// let hex = Hex::encode(&mut hex_buf, data)?;
33/// assert_eq!(hex, b"48656c6c6f");
34///
35/// let mut bin_buf = [0u8; 5];
36/// let bin = Hex::decode(&mut bin_buf, hex, None)?;
37/// assert_eq!(bin, data);
38/// # Ok::<(), ct_codecs::Error>(())
39/// ```
40pub struct Hex;
41
42impl Encoder for Hex {
43    /// Calculates the encoded length for a hexadecimal representation.
44    ///
45    /// The encoded length is always twice the binary length, as each byte
46    /// is represented by two hexadecimal characters.
47    ///
48    /// # Arguments
49    ///
50    /// * `bin_len` - The length of the binary input in bytes
51    ///
52    /// # Returns
53    ///
54    /// * `Ok(usize)` - The required length for the encoded output
55    /// * `Err(Error::Overflow)` - If the calculation would overflow
56    #[inline]
57    fn encoded_len(bin_len: usize) -> Result<usize, Error> {
58        bin_len.checked_mul(2).ok_or(Error::Overflow)
59    }
60
61    /// Encodes binary data into a hexadecimal representation.
62    ///
63    /// The encoding is performed in constant time relative to the input length.
64    ///
65    /// # Arguments
66    ///
67    /// * `hex` - Mutable buffer to store the encoded output
68    /// * `bin` - Binary input data to encode
69    ///
70    /// # Returns
71    ///
72    /// * `Ok(&[u8])` - A slice of the encoded buffer containing the hex data
73    /// * `Err(Error::Overflow)` - If the output buffer is too small
74    fn encode<IN: AsRef<[u8]>>(hex: &mut [u8], bin: IN) -> Result<&[u8], Error> {
75        let bin = bin.as_ref();
76        let bin_len = bin.len();
77        let hex_maxlen = hex.len();
78        if hex_maxlen < bin_len.checked_shl(1).ok_or(Error::Overflow)? {
79            return Err(Error::Overflow);
80        }
81        for (i, v) in bin.iter().enumerate() {
82            let (b, c) = ((v >> 4) as u16, (v & 0xf) as u16);
83            let x = (((87 + c + (((c.wrapping_sub(10)) >> 8) & !38)) as u8) as u16) << 8
84                | ((87 + b + (((b.wrapping_sub(10)) >> 8) & !38)) as u8) as u16;
85            hex[i * 2] = x as u8;
86            hex[i * 2 + 1] = (x >> 8) as u8;
87        }
88        Ok(&hex[..bin_len * 2])
89    }
90}
91
92impl Decoder for Hex {
93    /// Decodes hexadecimal data back into its binary representation.
94    ///
95    /// The decoding is performed in constant time relative to the input length.
96    /// Both uppercase and lowercase hexadecimal characters are accepted.
97    ///
98    /// # Arguments
99    ///
100    /// * `bin` - Mutable buffer to store the decoded output
101    /// * `hex` - Hexadecimal input data to decode
102    /// * `ignore` - Optional set of characters to ignore during decoding
103    ///
104    /// # Returns
105    ///
106    /// * `Ok(&[u8])` - A slice of the binary buffer containing the decoded data
107    /// * `Err(Error::Overflow)` - If the output buffer is too small
108    /// * `Err(Error::InvalidInput)` - If the input contains invalid characters or has odd length
109    fn decode<'t, IN: AsRef<[u8]>>(
110        bin: &'t mut [u8],
111        hex: IN,
112        ignore: Option<&[u8]>,
113    ) -> Result<&'t [u8], Error> {
114        let hex = hex.as_ref();
115        let bin_maxlen = bin.len();
116        let mut bin_pos = 0;
117        let mut state = false;
118        let mut c_acc = 0;
119        for &c in hex {
120            let c_num = c ^ 48;
121            let c_num0 = ((c_num as u16).wrapping_sub(10) >> 8) as u8;
122            let c_alpha = (c & !32).wrapping_sub(55);
123            let c_alpha0 = (((c_alpha as u16).wrapping_sub(10)
124                ^ ((c_alpha as u16).wrapping_sub(16)))
125                >> 8) as u8;
126            if (c_num0 | c_alpha0) == 0 {
127                match ignore {
128                    Some(ignore) if ignore.contains(&c) => continue,
129                    _ => return Err(Error::InvalidInput),
130                };
131            }
132            let c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
133            if bin_pos >= bin_maxlen {
134                return Err(Error::Overflow);
135            }
136            if !state {
137                c_acc = c_val << 4;
138            } else {
139                bin[bin_pos] = c_acc | c_val;
140                bin_pos += 1;
141            }
142            state = !state;
143        }
144        if state {
145            return Err(Error::InvalidInput);
146        }
147        Ok(&bin[..bin_pos])
148    }
149}
150
151#[cfg(feature = "std")]
152#[test]
153fn test_hex() {
154    let bin = [1u8, 5, 11, 15, 19, 131];
155    let hex = Hex::encode_to_string(bin).unwrap();
156    let expected = "01050b0f1383";
157    assert_eq!(hex, expected);
158    let bin2 = Hex::decode_to_vec(&hex, None).unwrap();
159    assert_eq!(bin, &bin2[..]);
160}
161
162#[test]
163fn test_hex_no_std() {
164    let bin = [1u8, 5, 11, 15, 19, 131];
165    let expected = "01050b0f1383";
166    let mut hex = [0u8; 12];
167    let hex = Hex::encode_to_str(&mut hex, bin).unwrap();
168    assert_eq!(&hex, &expected);
169    let mut bin2 = [0u8; 6];
170    let bin2 = Hex::decode(&mut bin2, hex, None).unwrap();
171    assert_eq!(bin, bin2);
172}