Skip to main content

xdrs/
chunked.rs

1//! Variable-length data held as separate chunks.
2//!
3//! [`ChunkedBytes`] and [`ChunkedVec`] encode a sequence of chunks as the
4//! XDR value of their concatenation, standing in for a `BytesM` or a `VecM`
5//! without first copying the chunks together. They only write: a reader sees
6//! one opaque or array with no chunk boundaries to recover, so decode into
7//! the wire type.
8//!
9//! Both hold their chunks in any container that dereferences to a slice of
10//! them, such as a `Vec`, a slice reference, a `Box<[_]>` or an `Arc<[_]>`.
11//! Equality compares the chunks, not their concatenation. Writing fails with
12//! [`WriteError::InvalidArrayLength`] when the chunks together hold more
13//! than `MAX` bytes or elements, as it does for an oversized `BytesM<MAX>`
14//! or `VecM<T, MAX>`.
15
16use crate::serialize::writer::*;
17use crate::serialize::writer_unchecked::*;
18use crate::{EncodesAs, WriteXDR};
19use core::ops::Deref;
20use xdr_common::{BytesM, StaticIfNoAlloc, VecM};
21
22/// Variable-length opaque data held as a sequence of byte chunks.
23///
24/// `ChunkedBytes<C, MAX>` is [`EncodesAs<BytesM<MAX>>`](EncodesAs). Each
25/// chunk is anything that is `AsRef<[u8]>`, such as a `Vec<u8>`, a `&[u8]`
26/// or an `Arc<[u8]>`, and is written straight from its buffer, followed by
27/// one TXS padding for the whole opaque. See the
28/// [module documentation](self).
29///
30/// # Example
31///
32/// ```rust
33/// # #[cfg(feature = "alloc")] {
34/// use xdrs::{BytesM, ChunkedBytes, txs};
35///
36/// let chunked = ChunkedBytes::<_>(vec![&b"hello, "[..], &b"world"[..]]);
37/// let joined = BytesM::<{ u32::MAX }>(b"hello, world".to_vec());
38/// assert_eq!(txs::encode(&chunked).unwrap(), txs::encode(&joined).unwrap());
39/// # }
40/// ```
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub struct ChunkedBytes<C, const MAX: u32 = { u32::MAX }>(pub C);
43
44/// A variable-length array held as a sequence of element chunks.
45///
46/// `ChunkedVec<C, MAX>` is [`EncodesAs<VecM<X, MAX>>`](EncodesAs) whenever
47/// its elements are `EncodesAs<X>`. Each chunk dereferences to a slice of
48/// elements, such as a `Vec<T>`, a `&[T]` or an `Arc<[T]>`. See the
49/// [module documentation](self).
50///
51/// # Example
52///
53/// ```rust
54/// # #[cfg(feature = "alloc")] {
55/// use xdrs::{ChunkedVec, VecM, txs};
56///
57/// let chunked = ChunkedVec::<_>(vec![vec![1u32, 2], vec![3]]);
58/// let joined = VecM::<u32>(vec![1, 2, 3]);
59/// assert_eq!(txs::encode(&chunked).unwrap(), txs::encode(&joined).unwrap());
60/// # }
61/// ```
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
63pub struct ChunkedVec<C, const MAX: u32 = { u32::MAX }>(pub C);
64
65impl<C, B, const MAX: u32> ChunkedBytes<C, MAX>
66where
67    C: Deref<Target = [B]>,
68    B: AsRef<[u8]>,
69{
70    /// The total number of bytes across all chunks.
71    pub fn len(&self) -> usize {
72        self.0.iter().map(|chunk| chunk.as_ref().len()).sum()
73    }
74
75    /// Whether every chunk is empty.
76    pub fn is_empty(&self) -> bool {
77        self.0.iter().all(|chunk| chunk.as_ref().is_empty())
78    }
79}
80
81impl<C, S, T, const MAX: u32> ChunkedVec<C, MAX>
82where
83    C: Deref<Target = [S]>,
84    S: Deref<Target = [T]>,
85{
86    /// The total number of elements across all chunks.
87    pub fn len(&self) -> usize {
88        self.0.iter().map(|chunk| chunk.len()).sum()
89    }
90
91    /// Whether every chunk is empty.
92    pub fn is_empty(&self) -> bool {
93        self.0.iter().all(|chunk| chunk.is_empty())
94    }
95}
96
97// A variable-length opaque is its length followed by the fixed-length opaque
98// of its bytes, so the chunks can be written back to back as the pieces of
99// one fixed-length opaque.
100impl<C, B, const MAX: u32> WriteXDR for ChunkedBytes<C, MAX>
101where
102    C: Deref<Target = [B]>,
103    B: AsRef<[u8]>,
104{
105    fn write_xdr<F, W: Writer<F>>(&self, w: &mut W) -> WriteResult<()> {
106        let len = checked_len::<MAX>(self.0.iter().map(|chunk| chunk.as_ref().len()))?;
107        w.write_u32(len)?;
108        for chunk in self.0.iter() {
109            w.write_opaque_unpadded(chunk.as_ref())?;
110        }
111        w.write_opaque_padding(len as usize)
112    }
113
114    unsafe fn write_xdr_unchecked<F, W: WriterUnchecked<F>>(&self, w: &mut W) {
115        let len = self.len();
116        debug_assert!(len <= MAX as usize, "ChunkedBytes exceeds declared bound");
117        // SAFETY: the caller guarantees the value respects its declared
118        // bound and that the writer has space for the encoded form, which is
119        // the length, the chunks and the padding written here.
120        unsafe {
121            w.write_u32(len as u32);
122            for chunk in self.0.iter() {
123                w.write_opaque_unpadded(chunk.as_ref());
124            }
125            w.write_opaque_padding(len);
126        }
127    }
128}
129
130// A variable-length array is its length followed by its elements.
131impl<C, S, T, const MAX: u32> WriteXDR for ChunkedVec<C, MAX>
132where
133    C: Deref<Target = [S]>,
134    S: Deref<Target = [T]>,
135    T: WriteXDR,
136{
137    fn write_xdr<F, W: Writer<F>>(&self, w: &mut W) -> WriteResult<()> {
138        let len = checked_len::<MAX>(self.0.iter().map(|chunk| chunk.len()))?;
139        w.write_u32(len)?;
140        for elt in self.0.iter().flat_map(|chunk| chunk.iter()) {
141            elt.write_xdr(w)?;
142        }
143        Ok(())
144    }
145
146    unsafe fn write_xdr_unchecked<F, W: WriterUnchecked<F>>(&self, w: &mut W) {
147        let len = self.len();
148        debug_assert!(len <= MAX as usize, "ChunkedVec exceeds declared bound");
149        // SAFETY: the caller guarantees the value respects its declared
150        // bound and that the writer has space for the length prefix and
151        // every element.
152        unsafe {
153            w.write_u32(len as u32);
154            for elt in self.0.iter().flat_map(|chunk| chunk.iter()) {
155                elt.write_xdr_unchecked(w);
156            }
157        }
158    }
159}
160
161/// The total of the chunk lengths `lens` as an XDR length, or
162/// [`WriteError::InvalidArrayLength`] if it exceeds `MAX`.
163///
164/// Sums in `u32`: on a 32-bit target, repeated references to one large
165/// chunk could overflow a `usize` total.
166fn checked_len<const MAX: u32>(mut lens: impl Iterator<Item = usize>) -> WriteResult<u32> {
167    lens.try_fold(0u32, |total, len| {
168        u32::try_from(len)
169            .ok()
170            .and_then(|len| total.checked_add(len))
171    })
172    .filter(|&len| len <= MAX)
173    .ok_or(WriteError::InvalidArrayLength)
174}
175
176impl<C, B, const MAX: u32> EncodesAs<Self> for ChunkedBytes<C, MAX>
177where
178    C: Deref<Target = [B]>,
179    B: AsRef<[u8]>,
180{
181}
182
183impl<C, B, const MAX: u32> EncodesAs<BytesM<MAX>> for ChunkedBytes<C, MAX>
184where
185    C: Deref<Target = [B]>,
186    B: AsRef<[u8]>,
187{
188}
189
190impl<C, S, T, const MAX: u32> EncodesAs<Self> for ChunkedVec<C, MAX>
191where
192    C: Deref<Target = [S]>,
193    S: Deref<Target = [T]>,
194    T: WriteXDR,
195{
196}
197
198impl<C, S, T, X, const MAX: u32> EncodesAs<VecM<X, MAX>> for ChunkedVec<C, MAX>
199where
200    C: Deref<Target = [S]>,
201    S: Deref<Target = [T]>,
202    T: EncodesAs<X>,
203    X: StaticIfNoAlloc,
204{
205}
206
207// `ChunkedBytes` shares the bound of `BytesM`. `ChunkedVec` is bounded like
208// the element-generic `VecM`: literal `None`, because its elements sit
209// behind indirection.
210macro_rules! impl_const_max_size {
211    ($($format:ident),*) => {$(
212        impl<C, const MAX: u32> crate::$format::ConstMaxSize for ChunkedBytes<C, MAX> {
213            const MAX_SIZE: Option<usize> =
214                crate::$format::const_writer::max_size_opaque_variable(MAX);
215        }
216
217        impl<C, const MAX: u32> crate::$format::ConstMaxSize for ChunkedVec<C, MAX> {
218            const MAX_SIZE: Option<usize> = None;
219        }
220    )*};
221}
222
223impl_const_max_size!(cxs, txs);