Skip to main content

qubit_io/codec/
binary_codec.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10
11use core::{
12    marker::PhantomData,
13    ptr,
14};
15
16use crate::{
17    BigEndian,
18    LittleEndian,
19};
20
21/// Type-level unchecked binary codec for one scalar type and one byte order.
22///
23/// `BinaryCodec` is intentionally a static namespace. It does not provide safe
24/// checked helpers, constructors, or instance methods. Callers must validate
25/// buffer lengths before entering the hot path.
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
27pub struct BinaryCodec<T, O> {
28    marker: PhantomData<fn() -> (T, O)>,
29}
30
31impl<O> BinaryCodec<u8, O> {
32    /// Minimum number of bytes required to encode or decode this type.
33    pub const REQUIRED_MIN_BUFFER_LEN: usize = 1;
34
35    /// Decodes a value from `input` starting at `index` without bounds checks.
36    ///
37    /// # Parameters
38    ///
39    /// - `input`: Source byte buffer.
40    /// - `index`: Start index in `input`.
41    ///
42    /// # Returns
43    ///
44    /// Returns the decoded value.
45    ///
46    /// # Safety
47    ///
48    /// The caller must guarantee that `input.as_ptr().add(index)` is valid to
49    /// read [`Self::REQUIRED_MIN_BUFFER_LEN`] bytes.
50    #[must_use]
51    #[inline(always)]
52    pub unsafe fn read_unchecked(input: &[u8], index: usize) -> u8 {
53        // SAFETY: The caller guarantees that the indexed byte is readable.
54        unsafe { *input.as_ptr().add(index) }
55    }
56
57    /// Encodes `value` into `output` starting at `index` without bounds checks.
58    ///
59    /// # Parameters
60    ///
61    /// - `output`: Destination byte buffer.
62    /// - `index`: Start index in `output`.
63    /// - `value`: Value to encode.
64    ///
65    /// # Safety
66    ///
67    /// The caller must guarantee that `output.as_mut_ptr().add(index)` is valid
68    /// to write [`Self::REQUIRED_MIN_BUFFER_LEN`] bytes.
69    #[inline(always)]
70    pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: u8) {
71        // SAFETY: The caller guarantees that the indexed byte is writable.
72        unsafe {
73            *output.as_mut_ptr().add(index) = value;
74        }
75    }
76}
77
78impl<O> BinaryCodec<i8, O> {
79    /// Minimum number of bytes required to encode or decode this type.
80    pub const REQUIRED_MIN_BUFFER_LEN: usize = 1;
81
82    /// Decodes a value from `input` starting at `index` without bounds checks.
83    ///
84    /// # Parameters
85    ///
86    /// - `input`: Source byte buffer.
87    /// - `index`: Start index in `input`.
88    ///
89    /// # Returns
90    ///
91    /// Returns the decoded value.
92    ///
93    /// # Safety
94    ///
95    /// The caller must guarantee that `input.as_ptr().add(index)` is valid to
96    /// read [`Self::REQUIRED_MIN_BUFFER_LEN`] bytes.
97    #[must_use]
98    #[inline(always)]
99    pub unsafe fn read_unchecked(input: &[u8], index: usize) -> i8 {
100        // SAFETY: The caller guarantees that the indexed byte is readable.
101        unsafe { *input.as_ptr().add(index) as i8 }
102    }
103
104    /// Encodes `value` into `output` starting at `index` without bounds checks.
105    ///
106    /// # Parameters
107    ///
108    /// - `output`: Destination byte buffer.
109    /// - `index`: Start index in `output`.
110    /// - `value`: Value to encode.
111    ///
112    /// # Safety
113    ///
114    /// The caller must guarantee that `output.as_mut_ptr().add(index)` is valid
115    /// to write [`Self::REQUIRED_MIN_BUFFER_LEN`] bytes.
116    #[inline(always)]
117    pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: i8) {
118        // SAFETY: The caller guarantees that the indexed byte is writable.
119        unsafe {
120            *output.as_mut_ptr().add(index) = value as u8;
121        }
122    }
123}
124
125macro_rules! impl_integer_binary_codec {
126    ($ty:ty, $len:expr) => {
127        impl BinaryCodec<$ty, BigEndian> {
128            /// Minimum number of bytes required to encode or decode this type.
129            pub const REQUIRED_MIN_BUFFER_LEN: usize = $len;
130
131            /// Decodes a value from `input` starting at `index` without bounds checks.
132            ///
133            /// This function is intended for hot binary codec paths where the
134            /// caller has already validated the buffer length externally.
135            ///
136            /// # Parameters
137            ///
138            /// - `input`: Source byte buffer.
139            /// - `index`: Start byte index in `input`.
140            ///
141            /// # Returns
142            ///
143            /// Returns the decoded value.
144            ///
145            /// # Safety
146            ///
147            /// The caller must guarantee that:
148            ///
149            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len()`
150            /// - `input[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
151            ///   is valid for reading.
152            #[must_use]
153            #[inline(always)]
154            pub unsafe fn read_unchecked(input: &[u8], index: usize) -> $ty {
155                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len());
156
157                // SAFETY:
158                // The caller guarantees that the readable range is fully in-bounds.
159                // `read_unaligned` permits unaligned memory access.
160                let pointer = unsafe { input.as_ptr().add(index).cast::<$ty>() };
161
162                // SAFETY:
163                // The pointer is valid for an unaligned integer load.
164                let raw = unsafe { ptr::read_unaligned(pointer) };
165
166                <$ty>::from_be(raw)
167            }
168
169            /// Encodes `value` into `output` starting at `index`
170            /// without bounds checks.
171            ///
172            /// This function is intended for hot binary codec paths where the
173            /// caller has already validated the buffer length externally.
174            ///
175            /// # Parameters
176            ///
177            /// - `output`: Destination byte buffer.
178            /// - `index`: Start byte index in `output`.
179            /// - `value`: Value to encode.
180            ///
181            /// # Safety
182            ///
183            /// The caller must guarantee that:
184            ///
185            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len()`
186            /// - `output[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
187            ///   is valid for writing.
188            #[inline(always)]
189            pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: $ty) {
190                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len());
191
192                let raw = value.to_be();
193
194                // SAFETY:
195                // The caller guarantees that the writable range is fully in-bounds.
196                // `write_unaligned` permits unaligned memory access.
197                let pointer = unsafe { output.as_mut_ptr().add(index).cast::<$ty>() };
198
199                // SAFETY:
200                // The pointer is valid for an unaligned integer store.
201                unsafe {
202                    ptr::write_unaligned(pointer, raw);
203                }
204            }
205        }
206
207        impl BinaryCodec<$ty, LittleEndian> {
208            /// Minimum number of bytes required to encode or decode this type.
209            pub const REQUIRED_MIN_BUFFER_LEN: usize = $len;
210
211            /// Decodes a value from `input` starting at `index` without bounds checks.
212            ///
213            /// This function is intended for hot binary codec paths where the
214            /// caller has already validated the buffer length externally.
215            ///
216            /// # Parameters
217            ///
218            /// - `input`: Source byte buffer.
219            /// - `index`: Start byte index in `input`.
220            ///
221            /// # Returns
222            ///
223            /// Returns the decoded value.
224            ///
225            /// # Safety
226            ///
227            /// The caller must guarantee that:
228            ///
229            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len()`
230            /// - `input[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
231            ///   is valid for reading.
232            #[must_use]
233            #[inline(always)]
234            pub unsafe fn read_unchecked(input: &[u8], index: usize) -> $ty {
235                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len());
236
237                // SAFETY:
238                // The caller guarantees that the readable range is fully in-bounds.
239                // `read_unaligned` permits unaligned memory access.
240                let pointer = unsafe { input.as_ptr().add(index).cast::<$ty>() };
241
242                // SAFETY:
243                // The pointer is valid for an unaligned integer load.
244                let raw = unsafe { ptr::read_unaligned(pointer) };
245
246                <$ty>::from_le(raw)
247            }
248
249            /// Encodes `value` into `output` starting at `index`
250            /// without bounds checks.
251            ///
252            /// This function is intended for hot binary codec paths where the
253            /// caller has already validated the buffer length externally.
254            ///
255            /// # Parameters
256            ///
257            /// - `output`: Destination byte buffer.
258            /// - `index`: Start byte index in `output`.
259            /// - `value`: Value to encode.
260            ///
261            /// # Safety
262            ///
263            /// The caller must guarantee that:
264            ///
265            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len()`
266            /// - `output[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
267            ///   is valid for writing.
268            #[inline(always)]
269            pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: $ty) {
270                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len());
271
272                let raw = value.to_le();
273
274                // SAFETY:
275                // The caller guarantees that the writable range is fully in-bounds.
276                // `write_unaligned` permits unaligned memory access.
277                let pointer = unsafe { output.as_mut_ptr().add(index).cast::<$ty>() };
278
279                // SAFETY:
280                // The pointer is valid for an unaligned integer store.
281                unsafe {
282                    ptr::write_unaligned(pointer, raw);
283                }
284            }
285        }
286    };
287}
288
289macro_rules! impl_float_binary_codec {
290    ($ty:ty, $bits:ty, $len:expr) => {
291        impl BinaryCodec<$ty, BigEndian> {
292            /// Minimum number of bytes required to encode or decode this type.
293            pub const REQUIRED_MIN_BUFFER_LEN: usize = $len;
294
295            /// Decodes a value from `input` starting at `index` without bounds checks.
296            ///
297            /// This function is intended for hot binary codec paths where the
298            /// caller has already validated the buffer length externally.
299            ///
300            /// # Parameters
301            ///
302            /// - `input`: Source byte buffer.
303            /// - `index`: Start byte index in `input`.
304            ///
305            /// # Returns
306            ///
307            /// Returns the decoded floating-point value.
308            ///
309            /// # Safety
310            ///
311            /// The caller must guarantee that:
312            ///
313            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len()`
314            /// - `input[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
315            ///   is valid for reading.
316            #[must_use]
317            #[inline(always)]
318            pub unsafe fn read_unchecked(input: &[u8], index: usize) -> $ty {
319                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len());
320
321                // SAFETY:
322                // The caller guarantees that the readable range is fully in-bounds.
323                // `read_unaligned` permits unaligned memory access.
324                let pointer = unsafe { input.as_ptr().add(index).cast::<$bits>() };
325
326                // SAFETY:
327                // The pointer is valid for an unaligned integer load.
328                let raw = unsafe { ptr::read_unaligned(pointer) };
329
330                <$ty>::from_bits(<$bits>::from_be(raw))
331            }
332
333            /// Encodes `value` into `output` starting at `index`
334            /// without bounds checks.
335            ///
336            /// This function is intended for hot binary codec paths where the
337            /// caller has already validated the buffer length externally.
338            ///
339            /// # Parameters
340            ///
341            /// - `output`: Destination byte buffer.
342            /// - `index`: Start byte index in `output`.
343            /// - `value`: Floating-point value to encode.
344            ///
345            /// # Safety
346            ///
347            /// The caller must guarantee that:
348            ///
349            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len()`
350            /// - `output[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
351            ///   is valid for writing.
352            #[inline(always)]
353            pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: $ty) {
354                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len());
355
356                let raw = value.to_bits().to_be();
357
358                // SAFETY:
359                // The caller guarantees that the writable range is fully in-bounds.
360                // `write_unaligned` permits unaligned memory access.
361                let pointer = unsafe { output.as_mut_ptr().add(index).cast::<$bits>() };
362
363                // SAFETY:
364                // The pointer is valid for an unaligned integer store.
365                unsafe {
366                    ptr::write_unaligned(pointer, raw);
367                }
368            }
369        }
370
371        impl BinaryCodec<$ty, LittleEndian> {
372            /// Minimum number of bytes required to encode or decode this type.
373            pub const REQUIRED_MIN_BUFFER_LEN: usize = $len;
374
375            /// Decodes a value from `input` starting at `index` without bounds checks.
376            ///
377            /// This function is intended for hot binary codec paths where the
378            /// caller has already validated the buffer length externally.
379            ///
380            /// # Parameters
381            ///
382            /// - `input`: Source byte buffer.
383            /// - `index`: Start byte index in `input`.
384            ///
385            /// # Returns
386            ///
387            /// Returns the decoded floating-point value.
388            ///
389            /// # Safety
390            ///
391            /// The caller must guarantee that:
392            ///
393            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len()`
394            /// - `input[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
395            ///   is valid for reading.
396            #[must_use]
397            #[inline(always)]
398            pub unsafe fn read_unchecked(input: &[u8], index: usize) -> $ty {
399                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= input.len());
400
401                // SAFETY:
402                // The caller guarantees that the readable range is fully in-bounds.
403                // `read_unaligned` permits unaligned memory access.
404                let pointer = unsafe { input.as_ptr().add(index).cast::<$bits>() };
405
406                // SAFETY:
407                // The pointer is valid for an unaligned integer load.
408                let raw = unsafe { ptr::read_unaligned(pointer) };
409
410                <$ty>::from_bits(<$bits>::from_le(raw))
411            }
412
413            /// Encodes `value` into `output` starting at `index`
414            /// without bounds checks.
415            ///
416            /// This function is intended for hot binary codec paths where the
417            /// caller has already validated the buffer length externally.
418            ///
419            /// # Parameters
420            ///
421            /// - `output`: Destination byte buffer.
422            /// - `index`: Start byte index in `output`.
423            /// - `value`: Floating-point value to encode.
424            ///
425            /// # Safety
426            ///
427            /// The caller must guarantee that:
428            ///
429            /// - `index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len()`
430            /// - `output[index..index + Self::REQUIRED_MIN_BUFFER_LEN]`
431            ///   is valid for writing.
432            #[inline(always)]
433            pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: $ty) {
434                debug_assert!(index + Self::REQUIRED_MIN_BUFFER_LEN <= output.len());
435
436                let raw = value.to_bits().to_le();
437
438                // SAFETY:
439                // The caller guarantees that the writable range is fully in-bounds.
440                // `write_unaligned` permits unaligned memory access.
441                let pointer = unsafe { output.as_mut_ptr().add(index).cast::<$bits>() };
442
443                // SAFETY:
444                // The pointer is valid for an unaligned integer store.
445                unsafe {
446                    ptr::write_unaligned(pointer, raw);
447                }
448            }
449        }
450    };
451}
452
453impl_integer_binary_codec!(u16, 2);
454impl_integer_binary_codec!(u32, 4);
455impl_integer_binary_codec!(u64, 8);
456impl_integer_binary_codec!(u128, 16);
457impl_integer_binary_codec!(i16, 2);
458impl_integer_binary_codec!(i32, 4);
459impl_integer_binary_codec!(i64, 8);
460impl_integer_binary_codec!(i128, 16);
461impl_float_binary_codec!(f32, u32, 4);
462impl_float_binary_codec!(f64, u64, 8);