1use alloc::string::String;
13use alloc::vec::Vec;
14use core::fmt;
15
16pub mod codes {
20 pub const NULL: u8 = 0x40;
22 pub const BOOLEAN_TRUE: u8 = 0x41;
24 pub const BOOLEAN_FALSE: u8 = 0x42;
26 pub const BOOLEAN: u8 = 0x56;
28 pub const UBYTE: u8 = 0x50;
30 pub const USHORT: u8 = 0x60;
32 pub const UINT: u8 = 0x70;
34 pub const SMALLUINT: u8 = 0x52;
36 pub const UINT0: u8 = 0x43;
38 pub const ULONG: u8 = 0x80;
40 pub const SMALLULONG: u8 = 0x53;
42 pub const ULONG0: u8 = 0x44;
44 pub const BYTE: u8 = 0x51;
46 pub const SHORT: u8 = 0x61;
48 pub const INT: u8 = 0x71;
50 pub const SMALLINT: u8 = 0x54;
52 pub const LONG: u8 = 0x81;
54 pub const SMALLLONG: u8 = 0x55;
56 pub const VBIN8: u8 = 0xA0;
58 pub const VBIN32: u8 = 0xB0;
60 pub const STR8: u8 = 0xA1;
62 pub const STR32: u8 = 0xB1;
64 pub const SYM8: u8 = 0xA3;
66 pub const SYM32: u8 = 0xB3;
68
69 pub const FLOAT: u8 = 0x72;
74 pub const DOUBLE: u8 = 0x82;
76 pub const CHAR: u8 = 0x73;
78 pub const DECIMAL32: u8 = 0x74;
80 pub const DECIMAL64: u8 = 0x84;
82 pub const DECIMAL128: u8 = 0x94;
84 pub const TIMESTAMP: u8 = 0x83;
86 pub const UUID: u8 = 0x98;
88
89 pub const LIST0: u8 = 0x45;
94 pub const LIST8: u8 = 0xC0;
96 pub const LIST32: u8 = 0xD0;
98 pub const MAP8: u8 = 0xC1;
100 pub const MAP32: u8 = 0xD1;
102 pub const ARRAY8: u8 = 0xE0;
104 pub const ARRAY32: u8 = 0xF0;
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum FormatCode {
111 Fixed(u8),
113 Variable(u8),
116 Compound(u8),
119 Array(u8),
122}
123
124impl FormatCode {
125 #[must_use]
128 pub const fn from_byte(b: u8) -> Self {
129 match b >> 4 {
130 0x4..=0x9 => Self::Fixed(b),
131 0xA | 0xB => Self::Variable(b),
132 0xC | 0xD => Self::Compound(b),
133 0xE | 0xF => Self::Array(b),
134 _ => Self::Fixed(b),
135 }
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub enum AmqpValue {
142 Null,
144 Boolean(bool),
146 Ulong(u64),
148 Long(i64),
150 Binary(Vec<u8>),
152 String(String),
154 Symbol(String),
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum TypeError {
161 Truncated,
163 UnsupportedFormatCode(u8),
165 InvalidUtf8,
167 NonAsciiSymbol,
170 LengthTooLarge,
172}
173
174impl fmt::Display for TypeError {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 Self::Truncated => f.write_str("input truncated"),
178 Self::UnsupportedFormatCode(c) => write!(f, "unsupported format code 0x{c:02X}"),
179 Self::InvalidUtf8 => f.write_str("invalid UTF-8 in str8/str32"),
180 Self::NonAsciiSymbol => f.write_str("non-ASCII byte in symbol"),
181 Self::LengthTooLarge => f.write_str("length exceeds u32::MAX"),
182 }
183 }
184}
185
186#[cfg(feature = "std")]
187impl std::error::Error for TypeError {}
188
189#[must_use]
191pub fn encode_null() -> Vec<u8> {
192 alloc::vec![codes::NULL]
193}
194
195#[must_use]
198pub fn encode_boolean(v: bool) -> Vec<u8> {
199 alloc::vec![if v {
200 codes::BOOLEAN_TRUE
201 } else {
202 codes::BOOLEAN_FALSE
203 }]
204}
205
206#[must_use]
209pub fn encode_ulong(v: u64) -> Vec<u8> {
210 if v == 0 {
211 alloc::vec![codes::ULONG0]
212 } else if v <= u64::from(u8::MAX) {
213 let b = (v & 0xFF) as u8;
214 alloc::vec![codes::SMALLULONG, b]
215 } else {
216 let mut out = Vec::with_capacity(9);
217 out.push(codes::ULONG);
218 out.extend_from_slice(&v.to_be_bytes());
219 out
220 }
221}
222
223#[must_use]
226pub fn encode_long(v: i64) -> Vec<u8> {
227 if (i64::from(i8::MIN)..=i64::from(i8::MAX)).contains(&v) {
228 let b = (v as i8) as u8;
229 alloc::vec![codes::SMALLLONG, b]
230 } else {
231 let mut out = Vec::with_capacity(9);
232 out.push(codes::LONG);
233 out.extend_from_slice(&v.to_be_bytes());
234 out
235 }
236}
237
238pub fn encode_binary(data: &[u8]) -> Result<Vec<u8>, TypeError> {
243 let len = data.len();
244 if len > u32::MAX as usize {
245 return Err(TypeError::LengthTooLarge);
246 }
247 if len <= u8::MAX as usize {
248 let mut out = Vec::with_capacity(2 + len);
249 out.push(codes::VBIN8);
250 #[allow(clippy::cast_possible_truncation)]
251 out.push(len as u8);
252 out.extend_from_slice(data);
253 Ok(out)
254 } else {
255 let mut out = Vec::with_capacity(5 + len);
256 out.push(codes::VBIN32);
257 #[allow(clippy::cast_possible_truncation)]
258 out.extend_from_slice(&(len as u32).to_be_bytes());
259 out.extend_from_slice(data);
260 Ok(out)
261 }
262}
263
264pub fn encode_string(s: &str) -> Result<Vec<u8>, TypeError> {
269 let bytes = s.as_bytes();
270 let len = bytes.len();
271 if len > u32::MAX as usize {
272 return Err(TypeError::LengthTooLarge);
273 }
274 if len <= u8::MAX as usize {
275 let mut out = Vec::with_capacity(2 + len);
276 out.push(codes::STR8);
277 #[allow(clippy::cast_possible_truncation)]
278 out.push(len as u8);
279 out.extend_from_slice(bytes);
280 Ok(out)
281 } else {
282 let mut out = Vec::with_capacity(5 + len);
283 out.push(codes::STR32);
284 #[allow(clippy::cast_possible_truncation)]
285 out.extend_from_slice(&(len as u32).to_be_bytes());
286 out.extend_from_slice(bytes);
287 Ok(out)
288 }
289}
290
291pub fn encode_symbol(s: &str) -> Result<Vec<u8>, TypeError> {
297 if !s.is_ascii() {
298 return Err(TypeError::NonAsciiSymbol);
299 }
300 let bytes = s.as_bytes();
301 let len = bytes.len();
302 if len > u32::MAX as usize {
303 return Err(TypeError::LengthTooLarge);
304 }
305 if len <= u8::MAX as usize {
306 let mut out = Vec::with_capacity(2 + len);
307 out.push(codes::SYM8);
308 #[allow(clippy::cast_possible_truncation)]
309 out.push(len as u8);
310 out.extend_from_slice(bytes);
311 Ok(out)
312 } else {
313 let mut out = Vec::with_capacity(5 + len);
314 out.push(codes::SYM32);
315 #[allow(clippy::cast_possible_truncation)]
316 out.extend_from_slice(&(len as u32).to_be_bytes());
317 out.extend_from_slice(bytes);
318 Ok(out)
319 }
320}
321
322pub fn decode_value(bytes: &[u8]) -> Result<(AmqpValue, usize), TypeError> {
328 if bytes.is_empty() {
329 return Err(TypeError::Truncated);
330 }
331 let code = bytes[0];
332 match code {
333 codes::NULL => Ok((AmqpValue::Null, 1)),
334 codes::BOOLEAN_TRUE => Ok((AmqpValue::Boolean(true), 1)),
335 codes::BOOLEAN_FALSE => Ok((AmqpValue::Boolean(false), 1)),
336 codes::BOOLEAN => {
337 if bytes.len() < 2 {
338 return Err(TypeError::Truncated);
339 }
340 Ok((AmqpValue::Boolean(bytes[1] != 0), 2))
341 }
342 codes::ULONG0 => Ok((AmqpValue::Ulong(0), 1)),
343 codes::SMALLULONG => {
344 if bytes.len() < 2 {
345 return Err(TypeError::Truncated);
346 }
347 Ok((AmqpValue::Ulong(u64::from(bytes[1])), 2))
348 }
349 codes::ULONG => {
350 if bytes.len() < 9 {
351 return Err(TypeError::Truncated);
352 }
353 let mut buf = [0u8; 8];
354 buf.copy_from_slice(&bytes[1..9]);
355 Ok((AmqpValue::Ulong(u64::from_be_bytes(buf)), 9))
356 }
357 codes::SMALLLONG => {
358 if bytes.len() < 2 {
359 return Err(TypeError::Truncated);
360 }
361 #[allow(clippy::cast_possible_wrap)]
362 Ok((AmqpValue::Long(i64::from(bytes[1] as i8)), 2))
363 }
364 codes::LONG => {
365 if bytes.len() < 9 {
366 return Err(TypeError::Truncated);
367 }
368 let mut buf = [0u8; 8];
369 buf.copy_from_slice(&bytes[1..9]);
370 Ok((AmqpValue::Long(i64::from_be_bytes(buf)), 9))
371 }
372 codes::VBIN8 => {
373 if bytes.len() < 2 {
374 return Err(TypeError::Truncated);
375 }
376 let len = usize::from(bytes[1]);
377 if bytes.len() < 2 + len {
378 return Err(TypeError::Truncated);
379 }
380 Ok((AmqpValue::Binary(bytes[2..2 + len].to_vec()), 2 + len))
381 }
382 codes::VBIN32 => {
383 if bytes.len() < 5 {
384 return Err(TypeError::Truncated);
385 }
386 let len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
387 if bytes.len() < 5 + len {
388 return Err(TypeError::Truncated);
389 }
390 Ok((AmqpValue::Binary(bytes[5..5 + len].to_vec()), 5 + len))
391 }
392 codes::STR8 => decode_str8(bytes, AmqpValue::String),
393 codes::STR32 => decode_str32(bytes, AmqpValue::String),
394 codes::SYM8 => decode_str8(bytes, AmqpValue::Symbol),
395 codes::SYM32 => decode_str32(bytes, AmqpValue::Symbol),
396 other => Err(TypeError::UnsupportedFormatCode(other)),
397 }
398}
399
400fn decode_str8(
401 bytes: &[u8],
402 wrap: fn(String) -> AmqpValue,
403) -> Result<(AmqpValue, usize), TypeError> {
404 if bytes.len() < 2 {
405 return Err(TypeError::Truncated);
406 }
407 let len = usize::from(bytes[1]);
408 if bytes.len() < 2 + len {
409 return Err(TypeError::Truncated);
410 }
411 let s = core::str::from_utf8(&bytes[2..2 + len])
412 .map_err(|_| TypeError::InvalidUtf8)?
413 .to_owned();
414 Ok((wrap(s), 2 + len))
415}
416
417fn decode_str32(
418 bytes: &[u8],
419 wrap: fn(String) -> AmqpValue,
420) -> Result<(AmqpValue, usize), TypeError> {
421 if bytes.len() < 5 {
422 return Err(TypeError::Truncated);
423 }
424 let len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
425 if bytes.len() < 5 + len {
426 return Err(TypeError::Truncated);
427 }
428 let s = core::str::from_utf8(&bytes[5..5 + len])
429 .map_err(|_| TypeError::InvalidUtf8)?
430 .to_owned();
431 Ok((wrap(s), 5 + len))
432}
433
434#[cfg(test)]
435#[allow(clippy::expect_used, clippy::panic)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn null_encodes_to_single_byte_0x40() {
441 assert_eq!(encode_null(), alloc::vec![0x40]);
443 }
444
445 #[test]
446 fn boolean_uses_compact_format_codes() {
447 assert_eq!(encode_boolean(true), alloc::vec![0x41]);
449 assert_eq!(encode_boolean(false), alloc::vec![0x42]);
450 }
451
452 #[test]
453 fn ulong_zero_uses_ulong0_format() {
454 assert_eq!(encode_ulong(0), alloc::vec![0x44]);
456 }
457
458 #[test]
459 fn ulong_small_uses_smallulong_format() {
460 assert_eq!(encode_ulong(255), alloc::vec![0x53, 0xFF]);
462 }
463
464 #[test]
465 fn ulong_large_uses_full_8_byte_format() {
466 let bytes = encode_ulong(0x1122_3344_5566_7788);
468 assert_eq!(bytes[0], 0x80);
469 assert_eq!(&bytes[1..], &0x1122_3344_5566_7788_u64.to_be_bytes());
470 }
471
472 #[test]
473 fn long_small_uses_smalllong_format() {
474 assert_eq!(encode_long(-1), alloc::vec![0x55, 0xFF]);
476 assert_eq!(encode_long(127), alloc::vec![0x55, 0x7F]);
477 }
478
479 #[test]
480 fn long_large_uses_full_8_byte_format() {
481 let bytes = encode_long(i64::MIN);
482 assert_eq!(bytes[0], 0x81);
483 }
484
485 #[test]
486 fn binary_short_uses_vbin8_format() {
487 let bytes = encode_binary(&[1, 2, 3]).expect("encode");
489 assert_eq!(bytes, alloc::vec![0xA0, 0x03, 1, 2, 3]);
490 }
491
492 #[test]
493 fn binary_long_uses_vbin32_format() {
494 let data = alloc::vec![0xAA; 300];
496 let bytes = encode_binary(&data).expect("encode");
497 assert_eq!(bytes[0], 0xB0);
498 assert_eq!(&bytes[1..5], &300u32.to_be_bytes());
499 }
500
501 #[test]
502 fn string_short_uses_str8_format() {
503 let bytes = encode_string("hi").expect("encode");
505 assert_eq!(bytes, alloc::vec![0xA1, 0x02, b'h', b'i']);
506 }
507
508 #[test]
509 fn string_unicode_round_trip() {
510 let bytes = encode_string("Käfer").expect("encode");
511 let (parsed, consumed) = decode_value(&bytes).expect("decode");
512 assert_eq!(consumed, bytes.len());
513 match parsed {
514 AmqpValue::String(s) => assert_eq!(s, "Käfer"),
515 _ => panic!("expected string"),
516 }
517 }
518
519 #[test]
520 fn symbol_rejects_non_ascii() {
521 assert_eq!(encode_symbol("Käfer"), Err(TypeError::NonAsciiSymbol));
523 }
524
525 #[test]
526 fn symbol_short_uses_sym8_format() {
527 let bytes = encode_symbol("hello").expect("encode");
528 assert_eq!(bytes[0], 0xA3);
529 assert_eq!(bytes[1], 0x05);
530 assert_eq!(&bytes[2..], b"hello");
531 }
532
533 #[test]
534 fn round_trip_all_primitive_values() {
535 let values = alloc::vec![
536 (encode_null(), AmqpValue::Null),
537 (encode_boolean(true), AmqpValue::Boolean(true)),
538 (encode_boolean(false), AmqpValue::Boolean(false)),
539 (encode_ulong(0), AmqpValue::Ulong(0)),
540 (encode_ulong(42), AmqpValue::Ulong(42)),
541 (
542 encode_ulong(0x1234_5678_9ABC_DEF0),
543 AmqpValue::Ulong(0x1234_5678_9ABC_DEF0)
544 ),
545 (encode_long(-100), AmqpValue::Long(-100)),
546 (encode_long(i64::MIN), AmqpValue::Long(i64::MIN)),
547 (
548 encode_binary(&[1, 2, 3]).expect("ok"),
549 AmqpValue::Binary(alloc::vec![1, 2, 3])
550 ),
551 (
552 encode_binary(&alloc::vec![0u8; 500]).expect("ok"),
553 AmqpValue::Binary(alloc::vec![0u8; 500])
554 ),
555 (
556 encode_string("foo").expect("ok"),
557 AmqpValue::String("foo".into())
558 ),
559 (
560 encode_symbol("bar").expect("ok"),
561 AmqpValue::Symbol("bar".into())
562 ),
563 ];
564 for (bytes, expected) in values {
565 let (parsed, consumed) = decode_value(&bytes).expect("decode");
566 assert_eq!(parsed, expected);
567 assert_eq!(consumed, bytes.len());
568 }
569 }
570
571 #[test]
572 fn unsupported_format_code_yields_error() {
573 assert_eq!(
575 decode_value(&[0xFF]),
576 Err(TypeError::UnsupportedFormatCode(0xFF))
577 );
578 }
579
580 #[test]
581 fn truncated_inputs_yield_error() {
582 assert_eq!(decode_value(&[]), Err(TypeError::Truncated));
583 assert_eq!(decode_value(&[0xA0]), Err(TypeError::Truncated)); assert_eq!(decode_value(&[0xA0, 5, 1]), Err(TypeError::Truncated)); assert_eq!(decode_value(&[0x80, 0, 0, 0]), Err(TypeError::Truncated)); }
587
588 #[test]
589 fn invalid_utf8_in_str_yields_error() {
590 assert_eq!(
592 decode_value(&[0xA1, 0x01, 0xFF]),
593 Err(TypeError::InvalidUtf8)
594 );
595 }
596
597 #[test]
598 fn format_code_categorizes_correctly() {
599 assert!(matches!(FormatCode::from_byte(0x40), FormatCode::Fixed(_)));
601 assert!(matches!(FormatCode::from_byte(0x80), FormatCode::Fixed(_)));
602 assert!(matches!(
603 FormatCode::from_byte(0xA0),
604 FormatCode::Variable(_)
605 ));
606 assert!(matches!(
607 FormatCode::from_byte(0xB0),
608 FormatCode::Variable(_)
609 ));
610 assert!(matches!(
611 FormatCode::from_byte(0xC0),
612 FormatCode::Compound(_)
613 ));
614 assert!(matches!(
615 FormatCode::from_byte(0xD0),
616 FormatCode::Compound(_)
617 ));
618 assert!(matches!(FormatCode::from_byte(0xE0), FormatCode::Array(_)));
619 assert!(matches!(FormatCode::from_byte(0xF0), FormatCode::Array(_)));
620 }
621}