Skip to main content

hopr_transport_protocol/
codec.rs

1use tokio_util::codec::{Decoder, Encoder};
2
3#[derive(Clone)]
4pub struct FixedLengthCodec<const SIZE: usize>;
5
6impl<const SIZE: usize> Encoder<Box<[u8]>> for FixedLengthCodec<SIZE> {
7    type Error = std::io::Error;
8
9    #[tracing::instrument(level = "trace", skip(self, item, dst), name = "encode data", fields(size = item.len(), protocol = "msg"), ret, err)]
10    fn encode(&mut self, item: Box<[u8]>, dst: &mut tokio_util::bytes::BytesMut) -> Result<(), Self::Error> {
11        dst.extend_from_slice(&item);
12        Ok(())
13    }
14}
15
16impl<const SIZE: usize> Decoder for FixedLengthCodec<SIZE> {
17    type Error = std::io::Error;
18    type Item = Box<[u8]>;
19
20    #[tracing::instrument(level = "trace", skip(self, src), name = "decode data", fields(size = src.len(), protocol = "msg"), ret(Debug), err)]
21    fn decode(&mut self, src: &mut tokio_util::bytes::BytesMut) -> Result<Option<Self::Item>, Self::Error> {
22        let len = src.len();
23        if len >= SIZE {
24            // `split_to` already hands us a contiguous, non-owning view onto the buffer.
25            // `to_vec` issues a single memcpy into a fresh Vec; the prior `Box::from_iter`
26            // walked byte-by-byte via the `Bytes` iterator, paying a branch + bounds
27            // check per byte on the inbound packet hot path.
28            let packet = src.split_to(SIZE);
29
30            Ok(Some(packet.to_vec().into_boxed_slice()))
31        } else {
32            tracing::trace!(
33                available_bytes = len,
34                protocol = "msg",
35                "Skipping decoding operation, insufficient bytes available"
36            );
37            Ok(None)
38        }
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use anyhow::Context;
45
46    use super::*;
47
48    const TEST_SIZE: usize = 10;
49
50    #[test]
51    fn fixed_size_codec_serialization_and_deserialization_are_reverse_operations() -> anyhow::Result<()> {
52        let mut codec = FixedLengthCodec::<TEST_SIZE>;
53        let mut buf = tokio_util::bytes::BytesMut::new();
54
55        let random_data_of_expected_packet_size: Box<[u8]> =
56            Box::from(hopr_api::types::crypto_random::random_bytes::<TEST_SIZE>());
57
58        codec.encode(random_data_of_expected_packet_size.clone(), &mut buf)?;
59
60        let actual = codec.decode(&mut buf)?;
61
62        assert_eq!(actual, Some(random_data_of_expected_packet_size));
63
64        assert_eq!(buf.len(), 0);
65
66        Ok(())
67    }
68
69    #[test]
70    fn fixed_size_codec_deserialization_of_an_incomplete_byte_sequence_should_not_produce_an_item() -> anyhow::Result<()>
71    {
72        let mut codec = FixedLengthCodec::<TEST_SIZE>;
73        let mut buf = tokio_util::bytes::BytesMut::new();
74
75        const LESS_THAN_PAYLOAD_SIZE: usize = TEST_SIZE - 1;
76        let random_data_too_few_bytes: Box<[u8]> =
77            Box::from(hopr_api::types::crypto_random::random_bytes::<LESS_THAN_PAYLOAD_SIZE>());
78
79        codec.encode(random_data_too_few_bytes, &mut buf)?;
80
81        let actual = codec.decode(&mut buf)?;
82
83        assert_eq!(actual, None);
84
85        assert_eq!(buf.len(), LESS_THAN_PAYLOAD_SIZE);
86
87        Ok(())
88    }
89
90    #[test]
91    fn fixed_size_codec_deserialization_of_too_many_bytes_should_produce_the_value_from_only_the_bytes_needed_for_an_item()
92    -> anyhow::Result<()> {
93        let mut codec = FixedLengthCodec::<TEST_SIZE>;
94        let mut buf = tokio_util::bytes::BytesMut::new();
95
96        const MORE_THAN_PAYLOAD_SIZE: usize = TEST_SIZE + 1;
97        let random_data_more_bytes_than_needed: Box<[u8]> =
98            Box::from(hopr_api::types::crypto_random::random_bytes::<MORE_THAN_PAYLOAD_SIZE>());
99
100        codec.encode(random_data_more_bytes_than_needed.clone(), &mut buf)?;
101
102        let actual = codec.decode(&mut buf)?.context("The value should be available")?;
103
104        assert_eq!(actual[..], random_data_more_bytes_than_needed[..TEST_SIZE]);
105
106        assert_eq!(buf.len(), MORE_THAN_PAYLOAD_SIZE - TEST_SIZE);
107
108        Ok(())
109    }
110}