musli_zerocopy/buf/padder.rs
1use core::marker::PhantomData;
2use core::mem::{align_of, size_of, size_of_val, transmute};
3use core::ptr::NonNull;
4
5use crate::buf;
6use crate::traits::ZeroCopy;
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<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<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 using a custom alignment `align`.
65 ///
66 /// Note that this is necessary to do correctly in order to satisfy the
67 /// safety requirements by [`remaining()`].
68 ///
69 /// This is typically not called directly, but rather is implemented by the
70 /// [`ZeroCopy`] derive.
71 ///
72 /// [`remaining()`]: Self::remaining
73 /// [`ZeroCopy`]: derive@crate::ZeroCopy
74 ///
75 /// # Safety
76 ///
77 /// The caller must ensure that the field type `F` is an actual field in
78 /// order in the struct being padded and that `align` matches the argument
79 /// provided to `#[repr(packed)]` (note that empty means 1).
80 #[inline]
81 pub unsafe fn pad_with<F>(&mut self, align: usize)
82 where
83 F: ZeroCopy,
84 {
85 unsafe {
86 let count = buf::padding_to(self.offset, align);
87 // zero out padding.
88 self.data.as_ptr().add(self.offset).write_bytes(0, count);
89 self.offset += count;
90
91 if F::PADDED {
92 let ptr = NonNull::new_unchecked(self.data.as_ptr().add(self.offset));
93 let mut padder = Padder::new(ptr);
94 F::pad(&mut padder);
95 padder.remaining();
96 }
97
98 self.offset += size_of::<F>();
99 }
100 }
101
102 /// Specific method to both pad for a discriminant and load it
103 /// simultaneously for inspection.
104 ///
105 /// # Safety
106 ///
107 /// The caller must ensure that the field type `D` is the actual type of the
108 /// discriminant first in order in the enum being padded and that `D` is a
109 /// primitive that does not contain any interior padding.
110 #[inline]
111 pub unsafe fn pad_discriminant<D>(&mut self) -> D
112 where
113 D: ZeroCopy,
114 {
115 unsafe {
116 let count = buf::padding_to(self.offset, align_of::<D>());
117 // zero out padding.
118 self.data.as_ptr().add(self.offset).write_bytes(0, count);
119 let at = self.offset + count;
120 let value = self.data.as_ptr().add(at).cast::<D>().read_unaligned();
121 self.offset = at + size_of::<D>();
122 value
123 }
124 }
125
126 /// Finish writing the current buffer.
127 ///
128 /// This is typically not called directly, but rather is implemented by the
129 /// [`ZeroCopy`] derive.
130 ///
131 /// [`ZeroCopy`]: derive@crate::ZeroCopy
132 ///
133 /// # Safety
134 ///
135 /// Before calling `remaining()`, the caller must ensure that they've called
136 /// [`pad::<F>()`] *in order* for every field in a struct being serialized
137 /// where `F` is the type of the field. Otherwise we might not have written
138 /// the necessary padding to ensure that all bytes related to the struct are
139 /// initialized. Failure to do so would result in undefined behavior.
140 ///
141 /// Fields which are [`ZeroSized`] can be skipped.
142 ///
143 /// [`pad::<F>()`]: Self::pad
144 /// [`ZeroSized`]: crate::traits::ZeroSized
145 #[inline]
146 pub unsafe fn remaining(self)
147 where
148 T: Sized,
149 {
150 unsafe {
151 let count = size_of::<T>() - self.offset;
152 self.data.as_ptr().add(self.offset).write_bytes(0, count);
153 }
154 }
155
156 /// Finalize remaining padding based on the size of an unsized value.
157 #[inline]
158 pub(crate) unsafe fn remaining_unsized(self, value: &T) {
159 unsafe {
160 let count = size_of_val(value) - self.offset;
161 self.data.as_ptr().add(self.offset).write_bytes(0, count);
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use std::mem::size_of;
169
170 use anyhow::Result;
171
172 use crate::{OwnedBuf, ZeroCopy, buf};
173
174 #[test]
175 fn ensure_padding() -> Result<()> {
176 #[derive(Debug, PartialEq, Eq, ZeroCopy)]
177 #[repr(C)]
178 #[zero_copy(crate)]
179 struct ZeroPadded(u8, u16, u64);
180
181 let padded = ZeroPadded(
182 0x01u8.to_be(),
183 0x0203u16.to_be(),
184 0x0405060708090a0bu64.to_be(),
185 );
186
187 let mut buf = OwnedBuf::new();
188
189 // Note: You're responsible for ensuring that the buffer has enough
190 // capacity.
191 buf.reserve(size_of::<ZeroPadded>());
192
193 // SAFETY: We do not pad beyond known fields and are making sure to
194 // initialize all of the buffer.
195 unsafe {
196 buf::store_unaligned(buf.as_nonnull(), &padded);
197 buf.advance(size_of::<ZeroPadded>());
198 }
199
200 // Note: The bytes are explicitly convert to big-endian encoding above.
201 assert_eq!(
202 buf.as_slice(),
203 &[1, 0, 2, 3, 0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 10, 11]
204 );
205
206 Ok(())
207 }
208}