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 of this module is [`BufEncoder`] which provides buffered hex encoding. Such is
8//! faster than the usual `write!(f, "{02x}", b)?` in a for loop because it reduces dynamic
9//! dispatch and decreases the number of allocations if a `String` is being created.
10
11use core::borrow::Borrow;
12
13pub use out_bytes::OutBytes;
14
15use super::Case;
16
17/// Trait for types that can be soundly converted to `OutBytes`.
18///
19/// To protect the API from future breakage this sealed trait guards which types can be used with
20/// the `Encoder`. Currently it is implemented for byte arrays of various interesting lengths.
21///
22/// ## Safety
23///
24/// This is not `unsafe` yet but the `as_out_bytes` should always return the same reference if the
25/// same reference is supplied. IOW the returned memory address and length should be the same if
26/// the input memory address and length are the same.
27///
28/// If the trait ever becomes `unsafe` this will be required for soundness.
29pub trait AsOutBytes: out_bytes::Sealed {
30    /// Performs the conversion.
31    fn as_out_bytes(&self) -> &OutBytes;
32
33    /// Performs the conversion.
34    fn as_mut_out_bytes(&mut self) -> &mut OutBytes;
35}
36
37/// A buffer with compile-time-known length.
38///
39/// This is essentially `Default + AsOutBytes` but supports lengths 1.41 doesn't.
40pub trait FixedLenBuf: Sized + AsOutBytes {
41    /// Creates an uninitialized buffer.
42    ///
43    /// The current implementtions initialize the buffer with zeroes but it should be treated a
44    /// uninitialized anyway.
45    fn uninit() -> Self;
46}
47
48/// Implements `OutBytes`
49///
50/// This prevents the rest of the crate from accessing the field of `OutBytes`.
51mod out_bytes {
52    use super::AsOutBytes;
53
54    /// A byte buffer that can only be written-into.
55    ///
56    /// You shouldn't concern yourself with this, just call `BufEncoder::new` with your array.
57    ///
58    /// This prepares the API for potential future support of `[MaybeUninit<u8>]`. We don't want to use
59    /// `unsafe` until it's proven to be needed but if it does we have an easy, compatible upgrade
60    /// option.
61    ///
62    /// Warning: `repr(transparent)` is an internal implementation detail and **must not** be
63    /// relied on!
64    #[repr(transparent)]
65    pub struct OutBytes([u8]);
66
67    impl OutBytes {
68        /// Returns the first `len` bytes as initialized.
69        ///
70        /// Not `unsafe` because we don't use `unsafe` (yet).
71        ///
72        /// ## Panics
73        ///
74        /// The method panics if `len` is out of bounds.
75        #[track_caller]
76        pub(crate) fn assume_init(&self, len: usize) -> &[u8] { &self.0[..len] }
77
78        /// Writes given bytes into the buffer.
79        ///
80        /// ## Panics
81        ///
82        /// The method panics if pos is out of bounds or `bytes` don't fit into the buffer.
83        #[track_caller]
84        pub(crate) fn write(&mut self, pos: usize, bytes: &[u8]) {
85            self.0[pos..(pos + bytes.len())].copy_from_slice(bytes);
86        }
87
88        /// Returns the length of the buffer.
89        pub(crate) fn len(&self) -> usize { self.0.len() }
90
91        fn from_bytes(slice: &[u8]) -> &Self {
92            // SAFETY: copied from std
93            // conversion of reference to pointer of the same referred type is always sound,
94            // including in unsized types.
95            // Thanks to repr(transparent) the types have the same layout making the other
96            // conversion sound.
97            // The pointer was just created from a reference that's still alive so dereferencing is
98            // sound.
99            unsafe { &*(slice as *const [u8] as *const Self) }
100        }
101
102        fn from_mut_bytes(slice: &mut [u8]) -> &mut Self {
103            // SAFETY: copied from std
104            // conversion of reference to pointer of the same referred type is always sound,
105            // including in unsized types.
106            // Thanks to repr(transparent) the types have the same layout making the other
107            // conversion sound.
108            // The pointer was just created from a reference that's still alive so dereferencing is
109            // sound.
110            unsafe { &mut *(slice as *mut [u8] as *mut Self) }
111        }
112    }
113
114    macro_rules! impl_encode {
115        ($($len:expr),* $(,)?) => {
116            $(
117                impl super::FixedLenBuf for [u8; $len] {
118                    fn uninit() -> Self {
119                        [0u8; $len]
120                    }
121                }
122
123                impl AsOutBytes for [u8; $len] {
124                    fn as_out_bytes(&self) -> &OutBytes {
125                        OutBytes::from_bytes(self)
126                    }
127
128                    fn as_mut_out_bytes(&mut self) -> &mut OutBytes {
129                        OutBytes::from_mut_bytes(self)
130                    }
131                }
132
133                impl Sealed for [u8; $len] {}
134
135                impl<'a> super::super::display::DisplayHex for &'a [u8; $len / 2] {
136                    type Display = super::super::display::DisplayArray<core::slice::Iter<'a, u8>, [u8; $len]>;
137                    fn as_hex(self) -> Self::Display {
138                        super::super::display::DisplayArray::new(self.iter())
139                    }
140
141                    fn hex_reserve_suggestion(self) -> usize {
142                        $len
143                    }
144                }
145            )*
146        }
147    }
148
149    impl<T: AsOutBytes + ?Sized> AsOutBytes for &'_ mut T {
150        fn as_out_bytes(&self) -> &OutBytes { (**self).as_out_bytes() }
151
152        fn as_mut_out_bytes(&mut self) -> &mut OutBytes { (**self).as_mut_out_bytes() }
153    }
154
155    impl<T: AsOutBytes + ?Sized> Sealed for &'_ mut T {}
156
157    impl AsOutBytes for OutBytes {
158        fn as_out_bytes(&self) -> &OutBytes { self }
159
160        fn as_mut_out_bytes(&mut self) -> &mut OutBytes { self }
161    }
162
163    impl Sealed for OutBytes {}
164
165    // As a sanity check we only provide conversions for even, non-empty arrays.
166    // Weird lengths 66 and 130 are provided for serialized public keys.
167    impl_encode!(
168        2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 40, 64, 66, 128, 130, 256, 512,
169        1024, 2048, 4096, 8192
170    );
171
172    /// Prevents outside crates from implementing the trait
173    pub trait Sealed {}
174}
175
176/// Hex-encodes bytes into the provided buffer.
177///
178/// This is an important building block for fast hex-encoding. Because string writing tools
179/// provided by `core::fmt` involve dynamic dispatch and don't allow reserving capacity in strings
180/// buffering the hex and then formatting it is significantly faster.
181pub struct BufEncoder<T: AsOutBytes> {
182    buf: T,
183    pos: usize,
184}
185
186impl<T: AsOutBytes> BufEncoder<T> {
187    /// Creates an empty `BufEncoder`.
188    ///
189    /// This is usually used with uninitialized (zeroed) byte array allocated on stack.
190    /// This can only be constructed with an even-length, non-empty array.
191    #[inline]
192    pub fn new(buf: T) -> Self { BufEncoder { buf, pos: 0 } }
193
194    /// Encodes `byte` as hex in given `case` and appends it to the buffer.
195    ///
196    /// ## Panics
197    ///
198    /// The method panics if the buffer is full.
199    #[inline]
200    #[track_caller]
201    pub fn put_byte(&mut self, byte: u8, case: Case) {
202        self.buf.as_mut_out_bytes().write(self.pos, &super::byte_to_hex(byte, case.table()));
203        self.pos += 2;
204    }
205
206    /// Encodes `bytes` as hex in given `case` and appends them to the buffer.
207    ///
208    /// ## Panics
209    ///
210    /// The method panics if the bytes wouldn't fit the buffer.
211    #[inline]
212    #[track_caller]
213    pub fn put_bytes<I>(&mut self, bytes: I, case: Case)
214    where
215        I: IntoIterator,
216        I::Item: Borrow<u8>,
217    {
218        self.put_bytes_inner(bytes.into_iter(), case)
219    }
220
221    #[inline]
222    #[track_caller]
223    fn put_bytes_inner<I>(&mut self, bytes: I, case: Case)
224    where
225        I: Iterator,
226        I::Item: Borrow<u8>,
227    {
228        // May give the compiler better optimization opportunity
229        if let Some(max) = bytes.size_hint().1 {
230            assert!(max <= self.space_remaining());
231        }
232        for byte in bytes {
233            self.put_byte(*byte.borrow(), case);
234        }
235    }
236
237    /// Encodes as many `bytes` as fit into the buffer as hex and return the remainder.
238    ///
239    /// This method works just like `put_bytes` but instead of panicking it returns the unwritten
240    /// bytes. The method returns an empty slice if all bytes were written
241    #[must_use = "this may write only part of the input buffer"]
242    #[inline]
243    #[track_caller]
244    pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8], case: Case) -> &'a [u8] {
245        let to_write = self.space_remaining().min(bytes.len());
246        self.put_bytes(&bytes[..to_write], case);
247        &bytes[to_write..]
248    }
249
250    /// Returns true if no more bytes can be written into the buffer.
251    #[inline]
252    pub fn is_full(&self) -> bool { self.pos == self.buf.as_out_bytes().len() }
253
254    /// Returns the written bytes as a hex `str`.
255    #[inline]
256    pub fn as_str(&self) -> &str {
257        core::str::from_utf8(self.buf.as_out_bytes().assume_init(self.pos))
258            .expect("we only write ASCII")
259    }
260
261    /// Resets the buffer to become empty.
262    #[inline]
263    pub fn clear(&mut self) { self.pos = 0; }
264
265    /// How many bytes can be written to this buffer.
266    ///
267    /// Note that this returns the number of bytes before encoding, not number of hex digits.
268    #[inline]
269    pub fn space_remaining(&self) -> usize { (self.buf.as_out_bytes().len() - self.pos) / 2 }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn empty() {
278        let mut buf = [0u8; 2];
279        let encoder = BufEncoder::new(&mut buf);
280        assert_eq!(encoder.as_str(), "");
281        assert!(!encoder.is_full());
282    }
283
284    #[test]
285    fn single_byte_exact_buf() {
286        let mut buf = [0u8; 2];
287        let mut encoder = BufEncoder::new(&mut buf);
288        assert_eq!(encoder.space_remaining(), 1);
289        encoder.put_byte(42, Case::Lower);
290        assert_eq!(encoder.as_str(), "2a");
291        assert_eq!(encoder.space_remaining(), 0);
292        assert!(encoder.is_full());
293        encoder.clear();
294        assert_eq!(encoder.space_remaining(), 1);
295        assert!(!encoder.is_full());
296        encoder.put_byte(42, Case::Upper);
297        assert_eq!(encoder.as_str(), "2A");
298        assert_eq!(encoder.space_remaining(), 0);
299        assert!(encoder.is_full());
300    }
301
302    #[test]
303    fn single_byte_oversized_buf() {
304        let mut buf = [0u8; 4];
305        let mut encoder = BufEncoder::new(&mut buf);
306        assert_eq!(encoder.space_remaining(), 2);
307        encoder.put_byte(42, Case::Lower);
308        assert_eq!(encoder.space_remaining(), 1);
309        assert_eq!(encoder.as_str(), "2a");
310        assert!(!encoder.is_full());
311        encoder.clear();
312        assert_eq!(encoder.space_remaining(), 2);
313        encoder.put_byte(42, Case::Upper);
314        assert_eq!(encoder.as_str(), "2A");
315        assert_eq!(encoder.space_remaining(), 1);
316        assert!(!encoder.is_full());
317    }
318
319    #[test]
320    fn two_bytes() {
321        let mut buf = [0u8; 4];
322        let mut encoder = BufEncoder::new(&mut buf);
323        encoder.put_byte(42, Case::Lower);
324        assert_eq!(encoder.space_remaining(), 1);
325        encoder.put_byte(255, Case::Lower);
326        assert_eq!(encoder.space_remaining(), 0);
327        assert_eq!(encoder.as_str(), "2aff");
328        assert!(encoder.is_full());
329        encoder.clear();
330        assert!(!encoder.is_full());
331        encoder.put_byte(42, Case::Upper);
332        encoder.put_byte(255, Case::Upper);
333        assert_eq!(encoder.as_str(), "2AFF");
334        assert!(encoder.is_full());
335    }
336
337    #[test]
338    fn put_bytes_min() {
339        let mut buf = [0u8; 2];
340        let mut encoder = BufEncoder::new(&mut buf);
341        let remainder = encoder.put_bytes_min(b"", Case::Lower);
342        assert_eq!(remainder, b"");
343        assert_eq!(encoder.as_str(), "");
344        let remainder = encoder.put_bytes_min(b"*", Case::Lower);
345        assert_eq!(remainder, b"");
346        assert_eq!(encoder.as_str(), "2a");
347        encoder.clear();
348        let remainder = encoder.put_bytes_min(&[42, 255], Case::Lower);
349        assert_eq!(remainder, &[255]);
350        assert_eq!(encoder.as_str(), "2a");
351    }
352
353    #[test]
354    fn same_as_fmt() {
355        use core::fmt::{self, Write};
356
357        struct Writer {
358            buf: [u8; 2],
359            pos: usize,
360        }
361
362        impl Writer {
363            fn as_str(&self) -> &str { core::str::from_utf8(&self.buf[..self.pos]).unwrap() }
364        }
365
366        impl Write for Writer {
367            fn write_str(&mut self, s: &str) -> fmt::Result {
368                assert!(self.pos <= 2);
369                if s.len() > 2 - self.pos {
370                    Err(fmt::Error)
371                } else {
372                    self.buf[self.pos..(self.pos + s.len())].copy_from_slice(s.as_bytes());
373                    self.pos += s.len();
374                    Ok(())
375                }
376            }
377        }
378
379        let mut writer = Writer { buf: [0u8; 2], pos: 0 };
380        let mut buf = [0u8; 2];
381        let mut encoder = BufEncoder::new(&mut buf);
382
383        for i in 0..=255 {
384            write!(writer, "{:02x}", i).unwrap();
385            encoder.put_byte(i, Case::Lower);
386            assert_eq!(encoder.as_str(), writer.as_str());
387            writer.pos = 0;
388            encoder.clear();
389        }
390        for i in 0..=255 {
391            write!(writer, "{:02X}", i).unwrap();
392            encoder.put_byte(i, Case::Upper);
393            assert_eq!(encoder.as_str(), writer.as_str());
394            writer.pos = 0;
395            encoder.clear();
396        }
397    }
398}