bitcoin_internals/hex/
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
8use core::borrow::Borrow;
9use core::fmt;
10
11use super::buf_encoder::{BufEncoder, OutBytes};
12use super::Case;
13use crate::hex::buf_encoder::FixedLenBuf;
14#[cfg(feature = "alloc")]
15use crate::prelude::*;
16
17/// Extension trait for types that can be displayed as hex.
18///
19/// Types that have a single, obvious text representation being hex should **not** implement this
20/// trait and simply implement `Display` instead.
21///
22/// This trait should be generally implemented for references only. We would prefer to use GAT but
23/// that is beyond our MSRV. As a lint we require the `IsRef` trait which is implemented for all
24/// references.
25pub trait DisplayHex: Copy + sealed::IsRef {
26    /// The type providing [`fmt::Display`] implementation.
27    ///
28    /// This is usually a wrapper type holding a reference to `Self`.
29    type Display: fmt::LowerHex + fmt::UpperHex;
30
31    /// Display `Self` as a continuous sequence of ASCII hex chars.
32    fn as_hex(self) -> Self::Display;
33
34    /// Create a lower-hex-encoded string.
35    ///
36    /// A shorthand for `to_hex_string(Case::Lower)`, so that `Case` doesn't need to be imported.
37    ///
38    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
39    #[cfg(feature = "alloc")]
40    fn to_lower_hex_string(self) -> String { self.to_hex_string(Case::Lower) }
41
42    /// Create an upper-hex-encoded string.
43    ///
44    /// A shorthand for `to_hex_string(Case::Upper)`, so that `Case` doesn't need to be imported.
45    ///
46    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
47    #[cfg(feature = "alloc")]
48    fn to_upper_hex_string(self) -> String { self.to_hex_string(Case::Upper) }
49
50    /// Create a hex-encoded string.
51    ///
52    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
53    #[cfg(feature = "alloc")]
54    fn to_hex_string(self, case: Case) -> String {
55        let mut string = String::new();
56        self.append_hex_to_string(case, &mut string);
57        string
58    }
59
60    /// Appends hex-encoded content to an existing `String`.
61    ///
62    /// This may be faster than `write!(string, "{:x}", self.display_hex())` because it uses
63    /// `reserve_sugggestion`.
64    #[cfg(feature = "alloc")]
65    fn append_hex_to_string(self, case: Case, string: &mut String) {
66        use fmt::Write;
67
68        string.reserve(self.hex_reserve_suggestion());
69        match case {
70            Case::Lower => write!(string, "{:x}", self.as_hex()),
71            Case::Upper => write!(string, "{:X}", self.as_hex()),
72        }
73        .unwrap_or_else(|_| {
74            let name = core::any::type_name::<Self::Display>();
75            // We don't expect `std` to ever be buggy, so the bug is most likely in the `Display`
76            // impl of `Self::Display`.
77            panic!("The implementation of Display for {} returned an error when it shouldn't", name)
78        })
79    }
80
81    /// Hints how much bytes to reserve when creating a `String`.
82    ///
83    /// Implementors that know the number of produced bytes upfront should override this.
84    /// Defaults to 0.
85    ///
86    // We prefix the name with `hex_` to avoid potential collision with other methods.
87    fn hex_reserve_suggestion(self) -> usize { 0 }
88}
89
90mod sealed {
91    /// Trait marking a shared reference.
92    pub trait IsRef: Copy {}
93
94    impl<T: ?Sized> IsRef for &'_ T {}
95}
96
97impl<'a> DisplayHex for &'a [u8] {
98    type Display = DisplayByteSlice<'a>;
99
100    #[inline]
101    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
102
103    #[inline]
104    fn hex_reserve_suggestion(self) -> usize {
105        // Since the string wouldn't fit into address space if this overflows (actually even for
106        // smaller amounts) it's better to panic right away. It should also give the optimizer
107        // better opportunities.
108        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
109    }
110}
111
112#[cfg(feature = "alloc")]
113impl<'a> DisplayHex for &'a alloc::vec::Vec<u8> {
114    type Display = DisplayByteSlice<'a>;
115
116    #[inline]
117    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
118
119    #[inline]
120    fn hex_reserve_suggestion(self) -> usize {
121        // Since the string wouldn't fit into address space if this overflows (actually even for
122        // smaller amounts) it's better to panic right away. It should also give the optimizer
123        // better opportunities.
124        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
125    }
126}
127
128/// Displays byte slice as hex.
129///
130/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
131pub struct DisplayByteSlice<'a> {
132    // pub because we want to keep lengths in sync
133    pub(crate) bytes: &'a [u8],
134}
135
136impl<'a> DisplayByteSlice<'a> {
137    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
138        let mut buf = [0u8; 1024];
139        let mut encoder = super::BufEncoder::new(&mut buf);
140
141        let mut chunks = self.bytes.chunks_exact(512);
142        for chunk in &mut chunks {
143            encoder.put_bytes(chunk, case);
144            f.write_str(encoder.as_str())?;
145            encoder.clear();
146        }
147        encoder.put_bytes(chunks.remainder(), case);
148        f.write_str(encoder.as_str())
149    }
150}
151
152impl<'a> fmt::LowerHex for DisplayByteSlice<'a> {
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
154}
155
156impl<'a> fmt::UpperHex for DisplayByteSlice<'a> {
157    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
158}
159
160/// Displays byte array as hex.
161///
162/// Created by [`<&[u8; LEN] as DisplayHex>::as_hex`](DisplayHex::as_hex).
163pub struct DisplayArray<A: Clone + IntoIterator, B: FixedLenBuf>
164where
165    A::Item: Borrow<u8>,
166{
167    array: A,
168    _buffer_marker: core::marker::PhantomData<B>,
169}
170
171impl<A: Clone + IntoIterator, B: FixedLenBuf> DisplayArray<A, B>
172where
173    A::Item: Borrow<u8>,
174{
175    /// Creates the wrapper.
176    pub fn new(array: A) -> Self { DisplayArray { array, _buffer_marker: Default::default() } }
177
178    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
179        let mut buf = B::uninit();
180        let mut encoder = super::BufEncoder::new(&mut buf);
181        encoder.put_bytes(self.array.clone(), case);
182        f.pad_integral(true, "0x", encoder.as_str())
183    }
184}
185
186impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::LowerHex for DisplayArray<A, B>
187where
188    A::Item: Borrow<u8>,
189{
190    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
191}
192
193impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::UpperHex for DisplayArray<A, B>
194where
195    A::Item: Borrow<u8>,
196{
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
198}
199
200/// Format known-length array as hex.
201///
202/// This supports all formatting options of formatter and may be faster than calling
203/// `display_as_hex()` on an arbitrary `&[u8]`. Note that the implementation intentionally keeps
204/// leading zeros even when not requested. This is designed to display values such as hashes and
205/// keys and removing leading zeros would be confusing.
206///
207/// ## Parameters
208///
209/// * `$formatter` - a [`fmt::Formatter`].
210/// * `$len` known length of `$bytes`, must be a const expression.
211/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
212/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
213///
214/// ## Panics
215///
216/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len`
217/// is more than half of `usize::MAX`.
218#[macro_export]
219macro_rules! fmt_hex_exact {
220    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
221        // statically check $len
222        #[allow(deprecated)]
223        const _: () = [()][($len > usize::MAX / 2) as usize];
224        assert_eq!($bytes.len(), $len);
225        let mut buf = [0u8; $len * 2];
226        let buf = $crate::hex::buf_encoder::AsOutBytes::as_mut_out_bytes(&mut buf);
227        $crate::hex::display::fmt_hex_exact_fn($formatter, buf, $bytes, $case)
228    }};
229}
230pub use fmt_hex_exact;
231
232// Implementation detail of `write_hex_exact` macro to de-duplicate the code
233#[doc(hidden)]
234#[inline]
235pub fn fmt_hex_exact_fn<I>(
236    f: &mut fmt::Formatter,
237    buf: &mut OutBytes,
238    bytes: I,
239    case: Case,
240) -> fmt::Result
241where
242    I: IntoIterator,
243    I::Item: Borrow<u8>,
244{
245    let mut encoder = BufEncoder::new(buf);
246    encoder.put_bytes(bytes, case);
247    f.pad_integral(true, "0x", encoder.as_str())
248}
249
250#[cfg(test)]
251mod tests {
252    #[cfg(feature = "alloc")]
253    use super::*;
254
255    #[cfg(feature = "alloc")]
256    mod alloc {
257        use super::*;
258
259        fn check_encoding(bytes: &[u8]) {
260            use core::fmt::Write;
261
262            let s1 = bytes.to_lower_hex_string();
263            let mut s2 = String::with_capacity(bytes.len() * 2);
264            for b in bytes {
265                write!(s2, "{:02x}", b).unwrap();
266            }
267            assert_eq!(s1, s2);
268        }
269
270        #[test]
271        fn empty() { check_encoding(b""); }
272
273        #[test]
274        fn single() { check_encoding(b"*"); }
275
276        #[test]
277        fn two() { check_encoding(b"*x"); }
278
279        #[test]
280        fn just_below_boundary() { check_encoding(&[42; 512]); }
281
282        #[test]
283        fn just_above_boundary() { check_encoding(&[42; 513]); }
284
285        #[test]
286        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
287
288        #[test]
289        fn fmt_exact_macro() {
290            use crate::alloc::string::ToString;
291
292            struct Dummy([u8; 32]);
293
294            impl fmt::Display for Dummy {
295                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
297                }
298            }
299
300            assert_eq!(Dummy([42; 32]).to_string(), "2a".repeat(32));
301        }
302    }
303}