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