]> git.lizzy.rs Git - connect-rs.git/blob - src/protocol.rs
add doc comments for the datagram
[connect-rs.git] / src / protocol.rs
1 use std::error::Error;
2 use std::io::Read;
3
4 const VERSION: u8 = 1;
5
6 /// Encountered when trying to construct a [`ConnectDatagram`] with an empty message body.
7 ///
8 #[derive(Debug, Clone)]
9 pub struct DatagramEmptyError;
10
11 impl Error for DatagramEmptyError {}
12
13 impl std::fmt::Display for DatagramEmptyError {
14     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
15         write!(
16             f,
17             "datagram cannot be constructed when provided payload is empty"
18         )
19     }
20 }
21
22 /// A simple packet format containing a version, recipient tag, and message body.
23 ///
24 pub struct ConnectDatagram {
25     version: u8,
26     recipient: u16,
27     data: Option<Vec<u8>>,
28 }
29
30 impl ConnectDatagram {
31     /// Creates a new [`ConnectDatagram`] based on an intended recipient and message body.
32     ///
33     /// This will return a [`DatagramEmptyError`] if the `data` parameter contains no bytes, or
34     /// in other words, when there is no message body.
35     ///
36     /// The version field is decided by the library version and used to maintain backwards
37     /// compatibility with previous datagram formats.
38     ///
39     pub fn new(recipient: u16, data: Vec<u8>) -> Result<Self, DatagramEmptyError> {
40         if data.len() > 0 {
41             Ok(Self {
42                 version: VERSION,
43                 recipient,
44                 data: Some(data),
45             })
46         } else {
47             Err(DatagramEmptyError)
48         }
49     }
50
51     /// Gets the version number of the datagram.
52     ///
53     pub fn version(&self) -> u8 {
54         self.version
55     }
56
57     /// Gets the recipient of the datagram.
58     ///
59     pub fn recipient(&self) -> u16 {
60         self.recipient
61     }
62
63     /// Gets the message body of the datagram.
64     ///
65     pub fn data(&self) -> Option<&Vec<u8>> {
66         self.data.as_ref()
67     }
68
69     /// Takes ownership of the message body of the datagram.
70     ///
71     pub fn take_data(&mut self) -> Option<Vec<u8>> {
72         self.data.take()
73     }
74
75     /// Calculates the serialized byte-size of the datagram.
76     ///
77     pub fn size(&self) -> usize {
78         let data_len = if let Some(data) = self.data() {
79             data.len()
80         } else {
81             0
82         };
83
84         3 + data_len
85     }
86
87     /// Constructs a serialized representation of the datagram.
88     ///
89     pub(crate) fn bytes(&self) -> Vec<u8> {
90         let mut bytes = Vec::with_capacity(self.size());
91
92         bytes.extend(&self.version.to_be_bytes());
93         bytes.extend(&self.recipient.to_be_bytes());
94
95         if let Some(data) = self.data() {
96             bytes.extend(data.as_slice());
97         }
98
99         return bytes;
100     }
101
102     /// Serializes the datagram.
103     ///
104     pub fn encode(&self) -> Vec<u8> {
105         let size: u32 = (self.size()) as u32;
106
107         let mut bytes = Vec::from(size.to_be_bytes());
108         bytes.extend(self.bytes());
109
110         return bytes;
111     }
112
113     /// Deserializes the datagram from a `source`.
114     ///
115     pub fn decode(source: &mut (dyn Read + Send + Sync)) -> anyhow::Result<Self> {
116         // payload size
117         let mut payload_size_bytes: [u8; 4] = [0; 4];
118         source.read_exact(&mut payload_size_bytes)?;
119         let payload_size = u32::from_be_bytes(payload_size_bytes);
120
121         // read whole payload
122         let mut payload_bytes = vec![0; payload_size as usize];
123         source.read_exact(payload_bytes.as_mut_slice())?;
124
125         // version
126         let version_bytes = payload_bytes.remove(0);
127         let version = u8::from_be(version_bytes);
128
129         // recipient
130         let mut recipient_bytes: [u8; 2] = [0; 2];
131         for i in 0..recipient_bytes.len() {
132             recipient_bytes[i] = payload_bytes.remove(0);
133         }
134         let recipient = u16::from_be_bytes(recipient_bytes);
135
136         // data
137         let data = payload_bytes;
138
139         if data.len() > 0 {
140             Ok(Self {
141                 version,
142                 recipient,
143                 data: Some(data),
144             })
145         } else {
146             Err(anyhow::Error::from(DatagramEmptyError))
147         }
148     }
149 }
150
151 #[cfg(test)]
152 mod tests {
153     use crate::protocol::ConnectDatagram;
154     use std::io::Cursor;
155
156     #[test]
157     fn encoded_size() -> anyhow::Result<()> {
158         let mut data = Vec::new();
159         for _ in 0..5 {
160             data.push(1);
161         }
162         assert_eq!(5, data.len());
163
164         let sample = ConnectDatagram::new(1, data)?;
165         assert_eq!(7 + 5, sample.encode().len());
166
167         Ok(())
168     }
169
170     #[test]
171     fn take_data() -> anyhow::Result<()> {
172         let mut data = Vec::new();
173         for _ in 0..5 {
174             data.push(1);
175         }
176
177         let mut sample = ConnectDatagram::new(1, data)?;
178
179         let taken_data = sample.take_data().unwrap();
180         assert!(sample.data().is_none());
181         assert_eq!(5, taken_data.len());
182
183         Ok(())
184     }
185
186     #[async_std::test]
187     async fn encode_and_decode() -> anyhow::Result<()> {
188         let mut data = Vec::new();
189         for _ in 0..5 {
190             data.push(1);
191         }
192         assert_eq!(5, data.len());
193
194         let sample = ConnectDatagram::new(1, data)?;
195
196         let mut payload = sample.encode();
197         assert_eq!(7 + 5, payload.len());
198
199         let mut cursor: Cursor<&mut [u8]> = Cursor::new(payload.as_mut());
200         let sample_back_res = ConnectDatagram::decode(&mut cursor);
201         assert!(sample_back_res.is_ok());
202
203         let sample_back = sample_back_res.unwrap();
204         assert_eq!(sample_back.version(), 1);
205         assert_eq!(sample_back.recipient(), 1);
206         assert_eq!(sample_back.data().unwrap().len(), 5);
207
208         Ok(())
209     }
210 }