bitcoin_private/hex/
display.rs

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