Skip to main content

fixed_bigint/heapless/
bytes.rs

1//! Byte serialization for `HeaplessBigInt`.
2//!
3//! The written byte count is `self.len * word_size` — public shape,
4//! derived from the public `len`. Callers own the output buffer; the
5//! methods panic if it is too small (checked at runtime, no `Result`).
6//!
7//! Layout matches `FixedUInt`'s slice-based conventions: BE writes the
8//! high-order word's high byte at index 0, LE writes the low-order
9//! word's low byte at index 0.
10//!
11//! Per-word reads are bit-shifted rather than `T::from_be_bytes`, so `T`
12//! needs only `MachineWord`, not a byte-conversion trait bound.
13
14use super::{HeaplessBigInt, zero};
15use crate::MachineWord;
16use const_num_traits::{ByteSliceError, ByteSliceErrorKind, FromByteSlice, Personality};
17use core::marker::PhantomData;
18
19// Read MSB-first bytes into a T, zero-padding the high side if
20// `bytes.len() < size_of::<T>()`. Skips the shift on the first iteration
21// because a `T::BITS`-wide shift is UB — matters at `size_of::<T>() == 1`
22// (u8 backing) where the loop runs exactly once.
23#[inline]
24fn read_be_word<T: MachineWord>(bytes: &[u8]) -> T {
25    let mut val = zero::<T>();
26    let mut first = true;
27    for &b in bytes {
28        if !first {
29            val <<= 8;
30        }
31        val |= <T as From<u8>>::from(b);
32        first = false;
33    }
34    val
35}
36
37// LE counterpart: byte `i` of the input contributes at bit position `i*8`.
38#[inline]
39fn read_le_word<T: MachineWord>(bytes: &[u8]) -> T {
40    let mut val = zero::<T>();
41    let mut shift = 0;
42    for &b in bytes {
43        val |= <T as From<u8>>::from(b) << shift;
44        shift += 8;
45    }
46    val
47}
48
49impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
50    /// Serialize into `out` as big-endian bytes. Writes `self.len *
51    /// size_of::<T>()` bytes into `out[..byte_count]` and returns that
52    /// slice. Panics if `out.len() < byte_count`.
53    pub fn to_be_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8] {
54        let word_size = core::mem::size_of::<T>();
55        let byte_count = self.len as usize * word_size;
56        assert!(
57            out.len() >= byte_count,
58            "HeaplessBigInt::to_be_bytes: out.len() < required ({byte_count} bytes)"
59        );
60        for (chunk, word) in out[..byte_count]
61            .chunks_exact_mut(word_size)
62            .zip(self.limbs[..self.len as usize].iter().rev())
63        {
64            let word_bytes = word.to_be_bytes();
65            for (dst, src) in chunk.iter_mut().zip(word_bytes.as_ref()) {
66                *dst = *src;
67            }
68        }
69        &out[..byte_count]
70    }
71
72    /// Serialize into `out` as little-endian bytes. Same size + panic
73    /// contract as [`to_be_bytes`](Self::to_be_bytes).
74    pub fn to_le_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8] {
75        let word_size = core::mem::size_of::<T>();
76        let byte_count = self.len as usize * word_size;
77        assert!(
78            out.len() >= byte_count,
79            "HeaplessBigInt::to_le_bytes: out.len() < required ({byte_count} bytes)"
80        );
81        for (chunk, word) in out[..byte_count]
82            .chunks_exact_mut(word_size)
83            .zip(self.limbs[..self.len as usize].iter())
84        {
85            let word_bytes = word.to_le_bytes();
86            for (dst, src) in chunk.iter_mut().zip(word_bytes.as_ref()) {
87                *dst = *src;
88            }
89        }
90        &out[..byte_count]
91    }
92
93    /// Deserialize a big-endian byte slice. Output `len =
94    /// ceil(bytes.len() / word_size)`, capped at `CAP`. A partial top
95    /// word (input length not a multiple of `word_size`) leaves the
96    /// missing high bytes zero — matches the BE convention. Panics if
97    /// `bytes.len() > CAP * word_size`.
98    pub fn from_be_bytes(bytes: &[u8]) -> Self {
99        let word_size = core::mem::size_of::<T>();
100        let max_bytes = CAP * word_size;
101        assert!(
102            bytes.len() <= max_bytes,
103            "HeaplessBigInt::from_be_bytes: input {} bytes > CAP * word_size ({max_bytes})",
104            bytes.len()
105        );
106        let byte_count = bytes.len();
107        let out_len = byte_count.div_ceil(word_size);
108
109        let mut limbs = [zero::<T>(); CAP];
110        // Fill limbs from limb 0, consuming `bytes` back-to-front (a
111        // partial chunk, if any, lands at the high end).
112        let mut hi = byte_count;
113        let mut word_idx = 0;
114        while hi > 0 {
115            let take = core::cmp::min(word_size, hi);
116            let lo = hi - take;
117            limbs[word_idx] = read_be_word::<T>(&bytes[lo..hi]);
118            word_idx += 1;
119            hi = lo;
120        }
121
122        Self {
123            limbs,
124            len: out_len as u16,
125            _p: PhantomData,
126        }
127    }
128
129    /// Deserialize a little-endian byte slice. Same size contract as
130    /// [`from_be_bytes`](Self::from_be_bytes).
131    pub fn from_le_bytes(bytes: &[u8]) -> Self {
132        let word_size = core::mem::size_of::<T>();
133        let max_bytes = CAP * word_size;
134        assert!(
135            bytes.len() <= max_bytes,
136            "HeaplessBigInt::from_le_bytes: input {} bytes > CAP * word_size ({max_bytes})",
137            bytes.len()
138        );
139        let byte_count = bytes.len();
140        let out_len = byte_count.div_ceil(word_size);
141
142        let mut limbs = [zero::<T>(); CAP];
143        let mut offset = 0;
144        let mut word_idx = 0;
145        while offset < byte_count {
146            let take = core::cmp::min(word_size, byte_count - offset);
147            limbs[word_idx] = read_le_word::<T>(&bytes[offset..offset + take]);
148            word_idx += 1;
149            offset += take;
150        }
151
152        Self {
153            limbs,
154            len: out_len as u16,
155            _p: PhantomData,
156        }
157    }
158}
159
160// ── const_num_traits::FromByteSlice ──
161//
162// Result-returning slice parse: empty → `Empty`, wider than the
163// container → `Overflow`, shorter → zero-extended. The inherent
164// `from_be_bytes`/`from_le_bytes` already zero-extend; the length
165// guard converts their panic-on-oversize into the `Overflow` error.
166
167impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
168    #[inline]
169    fn check_slice_len(len: usize) -> Result<(), ByteSliceError> {
170        if len == 0 {
171            return Err(ByteSliceError {
172                kind: ByteSliceErrorKind::Empty,
173            });
174        }
175        if len > CAP * core::mem::size_of::<T>() {
176            return Err(ByteSliceError {
177                kind: ByteSliceErrorKind::Overflow,
178            });
179        }
180        Ok(())
181    }
182}
183
184impl<T: MachineWord, const CAP: usize, P: Personality> FromByteSlice for HeaplessBigInt<T, CAP, P> {
185    fn from_be_slice(bytes: &[u8]) -> Result<Self, ByteSliceError> {
186        Self::check_slice_len(bytes.len())?;
187        Ok(Self::from_be_bytes(bytes))
188    }
189
190    fn from_le_slice(bytes: &[u8]) -> Result<Self, ByteSliceError> {
191        Self::check_slice_len(bytes.len())?;
192        Ok(Self::from_le_bytes(bytes))
193    }
194}