use std::any::type_name;
use std::ffi::{c_char, c_void, CStr};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem::{align_of, size_of, transmute, MaybeUninit};
use std::num::NonZeroUsize;
use std::ptr;
use std::slice::from_raw_parts_mut;
use memchr::memchr;
use crate::ffi::{txt, vrt_blob, WS_Allocated, VCL_BLOB, VCL_STRING};
pub use crate::vcl::ws_str_buffer::WsBlobBuffer;
pub use crate::vcl::ws_str_buffer::{WsBuffer, WsStrBuffer, WsTempBuffer};
use crate::vcl::{VclError, VclResult};
use crate::{ffi, validate_ws};
#[cfg(not(test))]
impl ffi::ws {
pub(crate) unsafe fn alloc(&mut self, size: u32) -> *mut c_void {
assert!(size > 0);
ffi::WS_Alloc(self, size)
}
pub(crate) unsafe fn reserve_all(&mut self) -> u32 {
ffi::WS_ReserveAll(self)
}
pub(crate) unsafe fn release(&mut self, len: u32) {
ffi::WS_Release(self, len);
}
}
#[cfg(test)]
impl ffi::ws {
const ALIGN: usize = align_of::<*const c_void>();
pub(crate) unsafe fn alloc(&mut self, size: u32) -> *mut c_void {
let ws = validate_ws(self);
assert!(size > 0);
let aligned_sz = (size as usize).div_ceil(Self::ALIGN) * Self::ALIGN;
if ws.e.offset_from(ws.f) < aligned_sz as isize {
ptr::null_mut()
} else {
let p = ws.f.cast::<c_void>();
ws.f = ws.f.add(aligned_sz);
assert!(p.is_aligned());
p
}
}
#[allow(clippy::unused_self)]
pub(crate) unsafe fn reserve_all(&mut self) -> u32 {
let ws = validate_ws(self);
assert!(ws.r.is_null());
ws.r = ws.e;
ws.e.offset_from(ws.f).try_into().unwrap()
}
#[allow(clippy::unused_self)]
pub(crate) unsafe fn release(&mut self, size: u32) {
let ws = validate_ws(self);
assert!(isize::try_from(size).unwrap() <= ws.e.offset_from(ws.f));
assert!(isize::try_from(size).unwrap() <= ws.r.offset_from(ws.f));
assert!(!ws.r.is_null());
let aligned_sz = usize::try_from(size).unwrap().div_ceil(Self::ALIGN) * Self::ALIGN;
ws.f = ws.f.add(aligned_sz);
assert!(ws.f.is_aligned());
ws.r = ptr::null_mut::<c_char>();
}
}
#[derive(Debug)]
pub struct Workspace<'ctx> {
pub raw: *mut ffi::ws,
_phantom: PhantomData<&'ctx ()>,
}
impl<'ctx> Workspace<'ctx> {
pub(crate) fn from_ptr(raw: *mut ffi::ws) -> Self {
assert!(!raw.is_null(), "raw pointer was null");
Self {
raw,
_phantom: PhantomData,
}
}
pub unsafe fn alloc(&mut self, size: NonZeroUsize) -> *mut c_void {
validate_ws(self.raw).alloc(size.get() as u32)
}
pub fn contains(&self, data: &[u8]) -> bool {
unsafe { WS_Allocated(self.raw, data.as_ptr().cast(), data.len() as isize) == 1 }
}
pub fn allocate(
&mut self,
size: NonZeroUsize,
) -> Result<&'ctx mut [MaybeUninit<u8>], VclError> {
let ptr = unsafe { self.alloc(size) };
if ptr.is_null() {
Err(VclError::WsOutOfMemory(size))
} else {
Ok(unsafe { from_raw_parts_mut(ptr.cast(), size.get()) })
}
}
pub fn allocate_zeroed(&mut self, size: NonZeroUsize) -> Result<&'ctx mut [u8], VclError> {
let buf = self.allocate(size)?;
unsafe {
buf.as_mut_ptr().write_bytes(0, buf.len());
Ok(slice_assume_init_mut(buf))
}
}
pub(crate) fn copy_value<T>(&mut self, value: T) -> Result<&'ctx mut T, VclError> {
let size = NonZeroUsize::new(size_of::<T>())
.unwrap_or_else(|| panic!("Type {} has sizeof=0", type_name::<T>()));
let val = unsafe { self.alloc(size).cast::<T>().as_mut() };
let val = val.ok_or(VclError::WsOutOfMemory(size))?;
*val = value;
Ok(val)
}
fn copy_bytes(&mut self, src: impl AsRef<[u8]>) -> Result<&'ctx [u8], VclError> {
let src = src.as_ref();
let Some(len) = NonZeroUsize::new(src.len()) else {
Err(VclError::CStr(c"Unable to allocate 0 bytes in a Workspace"))?
};
let dest = self.allocate(len)?;
dest.copy_from_slice(maybe_uninit(src));
Ok(unsafe { slice_assume_init_mut(dest) })
}
pub fn copy_blob(&mut self, value: impl AsRef<[u8]>) -> Result<VCL_BLOB, VclError> {
let buf = self.copy_bytes(value)?;
let blob = self.copy_value(vrt_blob {
blob: ptr::from_ref(buf).cast::<c_void>(),
len: buf.len(),
..Default::default()
})?;
Ok(VCL_BLOB(ptr::from_ref(blob)))
}
pub fn copy_txt(&mut self, value: impl AsRef<CStr>) -> Result<txt, VclError> {
let dest = self.copy_bytes(value.as_ref().to_bytes_with_nul())?;
Ok(bytes_with_nul_to_txt(dest))
}
pub fn copy_cstr(&mut self, value: impl AsRef<CStr>) -> Result<VCL_STRING, VclError> {
Ok(VCL_STRING(self.copy_txt(value)?.b))
}
pub fn copy_bytes_with_null(&mut self, src: impl AsRef<[u8]>) -> Result<txt, VclError> {
let src = src.as_ref();
match memchr(0, src) {
Some(pos) if pos + 1 == src.len() => {
self.copy_txt(unsafe { CStr::from_bytes_with_nul_unchecked(src) })
}
Some(_) => Err(VclError::CStr(c"NULL byte found in the source string")),
None => {
let len = src.len();
let dest = self.allocate(unsafe { NonZeroUsize::new_unchecked(len + 1) })?;
dest[..len].copy_from_slice(maybe_uninit(src));
dest[len].write(b'\0');
let dest = unsafe { slice_assume_init_mut(dest) };
Ok(bytes_with_nul_to_txt(dest))
}
}
}
pub fn vcl_string_builder(&mut self) -> VclResult<WsStrBuffer<'ctx>> {
unsafe { WsStrBuffer::new(validate_ws(self.raw)) }
}
pub fn vcl_blob_builder(&mut self) -> VclResult<WsBlobBuffer<'ctx>> {
unsafe { WsBlobBuffer::new(validate_ws(self.raw)) }
}
pub fn slice_builder<T: Copy>(&mut self) -> VclResult<WsTempBuffer<'ctx, T>> {
unsafe { WsTempBuffer::new(validate_ws(self.raw)) }
}
}
fn maybe_uninit(value: &[u8]) -> &[MaybeUninit<u8>] {
unsafe {
#[expect(clippy::transmute_ptr_to_ptr)]
transmute(value)
}
}
unsafe fn slice_assume_init_mut(value: &mut [MaybeUninit<u8>]) -> &mut [u8] {
&mut *(ptr::from_mut::<[MaybeUninit<u8>]>(value) as *mut [u8])
}
fn bytes_with_nul_to_txt(buf: &[u8]) -> txt {
txt::from_cstr(unsafe { CStr::from_bytes_with_nul_unchecked(buf) })
}
#[derive(Debug)]
pub struct TestWS {
c_ws: ffi::ws,
#[expect(dead_code)]
space: Vec<c_char>,
}
impl TestWS {
pub fn new(sz: usize) -> Self {
let al = align_of::<*const c_void>();
let aligned_sz = (sz / al) * al;
let mut space: Vec<c_char> = vec![0; sz];
let s = space.as_mut_ptr();
assert!(s.is_aligned());
assert!(unsafe { s.add(aligned_sz).is_aligned() });
Self {
c_ws: ffi::ws {
magic: ffi::WS_MAGIC,
id: ['t' as c_char, 's' as c_char, 't' as c_char, '\0' as c_char],
s,
f: s,
r: ptr::null_mut(),
e: unsafe { s.add(aligned_sz) },
},
space,
}
}
pub fn as_ptr(&mut self) -> *mut ffi::ws {
ptr::from_mut::<ffi::ws>(&mut self.c_ws)
}
pub fn workspace(&mut self) -> Workspace<'_> {
Workspace::from_ptr(self.as_ptr())
}
}
#[cfg(test)]
mod tests {
use std::num::NonZero;
use super::*;
#[test]
fn ws_test_alloc() {
let mut test_ws = TestWS::new(160);
let mut ws = test_ws.workspace();
for _ in 0..10 {
unsafe {
assert!(!ws.alloc(NonZero::new(16).unwrap()).is_null());
}
}
unsafe {
assert!(ws.alloc(NonZero::new(1).unwrap()).is_null());
}
}
}