qubit_codec_binary/codec/binary_codec.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9use core::{
10 convert::Infallible,
11 marker::PhantomData,
12 ptr,
13};
14
15use qubit_codec::Codec;
16
17use crate::{
18 BigEndian,
19 LittleEndian,
20};
21
22/// Type-level unchecked binary codec for one scalar type and one byte order.
23///
24/// `BinaryCodec` is intentionally a zero-sized codec type. It exposes
25/// type-level unchecked helpers for direct hot-path use and also implements
26/// [`Codec`] for generic codec pipelines. Callers must validate buffer lengths
27/// before entering the hot path.
28///
29/// # Type Parameters
30///
31/// - `T`: Scalar value type to decode from bytes and encode into bytes.
32/// - `O`: Type-level byte order marker. Multi-byte scalar implementations use
33/// [`BigEndian`] or [`LittleEndian`]. Single-byte scalar implementations
34/// accept any marker because byte order does not affect one-byte values.
35///
36/// # Examples
37///
38/// ```
39/// use qubit_codec_binary::{
40/// BigEndian,
41/// BinaryCodec,
42/// };
43///
44/// let mut output = [0_u8; BinaryCodec::<u32, BigEndian>::MAX_UNITS_PER_VALUE];
45/// let written = unsafe {
46/// BinaryCodec::<u32, BigEndian>::encode_unchecked(0x0102_0304, &mut output, 0)
47/// };
48/// assert_eq!(4, written);
49/// assert_eq!([1, 2, 3, 4], output);
50///
51/// let (decoded, consumed) = unsafe {
52/// BinaryCodec::<u32, BigEndian>::decode_unchecked(&output, 0)
53/// };
54/// assert_eq!(0x0102_0304, decoded);
55/// assert_eq!(4, consumed.get());
56/// ```
57#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
58pub struct BinaryCodec<T, O> {
59 marker: PhantomData<fn() -> (T, O)>,
60}
61
62impl<O> BinaryCodec<u8, O> {
63 /// Minimum number of bytes required to encode or decode this type.
64 pub const MIN_UNITS_PER_VALUE: usize = 1;
65
66 /// Maximum number of bytes required to encode or decode this type.
67 pub const MAX_UNITS_PER_VALUE: usize = Self::MIN_UNITS_PER_VALUE;
68
69 /// Decodes a value from `input` starting at `index` without bounds checks.
70 ///
71 /// # Parameters
72 ///
73 /// - `input`: Source byte buffer.
74 /// - `index`: Start index in `input`.
75 ///
76 /// # Returns
77 ///
78 /// Returns the decoded value and the non-zero number of consumed bytes.
79 ///
80 /// # Safety
81 ///
82 /// The caller must guarantee that `input.as_ptr().add(index)` is valid to
83 /// read [`Self::MIN_UNITS_PER_VALUE`] bytes.
84 #[must_use]
85 #[inline(always)]
86 pub unsafe fn decode_unchecked(
87 input: &[u8],
88 index: usize,
89 ) -> (u8, core::num::NonZeroUsize) {
90 debug_assert!(index + Self::MIN_UNITS_PER_VALUE <= input.len());
91
92 // SAFETY: The caller guarantees that the indexed byte is readable.
93 (
94 unsafe { *input.as_ptr().add(index) },
95 // SAFETY: `u8` is one byte wide.
96 unsafe {
97 core::num::NonZeroUsize::new_unchecked(
98 Self::MIN_UNITS_PER_VALUE,
99 )
100 },
101 )
102 }
103
104 /// Encodes `value` into `output` starting at `index` without bounds checks.
105 ///
106 /// # Parameters
107 ///
108 /// - `value`: Value to encode.
109 /// - `output`: Destination byte buffer.
110 /// - `index`: Start index in `output`.
111 ///
112 /// # Safety
113 ///
114 /// The caller must guarantee that `output.as_mut_ptr().add(index)` is valid
115 /// to write [`Self::MAX_UNITS_PER_VALUE`] bytes.
116 #[inline(always)]
117 pub unsafe fn encode_unchecked(
118 value: u8,
119 output: &mut [u8],
120 index: usize,
121 ) -> usize {
122 debug_assert!(index + Self::MAX_UNITS_PER_VALUE <= output.len());
123
124 // SAFETY: The caller guarantees that the indexed byte is writable.
125 unsafe {
126 *output.as_mut_ptr().add(index) = value;
127 }
128 Self::MAX_UNITS_PER_VALUE
129 }
130}
131
132unsafe impl<O> Codec for BinaryCodec<u8, O> {
133 type Value = u8;
134 type Unit = u8;
135 type DecodeError = Infallible;
136 type EncodeError = Infallible;
137
138 #[inline(always)]
139 fn min_units_per_value(&self) -> core::num::NonZeroUsize {
140 // SAFETY: `u8` is one byte wide.
141 unsafe {
142 core::num::NonZeroUsize::new_unchecked(Self::MIN_UNITS_PER_VALUE)
143 }
144 }
145
146 #[inline(always)]
147 fn max_units_per_value(&self) -> core::num::NonZeroUsize {
148 // SAFETY: `u8` is one byte wide.
149 unsafe {
150 core::num::NonZeroUsize::new_unchecked(Self::MAX_UNITS_PER_VALUE)
151 }
152 }
153
154 #[inline(always)]
155 unsafe fn decode_unchecked(
156 &self,
157 input: &[u8],
158 index: usize,
159 ) -> Result<(u8, core::num::NonZeroUsize), Self::DecodeError> {
160 // SAFETY: The caller upholds the `Codec::decode_unchecked` contract.
161 Ok(unsafe { Self::decode_unchecked(input, index) })
162 }
163
164 #[inline(always)]
165 unsafe fn encode_unchecked(
166 &self,
167 value: &u8,
168 output: &mut [u8],
169 index: usize,
170 ) -> Result<usize, Self::EncodeError> {
171 // SAFETY: The caller upholds the `Codec::encode_unchecked` contract.
172 Ok(unsafe { Self::encode_unchecked(*value, output, index) })
173 }
174}
175
176impl<O> BinaryCodec<i8, O> {
177 /// Minimum number of bytes required to encode or decode this type.
178 pub const MIN_UNITS_PER_VALUE: usize = 1;
179
180 /// Maximum number of bytes required to encode or decode this type.
181 pub const MAX_UNITS_PER_VALUE: usize = Self::MIN_UNITS_PER_VALUE;
182
183 /// Decodes a value from `input` starting at `index` without bounds checks.
184 ///
185 /// # Parameters
186 ///
187 /// - `input`: Source byte buffer.
188 /// - `index`: Start index in `input`.
189 ///
190 /// # Returns
191 ///
192 /// Returns the decoded value and the non-zero number of consumed bytes.
193 ///
194 /// # Safety
195 ///
196 /// The caller must guarantee that `input.as_ptr().add(index)` is valid to
197 /// read [`Self::MIN_UNITS_PER_VALUE`] bytes.
198 #[must_use]
199 #[inline(always)]
200 pub unsafe fn decode_unchecked(
201 input: &[u8],
202 index: usize,
203 ) -> (i8, core::num::NonZeroUsize) {
204 debug_assert!(index + Self::MIN_UNITS_PER_VALUE <= input.len());
205
206 // SAFETY: The caller guarantees that the indexed byte is readable.
207 (
208 unsafe { *input.as_ptr().add(index) as i8 },
209 // SAFETY: `i8` is one byte wide.
210 unsafe {
211 core::num::NonZeroUsize::new_unchecked(
212 Self::MIN_UNITS_PER_VALUE,
213 )
214 },
215 )
216 }
217
218 /// Encodes `value` into `output` starting at `index` without bounds checks.
219 ///
220 /// # Parameters
221 ///
222 /// - `value`: Value to encode.
223 /// - `output`: Destination byte buffer.
224 /// - `index`: Start index in `output`.
225 ///
226 /// # Safety
227 ///
228 /// The caller must guarantee that `output.as_mut_ptr().add(index)` is valid
229 /// to write [`Self::MAX_UNITS_PER_VALUE`] bytes.
230 #[inline(always)]
231 pub unsafe fn encode_unchecked(
232 value: i8,
233 output: &mut [u8],
234 index: usize,
235 ) -> usize {
236 debug_assert!(index + Self::MAX_UNITS_PER_VALUE <= output.len());
237
238 // SAFETY: The caller guarantees that the indexed byte is writable.
239 unsafe {
240 *output.as_mut_ptr().add(index) = value as u8;
241 }
242 Self::MAX_UNITS_PER_VALUE
243 }
244}
245
246unsafe impl<O> Codec for BinaryCodec<i8, O> {
247 type Value = i8;
248 type Unit = u8;
249 type DecodeError = Infallible;
250 type EncodeError = Infallible;
251
252 #[inline(always)]
253 fn min_units_per_value(&self) -> core::num::NonZeroUsize {
254 // SAFETY: `i8` is one byte wide.
255 unsafe {
256 core::num::NonZeroUsize::new_unchecked(Self::MIN_UNITS_PER_VALUE)
257 }
258 }
259
260 #[inline(always)]
261 fn max_units_per_value(&self) -> core::num::NonZeroUsize {
262 // SAFETY: `i8` is one byte wide.
263 unsafe {
264 core::num::NonZeroUsize::new_unchecked(Self::MAX_UNITS_PER_VALUE)
265 }
266 }
267
268 #[inline(always)]
269 unsafe fn decode_unchecked(
270 &self,
271 input: &[u8],
272 index: usize,
273 ) -> Result<(i8, core::num::NonZeroUsize), Self::DecodeError> {
274 // SAFETY: The caller upholds the `Codec::decode_unchecked` contract.
275 Ok(unsafe { Self::decode_unchecked(input, index) })
276 }
277
278 #[inline(always)]
279 unsafe fn encode_unchecked(
280 &self,
281 value: &i8,
282 output: &mut [u8],
283 index: usize,
284 ) -> Result<usize, Self::EncodeError> {
285 // SAFETY: The caller upholds the `Codec::encode_unchecked` contract.
286 Ok(unsafe { Self::encode_unchecked(*value, output, index) })
287 }
288}
289
290macro_rules! impl_integer_binary_codec {
291 ($ty:ty, $len:expr) => {
292 impl BinaryCodec<$ty, BigEndian> {
293 /// Minimum number of bytes required to encode or decode this type.
294 pub const MIN_UNITS_PER_VALUE: usize = $len;
295
296 /// Maximum number of bytes required to encode or decode this type.
297 pub const MAX_UNITS_PER_VALUE: usize = Self::MIN_UNITS_PER_VALUE;
298
299 /// Decodes a value from `input` starting at `index` without bounds
300 /// checks.
301 ///
302 /// This function is intended for hot binary codec paths where the
303 /// caller has already validated the buffer length externally.
304 ///
305 /// # Parameters
306 ///
307 /// - `input`: Source byte buffer.
308 /// - `index`: Start byte index in `input`.
309 ///
310 /// # Returns
311 ///
312 /// Returns the decoded value and the non-zero number of consumed
313 /// bytes.
314 ///
315 /// # Safety
316 ///
317 /// The caller must guarantee that:
318 ///
319 /// - `index + Self::MIN_UNITS_PER_VALUE <= input.len()`
320 /// - `input[index..index + Self::MIN_UNITS_PER_VALUE]` is valid for
321 /// reading.
322 #[must_use]
323 #[inline(always)]
324 pub unsafe fn decode_unchecked(
325 input: &[u8],
326 index: usize,
327 ) -> ($ty, core::num::NonZeroUsize) {
328 debug_assert!(index + Self::MIN_UNITS_PER_VALUE <= input.len());
329
330 // SAFETY:
331 // The caller guarantees that the readable range is fully
332 // in-bounds. `read_unaligned` permits unaligned memory
333 // access.
334 let pointer =
335 unsafe { input.as_ptr().add(index).cast::<$ty>() };
336
337 // SAFETY:
338 // The pointer is valid for an unaligned integer load.
339 let raw = unsafe { ptr::read_unaligned(pointer) };
340
341 (
342 <$ty>::from_be(raw),
343 // SAFETY: `$ty` is a concrete primitive integer type with
344 // non-zero size.
345 unsafe {
346 core::num::NonZeroUsize::new_unchecked(
347 Self::MIN_UNITS_PER_VALUE,
348 )
349 },
350 )
351 }
352
353 /// Encodes `value` into `output` starting at `index`
354 /// without bounds checks.
355 ///
356 /// This function is intended for hot binary codec paths where the
357 /// caller has already validated the buffer length externally.
358 ///
359 /// # Parameters
360 ///
361 /// - `value`: Value to encode.
362 /// - `output`: Destination byte buffer.
363 /// - `index`: Start byte index in `output`.
364 ///
365 /// # Safety
366 ///
367 /// The caller must guarantee that:
368 ///
369 /// - `index + Self::MAX_UNITS_PER_VALUE <= output.len()`
370 /// - `output[index..index + Self::MAX_UNITS_PER_VALUE]` is valid
371 /// for writing.
372 #[inline(always)]
373 pub unsafe fn encode_unchecked(
374 value: $ty,
375 output: &mut [u8],
376 index: usize,
377 ) -> usize {
378 debug_assert!(
379 index + Self::MAX_UNITS_PER_VALUE <= output.len()
380 );
381
382 let raw = value.to_be();
383
384 // SAFETY:
385 // The caller guarantees that the writable range is fully
386 // in-bounds. `write_unaligned` permits unaligned memory
387 // access.
388 let pointer =
389 unsafe { output.as_mut_ptr().add(index).cast::<$ty>() };
390
391 // SAFETY:
392 // The pointer is valid for an unaligned integer store.
393 unsafe {
394 ptr::write_unaligned(pointer, raw);
395 }
396 Self::MAX_UNITS_PER_VALUE
397 }
398 }
399
400 unsafe impl Codec for BinaryCodec<$ty, BigEndian> {
401 type Value = $ty;
402 type Unit = u8;
403 type DecodeError = Infallible;
404 type EncodeError = Infallible;
405
406 #[inline(always)]
407 fn min_units_per_value(&self) -> core::num::NonZeroUsize {
408 // SAFETY: `$ty` is a concrete primitive integer type with
409 // non-zero size.
410 unsafe {
411 core::num::NonZeroUsize::new_unchecked(
412 Self::MIN_UNITS_PER_VALUE,
413 )
414 }
415 }
416
417 #[inline(always)]
418 fn max_units_per_value(&self) -> core::num::NonZeroUsize {
419 // SAFETY: `$ty` is a concrete primitive integer type with
420 // non-zero size.
421 unsafe {
422 core::num::NonZeroUsize::new_unchecked(
423 Self::MAX_UNITS_PER_VALUE,
424 )
425 }
426 }
427
428 #[inline(always)]
429 unsafe fn decode_unchecked(
430 &self,
431 input: &[u8],
432 index: usize,
433 ) -> Result<($ty, core::num::NonZeroUsize), Self::DecodeError> {
434 // SAFETY: The caller upholds the `Codec::decode_unchecked`
435 // contract.
436 Ok(unsafe { Self::decode_unchecked(input, index) })
437 }
438
439 #[inline(always)]
440 unsafe fn encode_unchecked(
441 &self,
442 value: &$ty,
443 output: &mut [u8],
444 index: usize,
445 ) -> Result<usize, Self::EncodeError> {
446 // SAFETY: The caller upholds the `Codec::encode_unchecked`
447 // contract.
448 Ok(unsafe { Self::encode_unchecked(*value, output, index) })
449 }
450 }
451
452 impl BinaryCodec<$ty, LittleEndian> {
453 /// Minimum number of bytes required to encode or decode this type.
454 pub const MIN_UNITS_PER_VALUE: usize = $len;
455
456 /// Maximum number of bytes required to encode or decode this type.
457 pub const MAX_UNITS_PER_VALUE: usize = Self::MIN_UNITS_PER_VALUE;
458
459 /// Decodes a value from `input` starting at `index` without bounds
460 /// checks.
461 ///
462 /// This function is intended for hot binary codec paths where the
463 /// caller has already validated the buffer length externally.
464 ///
465 /// # Parameters
466 ///
467 /// - `input`: Source byte buffer.
468 /// - `index`: Start byte index in `input`.
469 ///
470 /// # Returns
471 ///
472 /// Returns the decoded value and the non-zero number of consumed
473 /// bytes.
474 ///
475 /// # Safety
476 ///
477 /// The caller must guarantee that:
478 ///
479 /// - `index + Self::MIN_UNITS_PER_VALUE <= input.len()`
480 /// - `input[index..index + Self::MIN_UNITS_PER_VALUE]` is valid for
481 /// reading.
482 #[must_use]
483 #[inline(always)]
484 pub unsafe fn decode_unchecked(
485 input: &[u8],
486 index: usize,
487 ) -> ($ty, core::num::NonZeroUsize) {
488 debug_assert!(index + Self::MIN_UNITS_PER_VALUE <= input.len());
489
490 // SAFETY:
491 // The caller guarantees that the readable range is fully
492 // in-bounds. `read_unaligned` permits unaligned memory
493 // access.
494 let pointer =
495 unsafe { input.as_ptr().add(index).cast::<$ty>() };
496
497 // SAFETY:
498 // The pointer is valid for an unaligned integer load.
499 let raw = unsafe { ptr::read_unaligned(pointer) };
500
501 (
502 <$ty>::from_le(raw),
503 // SAFETY: `$ty` is a concrete primitive integer type with
504 // non-zero size.
505 unsafe {
506 core::num::NonZeroUsize::new_unchecked(
507 Self::MIN_UNITS_PER_VALUE,
508 )
509 },
510 )
511 }
512
513 /// Encodes `value` into `output` starting at `index`
514 /// without bounds checks.
515 ///
516 /// This function is intended for hot binary codec paths where the
517 /// caller has already validated the buffer length externally.
518 ///
519 /// # Parameters
520 ///
521 /// - `value`: Value to encode.
522 /// - `output`: Destination byte buffer.
523 /// - `index`: Start byte index in `output`.
524 ///
525 /// # Safety
526 ///
527 /// The caller must guarantee that:
528 ///
529 /// - `index + Self::MAX_UNITS_PER_VALUE <= output.len()`
530 /// - `output[index..index + Self::MAX_UNITS_PER_VALUE]` is valid
531 /// for writing.
532 #[inline(always)]
533 pub unsafe fn encode_unchecked(
534 value: $ty,
535 output: &mut [u8],
536 index: usize,
537 ) -> usize {
538 debug_assert!(
539 index + Self::MAX_UNITS_PER_VALUE <= output.len()
540 );
541
542 let raw = value.to_le();
543
544 // SAFETY:
545 // The caller guarantees that the writable range is fully
546 // in-bounds. `write_unaligned` permits unaligned memory
547 // access.
548 let pointer =
549 unsafe { output.as_mut_ptr().add(index).cast::<$ty>() };
550
551 // SAFETY:
552 // The pointer is valid for an unaligned integer store.
553 unsafe {
554 ptr::write_unaligned(pointer, raw);
555 }
556 Self::MAX_UNITS_PER_VALUE
557 }
558 }
559
560 unsafe impl Codec for BinaryCodec<$ty, LittleEndian> {
561 type Value = $ty;
562 type Unit = u8;
563 type DecodeError = Infallible;
564 type EncodeError = Infallible;
565
566 #[inline(always)]
567 fn min_units_per_value(&self) -> core::num::NonZeroUsize {
568 // SAFETY: `$ty` is a concrete primitive integer type with
569 // non-zero size.
570 unsafe {
571 core::num::NonZeroUsize::new_unchecked(
572 Self::MIN_UNITS_PER_VALUE,
573 )
574 }
575 }
576
577 #[inline(always)]
578 fn max_units_per_value(&self) -> core::num::NonZeroUsize {
579 // SAFETY: `$ty` is a concrete primitive integer type with
580 // non-zero size.
581 unsafe {
582 core::num::NonZeroUsize::new_unchecked(
583 Self::MAX_UNITS_PER_VALUE,
584 )
585 }
586 }
587
588 #[inline(always)]
589 unsafe fn decode_unchecked(
590 &self,
591 input: &[u8],
592 index: usize,
593 ) -> Result<($ty, core::num::NonZeroUsize), Self::DecodeError> {
594 // SAFETY: The caller upholds the `Codec::decode_unchecked`
595 // contract.
596 Ok(unsafe { Self::decode_unchecked(input, index) })
597 }
598
599 #[inline(always)]
600 unsafe fn encode_unchecked(
601 &self,
602 value: &$ty,
603 output: &mut [u8],
604 index: usize,
605 ) -> Result<usize, Self::EncodeError> {
606 // SAFETY: The caller upholds the `Codec::encode_unchecked`
607 // contract.
608 Ok(unsafe { Self::encode_unchecked(*value, output, index) })
609 }
610 }
611 };
612}
613
614macro_rules! impl_float_binary_codec {
615 ($ty:ty, $bits:ty, $len:expr) => {
616 impl BinaryCodec<$ty, BigEndian> {
617 /// Minimum number of bytes required to encode or decode this type.
618 pub const MIN_UNITS_PER_VALUE: usize = $len;
619
620 /// Maximum number of bytes required to encode or decode this type.
621 pub const MAX_UNITS_PER_VALUE: usize = Self::MIN_UNITS_PER_VALUE;
622
623 /// Decodes a value from `input` starting at `index` without bounds
624 /// checks.
625 ///
626 /// This function is intended for hot binary codec paths where the
627 /// caller has already validated the buffer length externally.
628 ///
629 /// # Parameters
630 ///
631 /// - `input`: Source byte buffer.
632 /// - `index`: Start byte index in `input`.
633 ///
634 /// # Returns
635 ///
636 /// Returns the decoded floating-point value and the non-zero number
637 /// of consumed bytes.
638 ///
639 /// # Safety
640 ///
641 /// The caller must guarantee that:
642 ///
643 /// - `index + Self::MIN_UNITS_PER_VALUE <= input.len()`
644 /// - `input[index..index + Self::MIN_UNITS_PER_VALUE]` is valid for
645 /// reading.
646 #[must_use]
647 #[inline(always)]
648 pub unsafe fn decode_unchecked(
649 input: &[u8],
650 index: usize,
651 ) -> ($ty, core::num::NonZeroUsize) {
652 debug_assert!(index + Self::MIN_UNITS_PER_VALUE <= input.len());
653
654 // SAFETY:
655 // The caller guarantees that the readable range is fully
656 // in-bounds. `read_unaligned` permits unaligned memory
657 // access.
658 let pointer =
659 unsafe { input.as_ptr().add(index).cast::<$bits>() };
660
661 // SAFETY:
662 // The pointer is valid for an unaligned integer load.
663 let raw = unsafe { ptr::read_unaligned(pointer) };
664
665 (
666 <$ty>::from_bits(<$bits>::from_be(raw)),
667 // SAFETY: `$ty` is a concrete primitive floating-point
668 // type with non-zero size.
669 unsafe {
670 core::num::NonZeroUsize::new_unchecked(
671 Self::MIN_UNITS_PER_VALUE,
672 )
673 },
674 )
675 }
676
677 /// Encodes `value` into `output` starting at `index`
678 /// without bounds checks.
679 ///
680 /// This function is intended for hot binary codec paths where the
681 /// caller has already validated the buffer length externally.
682 ///
683 /// # Parameters
684 ///
685 /// - `value`: Floating-point value to encode.
686 /// - `output`: Destination byte buffer.
687 /// - `index`: Start byte index in `output`.
688 ///
689 /// # Safety
690 ///
691 /// The caller must guarantee that:
692 ///
693 /// - `index + Self::MAX_UNITS_PER_VALUE <= output.len()`
694 /// - `output[index..index + Self::MAX_UNITS_PER_VALUE]` is valid
695 /// for writing.
696 #[inline(always)]
697 pub unsafe fn encode_unchecked(
698 value: $ty,
699 output: &mut [u8],
700 index: usize,
701 ) -> usize {
702 debug_assert!(
703 index + Self::MAX_UNITS_PER_VALUE <= output.len()
704 );
705
706 let raw = value.to_bits().to_be();
707
708 // SAFETY:
709 // The caller guarantees that the writable range is fully
710 // in-bounds. `write_unaligned` permits unaligned memory
711 // access.
712 let pointer =
713 unsafe { output.as_mut_ptr().add(index).cast::<$bits>() };
714
715 // SAFETY:
716 // The pointer is valid for an unaligned integer store.
717 unsafe {
718 ptr::write_unaligned(pointer, raw);
719 }
720 Self::MAX_UNITS_PER_VALUE
721 }
722 }
723
724 unsafe impl Codec for BinaryCodec<$ty, BigEndian> {
725 type Value = $ty;
726 type Unit = u8;
727 type DecodeError = Infallible;
728 type EncodeError = Infallible;
729
730 #[inline(always)]
731 fn min_units_per_value(&self) -> core::num::NonZeroUsize {
732 // SAFETY: `$ty` is a concrete primitive floating-point type
733 // with non-zero size.
734 unsafe {
735 core::num::NonZeroUsize::new_unchecked(
736 Self::MIN_UNITS_PER_VALUE,
737 )
738 }
739 }
740
741 #[inline(always)]
742 fn max_units_per_value(&self) -> core::num::NonZeroUsize {
743 // SAFETY: `$ty` is a concrete primitive floating-point type
744 // with non-zero size.
745 unsafe {
746 core::num::NonZeroUsize::new_unchecked(
747 Self::MAX_UNITS_PER_VALUE,
748 )
749 }
750 }
751
752 #[inline(always)]
753 unsafe fn decode_unchecked(
754 &self,
755 input: &[u8],
756 index: usize,
757 ) -> Result<($ty, core::num::NonZeroUsize), Self::DecodeError> {
758 // SAFETY: The caller upholds the `Codec::decode_unchecked`
759 // contract.
760 Ok(unsafe { Self::decode_unchecked(input, index) })
761 }
762
763 #[inline(always)]
764 unsafe fn encode_unchecked(
765 &self,
766 value: &$ty,
767 output: &mut [u8],
768 index: usize,
769 ) -> Result<usize, Self::EncodeError> {
770 // SAFETY: The caller upholds the `Codec::encode_unchecked`
771 // contract.
772 Ok(unsafe { Self::encode_unchecked(*value, output, index) })
773 }
774 }
775
776 impl BinaryCodec<$ty, LittleEndian> {
777 /// Minimum number of bytes required to encode or decode this type.
778 pub const MIN_UNITS_PER_VALUE: usize = $len;
779
780 /// Maximum number of bytes required to encode or decode this type.
781 pub const MAX_UNITS_PER_VALUE: usize = Self::MIN_UNITS_PER_VALUE;
782
783 /// Decodes a value from `input` starting at `index` without bounds
784 /// checks.
785 ///
786 /// This function is intended for hot binary codec paths where the
787 /// caller has already validated the buffer length externally.
788 ///
789 /// # Parameters
790 ///
791 /// - `input`: Source byte buffer.
792 /// - `index`: Start byte index in `input`.
793 ///
794 /// # Returns
795 ///
796 /// Returns the decoded floating-point value and the non-zero number
797 /// of consumed bytes.
798 ///
799 /// # Safety
800 ///
801 /// The caller must guarantee that:
802 ///
803 /// - `index + Self::MIN_UNITS_PER_VALUE <= input.len()`
804 /// - `input[index..index + Self::MIN_UNITS_PER_VALUE]` is valid for
805 /// reading.
806 #[must_use]
807 #[inline(always)]
808 pub unsafe fn decode_unchecked(
809 input: &[u8],
810 index: usize,
811 ) -> ($ty, core::num::NonZeroUsize) {
812 debug_assert!(index + Self::MIN_UNITS_PER_VALUE <= input.len());
813
814 // SAFETY:
815 // The caller guarantees that the readable range is fully
816 // in-bounds. `read_unaligned` permits unaligned memory
817 // access.
818 let pointer =
819 unsafe { input.as_ptr().add(index).cast::<$bits>() };
820
821 // SAFETY:
822 // The pointer is valid for an unaligned integer load.
823 let raw = unsafe { ptr::read_unaligned(pointer) };
824
825 (
826 <$ty>::from_bits(<$bits>::from_le(raw)),
827 // SAFETY: `$ty` is a concrete primitive floating-point
828 // type with non-zero size.
829 unsafe {
830 core::num::NonZeroUsize::new_unchecked(
831 Self::MIN_UNITS_PER_VALUE,
832 )
833 },
834 )
835 }
836
837 /// Encodes `value` into `output` starting at `index`
838 /// without bounds checks.
839 ///
840 /// This function is intended for hot binary codec paths where the
841 /// caller has already validated the buffer length externally.
842 ///
843 /// # Parameters
844 ///
845 /// - `value`: Floating-point value to encode.
846 /// - `output`: Destination byte buffer.
847 /// - `index`: Start byte index in `output`.
848 ///
849 /// # Safety
850 ///
851 /// The caller must guarantee that:
852 ///
853 /// - `index + Self::MAX_UNITS_PER_VALUE <= output.len()`
854 /// - `output[index..index + Self::MAX_UNITS_PER_VALUE]` is valid
855 /// for writing.
856 #[inline(always)]
857 pub unsafe fn encode_unchecked(
858 value: $ty,
859 output: &mut [u8],
860 index: usize,
861 ) -> usize {
862 debug_assert!(
863 index + Self::MAX_UNITS_PER_VALUE <= output.len()
864 );
865
866 let raw = value.to_bits().to_le();
867
868 // SAFETY:
869 // The caller guarantees that the writable range is fully
870 // in-bounds. `write_unaligned` permits unaligned memory
871 // access.
872 let pointer =
873 unsafe { output.as_mut_ptr().add(index).cast::<$bits>() };
874
875 // SAFETY:
876 // The pointer is valid for an unaligned integer store.
877 unsafe {
878 ptr::write_unaligned(pointer, raw);
879 }
880 Self::MAX_UNITS_PER_VALUE
881 }
882 }
883
884 unsafe impl Codec for BinaryCodec<$ty, LittleEndian> {
885 type Value = $ty;
886 type Unit = u8;
887 type DecodeError = Infallible;
888 type EncodeError = Infallible;
889
890 #[inline(always)]
891 fn min_units_per_value(&self) -> core::num::NonZeroUsize {
892 // SAFETY: `$ty` is a concrete primitive floating-point type
893 // with non-zero size.
894 unsafe {
895 core::num::NonZeroUsize::new_unchecked(
896 Self::MIN_UNITS_PER_VALUE,
897 )
898 }
899 }
900
901 #[inline(always)]
902 fn max_units_per_value(&self) -> core::num::NonZeroUsize {
903 // SAFETY: `$ty` is a concrete primitive floating-point type
904 // with non-zero size.
905 unsafe {
906 core::num::NonZeroUsize::new_unchecked(
907 Self::MAX_UNITS_PER_VALUE,
908 )
909 }
910 }
911
912 #[inline(always)]
913 unsafe fn decode_unchecked(
914 &self,
915 input: &[u8],
916 index: usize,
917 ) -> Result<($ty, core::num::NonZeroUsize), Self::DecodeError> {
918 // SAFETY: The caller upholds the `Codec::decode_unchecked`
919 // contract.
920 Ok(unsafe { Self::decode_unchecked(input, index) })
921 }
922
923 #[inline(always)]
924 unsafe fn encode_unchecked(
925 &self,
926 value: &$ty,
927 output: &mut [u8],
928 index: usize,
929 ) -> Result<usize, Self::EncodeError> {
930 // SAFETY: The caller upholds the `Codec::encode_unchecked`
931 // contract.
932 Ok(unsafe { Self::encode_unchecked(*value, output, index) })
933 }
934 }
935 };
936}
937
938impl_integer_binary_codec!(u16, 2);
939impl_integer_binary_codec!(u32, 4);
940impl_integer_binary_codec!(u64, 8);
941impl_integer_binary_codec!(u128, 16);
942impl_integer_binary_codec!(i16, 2);
943impl_integer_binary_codec!(i32, 4);
944impl_integer_binary_codec!(i64, 8);
945impl_integer_binary_codec!(i128, 16);
946impl_float_binary_codec!(f32, u32, 4);
947impl_float_binary_codec!(f64, u64, 8);