use std::io::Write;
use std::marker::PhantomData;
use std::num::NonZeroUsize;
use std::ops::{Add, Rem};
use std::slice::from_raw_parts_mut;
use std::{io, mem, ptr};
use crate::ffi;
use crate::ffi::{vrt_blob, VCL_BLOB, VCL_STRING};
use crate::vcl::VclError::WsOutOfMemory;
use crate::vcl::VclResult;
#[derive(Debug)]
pub struct WsBuffer<'ws, Item, Suffix, Output> {
ws: &'ws mut ffi::ws,
start_items: *mut Item,
unused: &'ws mut [Item],
_output: PhantomData<Output>,
_suffix: PhantomData<Suffix>,
}
pub type WsStrBuffer<'ws> = WsBuffer<'ws, u8, u8, VCL_STRING>;
pub type WsBlobBuffer<'ws> = WsBuffer<'ws, u8, vrt_blob, VCL_BLOB>;
pub type WsTempBuffer<'ws, T> = WsBuffer<'ws, T, (), &'ws [T]>;
impl<Item, Suffix, Output> AsRef<[Item]> for WsBuffer<'_, Item, Suffix, Output> {
fn as_ref(&self) -> &[Item] {
unsafe { std::slice::from_raw_parts(self.start_items, self.len()) }
}
}
impl<Item, Suffix, Output> AsMut<[Item]> for WsBuffer<'_, Item, Suffix, Output> {
fn as_mut(&mut self) -> &mut [Item] {
unsafe { from_raw_parts_mut(self.start_items, self.len()) }
}
}
impl<'ws, Item: Copy, Suffix, Output> WsBuffer<'ws, Item, Suffix, Output> {
pub(crate) unsafe fn new(ws: &'ws mut ffi::ws) -> VclResult<Self> {
let reserved_space = ws.reserve_all() as usize;
let raw_start = get_raw_start(ws);
let items_align = raw_start.align_offset(align_of::<Item>());
let end = raw_start.add(reserved_space);
let suffix_ptr = end.sub(size_of::<Suffix>());
let suffix_ptr = suffix_ptr.sub((suffix_ptr as usize).rem(align_of::<Suffix>()));
debug_assert!(suffix_ptr.is_aligned(), "suffix_ptr is not aligned");
let suffix_size = end.offset_from(suffix_ptr);
let suffix_size = usize::try_from(suffix_size).expect("invalid suffix size");
let items_start = raw_start.add(items_align).cast::<Item>().cast_mut();
debug_assert!(items_start.is_aligned(), "WS buffer is not aligned");
let required = if size_of::<Suffix>() > 0 {
items_align + suffix_size
} else {
items_align + size_of::<Item>()
};
if reserved_space < required {
return Err(WsOutOfMemory(NonZeroUsize::new_unchecked(required)));
}
let len = (reserved_space - items_align - suffix_size) / Self::ITEM_SIZE;
Ok(WsBuffer {
ws,
start_items: items_start,
unused: from_raw_parts_mut(items_start, len),
_output: PhantomData,
_suffix: PhantomData,
})
}
}
impl<Item, Suffix, Output> WsBuffer<'_, Item, Suffix, Output> {
const ITEM_SIZE: usize = size_of::<Item>();
const _ITEM_SIZE_CHECK: () = assert!(
Self::ITEM_SIZE >= size_of::<u8>(),
"size_of::<T>() must be at least 1 byte"
);
unsafe fn release(&mut self, preserve: bool) {
let start = mem::replace(&mut self.start_items, ptr::null_mut());
if !start.is_null() {
let preserve_bytes = if preserve {
usize::try_from(
self.get_suffix_ptr()
.cast::<u8>()
.offset_from(get_raw_start(self.ws)),
)
.expect("used_bytes overflow")
.add(size_of::<Suffix>())
.try_into()
.expect("preserve_bytes overflow")
} else {
0
};
self.ws.release(preserve_bytes);
}
}
fn calc_len(start: *const Item, buffer: &[Item]) -> usize {
unsafe {
let len = buffer.as_ptr().offset_from(start);
debug_assert!(len >= 0, "len={len} is negative");
len as usize
}
}
pub fn len(&self) -> usize {
Self::calc_len(self.start_items, self.unused)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn remaining(&self) -> usize {
self.unused.len()
}
pub fn push(&mut self, item: Item) -> VclResult<()> {
if self.unused.is_empty() {
return Err(WsOutOfMemory(NonZeroUsize::MIN));
}
unsafe {
let end = self.unused.as_mut_ptr();
ptr::write(end, item);
self.unused = from_raw_parts_mut(end.add(1), self.unused.len() - 1);
}
Ok(())
}
pub fn extend_from_slice(&mut self, slice: &[Item]) -> VclResult<()> {
if self.unused.len() < slice.len() {
return Err(WsOutOfMemory(NonZeroUsize::new(slice.len()).unwrap()));
}
unsafe {
let end = self.unused.as_mut_ptr();
ptr::copy_nonoverlapping(slice.as_ptr(), end, slice.len());
self.unused = from_raw_parts_mut(end.add(slice.len()), self.unused.len() - slice.len());
}
Ok(())
}
unsafe fn get_suffix_ptr(&self) -> *mut Suffix {
let ptr_unused = self.unused.as_ptr();
let offset = ptr_unused.align_offset(align_of::<Suffix>());
ptr_unused.add(offset).cast::<Suffix>().cast_mut()
}
}
impl<Output, Suffix> Write for WsBuffer<'_, u8, Suffix, Output> {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.unused.write(data)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<Item, Suffix, Output> Drop for WsBuffer<'_, Item, Suffix, Output> {
fn drop(&mut self) {
unsafe { self.release(false) };
}
}
impl WsStrBuffer<'_> {
pub fn finish(mut self) -> VCL_STRING {
unsafe {
self.unused.as_mut_ptr().write(b'\0');
let result = get_raw_start(self.ws).cast();
self.release(true);
VCL_STRING(result)
}
}
}
impl WsBlobBuffer<'_> {
pub fn finish(mut self) -> VCL_BLOB {
unsafe {
let data = self.as_ref();
let suffix_ptr = self.get_suffix_ptr();
suffix_ptr.write(vrt_blob {
blob: data.as_ptr().cast(),
len: data.len(),
..Default::default()
});
self.release(true);
VCL_BLOB(suffix_ptr)
}
}
}
impl<'ws, T> WsTempBuffer<'ws, T> {
pub fn finish(mut self) -> &'ws [T] {
unsafe {
let data = mem::transmute::<&[T], &'ws [T]>(self.as_ref());
self.release(true);
data
}
}
}
fn get_raw_start(ws: &ffi::ws) -> *const u8 {
ws.f.cast::<u8>()
}
#[cfg(test)]
mod tests {
use std::ffi::{CStr, CString};
use super::*;
use crate::vcl::TestWS;
fn get_cstr(s: &VCL_STRING) -> &CStr {
unsafe { CStr::from_ptr(s.0) }
}
fn round_up_to_usize(number: usize) -> usize {
number.div_ceil(size_of::<usize>()) * size_of::<usize>()
}
fn buf_to_vec(buf: WsBlobBuffer<'_>) -> &'_ [u8] {
let data = buf.finish();
let vrt_blob { blob, len, .. } = unsafe { *(data.0) };
unsafe { std::slice::from_raw_parts(blob.cast::<u8>(), len) }
}
#[test]
fn str_buffer() {
let mut test_ws = TestWS::new(160);
let mut ws = test_ws.workspace();
let mut buf = ws.vcl_string_builder().unwrap();
assert_eq!(buf.remaining(), 159);
buf.write_all(b"0123456789").unwrap();
assert_eq!(buf.remaining(), 149);
assert_eq!(get_cstr(&buf.finish()), c"0123456789");
let mut buf = ws.vcl_string_builder().unwrap();
assert_eq!(buf.remaining(), 160 - round_up_to_usize(10 + 1) - 1);
write!(buf, "this data is ignored").unwrap();
drop(buf);
let mut buf = ws.vcl_string_builder().unwrap();
assert_eq!(buf.remaining(), 160 - round_up_to_usize(10 + 1) - 1);
let fill = vec![b'x'; buf.remaining() - 1];
buf.write_all(&fill).unwrap();
assert_eq!(buf.remaining(), 1);
assert_eq!(
get_cstr(&buf.finish()),
CString::new(fill).unwrap().as_c_str()
);
assert!(matches!(ws.vcl_string_builder(), Err(WsOutOfMemory(_))));
let mut test_ws = TestWS::new(160);
let mut ws = test_ws.workspace();
let mut buf = ws.vcl_string_builder().unwrap();
assert_eq!(buf.remaining(), 159);
let fill = vec![b'x'; buf.remaining()];
buf.write_all(&fill).unwrap();
assert_eq!(buf.remaining(), 0);
assert_eq!(
get_cstr(&buf.finish()),
CString::new(fill).unwrap().as_c_str()
);
assert!(matches!(ws.vcl_string_builder(), Err(WsOutOfMemory(_))));
}
#[test]
fn blob_buffer() {
assert_eq!(size_of::<vrt_blob>(), 24);
assert_eq!(align_of::<vrt_blob>(), 8);
let mut test_ws = TestWS::new(160);
let mut ws = test_ws.workspace();
let mut buf = ws.vcl_blob_builder().unwrap();
assert_eq!(buf.remaining(), 160 - 24);
buf.write_all(b"0123456789").unwrap();
let used = round_up_to_usize(24 + 10);
let data = buf_to_vec(buf);
assert_eq!(data, b"0123456789");
let mut buf = ws.vcl_blob_builder().unwrap();
assert_eq!(buf.remaining(), 160 - used - 24);
write!(buf, "this data is ignored").unwrap();
drop(buf);
assert_eq!(data, b"0123456789");
let mut buf = ws.vcl_blob_builder().unwrap();
assert_eq!(buf.remaining(), 160 - used - 24);
write!(buf, "this data is ignored").unwrap();
}
#[repr(C)]
#[derive(Debug, PartialEq, Clone, Copy)]
struct TestStruct(u16, u8);
#[test]
fn temp_buffer() {
assert_eq!(4, size_of::<TestStruct>());
let mut test_ws = TestWS::new(160);
let mut ws = test_ws.workspace();
let mut buf = ws.slice_builder::<TestStruct>().unwrap();
assert_eq!(buf.remaining(), 160 / 4);
buf.push(TestStruct(1, 2)).unwrap();
let used = round_up_to_usize(4);
let data = buf.finish();
assert_eq!(data, [TestStruct(1, 2)]);
let mut buf = ws.slice_builder().unwrap();
assert_eq!(buf.remaining(), 160 - used);
write!(buf, "this data is ignored").unwrap();
drop(buf);
assert_eq!(data, [TestStruct(1, 2)]);
let mut buf = ws.slice_builder().unwrap();
assert_eq!(buf.remaining(), 160 - used);
buf.extend_from_slice(b"0123456789").unwrap();
assert_eq!(buf.finish(), b"0123456789");
}
}