hex_conservative/
display.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Helpers for displaying bytes as hex strings.
4//!
5//! This module provides a trait for displaying things as hex as well as an implementation for
6//! `&[u8]`.
7//!
8//! For arrays and slices we support padding and precision for length < 512 bytes.
9//!
10//! # Examples
11//!
12//! ```
13//! use hex_conservative::DisplayHex;
14//!
15//! // Display as hex.
16//! let v = vec![0xde, 0xad, 0xbe, 0xef];
17//! assert_eq!(format!("{}", v.as_hex()), "deadbeef");
18//!
19//! // Get the most significant bytes.
20//! let v = vec![0x01, 0x23, 0x45, 0x67];
21//! assert_eq!(format!("{0:.4}", v.as_hex()), "0123");
22//!
23//! // Padding with zeros
24//! let v = vec![0xab; 2];
25//! assert_eq!(format!("{:0>8}", v.as_hex()), "0000abab");
26//!```
27
28use core::borrow::Borrow;
29use core::fmt;
30
31use super::Case;
32use crate::buf_encoder::{BufEncoder, FixedLenBuf, OutBytes};
33#[cfg(feature = "alloc")]
34use crate::prelude::*;
35
36/// Extension trait for types that can be displayed as hex.
37///
38/// Types that have a single, obvious text representation being hex should **not** implement this
39/// trait and simply implement `Display` instead.
40///
41/// This trait should be generally implemented for references only. We would prefer to use GAT but
42/// that is beyond our MSRV. As a lint we require the `IsRef` trait which is implemented for all
43/// references.
44pub trait DisplayHex: Copy + sealed::IsRef {
45    /// The type providing [`fmt::Display`] implementation.
46    ///
47    /// This is usually a wrapper type holding a reference to `Self`.
48    type Display: fmt::Display + fmt::Debug + fmt::LowerHex + fmt::UpperHex;
49
50    /// Display `Self` as a continuous sequence of ASCII hex chars.
51    fn as_hex(self) -> Self::Display;
52
53    /// Create a lower-hex-encoded string.
54    ///
55    /// A shorthand for `to_hex_string(Case::Lower)`, so that `Case` doesn't need to be imported.
56    ///
57    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
58    #[cfg(feature = "alloc")]
59    fn to_lower_hex_string(self) -> String { self.to_hex_string(Case::Lower) }
60
61    /// Create an upper-hex-encoded string.
62    ///
63    /// A shorthand for `to_hex_string(Case::Upper)`, so that `Case` doesn't need to be imported.
64    ///
65    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
66    #[cfg(feature = "alloc")]
67    fn to_upper_hex_string(self) -> String { self.to_hex_string(Case::Upper) }
68
69    /// Create a hex-encoded string.
70    ///
71    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
72    #[cfg(feature = "alloc")]
73    fn to_hex_string(self, case: Case) -> String {
74        let mut string = String::new();
75        self.append_hex_to_string(case, &mut string);
76        string
77    }
78
79    /// Appends hex-encoded content to an existing `String`.
80    ///
81    /// This may be faster than `write!(string, "{:x}", self.as_hex())` because it uses
82    /// `hex_reserve_sugggestion`.
83    #[cfg(feature = "alloc")]
84    fn append_hex_to_string(self, case: Case, string: &mut String) {
85        use fmt::Write;
86
87        string.reserve(self.hex_reserve_suggestion());
88        match case {
89            Case::Lower => write!(string, "{:x}", self.as_hex()),
90            Case::Upper => write!(string, "{:X}", self.as_hex()),
91        }
92        .unwrap_or_else(|_| {
93            let name = core::any::type_name::<Self::Display>();
94            // We don't expect `std` to ever be buggy, so the bug is most likely in the `Display`
95            // impl of `Self::Display`.
96            panic!("The implementation of Display for {} returned an error when it shouldn't", name)
97        })
98    }
99
100    /// Hints how much bytes to reserve when creating a `String`.
101    ///
102    /// Implementors that know the number of produced bytes upfront should override this.
103    /// Defaults to 0.
104    ///
105    // We prefix the name with `hex_` to avoid potential collision with other methods.
106    fn hex_reserve_suggestion(self) -> usize { 0 }
107}
108
109mod sealed {
110    /// Trait marking a shared reference.
111    pub trait IsRef: Copy {}
112
113    impl<T: ?Sized> IsRef for &'_ T {}
114}
115
116impl<'a> DisplayHex for &'a [u8] {
117    type Display = DisplayByteSlice<'a>;
118
119    #[inline]
120    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
121
122    #[inline]
123    fn hex_reserve_suggestion(self) -> usize {
124        // Since the string wouldn't fit into address space if this overflows (actually even for
125        // smaller amounts) it's better to panic right away. It should also give the optimizer
126        // better opportunities.
127        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
128    }
129}
130
131#[cfg(feature = "alloc")]
132impl<'a> DisplayHex for &'a alloc::vec::Vec<u8> {
133    type Display = DisplayByteSlice<'a>;
134
135    #[inline]
136    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
137
138    #[inline]
139    fn hex_reserve_suggestion(self) -> usize {
140        // Since the string wouldn't fit into address space if this overflows (actually even for
141        // smaller amounts) it's better to panic right away. It should also give the optimizer
142        // better opportunities.
143        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
144    }
145}
146
147/// Displays byte slice as hex.
148///
149/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
150pub struct DisplayByteSlice<'a> {
151    // pub because we want to keep lengths in sync
152    pub(crate) bytes: &'a [u8],
153}
154
155impl<'a> DisplayByteSlice<'a> {
156    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
157        let mut buf = [0u8; 1024];
158        let mut encoder = BufEncoder::new(&mut buf);
159
160        // Its unlikely that someone will want special formatting for a hex string that
161        // is over 1024 characters so just handle padding for short slices.
162        if self.bytes.len() < 512 {
163            encoder.put_bytes(self.bytes, case);
164            return f.pad(encoder.as_str());
165        }
166
167        let mut chunks = self.bytes.chunks_exact(512);
168        for chunk in &mut chunks {
169            encoder.put_bytes(chunk, case);
170            f.write_str(encoder.as_str())?;
171            encoder.clear();
172        }
173        encoder.put_bytes(chunks.remainder(), case);
174        f.write_str(encoder.as_str())
175    }
176}
177
178impl<'a> fmt::Display for DisplayByteSlice<'a> {
179    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
180}
181
182impl<'a> fmt::Debug for DisplayByteSlice<'a> {
183    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
184}
185
186impl<'a> fmt::LowerHex for DisplayByteSlice<'a> {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
188}
189
190impl<'a> fmt::UpperHex for DisplayByteSlice<'a> {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
192}
193
194/// Displays byte array as hex.
195///
196/// Created by [`<&[u8; LEN] as DisplayHex>::as_hex`](DisplayHex::as_hex).
197// See `buf_encoder::impl_encode!` for `DisplayHex` implementation.
198pub struct DisplayArray<A: Clone + IntoIterator, B: FixedLenBuf>
199where
200    A::Item: Borrow<u8>,
201{
202    array: A,
203    _buffer_marker: core::marker::PhantomData<B>,
204}
205
206impl<A: Clone + IntoIterator, B: FixedLenBuf> DisplayArray<A, B>
207where
208    A::Item: Borrow<u8>,
209{
210    /// Creates the wrapper.
211    pub fn new(array: A) -> Self { DisplayArray { array, _buffer_marker: Default::default() } }
212
213    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
214        let mut buf = B::uninit();
215        let mut encoder = BufEncoder::new(&mut buf);
216        encoder.put_bytes(self.array.clone(), case);
217        f.pad_integral(true, "0x", encoder.as_str())
218    }
219}
220
221impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::Display for DisplayArray<A, B>
222where
223    A::Item: Borrow<u8>,
224{
225    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
226}
227
228impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::Debug for DisplayArray<A, B>
229where
230    A::Item: Borrow<u8>,
231{
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
233}
234
235impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::LowerHex for DisplayArray<A, B>
236where
237    A::Item: Borrow<u8>,
238{
239    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
240}
241
242impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::UpperHex for DisplayArray<A, B>
243where
244    A::Item: Borrow<u8>,
245{
246    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
247}
248
249/// Format known-length array as hex.
250///
251/// This supports all formatting options of formatter and may be faster than calling `as_hex()` on
252/// an arbitrary `&[u8]`. Note that the implementation intentionally keeps leading zeros even when
253/// not requested. This is designed to display values such as hashes and keys and removing leading
254/// zeros would be confusing.
255///
256/// Note that the bytes parameter is `IntoIterator` this means that if you would like to do some
257/// manipulation to the byte array before formatting then you can. For example `bytes.iter().rev()`
258/// to print the array backwards.
259///
260/// ## Parameters
261///
262/// * `$formatter` - a [`fmt::Formatter`].
263/// * `$len` known length of `$bytes`, must be a const expression.
264/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
265/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
266///
267/// ## Panics
268///
269/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len`
270/// is more than half of `usize::MAX`.
271#[macro_export]
272macro_rules! fmt_hex_exact {
273    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
274        // statically check $len
275        #[allow(deprecated)]
276        const _: () = [()][($len > usize::MAX / 2) as usize];
277        assert_eq!($bytes.len(), $len);
278        let mut buf = [0u8; $len * 2];
279        let buf = $crate::buf_encoder::AsOutBytes::as_mut_out_bytes(&mut buf);
280        $crate::display::fmt_hex_exact_fn($formatter, buf, $bytes, $case)
281    }};
282}
283pub use fmt_hex_exact;
284
285// Implementation detail of `write_hex_exact` macro to de-duplicate the code
286#[doc(hidden)]
287#[inline]
288pub fn fmt_hex_exact_fn<I>(
289    f: &mut fmt::Formatter,
290    buf: &mut OutBytes,
291    bytes: I,
292    case: Case,
293) -> fmt::Result
294where
295    I: IntoIterator,
296    I::Item: Borrow<u8>,
297{
298    let mut encoder = BufEncoder::new(buf);
299    encoder.put_bytes(bytes, case);
300    f.pad_integral(true, "0x", encoder.as_str())
301}
302
303#[cfg(test)]
304mod tests {
305    #[cfg(feature = "alloc")]
306    use super::*;
307
308    #[cfg(feature = "alloc")]
309    mod alloc {
310        use super::*;
311
312        fn check_encoding(bytes: &[u8]) {
313            use core::fmt::Write;
314
315            let s1 = bytes.to_lower_hex_string();
316            let mut s2 = String::with_capacity(bytes.len() * 2);
317            for b in bytes {
318                write!(s2, "{:02x}", b).unwrap();
319            }
320            assert_eq!(s1, s2);
321        }
322
323        #[test]
324        fn empty() { check_encoding(b""); }
325
326        #[test]
327        fn single() { check_encoding(b"*"); }
328
329        #[test]
330        fn two() { check_encoding(b"*x"); }
331
332        #[test]
333        fn just_below_boundary() { check_encoding(&[42; 512]); }
334
335        #[test]
336        fn just_above_boundary() { check_encoding(&[42; 513]); }
337
338        #[test]
339        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
340
341        #[test]
342        fn fmt_exact_macro() {
343            use crate::alloc::string::ToString;
344
345            struct Dummy([u8; 32]);
346
347            impl fmt::Display for Dummy {
348                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
349                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
350                }
351            }
352
353            assert_eq!(Dummy([42; 32]).to_string(), "2a".repeat(32));
354        }
355
356        #[test]
357        fn display_short_with_padding() {
358            let v = vec![0xbe, 0xef];
359            assert_eq!(format!("Hello {:<8}!", v.as_hex()), "Hello beef    !");
360            assert_eq!(format!("Hello {:-<8}!", v.as_hex()), "Hello beef----!");
361            assert_eq!(format!("Hello {:^8}!", v.as_hex()), "Hello   beef  !");
362            assert_eq!(format!("Hello {:>8}!", v.as_hex()), "Hello     beef!");
363        }
364
365        // We only pad arrays 512 bytes and shorter.
366        #[test]
367        fn display_long_no_padding() {
368            // Sanity.
369            let x = 1;
370            // This is here to show how long 2000 is so one can visually see the test below does not pad.
371            let want = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001";
372            let got = format!("{:0>2000}", x);
373            assert_eq!(got, want);
374
375            // Note this string is shorter than the one above.
376            let v = vec![0xab; 512];
377            let want = "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab";
378            let got = format!("{:0>2000}", v.as_hex());
379            assert_eq!(got, want)
380        }
381    }
382}