Skip to main content

varnish_sys/vcl/
ws_str_buffer.rs

1use std::io::Write;
2use std::marker::PhantomData;
3use std::num::NonZeroUsize;
4use std::ops::{Add, Rem};
5use std::slice::from_raw_parts_mut;
6use std::{io, mem, ptr};
7
8use crate::ffi;
9use crate::ffi::{vrt_blob, VCL_BLOB, VCL_STRING};
10use crate::vcl::VclError::WsOutOfMemory;
11use crate::vcl::VclResult;
12
13/// The free region of the workspace that functions as a "resizable" vector, up to the end of the workspace.
14/// The buffer must be finalized using `finish()` to avoid being reclaimed when dropped.
15#[derive(Debug)]
16pub struct WsBuffer<'ws, Item, Suffix, Output> {
17    /// The workspace pointer, used to release the workspace
18    ws: &'ws mut ffi::ws,
19    /// The start of the writable buffer, aligned to the content type. Will set to null when finished.
20    start_items: *mut Item,
21    /// The reserved buffer will move its start as we write to it, thus becoming "used"
22    unused: &'ws mut [Item],
23
24    _output: PhantomData<Output>,
25    _suffix: PhantomData<Suffix>,
26}
27
28pub type WsStrBuffer<'ws> = WsBuffer<'ws, u8, u8, VCL_STRING>;
29pub type WsBlobBuffer<'ws> = WsBuffer<'ws, u8, vrt_blob, VCL_BLOB>;
30pub type WsTempBuffer<'ws, T> = WsBuffer<'ws, T, (), &'ws [T]>;
31
32impl<Item, Suffix, Output> AsRef<[Item]> for WsBuffer<'_, Item, Suffix, Output> {
33    /// Get the written data as a slice
34    fn as_ref(&self) -> &[Item] {
35        unsafe { std::slice::from_raw_parts(self.start_items, self.len()) }
36    }
37}
38
39impl<Item, Suffix, Output> AsMut<[Item]> for WsBuffer<'_, Item, Suffix, Output> {
40    /// Get the writable buffer as a slice
41    fn as_mut(&mut self) -> &mut [Item] {
42        unsafe { from_raw_parts_mut(self.start_items, self.len()) }
43    }
44}
45
46impl<'ws, Item: Copy, Suffix, Output> WsBuffer<'ws, Item, Suffix, Output> {
47    /// Internal method to create a new buffer
48    pub(crate) unsafe fn new(ws: &'ws mut ffi::ws) -> VclResult<Self> {
49        let reserved_space = ws.reserve_all() as usize;
50        let raw_start = get_raw_start(ws);
51
52        // Compute the size of the alignment, usually compile-time zero, but just in case
53        let items_align = raw_start.align_offset(align_of::<Item>());
54
55        // Computes how many bytes need to be reserved for the suffix
56        let end = raw_start.add(reserved_space);
57        // last byte where Suffix can start and fit entirely,
58        // rounded down to the alignment
59        let suffix_ptr = end.sub(size_of::<Suffix>());
60        let suffix_ptr = suffix_ptr.sub((suffix_ptr as usize).rem(align_of::<Suffix>()));
61        debug_assert!(suffix_ptr.is_aligned(), "suffix_ptr is not aligned");
62        let suffix_size = end.offset_from(suffix_ptr);
63        let suffix_size = usize::try_from(suffix_size).expect("invalid suffix size");
64
65        let items_start = raw_start.add(items_align).cast::<Item>().cast_mut();
66        debug_assert!(items_start.is_aligned(), "WS buffer is not aligned");
67
68        // Minimum space we need to function properly. A zero-length null-terminated C-string is valid.
69        let required = if size_of::<Suffix>() > 0 {
70            // With the suffix, it's enough to have space for the suffix itself, i.e. `\0`
71            items_align + suffix_size
72        } else {
73            // Without the suffix, require space for at least one item
74            items_align + size_of::<Item>()
75        };
76
77        if reserved_space < required {
78            return Err(WsOutOfMemory(NonZeroUsize::new_unchecked(required)));
79        }
80
81        let len = (reserved_space - items_align - suffix_size) / Self::ITEM_SIZE;
82
83        Ok(WsBuffer {
84            ws,
85            start_items: items_start,
86            unused: from_raw_parts_mut(items_start, len),
87            _output: PhantomData,
88            _suffix: PhantomData,
89        })
90    }
91}
92
93impl<Item, Suffix, Output> WsBuffer<'_, Item, Suffix, Output> {
94    const ITEM_SIZE: usize = size_of::<Item>();
95    const _ITEM_SIZE_CHECK: () = assert!(
96        Self::ITEM_SIZE >= size_of::<u8>(),
97        "size_of::<T>() must be at least 1 byte"
98    );
99
100    /// Release the workspace, reclaiming the memory except for the written data.
101    ///
102    /// Safety:
103    ///     This must be the last call before dropping. It may be called multiple times.
104    unsafe fn release(&mut self, preserve: bool) {
105        let start = mem::replace(&mut self.start_items, ptr::null_mut());
106        if !start.is_null() {
107            let preserve_bytes = if preserve {
108                // compute total bytes used by the buffer
109                usize::try_from(
110                    self.get_suffix_ptr()
111                        .cast::<u8>()
112                        .offset_from(get_raw_start(self.ws)),
113                )
114                .expect("used_bytes overflow")
115                .add(size_of::<Suffix>())
116                .try_into()
117                .expect("preserve_bytes overflow")
118            } else {
119                0
120            };
121
122            self.ws.release(preserve_bytes);
123        }
124    }
125
126    /// Internal method to calculate the length of the written data
127    fn calc_len(start: *const Item, buffer: &[Item]) -> usize {
128        unsafe {
129            let len = buffer.as_ptr().offset_from(start);
130            debug_assert!(len >= 0, "len={len} is negative");
131            len as usize
132        }
133    }
134
135    /// Get the length of the written data
136    pub fn len(&self) -> usize {
137        Self::calc_len(self.start_items, self.unused)
138    }
139
140    /// Check if anything has been written to the buffer
141    pub fn is_empty(&self) -> bool {
142        self.len() == 0
143    }
144
145    /// Get the remaining capacity of the buffer
146    pub fn remaining(&self) -> usize {
147        self.unused.len()
148    }
149
150    pub fn push(&mut self, item: Item) -> VclResult<()> {
151        if self.unused.is_empty() {
152            return Err(WsOutOfMemory(NonZeroUsize::MIN));
153        }
154        unsafe {
155            let end = self.unused.as_mut_ptr();
156            ptr::write(end, item);
157            self.unused = from_raw_parts_mut(end.add(1), self.unused.len() - 1);
158        }
159        Ok(())
160    }
161
162    pub fn extend_from_slice(&mut self, slice: &[Item]) -> VclResult<()> {
163        if self.unused.len() < slice.len() {
164            return Err(WsOutOfMemory(
165                NonZeroUsize::new(slice.len())
166                    .expect("slice.len() is non-zero since it exceeds unused.len()"),
167            ));
168        }
169        unsafe {
170            let end = self.unused.as_mut_ptr();
171            ptr::copy_nonoverlapping(slice.as_ptr(), end, slice.len());
172            self.unused = from_raw_parts_mut(end.add(slice.len()), self.unused.len() - slice.len());
173        }
174        Ok(())
175    }
176
177    /// Get the pointer to the allowed suffix location right after currently used data.
178    unsafe fn get_suffix_ptr(&self) -> *mut Suffix {
179        let ptr_unused = self.unused.as_ptr();
180        let offset = ptr_unused.align_offset(align_of::<Suffix>());
181        ptr_unused.add(offset).cast::<Suffix>().cast_mut()
182    }
183}
184
185impl<Output, Suffix> Write for WsBuffer<'_, u8, Suffix, Output> {
186    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
187        self.unused.write(data)
188    }
189
190    fn flush(&mut self) -> io::Result<()> {
191        Ok(())
192    }
193}
194
195impl<Item, Suffix, Output> Drop for WsBuffer<'_, Item, Suffix, Output> {
196    /// Ignore all the write commands, reclaiming the workspace memory
197    fn drop(&mut self) {
198        unsafe { self.release(false) };
199    }
200}
201
202impl WsStrBuffer<'_> {
203    /// Finish writing to the [`WsBuffer`], returning the allocated [`VCL_STRING`].
204    pub fn finish(mut self) -> VCL_STRING {
205        unsafe {
206            // SAFETY:
207            // Since we reserved one extra byte for the NUL terminator,
208            // we can force write the NUL terminator even past the end of the slice.
209            self.unused.as_mut_ptr().write(b'\0');
210
211            // Must get the result before releasing the workspace, as it updates the pointer
212            let result = get_raw_start(self.ws).cast();
213
214            // Reserve written data including the NUL terminator, and release the rest
215            self.release(true);
216
217            VCL_STRING(result)
218        }
219    }
220}
221
222impl WsBlobBuffer<'_> {
223    /// Finish writing to the [`WsBlobBuffer`], returning the allocated [`VCL_BLOB`].
224    pub fn finish(mut self) -> VCL_BLOB {
225        unsafe {
226            let data = self.as_ref();
227            // Create vrt_blob suffix right after the data
228            let suffix_ptr = self.get_suffix_ptr();
229            suffix_ptr.write(vrt_blob {
230                blob: data.as_ptr().cast(),
231                len: data.len(),
232                ..Default::default()
233            });
234
235            self.release(true);
236
237            VCL_BLOB(suffix_ptr)
238        }
239    }
240}
241
242impl<'ws, T> WsTempBuffer<'ws, T> {
243    /// Finish writing to the [`WsTempBuffer`], returning the allocated `&'ws [T]`.
244    pub fn finish(mut self) -> &'ws [T] {
245        unsafe {
246            // force lifetime to be 'ws because we are dropping self
247            let data = mem::transmute::<&[T], &'ws [T]>(self.as_ref());
248            self.release(true);
249            data
250        }
251    }
252}
253
254fn get_raw_start(ws: &ffi::ws) -> *const u8 {
255    ws.f.cast::<u8>()
256}
257
258#[cfg(test)]
259mod tests {
260    use std::ffi::{CStr, CString};
261
262    use super::*;
263    use crate::vcl::TestWS;
264
265    fn get_cstr(s: &VCL_STRING) -> &CStr {
266        unsafe { CStr::from_ptr(s.0) }
267    }
268
269    fn round_up_to_usize(number: usize) -> usize {
270        number.div_ceil(size_of::<usize>()) * size_of::<usize>()
271    }
272
273    fn buf_to_vec(buf: WsBlobBuffer<'_>) -> &'_ [u8] {
274        let data = buf.finish();
275        let vrt_blob { blob, len, .. } = unsafe { *(data.0) };
276        unsafe { std::slice::from_raw_parts(blob.cast::<u8>(), len) }
277    }
278
279    #[test]
280    fn str_buffer() {
281        let mut test_ws = TestWS::new(160);
282        let mut ws = test_ws.workspace();
283
284        // first buffer call gets all available space
285        let mut buf = ws
286            .vcl_string_builder()
287            .expect("workspace must have enough space");
288        assert_eq!(buf.remaining(), 159);
289        buf.write_all(b"0123456789").expect("write must succeed");
290        assert_eq!(buf.remaining(), 149);
291        // saving 10 bytes + nul
292        assert_eq!(get_cstr(&buf.finish()), c"0123456789");
293
294        let mut buf = ws
295            .vcl_string_builder()
296            .expect("workspace must have enough space");
297        assert_eq!(buf.remaining(), 160 - round_up_to_usize(10 + 1) - 1);
298        write!(buf, "this data is ignored").expect("write must succeed");
299        // the ReservedBuf goes out of scope without a call to .finish()
300        // so now data is fully allocated
301        drop(buf);
302
303        let mut buf = ws
304            .vcl_string_builder()
305            .expect("workspace must have enough space");
306        assert_eq!(buf.remaining(), 160 - round_up_to_usize(10 + 1) - 1);
307        let fill = vec![b'x'; buf.remaining() - 1];
308        buf.write_all(&fill).expect("write must succeed");
309        assert_eq!(buf.remaining(), 1);
310        assert_eq!(
311            get_cstr(&buf.finish()),
312            CString::new(fill)
313                .expect("fill bytes must not contain null bytes")
314                .as_c_str()
315        );
316
317        assert!(matches!(ws.vcl_string_builder(), Err(WsOutOfMemory(_))));
318
319        // Will to the end of the buffer
320        let mut test_ws = TestWS::new(160);
321        let mut ws = test_ws.workspace();
322        let mut buf = ws
323            .vcl_string_builder()
324            .expect("workspace must have enough space");
325        assert_eq!(buf.remaining(), 159);
326        let fill = vec![b'x'; buf.remaining()];
327        buf.write_all(&fill).expect("write must succeed");
328        assert_eq!(buf.remaining(), 0);
329        assert_eq!(
330            get_cstr(&buf.finish()),
331            CString::new(fill)
332                .expect("fill bytes must not contain null bytes")
333                .as_c_str()
334        );
335
336        assert!(matches!(ws.vcl_string_builder(), Err(WsOutOfMemory(_))));
337    }
338
339    #[test]
340    fn blob_buffer() {
341        assert_eq!(size_of::<vrt_blob>(), 24);
342        assert_eq!(align_of::<vrt_blob>(), 8);
343
344        // init a workspace
345        let mut test_ws = TestWS::new(160);
346        let mut ws = test_ws.workspace();
347
348        // first buffer call gets all available space
349        let mut buf = ws
350            .vcl_blob_builder()
351            .expect("workspace must have enough space");
352        assert_eq!(buf.remaining(), 160 - 24);
353        buf.write_all(b"0123456789").expect("write must succeed");
354        let used = round_up_to_usize(24 + 10);
355        let data = buf_to_vec(buf);
356        assert_eq!(data, b"0123456789");
357
358        // second reservation without (header + )
359        let mut buf = ws
360            .vcl_blob_builder()
361            .expect("workspace must have enough space");
362        assert_eq!(buf.remaining(), 160 - used - 24);
363        write!(buf, "this data is ignored").expect("write must succeed");
364        drop(buf);
365
366        // validate no data corruption
367        assert_eq!(data, b"0123456789");
368
369        // the ReservedBuf goes out of scope without a call to .finish()
370        // so now data is fully allocated
371        let mut buf = ws
372            .vcl_blob_builder()
373            .expect("workspace must have enough space");
374        assert_eq!(buf.remaining(), 160 - used - 24);
375        write!(buf, "this data is ignored").expect("write must succeed");
376    }
377
378    #[repr(C)]
379    #[derive(Debug, PartialEq, Clone, Copy)]
380    struct TestStruct(u16, u8);
381
382    #[test]
383    fn temp_buffer() {
384        assert_eq!(4, size_of::<TestStruct>());
385        let mut test_ws = TestWS::new(160);
386        let mut ws = test_ws.workspace();
387
388        // first buffer call gets all available space
389        let mut buf = ws
390            .slice_builder::<TestStruct>()
391            .expect("workspace must have enough space");
392        assert_eq!(buf.remaining(), 160 / 4);
393        buf.push(TestStruct(1, 2)).expect("push must succeed");
394        let used = round_up_to_usize(4);
395        let data = buf.finish();
396        assert_eq!(data, [TestStruct(1, 2)]);
397
398        // second reservation without (header + )
399        let mut buf = ws
400            .slice_builder()
401            .expect("workspace must have enough space");
402        assert_eq!(buf.remaining(), 160 - used);
403        write!(buf, "this data is ignored").expect("write must succeed");
404        drop(buf);
405
406        // validate no data corruption
407        assert_eq!(data, [TestStruct(1, 2)]);
408
409        // buf went out of scope without a call to .finish(), discarding it
410        let mut buf = ws
411            .slice_builder()
412            .expect("workspace must have enough space");
413        assert_eq!(buf.remaining(), 160 - used);
414        buf.extend_from_slice(b"0123456789")
415            .expect("extend_from_slice must succeed");
416        assert_eq!(buf.finish(), b"0123456789");
417    }
418}