1#[derive(Debug, Clone, PartialEq)]
13pub enum OpackValue {
14 Null,
15 Bool(bool),
16 Int(i64),
17 String(String),
18 Bytes(Vec<u8>),
19 Array(Vec<OpackValue>),
20 Dict(Vec<(OpackValue, OpackValue)>),
21}
22
23#[derive(Debug, thiserror::Error)]
24pub enum OpackError {
25 #[error("unexpected end of buffer")]
26 UnexpectedEof,
27 #[error("unknown opack tag: 0x{0:02X}")]
28 UnknownTag(u8),
29 #[error("invalid UTF-8 in string")]
30 InvalidUtf8,
31 #[error("opack encode error: {0}")]
32 Encode(String),
33}
34
35pub fn encode(value: &OpackValue) -> Result<Vec<u8>, OpackError> {
37 let mut out = Vec::new();
38 encode_value(value, &mut out)?;
39 Ok(out)
40}
41
42fn encode_value(value: &OpackValue, out: &mut Vec<u8>) -> Result<(), OpackError> {
43 match value {
44 OpackValue::Null => out.push(0x04),
45 OpackValue::Bool(true) => out.push(0x01),
46 OpackValue::Bool(false) => out.push(0x02),
47 OpackValue::Int(n) => {
48 if *n >= 0 && *n < 8 {
49 out.push(0x08 + *n as u8);
50 } else if *n >= i8::MIN as i64 && *n <= i8::MAX as i64 {
51 out.push(0x30);
52 out.push(*n as i8 as u8);
53 } else if *n >= i16::MIN as i64 && *n <= i16::MAX as i64 {
54 out.push(0x31);
55 out.extend_from_slice(&(*n as i16).to_le_bytes());
56 } else {
57 out.push(0x33);
58 out.extend_from_slice(&n.to_le_bytes());
59 }
60 }
61 OpackValue::String(s) => {
62 let bytes = s.as_bytes();
63 if bytes.len() < 0x20 {
64 out.push(0x40 | bytes.len() as u8);
65 } else if bytes.len() <= 0xFF {
66 out.push(0x61);
67 out.push(bytes.len() as u8);
68 } else {
69 if bytes.len() > u16::MAX as usize {
70 return Err(OpackError::Encode(format!(
71 "string too long: {} bytes (max {})",
72 bytes.len(),
73 u16::MAX
74 )));
75 }
76 out.push(0x62);
77 out.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
78 }
79 out.extend_from_slice(bytes);
80 }
81 OpackValue::Bytes(b) => {
82 let len = b.len();
83 if len <= 0x0F {
84 out.push(0x70 | len as u8);
85 } else if len < 0x20 {
86 out.push(0x80 | (len as u8 & 0x0F));
87 } else if len <= 0xFF {
88 out.push(0x91);
89 out.push(len as u8);
90 } else {
91 return Err(OpackError::Encode(format!(
92 "bytes too long: {len} bytes (max {})",
93 u8::MAX
94 )));
95 }
96 out.extend_from_slice(b);
97 }
98 OpackValue::Array(arr) => {
99 if arr.len() > 0x0F {
100 return Err(OpackError::Encode(format!(
101 "array too large: {} elements (max 15)",
102 arr.len()
103 )));
104 }
105 out.push(0xD0 | (arr.len() as u8));
106 for v in arr {
107 encode_value(v, out)?;
108 }
109 }
110 OpackValue::Dict(pairs) => {
111 if pairs.len() > 0x0F {
112 return Err(OpackError::Encode(format!(
113 "dict too large: {} entries (max 15)",
114 pairs.len()
115 )));
116 }
117 out.push(0xE0 | (pairs.len() as u8));
118 for (k, v) in pairs {
119 encode_value(k, out)?;
120 encode_value(v, out)?;
121 }
122 }
123 }
124 Ok(())
125}
126
127pub fn decode(buf: &[u8]) -> Result<(OpackValue, usize), OpackError> {
129 decode_at(buf, 0)
130}
131
132fn decode_at(buf: &[u8], pos: usize) -> Result<(OpackValue, usize), OpackError> {
133 if pos >= buf.len() {
134 return Err(OpackError::UnexpectedEof);
135 }
136 let tag = buf[pos];
137 match tag {
138 0x04 => Ok((OpackValue::Null, pos + 1)),
139 0x01 => Ok((OpackValue::Bool(true), pos + 1)),
140 0x02 => Ok((OpackValue::Bool(false), pos + 1)),
141 0x08..=0x0F => Ok((OpackValue::Int((tag - 0x08) as i64), pos + 1)),
142 0x30 => {
143 if pos + 2 > buf.len() {
144 return Err(OpackError::UnexpectedEof);
145 }
146 Ok((OpackValue::Int(buf[pos + 1] as i8 as i64), pos + 2))
147 }
148 0x31 => {
149 if pos + 3 > buf.len() {
150 return Err(OpackError::UnexpectedEof);
151 }
152 let n = i16::from_le_bytes([buf[pos + 1], buf[pos + 2]]);
153 Ok((OpackValue::Int(n as i64), pos + 3))
154 }
155 0x33 => {
156 if pos + 9 > buf.len() {
157 return Err(OpackError::UnexpectedEof);
158 }
159 let mut arr = [0u8; 8];
160 arr.copy_from_slice(&buf[pos + 1..pos + 9]);
161 Ok((OpackValue::Int(i64::from_le_bytes(arr)), pos + 9))
162 }
163 0x40..=0x5F => {
164 let len = (tag - 0x40) as usize;
165 if pos + 1 + len > buf.len() {
166 return Err(OpackError::UnexpectedEof);
167 }
168 let s = std::str::from_utf8(&buf[pos + 1..pos + 1 + len])
169 .map_err(|_| OpackError::InvalidUtf8)?;
170 Ok((OpackValue::String(s.to_string()), pos + 1 + len))
171 }
172 0x61 => {
173 if pos + 2 > buf.len() {
174 return Err(OpackError::UnexpectedEof);
175 }
176 let len = buf[pos + 1] as usize;
177 if pos + 2 + len > buf.len() {
178 return Err(OpackError::UnexpectedEof);
179 }
180 let s = std::str::from_utf8(&buf[pos + 2..pos + 2 + len])
181 .map_err(|_| OpackError::InvalidUtf8)?;
182 Ok((OpackValue::String(s.to_string()), pos + 2 + len))
183 }
184 0x62 => {
185 if pos + 3 > buf.len() {
186 return Err(OpackError::UnexpectedEof);
187 }
188 let len = u16::from_le_bytes([buf[pos + 1], buf[pos + 2]]) as usize;
189 if pos + 3 + len > buf.len() {
190 return Err(OpackError::UnexpectedEof);
191 }
192 let s = std::str::from_utf8(&buf[pos + 3..pos + 3 + len])
193 .map_err(|_| OpackError::InvalidUtf8)?;
194 Ok((OpackValue::String(s.to_string()), pos + 3 + len))
195 }
196 0x70..=0x7F => {
197 let len = (tag - 0x70) as usize;
198 if pos + 1 + len > buf.len() {
199 return Err(OpackError::UnexpectedEof);
200 }
201 Ok((
202 OpackValue::Bytes(buf[pos + 1..pos + 1 + len].to_vec()),
203 pos + 1 + len,
204 ))
205 }
206 0x80..=0x8F => {
207 let len = 0x10 + (tag - 0x80) as usize;
208 if pos + 1 + len > buf.len() {
209 return Err(OpackError::UnexpectedEof);
210 }
211 Ok((
212 OpackValue::Bytes(buf[pos + 1..pos + 1 + len].to_vec()),
213 pos + 1 + len,
214 ))
215 }
216 0x91 => {
217 if pos + 2 > buf.len() {
218 return Err(OpackError::UnexpectedEof);
219 }
220 let len = buf[pos + 1] as usize;
221 if pos + 2 + len > buf.len() {
222 return Err(OpackError::UnexpectedEof);
223 }
224 Ok((
225 OpackValue::Bytes(buf[pos + 2..pos + 2 + len].to_vec()),
226 pos + 2 + len,
227 ))
228 }
229 0xD0..=0xDF => {
230 let count = (tag - 0xD0) as usize;
231 let mut items = Vec::with_capacity(count);
232 let mut cur = pos + 1;
233 for _ in 0..count {
234 let (v, next) = decode_at(buf, cur)?;
235 items.push(v);
236 cur = next;
237 }
238 Ok((OpackValue::Array(items), cur))
239 }
240 0xE0..=0xEF => {
241 let count = (tag - 0xE0) as usize;
242 let mut pairs = Vec::with_capacity(count);
243 let mut cur = pos + 1;
244 for _ in 0..count {
245 let (k, next) = decode_at(buf, cur)?;
246 cur = next;
247 let (v, next) = decode_at(buf, cur)?;
248 cur = next;
249 pairs.push((k, v));
250 }
251 Ok((OpackValue::Dict(pairs), cur))
252 }
253 _ => Err(OpackError::UnknownTag(tag)),
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::{decode, encode, OpackValue};
260
261 fn roundtrip(value: OpackValue) -> OpackValue {
262 let encoded = encode(&value).expect("encode should succeed");
263 let (decoded, used) = decode(&encoded).expect("decode should succeed");
264 assert_eq!(used, encoded.len());
265 decoded
266 }
267
268 fn sample_bytes(len: usize) -> Vec<u8> {
269 (0..len).map(|i| i as u8).collect()
270 }
271
272 #[test]
273 fn roundtrips_scalars() {
274 assert_eq!(roundtrip(OpackValue::Null), OpackValue::Null);
275 assert_eq!(roundtrip(OpackValue::Bool(true)), OpackValue::Bool(true));
276 assert_eq!(roundtrip(OpackValue::Bool(false)), OpackValue::Bool(false));
277 assert_eq!(roundtrip(OpackValue::Int(0)), OpackValue::Int(0));
278 assert_eq!(roundtrip(OpackValue::Int(7)), OpackValue::Int(7));
279 assert_eq!(roundtrip(OpackValue::Int(0xff)), OpackValue::Int(0xff));
280 assert_eq!(
281 roundtrip(OpackValue::Int(i64::MIN)),
282 OpackValue::Int(i64::MIN)
283 );
284 }
285
286 #[test]
287 fn roundtrips_strings_and_bytes() {
288 assert_eq!(
289 roundtrip(OpackValue::String(String::new())),
290 OpackValue::String(String::new())
291 );
292 assert_eq!(
293 roundtrip(OpackValue::String("short string".into())),
294 OpackValue::String("short string".into())
295 );
296 assert_eq!(
297 roundtrip(OpackValue::String("x".repeat(32))),
298 OpackValue::String("x".repeat(32))
299 );
300 }
301
302 #[test]
303 fn encodes_bytes_with_reference_tags() {
304 let cases = [
305 (0usize, vec![0x70]),
306 (1, vec![0x71]),
307 (15, vec![0x7F]),
308 (16, vec![0x80]),
309 (31, vec![0x8F]),
310 (32, vec![0x91, 0x20]),
311 ];
312
313 for (len, expected_prefix) in cases {
314 let bytes = sample_bytes(len);
315 let mut expected = expected_prefix;
316 expected.extend_from_slice(&bytes);
317 assert_eq!(encode(&OpackValue::Bytes(bytes)).unwrap(), expected);
318 }
319 }
320
321 #[test]
322 fn decodes_bytes_with_reference_tags() {
323 let cases = [
324 (vec![0x70], 0usize),
325 (
326 {
327 let bytes = sample_bytes(1);
328 let mut encoded = vec![0x71];
329 encoded.extend_from_slice(&bytes);
330 encoded
331 },
332 1usize,
333 ),
334 (
335 {
336 let bytes = sample_bytes(15);
337 let mut encoded = vec![0x7F];
338 encoded.extend_from_slice(&bytes);
339 encoded
340 },
341 15usize,
342 ),
343 (
344 {
345 let bytes = sample_bytes(16);
346 let mut encoded = vec![0x80];
347 encoded.extend_from_slice(&bytes);
348 encoded
349 },
350 16usize,
351 ),
352 (
353 {
354 let bytes = sample_bytes(31);
355 let mut encoded = vec![0x8F];
356 encoded.extend_from_slice(&bytes);
357 encoded
358 },
359 31usize,
360 ),
361 (
362 {
363 let bytes = sample_bytes(32);
364 let mut encoded = vec![0x91, 0x20];
365 encoded.extend_from_slice(&bytes);
366 encoded
367 },
368 32usize,
369 ),
370 ];
371
372 for (encoded, len) in cases {
373 let (decoded, used) = decode(&encoded).expect("decode should succeed");
374 assert_eq!(decoded, OpackValue::Bytes(sample_bytes(len)));
375 assert_eq!(used, encoded.len());
376 }
377 }
378
379 #[test]
380 fn roundtrips_collections_with_fifteen_entries() {
381 let array = OpackValue::Array((0..15).map(OpackValue::Int).collect());
382 assert_eq!(roundtrip(array.clone()), array);
383
384 let dict = OpackValue::Dict(
385 (0..15)
386 .map(|i| (OpackValue::String(format!("k{i}")), OpackValue::Int(i)))
387 .collect(),
388 );
389 assert_eq!(roundtrip(dict.clone()), dict);
390 }
391
392 #[test]
393 fn rejects_arrays_and_dicts_larger_than_reference_limit() {
394 let array = OpackValue::Array((0..16).map(OpackValue::Int).collect());
395 let err = encode(&array).unwrap_err();
396 assert!(err.to_string().contains("array too large"));
397
398 let dict = OpackValue::Dict(
399 (0..16)
400 .map(|i| (OpackValue::String(format!("k{i}")), OpackValue::Int(i)))
401 .collect(),
402 );
403 let err = encode(&dict).unwrap_err();
404 assert!(err.to_string().contains("dict too large"));
405 }
406}