use std::io::{self, Cursor};
use bytes::{Buf, BufMut, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
use crate::FrameSize;
use crate::proto::payload::PayloadMessage;
#[derive(Clone, Debug, Default)]
pub struct PayloadCodec;
impl Encoder<PayloadMessage> for PayloadCodec {
type Error = io::Error;
fn encode(&mut self, msg: PayloadMessage, dst: &mut BytesMut) -> io::Result<()> {
if msg.data.len() > Self::mss(Self::mtu()) {
return Err(io::Error::from(io::ErrorKind::FileTooLarge));
}
dst.reserve(Self::max_header_len() + msg.data.len());
dst.put_u64(msg.id);
dst.put_u16(u16::try_from(msg.data.len()).unwrap());
dst.extend_from_slice(&msg.data);
Ok(())
}
}
impl Decoder for PayloadCodec {
type Item = PayloadMessage;
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() >= 10 {
let id = reader.get_u64();
let len = reader.get_u16() as usize;
if reader.remaining() >= len {
src.advance(10);
let payload = src.split_to(len).freeze();
Ok(Some(PayloadMessage::new(id, payload)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
}
impl PayloadCodec {
pub fn new() -> Self {
Self
}
const fn max_header_len() -> usize {
8 + 2
}
}
impl FrameSize for PayloadCodec {
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: PayloadMessage) {
let mut buf = BytesMut::new();
let encode_result = PayloadCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_ok());
let decode_result = PayloadCodec.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(payload: Bytes) -> PayloadMessage {
PayloadMessage::new(1, payload)
}
fn gen_payload(len: usize) -> Bytes {
TestPayload::bytes(len)
}
fn test_valid_command(payload: Bytes) {
assert_encode_decode(message(payload));
}
#[test]
fn valid_bytes() {
test_valid_command(Bytes::from("This is the payload."));
}
#[test]
fn invalid_payload_length() {
let max_len = PayloadCodec::mss(PayloadCodec::mtu());
let bytes = gen_payload(max_len);
test_valid_command(bytes);
let bytes = gen_payload(max_len + 1);
let msg = message(bytes);
let mut buf = BytesMut::new();
let encode_result = PayloadCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_err());
}
}