Skip to main content

byte_wrapper/
hex_slice.rs

1// Copyright (c) The byte-wrapper Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! The [`HexSlice`] newtype wrapper.
5
6use core::fmt::{self, Write};
7
8/// A byte slice that displays as hex.
9///
10/// The borrowed, dynamically-sized equivalent to
11/// [`HexVec`](crate::HexVec), akin to `str`'s relationship to `String`.
12///
13/// `HexSlice` wraps `[u8]`, providing [`Display`](fmt::Display),
14/// [`LowerHex`](fmt::LowerHex), and [`UpperHex`](fmt::UpperHex)
15/// implementations that use hexadecimal encoding.
16///
17/// With the **`serde`** feature enabled, it also implements
18/// `Serialize` (hex strings in human-readable formats, raw bytes in
19/// binary formats).
20///
21/// # Examples
22///
23/// ```
24/// use byte_wrapper::HexSlice;
25///
26/// const BYTES: [u8; 4] = [0x01, 0x02, 0xab, 0xff];
27/// const H: &HexSlice = HexSlice::new(&BYTES);
28/// assert_eq!(H.to_string(), "0102abff");
29/// ```
30#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[repr(transparent)]
32pub struct HexSlice([u8]);
33
34impl HexSlice {
35    /// Creates a new `HexSlice` from a byte slice.
36    #[inline]
37    #[must_use]
38    pub const fn new(bytes: &[u8]) -> &Self {
39        // SAFETY:
40        //
41        // * `repr(transparent)` gives `HexSlice` the layout and validity of
42        //   `[u8]`.
43        // * An unsized-to-unsized pointer cast retains the length metadata and
44        //   provenance.
45        // * The output borrow inherits the input's lifetime and shared-ness,
46        //   and `HexSlice` has no interior mutability.
47        unsafe { &*(bytes as *const [u8] as *const Self) }
48    }
49
50    /// Returns the inner byte slice.
51    #[inline]
52    #[must_use]
53    pub const fn as_bytes(&self) -> &[u8] {
54        &self.0
55    }
56}
57
58/// Formats a byte slice as lower-case hex, ignoring width and fill.
59///
60/// This is used both for serialization (via `Display`) and as the
61/// inner value in the hex types' `Debug` output (via `Debug`).
62pub(crate) struct HexDisplay<'a>(pub(crate) &'a [u8]);
63
64impl fmt::Display for HexDisplay<'_> {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        for byte in self.0 {
67            write!(f, "{byte:02x}")?;
68        }
69        Ok(())
70    }
71}
72
73impl fmt::Debug for HexDisplay<'_> {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        fmt::Display::fmt(self, f)
76    }
77}
78
79/// Writes hex bytes with padding/alignment support.
80///
81/// `write_byte` is called for each byte in the slice to allow
82/// callers to choose between lowercase and uppercase hex.
83fn fmt_hex_padded(
84    bytes: &[u8],
85    f: &mut fmt::Formatter<'_>,
86    write_byte: fn(&mut fmt::Formatter<'_>, u8) -> fmt::Result,
87) -> fmt::Result {
88    let content_len = bytes.len() * 2;
89
90    match f.width() {
91        Some(width) if width > content_len => {
92            let padding = width - content_len;
93            let fill = f.fill();
94            let (pre, post) = match f.align() {
95                Some(fmt::Alignment::Left) => (0, padding),
96                Some(fmt::Alignment::Right) | None => (padding, 0),
97                Some(fmt::Alignment::Center) => {
98                    (padding / 2, padding - padding / 2)
99                }
100            };
101            for _ in 0..pre {
102                f.write_char(fill)?;
103            }
104            for &byte in bytes {
105                write_byte(f, byte)?;
106            }
107            for _ in 0..post {
108                f.write_char(fill)?;
109            }
110            Ok(())
111        }
112        Some(_) | None => {
113            for &byte in bytes {
114                write_byte(f, byte)?;
115            }
116            Ok(())
117        }
118    }
119}
120
121impl fmt::Debug for HexSlice {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.debug_tuple("HexSlice").field(&HexDisplay(&self.0)).finish()
124    }
125}
126
127impl fmt::Display for HexSlice {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        fmt::LowerHex::fmt(self, f)
130    }
131}
132
133impl fmt::LowerHex for HexSlice {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        fmt_hex_padded(&self.0, f, |f, b| write!(f, "{b:02x}"))
136    }
137}
138
139impl fmt::UpperHex for HexSlice {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        fmt_hex_padded(&self.0, f, |f, b| write!(f, "{b:02X}"))
142    }
143}
144
145impl core::ops::Deref for HexSlice {
146    type Target = [u8];
147
148    #[inline]
149    fn deref(&self) -> &Self::Target {
150        &self.0
151    }
152}
153
154impl AsRef<[u8]> for HexSlice {
155    #[inline]
156    fn as_ref(&self) -> &[u8] {
157        &self.0
158    }
159}
160
161impl AsRef<HexSlice> for HexSlice {
162    #[inline]
163    fn as_ref(&self) -> &HexSlice {
164        self
165    }
166}
167
168#[cfg(feature = "serde")]
169mod serde_impls {
170    use super::{HexDisplay, HexSlice};
171
172    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
173    impl serde_core::Serialize for HexSlice {
174        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175        where
176            S: serde_core::Serializer,
177        {
178            if serializer.is_human_readable() {
179                serializer.collect_str(&HexDisplay(&self.0))
180            } else {
181                serializer.serialize_bytes(&self.0)
182            }
183        }
184    }
185}