rs-netty 1.1.0

A Tokio-native typed TCP/UDP pipeline framework inspired by Netty.
Documentation
use bytes::{Bytes, BytesMut};

use crate::{
    codec::{Decoder, Encoder},
    Error, Result,
};

/// Codec for fixed-size binary frames.
///
/// Decoding waits until exactly `frame_length` bytes are available and then
/// returns that chunk. Encoding rejects items whose length does not match the
/// configured frame length.
pub struct FixedLengthFrameDecoder {
    frame_length: usize,
}

impl FixedLengthFrameDecoder {
    /// Creates a fixed-length frame codec.
    ///
    /// Panics if `frame_length` is zero.
    pub fn new(frame_length: usize) -> Self {
        assert!(frame_length > 0, "frame_length must be greater than zero");
        Self { frame_length }
    }

    /// Returns the exact frame length in bytes.
    pub fn frame_length(&self) -> usize {
        self.frame_length
    }
}

impl Decoder for FixedLengthFrameDecoder {
    type Item = Bytes;

    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>> {
        if src.len() < self.frame_length {
            return Ok(None);
        }

        Ok(Some(src.split_to(self.frame_length).freeze()))
    }
}

impl Encoder<Bytes> for FixedLengthFrameDecoder {
    fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<()> {
        if item.len() != self.frame_length {
            return Err(Error::Encode(format!(
                "fixed frame length mismatch: current={}, expected={}",
                item.len(),
                self.frame_length
            )));
        }

        dst.extend_from_slice(&item);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn splits_fixed_length_frames() {
        let mut codec = FixedLengthFrameDecoder::new(3);
        let mut buf = BytesMut::from(&b"abcdef"[..]);

        assert_eq!(
            codec.decode(&mut buf).expect("decode"),
            Some(Bytes::from_static(b"abc"))
        );
        assert_eq!(
            codec.decode(&mut buf).expect("decode"),
            Some(Bytes::from_static(b"def"))
        );
        assert!(buf.is_empty());
    }
}