use std::io::{self, Cursor};
use bytes::{Buf, BufMut, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
use xxhash_rust::xxh3::xxh3_64;
use crate::FrameSize;
use crate::proto::framing::FramingMessage;
#[derive(Clone, Debug, Default)]
pub struct FramingCodec;
impl Encoder<FramingMessage> for FramingCodec {
type Error = io::Error;
fn encode(&mut self, msg: FramingMessage, dst: &mut BytesMut) -> io::Result<()> {
let payload_len = msg.data.len() + msg.padding.len();
if payload_len > Self::mss(Self::mtu()) {
return Err(io::Error::from(io::ErrorKind::FileTooLarge));
}
let total_len = Self::max_header_len() + payload_len;
dst.reserve(total_len);
let total_len = u16::try_from(total_len).unwrap();
let data_len = u16::try_from(msg.data.len()).unwrap();
let len_pos = dst.len();
let payload_pos = len_pos + 12;
dst.put_u16(total_len);
dst.put_u16(data_len);
dst.put_u64(xxh3_64(&dst[len_pos..len_pos + 4]));
dst.put(msg.data);
dst.put(msg.padding);
dst.put_u64(xxh3_64(&dst[payload_pos..payload_pos + payload_len]));
Ok(())
}
}
impl Decoder for FramingCodec {
type Item = FramingMessage;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> io::Result<Option<Self::Item>> {
let mut reader = Cursor::new(src as &BytesMut);
if reader.remaining() < 12 {
return Ok(None);
}
let len_crc_dst = xxh3_64(&src[0..4]);
let total_len = reader.get_u16();
let data_len = reader.get_u16();
let len_crc_src = reader.get_u64();
if len_crc_src != len_crc_dst {
log::debug!("Framing codec: invalid length crc");
return Err(std::io::ErrorKind::InvalidData.into());
}
let total_len = total_len as usize;
let data_len = data_len as usize;
let padding_len = total_len - Self::max_header_len() - data_len;
if total_len < Self::max_header_len() {
log::debug!("Framing codec: invalid length value");
return Err(std::io::ErrorKind::InvalidInput.into());
}
if src.len() < total_len {
return Ok(None);
}
let mut msg_bytes = src.split_to(total_len);
msg_bytes.advance(12);
let payload_crc_dst = xxh3_64(&msg_bytes[0..data_len + padding_len]);
let data = msg_bytes.split_to(data_len).freeze();
let padding = msg_bytes.split_to(padding_len).freeze();
let payload_crc_src = msg_bytes.get_u64();
if payload_crc_src != payload_crc_dst {
log::debug!("Framing codec: invalid payload crc");
return Err(std::io::ErrorKind::InvalidData.into());
}
Ok(Some(FramingMessage::new(data, padding)))
}
}
impl FramingCodec {
pub fn new() -> Self {
Self
}
const fn max_header_len() -> usize {
2 + 2 + 8 + 8
}
}
impl FrameSize for FramingCodec {
fn mtu() -> usize {
u16::MAX as usize }
fn mss(target_mtu: usize) -> usize {
let mtu = target_mtu.min(Self::mtu());
mtu.checked_sub(Self::max_header_len()).unwrap()
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use super::*;
use crate::test::TestPayload;
fn assert_encode_decode(msg: FramingMessage) {
let mut buf = BytesMut::new();
let encode_result = FramingCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_ok());
let decode_result = FramingCodec.decode(&mut buf);
assert!(matches!(decode_result, Ok(Some(_))));
let encoded_decoded_msg = decode_result.unwrap().unwrap();
assert_eq!(msg, encoded_decoded_msg);
}
fn message(data: Bytes, padding: Option<Bytes>) -> FramingMessage {
FramingMessage::new(data, padding.unwrap_or_default())
}
fn payload(len: usize) -> Bytes {
TestPayload::bytes(len)
}
fn padding(len: usize) -> Bytes {
BytesMut::zeroed(len).freeze()
}
fn test_valid_command(data: Bytes) {
assert_encode_decode(message(data, None));
}
#[test]
fn valid_bytes() {
test_valid_command(Bytes::from("This is the payload."));
}
#[test]
fn valid_payload_length() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
for n in [0, 1, 2, 10, max_len - 1, max_len] {
test_valid_command(payload(n));
}
}
#[test]
fn valid_padding_length() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
for n in [0, 1, 2, 10, max_len - 1, max_len] {
assert_encode_decode(message(payload(max_len - n), Some(padding(n))));
}
}
#[test]
fn invalid_payload_length() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
let bytes = payload(max_len + 1);
let msg = message(bytes, None);
let mut buf = BytesMut::new();
let encode_result = FramingCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_err());
}
#[test]
fn invalid_padding_length() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
for n in [0, 1, 2, 10, max_len - 1, max_len] {
let msg = message(payload(max_len - n), Some(padding(n + 1)));
let mut buf = BytesMut::new();
let encode_result = FramingCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_err());
}
}
fn test_invalid(data: Bytes, padding: Option<Bytes>, byte_to_stomp: usize) {
let mut buf = BytesMut::new();
let msg = message(data, padding);
assert!(FramingCodec.encode(msg, &mut buf).is_ok());
buf[byte_to_stomp] = 99;
assert!(FramingCodec.decode(&mut buf).is_err());
}
#[test]
fn invalid_payload_bytes() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
test_invalid(padding(max_len), None, 10_000);
test_invalid(padding(0), Some(padding(max_len)), 10_000);
test_invalid(padding(50), Some(padding(50)), 49);
test_invalid(padding(50), Some(padding(50)), 50);
}
#[test]
fn valid_checksum() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
let bytes = payload(max_len);
let checksum_src = xxh3_64(&bytes);
let checksum_dst = xxh3_64(&bytes);
assert_eq!(checksum_src, checksum_dst);
}
#[test]
fn invalid_checksum() {
let max_len = FramingCodec::mss(FramingCodec::mtu());
let bytes = payload(max_len);
let mut buf = BytesMut::from(bytes);
let n = buf.len();
buf[n - 1] = 1;
let checksum_src = xxh3_64(&buf);
buf[n - 1] = 0;
let checksum_dst = xxh3_64(&buf);
assert_ne!(checksum_src, checksum_dst);
}
}