Skip to main content

xdrs/cxs/
put.rs

1//! Provide a generic CXS implementation for any type
2//! that implements some basic byte operations
3
4#![allow(clippy::upper_case_acronyms)]
5
6use super::limits::*;
7use crate::serialize::writer::*;
8use crate::serialize::writer_unchecked::*;
9
10const V_MULTIBYTE: u8 = 63;
11const S_8BYTE: u8 = 3 << 6;
12const S_4BYTE: u8 = 2 << 6;
13const S_2BYTE: u8 = 1 << 6;
14const S_1BYTE: u8 = 0 << 6;
15const S_HEADER: u8 = 0 << 6;
16
17/// Implement these basic byte operations in order
18/// to get a `Writer<CXS>` implementation for free
19pub(crate) trait Put {
20    // Required:
21    fn put_byte(&mut self, b: u8) -> WriteResult<()>;
22    fn put_bytes(&mut self, bytes: &[u8]) -> WriteResult<()>;
23}
24
25pub struct CXS;
26
27impl<P> Writer<CXS> for P
28where
29    P: Put,
30{
31    #[inline]
32    fn write_opaque_fixed(&mut self, bytes: &[u8]) -> WriteResult<()> {
33        debug_assert!(bytes.len() <= u32::MAX as usize);
34        self.put_bytes(bytes)
35    }
36
37    #[inline]
38    fn write_opaque_unpadded(&mut self, bytes: &[u8]) -> WriteResult<()> {
39        self.put_bytes(bytes)
40    }
41
42    // CXS does not pad opaques.
43    #[inline]
44    fn write_opaque_padding(&mut self, _len: usize) -> WriteResult<()> {
45        Ok(())
46    }
47
48    // inline here slows down benchmarks
49    fn write_opaque_variable(&mut self, bytes: &[u8]) -> WriteResult<()> {
50        if bytes.len() > u32::MAX as usize {
51            return Err(WriteError::InvalidArrayLength);
52        }
53
54        self.write_u32(bytes.len() as u32)?;
55        self.put_bytes(bytes)
56    }
57
58    #[inline]
59    fn write_bool(&mut self, v: bool) -> WriteResult<()> {
60        self.write_i32(if v { 1 } else { 0 })
61    }
62
63    #[inline]
64    fn write_i64(&mut self, n: i64) -> WriteResult<()> {
65        if n > i32::MAX as i64 || n < i32::MIN as i64 {
66            // Use the 8-byte representation
67            let h = V_MULTIBYTE | S_8BYTE;
68            let b = n.to_le_bytes();
69            let combined = [h, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
70            self.put_bytes(&combined)
71        } else {
72            self.write_i32(n as i32)
73        }
74    }
75
76    #[inline]
77    fn write_i32(&mut self, n: i32) -> WriteResult<()> {
78        if n > i16::MAX as i32 || n < i16::MIN as i32 {
79            // use 4-byte repr
80            let h = V_MULTIBYTE | S_4BYTE;
81            let b = n.to_le_bytes();
82            let combined = [h, b[0], b[1], b[2], b[3]];
83            self.put_bytes(&combined)
84        } else {
85            self.write_i16(n as i16)
86        }
87    }
88
89    #[inline]
90    fn write_i16(&mut self, n: i16) -> WriteResult<()> {
91        if n > i8::MAX as i16 || n < i8::MIN as i16 {
92            // use 2-byte repr
93            let h = V_MULTIBYTE | S_2BYTE;
94            let b = n.to_le_bytes();
95            let combined = [h, b[0], b[1]];
96            self.put_bytes(&combined)
97        } else {
98            write_i8(self, n as i8)
99        }
100    }
101
102    #[inline]
103    fn write_u64(&mut self, n: u64) -> WriteResult<()> {
104        if n > u32::MAX as u64 {
105            // Use 8-byte representation
106            let h = V_MULTIBYTE | S_8BYTE;
107            let b = n.to_le_bytes();
108            let combined = [h, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
109            self.put_bytes(&combined)
110        } else {
111            self.write_u32(n as u32)
112        }
113    }
114
115    #[inline]
116    fn write_u32(&mut self, n: u32) -> WriteResult<()> {
117        if n > u16::MAX as u32 {
118            // Use 4-byte representation
119            let h = V_MULTIBYTE | S_4BYTE;
120            let b = n.to_le_bytes();
121            let combined = [h, b[0], b[1], b[2], b[3]];
122            self.put_bytes(&combined)
123        } else {
124            self.write_u16(n as u16)
125        }
126    }
127
128    #[inline]
129    fn write_u16(&mut self, n: u16) -> WriteResult<()> {
130        if n > u8::MAX as u16 {
131            // Use 2-byte representation
132            let h = V_MULTIBYTE | S_2BYTE;
133            let b = n.to_le_bytes();
134            let combined = [h, b[0], b[1]];
135            self.put_bytes(&combined)
136        } else {
137            write_u8(self, n as u8)
138        }
139    }
140
141    /// Writes a 32-bit IEEE 754 float as raw little-endian bytes.
142    ///
143    /// Unlike integers, floats are not compressed with CXS variable-width
144    /// encoding — the bit pattern is written as-is. Little-endian byte order
145    /// follows CXS convention (not RFC 4506 big-endian).
146    #[inline]
147    fn write_f32(&mut self, n: f32) -> WriteResult<()> {
148        let bytes: [u8; 4] = n.to_le_bytes();
149        self.put_bytes(&bytes)
150    }
151
152    /// Writes a 64-bit IEEE 754 double as raw little-endian bytes.
153    ///
154    /// Unlike integers, floats are not compressed with CXS variable-width
155    /// encoding — the bit pattern is written as-is. Little-endian byte order
156    /// follows CXS convention (not RFC 4506 big-endian).
157    #[inline]
158    fn write_f64(&mut self, n: f64) -> WriteResult<()> {
159        let bytes: [u8; 8] = n.to_le_bytes();
160        self.put_bytes(&bytes)
161    }
162}
163
164#[inline]
165fn write_u8<P>(p: &mut P, n: u8) -> WriteResult<()>
166where
167    P: Put,
168{
169    if n > UH_MAX {
170        // Use 1-additional-byte representation
171        let h = V_MULTIBYTE | S_1BYTE;
172        let combined = [h, n];
173        p.put_bytes(&combined)
174    } else {
175        // Use header-byte-only representation
176        p.put_byte(n)
177    }
178}
179
180#[inline]
181fn write_i8<P>(p: &mut P, n: i8) -> WriteResult<()>
182where
183    P: Put,
184{
185    if !(IH_MIN..=IH_MAX).contains(&n) {
186        // Use the 1-byte repr
187        let h = V_MULTIBYTE | S_1BYTE;
188        let combined = [h, n as u8];
189        p.put_bytes(&combined)
190    } else {
191        // Represent this small, signed number in the header-byte with 6-bit,
192        // one's-complement encoding
193        let nu: u8 = n.unsigned_abs();
194        let v = if n >= 0 {
195            nu
196        } else {
197            // invert and set the high bit
198            (!nu & 0b01_1111) | 0b0010_0000
199        };
200
201        let s = S_HEADER;
202        let h = v | s;
203        p.put_byte(h)
204    }
205}
206
207/// The unchecked counterpart to [`Put`]: implement these basic byte
208/// operations to get a `WriterUnchecked<CXS>` implementation for free.
209pub(crate) trait PutUnchecked {
210    /// # Safety
211    /// The underlying buffer must have space for 1 more byte.
212    unsafe fn put_byte(&mut self, b: u8);
213
214    /// # Safety
215    /// The underlying buffer must have space for `bytes.len()` more bytes.
216    unsafe fn put_bytes(&mut self, bytes: &[u8]);
217}
218
219impl<P> WriterUnchecked<CXS> for P
220where
221    P: PutUnchecked,
222{
223    #[inline]
224    unsafe fn write_opaque_fixed(&mut self, bytes: &[u8]) {
225        debug_assert!(bytes.len() <= u32::MAX as usize);
226        // SAFETY: forwarded caller contract.
227        unsafe { self.put_bytes(bytes) }
228    }
229
230    #[inline]
231    unsafe fn write_opaque_unpadded(&mut self, bytes: &[u8]) {
232        // SAFETY: forwarded caller contract.
233        unsafe { self.put_bytes(bytes) }
234    }
235
236    // CXS does not pad opaques.
237    #[inline]
238    unsafe fn write_opaque_padding(&mut self, _len: usize) {}
239
240    unsafe fn write_opaque_variable(&mut self, bytes: &[u8]) {
241        debug_assert!(bytes.len() <= u32::MAX as usize);
242        // SAFETY: forwarded caller contract.
243        unsafe {
244            self.write_u32(bytes.len() as u32);
245            self.put_bytes(bytes);
246        }
247    }
248
249    #[inline]
250    unsafe fn write_bool(&mut self, v: bool) {
251        // SAFETY: forwarded caller contract.
252        unsafe { self.write_i32(if v { 1 } else { 0 }) }
253    }
254
255    #[inline]
256    unsafe fn write_i64(&mut self, n: i64) {
257        if n > i32::MAX as i64 || n < i32::MIN as i64 {
258            // Use the 8-byte representation
259            let h = V_MULTIBYTE | S_8BYTE;
260            let b = n.to_le_bytes();
261            let combined = [h, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
262            // SAFETY: forwarded caller contract.
263            unsafe { self.put_bytes(&combined) }
264        } else {
265            // SAFETY: forwarded caller contract.
266            unsafe { self.write_i32(n as i32) }
267        }
268    }
269
270    #[inline]
271    unsafe fn write_i32(&mut self, n: i32) {
272        if n > i16::MAX as i32 || n < i16::MIN as i32 {
273            // use 4-byte repr
274            let h = V_MULTIBYTE | S_4BYTE;
275            let b = n.to_le_bytes();
276            let combined = [h, b[0], b[1], b[2], b[3]];
277            // SAFETY: forwarded caller contract.
278            unsafe { self.put_bytes(&combined) }
279        } else {
280            // SAFETY: forwarded caller contract.
281            unsafe { self.write_i16(n as i16) }
282        }
283    }
284
285    #[inline]
286    unsafe fn write_i16(&mut self, n: i16) {
287        if n > i8::MAX as i16 || n < i8::MIN as i16 {
288            // use 2-byte repr
289            let h = V_MULTIBYTE | S_2BYTE;
290            let b = n.to_le_bytes();
291            let combined = [h, b[0], b[1]];
292            // SAFETY: forwarded caller contract.
293            unsafe { self.put_bytes(&combined) }
294        } else {
295            // SAFETY: forwarded caller contract.
296            unsafe { write_i8_unchecked(self, n as i8) }
297        }
298    }
299
300    #[inline]
301    unsafe fn write_u64(&mut self, n: u64) {
302        if n > u32::MAX as u64 {
303            // Use 8-byte representation
304            let h = V_MULTIBYTE | S_8BYTE;
305            let b = n.to_le_bytes();
306            let combined = [h, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
307            // SAFETY: forwarded caller contract.
308            unsafe { self.put_bytes(&combined) }
309        } else {
310            // SAFETY: forwarded caller contract.
311            unsafe { self.write_u32(n as u32) }
312        }
313    }
314
315    #[inline]
316    unsafe fn write_u32(&mut self, n: u32) {
317        if n > u16::MAX as u32 {
318            // Use 4-byte representation
319            let h = V_MULTIBYTE | S_4BYTE;
320            let b = n.to_le_bytes();
321            let combined = [h, b[0], b[1], b[2], b[3]];
322            // SAFETY: forwarded caller contract.
323            unsafe { self.put_bytes(&combined) }
324        } else {
325            // SAFETY: forwarded caller contract.
326            unsafe { self.write_u16(n as u16) }
327        }
328    }
329
330    #[inline]
331    unsafe fn write_u16(&mut self, n: u16) {
332        if n > u8::MAX as u16 {
333            // Use 2-byte representation
334            let h = V_MULTIBYTE | S_2BYTE;
335            let b = n.to_le_bytes();
336            let combined = [h, b[0], b[1]];
337            // SAFETY: forwarded caller contract.
338            unsafe { self.put_bytes(&combined) }
339        } else {
340            // SAFETY: forwarded caller contract.
341            unsafe { write_u8_unchecked(self, n as u8) }
342        }
343    }
344
345    /// Writes a 32-bit IEEE 754 float as raw little-endian bytes,
346    /// mirroring the checked CXS writer.
347    #[inline]
348    unsafe fn write_f32(&mut self, n: f32) {
349        let bytes: [u8; 4] = n.to_le_bytes();
350        // SAFETY: forwarded caller contract.
351        unsafe { self.put_bytes(&bytes) }
352    }
353
354    /// Writes a 64-bit IEEE 754 double as raw little-endian bytes,
355    /// mirroring the checked CXS writer.
356    #[inline]
357    unsafe fn write_f64(&mut self, n: f64) {
358        let bytes: [u8; 8] = n.to_le_bytes();
359        // SAFETY: forwarded caller contract.
360        unsafe { self.put_bytes(&bytes) }
361    }
362}
363
364/// # Safety
365/// The underlying buffer must have space for the encoded form (1-2 bytes).
366#[inline]
367unsafe fn write_u8_unchecked<P>(p: &mut P, n: u8)
368where
369    P: PutUnchecked,
370{
371    if n > UH_MAX {
372        // Use 1-additional-byte representation
373        let h = V_MULTIBYTE | S_1BYTE;
374        let combined = [h, n];
375        // SAFETY: forwarded caller contract.
376        unsafe { p.put_bytes(&combined) }
377    } else {
378        // Use header-byte-only representation
379        // SAFETY: forwarded caller contract.
380        unsafe { p.put_byte(n) }
381    }
382}
383
384/// # Safety
385/// The underlying buffer must have space for the encoded form (1-2 bytes).
386#[inline]
387unsafe fn write_i8_unchecked<P>(p: &mut P, n: i8)
388where
389    P: PutUnchecked,
390{
391    if !(IH_MIN..=IH_MAX).contains(&n) {
392        // Use the 1-byte repr
393        let h = V_MULTIBYTE | S_1BYTE;
394        let combined = [h, n as u8];
395        // SAFETY: forwarded caller contract.
396        unsafe { p.put_bytes(&combined) }
397    } else {
398        // Represent this small, signed number in the header-byte with 6-bit,
399        // one's-complement encoding
400        let nu: u8 = n.unsigned_abs();
401        let v = if n >= 0 {
402            nu
403        } else {
404            // invert and set the high bit
405            (!nu & 0b01_1111) | 0b0010_0000
406        };
407
408        let s = S_HEADER;
409        let h = v | s;
410        // SAFETY: forwarded caller contract.
411        unsafe { p.put_byte(h) }
412    }
413}