Skip to main content

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
187impl std::io::Write for BufferMut<'_> {
188    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
189        let last_write = (*self.write_ptr) as usize;
190        let free = self.buffer.len() - last_write;
191        let n = buf.len().min(free);
192        if n == 0 && !buf.is_empty() {
193            return Err(std::io::Error::new(
194                std::io::ErrorKind::WriteZero,
195                "buffer full",
196            ));
197        }
198        self.buffer[last_write..last_write + n].copy_from_slice(&buf[..n]);
199        *self.write_ptr = (last_write + n) as u32;
200        Ok(n)
201    }
202
203    fn flush(&mut self) -> std::io::Result<()> {
204        Ok(())
205    }
206}
207
208#[inline(always)]
209pub(crate) fn compute_ptr<T>(ptr: *mut T, linear_mem_space: &WasmLinearMem) -> *mut T {
210    let mem_start_ptr = linear_mem_space.start_ptr;
211    (mem_start_ptr as isize + ptr as isize) as _
212}
213
214struct BuilderInfo<'instance> {
215    buffer: &'instance mut [u8],
216    read_ptr: &'instance mut u32,
217    write_ptr: &'instance mut u32,
218}
219
220fn from_raw_builder<'a>(builder_ptr: *mut BufferBuilder, mem: WasmLinearMem) -> BuilderInfo<'a> {
221    unsafe {
222        #[cfg(feature = "trace")]
223        {
224            if !mem.start_ptr.is_null() && mem.size > 0 {
225                let contract_mem = std::slice::from_raw_parts(mem.start_ptr, mem.size as usize);
226                tracing::trace!(
227                    "*mut BufferBuilder <- offset: {}; in mem: {:?}",
228                    builder_ptr as usize,
229                    &contract_mem[builder_ptr as usize
230                        ..builder_ptr as usize + std::mem::size_of::<BufferBuilder>()]
231                );
232            }
233            // use std::{fs::File, io::Write};
234            // let mut f = File::create(std::env::temp_dir().join("dump.mem")).unwrap();
235            // f.write_all(contract_mem).unwrap();
236        }
237
238        let builder_ptr = compute_ptr(builder_ptr, &mem);
239        let buf_builder: &'static mut BufferBuilder = Box::leak(Box::from_raw(builder_ptr));
240        #[cfg(feature = "trace")]
241        {
242            tracing::trace!("buf builder from FFI: {buf_builder:?}");
243        }
244
245        let read_ptr = Box::leak(Box::from_raw(compute_ptr(
246            buf_builder.last_read as *mut u32,
247            &mem,
248        )));
249        let write_ptr = Box::leak(Box::from_raw(compute_ptr(
250            buf_builder.last_write as *mut u32,
251            &mem,
252        )));
253        let buffer_ptr = compute_ptr(buf_builder.start as *mut u8, &mem);
254        let buffer =
255            &mut *std::ptr::slice_from_raw_parts_mut(buffer_ptr, buf_builder.capacity as usize);
256        BuilderInfo {
257            buffer,
258            read_ptr,
259            write_ptr,
260        }
261    }
262}
263
264#[derive(Debug)]
265/// A live buffer in the WASM linear memory.
266pub struct Buffer<'instance> {
267    buffer: &'instance mut [u8],
268    /// stores the last read in the buffer
269    read_ptr: &'instance mut u32,
270    write_ptr: &'instance u32,
271    builder_ptr: *mut BufferBuilder,
272    mem: WasmLinearMem,
273}
274
275impl<'instance> Buffer<'instance> {
276    /// # Safety
277    /// In order for this to be a safe T must be properly aligned and cannot re-use the buffer
278    /// trying to read the same memory region again (that would create more than one copy to
279    /// the same underlying data and break aliasing rules).
280    pub unsafe fn read<T: Sized>(&mut self) -> T {
281        let next_offset = *self.read_ptr as usize;
282        let bytes = &self.buffer[next_offset..next_offset + std::mem::size_of::<T>()];
283        let t = std::ptr::read(bytes.as_ptr() as *const T);
284        *self.read_ptr += std::mem::size_of::<T>() as u32;
285        t
286    }
287
288    /// Read the specified number of bytes from the buffer.
289    pub fn read_bytes(&mut self, len: usize) -> &[u8] {
290        let next_offset = *self.read_ptr as usize;
291        *self.read_ptr += len as u32;
292        &self.buffer[next_offset..next_offset + len]
293    }
294
295    /// Reads all the bytes from the buffer.
296    pub fn read_all(&mut self) -> &[u8] {
297        let next_offset = *self.read_ptr as usize;
298        *self.read_ptr += self.buffer.len() as u32;
299        &self.buffer[next_offset..=*self.write_ptr as usize]
300    }
301
302    /// Give ownership of the buffer back to the guest.
303    ///
304    /// # Safety
305    /// Must guarantee that there are not underlying alive shared references.
306    #[doc(hidden)]
307    pub unsafe fn exclusive(self) -> BufferMut<'instance> {
308        let Buffer {
309            builder_ptr, mem, ..
310        } = self;
311        let BuilderInfo {
312            buffer,
313            read_ptr,
314            write_ptr,
315        } = from_raw_builder(builder_ptr, mem);
316        BufferMut {
317            buffer,
318            read_ptr,
319            write_ptr,
320            builder_ptr,
321            mem,
322        }
323    }
324}
325
326/// Returns the pointer to a new BufferBuilder.
327///
328/// This buffer leaks it's own memory and will only be freed by the runtime when a contract instance is dropped.
329#[doc(hidden)]
330#[allow(non_snake_case)]
331#[no_mangle]
332#[cfg(any(feature = "contract", test))]
333fn __frnt__initiate_buffer(capacity: u32) -> i64 {
334    let buf: Vec<u8> = Vec::with_capacity(capacity as usize);
335    let start = buf.as_ptr() as i64;
336
337    let last_read = Box::into_raw(Box::new(0u32));
338    let last_write = Box::into_raw(Box::new(0u32));
339    let buffer = Box::into_raw(Box::new(BufferBuilder {
340        start,
341        capacity,
342        last_read: last_read as _,
343        last_write: last_write as _,
344    }));
345    #[cfg(feature = "trace")]
346    {
347        tracing::trace!(
348            "new buffer ptr: {:p} -> {} as i64 w/ cap: {capacity}",
349            buf.as_ptr(),
350            start
351        );
352        tracing::trace!(
353            "last read ptr: {last_read:p} -> {} as i64",
354            last_read as i64
355        );
356        tracing::trace!(
357            "last write ptr: {last_write:p} -> {} as i64",
358            last_write as i64
359        );
360        tracing::trace!("buffer ptr: {buffer:p} -> {} as i64", buffer as i64);
361    }
362    std::mem::forget(buf);
363    buffer as i64
364}
365
366#[cfg(test)]
367mod test_io_write {
368    use super::*;
369    use std::io::Write;
370
371    /// Create a BufferMut backed by host memory (no WASM runtime needed).
372    /// Uses `__frnt__initiate_buffer` which allocates in host memory during tests,
373    /// and a null-base WasmLinearMem so compute_ptr is a no-op on absolute pointers.
374    unsafe fn host_buffer_mut(capacity: u32) -> BufferMut<'static> {
375        let builder_ptr = __frnt__initiate_buffer(capacity) as *mut BufferBuilder;
376        let linear_mem = WasmLinearMem {
377            start_ptr: std::ptr::null(),
378            size: 0,
379        };
380        BufferMut::from_ptr(builder_ptr, linear_mem)
381    }
382
383    /// Call std::io::Write::write (not BufferMut::write which has different signature)
384    fn io_write(buf: &mut BufferMut<'_>, data: &[u8]) -> std::io::Result<usize> {
385        Write::write(buf, data)
386    }
387
388    #[test]
389    fn write_trait_basic() {
390        let mut buf = unsafe { host_buffer_mut(32) };
391        let n = io_write(&mut buf, b"hello").unwrap();
392        assert_eq!(n, 5);
393        assert_eq!(buf.read_bytes(5), b"hello");
394    }
395
396    #[test]
397    fn write_trait_fills_exactly() {
398        let mut buf = unsafe { host_buffer_mut(4) };
399        let n = io_write(&mut buf, b"abcd").unwrap();
400        assert_eq!(n, 4);
401        assert_eq!(buf.read_bytes(4), b"abcd");
402    }
403
404    #[test]
405    fn write_trait_partial_when_near_full() {
406        let mut buf = unsafe { host_buffer_mut(4) };
407        io_write(&mut buf, b"ab").unwrap();
408        // Only 2 bytes free, writing 3 should write 2
409        let n = io_write(&mut buf, b"xyz").unwrap();
410        assert_eq!(n, 2);
411        assert_eq!(buf.read_bytes(4), b"abxy");
412    }
413
414    #[test]
415    fn write_trait_error_when_full() {
416        let mut buf = unsafe { host_buffer_mut(2) };
417        io_write(&mut buf, b"ab").unwrap();
418        let err = io_write(&mut buf, b"c").unwrap_err();
419        assert_eq!(err.kind(), std::io::ErrorKind::WriteZero);
420    }
421
422    #[test]
423    fn write_trait_empty_slice_ok() {
424        let mut buf = unsafe { host_buffer_mut(4) };
425        let n = io_write(&mut buf, b"").unwrap();
426        assert_eq!(n, 0);
427    }
428
429    #[test]
430    fn write_all_trait() {
431        let mut buf = unsafe { host_buffer_mut(16) };
432        buf.write_all(b"hello world").unwrap();
433        assert_eq!(buf.read_bytes(11), b"hello world");
434    }
435
436    #[test]
437    fn write_all_insufficient_space() {
438        let mut buf = unsafe { host_buffer_mut(4) };
439        let err = buf.write_all(b"hello").unwrap_err();
440        assert_eq!(err.kind(), std::io::ErrorKind::WriteZero);
441    }
442
443    #[test]
444    fn bincode_serialize_into() {
445        let data: Vec<u32> = vec![1, 2, 3, 4, 5];
446        let size = bincode::serialized_size(&data).unwrap() as usize;
447        let mut buf = unsafe { host_buffer_mut(size as u32) };
448        bincode::serialize_into(&mut buf, &data).unwrap();
449        let result: Vec<u32> = bincode::deserialize(buf.read_bytes(size)).unwrap();
450        assert_eq!(result, data);
451    }
452}
453
454#[cfg(all(test, any(unix, windows), feature = "wasmer-tests"))]
455mod test {
456    use super::*;
457    use wasmer::{
458        imports, wat2wasm, AsStoreMut, Cranelift, Function, Instance, Module, Store, TypedFunction,
459    };
460
461    const TEST_MODULE: &str = r#"
462        (module
463            (func $initiate_buffer (import "freenet" "initiate_buffer") (param i32) (result i64))
464            (memory $locutus_mem (export "memory") 20)
465            (export "initiate_buffer" (func $initiate_buffer))
466        )"#;
467
468    fn build_test_mod() -> Result<(Store, Instance), Box<dyn std::error::Error>> {
469        let wasm_bytes = wat2wasm(TEST_MODULE.as_bytes())?;
470        let mut store = Store::new(Cranelift::new());
471        let module = Module::new(&store, wasm_bytes)?;
472
473        let init_buf_fn = Function::new_typed(&mut store, __frnt__initiate_buffer);
474        let imports = imports! {
475            "freenet" => { "initiate_buffer" => init_buf_fn }
476        };
477        let instance = Instance::new(&mut store, &module, &imports).unwrap();
478        Ok((store, instance))
479    }
480
481    fn init_buf(store: &mut impl AsStoreMut, instance: &Instance, size: u32) -> *mut BufferBuilder {
482        let initiate_buffer: TypedFunction<u32, i64> = instance
483            .exports
484            .get_typed_function(&store, "initiate_buffer")
485            .unwrap();
486        initiate_buffer.call(store, size).unwrap() as *mut BufferBuilder
487    }
488
489    #[test]
490    #[ignore]
491    fn read_and_write() -> Result<(), Box<dyn std::error::Error>> {
492        let (mut store, instance) = build_test_mod()?;
493        let mem = instance.exports.get_memory("memory")?.view(&store);
494        let linear_mem = WasmLinearMem {
495            start_ptr: mem.data_ptr() as *const _,
496            size: mem.data_size(),
497        };
498
499        let mut writer =
500            unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
501        writer.write([1u8, 2])?;
502        let mut reader = writer.shared();
503        let r: [u8; 2] = unsafe { reader.read() };
504        assert_eq!(r, [1, 2]);
505
506        let mut writer = unsafe { reader.exclusive() };
507        writer.write([3u8, 4])?;
508        let mut reader = writer.shared();
509        let r: [u8; 2] = unsafe { reader.read() };
510        assert_eq!(r, [3, 4]);
511        Ok(())
512    }
513
514    #[test]
515    #[ignore]
516    fn read_and_write_bytes() -> Result<(), Box<dyn std::error::Error>> {
517        let (mut store, instance) = build_test_mod()?;
518        let mem = instance.exports.get_memory("memory")?.view(&store);
519        let linear_mem = WasmLinearMem {
520            start_ptr: mem.data_ptr() as *const _,
521            size: mem.data_size(),
522        };
523
524        let mut writer =
525            unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
526        writer.write([1u8, 2])?;
527        let mut reader = writer.shared();
528        let r = reader.read_bytes(2);
529        assert_eq!(r, &[1, 2]);
530
531        let mut writer = unsafe { reader.exclusive() };
532        writer.write([3u8, 4])?;
533        let mut reader = writer.shared();
534        let r = reader.read_bytes(2);
535        assert_eq!(r, &[3, 4]);
536        Ok(())
537    }
538
539    #[test]
540    #[ignore]
541    fn update() -> Result<(), Box<dyn std::error::Error>> {
542        let (mut store, instance) = build_test_mod()?;
543        let mem = instance.exports.get_memory("memory")?.view(&store);
544        let linear_mem = WasmLinearMem {
545            start_ptr: mem.data_ptr() as *const _,
546            size: mem.data_size(),
547        };
548
549        let ptr = {
550            let mut writer =
551                unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
552            writer.write([1u8, 2])?;
553            writer.ptr()
554        };
555
556        let writer = unsafe {
557            let builder = &mut *ptr;
558            builder.update_buffer(vec![3, 5, 7]);
559            BufferMut::from_ptr(ptr, linear_mem)
560        };
561        let mut reader = writer.shared();
562        assert_eq!(reader.read_all(), &[3, 5, 7]);
563
564        Ok(())
565    }
566}