1use std::any::type_name;
15use std::ffi::{c_char, c_void, CStr};
16use std::fmt::Debug;
17use std::marker::PhantomData;
18use std::mem::{align_of, size_of, transmute, MaybeUninit};
19use std::num::NonZeroUsize;
20use std::ptr;
21use std::slice::from_raw_parts_mut;
22
23use memchr::memchr;
24
25use crate::ffi::{txt, vrt_blob, WS_Allocated, VCL_BLOB, VCL_STRING};
26pub use crate::vcl::ws_str_buffer::WsBlobBuffer;
27pub use crate::vcl::ws_str_buffer::{WsBuffer, WsStrBuffer, WsTempBuffer};
28use crate::vcl::{VclError, VclResult};
29use crate::{ffi, validate_ws};
30
31#[cfg(not(test))]
32impl ffi::ws {
33 pub(crate) unsafe fn alloc(&mut self, size: u32) -> *mut c_void {
34 assert!(size > 0);
35 ffi::WS_Alloc(self, size)
36 }
37 pub(crate) unsafe fn reserve_all(&mut self) -> u32 {
38 ffi::WS_ReserveAll(self)
39 }
40 pub(crate) unsafe fn release(&mut self, len: u32) {
41 ffi::WS_Release(self, len);
42 }
43}
44
45#[cfg(test)]
46impl ffi::ws {
47 const ALIGN: usize = align_of::<*const c_void>();
48 pub(crate) unsafe fn alloc(&mut self, size: u32) -> *mut c_void {
49 let ws = validate_ws(self);
54 assert!(size > 0);
55 let aligned_sz = (size as usize).div_ceil(Self::ALIGN) * Self::ALIGN;
56 if ws.e.offset_from(ws.f) < aligned_sz as isize {
57 ptr::null_mut()
58 } else {
59 let p = ws.f.cast::<c_void>();
60 ws.f = ws.f.add(aligned_sz);
61 assert!(p.is_aligned());
62 p
63 }
64 }
65
66 #[allow(clippy::unused_self)]
67 pub(crate) unsafe fn reserve_all(&mut self) -> u32 {
68 let ws = validate_ws(self);
69 assert!(ws.r.is_null());
70 ws.r = ws.e;
71 ws.e.offset_from(ws.f)
72 .try_into()
73 .expect("workspace free space must fit in u32")
74 }
75
76 #[allow(clippy::unused_self)]
77 pub(crate) unsafe fn release(&mut self, size: u32) {
78 let ws = validate_ws(self);
79 assert!(
80 isize::try_from(size).expect("workspace size must fit in isize")
81 <= ws.e.offset_from(ws.f)
82 );
83 assert!(
84 isize::try_from(size).expect("workspace size must fit in isize")
85 <= ws.r.offset_from(ws.f)
86 );
87 assert!(!ws.r.is_null());
88 let aligned_sz = usize::try_from(size)
89 .expect("workspace size must fit in usize")
90 .div_ceil(Self::ALIGN)
91 * Self::ALIGN;
92 ws.f = ws.f.add(aligned_sz);
93 assert!(ws.f.is_aligned());
94 ws.r = ptr::null_mut::<c_char>();
95 }
96}
97
98#[derive(Debug)]
106pub struct Workspace<'ctx> {
107 pub raw: *mut ffi::ws,
109 _phantom: PhantomData<&'ctx ()>,
110}
111
112impl<'ctx> Workspace<'ctx> {
113 pub(crate) fn from_ptr(raw: *mut ffi::ws) -> Self {
115 assert!(!raw.is_null(), "raw pointer was null");
116 Self {
117 raw,
118 _phantom: PhantomData,
119 }
120 }
121
122 pub unsafe fn alloc(&mut self, size: NonZeroUsize) -> *mut c_void {
127 validate_ws(self.raw).alloc(size.get() as u32)
128 }
129
130 pub fn contains(&self, data: &[u8]) -> bool {
132 unsafe { WS_Allocated(self.raw, data.as_ptr().cast(), data.len() as isize) == 1 }
133 }
134
135 pub fn allocate(
138 &mut self,
139 size: NonZeroUsize,
140 ) -> Result<&'ctx mut [MaybeUninit<u8>], VclError> {
141 let ptr = unsafe { self.alloc(size) };
142 if ptr.is_null() {
143 Err(VclError::WsOutOfMemory(size))
144 } else {
145 Ok(unsafe { from_raw_parts_mut(ptr.cast(), size.get()) })
146 }
147 }
148
149 pub fn allocate_zeroed(&mut self, size: NonZeroUsize) -> Result<&'ctx mut [u8], VclError> {
151 let buf = self.allocate(size)?;
152 unsafe {
153 buf.as_mut_ptr().write_bytes(0, buf.len());
154 Ok(slice_assume_init_mut(buf))
155 }
156 }
157
158 pub(crate) fn copy_value<T>(&mut self, value: T) -> Result<&'ctx mut T, VclError> {
161 let size = NonZeroUsize::new(size_of::<T>())
162 .unwrap_or_else(|| panic!("Type {} has sizeof=0", type_name::<T>()));
163
164 let val = unsafe { self.alloc(size).cast::<T>().as_mut() };
165 let val = val.ok_or(VclError::WsOutOfMemory(size))?;
166 *val = value;
167 Ok(val)
168 }
169
170 fn copy_bytes(&mut self, src: impl AsRef<[u8]>) -> Result<&'ctx [u8], VclError> {
172 let src = src.as_ref();
176 let Some(len) = NonZeroUsize::new(src.len()) else {
177 Err(VclError::CStr(c"Unable to allocate 0 bytes in a Workspace"))?
178 };
179 let dest = self.allocate(len)?;
180 dest.copy_from_slice(maybe_uninit(src));
181 Ok(unsafe { slice_assume_init_mut(dest) })
182 }
183
184 pub fn copy_blob(&mut self, value: impl AsRef<[u8]>) -> Result<VCL_BLOB, VclError> {
186 let buf = self.copy_bytes(value)?;
187 let blob = self.copy_value(vrt_blob {
188 magic: ffi::VRT_BLOB_MAGIC,
189 blob: ptr::from_ref(buf).cast::<c_void>(),
190 len: buf.len(),
191 ..Default::default()
192 })?;
193 Ok(VCL_BLOB(ptr::from_ref(blob)))
194 }
195
196 pub fn copy_txt(&mut self, value: impl AsRef<CStr>) -> Result<txt, VclError> {
198 let dest = self.copy_bytes(value.as_ref().to_bytes_with_nul())?;
199 Ok(bytes_with_nul_to_txt(dest))
200 }
201
202 pub fn copy_cstr(&mut self, value: impl AsRef<CStr>) -> Result<VCL_STRING, VclError> {
204 Ok(VCL_STRING(self.copy_txt(value)?.b))
205 }
206
207 pub fn copy_bytes_with_null(&mut self, src: impl AsRef<[u8]>) -> Result<txt, VclError> {
211 let src = src.as_ref();
212 match memchr(0, src) {
213 Some(pos) if pos + 1 == src.len() => {
214 self.copy_txt(unsafe { CStr::from_bytes_with_nul_unchecked(src) })
216 }
217 Some(_) => Err(VclError::CStr(c"NULL byte found in the source string")),
218 None => {
219 let len = src.len();
222 let dest = self.allocate(unsafe { NonZeroUsize::new_unchecked(len + 1) })?;
223 dest[..len].copy_from_slice(maybe_uninit(src));
224 dest[len].write(b'\0');
225 let dest = unsafe { slice_assume_init_mut(dest) };
226 Ok(bytes_with_nul_to_txt(dest))
227 }
228 }
229 }
230
231 pub fn vcl_string_builder(&mut self) -> VclResult<WsStrBuffer<'ctx>> {
236 unsafe { WsStrBuffer::new(validate_ws(self.raw)) }
237 }
238
239 pub fn vcl_blob_builder(&mut self) -> VclResult<WsBlobBuffer<'ctx>> {
242 unsafe { WsBlobBuffer::new(validate_ws(self.raw)) }
243 }
244
245 pub fn slice_builder<T: Copy>(&mut self) -> VclResult<WsTempBuffer<'ctx, T>> {
251 unsafe { WsTempBuffer::new(validate_ws(self.raw)) }
252 }
253}
254
255fn maybe_uninit(value: &[u8]) -> &[MaybeUninit<u8>] {
257 unsafe {
260 #[expect(clippy::transmute_ptr_to_ptr)]
261 transmute(value)
262 }
263}
264
265unsafe fn slice_assume_init_mut(value: &mut [MaybeUninit<u8>]) -> &mut [u8] {
267 &mut *(ptr::from_mut::<[MaybeUninit<u8>]>(value) as *mut [u8])
270}
271
272fn bytes_with_nul_to_txt(buf: &[u8]) -> txt {
274 txt::from_cstr(unsafe { CStr::from_bytes_with_nul_unchecked(buf) })
275}
276
277#[derive(Debug)]
282pub struct TestWS {
283 c_ws: ffi::ws,
284 #[expect(dead_code)]
285 space: Vec<c_char>,
286}
287
288impl TestWS {
289 pub fn new(sz: usize) -> Self {
291 let al = align_of::<*const c_void>();
292 let aligned_sz = (sz / al) * al;
293 let mut space: Vec<c_char> = vec![0; sz];
294 let s = space.as_mut_ptr();
295 assert!(s.is_aligned());
296 assert!(unsafe { s.add(aligned_sz).is_aligned() });
297 Self {
298 c_ws: ffi::ws {
299 magic: ffi::WS_MAGIC,
300 id: ['t' as c_char, 's' as c_char, 't' as c_char, '\0' as c_char],
301 s,
302 f: s,
303 r: ptr::null_mut(),
304 e: unsafe { s.add(aligned_sz) },
305 },
306 space,
307 }
308 }
309
310 pub fn as_ptr(&mut self) -> *mut ffi::ws {
313 ptr::from_mut::<ffi::ws>(&mut self.c_ws)
314 }
315
316 pub fn workspace(&mut self) -> Workspace<'_> {
318 Workspace::from_ptr(self.as_ptr())
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use std::num::NonZero;
325
326 use super::*;
327
328 #[test]
329 fn ws_test_alloc() {
330 let mut test_ws = TestWS::new(160);
331 let mut ws = test_ws.workspace();
332 for _ in 0..10 {
333 unsafe {
334 assert!(!ws
335 .alloc(NonZero::new(16).expect("16 is non-zero"))
336 .is_null());
337 }
338 }
339 unsafe {
340 assert!(ws.alloc(NonZero::new(1).expect("1 is non-zero")).is_null());
341 }
342 }
343}