ntex_bytes/stext.rs
1use crate::{ByteString, Bytes, storage::Storage};
2
3/// Functions that give [`Bytes`] access to externally owned data.
4///
5/// Every function receives the `(addr, len)` pair returned by
6/// [`StorageExt::create`], or by a previous `clone`.
7#[derive(Debug)]
8pub struct StorageVTable {
9 pub(crate) as_ptr: unsafe fn(*const u8, usize) -> *const u8,
10 pub(crate) len: unsafe fn(*const u8, usize) -> usize,
11 pub(crate) clone: unsafe fn(*const u8, usize) -> Option<(*const u8, usize)>,
12 pub(crate) drop: unsafe fn(*const u8, usize),
13}
14
15impl StorageVTable {
16 /// Creates a vtable from its functions.
17 ///
18 /// - `as_ptr` returns a pointer to the start of the data.
19 /// - `len` returns the length of the data in bytes.
20 /// - `clone` returns the `(addr, len)` pair of a new handle to the same
21 /// data, or `None` to make the clone copy the data instead.
22 /// - `drop` releases the handle.
23 ///
24 /// The requirements these functions must meet are listed on
25 /// [`StorageExt`].
26 pub const fn new(
27 as_ptr: unsafe fn(*const u8, usize) -> *const u8,
28 len: unsafe fn(*const u8, usize) -> usize,
29 clone: unsafe fn(*const u8, usize) -> Option<(*const u8, usize)>,
30 drop: unsafe fn(*const u8, usize),
31 ) -> StorageVTable {
32 StorageVTable {
33 as_ptr,
34 len,
35 clone,
36 drop,
37 }
38 }
39}
40
41/// Types that can be used as external storage for [`Bytes`], see
42/// [`Bytes::from_ext`].
43///
44/// # Safety
45///
46/// `Bytes` trusts the values returned by `create` without checking them:
47///
48/// - For the returned `(addr, len)` pair, and for every pair returned by the
49/// vtable's `clone`, `as_ptr` and `len` must describe memory that is
50/// readable for `len` bytes and stays valid and unchanged until `drop` is
51/// called for that pair.
52/// - The vtable functions can be called from any thread.
53///
54/// `Bytes` calls `drop` exactly once for each pair and does not use the pair
55/// afterwards.
56pub unsafe trait StorageExt: Send + Sync {
57 /// Converts the value into an `(addr, len)` pair and the vtable that
58 /// operates on it.
59 fn create(self) -> (*const u8, usize, &'static StorageVTable);
60}
61
62/// External storage that holds valid UTF-8, see [`ByteString::from_ext`].
63///
64/// # Safety
65///
66/// The data exposed through the vtable must be valid UTF-8.
67pub unsafe trait StorageExtStr: StorageExt + Sized {
68 /// Same as [`StorageExt::create`].
69 fn create(self) -> (*const u8, usize, &'static StorageVTable) {
70 StorageExt::create(self)
71 }
72}
73
74impl Bytes {
75 /// Creates a `Bytes` that shares externally owned data without copying.
76 ///
77 /// Cloning and dropping are delegated to the value's vtable.
78 pub fn from_ext<T: StorageExt>(val: T) -> Bytes {
79 let (addr, len, vtable) = val.create();
80
81 Bytes {
82 storage: Storage::from_stext(addr, len, vtable),
83 }
84 }
85}
86
87impl ByteString {
88 /// Creates a `ByteString` that shares externally owned UTF-8 data without
89 /// copying.
90 ///
91 /// Cloning and dropping are delegated to the value's vtable.
92 pub fn from_ext<T: StorageExtStr>(val: T) -> ByteString {
93 let (addr, len, vtable) = StorageExtStr::create(val);
94
95 // SAFETY: `StorageExtStr` guarantees that the externally owned data is valid UTF-8.
96 unsafe {
97 ByteString::from_bytes_unchecked(Bytes {
98 storage: Storage::from_stext(addr, len, vtable),
99 })
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::info::Kind;
108
109 fn as_ptr(addr: *const u8, _: usize) -> *const u8 {
110 addr
111 }
112
113 fn len(_: *const u8, len: usize) -> usize {
114 len
115 }
116
117 fn clone(_: *const u8, _: usize) -> Option<(*const u8, usize)> {
118 None
119 }
120
121 fn drop(addr: *const u8, len: usize) {
122 let ptr = std::ptr::slice_from_raw_parts_mut(addr.cast_mut(), len);
123 // SAFETY: This pair comes from `Box::into_raw` in `Boxed::create` and is released exactly
124 // once.
125 std::mem::drop(unsafe { Box::from_raw(ptr) });
126 }
127
128 struct Boxed(Box<[u8]>);
129
130 // SAFETY: the vtable releases the leaked box exactly once
131 unsafe impl StorageExt for Boxed {
132 fn create(self) -> (*const u8, usize, &'static StorageVTable) {
133 static VTABLE: StorageVTable = StorageVTable::new(as_ptr, len, clone, drop);
134 let len = self.0.len();
135 (Box::into_raw(self.0).cast::<u8>(), len, &VTABLE)
136 }
137 }
138
139 /// STATEMENT: Cloning external storage with no clone callback result falls back to copying into vector storage.
140 ///
141 /// HOW:
142 /// - Wrap a boxed 100-byte payload with `Boxed`, clone it, and check `StExt` becomes an equal `Vec` copy.
143 /// - Format its `StorageVTable` and check the debug output identifies the type.
144 #[test]
145 fn clone_copies_data() {
146 let data = vec![7u8; 100].into_boxed_slice();
147 let b = Bytes::from_ext(Boxed(data));
148 assert_eq!(b.info().kind, Kind::StExt);
149
150 let b2 = b.clone();
151 assert_eq!(b2.info().kind, Kind::Vec);
152 assert_eq!(b, b2);
153
154 let vtable = StorageVTable::new(as_ptr, len, clone, drop);
155 assert!(format!("{vtable:?}").contains("StorageVTable"));
156 }
157}