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);