use std::io::{self, Cursor};
use bytes::{Buf, BufMut, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
use crate::FrameSize;
use crate::proto::resumption::{Command, ResumptionMessage};
#[derive(Clone, Debug, Default)]
pub struct ResumptionCodec;
impl Encoder<ResumptionMessage> for ResumptionCodec {
type Error = io::Error;
fn encode(&mut self, msg: ResumptionMessage, dst: &mut BytesMut) -> io::Result<()> {
let payload_len = match &msg.command {
Command::Forward(payload) => payload.chunk.len(),
_ => 0,
};
if payload_len > Self::mss(Self::mtu()) {
return Err(io::Error::from(io::ErrorKind::FileTooLarge));
}
let len = Self::max_header_len() + payload_len;
dst.reserve(len);
dst.put_u8(Self::command_to_type(&msg.command));
match msg.command {
Command::Start(tid) => dst.put_u64(tid),
Command::Resume(tid) => dst.put_u64(tid),
Command::ResumeOk(tid) => dst.put_u64(tid),
Command::ResumeErr(tid) => dst.put_u64(tid),
Command::Forward(payload) => {
dst.put_u16(u16::try_from(payload.chunk.len()).unwrap());
dst.put(payload.chunk);
}
}
Ok(())
}
}
impl Decoder for ResumptionCodec {
type Item = ResumptionMessage;
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() < 1 {
return Ok(None); }
let maybe_command = match reader.get_u8() {
0 => (reader.remaining() >= 8).then(|| Command::Start(reader.get_u64())),
1 => (reader.remaining() >= 8).then(|| Command::Resume(reader.get_u64())),
2 => (reader.remaining() >= 8).then(|| Command::ResumeOk(reader.get_u64())),
3 => (reader.remaining() >= 8).then(|| Command::ResumeErr(reader.get_u64())),
4 => {
if reader.remaining() < 2 {
return Ok(None); }
let payload_len = reader.get_u16() as usize;
if reader.remaining() < payload_len {
return Ok(None); }
src.advance(reader.position() as usize);
let data = src.split_to(payload_len).freeze();
return Ok(Some(ResumptionMessage::from(data)));
}
_ => return Err(io::Error::from(io::ErrorKind::InvalidData)),
};
if let Some(command) = maybe_command {
src.advance(reader.position() as usize);
Ok(Some(ResumptionMessage::from(command)))
} else {
Ok(None) }
}
}
impl ResumptionCodec {
pub fn new() -> Self {
Self
}
const fn max_header_len() -> usize {
1 + 2
}
fn command_to_type(command: &Command) -> u8 {
match command {
Command::Start(_) => 0,
Command::Resume(_) => 1,
Command::ResumeOk(_) => 2,
Command::ResumeErr(_) => 3,
Command::Forward(_) => 4,
}
}
}
impl FrameSize for ResumptionCodec {
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: ResumptionMessage) {
let mut buf = BytesMut::new();
let encode_result = ResumptionCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_ok());
let decode_result = ResumptionCodec.decode(&mut buf);
assert!(matches!(decode_result, Ok(Some(_))));
let encoded_decoded_msg = decode_result.unwrap().unwrap();
assert_eq!(msg, encoded_decoded_msg);
}
fn gen_payload(len: usize) -> Bytes {
TestPayload::bytes(len)
}
fn test_valid_command(command: Command) {
assert_encode_decode(ResumptionMessage::from(command));
}
#[test]
fn valid_start() {
test_valid_command(Command::Start(123));
}
#[test]
fn valid_resume() {
test_valid_command(Command::Resume(123));
}
#[test]
fn valid_resumeok() {
test_valid_command(Command::ResumeOk(123));
}
#[test]
fn valid_resumeerr() {
test_valid_command(Command::ResumeErr(123));
}
#[test]
fn valid_forward() {
test_valid_command(Command::from(Bytes::from("This is the payload.")));
}
#[test]
fn invalid_payload_length() {
let max_len = ResumptionCodec::mss(ResumptionCodec::mtu());
let bytes = gen_payload(max_len);
test_valid_command(Command::from(bytes));
let bytes = gen_payload(max_len + 1);
let msg = ResumptionMessage::from(bytes);
let mut buf = BytesMut::new();
let encode_result = ResumptionCodec.encode(msg.clone(), &mut buf);
assert!(encode_result.is_err());
}
fn test_invalid(command: Command, byte_to_stomp: usize) {
let mut buf = BytesMut::new();
let msg = ResumptionMessage::from(command);
assert!(ResumptionCodec.encode(msg, &mut buf).is_ok());
buf[byte_to_stomp] = 99;
assert!(ResumptionCodec.decode(&mut buf).is_err());
}
#[test]
fn invalid_command_kind() {
test_invalid(Command::Start(111), 0);
}
}