1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Byte serialization for `HeaplessBigInt`.
//!
//! The written byte count is `self.len * word_size` — public shape,
//! derived from the public `len`. Callers own the output buffer; the
//! methods panic if it is too small (checked at runtime, no `Result`).
//!
//! Layout matches `FixedUInt`'s slice-based conventions: BE writes the
//! high-order word's high byte at index 0, LE writes the low-order
//! word's low byte at index 0.
//!
//! Decoding delegates to `FixedUInt`'s `impl_from_{be,le}_bytes_slice`
//! (the byte→limb scatter is identical, and those are the helpers
//! `panic-free-audit` already validates), so `HeaplessBigInt` only owns the
//! `len` computation and the oversize guard.
use super::HeaplessBigInt;
use crate::MachineWord;
use crate::fixeduint::{impl_from_be_bytes_slice, impl_from_le_bytes_slice};
use const_num_traits::{ByteSliceError, ByteSliceErrorKind, FromByteSlice, Personality};
use core::marker::PhantomData;
impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
/// Serialize into `out` as big-endian bytes. Writes `self.len *
/// size_of::<T>()` bytes into `out[..byte_count]` and returns that
/// slice. Panics if `out.len() < byte_count`.
pub fn to_be_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8] {
let word_size = core::mem::size_of::<T>();
let byte_count = self.len as usize * word_size;
assert!(
out.len() >= byte_count,
"HeaplessBigInt::to_be_bytes: out.len() < required ({byte_count} bytes)"
);
// `by_ref().zip` over a flat `iter_mut()` rather than
// `chunks_exact_mut(word_size)`, whose `size()` division by the
// chunk size retains a div-by-zero panic guard at MSRV/`-Oz`.
let mut dst = out[..byte_count].iter_mut();
for word in self.limbs[..self.len as usize].iter().rev() {
let word_bytes = word.to_be_bytes();
for (&src, slot) in word_bytes.as_ref().iter().zip(dst.by_ref()) {
*slot = src;
}
}
&out[..byte_count]
}
/// Serialize into `out` as little-endian bytes. Same size + panic
/// contract as [`to_be_bytes`](Self::to_be_bytes).
pub fn to_le_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8] {
let word_size = core::mem::size_of::<T>();
let byte_count = self.len as usize * word_size;
assert!(
out.len() >= byte_count,
"HeaplessBigInt::to_le_bytes: out.len() < required ({byte_count} bytes)"
);
// See `to_be_bytes` for why this avoids `chunks_exact_mut`.
let mut dst = out[..byte_count].iter_mut();
for word in self.limbs[..self.len as usize].iter() {
let word_bytes = word.to_le_bytes();
for (&src, slot) in word_bytes.as_ref().iter().zip(dst.by_ref()) {
*slot = src;
}
}
&out[..byte_count]
}
/// Deserialize a big-endian byte slice. Output `len =
/// ceil(bytes.len() / word_size)`, capped at `CAP`. A partial top
/// word (input length not a multiple of `word_size`) leaves the
/// missing high bytes zero — matches the BE convention. Panics if
/// `bytes.len() > CAP * word_size`.
pub fn from_be_bytes(bytes: &[u8]) -> Self {
let word_size = core::mem::size_of::<T>();
let max_bytes = CAP * word_size;
assert!(
bytes.len() <= max_bytes,
"HeaplessBigInt::from_be_bytes: input {} bytes > CAP * word_size ({max_bytes})",
bytes.len()
);
let out_len = bytes.len().div_ceil(word_size);
// The oversize case is rejected above, so the helper fills the full
// `[T; CAP]` without truncating.
Self {
limbs: impl_from_be_bytes_slice::<T, CAP>(bytes),
len: out_len as u16,
_p: PhantomData,
}
}
/// Deserialize a little-endian byte slice. Same size contract as
/// [`from_be_bytes`](Self::from_be_bytes).
pub fn from_le_bytes(bytes: &[u8]) -> Self {
let word_size = core::mem::size_of::<T>();
let max_bytes = CAP * word_size;
assert!(
bytes.len() <= max_bytes,
"HeaplessBigInt::from_le_bytes: input {} bytes > CAP * word_size ({max_bytes})",
bytes.len()
);
let out_len = bytes.len().div_ceil(word_size);
Self {
limbs: impl_from_le_bytes_slice::<T, CAP>(bytes),
len: out_len as u16,
_p: PhantomData,
}
}
}
// ── const_num_traits::FromByteSlice ──
//
// Result-returning slice parse: empty → `Empty`, wider than the
// container → `Overflow`, shorter → zero-extended. The inherent
// `from_be_bytes`/`from_le_bytes` already zero-extend; the length
// guard converts their panic-on-oversize into the `Overflow` error.
impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
#[inline]
fn check_slice_len(len: usize) -> Result<(), ByteSliceError> {
if len == 0 {
return Err(ByteSliceError {
kind: ByteSliceErrorKind::Empty,
});
}
if len > CAP * core::mem::size_of::<T>() {
return Err(ByteSliceError {
kind: ByteSliceErrorKind::Overflow,
});
}
Ok(())
}
}
impl<T: MachineWord, const CAP: usize, P: Personality> FromByteSlice for HeaplessBigInt<T, CAP, P> {
fn from_be_slice(bytes: &[u8]) -> Result<Self, ByteSliceError> {
Self::check_slice_len(bytes.len())?;
Ok(Self::from_be_bytes(bytes))
}
fn from_le_slice(bytes: &[u8]) -> Result<Self, ByteSliceError> {
Self::check_slice_len(bytes.len())?;
Ok(Self::from_le_bytes(bytes))
}
}