Skip to main content

qubit_io/stream/
binary_writer.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::marker::PhantomData;
12use std::io::{
13    Result,
14    Seek,
15    SeekFrom,
16    Write,
17};
18
19use crate::WriteExt;
20use crate::codec::{
21    BigEndian,
22    BinaryCodec,
23    ByteOrder,
24    ByteOrderSpec,
25    LittleEndian,
26};
27use crate::util::{
28    checked_u16_len,
29    checked_u32_len,
30};
31
32/// Writer wrapper for fixed-width binary values.
33///
34/// The byte order is selected by the `O` type parameter. Use
35/// `BinaryWriter<W, BigEndian>` for big-endian data and
36/// `BinaryWriter<W, LittleEndian>` for little-endian data.
37pub struct BinaryWriter<W, O = BigEndian> {
38    inner: W,
39    buffer: [u8; 16],
40    marker: PhantomData<fn() -> O>,
41}
42
43impl<W, O> BinaryWriter<W, O>
44where
45    W: Write,
46    O: ByteOrderSpec,
47{
48    /// Creates a binary writer.
49    ///
50    /// # Parameters
51    ///
52    /// - `inner`: Underlying byte writer.
53    ///
54    /// # Returns
55    ///
56    /// Returns a writer using the byte order selected by `O`.
57    #[must_use]
58    #[inline]
59    pub const fn new(inner: W) -> Self {
60        Self {
61            inner,
62            buffer: [0; 16],
63            marker: PhantomData,
64        }
65    }
66
67    /// Returns the byte order selected by this writer.
68    #[must_use]
69    #[inline]
70    pub const fn byte_order(&self) -> ByteOrder {
71        O::ORDER
72    }
73
74    /// Returns a shared reference to the underlying writer.
75    #[must_use]
76    #[inline]
77    pub const fn get_ref(&self) -> &W {
78        &self.inner
79    }
80
81    /// Returns an exclusive reference to the underlying writer.
82    #[must_use]
83    #[inline]
84    pub fn get_mut(&mut self) -> &mut W {
85        &mut self.inner
86    }
87
88    /// Consumes this wrapper and returns the underlying writer.
89    #[must_use]
90    #[inline]
91    pub fn into_inner(self) -> W {
92        self.inner
93    }
94}
95
96macro_rules! impl_value_write {
97    ($order:ty, $method:ident, $ty:ty, $doc:literal) => {
98        #[doc = $doc]
99        #[inline]
100        pub fn $method(&mut self, value: $ty) -> Result<()> {
101            type Codec = BinaryCodec<$ty, $order>;
102
103            const LEN: usize = Codec::REQUIRED_MIN_BUFFER_LEN;
104            // SAFETY: `LEN` is declared by the codec and fits the fixed internal buffer.
105            unsafe {
106                Codec::write_unchecked(&mut self.buffer, 0, value);
107                self.inner.write_all_unchecked(&self.buffer, 0, LEN)
108            }
109        }
110    };
111}
112
113macro_rules! impl_for_order {
114    ($order:ty) => {
115        impl<W> BinaryWriter<W, $order>
116        where
117            W: Write,
118        {
119            impl_value_write!($order, write_u8, u8, "Writes an unsigned 8-bit integer.");
120            impl_value_write!($order, write_i8, i8, "Writes a signed 8-bit integer.");
121            impl_value_write!($order, write_u16, u16, "Writes an unsigned 16-bit integer.");
122            impl_value_write!($order, write_u32, u32, "Writes an unsigned 32-bit integer.");
123            impl_value_write!($order, write_u64, u64, "Writes an unsigned 64-bit integer.");
124            impl_value_write!($order, write_u128, u128, "Writes an unsigned 128-bit integer.");
125            impl_value_write!($order, write_i16, i16, "Writes a signed 16-bit integer.");
126            impl_value_write!($order, write_i32, i32, "Writes a signed 32-bit integer.");
127            impl_value_write!($order, write_i64, i64, "Writes a signed 64-bit integer.");
128            impl_value_write!($order, write_i128, i128, "Writes a signed 128-bit integer.");
129            impl_value_write!($order, write_f32, f32, "Writes a 32-bit float.");
130            impl_value_write!($order, write_f64, f64, "Writes a 64-bit float.");
131
132            /// Writes a UTF-8 string prefixed by a 16-bit byte length.
133            #[inline]
134            pub fn write_utf8_string_u16(&mut self, value: &str) -> Result<()> {
135                self.write_u16(checked_u16_len(value.len())?)?;
136                let bytes = value.as_bytes();
137                // SAFETY: The range covers the full byte slice produced by `str::as_bytes`.
138                unsafe { self.inner.write_all_unchecked(bytes, 0, bytes.len()) }
139            }
140
141            /// Writes a UTF-8 string prefixed by a 32-bit byte length.
142            #[inline]
143            pub fn write_utf8_string_u32(&mut self, value: &str) -> Result<()> {
144                self.write_u32(checked_u32_len(value.len())?)?;
145                let bytes = value.as_bytes();
146                // SAFETY: The range covers the full byte slice produced by `str::as_bytes`.
147                unsafe { self.inner.write_all_unchecked(bytes, 0, bytes.len()) }
148            }
149        }
150    };
151}
152
153impl_for_order!(BigEndian);
154impl_for_order!(LittleEndian);
155
156impl<W, O> Write for BinaryWriter<W, O>
157where
158    W: Write,
159{
160    /// Writes bytes to the wrapped writer.
161    ///
162    /// # Parameters
163    ///
164    /// - `buffer`: Source bytes to write.
165    ///
166    /// # Returns
167    ///
168    /// Returns the number of bytes written.
169    ///
170    /// # Errors
171    ///
172    /// Returns the I/O error reported by the wrapped writer.
173    #[inline]
174    fn write(&mut self, buffer: &[u8]) -> Result<usize> {
175        self.inner.write(buffer)
176    }
177
178    /// Flushes the wrapped writer.
179    ///
180    /// # Errors
181    ///
182    /// Returns the I/O error reported by the wrapped writer.
183    #[inline]
184    fn flush(&mut self) -> Result<()> {
185        self.inner.flush()
186    }
187}
188
189impl<W, O> Seek for BinaryWriter<W, O>
190where
191    W: Seek,
192{
193    /// Seeks the wrapped writer.
194    ///
195    /// # Parameters
196    ///
197    /// - `position`: Target seek position.
198    ///
199    /// # Returns
200    ///
201    /// Returns the new stream position.
202    ///
203    /// # Errors
204    ///
205    /// Returns the seek error reported by the wrapped writer.
206    #[inline]
207    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
208        self.inner.seek(position)
209    }
210}