Coverage Report

Created: 2025-11-11 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/diplomat-runtime-0.8.2/src/writeable.rs
Line
Count
Source
1
use alloc::boxed::Box;
2
use alloc::vec::Vec;
3
use core::ffi::c_void;
4
use 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)]
37
pub 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
67
impl DiplomatWriteable {
68
    /// Call this function before releasing the buffer to C
69
0
    pub fn flush(&mut self) {
70
0
        (self.flush)(self);
71
0
    }
72
}
73
impl fmt::Write for DiplomatWriteable {
74
0
    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
75
0
        let needed_len = self.len + s.len();
76
0
        if needed_len > self.cap {
77
0
            let success = (self.grow)(self, needed_len);
78
0
            if !success {
79
0
                return Err(fmt::Error);
80
0
            }
81
0
        }
82
0
        debug_assert!(needed_len <= self.cap);
83
0
        unsafe {
84
0
            ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf.add(self.len), s.len());
85
0
        }
86
0
        self.len = needed_len;
87
0
        Ok(())
88
0
    }
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]
99
0
pub unsafe extern "C" fn diplomat_simple_writeable(
100
0
    buf: *mut u8,
101
0
    buf_size: usize,
102
0
) -> DiplomatWriteable {
103
0
    extern "C" fn grow(_this: *mut DiplomatWriteable, _cap: usize) -> bool {
104
0
        false
105
0
    }
106
0
    extern "C" fn flush(this: *mut DiplomatWriteable) {
107
        unsafe {
108
0
            debug_assert!((*this).len <= (*this).cap);
109
0
            let buf = (*this).buf;
110
0
            ptr::write(buf.add((*this).len), 0)
111
        }
112
0
    }
113
0
    DiplomatWriteable {
114
0
        context: ptr::null_mut(),
115
0
        buf,
116
0
        len: 0,
117
0
        // keep an extra byte in our pocket for the null terminator
118
0
        cap: buf_size - 1,
119
0
        flush,
120
0
        grow,
121
0
    }
122
0
}
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]
128
0
pub extern "C" fn diplomat_buffer_writeable_create(cap: usize) -> *mut DiplomatWriteable {
129
0
    extern "C" fn grow(this: *mut DiplomatWriteable, new_cap: usize) -> bool {
130
0
        unsafe {
131
0
            let this = this.as_mut().unwrap();
132
0
            let mut vec = Vec::from_raw_parts(this.buf, 0, this.cap);
133
0
            vec.reserve(new_cap);
134
0
            this.cap = vec.capacity();
135
0
            this.buf = vec.as_mut_ptr();
136
0
            core::mem::forget(vec);
137
0
        }
138
0
        true
139
0
    }
140
141
0
    extern "C" fn flush(_: *mut DiplomatWriteable) {}
142
143
0
    let mut vec = Vec::<u8>::with_capacity(cap);
144
0
    let ret = DiplomatWriteable {
145
0
        context: ptr::null_mut(),
146
0
        buf: vec.as_mut_ptr(),
147
0
        len: 0,
148
0
        cap,
149
0
        flush,
150
0
        grow,
151
0
    };
152
153
0
    core::mem::forget(vec);
154
0
    Box::into_raw(Box::new(ret))
155
0
}
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]
164
0
pub extern "C" fn diplomat_buffer_writeable_get_bytes(this: &DiplomatWriteable) -> *mut u8 {
165
0
    this.buf
166
0
}
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]
174
0
pub extern "C" fn diplomat_buffer_writeable_len(this: &DiplomatWriteable) -> usize {
175
0
    this.len
176
0
}
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]
184
0
pub unsafe extern "C" fn diplomat_buffer_writeable_destroy(this: *mut DiplomatWriteable) {
185
0
    let this = Box::from_raw(this);
186
0
    let vec = Vec::from_raw_parts(this.buf, 0, this.cap);
187
0
    drop(vec);
188
0
    drop(this);
189
0
}