use bytes::{Buf, BufMut, BytesMut};
use std::io;
use tokio_util::codec::{Decoder, Encoder};
use crate::frame::Frame;
use crate::parser::{DEFAULT_MAX_FRAME_SIZE, parse_frame_slice_bounded, unescape_header_value};
fn escape_header_value(input: &str) -> String {
let mut result = String::with_capacity(input.len());
for ch in input.chars() {
match ch {
'\\' => result.push_str("\\\\"),
'\r' => result.push_str("\\r"),
'\n' => result.push_str("\\n"),
':' => result.push_str("\\c"),
_ => result.push(ch),
}
}
result
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StompItem {
Frame(Frame),
Heartbeat,
}
pub struct StompCodec {
max_frame_size: usize,
}
impl StompCodec {
pub fn new() -> Self {
Self {
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
}
}
pub fn with_max_frame_size(max_frame_size: usize) -> Self {
Self { max_frame_size }
}
}
impl Default for StompCodec {
fn default() -> Self {
Self::new()
}
}
impl Decoder for StompCodec {
type Item = StompItem;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match src.chunk().first() {
Some(b'\n') => {
src.advance(1);
return Ok(Some(StompItem::Heartbeat));
}
Some(b'\r') => match src.chunk().get(1) {
Some(b'\n') => {
src.advance(2);
return Ok(Some(StompItem::Heartbeat));
}
None => return Ok(None),
Some(_) => {}
},
_ => {}
}
let chunk = src.chunk();
match parse_frame_slice_bounded(chunk, self.max_frame_size) {
Ok(Some((cmd_bytes, headers, body, consumed))) => {
if consumed > self.max_frame_size {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"frame size {} exceeds maximum frame size {}",
consumed, self.max_frame_size
),
));
}
src.advance(consumed);
let command = String::from_utf8(cmd_bytes).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid utf8 in command: {}", e),
)
})?;
let mut hdrs: Vec<(String, String)> = Vec::new();
for (k, v) in headers {
let k_unescaped = unescape_header_value(&k).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid escape in header key: {}", e),
)
})?;
let ks = String::from_utf8(k_unescaped).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid utf8 in header key: {}", e),
)
})?;
let v_unescaped = unescape_header_value(&v).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid escape in header value: {}", e),
)
})?;
let vs = String::from_utf8(v_unescaped).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid utf8 in header value: {}", e),
)
})?;
hdrs.push((ks, vs));
}
let body = body.unwrap_or_default();
let frame = Frame {
command,
headers: hdrs,
body,
};
Ok(Some(StompItem::Frame(frame)))
}
Ok(None) => {
if src.len() > self.max_frame_size {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame exceeds maximum frame size {}", self.max_frame_size),
));
}
Ok(None)
}
Err(e) => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("parse error: {}", e),
)),
}
}
}
impl Encoder<StompItem> for StompCodec {
type Error = io::Error;
fn encode(&mut self, item: StompItem, dst: &mut BytesMut) -> Result<(), Self::Error> {
match item {
StompItem::Heartbeat => {
dst.put_u8(b'\n');
}
StompItem::Frame(frame) => {
dst.extend_from_slice(frame.command.as_bytes());
dst.put_u8(b'\n');
let mut headers = frame.headers;
let has_cl = headers
.iter()
.any(|(k, _)| k.to_lowercase() == "content-length");
if !has_cl {
let include_cl =
frame.body.contains(&0) || std::str::from_utf8(&frame.body).is_err();
if include_cl {
headers.push(("content-length".to_string(), frame.body.len().to_string()));
}
}
for (k, v) in headers {
let escaped_key = escape_header_value(&k);
let escaped_val = escape_header_value(&v);
dst.extend_from_slice(escaped_key.as_bytes());
dst.put_u8(b':');
dst.extend_from_slice(escaped_val.as_bytes());
dst.put_u8(b'\n');
}
dst.put_slice(b"\n");
dst.extend_from_slice(&frame.body);
dst.put_u8(0);
}
}
Ok(())
}
}