Skip to main content

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
28#[cfg(feature = "alloc")]
29use alloc::string::String;
30use core::borrow::Borrow;
31use core::fmt;
32
33use super::Case;
34#[cfg(feature = "std")]
35use super::Table;
36use crate::buf_encoder::BufEncoder;
37
38/// Extension trait for types that can be displayed as hex.
39///
40/// Types that have a single, obvious text representation being hex should **not** implement this
41/// trait and simply implement `Display` instead.
42pub trait DisplayHex {
43    /// The type providing [`fmt::Display`] implementation.
44    ///
45    /// This is a wrapper type holding a reference to `Self`.
46    type Display<'a>: fmt::Display + fmt::Debug + fmt::LowerHex + fmt::UpperHex
47    where
48        Self: 'a;
49
50    /// Display `Self` as a continuous sequence of ASCII hex chars.
51    fn as_hex<'a>(&'a self) -> Self::Display<'a>;
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    #[inline]
60    fn to_lower_hex_string(&self) -> String { self.to_hex_string(Case::Lower) }
61
62    /// Create an upper-hex-encoded string.
63    ///
64    /// A shorthand for `to_hex_string(Case::Upper)`, so that `Case` doesn't need to be imported.
65    ///
66    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
67    #[cfg(feature = "alloc")]
68    #[inline]
69    fn to_upper_hex_string(&self) -> String { self.to_hex_string(Case::Upper) }
70
71    /// Create a hex-encoded string.
72    ///
73    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
74    #[cfg(feature = "alloc")]
75    fn to_hex_string(&self, case: Case) -> String {
76        let mut string = String::new();
77        self.append_hex_to_string(case, &mut string);
78        string
79    }
80
81    /// Appends hex-encoded content to an existing `String`.
82    ///
83    /// This may be faster than `write!(string, "{:x}", self.as_hex())` because it uses
84    /// `hex_reserve_sugggestion`.
85    #[cfg(feature = "alloc")]
86    fn append_hex_to_string<'a>(&'a self, case: Case, string: &mut String) {
87        use fmt::Write;
88
89        string.reserve(self.hex_reserve_suggestion());
90        match case {
91            Case::Lower => write!(string, "{:x}", self.as_hex()),
92            Case::Upper => write!(string, "{:X}", self.as_hex()),
93        }
94        .unwrap_or_else(|_| {
95            let name = core::any::type_name::<Self::Display<'a>>();
96            // We don't expect `std` to ever be buggy, so the bug is most likely in the `Display`
97            // impl of `Self::Display`.
98            panic!("The implementation of Display for {} returned an error when it shouldn't", name)
99        });
100    }
101
102    /// Hints how many bytes to reserve when creating a `String`.
103    ///
104    /// If you don't know you can just return 0 and take the perf hit.
105    // We prefix the name with `hex_` to avoid potential collision with other methods.
106    fn hex_reserve_suggestion(&self) -> usize;
107}
108
109fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Result {
110    use fmt::Write;
111    // There are at least two optimizations left:
112    //
113    // * Reusing the buffer (encoder) which may decrease the number of virtual calls
114    // * Not recursing, avoiding another 1024B allocation and zeroing
115    //
116    // This would complicate the code so I was too lazy to do them but feel free to send a PR!
117
118    let mut encoder = BufEncoder::<1024>::new(case);
119    let pad_right = write_pad_left(f, bytes.len(), &mut encoder)?;
120
121    if f.alternate() {
122        f.write_str("0x")?;
123    }
124    match f.precision() {
125        Some(max) if bytes.len() > max / 2 => {
126            match case {
127                Case::Lower => write!(f, "{:x}", bytes[..(max / 2)].as_hex())?,
128                Case::Upper => write!(f, "{:X}", bytes[..(max / 2)].as_hex())?,
129            }
130            if max % 2 == 1 {
131                f.write_char(case.table().byte_to_chars(bytes[max / 2])[0])?;
132            }
133        }
134        Some(_) | None => {
135            let mut chunks = bytes.chunks_exact(512);
136            for chunk in &mut chunks {
137                encoder.put_bytes(chunk);
138                f.write_str(encoder.as_str())?;
139                encoder.clear();
140            }
141            encoder.put_bytes(chunks.remainder());
142            f.write_str(encoder.as_str())?;
143        }
144    }
145
146    write_pad_right(f, pad_right, &mut encoder)
147}
148
149fn write_pad_left(
150    f: &mut fmt::Formatter,
151    bytes_len: usize,
152    encoder: &mut BufEncoder<1024>,
153) -> Result<usize, fmt::Error> {
154    let pad_right = if let Some(width) = f.width() {
155        // Add space for 2 characters if the '#' flag is set
156        let full_string_len = if f.alternate() { bytes_len * 2 + 2 } else { bytes_len * 2 };
157        let string_len = match f.precision() {
158            Some(max) => core::cmp::min(max, full_string_len),
159            None => full_string_len,
160        };
161
162        if string_len < width {
163            let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) {
164                fmt::Alignment::Left => (0, width - string_len),
165                fmt::Alignment::Right => (width - string_len, 0),
166                fmt::Alignment::Center =>
167                    ((width - string_len) / 2, (width - string_len).div_ceil(2)),
168            };
169            // Avoid division by zero and optimize for common case.
170            if left > 0 {
171                let c = f.fill();
172                let chunk_len = encoder.put_filler(c, left);
173                let padding = encoder.as_str();
174                for _ in 0..(left / chunk_len) {
175                    f.write_str(padding)?;
176                }
177                f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?;
178                encoder.clear();
179            }
180            right
181        } else {
182            0
183        }
184    } else {
185        0
186    };
187    Ok(pad_right)
188}
189
190fn write_pad_right(
191    f: &mut fmt::Formatter,
192    pad_right: usize,
193    encoder: &mut BufEncoder<1024>,
194) -> fmt::Result {
195    // Avoid division by zero and optimize for common case.
196    if pad_right > 0 {
197        encoder.clear();
198        let c = f.fill();
199        let chunk_len = encoder.put_filler(c, pad_right);
200        let padding = encoder.as_str();
201        for _ in 0..(pad_right / chunk_len) {
202            f.write_str(padding)?;
203        }
204        f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?;
205    }
206    Ok(())
207}
208
209impl DisplayHex for [u8] {
210    type Display<'a> = DisplayByteSlice<'a>;
211
212    #[inline]
213    fn as_hex<'a>(&'a self) -> Self::Display<'a> { DisplayByteSlice { bytes: self } }
214
215    #[inline]
216    fn hex_reserve_suggestion(&self) -> usize {
217        // Since the string wouldn't fit into address space if this overflows (actually even for
218        // smaller amounts) it's better to panic right away. It should also give the optimizer
219        // better opportunities.
220        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
221    }
222}
223
224/// Displays byte slice as hex.
225///
226/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
227#[derive(Clone, PartialEq, Eq, Hash)]
228pub struct DisplayByteSlice<'a> {
229    // pub because we want to keep lengths in sync
230    pub(crate) bytes: &'a [u8],
231}
232
233impl DisplayByteSlice<'_> {
234    #[inline]
235    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
236        internal_display(self.bytes, f, case)
237    }
238}
239
240impl fmt::Display for DisplayByteSlice<'_> {
241    #[inline]
242    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
243}
244
245impl fmt::Debug for DisplayByteSlice<'_> {
246    #[inline]
247    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
248}
249
250impl fmt::LowerHex for DisplayByteSlice<'_> {
251    #[inline]
252    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
253}
254
255impl fmt::UpperHex for DisplayByteSlice<'_> {
256    #[inline]
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
258}
259
260/// Efficiently formats a sequence of bytes as a hexadecimal string with compile-time-known length.
261///
262/// Whenever the length of a sequence of bytes (usually an array, but it could be any iterator)
263/// is known at compile time, it is more efficient to use this macro because various length checks
264/// and loops are elided. The buffer is just filled once and then emitted to the formatter.
265///
266/// This supports all formatting options of formatter and may be faster than calling `as_hex()` on
267/// an arbitrary `&[u8]`. Note that the implementation intentionally keeps leading zeros even when
268/// not requested. This is designed to display values such as hashes and keys and removing leading
269/// zeros would be confusing.
270///
271/// Note that the bytes parameter is `IntoIterator` this means that if you would like to do some
272/// manipulation to the byte array before formatting then you can. For example `bytes.iter().rev()`
273/// to print the array backwards.
274///
275/// ## Parameters
276///
277/// * `$formatter` - a [`fmt::Formatter`].
278/// * `$len` known length of `$bytes`, must be a const expression.
279/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
280/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
281///
282/// ## Returns
283///
284/// Returns [`core::fmt::Result`].
285///
286/// ## Panics
287///
288/// This macro panics if the length of the encoded item is larger than `$len`.
289///
290/// ## Static Assertions
291///
292/// The use of a macro instead of a function allows for compile-time length validation. This macro
293/// fails to compile if `$len` is more than half of `usize::MAX`. This prevents runtime panics or
294/// logic errors when formatting fixed-size primitives like Bitcoin hashes or public keys.
295///
296/// ## Examples
297///
298/// ```rust
299/// use hex_conservative::{fmt_hex_max, Case};
300/// use std::fmt;
301///
302/// struct MyHash([u8; 32]);
303///
304/// impl fmt::LowerHex for MyHash {
305///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306///         // Explicitly use Lower case for {:x}
307///         fmt_hex_max!(f, 32, &self.0, Case::Lower)
308///     }
309/// }
310///
311/// impl fmt::UpperHex for MyHash {
312///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
313///         // Explicitly use Upper case for {:X}
314///         fmt_hex_max!(f, 32, &self.0, Case::Upper)
315///     }
316/// }
317/// ```
318#[macro_export]
319macro_rules! fmt_hex_max {
320    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
321        // statically check $len
322        #[allow(deprecated)]
323        const _: () = [()][($len > usize::MAX / 2) as usize];
324        assert!(
325            $bytes.len() <= $len,
326            "length of the encoded item ({}) is larger than {}",
327            $bytes.len(),
328            $len
329        );
330        $crate::display::fmt_hex_max_fn::<_, { $len * 2 }>($formatter, $bytes, $case)
331    }};
332}
333pub use fmt_hex_max;
334
335/// Formats bytes as hex with runtime and compile-time length checks.
336///
337/// ## Panics
338///
339/// This macro panics if `$len` is not equal to `$bytes.len()`
340///
341/// See [`fmt_hex_max`] for details.
342#[macro_export]
343macro_rules! fmt_hex_exact {
344    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
345        assert_eq!($bytes.len(), $len);
346        $crate::fmt_hex_max!($formatter, $len, $bytes, $case)
347    }};
348}
349pub use fmt_hex_exact;
350
351/// Formats bytes as hex in lower case.
352///
353/// See [`fmt_hex_max!`] for details.
354#[macro_export]
355macro_rules! fmt_hex_lower {
356    ($formatter:expr, $len:expr, $bytes:expr) => {
357        $crate::fmt_hex_max!($formatter, $len, $bytes, $crate::Case::Lower)
358    };
359}
360pub use fmt_hex_lower;
361
362/// Formats bytes as hex in upper case.
363///
364/// See [`fmt_hex_max!`] for details.
365#[macro_export]
366macro_rules! fmt_hex_upper {
367    ($formatter:expr, $len:expr, $bytes:expr) => {
368        $crate::fmt_hex_max!($formatter, $len, $bytes, $crate::Case::Upper)
369    };
370}
371pub use fmt_hex_upper;
372
373/// Adds `core::fmt` trait implementations to type `$ty`.
374///
375/// Implements:
376///
377/// - `fmt::{LowerHex, UpperHex}` using [`fmt_hex_exact`].
378/// - `fmt::{Display, Debug}` by calling `LowerHex`.
379///
380/// Requires:
381///
382/// - `$ty` must implement `IntoIterator<Item=Borrow<u8>>`.
383///
384/// ## Parameters
385///
386/// * `$ty` - the type to implement traits on.
387/// * `$len` - known length of `$bytes`, must be a const expression.
388/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
389/// * `$reverse` - true if you want the array to be displayed backwards.
390/// * `$gen: $gent` - optional generic type(s) and trait bound(s) to put on `$ty` e.g, `F: Foo`.
391///
392/// ## Examples
393///
394/// ```
395/// # use core::borrow::Borrow;
396/// # use hex_conservative::impl_fmt_traits;
397/// struct Wrapper([u8; 4]);
398///
399/// impl Borrow<[u8]> for Wrapper {
400///     fn borrow(&self) -> &[u8] { &self.0[..] }
401/// }
402///
403/// impl_fmt_traits! {
404///     impl fmt_traits for Wrapper {
405///         const LENGTH: usize = 4;
406///     }
407/// }
408///
409/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
410/// assert_eq!(format!("{}", w), "12345678");
411/// ```
412///
413/// We support generics on `$ty`:
414///
415/// ```
416/// # use core::borrow::Borrow;
417/// # use core::marker::PhantomData;
418/// # use hex_conservative::impl_fmt_traits;
419/// struct Wrapper<T>([u8; 4], PhantomData<T>);
420///
421/// // `Clone` is just some arbitrary trait.
422/// impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
423///     fn borrow(&self) -> &[u8] { &self.0[..] }
424/// }
425///
426/// impl_fmt_traits! {
427///     impl<T: Clone> fmt_traits for Wrapper<T> {
428///         const LENGTH: usize = 4;
429///     }
430/// }
431///
432/// let w = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
433/// assert_eq!(format!("{}", w), "12345678");
434/// ```
435///
436/// And also, as is required by `rust-bitcoin`, we support displaying
437/// the hex string byte-wise backwards:
438///
439/// ```
440/// # use core::borrow::Borrow;
441/// # use hex_conservative::impl_fmt_traits;
442/// struct Wrapper([u8; 4]);
443///
444/// impl Borrow<[u8]> for Wrapper {
445///     fn borrow(&self) -> &[u8] { &self.0[..] }
446/// }
447///
448/// impl_fmt_traits! {
449///     #[display_backward(true)]
450///     impl fmt_traits for Wrapper {
451///         const LENGTH: usize = 4;
452///     }
453/// }
454/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
455/// assert_eq!(format!("{}", w), "78563412");
456/// ```
457#[macro_export]
458macro_rules! impl_fmt_traits {
459    // Without generic and trait bounds and without display_backward attribute.
460    (impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
461        $crate::impl_fmt_traits! {
462            #[display_backward(false)]
463            impl<> fmt_traits for $ty<> {
464                const LENGTH: usize = $len;
465            }
466        }
467    };
468    // Without generic and trait bounds and with display_backward attribute.
469    (#[display_backward($reverse:expr)] impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
470        $crate::impl_fmt_traits! {
471            #[display_backward($reverse)]
472            impl<> fmt_traits for $ty<> {
473                const LENGTH: usize = $len;
474            }
475        }
476    };
477    // With generic and trait bounds and without display_backward attribute.
478    (impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
479        $crate::impl_fmt_traits! {
480            #[display_backward(false)]
481            impl<$($gen: $gent),*> fmt_traits for $ty<$($unused),*> {
482                const LENGTH: usize = $len;
483            }
484        }
485    };
486    // With generic and trait bounds and display_backward attribute.
487    (#[display_backward($reverse:expr)] impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
488        impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
489            #[inline]
490            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
491                let case = $crate::Case::Lower;
492
493                if $reverse {
494                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
495                    $crate::fmt_hex_exact!(f, $len, bytes, case)
496                } else {
497                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
498                    $crate::fmt_hex_exact!(f, $len, bytes, case)
499                }
500            }
501        }
502
503        impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
504            #[inline]
505            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
506                let case = $crate::Case::Upper;
507
508                if $reverse {
509                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
510                    $crate::fmt_hex_exact!(f, $len, bytes, case)
511                } else {
512                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
513                    $crate::fmt_hex_exact!(f, $len, bytes, case)
514                }
515            }
516        }
517
518        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
519            #[inline]
520            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
521                $crate::_export::_core::fmt::LowerHex::fmt(self, f)
522            }
523        }
524
525        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> {
526            #[inline]
527            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
528                $crate::_export::_core::fmt::LowerHex::fmt(&self, f)
529            }
530        }
531    };
532}
533pub use impl_fmt_traits;
534
535// Implementation detail of `fmt_hex_max` macro to de-duplicate the code
536//
537// Whether hex is an integer or a string is debatable, we cater a little bit to each.
538// - We support users adding `0x` prefix using "{:#}" (treating hex like an integer).
539// - We support limiting the output using precision "{:.10}" (treating hex like a string).
540//
541// This assumes `bytes.len() * 2 == N`.
542#[doc(hidden)]
543#[inline]
544pub fn fmt_hex_max_fn<I, const N: usize>(
545    f: &mut fmt::Formatter,
546    bytes: I,
547    case: Case,
548) -> fmt::Result
549where
550    I: IntoIterator,
551    I::Item: Borrow<u8>,
552{
553    let mut padding_encoder = BufEncoder::<1024>::new(case);
554    let pad_right = write_pad_left(f, N / 2, &mut padding_encoder)?;
555
556    if f.alternate() {
557        f.write_str("0x")?;
558    }
559    let mut encoder = BufEncoder::<N>::new(case);
560    let encoded = match f.precision() {
561        Some(p) if p < N => {
562            let n = p.div_ceil(2);
563            encoder.put_bytes(bytes.into_iter().take(n));
564            &encoder.as_str()[..p]
565        }
566        _ => {
567            encoder.put_bytes(bytes);
568            encoder.as_str()
569        }
570    };
571    f.write_str(encoded)?;
572
573    write_pad_right(f, pad_right, &mut padding_encoder)
574}
575
576/// Given a `T:` [`fmt::Write`], `HexWriter` implements [`std::io::Write`]
577/// and writes the source bytes to its inner `T` as hex characters.
578#[cfg(feature = "std")]
579#[derive(Debug, Clone, PartialEq, Eq, Hash)]
580pub struct HexWriter<T> {
581    writer: T,
582    table: &'static Table,
583}
584
585#[cfg(feature = "std")]
586impl<T> HexWriter<T> {
587    /// Creates a `HexWriter` that writes the source bytes to `dest` as hex characters
588    /// in the given `case`.
589    ///
590    /// Note even though we take ownership of the writer one can also call this with `&mut dest`.
591    pub fn new(dest: T, case: Case) -> Self { Self { writer: dest, table: case.table() } }
592    /// Consumes this `HexWriter` returning the inner `T`.
593    pub fn into_inner(self) -> T { self.writer }
594}
595
596#[cfg(feature = "std")]
597impl<T> std::io::Write for HexWriter<T>
598where
599    T: core::fmt::Write,
600{
601    /// Writes `buf` into [`HexWriter`].
602    ///
603    /// # Errors
604    ///
605    /// If no bytes could be written to this `HexWriter`, and the provided buffer is not empty,
606    /// returns [`std::io::ErrorKind::Other`], otherwise returns `Ok`.
607    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
608        let mut n = 0;
609        for byte in buf {
610            let mut hex_chars = [0u8; 2];
611            let hex_str = self.table.byte_to_str(&mut hex_chars, *byte);
612            if self.writer.write_str(hex_str).is_err() {
613                break;
614            }
615            n += 1;
616        }
617        if n == 0 && !buf.is_empty() {
618            Err(std::io::ErrorKind::Other.into())
619        } else {
620            Ok(n)
621        }
622    }
623
624    /// `flush` is a no-op for [`HexWriter`].
625    ///
626    /// # Errors
627    ///
628    /// [`HexWriter`] never errors when flushing.
629    fn flush(&mut self) -> Result<(), std::io::Error> { Ok(()) }
630}
631
632#[cfg(test)]
633mod tests {
634    #[cfg(feature = "alloc")]
635    use super::*;
636
637    #[cfg(feature = "alloc")]
638    mod alloc {
639        use core::marker::PhantomData;
640
641        use super::*;
642        use crate::alloc::vec::Vec;
643
644        fn check_encoding(bytes: &[u8]) {
645            use core::fmt::Write;
646
647            let s1 = bytes.to_lower_hex_string();
648            let mut s2 = String::with_capacity(bytes.len() * 2);
649            for b in bytes {
650                write!(s2, "{:02x}", b).unwrap();
651            }
652            assert_eq!(s1, s2);
653        }
654
655        #[test]
656        fn empty() { check_encoding(b""); }
657
658        #[test]
659        fn single() { check_encoding(b"*"); }
660
661        #[test]
662        fn two() { check_encoding(b"*x"); }
663
664        #[test]
665        fn just_below_boundary() { check_encoding(&[42; 512]); }
666
667        #[test]
668        fn just_above_boundary() { check_encoding(&[42; 513]); }
669
670        #[test]
671        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
672
673        #[test]
674        fn fmt_exact_macro() {
675            use crate::alloc::string::ToString;
676
677            struct Dummy([u8; 32]);
678
679            impl fmt::Display for Dummy {
680                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
681                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
682                }
683            }
684            let dummy = Dummy([42; 32]);
685            assert_eq!(dummy.to_string(), "2a".repeat(32));
686            assert_eq!(format!("{:.10}", dummy), "2a".repeat(5));
687            assert_eq!(format!("{:.11}", dummy), "2a".repeat(5) + "2");
688            assert_eq!(format!("{:.65}", dummy), "2a".repeat(32));
689        }
690
691        struct TestHexUpperLower<'a>(&'a [u8], bool);
692
693        impl fmt::Display for TestHexUpperLower<'_> {
694            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
695                if self.1 {
696                    fmt_hex_upper!(f, 3, self.0)
697                } else {
698                    fmt_hex_lower!(f, 3, self.0)
699                }
700            }
701        }
702
703        #[test]
704        fn fmt_hex_lower_macro() {
705            let bytes = [0x1a, 0x2b, 0x3c];
706            assert_eq!(format!("{}", TestHexUpperLower(&bytes, false)), "1a2b3c");
707        }
708
709        #[test]
710        fn fmt_hex_upper_macro() {
711            let bytes = [0x1a, 0x2b, 0x3c];
712            assert_eq!(format!("{}", TestHexUpperLower(&bytes, true)), "1A2B3C");
713        }
714
715        macro_rules! define_dummy {
716            ($len:literal) => {
717                struct Dummy([u8; $len]);
718                impl fmt::Debug for Dummy {
719                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
720                        fmt_hex_exact!(f, $len, &self.0, Case::Lower)
721                    }
722                }
723                impl fmt::Display for Dummy {
724                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
725                        fmt_hex_exact!(f, $len, &self.0, Case::Lower)
726                    }
727                }
728            };
729        }
730
731        macro_rules! test_display_hex {
732            ($fs: expr, $a: expr, $check: expr) => {
733                let array = $a;
734                let slice = &$a;
735                let vec = Vec::from($a);
736                let dummy = Dummy($a);
737                assert_eq!(format!($fs, array.as_hex()), $check);
738                assert_eq!(format!($fs, slice.as_hex()), $check);
739                assert_eq!(format!($fs, vec.as_hex()), $check);
740                assert_eq!(format!($fs, dummy), $check);
741            };
742        }
743
744        #[test]
745        fn alternate_flag() {
746            define_dummy!(4);
747
748            test_display_hex!("{:#?}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe");
749            test_display_hex!("{:#}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe");
750        }
751
752        #[test]
753        fn display_short_with_padding() {
754            define_dummy!(2);
755
756            test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef    !");
757            test_display_hex!("Hello {:-<8}!", [0xbe, 0xef], "Hello beef----!");
758            test_display_hex!("Hello {:^8}!", [0xbe, 0xef], "Hello   beef  !");
759            test_display_hex!("Hello {:>8}!", [0xbe, 0xef], "Hello     beef!");
760
761            test_display_hex!("Hello {:<#8}!", [0xbe, 0xef], "Hello 0xbeef  !");
762            test_display_hex!("Hello {:-<#8}!", [0xbe, 0xef], "Hello 0xbeef--!");
763            test_display_hex!("Hello {:^#8}!", [0xbe, 0xef], "Hello  0xbeef !");
764            test_display_hex!("Hello {:>#8}!", [0xbe, 0xef], "Hello   0xbeef!");
765        }
766
767        #[test]
768        fn display_long() {
769            define_dummy!(512);
770            // Note this string is shorter than the one above.
771            let a = [0xab; 512];
772
773            let mut want = "0".repeat(2000 - 1024);
774            want.extend(core::iter::repeat("ab").take(512));
775            test_display_hex!("{:0>2000}", a, want);
776
777            let mut want = "0".repeat(2000 - 1026);
778            want.push_str("0x");
779            want.extend(core::iter::repeat("ab").take(512));
780            test_display_hex!("{:0>#2000}", a, want);
781        }
782
783        // Precision and padding act the same as for strings in the stdlib (because we use `Formatter::pad`).
784
785        #[test]
786        fn precision_truncates() {
787            // Precision gets the most significant bytes.
788            // Remember the integer is number of hex chars not number of bytes.
789            define_dummy!(4);
790
791            test_display_hex!("{0:.4}", [0x12, 0x34, 0x56, 0x78], "1234");
792            test_display_hex!("{0:.5}", [0x12, 0x34, 0x56, 0x78], "12345");
793
794            test_display_hex!("{0:#.4}", [0x12, 0x34, 0x56, 0x78], "0x1234");
795            test_display_hex!("{0:#.5}", [0x12, 0x34, 0x56, 0x78], "0x12345");
796        }
797
798        #[test]
799        fn precision_with_padding_truncates() {
800            // Precision gets the most significant bytes.
801            define_dummy!(4);
802
803            test_display_hex!("{0:10.4}", [0x12, 0x34, 0x56, 0x78], "1234      ");
804            test_display_hex!("{0:10.5}", [0x12, 0x34, 0x56, 0x78], "12345     ");
805
806            test_display_hex!("{0:#10.4}", [0x12, 0x34, 0x56, 0x78], "0x1234      ");
807            test_display_hex!("{0:#10.5}", [0x12, 0x34, 0x56, 0x78], "0x12345     ");
808        }
809
810        #[test]
811        fn precision_with_padding_pads_right() {
812            define_dummy!(4);
813
814            test_display_hex!("{0:10.20}", [0x12, 0x34, 0x56, 0x78], "12345678  ");
815            test_display_hex!("{0:10.14}", [0x12, 0x34, 0x56, 0x78], "12345678  ");
816
817            test_display_hex!("{0:#12.20}", [0x12, 0x34, 0x56, 0x78], "0x12345678  ");
818            test_display_hex!("{0:#12.14}", [0x12, 0x34, 0x56, 0x78], "0x12345678  ");
819        }
820
821        #[test]
822        fn precision_with_padding_pads_left() {
823            define_dummy!(4);
824
825            test_display_hex!("{0:>10.20}", [0x12, 0x34, 0x56, 0x78], "  12345678");
826
827            test_display_hex!("{0:>#12.20}", [0x12, 0x34, 0x56, 0x78], "  0x12345678");
828        }
829
830        #[test]
831        fn precision_with_padding_pads_center() {
832            define_dummy!(4);
833
834            test_display_hex!("{0:^10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 ");
835
836            test_display_hex!("{0:^#12.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678 ");
837        }
838
839        #[test]
840        fn precision_with_padding_pads_center_odd() {
841            define_dummy!(4);
842
843            test_display_hex!("{0:^11.20}", [0x12, 0x34, 0x56, 0x78], " 12345678  ");
844
845            test_display_hex!("{0:^#13.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678  ");
846        }
847
848        #[test]
849        fn precision_does_not_extend() {
850            define_dummy!(4);
851
852            test_display_hex!("{0:.16}", [0x12, 0x34, 0x56, 0x78], "12345678");
853
854            test_display_hex!("{0:#.16}", [0x12, 0x34, 0x56, 0x78], "0x12345678");
855        }
856
857        #[test]
858        fn padding_extends() {
859            define_dummy!(2);
860
861            test_display_hex!("{:0>8}", [0xab; 2], "0000abab");
862
863            test_display_hex!("{:0>#8}", [0xab; 2], "000xabab");
864        }
865
866        #[test]
867        fn padding_does_not_truncate() {
868            define_dummy!(4);
869
870            test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678");
871            test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678");
872
873            test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678");
874            test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678");
875        }
876
877        // Tests `impl_fmt_traits` in module scope.
878        // ref: https://rust-lang.github.io/api-guidelines/macros.html#c-anywhere
879        #[allow(dead_code)]
880        struct Wrapper([u8; 4]);
881
882        impl Borrow<[u8]> for Wrapper {
883            fn borrow(&self) -> &[u8] { &self.0[..] }
884        }
885
886        impl_fmt_traits! {
887            #[display_backward(false)]
888            impl fmt_traits for Wrapper {
889                const LENGTH: usize = 4;
890            }
891        }
892
893        #[test]
894        fn hex_fmt_impl_macro_forward() {
895            struct Wrapper([u8; 4]);
896
897            impl Borrow<[u8]> for Wrapper {
898                fn borrow(&self) -> &[u8] { &self.0[..] }
899            }
900
901            impl_fmt_traits! {
902                #[display_backward(false)]
903                impl fmt_traits for Wrapper {
904                    const LENGTH: usize = 4;
905                }
906            }
907
908            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
909
910            let want = "12345678";
911            let got = format!("{}", tc);
912            assert_eq!(got, want);
913        }
914
915        #[test]
916        fn hex_fmt_impl_macro_backwards() {
917            struct Wrapper([u8; 4]);
918
919            impl Borrow<[u8]> for Wrapper {
920                fn borrow(&self) -> &[u8] { &self.0[..] }
921            }
922
923            impl_fmt_traits! {
924                #[display_backward(true)]
925                impl fmt_traits for Wrapper {
926                    const LENGTH: usize = 4;
927                }
928            }
929
930            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
931
932            let want = "78563412";
933            let got = format!("{}", tc);
934            assert_eq!(got, want);
935        }
936
937        #[test]
938        fn hex_fmt_impl_macro_gen_forward() {
939            struct Wrapper<T>([u8; 4], PhantomData<T>);
940
941            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
942                fn borrow(&self) -> &[u8] { &self.0[..] }
943            }
944
945            impl_fmt_traits! {
946                #[display_backward(false)]
947                impl<T: Clone> fmt_traits for Wrapper<T> {
948                    const LENGTH: usize = 4;
949                }
950            }
951
952            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
953            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
954
955            let want = "12345678";
956            let got = format!("{}", tc);
957            assert_eq!(got, want);
958        }
959
960        #[test]
961        fn hex_fmt_impl_macro_gen_backwards() {
962            struct Wrapper<T>([u8; 4], PhantomData<T>);
963
964            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
965                fn borrow(&self) -> &[u8] { &self.0[..] }
966            }
967
968            impl_fmt_traits! {
969                #[display_backward(true)]
970                impl<T: Clone> fmt_traits for Wrapper<T> {
971                    const LENGTH: usize = 4;
972                }
973            }
974
975            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
976            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
977
978            let want = "78563412";
979            let got = format!("{}", tc);
980            assert_eq!(got, want);
981        }
982
983        #[test]
984        fn hex_display_case() {
985            let bytes = [0xaa, 0xbb, 0xcc, 0xdd];
986            let upper = "AABBCCDD";
987            let lower = "aabbccdd";
988            assert_eq!(bytes.to_upper_hex_string(), upper);
989            assert_eq!(bytes.to_lower_hex_string(), lower);
990        }
991
992        #[test]
993        fn upper_hex_precision_preserves_case() {
994            let bytes: [u8; 4] = [0xab, 0xcd, 0xef, 0x12];
995            let slice: &[u8] = &bytes;
996            assert_eq!(format!("{:.4X}", slice.as_hex()), "ABCD");
997            assert_eq!(format!("{:.5X}", slice.as_hex()), "ABCDE");
998        }
999
1000        #[test]
1001        fn lower_hex_precision_works_correctly_with_lowecase() {
1002            let bytes: [u8; 4] = [0xab, 0xcd, 0xef, 0x12];
1003            let slice: &[u8] = &bytes;
1004            assert_eq!(format!("{:.4x}", slice.as_hex()), "abcd");
1005            assert_eq!(format!("{:.5x}", slice.as_hex()), "abcde");
1006        }
1007
1008        #[test]
1009        fn hex_precision_extreme_boundaries() {
1010            let bytes: [u8; 2] = [0xaa, 0xbb];
1011            let slice: &[u8] = &bytes;
1012
1013            // zero precision
1014            assert_eq!(format!("{:.0X}", slice.as_hex()), "");
1015            assert_eq!(format!("{:.0x}", slice.as_hex()), "");
1016
1017            // 1-nibble precision (odd, edge case)
1018            assert_eq!(format!("{:.1X}", slice.as_hex()), "A");
1019            assert_eq!(format!("{:.1x}", slice.as_hex()), "a");
1020        }
1021
1022        #[test]
1023        fn hex_precision_greater_than_length() {
1024            let bytes: [u8; 2] = [0xab, 0xcd];
1025            let slice: &[u8] = &bytes;
1026
1027            assert_eq!(format!("{:.10X}", slice.as_hex()), "ABCD");
1028            assert_eq!(format!("{:.10x}", slice.as_hex()), "abcd");
1029        }
1030    }
1031
1032    #[cfg(feature = "std")]
1033    mod std {
1034        use alloc::string::String;
1035        use alloc::vec::Vec;
1036        use std::io::Write as _;
1037
1038        use arrayvec::ArrayString;
1039
1040        use super::{Case, DisplayHex, HexWriter};
1041
1042        #[test]
1043        fn hex_writer() {
1044            use std::io::{ErrorKind, Result, Write};
1045
1046            use super::Case::{Lower, Upper};
1047
1048            macro_rules! test_hex_writer {
1049                ($cap:expr, $case: expr, $src: expr, $want: expr, $hex_result: expr) => {
1050                    let dest_buf = ArrayString::<$cap>::new();
1051                    let mut dest = HexWriter::new(dest_buf, $case);
1052                    let got = dest.write($src);
1053                    match $want {
1054                        Ok(n) => assert_eq!(got.unwrap(), n),
1055                        Err(e) => assert_eq!(got.unwrap_err().kind(), e.kind()),
1056                    }
1057                    assert_eq!(dest.into_inner().as_str(), $hex_result);
1058                };
1059            }
1060
1061            test_hex_writer!(0, Lower, &[], Result::Ok(0), "");
1062            test_hex_writer!(
1063                0,
1064                Lower,
1065                &[0xab, 0xcd],
1066                Result::<usize>::Err(ErrorKind::Other.into()),
1067                ""
1068            );
1069            test_hex_writer!(
1070                1,
1071                Lower,
1072                &[0xab, 0xcd],
1073                Result::<usize>::Err(ErrorKind::Other.into()),
1074                ""
1075            );
1076            test_hex_writer!(2, Lower, &[0xab, 0xcd], Result::Ok(1), "ab");
1077            test_hex_writer!(3, Lower, &[0xab, 0xcd], Result::Ok(1), "ab");
1078            test_hex_writer!(4, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd");
1079            test_hex_writer!(8, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd");
1080            test_hex_writer!(8, Upper, &[0xab, 0xcd], Result::Ok(2), "ABCD");
1081
1082            let vec: Vec<_> = (0u8..32).collect();
1083            let mut writer = HexWriter::new(String::new(), Lower);
1084            writer.write_all(&vec[..]).unwrap();
1085            assert_eq!(writer.into_inner(), vec.to_lower_hex_string());
1086        }
1087
1088        #[test]
1089        fn hex_writer_accepts_and_mut() {
1090            let mut dest_buf = ArrayString::<64>::new();
1091            let mut dest = HexWriter::new(&mut dest_buf, Case::Lower);
1092            let _got = dest.write(b"some data").unwrap();
1093        }
1094    }
1095}