use crate::utils::truncate::truncate_str;
use anyhow::{anyhow, Result};
use serde_json::{Map, Value};
pub const PROTOCOL_HEADER_091: [u8; 8] = *b"AMQP\x00\x00\x09\x01";
pub const FRAME_METHOD: u8 = 1;
pub const FRAME_HEADER: u8 = 2;
pub const FRAME_BODY: u8 = 3;
pub const FRAME_HEARTBEAT: u8 = 8;
pub const FRAME_END: u8 = 0xCE;
pub const FRAME_OVERHEAD: usize = 8;
pub const CLASS_CONNECTION: u16 = 10;
pub const CLASS_CHANNEL: u16 = 20;
pub const CLASS_EXCHANGE: u16 = 40;
pub const CLASS_QUEUE: u16 = 50;
pub const CLASS_BASIC: u16 = 60;
pub const CONNECTION_START: u16 = 10;
pub const CONNECTION_START_OK: u16 = 11;
pub const CONNECTION_TUNE: u16 = 30;
pub const CONNECTION_TUNE_OK: u16 = 31;
pub const CONNECTION_OPEN: u16 = 40;
pub const CONNECTION_OPEN_OK: u16 = 41;
pub const CONNECTION_CLOSE: u16 = 50;
pub const CONNECTION_CLOSE_OK: u16 = 51;
pub const CHANNEL_OPEN: u16 = 10;
pub const CHANNEL_OPEN_OK: u16 = 11;
pub const CHANNEL_CLOSE: u16 = 40;
pub const CHANNEL_CLOSE_OK: u16 = 41;
pub const EXCHANGE_DECLARE: u16 = 10;
pub const EXCHANGE_DECLARE_OK: u16 = 11;
pub const QUEUE_DECLARE: u16 = 10;
pub const QUEUE_DECLARE_OK: u16 = 11;
pub const QUEUE_BIND: u16 = 20;
pub const QUEUE_BIND_OK: u16 = 21;
pub const BASIC_QOS: u16 = 10;
pub const BASIC_QOS_OK: u16 = 11;
pub const BASIC_CONSUME: u16 = 20;
pub const BASIC_CONSUME_OK: u16 = 21;
pub const BASIC_CANCEL: u16 = 30;
pub const BASIC_CANCEL_OK: u16 = 31;
pub const BASIC_PUBLISH: u16 = 40;
pub const BASIC_RETURN: u16 = 50;
pub const BASIC_DELIVER: u16 = 60;
pub const BASIC_ACK: u16 = 80;
pub const BASIC_REJECT: u16 = 90;
pub const BASIC_NACK: u16 = 120;
pub const REPLY_NOT_IMPLEMENTED: u16 = 540;
pub const REPLY_ACCESS_REFUSED: u16 = 403;
pub const REPLY_RESOURCE_LOCKED: u16 = 405;
pub fn method_name(class_id: u16, method_id: u16) -> String {
let name = match (class_id, method_id) {
(CLASS_CONNECTION, CONNECTION_START) => "connection.start",
(CLASS_CONNECTION, CONNECTION_START_OK) => "connection.start-ok",
(CLASS_CONNECTION, CONNECTION_TUNE) => "connection.tune",
(CLASS_CONNECTION, CONNECTION_TUNE_OK) => "connection.tune-ok",
(CLASS_CONNECTION, CONNECTION_OPEN) => "connection.open",
(CLASS_CONNECTION, CONNECTION_OPEN_OK) => "connection.open-ok",
(CLASS_CONNECTION, CONNECTION_CLOSE) => "connection.close",
(CLASS_CONNECTION, CONNECTION_CLOSE_OK) => "connection.close-ok",
(CLASS_CHANNEL, CHANNEL_OPEN) => "channel.open",
(CLASS_CHANNEL, CHANNEL_OPEN_OK) => "channel.open-ok",
(CLASS_CHANNEL, CHANNEL_CLOSE) => "channel.close",
(CLASS_CHANNEL, CHANNEL_CLOSE_OK) => "channel.close-ok",
(CLASS_EXCHANGE, EXCHANGE_DECLARE) => "exchange.declare",
(CLASS_QUEUE, QUEUE_DECLARE) => "queue.declare",
(CLASS_QUEUE, QUEUE_BIND) => "queue.bind",
(CLASS_BASIC, BASIC_QOS) => "basic.qos",
(CLASS_BASIC, BASIC_CONSUME) => "basic.consume",
(CLASS_BASIC, BASIC_CANCEL) => "basic.cancel",
(CLASS_BASIC, BASIC_PUBLISH) => "basic.publish",
(CLASS_BASIC, BASIC_ACK) => "basic.ack",
(CLASS_BASIC, BASIC_REJECT) => "basic.reject",
(CLASS_BASIC, BASIC_NACK) => "basic.nack",
_ => "",
};
if name.is_empty() {
format!("class {} method {}", class_id, method_id)
} else {
name.to_string()
}
}
const MAX_FIELD_TABLE_DEPTH: usize = 32;
pub const MAX_SHORT_STRING_LEN: usize = 255;
pub struct Decoder<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Decoder<'a> {
pub fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
pub fn remaining(&self) -> usize {
self.buf.len().saturating_sub(self.pos)
}
fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.ok_or_else(|| anyhow!("AMQP field length {} overflows the payload offset", n))?;
let slice = self.buf.get(self.pos..end).ok_or_else(|| {
anyhow!(
"AMQP payload truncated: {} bytes wanted at offset {}, only {} available",
n,
self.pos,
self.remaining()
)
})?;
self.pos = end;
Ok(slice)
}
pub fn u8(&mut self) -> Result<u8> {
let b: [u8; 1] = self
.take(1)?
.try_into()
.map_err(|_| anyhow!("AMQP octet read failed"))?;
Ok(b[0])
}
pub fn u16(&mut self) -> Result<u16> {
let b: [u8; 2] = self
.take(2)?
.try_into()
.map_err(|_| anyhow!("AMQP short read failed"))?;
Ok(u16::from_be_bytes(b))
}
pub fn u32(&mut self) -> Result<u32> {
let b: [u8; 4] = self
.take(4)?
.try_into()
.map_err(|_| anyhow!("AMQP long read failed"))?;
Ok(u32::from_be_bytes(b))
}
pub fn u64(&mut self) -> Result<u64> {
let b: [u8; 8] = self
.take(8)?
.try_into()
.map_err(|_| anyhow!("AMQP long-long read failed"))?;
Ok(u64::from_be_bytes(b))
}
pub fn short_string(&mut self) -> Result<String> {
let len = self.u8()? as usize;
let bytes = self.take(len)?;
Ok(String::from_utf8_lossy(bytes).into_owned())
}
pub fn long_bytes(&mut self) -> Result<&'a [u8]> {
let len = self.u32()? as usize;
self.take(len)
}
pub fn long_string(&mut self) -> Result<String> {
let bytes = self.long_bytes()?;
Ok(String::from_utf8_lossy(bytes).into_owned())
}
pub fn bits(&mut self, count: usize) -> Result<Vec<bool>> {
let bytes = self.take(count.div_ceil(8))?;
let mut out = Vec::with_capacity(count);
for i in 0..count {
let byte = bytes.get(i / 8).copied().unwrap_or(0);
out.push(byte & (1u8 << (i % 8)) != 0);
}
Ok(out)
}
pub fn field_table(&mut self) -> Result<Value> {
self.field_table_at(0)
}
fn field_table_at(&mut self, depth: usize) -> Result<Value> {
if depth >= MAX_FIELD_TABLE_DEPTH {
return Err(anyhow!(
"AMQP field table nested deeper than {} levels",
MAX_FIELD_TABLE_DEPTH
));
}
let body = self.long_bytes()?;
let mut inner = Decoder::new(body);
let mut map = Map::new();
while inner.remaining() > 0 {
let Ok(key) = inner.short_string() else { break };
match inner.field_value_at(depth + 1) {
Ok(value) => {
map.insert(key, value);
}
Err(_) => break,
}
}
Ok(Value::Object(map))
}
fn field_value_at(&mut self, depth: usize) -> Result<Value> {
if depth >= MAX_FIELD_TABLE_DEPTH {
return Err(anyhow!(
"AMQP field table nested deeper than {} levels",
MAX_FIELD_TABLE_DEPTH
));
}
let tag = self.u8()?;
let value = match tag {
b't' => Value::Bool(self.u8()? != 0),
b'b' => Value::from(self.u8()? as i8),
b'B' => Value::from(self.u8()?),
b's' | b'U' => Value::from(self.u16()? as i16),
b'u' => Value::from(self.u16()?),
b'I' => Value::from(self.u32()? as i32),
b'i' => Value::from(self.u32()?),
b'l' | b'L' => Value::from(self.u64()? as i64),
b'f' => Value::from(f32::from_bits(self.u32()?)),
b'd' => Value::from(f64::from_bits(self.u64()?)),
b'D' => {
let scale = self.u8()? as u32;
let raw = self.u32()? as i32;
Value::from(raw as f64 / 10f64.powi(scale as i32))
}
b'S' => Value::String(self.long_string()?),
b'A' => {
let body = self.long_bytes()?;
let mut inner = Decoder::new(body);
let mut items = Vec::new();
while inner.remaining() > 0 {
match inner.field_value_at(depth + 1) {
Ok(v) => items.push(v),
Err(_) => break,
}
}
Value::Array(items)
}
b'T' => Value::from(self.u64()?),
b'F' => self.field_table_at(depth + 1)?,
b'x' => {
let bytes = self.long_bytes()?;
Value::String(String::from_utf8_lossy(bytes).into_owned())
}
b'V' => Value::Null,
other => {
return Err(anyhow!(
"unknown AMQP field-table type id {:?}",
other as char
))
}
};
Ok(value)
}
}
#[derive(Default)]
pub struct Encoder {
buf: Vec<u8>,
}
impl Encoder {
pub fn new() -> Self {
Self { buf: Vec::new() }
}
pub fn u8(&mut self, v: u8) {
self.buf.push(v);
}
pub fn u16(&mut self, v: u16) {
self.buf.extend_from_slice(&v.to_be_bytes());
}
pub fn u32(&mut self, v: u32) {
self.buf.extend_from_slice(&v.to_be_bytes());
}
pub fn u64(&mut self, v: u64) {
self.buf.extend_from_slice(&v.to_be_bytes());
}
pub fn short_string(&mut self, s: &str) -> Result<()> {
if s.len() > MAX_SHORT_STRING_LEN {
return Err(anyhow!(
"AMQP shortstr cannot carry {} bytes; the wire format's length octet stops at \
{}. Shorten the name rather than letting it be cut, which would name \
something else: {}",
s.len(),
MAX_SHORT_STRING_LEN,
crate::utils::truncate_for_log(s, 48)
));
}
self.buf.push(s.len() as u8);
self.buf.extend_from_slice(s.as_bytes());
Ok(())
}
pub fn reply_text(&mut self, text: &str) {
let text = truncate_str(text, MAX_SHORT_STRING_LEN);
self.buf.push(text.len() as u8);
self.buf.extend_from_slice(text.as_bytes());
}
pub fn long_string(&mut self, bytes: &[u8]) {
self.u32(bytes.len() as u32);
self.buf.extend_from_slice(bytes);
}
pub fn bits(&mut self, bits: &[bool]) {
for chunk in bits.chunks(8) {
let mut byte = 0u8;
for (i, set) in chunk.iter().enumerate() {
if *set {
byte |= 1u8 << i;
}
}
self.buf.push(byte);
}
}
pub fn field_table(&mut self, value: &Value) -> Result<()> {
self.field_table_at(value, 0)
}
fn field_table_at(&mut self, value: &Value, depth: usize) -> Result<()> {
if depth >= MAX_FIELD_TABLE_DEPTH {
return Err(anyhow!(
"AMQP field table nested deeper than {} levels",
MAX_FIELD_TABLE_DEPTH
));
}
let mut inner = Encoder::new();
if let Some(map) = value.as_object() {
for (key, val) in map {
inner.short_string(key)?;
inner.field_value_at(val, depth + 1)?;
}
}
self.long_string(&inner.buf);
Ok(())
}
fn field_value_at(&mut self, value: &Value, depth: usize) -> Result<()> {
if depth >= MAX_FIELD_TABLE_DEPTH {
return Err(anyhow!(
"AMQP field table nested deeper than {} levels",
MAX_FIELD_TABLE_DEPTH
));
}
match value {
Value::Null => self.u8(b'V'),
Value::Bool(b) => {
self.u8(b't');
self.u8(u8::from(*b));
}
Value::Number(n) => {
if let Some(i) = n.as_i64() {
self.u8(b'l');
self.u64(i as u64);
} else {
self.u8(b'd');
self.u64(n.as_f64().unwrap_or(0.0).to_bits());
}
}
Value::String(s) => {
self.u8(b'S');
self.long_string(s.as_bytes());
}
Value::Array(items) => {
let mut inner = Encoder::new();
for item in items {
inner.field_value_at(item, depth + 1)?;
}
self.u8(b'A');
self.long_string(&inner.buf);
}
Value::Object(_) => {
let mut inner = Encoder::new();
inner.field_table_at(value, depth + 1)?;
self.u8(b'F');
self.buf.extend_from_slice(&inner.buf);
}
}
Ok(())
}
pub fn into_vec(self) -> Vec<u8> {
self.buf
}
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
}
pub struct Frame {
pub frame_type: u8,
pub channel: u16,
pub payload: Vec<u8>,
}
pub fn method_frame(channel: u16, class_id: u16, method_id: u16, args: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(4 + args.len());
payload.extend_from_slice(&class_id.to_be_bytes());
payload.extend_from_slice(&method_id.to_be_bytes());
payload.extend_from_slice(args);
raw_frame(FRAME_METHOD, channel, &payload)
}
pub fn content_header_frame(channel: u16, class_id: u16, body_size: u64, props: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(12 + props.len());
payload.extend_from_slice(&class_id.to_be_bytes());
payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&body_size.to_be_bytes());
payload.extend_from_slice(props);
raw_frame(FRAME_HEADER, channel, &payload)
}
pub fn body_frames(channel: u16, body: &[u8], max_frame_size: usize) -> Vec<Vec<u8>> {
let chunk_size = max_frame_size.saturating_sub(FRAME_OVERHEAD).max(1);
body.chunks(chunk_size)
.map(|chunk| raw_frame(FRAME_BODY, channel, chunk))
.collect()
}
pub fn heartbeat_frame() -> Vec<u8> {
raw_frame(FRAME_HEARTBEAT, 0, &[])
}
fn raw_frame(frame_type: u8, channel: u16, payload: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(FRAME_OVERHEAD + payload.len());
out.push(frame_type);
out.extend_from_slice(&channel.to_be_bytes());
out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
out.extend_from_slice(payload);
out.push(FRAME_END);
out
}
#[derive(Default, Clone, Debug)]
pub struct BasicProperties {
pub content_type: Option<String>,
pub content_encoding: Option<String>,
pub headers: Option<Value>,
pub delivery_mode: Option<u8>,
pub priority: Option<u8>,
pub correlation_id: Option<String>,
pub reply_to: Option<String>,
pub expiration: Option<String>,
pub message_id: Option<String>,
pub timestamp: Option<u64>,
pub kind: Option<String>,
pub user_id: Option<String>,
pub app_id: Option<String>,
pub cluster_id: Option<String>,
}
impl BasicProperties {
pub fn decode(d: &mut Decoder<'_>) -> Result<Self> {
let flags = d.u16()?;
let mut word = flags;
let mut words = 1;
while word & 1 != 0 {
if words >= 8 {
return Err(anyhow!(
"AMQP content header declares more than 8 property flag words"
));
}
word = d.u16()?;
words += 1;
}
let has = |bit: u32| flags & (1 << (15 - bit)) != 0;
let mut props = BasicProperties::default();
if has(0) {
props.content_type = Some(d.short_string()?);
}
if has(1) {
props.content_encoding = Some(d.short_string()?);
}
if has(2) {
props.headers = Some(d.field_table()?);
}
if has(3) {
props.delivery_mode = Some(d.u8()?);
}
if has(4) {
props.priority = Some(d.u8()?);
}
if has(5) {
props.correlation_id = Some(d.short_string()?);
}
if has(6) {
props.reply_to = Some(d.short_string()?);
}
if has(7) {
props.expiration = Some(d.short_string()?);
}
if has(8) {
props.message_id = Some(d.short_string()?);
}
if has(9) {
props.timestamp = Some(d.u64()?);
}
if has(10) {
props.kind = Some(d.short_string()?);
}
if has(11) {
props.user_id = Some(d.short_string()?);
}
if has(12) {
props.app_id = Some(d.short_string()?);
}
if has(13) {
props.cluster_id = Some(d.short_string()?);
}
Ok(props)
}
pub fn encode(&self) -> Result<Vec<u8>> {
let present = [
self.content_type.is_some(),
self.content_encoding.is_some(),
self.headers.is_some(),
self.delivery_mode.is_some(),
self.priority.is_some(),
self.correlation_id.is_some(),
self.reply_to.is_some(),
self.expiration.is_some(),
self.message_id.is_some(),
self.timestamp.is_some(),
self.kind.is_some(),
self.user_id.is_some(),
self.app_id.is_some(),
self.cluster_id.is_some(),
];
let mut flags: u16 = 0;
for (bit, set) in present.iter().enumerate() {
if *set {
flags |= 1 << (15 - bit as u16);
}
}
let mut e = Encoder::new();
e.u16(flags);
if let Some(v) = &self.content_type {
e.short_string(v)?;
}
if let Some(v) = &self.content_encoding {
e.short_string(v)?;
}
if let Some(v) = &self.headers {
e.field_table(v)?;
}
if let Some(v) = self.delivery_mode {
e.u8(v);
}
if let Some(v) = self.priority {
e.u8(v);
}
if let Some(v) = &self.correlation_id {
e.short_string(v)?;
}
if let Some(v) = &self.reply_to {
e.short_string(v)?;
}
if let Some(v) = &self.expiration {
e.short_string(v)?;
}
if let Some(v) = &self.message_id {
e.short_string(v)?;
}
if let Some(v) = self.timestamp {
e.u64(v);
}
if let Some(v) = &self.kind {
e.short_string(v)?;
}
if let Some(v) = &self.user_id {
e.short_string(v)?;
}
if let Some(v) = &self.app_id {
e.short_string(v)?;
}
if let Some(v) = &self.cluster_id {
e.short_string(v)?;
}
Ok(e.into_vec())
}
pub fn to_json(&self) -> Value {
let mut map = Map::new();
if let Some(v) = &self.content_type {
map.insert("content_type".into(), Value::String(v.clone()));
}
if let Some(v) = &self.content_encoding {
map.insert("content_encoding".into(), Value::String(v.clone()));
}
if let Some(v) = &self.headers {
map.insert("headers".into(), v.clone());
}
if let Some(v) = self.delivery_mode {
map.insert("delivery_mode".into(), Value::from(v));
}
if let Some(v) = self.priority {
map.insert("priority".into(), Value::from(v));
}
if let Some(v) = &self.correlation_id {
map.insert("correlation_id".into(), Value::String(v.clone()));
}
if let Some(v) = &self.reply_to {
map.insert("reply_to".into(), Value::String(v.clone()));
}
if let Some(v) = &self.expiration {
map.insert("expiration".into(), Value::String(v.clone()));
}
if let Some(v) = &self.message_id {
map.insert("message_id".into(), Value::String(v.clone()));
}
if let Some(v) = self.timestamp {
map.insert("timestamp".into(), Value::from(v));
}
if let Some(v) = &self.kind {
map.insert("type".into(), Value::String(v.clone()));
}
if let Some(v) = &self.user_id {
map.insert("user_id".into(), Value::String(v.clone()));
}
if let Some(v) = &self.app_id {
map.insert("app_id".into(), Value::String(v.clone()));
}
if let Some(v) = &self.cluster_id {
map.insert("cluster_id".into(), Value::String(v.clone()));
}
Value::Object(map)
}
}
pub fn parse_plain_response(response: &str) -> (Option<String>, bool) {
let mut parts = response.split('\0');
let _authzid = parts.next();
let username = parts.next().filter(|s| !s.is_empty()).map(str::to_string);
let has_password = parts.next().is_some_and(|p| !p.is_empty());
(username, has_password)
}