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