Skip to main content

over_there/core/transport/wire/output/
encoder.rs

1use crate::core::transport::{
2    auth::Signer,
3    wire::packet::{Metadata, Packet, PacketEncryption, PacketType},
4};
5use derive_more::{Display, Error};
6use std::collections::HashMap;
7
8pub(crate) struct EncodeArgs<'d, 's, S: Signer> {
9    /// ID used to group created packets together
10    pub id: u32,
11
12    /// Used to specify the level of encryption to use
13    pub encryption: PacketEncryption,
14
15    /// Desired maximum size of each packet (including all overhead like metadata)
16    pub max_packet_size: usize,
17
18    /// Key used to generate signatures
19    pub signer: &'s S,
20
21    /// The data to build packets around; encryption should have already happened
22    /// by this point
23    pub data: &'d [u8],
24}
25
26#[derive(Debug, Display, Error)]
27pub enum EncoderError {
28    MaxPacketSizeTooSmall,
29    FailedToEstimateDataSize,
30    FailedToSignPacket,
31}
32
33#[derive(Debug, Clone)]
34pub(crate) struct Encoder {
35    /// Key = Max Packet Size + PacketType
36    /// Value = Max data size to fit in packet without exceeding max packet size
37    max_data_size_cache: HashMap<String, usize>,
38
39    /// Key = Data Size + PacketType
40    /// Value = Size of packet given some length of data and packet type
41    packet_size_cache: HashMap<String, usize>,
42}
43
44impl Encoder {
45    pub fn encode<S: Signer>(
46        &mut self,
47        info: EncodeArgs<S>,
48    ) -> Result<Vec<Packet>, EncoderError> {
49        let EncodeArgs {
50            id,
51            encryption,
52            max_packet_size,
53            signer,
54            data,
55        } = info;
56
57        // Calculate the maximum size of the data section of a packet for
58        // a final and not final version
59        let not_final_max_data_size = self
60            .find_optimal_max_data_size(
61                max_packet_size,
62                PacketType::NotFinal,
63                signer,
64            )
65            .map_err(|_| EncoderError::FailedToEstimateDataSize)?;
66        let final_max_data_size = self
67            .find_optimal_max_data_size(
68                max_packet_size,
69                PacketType::Final { encryption },
70                signer,
71            )
72            .map_err(|_| EncoderError::FailedToEstimateDataSize)?;
73
74        // If a packet type cannot support data of any size, we have requested
75        // a max packet size that is too small
76        if not_final_max_data_size == 0 || final_max_data_size == 0 {
77            return Err(EncoderError::MaxPacketSizeTooSmall);
78        }
79
80        // Construct the packets, using the single id to associate all of
81        // them together and linking each to an individual position in the
82        // collective using the chunks
83        let mut packets = Vec::new();
84        let mut i = 0;
85        while i < data.len() {
86            // Chunk length is determined by this logic:
87            // 1. If we have room in the final packet for the remaining data,
88            //    store it in the final packet
89            // 2. If we have so much data left that it won't fit in the final
90            //    packet and it won't fit in a non-final packet, we store N
91            //    bytes into a non-final packet where N is the max capable size
92            //    of a non-final packet data section
93            // 3. If we have so much data left that it won't fit in the final
94            //    packet but it will fit entirely in a non-final packet, we
95            //    store N - 1 bytes where N is the size of the remaining data
96            let remaining_data_size = data.len() - i;
97            let can_fit_all_in_final_packet =
98                i + final_max_data_size >= data.len();
99            let can_fit_all_in_not_final_packet =
100                i + not_final_max_data_size >= data.len();
101            let data_size = if can_fit_all_in_final_packet {
102                final_max_data_size
103            } else if can_fit_all_in_not_final_packet {
104                remaining_data_size - 1
105            } else {
106                not_final_max_data_size
107            };
108
109            // Ensure data section size does not exceed our remaining data
110            let data_size = std::cmp::min(data_size, remaining_data_size);
111
112            // Grab our chunk of data to store into a packet
113            let chunk = &data[i..i + data_size];
114
115            // Construct the packet based on whether or not is final
116            let packet = Self::make_new_packet(
117                id,
118                packets.len() as u32,
119                if can_fit_all_in_final_packet {
120                    PacketType::Final { encryption }
121                } else {
122                    PacketType::NotFinal
123                },
124                chunk,
125                signer,
126            )
127            .map_err(|_| EncoderError::FailedToSignPacket)?;
128
129            // Store packet in our collection
130            packets.push(packet);
131
132            // Move our pointer by N bytes
133            i += data_size;
134        }
135
136        Ok(packets)
137    }
138
139    /// Creates a new packet and signs it using the given authenticator
140    fn make_new_packet<S: Signer>(
141        id: u32,
142        index: u32,
143        r#type: PacketType,
144        data: &[u8],
145        signer: &S,
146    ) -> Result<Packet, serde_cbor::Error> {
147        let metadata = Metadata { id, index, r#type };
148        metadata.to_vec().map(|md| {
149            let sig = signer.sign(&[md, data.to_vec()].concat());
150            Packet::new(metadata, sig, data.to_vec())
151        })
152    }
153
154    /// Finds the optimal data size to get as close to a maximum packet size
155    /// as possible without exceeding it. Will cache results for faster
156    /// performance on future runs.
157    fn find_optimal_max_data_size<S: Signer>(
158        &mut self,
159        max_packet_size: usize,
160        r#type: PacketType,
161        signer: &S,
162    ) -> Result<usize, serde_cbor::Error> {
163        // Calculate key to use for cache
164        let key = format!("{}{:?}", max_packet_size, r#type);
165
166        // Check if we have a cached value and, if so, use it
167        if let Some(value) = self.max_data_size_cache.get(&key) {
168            return Ok(*value);
169        }
170
171        // Start searching somewhere in the middle of the maximum packet size
172        let mut best_data_size = 0;
173        let mut data_size = (max_packet_size / 2) + 1;
174        loop {
175            let packet_size =
176                self.estimate_packet_size(data_size, r#type, signer)?;
177
178            // If the data section has reached our maximum packet size exactly,
179            // we are done searching
180            if packet_size == max_packet_size {
181                best_data_size = data_size;
182                break;
183            // Else, if the packet size would be too big, shrink down
184            } else if packet_size > max_packet_size {
185                let overflow_size = packet_size - max_packet_size;
186
187                // If the overflow is greater than the data available, shrink
188                // down by half (in case the overhead shrinks)
189                if overflow_size >= data_size {
190                    data_size /= 2;
191
192                    // If the shrinkage would cause the data to be nothing, exit
193                    if data_size == 0 {
194                        break;
195                    }
196
197                // Otherwise, shrink data size by N to see if we can fit
198                } else {
199                    data_size -= overflow_size;
200                }
201
202            // Else, if the packet was smaller than max capacity AND the data
203            // that can be fit is better (bigger) than the current best fit,
204            // update it
205            } else if data_size > best_data_size {
206                best_data_size = data_size;
207            // Else, we've reached a point where we are no longer finding
208            // better sizes, so exit with what we've found so far
209            } else {
210                break;
211            }
212        }
213
214        // Cache our best size for the data section given max packet size
215        self.max_data_size_cache.insert(key, best_data_size);
216        Ok(best_data_size)
217    }
218
219    /// Calculates the size of a packet comprised of data of length N, caching
220    /// the result to avoid expensive computations in the future.
221    pub(crate) fn estimate_packet_size<S: Signer>(
222        &mut self,
223        data_size: usize,
224        r#type: PacketType,
225        signer: &S,
226    ) -> Result<usize, serde_cbor::Error> {
227        // Calculate key to use for cache
228        let key = format!("{}{:?}", data_size, r#type);
229
230        // Check if we have a cached value and, if so, use it
231        if let Some(value) = self.packet_size_cache.get(&key) {
232            return Ok(*value);
233        }
234
235        // Produce random fake data to avoid any byte sequencing
236        let fake_data: Vec<u8> =
237            (0..data_size).map(|_| rand::random::<u8>()).collect();
238
239        // Produce a fake packet whose data fills the entire size, and then
240        // see how much larger it is and use that as the overhead cost
241        //
242        // NOTE: This is a rough estimate and requires an entire serialization,
243        //       but is the most straightforward way I can think of unless
244        //       serde offers some form of size hinting for msgpack/cbor specifically
245        let packet_size = Encoder::make_new_packet(
246            u32::max_value(),
247            u32::max_value(),
248            r#type,
249            &fake_data,
250            signer,
251        )?
252        .to_vec()?
253        .len();
254
255        // Cache the calculated size and return it
256        self.packet_size_cache.insert(key, packet_size);
257        Ok(packet_size)
258    }
259}
260
261impl Default for Encoder {
262    fn default() -> Self {
263        Self {
264            max_data_size_cache: HashMap::new(),
265            packet_size_cache: HashMap::new(),
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use crate::core::transport::auth::NoopAuthenticator;
274    use crate::core::transport::crypto::{nonce, Nonce};
275
276    #[test]
277    fn fails_if_max_packet_size_is_too_low() {
278        // Needs to accommodate metadata & data, which this does not
279        let chunk_size = 1;
280        let err = Encoder::default()
281            .encode(EncodeArgs {
282                id: 0,
283                encryption: PacketEncryption::None,
284                data: &vec![1, 2, 3],
285                max_packet_size: chunk_size,
286                signer: &NoopAuthenticator,
287            })
288            .unwrap_err();
289
290        match err {
291            EncoderError::MaxPacketSizeTooSmall => (),
292            x => panic!("Unexpected error: {:?}", x),
293        }
294    }
295
296    #[test]
297    fn produces_single_packet_with_data() {
298        let id = 12345;
299        let data: Vec<u8> = vec![1, 2];
300        let encryption =
301            PacketEncryption::from(Nonce::from(nonce::new_128bit_nonce()));
302
303        // Make it so all the data fits in one packet
304        let chunk_size = 1000;
305
306        let packets = Encoder::default()
307            .encode(EncodeArgs {
308                id,
309                encryption,
310                data: &data,
311                max_packet_size: chunk_size,
312                signer: &NoopAuthenticator,
313            })
314            .unwrap();
315        assert_eq!(packets.len(), 1, "More than one packet produced");
316
317        let p = &packets[0];
318        assert_eq!(p.id(), id, "ID not properly set on packet");
319        assert_eq!(p.index(), 0, "Unexpected index for single packet");
320        assert_eq!(
321            p.is_final(),
322            true,
323            "Single packet not marked as last packet"
324        );
325        assert_eq!(p.data(), &data);
326    }
327
328    #[test]
329    fn produces_multiple_packets_with_data() {
330        let id = 67890;
331        let data: Vec<u8> = vec![1, 2, 3];
332        let nonce = Nonce::from(nonce::new_128bit_nonce());
333        let mut encoder = Encoder::default();
334
335        // Calculate a packet size where the final packet can only
336        // fit a single byte of data to ensure that we get at least
337        // one additional packet
338        let max_packet_size = encoder
339            .estimate_packet_size(
340                /* data size */ 1,
341                PacketType::Final {
342                    encryption: PacketEncryption::from(nonce),
343                },
344                &NoopAuthenticator,
345            )
346            .unwrap();
347
348        let packets = encoder
349            .encode(EncodeArgs {
350                id,
351                encryption: PacketEncryption::from(nonce),
352                data: &data,
353                max_packet_size,
354                signer: &NoopAuthenticator,
355            })
356            .unwrap();
357        assert_eq!(packets.len(), 2, "Unexpected number of packets");
358
359        // Check data quality of first packet
360        let p1 = packets.get(0).unwrap();
361        assert_eq!(p1.id(), id, "ID not properly set on first packet");
362        assert_eq!(p1.index(), 0, "First packet not marked with index 0");
363        assert_eq!(
364            p1.is_final(),
365            false,
366            "Non-final packet unexpectedly marked as last"
367        );
368        assert_eq!(&p1.data()[..], &data[0..2]);
369
370        // Check data quality of second packet
371        let p2 = packets.get(1).unwrap();
372        assert_eq!(p2.id(), id, "ID not properly set on second packet");
373        assert_eq!(p2.index(), 1, "Last packet not marked with correct index");
374        assert_eq!(p2.is_final(), true, "Last packet not marked as final");
375        assert_eq!(&p2.data()[..], &data[2..]);
376    }
377
378    #[test]
379    fn produces_multiple_packets_respecting_size_constraints() {
380        let id = 67890;
381        let encryption =
382            PacketEncryption::from(Nonce::from(nonce::new_128bit_nonce()));
383
384        // Make it so not all of the data fits in one packet
385        //
386        // NOTE: Make sure we make large enough chunks so msgpack
387        //       serialization needs more bytes; use 100k of memory to
388        //       spread out packets
389        //
390        let data: Vec<u8> = [0; 100000].to_vec();
391        let chunk_size = 512;
392
393        let packets = Encoder::default()
394            .encode(EncodeArgs {
395                id,
396                encryption,
397                data: &data,
398                max_packet_size: chunk_size,
399                signer: &NoopAuthenticator,
400            })
401            .unwrap();
402
403        // All packets should be no larger than chunk size
404        for (i, p) in packets.iter().enumerate() {
405            let actual_size = p.to_vec().unwrap().len();
406            assert!(
407                actual_size <= chunk_size,
408                "Serialized packet {}/{} was {} bytes instead of max size of {}",
409                i + 1,
410                packets.len(),
411                actual_size,
412                chunk_size
413            );
414        }
415    }
416}