1use crate::Error;
2
3#[inline]
5pub const fn encoded_len(n: usize) -> usize {
6 n * 2
7}
8
9#[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 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#[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 let _ = encode_to_slice(src, &mut out, lowercase);
53
54 String::from_utf8(out).expect("hex output is always valid UTF-8")
56}
57
58#[cfg(test)]
59mod tests {
60 extern crate std; use super::*;
62 use std::prelude::v1::*; #[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]); }
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}