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            write!(f, "{}", bytes[..(max / 2)].as_hex())?;
127            if max % 2 == 1 {
128                f.write_char(case.table().byte_to_chars(bytes[max / 2])[0])?;
129            }
130        }
131        Some(_) | None => {
132            let mut chunks = bytes.chunks_exact(512);
133            for chunk in &mut chunks {
134                encoder.put_bytes(chunk);
135                f.write_str(encoder.as_str())?;
136                encoder.clear();
137            }
138            encoder.put_bytes(chunks.remainder());
139            f.write_str(encoder.as_str())?;
140        }
141    }
142
143    write_pad_right(f, pad_right, &mut encoder)
144}
145
146fn write_pad_left(
147    f: &mut fmt::Formatter,
148    bytes_len: usize,
149    encoder: &mut BufEncoder<1024>,
150) -> Result<usize, fmt::Error> {
151    let pad_right = if let Some(width) = f.width() {
152        // Add space for 2 characters if the '#' flag is set
153        let full_string_len = if f.alternate() { bytes_len * 2 + 2 } else { bytes_len * 2 };
154        let string_len = match f.precision() {
155            Some(max) => core::cmp::min(max, full_string_len),
156            None => full_string_len,
157        };
158
159        if string_len < width {
160            let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) {
161                fmt::Alignment::Left => (0, width - string_len),
162                fmt::Alignment::Right => (width - string_len, 0),
163                fmt::Alignment::Center =>
164                    ((width - string_len) / 2, (width - string_len).div_ceil(2)),
165            };
166            // Avoid division by zero and optimize for common case.
167            if left > 0 {
168                let c = f.fill();
169                let chunk_len = encoder.put_filler(c, left);
170                let padding = encoder.as_str();
171                for _ in 0..(left / chunk_len) {
172                    f.write_str(padding)?;
173                }
174                f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?;
175                encoder.clear();
176            }
177            right
178        } else {
179            0
180        }
181    } else {
182        0
183    };
184    Ok(pad_right)
185}
186
187fn write_pad_right(
188    f: &mut fmt::Formatter,
189    pad_right: usize,
190    encoder: &mut BufEncoder<1024>,
191) -> fmt::Result {
192    // Avoid division by zero and optimize for common case.
193    if pad_right > 0 {
194        encoder.clear();
195        let c = f.fill();
196        let chunk_len = encoder.put_filler(c, pad_right);
197        let padding = encoder.as_str();
198        for _ in 0..(pad_right / chunk_len) {
199            f.write_str(padding)?;
200        }
201        f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?;
202    }
203    Ok(())
204}
205
206impl DisplayHex for [u8] {
207    type Display<'a> = DisplayByteSlice<'a>;
208
209    #[inline]
210    fn as_hex<'a>(&'a self) -> Self::Display<'a> { DisplayByteSlice { bytes: self } }
211
212    #[inline]
213    fn hex_reserve_suggestion(&self) -> usize {
214        // Since the string wouldn't fit into address space if this overflows (actually even for
215        // smaller amounts) it's better to panic right away. It should also give the optimizer
216        // better opportunities.
217        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
218    }
219}
220
221/// Displays byte slice as hex.
222///
223/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
224#[derive(Clone, PartialEq, Eq, Hash)]
225pub struct DisplayByteSlice<'a> {
226    // pub because we want to keep lengths in sync
227    pub(crate) bytes: &'a [u8],
228}
229
230impl DisplayByteSlice<'_> {
231    #[inline]
232    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
233        internal_display(self.bytes, f, case)
234    }
235}
236
237impl fmt::Display for DisplayByteSlice<'_> {
238    #[inline]
239    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
240}
241
242impl fmt::Debug for DisplayByteSlice<'_> {
243    #[inline]
244    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
245}
246
247impl fmt::LowerHex for DisplayByteSlice<'_> {
248    #[inline]
249    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
250}
251
252impl fmt::UpperHex for DisplayByteSlice<'_> {
253    #[inline]
254    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
255}
256
257/// Format known-length array as hex.
258///
259/// This supports all formatting options of formatter and may be faster than calling `as_hex()` on
260/// an arbitrary `&[u8]`. Note that the implementation intentionally keeps leading zeros even when
261/// not requested. This is designed to display values such as hashes and keys and removing leading
262/// zeros would be confusing.
263///
264/// Note that the bytes parameter is `IntoIterator` this means that if you would like to do some
265/// manipulation to the byte array before formatting then you can. For example `bytes.iter().rev()`
266/// to print the array backwards.
267///
268/// ## Parameters
269///
270/// * `$formatter` - a [`fmt::Formatter`].
271/// * `$len` known length of `$bytes`, must be a const expression.
272/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
273/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
274///
275/// ## Panics
276///
277/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len`
278/// is more than half of `usize::MAX`.
279#[macro_export]
280macro_rules! fmt_hex_exact {
281    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
282        // statically check $len
283        #[allow(deprecated)]
284        const _: () = [()][($len > usize::MAX / 2) as usize];
285        assert_eq!($bytes.len(), $len);
286        $crate::display::fmt_hex_exact_fn::<_, { $len * 2 }>($formatter, $bytes, $case)
287    }};
288}
289pub use fmt_hex_exact;
290
291/// Adds `core::fmt` trait implementations to type `$ty`.
292///
293/// Implements:
294///
295/// - `fmt::{LowerHex, UpperHex}` using [`fmt_hex_exact`].
296/// - `fmt::{Display, Debug}` by calling `LowerHex`.
297///
298/// Requires:
299///
300/// - `$ty` must implement `IntoIterator<Item=Borrow<u8>>`.
301///
302/// ## Parameters
303///
304/// * `$ty` - the type to implement traits on.
305/// * `$len` - known length of `$bytes`, must be a const expression.
306/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
307/// * `$reverse` - true if you want the array to be displayed backwards.
308/// * `$gen: $gent` - optional generic type(s) and trait bound(s) to put on `$ty` e.g, `F: Foo`.
309///
310/// ## Examples
311///
312/// ```
313/// # use core::borrow::Borrow;
314/// # use hex_conservative::impl_fmt_traits;
315/// struct Wrapper([u8; 4]);
316///
317/// impl Borrow<[u8]> for Wrapper {
318///     fn borrow(&self) -> &[u8] { &self.0[..] }
319/// }
320///
321/// impl_fmt_traits! {
322///     impl fmt_traits for Wrapper {
323///         const LENGTH: usize = 4;
324///     }
325/// }
326///
327/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
328/// assert_eq!(format!("{}", w), "12345678");
329/// ```
330///
331/// We support generics on `$ty`:
332///
333/// ```
334/// # use core::borrow::Borrow;
335/// # use core::marker::PhantomData;
336/// # use hex_conservative::impl_fmt_traits;
337/// struct Wrapper<T>([u8; 4], PhantomData<T>);
338///
339/// // `Clone` is just some arbitrary trait.
340/// impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
341///     fn borrow(&self) -> &[u8] { &self.0[..] }
342/// }
343///
344/// impl_fmt_traits! {
345///     impl<T: Clone> fmt_traits for Wrapper<T> {
346///         const LENGTH: usize = 4;
347///     }
348/// }
349///
350/// let w = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
351/// assert_eq!(format!("{}", w), "12345678");
352/// ```
353///
354/// And also, as is required by `rust-bitcoin`, we support displaying
355/// the hex string byte-wise backwards:
356///
357/// ```
358/// # use core::borrow::Borrow;
359/// # use hex_conservative::impl_fmt_traits;
360/// struct Wrapper([u8; 4]);
361///
362/// impl Borrow<[u8]> for Wrapper {
363///     fn borrow(&self) -> &[u8] { &self.0[..] }
364/// }
365///
366/// impl_fmt_traits! {
367///     #[display_backward(true)]
368///     impl fmt_traits for Wrapper {
369///         const LENGTH: usize = 4;
370///     }
371/// }
372/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
373/// assert_eq!(format!("{}", w), "78563412");
374/// ```
375#[macro_export]
376macro_rules! impl_fmt_traits {
377    // Without generic and trait bounds and without display_backward attribute.
378    (impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
379        $crate::impl_fmt_traits! {
380            #[display_backward(false)]
381            impl<> fmt_traits for $ty<> {
382                const LENGTH: usize = $len;
383            }
384        }
385    };
386    // Without generic and trait bounds and with display_backward attribute.
387    (#[display_backward($reverse:expr)] impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
388        $crate::impl_fmt_traits! {
389            #[display_backward($reverse)]
390            impl<> fmt_traits for $ty<> {
391                const LENGTH: usize = $len;
392            }
393        }
394    };
395    // With generic and trait bounds and without display_backward attribute.
396    (impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
397        $crate::impl_fmt_traits! {
398            #[display_backward(false)]
399            impl<$($gen: $gent),*> fmt_traits for $ty<$($unused),*> {
400                const LENGTH: usize = $len;
401            }
402        }
403    };
404    // With generic and trait bounds and display_backward attribute.
405    (#[display_backward($reverse:expr)] impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
406        impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
407            #[inline]
408            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
409                let case = $crate::Case::Lower;
410
411                if $reverse {
412                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
413                    $crate::fmt_hex_exact!(f, $len, bytes, case)
414                } else {
415                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
416                    $crate::fmt_hex_exact!(f, $len, bytes, case)
417                }
418            }
419        }
420
421        impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
422            #[inline]
423            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
424                let case = $crate::Case::Upper;
425
426                if $reverse {
427                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
428                    $crate::fmt_hex_exact!(f, $len, bytes, case)
429                } else {
430                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
431                    $crate::fmt_hex_exact!(f, $len, bytes, case)
432                }
433            }
434        }
435
436        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
437            #[inline]
438            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
439                $crate::_export::_core::fmt::LowerHex::fmt(self, f)
440            }
441        }
442
443        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> {
444            #[inline]
445            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
446                $crate::_export::_core::fmt::LowerHex::fmt(&self, f)
447            }
448        }
449    };
450}
451pub use impl_fmt_traits;
452
453// Implementation detail of `write_hex_exact` macro to de-duplicate the code
454//
455// Whether hex is an integer or a string is debatable, we cater a little bit to each.
456// - We support users adding `0x` prefix using "{:#}" (treating hex like an integer).
457// - We support limiting the output using precision "{:.10}" (treating hex like a string).
458//
459// This assumes `bytes.len() * 2 == N`.
460#[doc(hidden)]
461#[inline]
462pub fn fmt_hex_exact_fn<I, const N: usize>(
463    f: &mut fmt::Formatter,
464    bytes: I,
465    case: Case,
466) -> fmt::Result
467where
468    I: IntoIterator,
469    I::Item: Borrow<u8>,
470{
471    let mut padding_encoder = BufEncoder::<1024>::new(case);
472    let pad_right = write_pad_left(f, N / 2, &mut padding_encoder)?;
473
474    if f.alternate() {
475        f.write_str("0x")?;
476    }
477    let mut encoder = BufEncoder::<N>::new(case);
478    let encoded = match f.precision() {
479        Some(p) if p < N => {
480            let n = p.div_ceil(2);
481            encoder.put_bytes(bytes.into_iter().take(n));
482            &encoder.as_str()[..p]
483        }
484        _ => {
485            encoder.put_bytes(bytes);
486            encoder.as_str()
487        }
488    };
489    f.write_str(encoded)?;
490
491    write_pad_right(f, pad_right, &mut padding_encoder)
492}
493
494/// Given a `T:` [`fmt::Write`], `HexWriter` implements [`std::io::Write`]
495/// and writes the source bytes to its inner `T` as hex characters.
496#[cfg(feature = "std")]
497#[derive(Debug, Clone, PartialEq, Eq, Hash)]
498pub struct HexWriter<T> {
499    writer: T,
500    table: &'static Table,
501}
502
503#[cfg(feature = "std")]
504impl<T> HexWriter<T> {
505    /// Creates a `HexWriter` that writes the source bytes to `dest` as hex characters
506    /// in the given `case`.
507    ///
508    /// Note even though we take ownership of the writer one can also call this with `&mut dest`.
509    pub fn new(dest: T, case: Case) -> Self { Self { writer: dest, table: case.table() } }
510    /// Consumes this `HexWriter` returning the inner `T`.
511    pub fn into_inner(self) -> T { self.writer }
512}
513
514#[cfg(feature = "std")]
515impl<T> std::io::Write for HexWriter<T>
516where
517    T: core::fmt::Write,
518{
519    /// Writes `buf` into [`HexWriter`].
520    ///
521    /// # Errors
522    ///
523    /// If no bytes could be written to this `HexWriter`, and the provided buffer is not empty,
524    /// returns [`std::io::ErrorKind::Other`], otherwise returns `Ok`.
525    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
526        let mut n = 0;
527        for byte in buf {
528            let mut hex_chars = [0u8; 2];
529            let hex_str = self.table.byte_to_str(&mut hex_chars, *byte);
530            if self.writer.write_str(hex_str).is_err() {
531                break;
532            }
533            n += 1;
534        }
535        if n == 0 && !buf.is_empty() {
536            Err(std::io::ErrorKind::Other.into())
537        } else {
538            Ok(n)
539        }
540    }
541
542    /// `flush` is a no-op for [`HexWriter`].
543    ///
544    /// # Errors
545    ///
546    /// [`HexWriter`] never errors when flushing.
547    fn flush(&mut self) -> Result<(), std::io::Error> { Ok(()) }
548}
549
550#[cfg(test)]
551mod tests {
552    #[cfg(feature = "alloc")]
553    use super::*;
554
555    #[cfg(feature = "alloc")]
556    mod alloc {
557        use core::marker::PhantomData;
558
559        use super::*;
560        use crate::alloc::vec::Vec;
561
562        fn check_encoding(bytes: &[u8]) {
563            use core::fmt::Write;
564
565            let s1 = bytes.to_lower_hex_string();
566            let mut s2 = String::with_capacity(bytes.len() * 2);
567            for b in bytes {
568                write!(s2, "{:02x}", b).unwrap();
569            }
570            assert_eq!(s1, s2);
571        }
572
573        #[test]
574        fn empty() { check_encoding(b""); }
575
576        #[test]
577        fn single() { check_encoding(b"*"); }
578
579        #[test]
580        fn two() { check_encoding(b"*x"); }
581
582        #[test]
583        fn just_below_boundary() { check_encoding(&[42; 512]); }
584
585        #[test]
586        fn just_above_boundary() { check_encoding(&[42; 513]); }
587
588        #[test]
589        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
590
591        #[test]
592        fn fmt_exact_macro() {
593            use crate::alloc::string::ToString;
594
595            struct Dummy([u8; 32]);
596
597            impl fmt::Display for Dummy {
598                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
599                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
600                }
601            }
602            let dummy = Dummy([42; 32]);
603            assert_eq!(dummy.to_string(), "2a".repeat(32));
604            assert_eq!(format!("{:.10}", dummy), "2a".repeat(5));
605            assert_eq!(format!("{:.11}", dummy), "2a".repeat(5) + "2");
606            assert_eq!(format!("{:.65}", dummy), "2a".repeat(32));
607        }
608
609        macro_rules! define_dummy {
610            ($len:literal) => {
611                struct Dummy([u8; $len]);
612                impl fmt::Debug for Dummy {
613                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
614                        fmt_hex_exact!(f, $len, &self.0, Case::Lower)
615                    }
616                }
617                impl fmt::Display for Dummy {
618                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
619                        fmt_hex_exact!(f, $len, &self.0, Case::Lower)
620                    }
621                }
622            };
623        }
624
625        macro_rules! test_display_hex {
626            ($fs: expr, $a: expr, $check: expr) => {
627                let array = $a;
628                let slice = &$a;
629                let vec = Vec::from($a);
630                let dummy = Dummy($a);
631                assert_eq!(format!($fs, array.as_hex()), $check);
632                assert_eq!(format!($fs, slice.as_hex()), $check);
633                assert_eq!(format!($fs, vec.as_hex()), $check);
634                assert_eq!(format!($fs, dummy), $check);
635            };
636        }
637
638        #[test]
639        fn alternate_flag() {
640            define_dummy!(4);
641
642            test_display_hex!("{:#?}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe");
643            test_display_hex!("{:#}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe");
644        }
645
646        #[test]
647        fn display_short_with_padding() {
648            define_dummy!(2);
649
650            test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef    !");
651            test_display_hex!("Hello {:-<8}!", [0xbe, 0xef], "Hello beef----!");
652            test_display_hex!("Hello {:^8}!", [0xbe, 0xef], "Hello   beef  !");
653            test_display_hex!("Hello {:>8}!", [0xbe, 0xef], "Hello     beef!");
654
655            test_display_hex!("Hello {:<#8}!", [0xbe, 0xef], "Hello 0xbeef  !");
656            test_display_hex!("Hello {:-<#8}!", [0xbe, 0xef], "Hello 0xbeef--!");
657            test_display_hex!("Hello {:^#8}!", [0xbe, 0xef], "Hello  0xbeef !");
658            test_display_hex!("Hello {:>#8}!", [0xbe, 0xef], "Hello   0xbeef!");
659        }
660
661        #[test]
662        fn display_long() {
663            define_dummy!(512);
664            // Note this string is shorter than the one above.
665            let a = [0xab; 512];
666
667            let mut want = "0".repeat(2000 - 1024);
668            want.extend(core::iter::repeat("ab").take(512));
669            test_display_hex!("{:0>2000}", a, want);
670
671            let mut want = "0".repeat(2000 - 1026);
672            want.push_str("0x");
673            want.extend(core::iter::repeat("ab").take(512));
674            test_display_hex!("{:0>#2000}", a, want);
675        }
676
677        // Precision and padding act the same as for strings in the stdlib (because we use `Formatter::pad`).
678
679        #[test]
680        fn precision_truncates() {
681            // Precision gets the most significant bytes.
682            // Remember the integer is number of hex chars not number of bytes.
683            define_dummy!(4);
684
685            test_display_hex!("{0:.4}", [0x12, 0x34, 0x56, 0x78], "1234");
686            test_display_hex!("{0:.5}", [0x12, 0x34, 0x56, 0x78], "12345");
687
688            test_display_hex!("{0:#.4}", [0x12, 0x34, 0x56, 0x78], "0x1234");
689            test_display_hex!("{0:#.5}", [0x12, 0x34, 0x56, 0x78], "0x12345");
690        }
691
692        #[test]
693        fn precision_with_padding_truncates() {
694            // Precision gets the most significant bytes.
695            define_dummy!(4);
696
697            test_display_hex!("{0:10.4}", [0x12, 0x34, 0x56, 0x78], "1234      ");
698            test_display_hex!("{0:10.5}", [0x12, 0x34, 0x56, 0x78], "12345     ");
699
700            test_display_hex!("{0:#10.4}", [0x12, 0x34, 0x56, 0x78], "0x1234      ");
701            test_display_hex!("{0:#10.5}", [0x12, 0x34, 0x56, 0x78], "0x12345     ");
702        }
703
704        #[test]
705        fn precision_with_padding_pads_right() {
706            define_dummy!(4);
707
708            test_display_hex!("{0:10.20}", [0x12, 0x34, 0x56, 0x78], "12345678  ");
709            test_display_hex!("{0:10.14}", [0x12, 0x34, 0x56, 0x78], "12345678  ");
710
711            test_display_hex!("{0:#12.20}", [0x12, 0x34, 0x56, 0x78], "0x12345678  ");
712            test_display_hex!("{0:#12.14}", [0x12, 0x34, 0x56, 0x78], "0x12345678  ");
713        }
714
715        #[test]
716        fn precision_with_padding_pads_left() {
717            define_dummy!(4);
718
719            test_display_hex!("{0:>10.20}", [0x12, 0x34, 0x56, 0x78], "  12345678");
720
721            test_display_hex!("{0:>#12.20}", [0x12, 0x34, 0x56, 0x78], "  0x12345678");
722        }
723
724        #[test]
725        fn precision_with_padding_pads_center() {
726            define_dummy!(4);
727
728            test_display_hex!("{0:^10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 ");
729
730            test_display_hex!("{0:^#12.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678 ");
731        }
732
733        #[test]
734        fn precision_with_padding_pads_center_odd() {
735            define_dummy!(4);
736
737            test_display_hex!("{0:^11.20}", [0x12, 0x34, 0x56, 0x78], " 12345678  ");
738
739            test_display_hex!("{0:^#13.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678  ");
740        }
741
742        #[test]
743        fn precision_does_not_extend() {
744            define_dummy!(4);
745
746            test_display_hex!("{0:.16}", [0x12, 0x34, 0x56, 0x78], "12345678");
747
748            test_display_hex!("{0:#.16}", [0x12, 0x34, 0x56, 0x78], "0x12345678");
749        }
750
751        #[test]
752        fn padding_extends() {
753            define_dummy!(2);
754
755            test_display_hex!("{:0>8}", [0xab; 2], "0000abab");
756
757            test_display_hex!("{:0>#8}", [0xab; 2], "000xabab");
758        }
759
760        #[test]
761        fn padding_does_not_truncate() {
762            define_dummy!(4);
763
764            test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678");
765            test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678");
766
767            test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678");
768            test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678");
769        }
770
771        // Tests `impl_fmt_traits` in module scope.
772        // ref: https://rust-lang.github.io/api-guidelines/macros.html#c-anywhere
773        #[allow(dead_code)]
774        struct Wrapper([u8; 4]);
775
776        impl Borrow<[u8]> for Wrapper {
777            fn borrow(&self) -> &[u8] { &self.0[..] }
778        }
779
780        impl_fmt_traits! {
781            #[display_backward(false)]
782            impl fmt_traits for Wrapper {
783                const LENGTH: usize = 4;
784            }
785        }
786
787        #[test]
788        fn hex_fmt_impl_macro_forward() {
789            struct Wrapper([u8; 4]);
790
791            impl Borrow<[u8]> for Wrapper {
792                fn borrow(&self) -> &[u8] { &self.0[..] }
793            }
794
795            impl_fmt_traits! {
796                #[display_backward(false)]
797                impl fmt_traits for Wrapper {
798                    const LENGTH: usize = 4;
799                }
800            }
801
802            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
803
804            let want = "12345678";
805            let got = format!("{}", tc);
806            assert_eq!(got, want);
807        }
808
809        #[test]
810        fn hex_fmt_impl_macro_backwards() {
811            struct Wrapper([u8; 4]);
812
813            impl Borrow<[u8]> for Wrapper {
814                fn borrow(&self) -> &[u8] { &self.0[..] }
815            }
816
817            impl_fmt_traits! {
818                #[display_backward(true)]
819                impl fmt_traits for Wrapper {
820                    const LENGTH: usize = 4;
821                }
822            }
823
824            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
825
826            let want = "78563412";
827            let got = format!("{}", tc);
828            assert_eq!(got, want);
829        }
830
831        #[test]
832        fn hex_fmt_impl_macro_gen_forward() {
833            struct Wrapper<T>([u8; 4], PhantomData<T>);
834
835            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
836                fn borrow(&self) -> &[u8] { &self.0[..] }
837            }
838
839            impl_fmt_traits! {
840                #[display_backward(false)]
841                impl<T: Clone> fmt_traits for Wrapper<T> {
842                    const LENGTH: usize = 4;
843                }
844            }
845
846            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
847            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
848
849            let want = "12345678";
850            let got = format!("{}", tc);
851            assert_eq!(got, want);
852        }
853
854        #[test]
855        fn hex_fmt_impl_macro_gen_backwards() {
856            struct Wrapper<T>([u8; 4], PhantomData<T>);
857
858            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
859                fn borrow(&self) -> &[u8] { &self.0[..] }
860            }
861
862            impl_fmt_traits! {
863                #[display_backward(true)]
864                impl<T: Clone> fmt_traits for Wrapper<T> {
865                    const LENGTH: usize = 4;
866                }
867            }
868
869            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
870            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
871
872            let want = "78563412";
873            let got = format!("{}", tc);
874            assert_eq!(got, want);
875        }
876
877        #[test]
878        fn hex_display_case() {
879            let bytes = [0xaa, 0xbb, 0xcc, 0xdd];
880            let upper = "AABBCCDD";
881            let lower = "aabbccdd";
882            assert_eq!(bytes.to_upper_hex_string(), upper);
883            assert_eq!(bytes.to_lower_hex_string(), lower);
884        }
885    }
886
887    #[cfg(feature = "std")]
888    mod std {
889        use alloc::string::String;
890        use alloc::vec::Vec;
891        use std::io::Write as _;
892
893        use arrayvec::ArrayString;
894
895        use super::{Case, DisplayHex, HexWriter};
896
897        #[test]
898        fn hex_writer() {
899            use std::io::{ErrorKind, Result, Write};
900
901            use super::Case::{Lower, Upper};
902
903            macro_rules! test_hex_writer {
904                ($cap:expr, $case: expr, $src: expr, $want: expr, $hex_result: expr) => {
905                    let dest_buf = ArrayString::<$cap>::new();
906                    let mut dest = HexWriter::new(dest_buf, $case);
907                    let got = dest.write($src);
908                    match $want {
909                        Ok(n) => assert_eq!(got.unwrap(), n),
910                        Err(e) => assert_eq!(got.unwrap_err().kind(), e.kind()),
911                    }
912                    assert_eq!(dest.into_inner().as_str(), $hex_result);
913                };
914            }
915
916            test_hex_writer!(0, Lower, &[], Result::Ok(0), "");
917            test_hex_writer!(
918                0,
919                Lower,
920                &[0xab, 0xcd],
921                Result::<usize>::Err(ErrorKind::Other.into()),
922                ""
923            );
924            test_hex_writer!(
925                1,
926                Lower,
927                &[0xab, 0xcd],
928                Result::<usize>::Err(ErrorKind::Other.into()),
929                ""
930            );
931            test_hex_writer!(2, Lower, &[0xab, 0xcd], Result::Ok(1), "ab");
932            test_hex_writer!(3, Lower, &[0xab, 0xcd], Result::Ok(1), "ab");
933            test_hex_writer!(4, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd");
934            test_hex_writer!(8, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd");
935            test_hex_writer!(8, Upper, &[0xab, 0xcd], Result::Ok(2), "ABCD");
936
937            let vec: Vec<_> = (0u8..32).collect();
938            let mut writer = HexWriter::new(String::new(), Lower);
939            writer.write_all(&vec[..]).unwrap();
940            assert_eq!(writer.into_inner(), vec.to_lower_hex_string());
941        }
942
943        #[test]
944        fn hex_writer_accepts_and_mut() {
945            let mut dest_buf = ArrayString::<64>::new();
946            let mut dest = HexWriter::new(&mut dest_buf, Case::Lower);
947            let _got = dest.write(b"some data").unwrap();
948        }
949    }
950}