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 uses 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;
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.
22pub struct BufEncoder<const CAP: usize> {
23    buf: ArrayString<CAP>,
24}
25
26impl<const CAP: usize> BufEncoder<CAP> {
27    const _CHECK_EVEN_CAPACITY: () = [(); 1][CAP % 2];
28
29    /// Creates an empty `BufEncoder`.
30    #[inline]
31    pub fn new() -> Self { BufEncoder { buf: ArrayString::new() } }
32
33    /// Encodes `byte` as hex in given `case` and appends it to the buffer.
34    ///
35    /// ## Panics
36    ///
37    /// The method panics if the buffer is full.
38    #[inline]
39    #[track_caller]
40    pub fn put_byte(&mut self, byte: u8, case: Case) {
41        self.buf.push_str(&case.table().byte_to_hex(byte));
42    }
43
44    /// Encodes `bytes` as hex in given `case` and appends them to the buffer.
45    ///
46    /// ## Panics
47    ///
48    /// The method panics if the bytes wouldn't fit the buffer.
49    #[inline]
50    #[track_caller]
51    pub fn put_bytes<I>(&mut self, bytes: I, case: Case)
52    where
53        I: IntoIterator,
54        I::Item: Borrow<u8>,
55    {
56        self.put_bytes_inner(bytes.into_iter(), case)
57    }
58
59    #[inline]
60    #[track_caller]
61    fn put_bytes_inner<I>(&mut self, bytes: I, case: Case)
62    where
63        I: Iterator,
64        I::Item: Borrow<u8>,
65    {
66        // May give the compiler better optimization opportunity
67        if let Some(max) = bytes.size_hint().1 {
68            assert!(max <= self.space_remaining());
69        }
70        for byte in bytes {
71            self.put_byte(*byte.borrow(), case);
72        }
73    }
74
75    /// Encodes as many `bytes` as fit into the buffer as hex and return the remainder.
76    ///
77    /// This method works just like `put_bytes` but instead of panicking it returns the unwritten
78    /// bytes. The method returns an empty slice if all bytes were written
79    #[must_use = "this may write only part of the input buffer"]
80    #[inline]
81    #[track_caller]
82    pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8], case: Case) -> &'a [u8] {
83        let to_write = self.space_remaining().min(bytes.len());
84        self.put_bytes(&bytes[..to_write], case);
85        &bytes[to_write..]
86    }
87
88    /// Returns true if no more bytes can be written into the buffer.
89    #[inline]
90    pub fn is_full(&self) -> bool { self.space_remaining() == 0 }
91
92    /// Returns the written bytes as a hex `str`.
93    #[inline]
94    pub fn as_str(&self) -> &str { &self.buf }
95
96    /// Resets the buffer to become empty.
97    #[inline]
98    pub fn clear(&mut self) { self.buf.clear(); }
99
100    /// How many bytes can be written to this buffer.
101    ///
102    /// Note that this returns the number of bytes before encoding, not number of hex digits.
103    #[inline]
104    pub fn space_remaining(&self) -> usize { self.buf.remaining_capacity() / 2 }
105
106    pub(crate) fn put_filler(&mut self, filler: char, max_count: usize) -> usize {
107        let mut buf = [0; 4];
108        let filler = filler.encode_utf8(&mut buf);
109        let max_capacity = self.buf.remaining_capacity() / filler.len();
110        let to_write = max_capacity.min(max_count);
111
112        for _ in 0..to_write {
113            self.buf.push_str(filler);
114        }
115
116        to_write
117    }
118}
119
120impl<const CAP: usize> Default for BufEncoder<CAP> {
121    fn default() -> Self { Self::new() }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn empty() {
130        let encoder = BufEncoder::<2>::new();
131        assert_eq!(encoder.as_str(), "");
132        assert!(!encoder.is_full());
133    }
134
135    #[test]
136    fn single_byte_exact_buf() {
137        let mut encoder = BufEncoder::<2>::new();
138        assert_eq!(encoder.space_remaining(), 1);
139        encoder.put_byte(42, Case::Lower);
140        assert_eq!(encoder.as_str(), "2a");
141        assert_eq!(encoder.space_remaining(), 0);
142        assert!(encoder.is_full());
143        encoder.clear();
144        assert_eq!(encoder.space_remaining(), 1);
145        assert!(!encoder.is_full());
146        encoder.put_byte(42, Case::Upper);
147        assert_eq!(encoder.as_str(), "2A");
148        assert_eq!(encoder.space_remaining(), 0);
149        assert!(encoder.is_full());
150    }
151
152    #[test]
153    fn single_byte_oversized_buf() {
154        let mut encoder = BufEncoder::<4>::new();
155        assert_eq!(encoder.space_remaining(), 2);
156        encoder.put_byte(42, Case::Lower);
157        assert_eq!(encoder.space_remaining(), 1);
158        assert_eq!(encoder.as_str(), "2a");
159        assert!(!encoder.is_full());
160        encoder.clear();
161        assert_eq!(encoder.space_remaining(), 2);
162        encoder.put_byte(42, Case::Upper);
163        assert_eq!(encoder.as_str(), "2A");
164        assert_eq!(encoder.space_remaining(), 1);
165        assert!(!encoder.is_full());
166    }
167
168    #[test]
169    fn two_bytes() {
170        let mut encoder = BufEncoder::<4>::new();
171        encoder.put_byte(42, Case::Lower);
172        assert_eq!(encoder.space_remaining(), 1);
173        encoder.put_byte(255, Case::Lower);
174        assert_eq!(encoder.space_remaining(), 0);
175        assert_eq!(encoder.as_str(), "2aff");
176        assert!(encoder.is_full());
177        encoder.clear();
178        assert!(!encoder.is_full());
179        encoder.put_byte(42, Case::Upper);
180        encoder.put_byte(255, Case::Upper);
181        assert_eq!(encoder.as_str(), "2AFF");
182        assert!(encoder.is_full());
183    }
184
185    #[test]
186    fn put_bytes_min() {
187        let mut encoder = BufEncoder::<2>::new();
188        let remainder = encoder.put_bytes_min(b"", Case::Lower);
189        assert_eq!(remainder, b"");
190        assert_eq!(encoder.as_str(), "");
191        let remainder = encoder.put_bytes_min(b"*", Case::Lower);
192        assert_eq!(remainder, b"");
193        assert_eq!(encoder.as_str(), "2a");
194        encoder.clear();
195        let remainder = encoder.put_bytes_min(&[42, 255], Case::Lower);
196        assert_eq!(remainder, &[255]);
197        assert_eq!(encoder.as_str(), "2a");
198    }
199
200    #[test]
201    fn same_as_fmt() {
202        use core::fmt::{self, Write};
203
204        struct Writer {
205            buf: [u8; 2],
206            pos: usize,
207        }
208
209        impl Writer {
210            fn as_str(&self) -> &str { core::str::from_utf8(&self.buf[..self.pos]).unwrap() }
211        }
212
213        impl Write for Writer {
214            fn write_str(&mut self, s: &str) -> fmt::Result {
215                assert!(self.pos <= 2);
216                if s.len() > 2 - self.pos {
217                    Err(fmt::Error)
218                } else {
219                    self.buf[self.pos..(self.pos + s.len())].copy_from_slice(s.as_bytes());
220                    self.pos += s.len();
221                    Ok(())
222                }
223            }
224        }
225
226        let mut writer = Writer { buf: [0u8; 2], pos: 0 };
227        let mut encoder = BufEncoder::<2>::new();
228
229        for i in 0..=255 {
230            write!(writer, "{:02x}", i).unwrap();
231            encoder.put_byte(i, Case::Lower);
232            assert_eq!(encoder.as_str(), writer.as_str());
233            writer.pos = 0;
234            encoder.clear();
235        }
236        for i in 0..=255 {
237            write!(writer, "{:02X}", i).unwrap();
238            encoder.put_byte(i, Case::Upper);
239            assert_eq!(encoder.as_str(), writer.as_str());
240            writer.pos = 0;
241            encoder.clear();
242        }
243    }
244}