const_num_traits/ops/byte_slice.rs
1//! Parsing an unsigned integer from a byte slice of *arbitrary* length.
2//!
3//! The input `&[u8]` need not match the target's byte width: shorter input
4//! zero-extends, equal is exact, wider is rejected rather than truncated. That
5//! reject case is why the parse returns a `Result` — an over-long slice has no
6//! lossless reading.
7//!
8//! Unsigned-only: zero-extension is unambiguous for magnitudes but has no
9//! sign-extension analogue, so signed targets are deliberately excluded.
10//!
11//! **CT tier A (structure-only)** for a fixed-length input: control flow
12//! branches only on `bytes.len()` (emptiness, over-width), never on byte
13//! *values*, so a constant-length caller is constant-time.
14
15use core::fmt;
16
17/// The reason a [`FromByteSlice`] parse failed.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ByteSliceErrorKind {
20 /// The input slice was empty. An empty slice is rejected rather than read
21 /// as `0`, so a truncated or missing buffer can't masquerade as a valid
22 /// zero value.
23 Empty,
24 /// The input had more bytes than the target type can hold. The value is
25 /// never silently truncated.
26 Overflow,
27}
28
29/// Error parsing an integer from a byte slice.
30///
31/// Crate-owned so it stays constructible on stable — the standard library's
32/// integer-parse error is opaque and can't be built outside `core`.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct ByteSliceError {
35 /// What went wrong.
36 pub kind: ByteSliceErrorKind,
37}
38
39impl fmt::Display for ByteSliceError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 let description = match self.kind {
42 ByteSliceErrorKind::Empty => "cannot parse integer from empty byte slice",
43 ByteSliceErrorKind::Overflow => "byte slice too long for target type",
44 };
45 description.fmt(f)
46 }
47}
48
49c0nst::c0nst! {
50/// Parses an unsigned integer from a byte slice of arbitrary length.
51///
52/// Input shorter than the target width is zero-extended (leading zeros for
53/// big-endian, trailing for little-endian); input wider than the target is
54/// rejected with [`ByteSliceErrorKind::Overflow`] — never truncated. An empty
55/// slice is [`ByteSliceErrorKind::Empty`], not `0`.
56pub c0nst trait FromByteSlice: Sized {
57 /// Parses from a big-endian byte slice.
58 ///
59 /// ```
60 /// use const_num_traits::FromByteSlice;
61 ///
62 /// assert_eq!(<u32 as FromByteSlice>::from_be_slice(&[0x12, 0x34]), Ok(0x1234));
63 /// assert_eq!(<u32 as FromByteSlice>::from_be_slice(&[1, 2, 3, 4]), Ok(0x0102_0304));
64 /// assert!(<u16 as FromByteSlice>::from_be_slice(&[1, 2, 3]).is_err()); // too wide
65 /// assert!(<u32 as FromByteSlice>::from_be_slice(&[]).is_err()); // empty
66 /// ```
67 fn from_be_slice(bytes: &[u8]) -> Result<Self, ByteSliceError>;
68
69 /// Parses from a little-endian byte slice.
70 ///
71 /// ```
72 /// use const_num_traits::FromByteSlice;
73 ///
74 /// assert_eq!(<u32 as FromByteSlice>::from_le_slice(&[0x34, 0x12]), Ok(0x1234));
75 /// assert_eq!(<u32 as FromByteSlice>::from_le_slice(&[1, 2, 3, 4]), Ok(0x0403_0201));
76 /// ```
77 fn from_le_slice(bytes: &[u8]) -> Result<Self, ByteSliceError>;
78}
79}
80
81macro_rules! from_byte_slice_impl {
82 ($($t:ty)*) => {$(
83 c0nst::c0nst! {
84 c0nst impl FromByteSlice for $t {
85 fn from_be_slice(bytes: &[u8]) -> Result<Self, ByteSliceError> {
86 if bytes.len() == 0 {
87 return Err(ByteSliceError { kind: ByteSliceErrorKind::Empty });
88 }
89 let width = core::mem::size_of::<$t>();
90 if bytes.len() > width {
91 return Err(ByteSliceError { kind: ByteSliceErrorKind::Overflow });
92 }
93 // right-align into a zero buffer, then reinterpret — avoids the
94 // `<< 8` shift that would overflow a single-byte accumulator.
95 let mut buf = [0u8; core::mem::size_of::<$t>()];
96 let off = width - bytes.len();
97 let mut i = 0;
98 while i < bytes.len() {
99 buf[off + i] = bytes[i];
100 i += 1;
101 }
102 Ok(<$t>::from_be_bytes(buf))
103 }
104
105 fn from_le_slice(bytes: &[u8]) -> Result<Self, ByteSliceError> {
106 if bytes.len() == 0 {
107 return Err(ByteSliceError { kind: ByteSliceErrorKind::Empty });
108 }
109 let width = core::mem::size_of::<$t>();
110 if bytes.len() > width {
111 return Err(ByteSliceError { kind: ByteSliceErrorKind::Overflow });
112 }
113 let mut buf = [0u8; core::mem::size_of::<$t>()];
114 let mut i = 0;
115 while i < bytes.len() {
116 buf[i] = bytes[i];
117 i += 1;
118 }
119 Ok(<$t>::from_le_bytes(buf))
120 }
121 }
122 }
123 )*};
124}
125
126from_byte_slice_impl!(usize u8 u16 u32 u64 u128);
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn exact_width() {
134 assert_eq!(
135 <u32 as FromByteSlice>::from_be_slice(&[1, 2, 3, 4]),
136 Ok(0x0102_0304)
137 );
138 assert_eq!(
139 <u32 as FromByteSlice>::from_le_slice(&[1, 2, 3, 4]),
140 Ok(0x0403_0201)
141 );
142 assert_eq!(<u8 as FromByteSlice>::from_be_slice(&[0xff]), Ok(0xff));
143 assert_eq!(<u8 as FromByteSlice>::from_le_slice(&[0xff]), Ok(0xff));
144 }
145
146 #[test]
147 fn zero_extends_short_input() {
148 // BE: fewer bytes are the least-significant end
149 assert_eq!(
150 <u32 as FromByteSlice>::from_be_slice(&[0x12, 0x34]),
151 Ok(0x0000_1234)
152 );
153 // LE: fewer bytes are the least-significant end too
154 assert_eq!(
155 <u32 as FromByteSlice>::from_le_slice(&[0x34, 0x12]),
156 Ok(0x0000_1234)
157 );
158 assert_eq!(<u128 as FromByteSlice>::from_be_slice(&[1]), Ok(1));
159 }
160
161 #[test]
162 fn round_trips_be_le() {
163 let v = 0x0102_0304u32;
164 assert_eq!(
165 <u32 as FromByteSlice>::from_be_slice(&v.to_be_bytes()),
166 Ok(v)
167 );
168 assert_eq!(
169 <u32 as FromByteSlice>::from_le_slice(&v.to_le_bytes()),
170 Ok(v)
171 );
172 }
173
174 #[test]
175 fn empty_is_error_not_zero() {
176 use ByteSliceErrorKind::*;
177 assert_eq!(
178 <u32 as FromByteSlice>::from_be_slice(&[]).unwrap_err().kind,
179 Empty
180 );
181 assert_eq!(
182 <u32 as FromByteSlice>::from_le_slice(&[]).unwrap_err().kind,
183 Empty
184 );
185 }
186
187 #[test]
188 fn too_wide_is_overflow() {
189 use ByteSliceErrorKind::*;
190 assert_eq!(
191 <u16 as FromByteSlice>::from_be_slice(&[1, 2, 3])
192 .unwrap_err()
193 .kind,
194 Overflow
195 );
196 // over-width even when the surplus high bytes are zero: length-based.
197 assert_eq!(
198 <u16 as FromByteSlice>::from_be_slice(&[0, 1, 2])
199 .unwrap_err()
200 .kind,
201 Overflow
202 );
203 assert_eq!(
204 <u8 as FromByteSlice>::from_le_slice(&[1, 2])
205 .unwrap_err()
206 .kind,
207 Overflow
208 );
209 }
210}