1use std::fmt;
13
14use serde_json::{Map, Number, Value};
15
16pub const DEFAULT_MAX_CBOR_BYTE_LENGTH: usize = 16 * 1024 * 1024;
18pub const DEFAULT_MAX_CBOR_CONTAINER_LENGTH: usize = 1_000_000;
20pub const DEFAULT_MAX_CBOR_DEPTH: usize = 64;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct CborError(pub String);
26
27impl fmt::Display for CborError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.write_str(&self.0)
30 }
31}
32
33impl std::error::Error for CborError {}
34
35#[derive(Debug, Clone, Copy)]
37pub struct CborOptions {
38 pub max_byte_length: usize,
39 pub max_container_length: usize,
40 pub max_depth: usize,
41}
42
43impl Default for CborOptions {
44 fn default() -> Self {
45 Self {
46 max_byte_length: DEFAULT_MAX_CBOR_BYTE_LENGTH,
47 max_container_length: DEFAULT_MAX_CBOR_CONTAINER_LENGTH,
48 max_depth: DEFAULT_MAX_CBOR_DEPTH,
49 }
50 }
51}
52
53fn write_head(out: &mut Vec<u8>, major: u8, value: u64) {
54 let m = major << 5;
55 if value < 24 {
56 out.push(m | value as u8);
57 } else if value <= u8::MAX as u64 {
58 out.push(m | 24);
59 out.push(value as u8);
60 } else if value <= u16::MAX as u64 {
61 out.push(m | 25);
62 out.extend_from_slice(&(value as u16).to_be_bytes());
63 } else if value <= u32::MAX as u64 {
64 out.push(m | 26);
65 out.extend_from_slice(&(value as u32).to_be_bytes());
66 } else {
67 out.push(m | 27);
68 out.extend_from_slice(&value.to_be_bytes());
69 }
70}
71
72fn encode_value(
73 value: &Value,
74 out: &mut Vec<u8>,
75 options: &CborOptions,
76 depth: usize,
77) -> Result<(), CborError> {
78 if depth > options.max_depth {
79 return Err(CborError("CBOR nesting depth exceeded".into()));
80 }
81 match value {
82 Value::Null => out.push(0xf6),
83 Value::Bool(false) => out.push(0xf4),
84 Value::Bool(true) => out.push(0xf5),
85 Value::Number(n) => {
86 if let Some(u) = n.as_u64() {
87 write_head(out, 0, u);
88 } else if let Some(i) = n.as_i64() {
89 write_head(out, 1, (-(i as i128) - 1) as u64);
91 } else if let Some(f) = n.as_f64() {
92 out.push(0xfb);
93 out.extend_from_slice(&f.to_be_bytes());
94 } else {
95 return Err(CborError("unsupported number".into()));
96 }
97 }
98 Value::String(s) => {
99 let bytes = s.as_bytes();
100 if bytes.len() > options.max_byte_length {
101 return Err(CborError("CBOR string exceeds max byte length".into()));
102 }
103 write_head(out, 3, bytes.len() as u64);
104 out.extend_from_slice(bytes);
105 }
106 Value::Array(items) => {
107 if items.len() > options.max_container_length {
108 return Err(CborError("CBOR array exceeds max container length".into()));
109 }
110 write_head(out, 4, items.len() as u64);
111 for item in items {
112 encode_value(item, out, options, depth + 1)?;
113 }
114 }
115 Value::Object(map) => {
116 if map.len() > options.max_container_length {
117 return Err(CborError("CBOR map exceeds max container length".into()));
118 }
119 write_head(out, 5, map.len() as u64);
120 for (key, item) in map {
121 if key.len() > options.max_byte_length {
122 return Err(CborError("CBOR string exceeds max byte length".into()));
123 }
124 write_head(out, 3, key.len() as u64);
125 out.extend_from_slice(key.as_bytes());
126 encode_value(item, out, options, depth + 1)?;
127 }
128 }
129 }
130 Ok(())
131}
132
133pub fn encode(value: &Value, options: &CborOptions) -> Result<Vec<u8>, CborError> {
135 let mut out = Vec::new();
136 encode_value(value, &mut out, options, 0)?;
137 if out.len() > options.max_byte_length {
138 return Err(CborError("CBOR payload exceeds max byte length".into()));
139 }
140 Ok(out)
141}
142
143struct Reader<'a> {
144 bytes: &'a [u8],
145 pos: usize,
146}
147
148impl<'a> Reader<'a> {
149 fn take(&mut self, n: usize) -> Result<&'a [u8], CborError> {
150 if self.pos + n > self.bytes.len() {
151 return Err(CborError("unexpected end of CBOR input".into()));
152 }
153 let slice = &self.bytes[self.pos..self.pos + n];
154 self.pos += n;
155 Ok(slice)
156 }
157
158 fn u8(&mut self) -> Result<u8, CborError> {
159 Ok(self.take(1)?[0])
160 }
161
162 fn read_len(&mut self, additional: u8) -> Result<Option<u64>, CborError> {
163 match additional {
164 0..=23 => Ok(Some(additional as u64)),
165 24 => Ok(Some(self.u8()? as u64)),
166 25 => {
167 let b = self.take(2)?;
168 Ok(Some(u16::from_be_bytes([b[0], b[1]]) as u64))
169 }
170 26 => {
171 let b = self.take(4)?;
172 Ok(Some(u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as u64))
173 }
174 27 => {
175 let b = self.take(8)?;
176 Ok(Some(u64::from_be_bytes([
177 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
178 ])))
179 }
180 _ => Ok(None),
181 }
182 }
183}
184
185fn decode_value(
186 reader: &mut Reader<'_>,
187 options: &CborOptions,
188 depth: usize,
189) -> Result<Value, CborError> {
190 if depth > options.max_depth {
191 return Err(CborError("CBOR nesting depth exceeded".into()));
192 }
193 let initial = reader.u8()?;
194 let major = initial >> 5;
195 let additional = initial & 0x1f;
196 match major {
197 0 => {
198 let n = reader
199 .read_len(additional)?
200 .ok_or_else(|| CborError("invalid CBOR integer".into()))?;
201 Ok(Value::Number(Number::from(n)))
202 }
203 1 => {
204 let n = reader
205 .read_len(additional)?
206 .ok_or_else(|| CborError("invalid CBOR integer".into()))?;
207 if n > i64::MAX as u64 {
208 return Err(CborError("CBOR negative integer out of range".into()));
209 }
210 Ok(Value::Number(Number::from(-(n as i64) - 1)))
211 }
212 2 => {
213 let len = reader
216 .read_len(additional)?
217 .ok_or_else(|| CborError("invalid CBOR byte string".into()))?
218 as usize;
219 if len > options.max_byte_length {
220 return Err(CborError("CBOR string exceeds max byte length".into()));
221 }
222 let bytes = reader.take(len)?;
223 Ok(Value::Array(
224 bytes.iter().map(|b| Value::Number(Number::from(*b))).collect(),
225 ))
226 }
227 3 => {
228 let len = reader
229 .read_len(additional)?
230 .ok_or_else(|| CborError("invalid CBOR text string".into()))?
231 as usize;
232 if len > options.max_byte_length {
233 return Err(CborError("CBOR string exceeds max byte length".into()));
234 }
235 let bytes = reader.take(len)?;
236 let text = std::str::from_utf8(bytes)
237 .map_err(|_| CborError("invalid UTF-8 in CBOR text string".into()))?;
238 Ok(Value::String(text.to_string()))
239 }
240 4 => {
241 let len = reader
242 .read_len(additional)?
243 .ok_or_else(|| CborError("invalid CBOR array".into()))?
244 as usize;
245 if len > options.max_container_length {
246 return Err(CborError("CBOR array exceeds max container length".into()));
247 }
248 let mut items = Vec::with_capacity(len.min(4096));
249 for _ in 0..len {
250 items.push(decode_value(reader, options, depth + 1)?);
251 }
252 Ok(Value::Array(items))
253 }
254 5 => {
255 let len = reader
256 .read_len(additional)?
257 .ok_or_else(|| CborError("invalid CBOR map".into()))?
258 as usize;
259 if len > options.max_container_length {
260 return Err(CborError("CBOR map exceeds max container length".into()));
261 }
262 let mut map = Map::new();
263 for _ in 0..len {
264 let key = decode_value(reader, options, depth + 1)?;
265 let Value::String(key) = key else {
266 return Err(CborError("CBOR map key must be a text string".into()));
267 };
268 let value = decode_value(reader, options, depth + 1)?;
269 map.insert(key, value);
270 }
271 Ok(Value::Object(map))
272 }
273 6 => {
274 let _ = reader.read_len(additional)?;
276 decode_value(reader, options, depth)
277 }
278 7 => match additional {
279 20 => Ok(Value::Bool(false)),
280 21 => Ok(Value::Bool(true)),
281 22 => Ok(Value::Null),
282 26 => {
283 let b = reader.take(4)?;
284 let f = f32::from_be_bytes([b[0], b[1], b[2], b[3]]) as f64;
285 Ok(Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null))
286 }
287 27 => {
288 let b = reader.take(8)?;
289 let f = f64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
290 Ok(Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null))
291 }
292 _ => Err(CborError("unsupported CBOR simple value".into())),
293 },
294 _ => Err(CborError("unsupported CBOR major type".into())),
295 }
296}
297
298pub fn decode(bytes: &[u8], options: &CborOptions) -> Result<Value, CborError> {
300 if bytes.len() > options.max_byte_length {
301 return Err(CborError("CBOR payload exceeds max byte length".into()));
302 }
303 let mut reader = Reader { bytes, pos: 0 };
304 let value = decode_value(&mut reader, options, 0)?;
305 if reader.pos != bytes.len() {
306 return Err(CborError("trailing bytes after CBOR value".into()));
307 }
308 Ok(value)
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use serde_json::json;
315
316 fn round_trip(value: Value) {
317 let options = CborOptions::default();
318 let bytes = encode(&value, &options).unwrap();
319 let decoded = decode(&bytes, &options).unwrap();
320 assert_eq!(decoded, value);
321 }
322
323 #[test]
324 fn round_trips_scalars() {
325 round_trip(json!(null));
326 round_trip(json!(true));
327 round_trip(json!(false));
328 round_trip(json!(0));
329 round_trip(json!(23));
330 round_trip(json!(24));
331 round_trip(json!(300));
332 round_trip(json!(70000));
333 round_trip(json!(5_000_000_000i64));
334 round_trip(json!(-1));
335 round_trip(json!(-300));
336 round_trip(json!("hello"));
337 round_trip(json!(1.5));
338 }
339
340 #[test]
341 fn round_trips_containers() {
342 round_trip(json!([1, "two", [3, 4], { "a": true }]));
343 round_trip(json!({ "type": "hello", "nested": { "x": [1, 2, 3] } }));
344 }
345
346 #[test]
347 fn rejects_deep_nesting() {
348 let options = CborOptions {
349 max_depth: 3,
350 ..Default::default()
351 };
352 let deep = json!([[[[1]]]]);
353 assert!(encode(&deep, &options).is_err());
354 }
355
356 #[test]
357 fn rejects_trailing_bytes() {
358 let options = CborOptions::default();
359 let mut bytes = encode(&json!(1), &options).unwrap();
360 bytes.push(0);
361 assert!(decode(&bytes, &options).is_err());
362 }
363
364 #[test]
365 fn rejects_truncated() {
366 let options = CborOptions::default();
367 let bytes = encode(&json!("abcdef"), &options).unwrap();
368 assert!(decode(&bytes[..3], &options).is_err());
369 }
370}