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