freenet_stdlib/memory/
buf.rs

1//! Memory buffers to interact with the WASM contracts.
2
3use super::WasmLinearMem;
4
5#[doc(hidden)]
6#[derive(Clone, Copy, Debug)]
7#[repr(C)]
8pub struct BufferBuilder {
9    start: i64,
10    capacity: u32,
11    last_read: i64,
12    last_write: i64,
13}
14
15impl BufferBuilder {
16    /// Return the buffer capacity.
17    pub fn capacity(&self) -> usize {
18        self.capacity as _
19    }
20
21    /// Returns the number of bytes written to the buffer.
22    #[cfg(not(feature = "contract"))]
23    pub fn bytes_written(&self, mem: &WasmLinearMem) -> usize {
24        unsafe {
25            let ptr = compute_ptr(self.last_write as *mut u32, mem);
26            *ptr as usize
27        }
28    }
29
30    #[cfg(feature = "contract")]
31    pub fn bytes_written(&self) -> usize {
32        unsafe { *(self.last_write as *mut u32) as usize }
33    }
34
35    /// Returns the first byte of buffer.
36    pub fn start(&self) -> *mut u8 {
37        self.start as _
38    }
39
40    /// # Safety
41    /// Requires that there are no living references to the current
42    /// underlying buffer or will trigger UB
43    pub unsafe fn update_buffer(&mut self, data: Vec<u8>) {
44        let read_ptr = Box::leak(Box::from_raw(self.last_read as *mut u32));
45        let write_ptr = Box::leak(Box::from_raw(self.last_write as *mut u32));
46
47        // drop previous buffer
48        let prev = Vec::from_raw_parts(self.start as *mut u8, *write_ptr as usize, self.capacity());
49        std::mem::drop(prev);
50
51        // write the new buffer information
52        let new_ptr = data.as_ptr();
53        self.start = new_ptr as i64;
54        self.capacity = data.capacity() as _;
55        *read_ptr = 0;
56        *write_ptr = data.len().saturating_sub(1) as _; // []
57        std::mem::forget(data);
58    }
59
60    /// Returns a wrapped raw pointer to the buffer builder.
61    pub fn to_ptr(self) -> *mut BufferBuilder {
62        Box::into_raw(Box::new(self))
63    }
64}
65
66/// Type of buffer errors.
67#[derive(thiserror::Error, Debug)]
68pub enum Error {
69    /// Insufficient memory while trying to write to the buffer.
70    #[error("insufficient memory, needed {req} bytes but had {free} bytes")]
71    InsufficientMemory {
72        /// Required memory available
73        req: usize,
74        /// Available memory.
75        free: usize,
76    },
77}
78
79/// A live mutable buffer in the WASM linear memory.
80#[derive(Debug)]
81pub struct BufferMut<'instance> {
82    buffer: &'instance mut [u8],
83    /// stores the last read in the buffer
84    read_ptr: &'instance u32,
85    /// stores the last write in the buffer
86    write_ptr: &'instance mut u32,
87    /// A pointer to the underlying builder
88    builder_ptr: *mut BufferBuilder,
89    /// Linear memory pointer and size in bytes
90    mem: WasmLinearMem,
91}
92
93impl<'instance> BufferMut<'instance> {
94    /// Tries to write data into the buffer, after any unread bytes.
95    ///
96    /// Will return an error if there is insufficient space.
97    pub fn write<T>(&mut self, obj: T) -> Result<(), Error>
98    where
99        T: AsRef<[u8]>,
100    {
101        let obj = obj.as_ref();
102        if obj.len() > self.buffer.len() {
103            return Err(Error::InsufficientMemory {
104                req: obj.len(),
105                free: self.buffer.len(),
106            });
107        }
108        let mut last_write = (*self.write_ptr) as usize;
109        let free_right = self.buffer.len() - last_write;
110        if obj.len() <= free_right {
111            let copy_to = &mut self.buffer[last_write..last_write + obj.len()];
112            copy_to.copy_from_slice(obj);
113            last_write += obj.len();
114            *self.write_ptr = last_write as u32;
115            Ok(())
116        } else {
117            Err(Error::InsufficientMemory {
118                req: obj.len(),
119                free: free_right,
120            })
121        }
122    }
123
124    /// Read bytes specified number of bytes from the buffer.
125    ///
126    /// Always reads from the beginning.
127    pub fn read_bytes(&self, len: usize) -> &[u8] {
128        let next_offset = *self.read_ptr as usize;
129        // don't update the read ptr
130        &self.buffer[next_offset..next_offset + len]
131    }
132
133    /// Give ownership of the buffer back to the guest.
134    pub fn shared(self) -> Buffer<'instance> {
135        let BufferMut {
136            builder_ptr, mem, ..
137        } = self;
138        let BuilderInfo {
139            buffer,
140            read_ptr,
141            write_ptr,
142            ..
143        } = from_raw_builder(builder_ptr, mem);
144        Buffer {
145            buffer,
146            read_ptr,
147            write_ptr,
148            builder_ptr,
149            mem,
150        }
151    }
152
153    /// Return the buffer capacity.
154    pub fn capacity(&self) -> usize {
155        unsafe {
156            let p = &*compute_ptr(self.builder_ptr, &self.mem);
157            p.capacity as _
158        }
159    }
160
161    /// # Safety
162    /// The pointer passed come from a previous call to `initiate_buffer` exported function from the contract.
163    pub unsafe fn from_ptr(
164        builder_ptr: *mut BufferBuilder,
165        linear_mem_space: WasmLinearMem,
166    ) -> Self {
167        let BuilderInfo {
168            buffer,
169            read_ptr,
170            write_ptr,
171        } = from_raw_builder(builder_ptr, linear_mem_space);
172        BufferMut {
173            buffer,
174            read_ptr,
175            write_ptr,
176            builder_ptr,
177            mem: linear_mem_space,
178        }
179    }
180
181    /// A pointer to the linear memory address.
182    pub fn ptr(&self) -> *mut BufferBuilder {
183        self.builder_ptr
184    }
185}
186
187#[inline(always)]
188pub(crate) fn compute_ptr<T>(ptr: *mut T, linear_mem_space: &WasmLinearMem) -> *mut T {
189    let mem_start_ptr = linear_mem_space.start_ptr;
190    (mem_start_ptr as isize + ptr as isize) as _
191}
192
193struct BuilderInfo<'instance> {
194    buffer: &'instance mut [u8],
195    read_ptr: &'instance mut u32,
196    write_ptr: &'instance mut u32,
197}
198
199fn from_raw_builder<'a>(builder_ptr: *mut BufferBuilder, mem: WasmLinearMem) -> BuilderInfo<'a> {
200    unsafe {
201        #[cfg(feature = "trace")]
202        {
203            let contract_mem = std::slice::from_raw_parts(mem.start_ptr, mem.size as usize);
204            tracing::trace!(
205                "*mut BufferBuilder <- offset: {}; in mem: {:?}",
206                builder_ptr as usize,
207                &contract_mem[builder_ptr as usize
208                    ..builder_ptr as usize + std::mem::size_of::<BufferBuilder>()]
209            );
210            // use std::{fs::File, io::Write};
211            // let mut f = File::create(std::env::temp_dir().join("dump.mem")).unwrap();
212            // f.write_all(contract_mem).unwrap();
213        }
214
215        let builder_ptr = compute_ptr(builder_ptr, &mem);
216        let buf_builder: &'static mut BufferBuilder = Box::leak(Box::from_raw(builder_ptr));
217        #[cfg(feature = "trace")]
218        {
219            tracing::trace!("buf builder from FFI: {buf_builder:?}");
220        }
221
222        let read_ptr = Box::leak(Box::from_raw(compute_ptr(
223            buf_builder.last_read as *mut u32,
224            &mem,
225        )));
226        let write_ptr = Box::leak(Box::from_raw(compute_ptr(
227            buf_builder.last_write as *mut u32,
228            &mem,
229        )));
230        let buffer_ptr = compute_ptr(buf_builder.start as *mut u8, &mem);
231        let buffer =
232            &mut *std::ptr::slice_from_raw_parts_mut(buffer_ptr, buf_builder.capacity as usize);
233        BuilderInfo {
234            buffer,
235            read_ptr,
236            write_ptr,
237        }
238    }
239}
240
241#[derive(Debug)]
242/// A live buffer in the WASM linear memory.
243pub struct Buffer<'instance> {
244    buffer: &'instance mut [u8],
245    /// stores the last read in the buffer
246    read_ptr: &'instance mut u32,
247    write_ptr: &'instance u32,
248    builder_ptr: *mut BufferBuilder,
249    mem: WasmLinearMem,
250}
251
252impl<'instance> Buffer<'instance> {
253    /// # Safety
254    /// In order for this to be a safe T must be properly aligned and cannot re-use the buffer
255    /// trying to read the same memory region again (that would create more than one copy to
256    /// the same underlying data and break aliasing rules).
257    pub unsafe fn read<T: Sized>(&mut self) -> T {
258        let next_offset = *self.read_ptr as usize;
259        let bytes = &self.buffer[next_offset..next_offset + std::mem::size_of::<T>()];
260        let t = std::ptr::read(bytes.as_ptr() as *const T);
261        *self.read_ptr += std::mem::size_of::<T>() as u32;
262        t
263    }
264
265    /// Read the specified number of bytes from the buffer.
266    pub fn read_bytes(&mut self, len: usize) -> &[u8] {
267        let next_offset = *self.read_ptr as usize;
268        *self.read_ptr += len as u32;
269        &self.buffer[next_offset..next_offset + len]
270    }
271
272    /// Reads all the bytes from the buffer.
273    pub fn read_all(&mut self) -> &[u8] {
274        let next_offset = *self.read_ptr as usize;
275        *self.read_ptr += self.buffer.len() as u32;
276        &self.buffer[next_offset..=*self.write_ptr as usize]
277    }
278
279    /// Give ownership of the buffer back to the guest.
280    ///
281    /// # Safety
282    /// Must guarantee that there are not underlying alive shared references.
283    #[doc(hidden)]
284    pub unsafe fn exclusive(self) -> BufferMut<'instance> {
285        let Buffer {
286            builder_ptr, mem, ..
287        } = self;
288        let BuilderInfo {
289            buffer,
290            read_ptr,
291            write_ptr,
292        } = from_raw_builder(builder_ptr, mem);
293        BufferMut {
294            buffer,
295            read_ptr,
296            write_ptr,
297            builder_ptr,
298            mem,
299        }
300    }
301}
302
303/// Returns the pointer to a new BufferBuilder.
304///
305/// This buffer leaks it's own memory and will only be freed by the runtime when a contract instance is dropped.
306#[doc(hidden)]
307#[allow(non_snake_case)]
308#[no_mangle]
309#[cfg(any(feature = "contract", test))]
310fn __frnt__initiate_buffer(capacity: u32) -> i64 {
311    let buf: Vec<u8> = Vec::with_capacity(capacity as usize);
312    let start = buf.as_ptr() as i64;
313
314    let last_read = Box::into_raw(Box::new(0u32));
315    let last_write = Box::into_raw(Box::new(0u32));
316    let buffer = Box::into_raw(Box::new(BufferBuilder {
317        start,
318        capacity,
319        last_read: last_read as _,
320        last_write: last_write as _,
321    }));
322    #[cfg(feature = "trace")]
323    {
324        tracing::trace!(
325            "new buffer ptr: {:p} -> {} as i64 w/ cap: {capacity}",
326            buf.as_ptr(),
327            start
328        );
329        tracing::trace!(
330            "last read ptr: {last_read:p} -> {} as i64",
331            last_read as i64
332        );
333        tracing::trace!(
334            "last write ptr: {last_write:p} -> {} as i64",
335            last_write as i64
336        );
337        tracing::trace!("buffer ptr: {buffer:p} -> {} as i64", buffer as i64);
338    }
339    std::mem::forget(buf);
340    buffer as i64
341}
342
343#[cfg(all(test, any(unix, windows)))]
344mod test {
345    use super::*;
346    use wasmer::{
347        imports, wat2wasm, AsStoreMut, Cranelift, Function, Instance, Module, Store, TypedFunction,
348    };
349
350    const TEST_MODULE: &str = r#"
351        (module
352            (func $initiate_buffer (import "freenet" "initiate_buffer") (param i32) (result i64))
353            (memory $locutus_mem (export "memory") 20)
354            (export "initiate_buffer" (func $initiate_buffer))
355        )"#;
356
357    fn build_test_mod() -> Result<(Store, Instance), Box<dyn std::error::Error>> {
358        let wasm_bytes = wat2wasm(TEST_MODULE.as_bytes())?;
359        let mut store = Store::new(Cranelift::new());
360        let module = Module::new(&store, wasm_bytes)?;
361
362        let init_buf_fn = Function::new_typed(&mut store, __frnt__initiate_buffer);
363        let imports = imports! {
364            "freenet" => { "initiate_buffer" => init_buf_fn }
365        };
366        let instance = Instance::new(&mut store, &module, &imports).unwrap();
367        Ok((store, instance))
368    }
369
370    fn init_buf(store: &mut impl AsStoreMut, instance: &Instance, size: u32) -> *mut BufferBuilder {
371        let initiate_buffer: TypedFunction<u32, i64> = instance
372            .exports
373            .get_typed_function(&store, "initiate_buffer")
374            .unwrap();
375        initiate_buffer.call(store, size).unwrap() as *mut BufferBuilder
376    }
377
378    #[test]
379    #[ignore]
380    fn read_and_write() -> Result<(), Box<dyn std::error::Error>> {
381        let (mut store, instance) = build_test_mod()?;
382        let mem = instance.exports.get_memory("memory")?.view(&store);
383        let linear_mem = WasmLinearMem {
384            start_ptr: mem.data_ptr() as *const _,
385            size: mem.data_size(),
386        };
387
388        let mut writer =
389            unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
390        writer.write([1u8, 2])?;
391        let mut reader = writer.shared();
392        let r: [u8; 2] = unsafe { reader.read() };
393        assert_eq!(r, [1, 2]);
394
395        let mut writer = unsafe { reader.exclusive() };
396        writer.write([3u8, 4])?;
397        let mut reader = writer.shared();
398        let r: [u8; 2] = unsafe { reader.read() };
399        assert_eq!(r, [3, 4]);
400        Ok(())
401    }
402
403    #[test]
404    #[ignore]
405    fn read_and_write_bytes() -> Result<(), Box<dyn std::error::Error>> {
406        let (mut store, instance) = build_test_mod()?;
407        let mem = instance.exports.get_memory("memory")?.view(&store);
408        let linear_mem = WasmLinearMem {
409            start_ptr: mem.data_ptr() as *const _,
410            size: mem.data_size(),
411        };
412
413        let mut writer =
414            unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
415        writer.write([1u8, 2])?;
416        let mut reader = writer.shared();
417        let r = reader.read_bytes(2);
418        assert_eq!(r, &[1, 2]);
419
420        let mut writer = unsafe { reader.exclusive() };
421        writer.write([3u8, 4])?;
422        let mut reader = writer.shared();
423        let r = reader.read_bytes(2);
424        assert_eq!(r, &[3, 4]);
425        Ok(())
426    }
427
428    #[test]
429    #[ignore]
430    fn update() -> Result<(), Box<dyn std::error::Error>> {
431        let (mut store, instance) = build_test_mod()?;
432        let mem = instance.exports.get_memory("memory")?.view(&store);
433        let linear_mem = WasmLinearMem {
434            start_ptr: mem.data_ptr() as *const _,
435            size: mem.data_size(),
436        };
437
438        let ptr = {
439            let mut writer =
440                unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
441            writer.write([1u8, 2])?;
442            writer.ptr()
443        };
444
445        let writer = unsafe {
446            let builder = &mut *ptr;
447            builder.update_buffer(vec![3, 5, 7]);
448            BufferMut::from_ptr(ptr, linear_mem)
449        };
450        let mut reader = writer.shared();
451        assert_eq!(reader.read_all(), &[3, 5, 7]);
452
453        Ok(())
454    }
455}