use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::types::{AmqpValue, TypeError, codes};
const MAX_COMPOUND_DEPTH: usize = 32;
#[must_use]
pub fn encode_ubyte(v: u8) -> Vec<u8> {
alloc::vec![codes::UBYTE, v]
}
pub fn decode_ubyte(bytes: &[u8]) -> Result<(u8, usize), TypeError> {
if bytes.len() < 2 || bytes[0] != codes::UBYTE {
return Err(TypeError::Truncated);
}
Ok((bytes[1], 2))
}
#[must_use]
pub fn encode_ushort(v: u16) -> Vec<u8> {
let mut out = alloc::vec![codes::USHORT];
out.extend_from_slice(&v.to_be_bytes());
out
}
pub fn decode_ushort(bytes: &[u8]) -> Result<(u16, usize), TypeError> {
if bytes.len() < 3 || bytes[0] != codes::USHORT {
return Err(TypeError::Truncated);
}
Ok((u16::from_be_bytes([bytes[1], bytes[2]]), 3))
}
#[must_use]
pub fn encode_uint(v: u32) -> Vec<u8> {
if v == 0 {
alloc::vec![codes::UINT0]
} else if v <= u32::from(u8::MAX) {
let b = (v & 0xFF) as u8;
alloc::vec![codes::SMALLUINT, b]
} else {
let mut out = alloc::vec![codes::UINT];
out.extend_from_slice(&v.to_be_bytes());
out
}
}
pub fn decode_uint(bytes: &[u8]) -> Result<(u32, usize), TypeError> {
if bytes.is_empty() {
return Err(TypeError::Truncated);
}
match bytes[0] {
codes::UINT0 => Ok((0, 1)),
codes::SMALLUINT => {
if bytes.len() < 2 {
return Err(TypeError::Truncated);
}
Ok((u32::from(bytes[1]), 2))
}
codes::UINT => {
if bytes.len() < 5 {
return Err(TypeError::Truncated);
}
Ok((
u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]),
5,
))
}
c => Err(TypeError::UnsupportedFormatCode(c)),
}
}
#[must_use]
pub fn encode_byte(v: i8) -> Vec<u8> {
#[allow(clippy::cast_sign_loss)]
let b = v as u8;
alloc::vec![codes::BYTE, b]
}
pub fn decode_byte(bytes: &[u8]) -> Result<(i8, usize), TypeError> {
if bytes.len() < 2 || bytes[0] != codes::BYTE {
return Err(TypeError::Truncated);
}
#[allow(clippy::cast_possible_wrap)]
Ok((bytes[1] as i8, 2))
}
#[must_use]
pub fn encode_short(v: i16) -> Vec<u8> {
let mut out = alloc::vec![codes::SHORT];
out.extend_from_slice(&v.to_be_bytes());
out
}
pub fn decode_short(bytes: &[u8]) -> Result<(i16, usize), TypeError> {
if bytes.len() < 3 || bytes[0] != codes::SHORT {
return Err(TypeError::Truncated);
}
Ok((i16::from_be_bytes([bytes[1], bytes[2]]), 3))
}
#[must_use]
pub fn encode_int(v: i32) -> Vec<u8> {
if (-128..=127).contains(&v) {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let b = v as i8 as u8;
alloc::vec![codes::SMALLINT, b]
} else {
let mut out = alloc::vec![codes::INT];
out.extend_from_slice(&v.to_be_bytes());
out
}
}
pub fn decode_int(bytes: &[u8]) -> Result<(i32, usize), TypeError> {
if bytes.is_empty() {
return Err(TypeError::Truncated);
}
match bytes[0] {
codes::SMALLINT => {
if bytes.len() < 2 {
return Err(TypeError::Truncated);
}
#[allow(clippy::cast_possible_wrap)]
Ok((i32::from(bytes[1] as i8), 2))
}
codes::INT => {
if bytes.len() < 5 {
return Err(TypeError::Truncated);
}
Ok((
i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]),
5,
))
}
c => Err(TypeError::UnsupportedFormatCode(c)),
}
}
#[must_use]
pub fn encode_float(v: f32) -> Vec<u8> {
let mut out = alloc::vec![codes::FLOAT];
out.extend_from_slice(&v.to_be_bytes());
out
}
pub fn decode_float(bytes: &[u8]) -> Result<(f32, usize), TypeError> {
if bytes.len() < 5 || bytes[0] != codes::FLOAT {
return Err(TypeError::Truncated);
}
Ok((
f32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]),
5,
))
}
#[must_use]
pub fn encode_double(v: f64) -> Vec<u8> {
let mut out = alloc::vec![codes::DOUBLE];
out.extend_from_slice(&v.to_be_bytes());
out
}
pub fn decode_double(bytes: &[u8]) -> Result<(f64, usize), TypeError> {
if bytes.len() < 9 || bytes[0] != codes::DOUBLE {
return Err(TypeError::Truncated);
}
let mut buf = [0u8; 8];
buf.copy_from_slice(&bytes[1..9]);
Ok((f64::from_be_bytes(buf), 9))
}
pub fn encode_char(c: char) -> Result<Vec<u8>, TypeError> {
let codepoint = u32::from(c);
let mut out = alloc::vec![codes::CHAR];
out.extend_from_slice(&codepoint.to_be_bytes());
Ok(out)
}
pub fn decode_char(bytes: &[u8]) -> Result<(char, usize), TypeError> {
if bytes.len() < 5 || bytes[0] != codes::CHAR {
return Err(TypeError::Truncated);
}
let cp = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
char::from_u32(cp)
.map(|c| (c, 5))
.ok_or(TypeError::LengthTooLarge)
}
#[must_use]
pub fn encode_timestamp(ms_since_epoch: i64) -> Vec<u8> {
let mut out = alloc::vec![codes::TIMESTAMP];
out.extend_from_slice(&ms_since_epoch.to_be_bytes());
out
}
pub fn decode_timestamp(bytes: &[u8]) -> Result<(i64, usize), TypeError> {
if bytes.len() < 9 || bytes[0] != codes::TIMESTAMP {
return Err(TypeError::Truncated);
}
let mut buf = [0u8; 8];
buf.copy_from_slice(&bytes[1..9]);
Ok((i64::from_be_bytes(buf), 9))
}
#[must_use]
pub fn encode_uuid(uuid: [u8; 16]) -> Vec<u8> {
let mut out = alloc::vec![codes::UUID];
out.extend_from_slice(&uuid);
out
}
pub fn decode_uuid(bytes: &[u8]) -> Result<([u8; 16], usize), TypeError> {
if bytes.len() < 17 || bytes[0] != codes::UUID {
return Err(TypeError::Truncated);
}
let mut out = [0u8; 16];
out.copy_from_slice(&bytes[1..17]);
Ok((out, 17))
}
#[must_use]
pub fn encode_decimal32(bid: [u8; 4]) -> Vec<u8> {
let mut out = alloc::vec![codes::DECIMAL32];
out.extend_from_slice(&bid);
out
}
#[must_use]
pub fn encode_decimal64(bid: [u8; 8]) -> Vec<u8> {
let mut out = alloc::vec![codes::DECIMAL64];
out.extend_from_slice(&bid);
out
}
#[must_use]
pub fn encode_decimal128(bid: [u8; 16]) -> Vec<u8> {
let mut out = alloc::vec![codes::DECIMAL128];
out.extend_from_slice(&bid);
out
}
#[derive(Debug, Clone, PartialEq)]
pub enum AmqpExtValue {
Null,
Boolean(bool),
Ubyte(u8),
Ushort(u16),
Uint(u32),
Ulong(u64),
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
Char(char),
Timestamp(i64),
Uuid([u8; 16]),
Binary(Vec<u8>),
Str(String),
Symbol(String),
List(Vec<AmqpExtValue>),
Map(Vec<(AmqpExtValue, AmqpExtValue)>),
Array(Vec<AmqpExtValue>),
}
impl Eq for AmqpExtValue {}
impl AmqpExtValue {
pub fn encode(&self) -> Result<Vec<u8>, TypeError> {
self.encode_at(0)
}
fn encode_at(&self, depth: usize) -> Result<Vec<u8>, TypeError> {
if depth > MAX_COMPOUND_DEPTH {
return Err(TypeError::LengthTooLarge);
}
match self {
Self::Null => Ok(alloc::vec![codes::NULL]),
Self::Boolean(b) => Ok(if *b {
alloc::vec![codes::BOOLEAN_TRUE]
} else {
alloc::vec![codes::BOOLEAN_FALSE]
}),
Self::Ubyte(v) => Ok(encode_ubyte(*v)),
Self::Ushort(v) => Ok(encode_ushort(*v)),
Self::Uint(v) => Ok(encode_uint(*v)),
Self::Ulong(v) => Ok(crate::types::encode_ulong(*v)),
Self::Byte(v) => Ok(encode_byte(*v)),
Self::Short(v) => Ok(encode_short(*v)),
Self::Int(v) => Ok(encode_int(*v)),
Self::Long(v) => Ok(crate::types::encode_long(*v)),
Self::Float(v) => Ok(encode_float(*v)),
Self::Double(v) => Ok(encode_double(*v)),
Self::Char(c) => encode_char(*c),
Self::Timestamp(t) => Ok(encode_timestamp(*t)),
Self::Uuid(u) => Ok(encode_uuid(*u)),
Self::Binary(b) => crate::types::encode_binary(b),
Self::Str(s) => crate::types::encode_string(s),
Self::Symbol(s) => crate::types::encode_symbol(s),
Self::List(items) => encode_list(items, depth),
Self::Map(entries) => encode_map(entries, depth),
Self::Array(items) => encode_array(items, depth),
}
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), TypeError> {
Self::decode_at(bytes, 0)
}
fn decode_at(bytes: &[u8], depth: usize) -> Result<(Self, usize), TypeError> {
if depth > MAX_COMPOUND_DEPTH {
return Err(TypeError::LengthTooLarge);
}
if bytes.is_empty() {
return Err(TypeError::Truncated);
}
let code = bytes[0];
match code {
codes::NULL => Ok((Self::Null, 1)),
codes::BOOLEAN_TRUE => Ok((Self::Boolean(true), 1)),
codes::BOOLEAN_FALSE => Ok((Self::Boolean(false), 1)),
codes::UBYTE => decode_ubyte(bytes).map(|(v, n)| (Self::Ubyte(v), n)),
codes::USHORT => decode_ushort(bytes).map(|(v, n)| (Self::Ushort(v), n)),
codes::UINT0 | codes::SMALLUINT | codes::UINT => {
decode_uint(bytes).map(|(v, n)| (Self::Uint(v), n))
}
codes::ULONG0 | codes::SMALLULONG | codes::ULONG => {
let (v, n) = crate::types::decode_value(bytes)?;
if let AmqpValue::Ulong(u) = v {
Ok((Self::Ulong(u), n))
} else {
Err(TypeError::UnsupportedFormatCode(code))
}
}
codes::BYTE => decode_byte(bytes).map(|(v, n)| (Self::Byte(v), n)),
codes::SHORT => decode_short(bytes).map(|(v, n)| (Self::Short(v), n)),
codes::SMALLINT | codes::INT => decode_int(bytes).map(|(v, n)| (Self::Int(v), n)),
codes::SMALLLONG | codes::LONG => {
let (v, n) = crate::types::decode_value(bytes)?;
if let AmqpValue::Long(l) = v {
Ok((Self::Long(l), n))
} else {
Err(TypeError::UnsupportedFormatCode(code))
}
}
codes::FLOAT => decode_float(bytes).map(|(v, n)| (Self::Float(v), n)),
codes::DOUBLE => decode_double(bytes).map(|(v, n)| (Self::Double(v), n)),
codes::CHAR => decode_char(bytes).map(|(v, n)| (Self::Char(v), n)),
codes::TIMESTAMP => decode_timestamp(bytes).map(|(v, n)| (Self::Timestamp(v), n)),
codes::UUID => decode_uuid(bytes).map(|(v, n)| (Self::Uuid(v), n)),
codes::VBIN8 | codes::VBIN32 => {
let (v, n) = crate::types::decode_value(bytes)?;
if let AmqpValue::Binary(b) = v {
Ok((Self::Binary(b), n))
} else {
Err(TypeError::UnsupportedFormatCode(code))
}
}
codes::STR8 | codes::STR32 => {
let (v, n) = crate::types::decode_value(bytes)?;
if let AmqpValue::String(s) = v {
Ok((Self::Str(s), n))
} else {
Err(TypeError::UnsupportedFormatCode(code))
}
}
codes::SYM8 | codes::SYM32 => {
let (v, n) = crate::types::decode_value(bytes)?;
if let AmqpValue::Symbol(s) = v {
Ok((Self::Symbol(s), n))
} else {
Err(TypeError::UnsupportedFormatCode(code))
}
}
codes::LIST0 => Ok((Self::List(Vec::new()), 1)),
codes::LIST8 | codes::LIST32 => decode_list(bytes, depth),
codes::MAP8 | codes::MAP32 => decode_map(bytes, depth),
codes::ARRAY8 | codes::ARRAY32 => decode_array(bytes, depth),
other => Err(TypeError::UnsupportedFormatCode(other)),
}
}
}
impl fmt::Display for AmqpExtValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
fn encode_list(items: &[AmqpExtValue], depth: usize) -> Result<Vec<u8>, TypeError> {
if items.is_empty() {
return Ok(alloc::vec![codes::LIST0]);
}
let mut body = Vec::new();
for it in items {
body.extend_from_slice(&it.encode_at(depth + 1)?);
}
let count = items.len();
let size = body.len() + 1; let use_short = body.len() <= 254 && count <= u8::MAX as usize;
if use_short {
let mut out = alloc::vec![codes::LIST8];
out.push((size) as u8);
out.push(count as u8);
out.extend_from_slice(&body);
Ok(out)
} else {
let mut out = alloc::vec![codes::LIST32];
let total_size = body.len() + 4;
out.extend_from_slice(
&u32::try_from(total_size)
.map_err(|_| TypeError::LengthTooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(
&u32::try_from(count)
.map_err(|_| TypeError::LengthTooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(&body);
Ok(out)
}
}
fn decode_list(bytes: &[u8], depth: usize) -> Result<(AmqpExtValue, usize), TypeError> {
if bytes.is_empty() {
return Err(TypeError::Truncated);
}
let (header, count, body_start, total) = match bytes[0] {
codes::LIST8 => {
if bytes.len() < 3 {
return Err(TypeError::Truncated);
}
let size = usize::from(bytes[1]);
let count = usize::from(bytes[2]);
(3, count, 3, 2 + size)
}
codes::LIST32 => {
if bytes.len() < 9 {
return Err(TypeError::Truncated);
}
let size = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
let count = u32::from_be_bytes([bytes[5], bytes[6], bytes[7], bytes[8]]) as usize;
(9, count, 9, 5 + size)
}
c => return Err(TypeError::UnsupportedFormatCode(c)),
};
let _ = header;
if bytes.len() < total {
return Err(TypeError::Truncated);
}
let mut items = Vec::with_capacity(count);
let mut cur = body_start;
for _ in 0..count {
let (v, n) = AmqpExtValue::decode_at(&bytes[cur..total], depth + 1)?;
items.push(v);
cur += n;
}
Ok((AmqpExtValue::List(items), total))
}
fn encode_map(
entries: &[(AmqpExtValue, AmqpExtValue)],
depth: usize,
) -> Result<Vec<u8>, TypeError> {
let mut body = Vec::new();
for (k, v) in entries {
body.extend_from_slice(&k.encode_at(depth + 1)?);
body.extend_from_slice(&v.encode_at(depth + 1)?);
}
let count = entries.len() * 2;
let use_short = body.len() <= 254 && count <= u8::MAX as usize;
if use_short {
let mut out = alloc::vec![codes::MAP8];
out.push((body.len() + 1) as u8);
out.push(count as u8);
out.extend_from_slice(&body);
Ok(out)
} else {
let mut out = alloc::vec![codes::MAP32];
let total_size = body.len() + 4;
out.extend_from_slice(
&u32::try_from(total_size)
.map_err(|_| TypeError::LengthTooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(
&u32::try_from(count)
.map_err(|_| TypeError::LengthTooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(&body);
Ok(out)
}
}
fn decode_map(bytes: &[u8], depth: usize) -> Result<(AmqpExtValue, usize), TypeError> {
if bytes.is_empty() {
return Err(TypeError::Truncated);
}
let (count, body_start, total) = match bytes[0] {
codes::MAP8 => {
if bytes.len() < 3 {
return Err(TypeError::Truncated);
}
(usize::from(bytes[2]), 3, 2 + usize::from(bytes[1]))
}
codes::MAP32 => {
if bytes.len() < 9 {
return Err(TypeError::Truncated);
}
let size = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
let count = u32::from_be_bytes([bytes[5], bytes[6], bytes[7], bytes[8]]) as usize;
(count, 9, 5 + size)
}
c => return Err(TypeError::UnsupportedFormatCode(c)),
};
if bytes.len() < total || count % 2 != 0 {
return Err(TypeError::Truncated);
}
let mut entries = Vec::with_capacity(count / 2);
let mut cur = body_start;
for _ in 0..count / 2 {
let (k, kn) = AmqpExtValue::decode_at(&bytes[cur..total], depth + 1)?;
cur += kn;
let (v, vn) = AmqpExtValue::decode_at(&bytes[cur..total], depth + 1)?;
cur += vn;
entries.push((k, v));
}
Ok((AmqpExtValue::Map(entries), total))
}
fn encode_array(items: &[AmqpExtValue], depth: usize) -> Result<Vec<u8>, TypeError> {
if items.is_empty() {
return Err(TypeError::LengthTooLarge); }
let element_constructor = items[0].encode_at(depth + 1)?;
if element_constructor.is_empty() {
return Err(TypeError::LengthTooLarge);
}
let constructor_byte = element_constructor[0];
let mut body = Vec::new();
body.push(constructor_byte);
for it in items {
let enc = it.encode_at(depth + 1)?;
if enc.is_empty() || enc[0] != constructor_byte {
return Err(TypeError::UnsupportedFormatCode(constructor_byte));
}
body.extend_from_slice(&enc[1..]);
}
let count = items.len();
let use_short = body.len() <= 254 && count <= u8::MAX as usize;
if use_short {
let mut out = alloc::vec![codes::ARRAY8];
out.push((body.len() + 1) as u8);
out.push(count as u8);
out.extend_from_slice(&body);
Ok(out)
} else {
let mut out = alloc::vec![codes::ARRAY32];
let total_size = body.len() + 4;
out.extend_from_slice(
&u32::try_from(total_size)
.map_err(|_| TypeError::LengthTooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(
&u32::try_from(count)
.map_err(|_| TypeError::LengthTooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(&body);
Ok(out)
}
}
fn decode_array(bytes: &[u8], depth: usize) -> Result<(AmqpExtValue, usize), TypeError> {
if bytes.is_empty() {
return Err(TypeError::Truncated);
}
let (count, body_start, total) = match bytes[0] {
codes::ARRAY8 => {
if bytes.len() < 3 {
return Err(TypeError::Truncated);
}
(usize::from(bytes[2]), 3, 2 + usize::from(bytes[1]))
}
codes::ARRAY32 => {
if bytes.len() < 9 {
return Err(TypeError::Truncated);
}
let size = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
let count = u32::from_be_bytes([bytes[5], bytes[6], bytes[7], bytes[8]]) as usize;
(count, 9, 5 + size)
}
c => return Err(TypeError::UnsupportedFormatCode(c)),
};
if bytes.len() < total || body_start >= total {
return Err(TypeError::Truncated);
}
let constructor_byte = bytes[body_start];
let mut items = Vec::with_capacity(count);
let mut cur = body_start + 1;
for _ in 0..count {
let mut elem = alloc::vec![constructor_byte];
elem.extend_from_slice(&bytes[cur..total]);
let (v, n) = AmqpExtValue::decode_at(&elem, depth + 1)?;
items.push(v);
cur += n - 1; }
Ok((AmqpExtValue::Array(items), total))
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
fn roundtrip(v: AmqpExtValue) {
let bytes = v.encode().expect("encode");
let (parsed, _) = AmqpExtValue::decode(&bytes).expect("decode");
assert_eq!(parsed, v);
}
#[test]
fn ubyte_round_trips_at_extremes() {
roundtrip(AmqpExtValue::Ubyte(0));
roundtrip(AmqpExtValue::Ubyte(255));
}
#[test]
fn ushort_round_trips() {
roundtrip(AmqpExtValue::Ushort(0));
roundtrip(AmqpExtValue::Ushort(0xABCD));
}
#[test]
fn uint_uses_compact_zero_form() {
let bytes = AmqpExtValue::Uint(0).encode().expect("encode");
assert_eq!(bytes, alloc::vec![codes::UINT0]);
}
#[test]
fn uint_uses_smalluint_for_low_values() {
let bytes = AmqpExtValue::Uint(42).encode().expect("encode");
assert_eq!(bytes[0], codes::SMALLUINT);
}
#[test]
fn uint_uses_full_for_high_values() {
let bytes = AmqpExtValue::Uint(0x1234_5678).encode().expect("encode");
assert_eq!(bytes[0], codes::UINT);
roundtrip(AmqpExtValue::Uint(0x1234_5678));
}
#[test]
fn byte_round_trips_negative() {
roundtrip(AmqpExtValue::Byte(-1));
roundtrip(AmqpExtValue::Byte(127));
roundtrip(AmqpExtValue::Byte(-128));
}
#[test]
fn short_round_trips() {
roundtrip(AmqpExtValue::Short(-1));
roundtrip(AmqpExtValue::Short(i16::MAX));
roundtrip(AmqpExtValue::Short(i16::MIN));
}
#[test]
fn int_uses_compact_when_in_byte_range() {
let bytes = AmqpExtValue::Int(42).encode().expect("encode");
assert_eq!(bytes[0], codes::SMALLINT);
roundtrip(AmqpExtValue::Int(42));
}
#[test]
fn int_uses_full_for_high_values() {
roundtrip(AmqpExtValue::Int(i32::MAX));
roundtrip(AmqpExtValue::Int(i32::MIN));
}
#[test]
fn float_round_trips() {
roundtrip(AmqpExtValue::Float(0.0));
roundtrip(AmqpExtValue::Float(core::f32::consts::PI));
roundtrip(AmqpExtValue::Float(-1.5));
}
#[test]
fn double_round_trips() {
roundtrip(AmqpExtValue::Double(core::f64::consts::E));
}
#[test]
fn char_round_trips_ascii_and_unicode() {
roundtrip(AmqpExtValue::Char('A'));
roundtrip(AmqpExtValue::Char('ä'));
roundtrip(AmqpExtValue::Char('🎉'));
}
#[test]
fn timestamp_round_trips_negative() {
roundtrip(AmqpExtValue::Timestamp(-1));
roundtrip(AmqpExtValue::Timestamp(1_700_000_000_000));
}
#[test]
fn uuid_round_trips() {
let mut u = [0u8; 16];
for (i, b) in u.iter_mut().enumerate() {
*b = i as u8;
}
roundtrip(AmqpExtValue::Uuid(u));
}
#[test]
fn empty_list_uses_list0() {
let bytes = AmqpExtValue::List(Vec::new()).encode().expect("encode");
assert_eq!(bytes, alloc::vec![codes::LIST0]);
}
#[test]
fn small_list_round_trips() {
roundtrip(AmqpExtValue::List(alloc::vec![
AmqpExtValue::Boolean(true),
AmqpExtValue::Int(42),
AmqpExtValue::Str("hello".to_string())
]));
}
#[test]
fn nested_list_round_trips() {
let inner = AmqpExtValue::List(alloc::vec![AmqpExtValue::Int(1), AmqpExtValue::Int(2)]);
let outer = AmqpExtValue::List(alloc::vec![inner.clone(), inner]);
roundtrip(outer);
}
#[test]
fn map_round_trips_with_string_keys() {
roundtrip(AmqpExtValue::Map(alloc::vec![
(AmqpExtValue::Str("k1".to_string()), AmqpExtValue::Int(1)),
(
AmqpExtValue::Str("k2".to_string()),
AmqpExtValue::Boolean(true)
),
]));
}
#[test]
fn array_round_trips_homogeneous_short() {
roundtrip(AmqpExtValue::Array(alloc::vec![
AmqpExtValue::Short(10),
AmqpExtValue::Short(20),
AmqpExtValue::Short(30)
]));
}
#[test]
fn array_homogeneous_constraint_violation_yields_error() {
let r = AmqpExtValue::Array(alloc::vec![
AmqpExtValue::Int(1),
AmqpExtValue::Boolean(true)
])
.encode();
assert!(r.is_err());
}
#[test]
fn deeply_nested_list_exceeds_dos_cap() {
let mut current = AmqpExtValue::Int(0);
for _ in 0..(MAX_COMPOUND_DEPTH + 5) {
current = AmqpExtValue::List(alloc::vec![current]);
}
assert!(current.encode().is_err());
}
#[test]
fn decimal32_64_128_have_correct_lengths() {
assert_eq!(encode_decimal32([0; 4]).len(), 5);
assert_eq!(encode_decimal64([0; 8]).len(), 9);
assert_eq!(encode_decimal128([0; 16]).len(), 17);
}
#[test]
fn ext_value_display_non_empty() {
let s = alloc::format!("{}", AmqpExtValue::Ubyte(42));
assert!(s.contains("42"), "Display must include value, got '{s}'");
let s2 = alloc::format!("{}", AmqpExtValue::Boolean(true));
assert!(s2.contains("true") || s2.contains("Boolean"));
}
#[test]
fn decode_at_depth_at_cap_accepted() {
let bytes = AmqpExtValue::Ubyte(7).encode().unwrap();
let res = AmqpExtValue::decode_at(&bytes, MAX_COMPOUND_DEPTH);
assert!(res.is_ok(), "depth=MAX must decode, got {res:?}");
}
#[test]
fn decode_at_depth_over_cap_rejected() {
let bytes = AmqpExtValue::Ubyte(7).encode().unwrap();
let res = AmqpExtValue::decode_at(&bytes, MAX_COMPOUND_DEPTH + 1);
assert!(matches!(res, Err(TypeError::LengthTooLarge)));
}
#[test]
fn encode_map_uses_long_form_when_count_exceeds_u8() {
let mut entries = Vec::new();
for i in 0..130u32 {
entries.push((AmqpExtValue::Uint(i), AmqpExtValue::Uint(i + 1)));
}
let bytes = AmqpExtValue::Map(entries.clone()).encode().unwrap();
assert_eq!(bytes[0], codes::MAP32, "expected MAP32 for count>255");
let (parsed, _) = AmqpExtValue::decode(&bytes).unwrap();
assert_eq!(parsed, AmqpExtValue::Map(entries));
}
#[test]
fn encode_array_uses_long_form_when_count_exceeds_u8() {
let items: Vec<AmqpExtValue> = (0..300u32)
.map(|i| AmqpExtValue::Ubyte((i & 0xff) as u8))
.collect();
let bytes = AmqpExtValue::Array(items.clone()).encode().unwrap();
assert_eq!(bytes[0], codes::ARRAY32, "expected ARRAY32 for count>255");
let (parsed, _) = AmqpExtValue::decode(&bytes).unwrap();
assert_eq!(parsed, AmqpExtValue::Array(items));
}
#[test]
fn decode_array_at_minimum_buffer_size() {
let bytes = AmqpExtValue::Array(vec![AmqpExtValue::Ubyte(0xAB)])
.encode()
.unwrap();
assert_eq!(bytes[0], codes::ARRAY8);
let (parsed, n) = AmqpExtValue::decode(&bytes).unwrap();
assert_eq!(n, bytes.len());
if let AmqpExtValue::Array(items) = parsed {
assert_eq!(items.len(), 1);
assert_eq!(items[0], AmqpExtValue::Ubyte(0xAB));
} else {
panic!("expected Array");
}
}
#[test]
fn list_map_array_roundtrip_concrete_values() {
roundtrip(AmqpExtValue::List(vec![
AmqpExtValue::Ubyte(1),
AmqpExtValue::Ushort(2),
AmqpExtValue::Uint(3),
]));
roundtrip(AmqpExtValue::Map(vec![
(AmqpExtValue::Str("a".into()), AmqpExtValue::Uint(1)),
(AmqpExtValue::Str("b".into()), AmqpExtValue::Uint(2)),
]));
roundtrip(AmqpExtValue::Array(vec![
AmqpExtValue::Ubyte(10),
AmqpExtValue::Ubyte(20),
AmqpExtValue::Ubyte(30),
]));
roundtrip(AmqpExtValue::List(vec![AmqpExtValue::List(vec![
AmqpExtValue::Ubyte(42),
])]));
}
}