musli_zerocopy/buf/padder.rs
1use core::marker::PhantomData;
2use core::mem::{MaybeUninit, align_of, size_of, size_of_val, transmute};
3use core::ptr::NonNull;
4
5use crate::buf;
6use crate::traits::{ZeroCopy, ZeroSized};
7
8/// A struct padder as provided to the [`ZeroCopy::pad`] method.
9///
10/// This knows how to find and initialize padding regions in `repr(C)` types,
11/// and provides a builder-like API to doing so.
12#[must_use = "For the writer to have an effect on `OwnedBuf` you must call `Padder::remaining` / `Padder::remaining_unsized`"]
13pub struct Padder<'a, T: ?Sized> {
14 data: NonNull<MaybeUninit<u8>>,
15 offset: usize,
16 _marker: PhantomData<&'a mut T>,
17}
18
19impl<'a, T: ?Sized> Padder<'a, T> {
20 #[inline]
21 pub(crate) fn new(data: NonNull<MaybeUninit<u8>>) -> Self {
22 Self {
23 data,
24 offset: 0,
25 _marker: PhantomData,
26 }
27 }
28
29 /// Indicate that this validate is transparent over `U`.
30 //
31 /// # Safety
32 ///
33 /// This is only allowed if `T` is `#[repr(transparent)]` over `U`.
34 #[inline]
35 pub unsafe fn transparent<U>(&mut self) -> &mut Padder<'a, U> {
36 unsafe { transmute(self) }
37 }
38
39 /// Pad around the given field with zeros.
40 ///
41 /// Note that this is necessary to do correctly in order to satisfy the
42 /// safety requirements by [`remaining()`].
43 ///
44 /// This is typically not called directly, but rather is implemented by the
45 /// [`ZeroCopy`] derive.
46 ///
47 /// [`remaining()`]: Self::remaining
48 /// [`ZeroCopy`]: derive@crate::ZeroCopy
49 ///
50 /// # Safety
51 ///
52 /// The caller must ensure that the field type `F` is an actual field in
53 /// order in the struct being padded.
54 #[inline]
55 pub unsafe fn pad<F>(&mut self)
56 where
57 F: ZeroCopy,
58 {
59 unsafe {
60 self.pad_with::<F>(align_of::<F>());
61 }
62 }
63
64 /// Pad around the given field with zeros for a struct marked
65 /// `#[repr(packed(align))]`.
66 ///
67 /// The field is aligned to `min(align, align_of::<F>())`, which is the
68 /// alignment the compiler uses for fields in a packed struct.
69 ///
70 /// Note that this is necessary to do correctly in order to satisfy the
71 /// safety requirements by [`remaining()`].
72 ///
73 /// This is typically not called directly, but rather is implemented by the
74 /// [`ZeroCopy`] derive.
75 ///
76 /// [`remaining()`]: Self::remaining
77 /// [`ZeroCopy`]: derive@crate::ZeroCopy
78 ///
79 /// # Safety
80 ///
81 /// The caller must ensure that the field type `F` is an actual field in
82 /// order in the struct being padded and that `align` matches the argument
83 /// provided to `#[repr(packed)]` (note that empty means 1). `align` must
84 /// be a power of two.
85 #[inline]
86 pub unsafe fn pad_with<F>(&mut self, align: usize)
87 where
88 F: ZeroCopy,
89 {
90 unsafe {
91 let align = align.min(align_of::<F>());
92 let count = buf::padding_to(self.offset, align);
93 // zero out padding.
94 self.data.as_ptr().add(self.offset).write_bytes(0, count);
95 self.offset += count;
96
97 if F::PADDED {
98 let ptr = NonNull::new_unchecked(self.data.as_ptr().add(self.offset));
99 let mut padder = Padder::new(ptr);
100 F::pad(&mut padder);
101 padder.remaining();
102 }
103
104 self.offset += size_of::<F>();
105 }
106 }
107
108 /// Pad up to an ignored zero-sized field `F`.
109 ///
110 /// Zero-sized fields can have an alignment larger than 1, in which case
111 /// they affect the offset of the fields that follow them.
112 ///
113 /// This is typically not called directly, but rather is implemented by the
114 /// [`ZeroCopy`] derive.
115 ///
116 /// [`ZeroCopy`]: derive@crate::ZeroCopy
117 ///
118 /// # Safety
119 ///
120 /// The caller must ensure that the field type `F` is an actual field in
121 /// order in the struct being padded.
122 #[inline]
123 pub unsafe fn pad_zero_sized<F>(&mut self)
124 where
125 F: ZeroSized,
126 {
127 unsafe {
128 self.pad_zero_sized_with::<F>(align_of::<F>());
129 }
130 }
131
132 /// Pad up to an ignored zero-sized field `F` in a struct marked
133 /// `#[repr(packed(align))]`.
134 ///
135 /// The field is aligned to `min(align, align_of::<F>())`.
136 ///
137 /// This is typically not called directly, but rather is implemented by the
138 /// [`ZeroCopy`] derive.
139 ///
140 /// [`ZeroCopy`]: derive@crate::ZeroCopy
141 ///
142 /// # Safety
143 ///
144 /// The caller must ensure that the field type `F` is an actual field in
145 /// order in the struct being padded and that `align` matches the argument
146 /// provided to `#[repr(packed)]` (note that empty means 1). `align` must
147 /// be a power of two.
148 #[inline]
149 pub unsafe fn pad_zero_sized_with<F>(&mut self, align: usize)
150 where
151 F: ZeroSized,
152 {
153 unsafe {
154 let count = buf::padding_to(self.offset, align.min(align_of::<F>()));
155 // zero out padding.
156 self.data.as_ptr().add(self.offset).write_bytes(0, count);
157 self.offset += count;
158 }
159 }
160
161 /// Specific method to both pad for a discriminant and load it
162 /// simultaneously for inspection.
163 ///
164 /// # Safety
165 ///
166 /// The caller must ensure that the field type `D` is the actual type of the
167 /// discriminant first in order in the enum being padded and that `D` is a
168 /// primitive that does not contain any interior padding.
169 #[inline]
170 pub unsafe fn pad_discriminant<D>(&mut self) -> D
171 where
172 D: ZeroCopy,
173 {
174 unsafe {
175 let count = buf::padding_to(self.offset, align_of::<D>());
176 // zero out padding.
177 self.data.as_ptr().add(self.offset).write_bytes(0, count);
178 let at = self.offset + count;
179 let value = self.data.as_ptr().add(at).cast::<D>().read_unaligned();
180 self.offset = at + size_of::<D>();
181 value
182 }
183 }
184
185 /// Finish writing the current buffer.
186 ///
187 /// This is typically not called directly, but rather is implemented by the
188 /// [`ZeroCopy`] derive.
189 ///
190 /// [`ZeroCopy`]: derive@crate::ZeroCopy
191 ///
192 /// # Safety
193 ///
194 /// Before calling `remaining()`, the caller must ensure that they've called
195 /// [`pad::<F>()`] *in order* for every field in a struct being serialized
196 /// where `F` is the type of the field. Otherwise we might not have written
197 /// the necessary padding to ensure that all bytes related to the struct are
198 /// initialized. Failure to do so would result in undefined behavior.
199 ///
200 /// Fields which are [`ZeroSized`] must be padded with
201 /// [`pad_zero_sized::<F>()`] unless their alignment is 1, since they can
202 /// affect the offset of the fields that follow them.
203 ///
204 /// [`pad::<F>()`]: Self::pad
205 /// [`pad_zero_sized::<F>()`]: Self::pad_zero_sized
206 /// [`ZeroSized`]: crate::traits::ZeroSized
207 #[inline]
208 pub unsafe fn remaining(self)
209 where
210 T: Sized,
211 {
212 debug_assert!(
213 self.offset <= size_of::<T>(),
214 "padded fields extend past the end of the type"
215 );
216
217 unsafe {
218 let count = size_of::<T>() - self.offset;
219 self.data.as_ptr().add(self.offset).write_bytes(0, count);
220 }
221 }
222
223 /// Finalize remaining padding based on the size of an unsized value.
224 #[inline]
225 pub(crate) unsafe fn remaining_unsized(self, value: &T) {
226 debug_assert!(
227 self.offset <= size_of_val(value),
228 "padded fields extend past the end of the value"
229 );
230
231 unsafe {
232 let count = size_of_val(value) - self.offset;
233 self.data.as_ptr().add(self.offset).write_bytes(0, count);
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use std::mem::size_of;
241
242 use anyhow::Result;
243
244 use crate::{OwnedBuf, ZeroCopy, buf};
245
246 #[test]
247 fn ensure_padding() -> Result<()> {
248 #[derive(Debug, PartialEq, Eq, ZeroCopy)]
249 #[repr(C)]
250 #[zero_copy(crate)]
251 struct ZeroPadded(u8, u16, u64);
252
253 let padded = ZeroPadded(
254 0x01u8.to_be(),
255 0x0203u16.to_be(),
256 0x0405060708090a0bu64.to_be(),
257 );
258
259 let mut buf = OwnedBuf::new();
260
261 // Note: You're responsible for ensuring that the buffer has enough
262 // capacity.
263 buf.reserve(size_of::<ZeroPadded>())?;
264
265 // SAFETY: We do not pad beyond known fields and are making sure to
266 // initialize all of the buffer.
267 unsafe {
268 buf::store_unaligned(buf.as_nonnull(), &padded);
269 buf.advance(size_of::<ZeroPadded>());
270 }
271
272 // Note: The bytes are explicitly convert to big-endian encoding above.
273 assert_eq!(
274 buf.as_slice(),
275 &[1, 0, 2, 3, 0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 10, 11]
276 );
277
278 Ok(())
279 }
280}