Skip to main content

hex_conservative/
buf_encoder.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Implements a buffered encoder.
4//!
5//! This is a low-level module, most users should be satisfied by the `display` module instead.
6//!
7//! The main type in this module is [`BufEncoder`] which provides buffered hex encoding.
8//! `BufEncoder` is faster than the usual `write!(f, "{02x}", b)?` in a for loop because it reduces
9//! dynamic dispatch and decreases the number of allocations if a `String` is being created.
10
11use core::borrow::Borrow;
12
13use arrayvec::ArrayString;
14
15use super::{Case, Table};
16
17/// Hex-encodes bytes into the provided buffer.
18///
19/// This is an important building block for fast hex-encoding. Because string writing tools
20/// provided by `core::fmt` involve dynamic dispatch and don't allow reserving capacity in strings
21/// buffering the hex and then formatting it is significantly faster.
22///
23/// The buffer has a fixed capacity specified when created. The capacity must be an even number since
24/// each byte is encoded as two hex characters.
25///
26/// # Examples
27/// ```
28/// # use hex_conservative::buf_encoder::BufEncoder;
29/// # use hex_conservative::Case;
30/// let mut encoder = BufEncoder::<4>::new(Case::Lower);
31/// encoder.put_byte(0xab);
32/// assert_eq!(encoder.as_str(), "ab");
33/// ```
34/// The following code doesn't compile because of odd capacity:
35/// ```compile_fail
36/// # use hex_conservative::buf_encoder::BufEncoder;
37/// # use hex_conservative::Case;
38/// let mut encoder = BufEncoder::<3>::new(Case::Lower);
39/// # let _ = encoder;
40/// ```
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct BufEncoder<const CAP: usize> {
43    buf: ArrayString<CAP>,
44    table: &'static Table,
45}
46
47impl<const CAP: usize> BufEncoder<CAP> {
48    const _CHECK_EVEN_CAPACITY: () = [(); 1][CAP % 2];
49
50    /// Creates an empty `BufEncoder` that will encode bytes to hex characters in the given case.
51    #[inline]
52    pub fn new(case: Case) -> Self {
53        let () = Self::_CHECK_EVEN_CAPACITY;
54        BufEncoder { buf: ArrayString::new(), table: case.table() }
55    }
56
57    /// Encodes `byte` as hex and appends it to the buffer.
58    ///
59    /// ## Panics
60    ///
61    /// The method panics if the buffer is full.
62    #[inline]
63    #[track_caller]
64    pub fn put_byte(&mut self, byte: u8) {
65        let mut hex_chars = [0u8; 2];
66        let hex_str = self.table.byte_to_str(&mut hex_chars, byte);
67        self.buf.push_str(hex_str);
68    }
69
70    /// Encodes `bytes` as hex and appends them to the buffer.
71    ///
72    /// ## Panics
73    ///
74    /// The method panics if the bytes wouldn't fit the buffer.
75    #[inline]
76    #[track_caller]
77    pub fn put_bytes<I>(&mut self, bytes: I)
78    where
79        I: IntoIterator,
80        I::Item: Borrow<u8>,
81    {
82        self.put_bytes_inner(bytes.into_iter());
83    }
84
85    #[inline]
86    #[track_caller]
87    fn put_bytes_inner<I>(&mut self, bytes: I)
88    where
89        I: Iterator,
90        I::Item: Borrow<u8>,
91    {
92        // May give the compiler better optimization opportunity
93        if let Some(max) = bytes.size_hint().1 {
94            assert!(max <= self.space_remaining());
95        }
96        for byte in bytes {
97            self.put_byte(*byte.borrow());
98        }
99    }
100
101    /// Encodes as many `bytes` as fit into the buffer as hex and return the remainder.
102    ///
103    /// This method works just like `put_bytes` but instead of panicking it returns the unwritten
104    /// bytes. The method returns an empty slice if all bytes were written
105    #[must_use = "this may write only part of the input buffer"]
106    #[inline]
107    #[track_caller]
108    pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8]) -> &'a [u8] {
109        let to_write = self.space_remaining().min(bytes.len());
110        self.put_bytes(&bytes[..to_write]);
111        &bytes[to_write..]
112    }
113
114    /// Returns true if no more bytes can be written into the buffer.
115    #[inline]
116    pub fn is_full(&self) -> bool { self.buf.is_full() }
117
118    /// Returns the written bytes as a hex `str`.
119    #[inline]
120    pub fn as_str(&self) -> &str { &self.buf }
121
122    /// Resets the buffer to become empty.
123    #[inline]
124    pub fn clear(&mut self) { self.buf.clear(); }
125
126    /// How many bytes can be written to this buffer.
127    ///
128    /// Note that this returns the number of bytes before encoding, not number of hex digits.
129    #[inline]
130    pub fn space_remaining(&self) -> usize { self.buf.remaining_capacity() / 2 }
131
132    pub(crate) fn put_filler(&mut self, filler: char, max_count: usize) -> usize {
133        let mut buf = [0; 4];
134        let filler = filler.encode_utf8(&mut buf);
135        let max_capacity = self.buf.remaining_capacity() / filler.len();
136        let to_write = max_capacity.min(max_count);
137
138        for _ in 0..to_write {
139            self.buf.push_str(filler);
140        }
141
142        to_write
143    }
144}
145
146impl<const CAP: usize> Default for BufEncoder<CAP> {
147    #[inline]
148    fn default() -> Self { Self::new(Case::Lower) }
149}
150
151impl<const CAP: usize, A: Borrow<u8>> FromIterator<A> for BufEncoder<CAP> {
152    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
153        let mut encoder = Self::default();
154        encoder.put_bytes(iter);
155        encoder
156    }
157}
158
159impl<const CAP: usize, A: Borrow<u8>> Extend<A> for BufEncoder<CAP> {
160    fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) { self.put_bytes(iter); }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn empty() {
169        let encoder = BufEncoder::<2>::new(Case::Lower);
170        assert_eq!(encoder.as_str(), "");
171        assert!(!encoder.is_full());
172
173        let encoder = BufEncoder::<2>::new(Case::Upper);
174        assert_eq!(encoder.as_str(), "");
175        assert!(!encoder.is_full());
176    }
177
178    #[test]
179    fn single_byte_exact_buf() {
180        let mut encoder = BufEncoder::<2>::new(Case::Lower);
181        assert_eq!(encoder.space_remaining(), 1);
182        encoder.put_byte(42);
183        assert_eq!(encoder.as_str(), "2a");
184        assert_eq!(encoder.space_remaining(), 0);
185        assert!(encoder.is_full());
186        encoder.clear();
187        assert_eq!(encoder.space_remaining(), 1);
188        assert!(!encoder.is_full());
189
190        let mut encoder = BufEncoder::<2>::new(Case::Upper);
191        assert_eq!(encoder.space_remaining(), 1);
192        encoder.put_byte(42);
193        assert_eq!(encoder.as_str(), "2A");
194        assert_eq!(encoder.space_remaining(), 0);
195        assert!(encoder.is_full());
196        encoder.clear();
197        assert_eq!(encoder.space_remaining(), 1);
198        assert!(!encoder.is_full());
199    }
200
201    #[test]
202    fn single_byte_oversized_buf() {
203        let mut encoder = BufEncoder::<4>::new(Case::Lower);
204        assert_eq!(encoder.space_remaining(), 2);
205        encoder.put_byte(42);
206        assert_eq!(encoder.space_remaining(), 1);
207        assert_eq!(encoder.as_str(), "2a");
208        assert!(!encoder.is_full());
209        encoder.clear();
210        assert_eq!(encoder.space_remaining(), 2);
211        assert!(!encoder.is_full());
212
213        let mut encoder = BufEncoder::<4>::new(Case::Upper);
214        assert_eq!(encoder.space_remaining(), 2);
215        encoder.put_byte(42);
216        assert_eq!(encoder.space_remaining(), 1);
217        assert_eq!(encoder.as_str(), "2A");
218        assert!(!encoder.is_full());
219        encoder.clear();
220        assert_eq!(encoder.space_remaining(), 2);
221        assert!(!encoder.is_full());
222    }
223
224    #[test]
225    fn two_bytes() {
226        let mut encoder = BufEncoder::<4>::new(Case::Lower);
227        assert_eq!(encoder.space_remaining(), 2);
228        encoder.put_byte(42);
229        assert_eq!(encoder.space_remaining(), 1);
230        encoder.put_byte(255);
231        assert_eq!(encoder.space_remaining(), 0);
232        assert_eq!(encoder.as_str(), "2aff");
233        assert!(encoder.is_full());
234        encoder.clear();
235        assert_eq!(encoder.space_remaining(), 2);
236        assert!(!encoder.is_full());
237
238        let mut encoder = BufEncoder::<4>::new(Case::Upper);
239        assert_eq!(encoder.space_remaining(), 2);
240        encoder.put_byte(42);
241        assert_eq!(encoder.space_remaining(), 1);
242        encoder.put_byte(255);
243        assert_eq!(encoder.space_remaining(), 0);
244        assert_eq!(encoder.as_str(), "2AFF");
245        assert!(encoder.is_full());
246        encoder.clear();
247        assert_eq!(encoder.space_remaining(), 2);
248        assert!(!encoder.is_full());
249    }
250
251    #[test]
252    fn put_bytes_min() {
253        let mut encoder = BufEncoder::<2>::new(Case::Lower);
254        let remainder = encoder.put_bytes_min(b"");
255        assert_eq!(remainder, b"");
256        assert_eq!(encoder.as_str(), "");
257        let remainder = encoder.put_bytes_min(b"*");
258        assert_eq!(remainder, b"");
259        assert_eq!(encoder.as_str(), "2a");
260        encoder.clear();
261        let remainder = encoder.put_bytes_min(&[42, 255]);
262        assert_eq!(remainder, &[255]);
263        assert_eq!(encoder.as_str(), "2a");
264    }
265
266    #[test]
267    fn put_filler() {
268        let mut encoder = BufEncoder::<8>::new(Case::Lower);
269        assert_eq!(encoder.put_filler(' ', 0), 0);
270        assert_eq!(encoder.as_str(), "");
271        assert_eq!(encoder.put_filler('a', 1), 1);
272        assert_eq!(encoder.as_str(), "a");
273        assert_eq!(encoder.put_filler('é', 2), 2); // Test 2 byte UTF-8
274        assert_eq!(encoder.as_str(), "aéé");
275        assert_eq!(encoder.put_filler('é', 4), 1); // Try to fill more than fits
276        assert_eq!(encoder.as_str(), "aééé");
277    }
278
279    #[test]
280    fn from_iterator() {
281        let bytes = [0x00_u8, 0xab, 0xff];
282        let encoder: BufEncoder<6> = bytes.iter().collect(); // ref iter
283        assert_eq!(encoder.as_str(), "00abff");
284
285        let encoder: BufEncoder<6> = bytes.into_iter().collect(); // owned iter
286        assert_eq!(encoder.as_str(), "00abff");
287    }
288
289    #[test]
290    fn extend() {
291        let mut encoder = BufEncoder::<8>::new(Case::Upper);
292        encoder.put_byte(0x00);
293        encoder.extend([0xab_u8, 0xff]);
294        assert_eq!(encoder.as_str(), "00ABFF");
295
296        let mut encoder = BufEncoder::<6>::new(Case::Lower);
297        encoder.put_byte(0x42);
298        encoder.extend([0xab_u8, 0xff]);
299        assert_eq!(encoder.as_str(), "42abff");
300    }
301
302    #[test]
303    fn same_as_fmt() {
304        use core::fmt::{self, Write};
305
306        struct Writer {
307            buf: [u8; 2],
308            pos: usize,
309        }
310
311        impl Writer {
312            fn as_str(&self) -> &str { core::str::from_utf8(&self.buf[..self.pos]).unwrap() }
313        }
314
315        impl Write for Writer {
316            fn write_str(&mut self, s: &str) -> fmt::Result {
317                assert!(self.pos <= 2);
318                if s.len() > 2 - self.pos {
319                    Err(fmt::Error)
320                } else {
321                    self.buf[self.pos..(self.pos + s.len())].copy_from_slice(s.as_bytes());
322                    self.pos += s.len();
323                    Ok(())
324                }
325            }
326        }
327
328        let mut writer = Writer { buf: [0u8; 2], pos: 0 };
329
330        let mut encoder = BufEncoder::<2>::new(Case::Lower);
331        for i in 0..=255 {
332            write!(writer, "{:02x}", i).unwrap();
333            encoder.put_byte(i);
334            assert_eq!(encoder.as_str(), writer.as_str());
335            writer.pos = 0;
336            encoder.clear();
337        }
338
339        let mut encoder = BufEncoder::<2>::new(Case::Upper);
340        for i in 0..=255 {
341            write!(writer, "{:02X}", i).unwrap();
342            encoder.put_byte(i);
343            assert_eq!(encoder.as_str(), writer.as_str());
344            writer.pos = 0;
345            encoder.clear();
346        }
347    }
348}