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
/*
* Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/blob-stream-rs
* Licensed under the MIT License. See LICENSE in the project root for license information.
*/
use crate::err::BlobError;
use crate::ChunkIndex;
use bit_array_rs::BitArray;
/// A struct representing a stream of binary data divided into fixed-size chunks.
#[allow(unused)]
#[derive(Debug)]
pub struct BlobStreamIn {
pub(crate) bit_array: BitArray,
pub(crate) fixed_chunk_size: usize,
pub(crate) octet_count: usize,
blob: Vec<u8>,
}
impl BlobStreamIn {
/// Creates a new `BlobStreamIn` instance with the specified number of octets and chunk size.
///
/// # Parameters
/// - `octet_count`: The total number of octets (bytes) in the stream.
/// - `fixed_chunk_size`: The size of each chunk in the stream.
///
/// # Panics
/// Will panic if `fixed_chunk_size` is zero.
///
/// # Returns
/// A new `BlobStreamIn` instance.
#[allow(unused)]
#[must_use]
pub fn new(octet_count: usize, fixed_chunk_size: usize) -> Self {
assert!(
fixed_chunk_size > 0,
"fixed_chunk_size must be greater than zero"
);
let chunk_count = octet_count.div_ceil(fixed_chunk_size);
Self {
bit_array: BitArray::new(chunk_count),
fixed_chunk_size,
octet_count,
blob: vec![0u8; octet_count],
}
}
/// Returns the total number of expected chunks.
///
/// This function provides the total count of chunks that are expected
/// based on the size of the data and the chunk size.
///
/// # Returns
///
/// The total number of chunks (`usize`) that are expected for the data.
#[must_use]
pub const fn chunk_count(&self) -> usize {
self.bit_array.bit_count()
}
/// Checks if all chunks have been received.
///
/// # Returns
/// `true` if all chunks have been received; `false` otherwise.
#[must_use]
pub const fn is_complete(&self) -> bool {
self.bit_array.all_set()
}
/// Returns a reference to the complete blob if all chunks have been received.
///
/// # Returns
/// An `Option` containing a reference to the blob if complete; otherwise, `None`.
#[must_use]
pub fn blob(&self) -> Option<&[u8]> {
self.is_complete().then(|| &self.blob[..])
}
/// Sets a chunk of data at the specified `chunk_index` with the provided `payload`.
///
/// # Parameters
/// - `chunk_index`: The index of the chunk to set.
/// - `payload`: A slice of octets representing the chunk's data.
///
/// # Errors
/// Returns a `BlobError` if:
/// - The `chunk_index` is invalid.
/// - The `payload` size does not match the expected size for the chunk.
/// - The chunk has already been set, with either the same or different contents.
///
/// # Returns
/// `Ok(())` if the chunk was set successfully; otherwise, a `BlobError`.
pub fn set_chunk(&mut self, chunk_index: ChunkIndex, payload: &[u8]) -> Result<(), BlobError> {
let chunk_count = self.bit_array.bit_count();
if chunk_index >= chunk_count {
return Err(BlobError::InvalidChunkIndex(chunk_index, chunk_count));
}
let expected_size = if chunk_index == chunk_count - 1 {
// It was the last chunk
if self.octet_count % self.fixed_chunk_size == 0 {
self.fixed_chunk_size
} else {
self.octet_count % self.fixed_chunk_size
}
} else {
self.fixed_chunk_size
};
if payload.len() != expected_size {
return Err(BlobError::UnexpectedChunkSize(
expected_size,
payload.len(),
chunk_index,
));
}
let octet_offset = chunk_index * self.fixed_chunk_size;
if octet_offset + expected_size > self.blob.len() {
return Err(BlobError::OutOfBounds);
}
if self.bit_array.get(chunk_index) {
// It has been set previously
let is_same_contents =
&self.blob[octet_offset..octet_offset + expected_size] == payload;
let err = if is_same_contents {
BlobError::RedundantSameContents(chunk_index)
} else {
BlobError::RedundantContentDiffers(chunk_index)
};
return Err(err);
}
self.blob[octet_offset..octet_offset + expected_size].copy_from_slice(payload);
self.bit_array.set(chunk_index);
Ok(())
}
}