diplomat_runtime/writeable.rs
1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::ffi::c_void;
4use core::{fmt, ptr};
5
6/// An object that can one can write UTF-8 strings to
7///
8/// This allows the C API to write to arbitrary kinds of objects, for example a
9/// C++ std::string or a char buffer.
10///
11/// The way to use this object is to fill out the `buf`, `len`, `cap` fields with
12/// appropriate values for the buffer, its current length, and its current capacity,
13/// and `flush` and `grow` with appropriate callbacks (using `context` to reference any
14/// state they need). This object will be passed by mutable reference to the Rust side,
15/// and Rust will write to it, calling `grow()` as necessary. Once done, it will call `flush()`
16/// to update any state on `context` (e.g. adding a null terminator, updating the length).
17/// The object on the foreign side will be directly usable after this, the foreign side
18/// need not perform additional state updates after passing an [`DiplomatWriteable`] to
19/// a function.
20///
21/// [`diplomat_simple_writeable()`] can be used to write to a fixed-size char buffer.
22///
23/// May be extended in the future to support further invariants
24///
25/// DiplomatWriteable will not perform any cleanup on `context` or `buf`, these are logically
26/// "borrows" from the FFI side.
27///
28/// # Safety invariants:
29/// - `flush()` and `grow()` will be passed `self` including `context` and it should always be safe to do so.
30/// `context` may be null, however `flush()` and `grow()` must then be ready to receive it as such.
31/// - `buf` must be `cap` bytes long
32/// - `grow()` must either return false or update `buf` and `cap` for a valid buffer
33/// of at least the requested buffer size
34/// - `DiplomatWriteable::flush()` will be automatically called by Diplomat. `flush()` might also be called
35/// (erroneously) on the Rust side (it's a public method), so it must be idempotent.
36#[repr(C)]
37pub struct DiplomatWriteable {
38 /// Context pointer for additional data needed by `grow()` and `flush()`. May be `null`.
39 ///
40 /// The pointer may reference structured data on the foreign side,
41 /// such as C++ std::string, used to reallocate buf.
42 context: *mut c_void,
43 /// The raw string buffer, which will be mutated on the Rust side.
44 buf: *mut u8,
45 /// The current filled size of the buffer
46 len: usize,
47 /// The current capacity of the buffer
48 cap: usize,
49 /// Called by Rust to indicate that there is no more data to write.
50 ///
51 /// May be called multiple times.
52 ///
53 /// Arguments:
54 /// - `self` (`*mut DiplomatWriteable`): This `DiplomatWriteable`
55 flush: extern "C" fn(*mut DiplomatWriteable),
56 /// Called by Rust to request more capacity in the buffer. The implementation should allocate a new
57 /// buffer and copy the contents of the old buffer into the new buffer, updating `self.buf` and `self.cap`
58 ///
59 /// Arguments:
60 /// - `self` (`*mut DiplomatWriteable`): This `DiplomatWriteable`
61 /// - `capacity` (`usize`): The requested capacity.
62 ///
63 /// Returns: `true` if the allocation succeeded. Should not update any state if it failed.
64 grow: extern "C" fn(*mut DiplomatWriteable, usize) -> bool,
65}
66
67impl DiplomatWriteable {
68 /// Call this function before releasing the buffer to C
69 pub fn flush(&mut self) {
70 (self.flush)(self);
71 }
72}
73impl fmt::Write for DiplomatWriteable {
74 fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
75 let needed_len = self.len + s.len();
76 if needed_len > self.cap {
77 let success = (self.grow)(self, needed_len);
78 if !success {
79 return Err(fmt::Error);
80 }
81 }
82 debug_assert!(needed_len <= self.cap);
83 unsafe {
84 ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf.add(self.len), s.len());
85 }
86 self.len = needed_len;
87 Ok(())
88 }
89}
90
91/// Create an `DiplomatWriteable` that can write to a fixed-length stack allocated `u8` buffer.
92///
93/// Once done, this will append a null terminator to the written string.
94///
95/// # Safety
96///
97/// - `buf` must be a valid pointer to a region of memory that can hold at `buf_size` bytes
98#[no_mangle]
99pub unsafe extern "C" fn diplomat_simple_writeable(
100 buf: *mut u8,
101 buf_size: usize,
102) -> DiplomatWriteable {
103 extern "C" fn grow(_this: *mut DiplomatWriteable, _cap: usize) -> bool {
104 false
105 }
106 extern "C" fn flush(this: *mut DiplomatWriteable) {
107 unsafe {
108 debug_assert!((*this).len <= (*this).cap);
109 let buf = (*this).buf;
110 ptr::write(buf.add((*this).len), 0)
111 }
112 }
113 DiplomatWriteable {
114 context: ptr::null_mut(),
115 buf,
116 len: 0,
117 // keep an extra byte in our pocket for the null terminator
118 cap: buf_size - 1,
119 flush,
120 grow,
121 }
122}
123
124/// Create an [`DiplomatWriteable`] that can write to a dynamically allocated buffer managed by Rust.
125///
126/// Use [`diplomat_buffer_writeable_destroy()`] to free the writable and its underlying buffer.
127#[no_mangle]
128pub extern "C" fn diplomat_buffer_writeable_create(cap: usize) -> *mut DiplomatWriteable {
129 extern "C" fn grow(this: *mut DiplomatWriteable, new_cap: usize) -> bool {
130 unsafe {
131 let this = this.as_mut().unwrap();
132 let mut vec = Vec::from_raw_parts(this.buf, 0, this.cap);
133 vec.reserve(new_cap);
134 this.cap = vec.capacity();
135 this.buf = vec.as_mut_ptr();
136 core::mem::forget(vec);
137 }
138 true
139 }
140
141 extern "C" fn flush(_: *mut DiplomatWriteable) {}
142
143 let mut vec = Vec::<u8>::with_capacity(cap);
144 let ret = DiplomatWriteable {
145 context: ptr::null_mut(),
146 buf: vec.as_mut_ptr(),
147 len: 0,
148 cap,
149 flush,
150 grow,
151 };
152
153 core::mem::forget(vec);
154 Box::into_raw(Box::new(ret))
155}
156
157/// Grabs a pointer to the underlying buffer of a writable.
158///
159/// # Safety
160/// - The returned pointer is valid until the passed writable is destroyed.
161/// - `this` must be a pointer to a valid [`DiplomatWriteable`] constructed by
162/// [`diplomat_buffer_writeable_create()`].
163#[no_mangle]
164pub extern "C" fn diplomat_buffer_writeable_get_bytes(this: &DiplomatWriteable) -> *mut u8 {
165 this.buf
166}
167
168/// Gets the length in bytes of the content written to the writable.
169///
170/// # Safety
171/// - `this` must be a pointer to a valid [`DiplomatWriteable`] constructed by
172/// [`diplomat_buffer_writeable_create()`].
173#[no_mangle]
174pub extern "C" fn diplomat_buffer_writeable_len(this: &DiplomatWriteable) -> usize {
175 this.len
176}
177
178/// Destructor for Rust-memory backed writables.
179///
180/// # Safety
181/// - `this` must be a pointer to a valid [`DiplomatWriteable`] constructed by
182/// [`diplomat_buffer_writeable_create()`].
183#[no_mangle]
184pub unsafe extern "C" fn diplomat_buffer_writeable_destroy(this: *mut DiplomatWriteable) {
185 let this = Box::from_raw(this);
186 let vec = Vec::from_raw_parts(this.buf, 0, this.cap);
187 drop(vec);
188 drop(this);
189}