1use std::fmt;
10use std::io::Cursor;
11
12use serde::{de::DeserializeOwned, Serialize};
13
14pub const DEFAULT_MAX_PAYLOAD_LENGTH: usize = 16 * 1024 * 1024;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct BinaryOptions {
20 pub max_payload_length: usize,
21}
22
23impl Default for BinaryOptions {
24 fn default() -> Self {
25 Self {
26 max_payload_length: DEFAULT_MAX_PAYLOAD_LENGTH,
27 }
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum BinaryError {
34 Encode(String),
35 Decode(String),
36 PayloadTooLarge { actual: usize, maximum: usize },
37 TrailingBytes,
38}
39
40impl fmt::Display for BinaryError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::Encode(message) => write!(f, "binary encode failed: {message}"),
44 Self::Decode(message) => write!(f, "binary decode failed: {message}"),
45 Self::PayloadTooLarge { actual, maximum } => write!(
46 f,
47 "binary payload is {actual} bytes, exceeding the {maximum}-byte limit"
48 ),
49 Self::TrailingBytes => f.write_str("binary payload contains trailing bytes"),
50 }
51 }
52}
53
54impl std::error::Error for BinaryError {}
55
56pub fn encode<T: Serialize>(value: &T, options: &BinaryOptions) -> Result<Vec<u8>, BinaryError> {
58 let mut bytes = Vec::new();
59 ciborium::ser::into_writer(value, &mut bytes)
60 .map_err(|error| BinaryError::Encode(error.to_string()))?;
61 if bytes.len() > options.max_payload_length {
62 return Err(BinaryError::PayloadTooLarge {
63 actual: bytes.len(),
64 maximum: options.max_payload_length,
65 });
66 }
67 Ok(bytes)
68}
69
70pub fn decode<T: DeserializeOwned>(
72 bytes: &[u8],
73 options: &BinaryOptions,
74) -> Result<T, BinaryError> {
75 if bytes.len() > options.max_payload_length {
76 return Err(BinaryError::PayloadTooLarge {
77 actual: bytes.len(),
78 maximum: options.max_payload_length,
79 });
80 }
81
82 let mut reader = Cursor::new(bytes);
83 let value = ciborium::de::from_reader(&mut reader)
84 .map_err(|error| BinaryError::Decode(error.to_string()))?;
85 if reader.position() != bytes.len() as u64 {
86 return Err(BinaryError::TrailingBytes);
87 }
88 Ok(value)
89}
90
91#[cfg(feature = "json")]
94pub fn encode_json(
95 value: &serde_json::Value,
96 options: &BinaryOptions,
97) -> Result<Vec<u8>, BinaryError> {
98 encode(value, options)
99}
100
101#[cfg(feature = "json")]
104pub fn decode_json(
105 bytes: &[u8],
106 options: &BinaryOptions,
107) -> Result<serde_json::Value, BinaryError> {
108 decode(bytes, options)
109}
110
111#[repr(C)]
117#[derive(Clone, Copy)]
118pub struct StbBytes {
119 pub ptr: *mut u8,
120 pub len: usize,
121}
122
123unsafe impl Send for StbBytes {}
127unsafe impl Sync for StbBytes {}
128
129pub type FreeBytesFn = unsafe extern "C" fn(bytes: StbBytes);
131
132impl StbBytes {
133 pub const fn empty() -> Self {
135 Self {
136 ptr: std::ptr::null_mut(),
137 len: 0,
138 }
139 }
140
141 pub const fn is_empty(&self) -> bool {
142 self.len == 0
143 }
144
145 pub fn from_vec(bytes: Vec<u8>) -> Self {
147 if bytes.is_empty() {
148 return Self::empty();
149 }
150 let boxed = bytes.into_boxed_slice();
151 let len = boxed.len();
152 let ptr = Box::into_raw(boxed) as *mut u8;
153 Self { ptr, len }
154 }
155
156 pub unsafe fn to_vec(&self) -> Vec<u8> {
162 if self.len == 0 || self.ptr.is_null() {
163 return Vec::new();
164 }
165 unsafe { std::slice::from_raw_parts(self.ptr, self.len).to_vec() }
167 }
168
169 pub unsafe fn free_with(self, free_fn: Option<FreeBytesFn>) {
175 if self.is_empty() {
176 return;
177 }
178 if let Some(free_fn) = free_fn {
179 unsafe { free_fn(self) };
182 }
183 }
184}
185
186pub unsafe extern "C" fn free_bytes(bytes: StbBytes) {
191 if bytes.is_empty() || bytes.ptr.is_null() {
192 return;
193 }
194 unsafe {
196 drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
197 bytes.ptr, bytes.len,
198 )));
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use serde_json::json;
206
207 #[test]
208 fn json_round_trip_uses_binary_payload() {
209 let value = json!({
210 "type": "tool_result",
211 "tool": "read",
212 "content": ["alpha", "beta"],
213 "ok": true,
214 });
215 let bytes = encode_json(&value, &BinaryOptions::default()).unwrap();
216 assert!(!bytes.is_empty());
217 assert_ne!(bytes, serde_json::to_vec(&value).unwrap());
218 assert_eq!(
219 decode_json(&bytes, &BinaryOptions::default()).unwrap(),
220 value
221 );
222 }
223
224 #[test]
225 fn typed_values_round_trip() {
226 let value = (String::from("tool_result"), 42_u64, true);
227 let bytes = encode(&value, &BinaryOptions::default()).unwrap();
228 assert_eq!(
229 decode::<(String, u64, bool)>(&bytes, &BinaryOptions::default()).unwrap(),
230 value
231 );
232 }
233
234 #[test]
235 fn rejects_oversized_payloads() {
236 let value = json!({"data": "x".repeat(64)});
237 let options = BinaryOptions {
238 max_payload_length: 8,
239 };
240 assert!(matches!(
241 encode_json(&value, &options),
242 Err(BinaryError::PayloadTooLarge { .. })
243 ));
244 }
245
246 #[test]
247 fn rejects_trailing_bytes() {
248 let mut bytes = encode_json(&json!({"ok": true}), &BinaryOptions::default()).unwrap();
249 bytes.push(0);
250 assert_eq!(
251 decode_json(&bytes, &BinaryOptions::default()),
252 Err(BinaryError::TrailingBytes)
253 );
254 }
255
256 #[test]
257 fn abi_buffer_owns_and_releases_bytes() {
258 let buffer = StbBytes::from_vec(vec![1, 2, 3]);
259 assert_eq!(unsafe { buffer.to_vec() }, vec![1, 2, 3]);
261 unsafe { buffer.free_with(Some(free_bytes)) };
263 }
264}