Skip to main content

hopr_transport/protocol/
codec.rs

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