Skip to main content

fast_hex_lite/
encode.rs

1use crate::Error;
2
3/// Returns the required output length (in bytes) for encoding `n` bytes to hex.
4#[inline]
5pub const fn encoded_len(n: usize) -> usize {
6    n * 2
7}
8
9/// Encode bytes into hex (lowercase or uppercase) into the provided output slice.
10///
11/// Returns the number of bytes written on success.
12///
13/// # Errors
14///
15/// Returns [`Error::OutputTooSmall`] if `dst_hex` is not large enough.
16#[inline]
17pub fn encode_to_slice(src: &[u8], dst_hex: &mut [u8], lowercase: bool) -> Result<usize, Error> {
18    let out_len = encoded_len(src.len());
19    if dst_hex.len() < out_len {
20        return Err(Error::OutputTooSmall);
21    }
22
23    let alphabet = if lowercase {
24        b"0123456789abcdef"
25    } else {
26        b"0123456789ABCDEF"
27    };
28
29    // SAFETY-FREE hot loop:
30    // - dst_hex length already validated
31    // - we write exactly 2 bytes per input byte
32    for (byte, out_pair) in src
33        .iter()
34        .copied()
35        .zip(dst_hex[..out_len].chunks_exact_mut(2))
36    {
37        out_pair[0] = alphabet[(byte >> 4) as usize];
38        out_pair[1] = alphabet[(byte & 0x0f) as usize];
39    }
40
41    Ok(out_len)
42}
43
44/// Encode into a newly allocated `String`.
45///
46/// Available only with the `std` feature.
47#[cfg(feature = "std")]
48#[inline]
49pub fn encode_to_string(src: &[u8], lowercase: bool) -> String {
50    let mut out = vec![0u8; encoded_len(src.len())];
51    // infallible because buffer is pre-sized
52    let _ = encode_to_slice(src, &mut out, lowercase);
53
54    // Always valid UTF-8 because we only write ASCII hex characters.
55    String::from_utf8(out).expect("hex output is always valid UTF-8")
56}
57
58#[cfg(test)]
59mod tests {
60    extern crate std; // ← обязательно для no_std крейта
61    use super::*;
62    use std::prelude::v1::*; // Vec, String, format!, etc.
63
64    #[test]
65    fn test_encode_empty() {
66        let mut out = [0u8; 0];
67        assert_eq!(encode_to_slice(&[], &mut out, true).unwrap(), 0);
68    }
69
70    #[test]
71    fn test_encoded_len() {
72        assert_eq!(encoded_len(0), 0);
73        assert_eq!(encoded_len(1), 2);
74        assert_eq!(encoded_len(4), 8);
75        assert_eq!(encoded_len(128), 256);
76    }
77
78    #[test]
79    fn test_encode_lowercase() {
80        let mut out = [0u8; 8];
81        encode_to_slice(&[0xde, 0xad, 0xbe, 0xef], &mut out, true).unwrap();
82        assert_eq!(&out, b"deadbeef");
83    }
84
85    #[test]
86    fn test_encode_uppercase() {
87        let mut out = [0u8; 8];
88        encode_to_slice(&[0xde, 0xad, 0xbe, 0xef], &mut out, false).unwrap();
89        assert_eq!(&out, b"DEADBEEF");
90    }
91
92    #[test]
93    fn test_encode_boundary_bytes() {
94        let mut out = [0u8; 4];
95        encode_to_slice(&[0x00, 0xff], &mut out, true).unwrap();
96        assert_eq!(&out, b"00ff");
97
98        encode_to_slice(&[0x00, 0xff], &mut out, false).unwrap();
99        assert_eq!(&out, b"00FF");
100    }
101
102    #[test]
103    fn test_encode_nibble_boundaries() {
104        let mut out = [0u8; 4];
105        encode_to_slice(&[0x0f, 0xf0], &mut out, true).unwrap();
106        assert_eq!(&out, b"0ff0");
107
108        encode_to_slice(&[0x0f, 0xf0], &mut out, false).unwrap();
109        assert_eq!(&out, b"0FF0");
110    }
111
112    #[test]
113    fn test_encode_output_larger_than_needed() {
114        let mut out = [0xAAu8; 10];
115        let n = encode_to_slice(&[0xde, 0xad], &mut out, true).unwrap();
116        assert_eq!(n, 4);
117        assert_eq!(&out[..4], b"dead");
118        assert_eq!(&out[4..], &[0xAAu8; 6]); // хвост не тронут
119    }
120
121    #[test]
122    fn test_encode_output_exact_size() {
123        let mut out = [0u8; 4];
124        let n = encode_to_slice(&[0xca, 0xfe], &mut out, true).unwrap();
125        assert_eq!(n, 4);
126        assert_eq!(&out, b"cafe");
127    }
128
129    #[test]
130    fn test_encode_output_too_small() {
131        let mut out = [0u8; 2];
132        assert_eq!(
133            encode_to_slice(&[0xde, 0xad], &mut out, true).unwrap_err(),
134            Error::OutputTooSmall
135        );
136    }
137
138    #[test]
139    fn test_encode_output_too_small_by_one() {
140        let mut out = [0u8; 3];
141        assert_eq!(
142            encode_to_slice(&[0xde, 0xad], &mut out, true).unwrap_err(),
143            Error::OutputTooSmall
144        );
145    }
146
147    #[test]
148    fn test_encode_returns_written_length() {
149        let mut out = [0u8; 6];
150        assert_eq!(
151            encode_to_slice(&[0x01, 0x02, 0x03], &mut out, true).unwrap(),
152            6
153        );
154    }
155
156    #[test]
157    fn test_encode_idempotent() {
158        let src = [0xde, 0xad, 0xbe, 0xef];
159        let mut out1 = [0u8; 8];
160        let mut out2 = [0u8; 8];
161        encode_to_slice(&src, &mut out1, true).unwrap();
162        encode_to_slice(&src, &mut out2, true).unwrap();
163        assert_eq!(out1, out2);
164    }
165
166    #[test]
167    fn test_encode_all_bytes_lower() {
168        for byte in 0u8..=255 {
169            let mut out = [0u8; 2];
170            encode_to_slice(&[byte], &mut out, true).unwrap();
171            let expected = std::format!("{byte:02x}");
172            assert_eq!(&out, expected.as_bytes(), "failed for 0x{byte:02x}");
173        }
174    }
175
176    #[test]
177    fn test_encode_all_bytes_upper() {
178        for byte in 0u8..=255 {
179            let mut out = [0u8; 2];
180            encode_to_slice(&[byte], &mut out, false).unwrap();
181            let expected = std::format!("{byte:02X}");
182            assert_eq!(&out, expected.as_bytes(), "failed for 0x{byte:02X}");
183        }
184    }
185
186    #[cfg(feature = "std")]
187    #[test]
188    fn test_encode_to_string_lowercase() {
189        assert_eq!(
190            encode_to_string(&[0xde, 0xad, 0xbe, 0xef], true),
191            "deadbeef"
192        );
193    }
194
195    #[cfg(feature = "std")]
196    #[test]
197    fn test_encode_to_string_uppercase() {
198        assert_eq!(
199            encode_to_string(&[0xde, 0xad, 0xbe, 0xef], false),
200            "DEADBEEF"
201        );
202    }
203
204    #[cfg(feature = "std")]
205    #[test]
206    fn test_encode_to_string_empty() {
207        assert_eq!(encode_to_string(&[], true), "");
208    }
209
210    #[cfg(feature = "std")]
211    #[test]
212    fn test_encode_to_string_all_bytes_are_ascii() {
213        let src: Vec<u8> = (0u8..=255).collect();
214        let s = encode_to_string(&src, true);
215        assert!(s.is_ascii());
216        assert_eq!(s.len(), 512);
217    }
218}