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}