Skip to main content

network_protocol/core/
codec.rs

1//! # Codec
2//!
3//! This file is part of the Network Protocol project.
4//!
5//! It defines the codec for encoding and decoding protocol packets using the [`Packet`] struct.
6//!
7//! The codec is designed to work with the [`tokio`] framework for asynchronous I/O.
8//! Specifically, the `PacketCodec` struct implements the [`Decoder`] and [`Encoder`] traits
9//! from [`tokio_util::codec`].
10//!
11//! ## Responsibilities
12//! - Decode packets from a byte stream
13//! - Encode packets into a byte stream
14//! - Handle fixed-length headers and variable-length payloads
15//!
16//! This module is essential for processing protocol packets in a networked environment,
17//! ensuring correct parsing and serialization.
18//!
19//! It is designed to be efficient, minimal, and easy to integrate into the protocol layer.
20//!
21
22use crate::config::{MAGIC_BYTES, MAX_PAYLOAD_SIZE, PROTOCOL_VERSION};
23use crate::core::packet::{Packet, HEADER_SIZE};
24use crate::error::{ProtocolError, Result};
25use bytes::{BufMut, BytesMut};
26use tokio_util::codec::{Decoder, Encoder};
27//use futures::StreamExt;
28
29pub struct PacketCodec;
30
31impl Decoder for PacketCodec {
32    type Item = Packet;
33    type Error = ProtocolError;
34
35    /// Decodes a packet from the byte stream
36    ///
37    /// Returns `None` if there aren't enough bytes to form a complete packet.
38    ///
39    /// The header is validated as soon as its 9 bytes have arrived, before any of
40    /// the payload is buffered, so a peer cannot make the connection hold a frame
41    /// larger than `MAX_PAYLOAD_SIZE` or one with a bad magic or version.
42    ///
43    /// # Errors
44    /// Returns `ProtocolError::InvalidHeader` for a wrong magic,
45    /// `ProtocolError::UnsupportedVersion` for an unknown version, and
46    /// `ProtocolError::OversizedPacket` for a declared length over
47    /// `MAX_PAYLOAD_SIZE`.
48    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Packet>> {
49        if src.len() < HEADER_SIZE {
50            return Ok(None);
51        }
52
53        if src[0..4] != MAGIC_BYTES {
54            return Err(ProtocolError::InvalidHeader);
55        }
56        if src[4] != PROTOCOL_VERSION {
57            return Err(ProtocolError::UnsupportedVersion(src[4]));
58        }
59        let len = u32::from_be_bytes([src[5], src[6], src[7], src[8]]) as usize;
60        if len > MAX_PAYLOAD_SIZE {
61            return Err(ProtocolError::OversizedPacket(len));
62        }
63        let total_len = HEADER_SIZE + len;
64
65        if src.len() < total_len {
66            return Ok(None); // Wait for full frame
67        }
68
69        let buf = src.split_to(total_len).freeze();
70        Packet::from_bytes(&buf).map(Some)
71    }
72}
73
74impl Encoder<Packet> for PacketCodec {
75    type Error = ProtocolError;
76
77    /// Encodes a packet into the byte stream
78    ///
79    /// # Errors
80    /// Returns `ProtocolError::OversizedPacket` if the payload is larger than
81    /// `MAX_PAYLOAD_SIZE`, which the receiving side would reject.
82    fn encode(&mut self, packet: Packet, dst: &mut BytesMut) -> Result<()> {
83        if packet.payload.len() > MAX_PAYLOAD_SIZE {
84            return Err(ProtocolError::OversizedPacket(packet.payload.len()));
85        }
86
87        // Calculate total size and reserve space in the buffer
88        let total_size = HEADER_SIZE + packet.payload.len();
89        dst.reserve(total_size);
90
91        // Write header directly to buffer: magic bytes + version + length
92        dst.put_slice(&MAGIC_BYTES);
93        dst.put_u8(PROTOCOL_VERSION);
94        dst.put_u32(packet.payload.len() as u32);
95
96        // Write payload directly to buffer
97        dst.put_slice(&packet.payload);
98
99        Ok(())
100    }
101}
102
103#[cfg(test)]
104#[allow(clippy::unwrap_used, clippy::expect_used)]
105mod tests {
106    use super::*;
107
108    fn header(magic: [u8; 4], version: u8, len: u32) -> BytesMut {
109        let mut buf = BytesMut::new();
110        buf.put_slice(&magic);
111        buf.put_u8(version);
112        buf.put_u32(len);
113        buf
114    }
115
116    #[test]
117    fn oversized_length_is_rejected_from_the_header_alone() {
118        let mut buf = header(MAGIC_BYTES, PROTOCOL_VERSION, (MAX_PAYLOAD_SIZE + 1) as u32);
119        assert!(matches!(
120            PacketCodec.decode(&mut buf),
121            Err(ProtocolError::OversizedPacket(_))
122        ));
123        let mut buf = header(MAGIC_BYTES, PROTOCOL_VERSION, u32::MAX);
124        assert!(matches!(
125            PacketCodec.decode(&mut buf),
126            Err(ProtocolError::OversizedPacket(_))
127        ));
128    }
129
130    #[test]
131    fn bad_magic_or_version_is_rejected_from_the_header_alone() {
132        let mut buf = header(*b"HTTP", PROTOCOL_VERSION, 4);
133        assert!(matches!(
134            PacketCodec.decode(&mut buf),
135            Err(ProtocolError::InvalidHeader)
136        ));
137        let mut buf = header(MAGIC_BYTES, PROTOCOL_VERSION.wrapping_add(1), 4);
138        assert!(matches!(
139            PacketCodec.decode(&mut buf),
140            Err(ProtocolError::UnsupportedVersion(_))
141        ));
142    }
143
144    #[test]
145    fn valid_header_waits_for_the_payload() {
146        let mut buf = header(MAGIC_BYTES, PROTOCOL_VERSION, 4);
147        assert!(PacketCodec.decode(&mut buf).unwrap().is_none());
148        buf.put_slice(b"ping");
149        let packet = PacketCodec.decode(&mut buf).unwrap().unwrap();
150        assert_eq!(packet.payload, b"ping");
151        assert!(buf.is_empty());
152    }
153
154    #[test]
155    fn largest_allowed_payload_round_trips_and_larger_is_not_sent() {
156        let mut buf = BytesMut::new();
157        let packet = Packet {
158            version: PROTOCOL_VERSION,
159            payload: vec![7u8; MAX_PAYLOAD_SIZE],
160        };
161        PacketCodec.encode(packet, &mut buf).unwrap();
162        let decoded = PacketCodec.decode(&mut buf).unwrap().unwrap();
163        assert_eq!(decoded.payload.len(), MAX_PAYLOAD_SIZE);
164
165        let mut buf = BytesMut::new();
166        let too_big = Packet {
167            version: PROTOCOL_VERSION,
168            payload: vec![0u8; MAX_PAYLOAD_SIZE + 1],
169        };
170        assert!(matches!(
171            PacketCodec.encode(too_big, &mut buf),
172            Err(ProtocolError::OversizedPacket(_))
173        ));
174        assert!(buf.is_empty(), "nothing is written for a rejected packet");
175    }
176}