datagram_chunker/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/datagram-chunker
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5
6//! # Datagram Chunker Crate
7//!
8//! This crate provides functionality to serialize and deserialize messages into
9//! datagrams with size constraints. It includes error handling mechanisms
10//! and utilities to manage datagram chunking efficiently.
11use err_rs::{ErrorLevel, ErrorLevelProvider};
12use flood_rs::in_stream::InOctetStream;
13use flood_rs::prelude::OutOctetStream;
14use flood_rs::{Deserialize, ReadOctetStream, Serialize};
15use hexify::format_hex;
16use log::trace;
17use std::fmt::{Debug, Display};
18use std::{io, mem};
19
20pub mod prelude;
21
22/// Represents errors that can occur while chunking datagrams.
23#[derive(Debug)]
24pub enum DatagramChunkerError {
25    /// The size of the item exceeds the maximum allowed datagram size.
26    ItemSizeTooBig,
27    /// An I/O error occurred.
28    IoError(io::Error),
29}
30
31impl ErrorLevelProvider for DatagramChunkerError {
32    fn error_level(&self) -> ErrorLevel {
33        match self {
34            Self::ItemSizeTooBig => ErrorLevel::Critical,
35            Self::IoError(_) => ErrorLevel::Info,
36        }
37    }
38}
39
40/// A utility for chunking messages into datagrams with a specified maximum size.
41pub struct DatagramChunker {
42    datagrams: Vec<Vec<u8>>,
43    current: Vec<u8>,
44    max_size: usize,
45}
46
47impl DatagramChunker {
48    /// Creates a new `DatagramChunker` with the given maximum datagram size.
49    ///
50    /// # Arguments
51    ///
52    /// * `max_size` - The maximum size of each datagram in bytes.
53    #[must_use]
54    pub fn new(max_size: usize) -> Self {
55        Self {
56            current: Vec::with_capacity(max_size),
57            datagrams: Vec::new(),
58            max_size,
59        }
60    }
61
62    /// Pushes a message into the chunker, creating a new datagram if necessary.
63    ///
64    /// # Arguments
65    ///
66    /// * `data` - A byte slice to be added to the current datagram.
67    ///
68    /// # Errors
69    ///
70    /// Returns `DatagramChunkerError::ItemSizeTooBig` if the data size exceeds `max_size`.
71    /// Propagates `DatagramChunkerError::IoError` if serialization fails.
72    pub fn push(&mut self, buf: &[u8]) -> Result<(), DatagramChunkerError> {
73        if buf.len() > self.max_size {
74            return Err(DatagramChunkerError::ItemSizeTooBig);
75        }
76
77        if self.current.len() + buf.len() > self.max_size {
78            self.datagrams.push(mem::take(&mut self.current));
79            self.current = buf.to_vec();
80        } else {
81            self.current.extend_from_slice(buf);
82        }
83
84        Ok(())
85    }
86
87    #[must_use]
88    pub fn finalize(mut self) -> Vec<Vec<u8>> {
89        if !self.current.is_empty() {
90            self.datagrams.push(self.current.clone());
91        }
92        self.datagrams
93    }
94}
95
96impl From<io::Error> for DatagramChunkerError {
97    fn from(value: io::Error) -> Self {
98        Self::IoError(value)
99    }
100}
101
102/// Serializes a collection of messages into datagrams, each not exceeding the specified maximum size.
103///
104/// # Type Parameters
105///
106/// * `I` - A reference to a slice of messages to serialize.
107/// * `T` - The type of messages to serialize, which must implement `Serialize` and `Debug`.
108///
109/// # Arguments
110///
111/// * `messages` - The collection of messages to serialize.
112/// * `max_datagram_size` - The maximum size of each datagram in bytes.
113///
114/// # Returns
115///
116/// A `Result` containing a vector of datagrams (`Vec<Vec<u8>>`) on success,
117/// or a `DatagramChunkerError` on failure.
118///
119/// # Errors
120///
121/// Returns `DatagramChunkerError::ItemSizeTooBig` if a message's serialized size exceeds the maximum datagram size.
122/// Returns `DatagramChunkerError::IoError` if an I/O error occurs during serialization.
123pub fn serialize_to_datagrams<I, T>(
124    messages: I,
125    max_datagram_size: usize,
126) -> Result<Vec<Vec<u8>>, DatagramChunkerError>
127where
128    T: Serialize + Debug + Display,
129    I: AsRef<[T]>,
130{
131    let mut chunker = DatagramChunker::new(max_datagram_size);
132    for message in messages.as_ref() {
133        let mut temp = OutOctetStream::new();
134        message.serialize(&mut temp)?;
135        trace!("serializing {message} to {}", format_hex(temp.octets_ref()));
136        chunker.push(temp.octets_ref())?;
137    }
138
139    Ok(chunker.finalize())
140}
141
142/// Deserializes a single datagram into a vector of messages.
143///
144/// # Type Parameters
145///
146/// * `T` - The type of messages to deserialize, which must implement `Deserialize` and `Display`.
147///
148/// # Arguments
149///
150/// * `buf` - An octet slice representing the datagram to deserialize.
151///
152/// # Returns
153///
154/// A `Result` containing a vector of deserialized messages (`Vec<T>`) on success,
155/// or an `io::Error` on failure.
156///
157/// # Errors
158///
159/// Returns `io::Error` if any deserialization failure occurs.
160pub fn deserialize_datagram<T>(buf: &[u8]) -> Result<Vec<T>, io::Error>
161where
162    T: Deserialize + Debug + Display,
163{
164    let mut messages = vec![];
165    let mut in_stream = InOctetStream::new(buf);
166
167    while !&in_stream.has_reached_end() {
168        let message = T::deserialize(&mut in_stream)?;
169        trace!("deserialize {message}");
170        messages.push(message);
171    }
172
173    Ok(messages)
174}
175
176/// Deserializes multiple datagrams into a unified vector of messages.
177///
178/// This function processes a collection of datagrams (`Vec<Vec<u8>>`), deserializing each one
179/// into its respective vector of messages and aggregating all messages into a single vector.
180///
181/// # Type Parameters
182///
183/// * `T` - The type of message to deserialize, which must implement `Deserialize` and `Display`.
184///
185/// # Arguments
186///
187/// * `datagrams` - A vector of datagrams, where each datagram is represented as a `Vec<u8>`.
188///
189/// # Returns
190///
191/// A `Result` containing a unified vector of deserialized commands (`Vec<CommandT>`) on success,
192/// or a `io::Error` if any deserialization fails.
193///
194/// # Errors
195///
196/// Returns `io::Error` if deserialization of any individual datagram fails.
197pub fn deserialize_datagrams<T>(datagrams: Vec<Vec<u8>>) -> Result<Vec<T>, io::Error>
198where
199    T: Deserialize + Debug + Display,
200{
201    let mut all_messages = Vec::new();
202    for datagram in datagrams {
203        let messages = deserialize_datagram(&datagram)?;
204        all_messages.extend(messages);
205    }
206    Ok(all_messages)
207}