blob_stream/in_stream.rs
1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/nimble-rust/nimble
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use crate::err::BlobError;
6use crate::ChunkIndex;
7use bit_array_rs::BitArray;
8
9/// A struct representing a stream of binary data divided into fixed-size chunks.
10#[allow(unused)]
11#[derive(Debug)]
12pub struct BlobStreamIn {
13 pub(crate) bit_array: BitArray,
14 pub(crate) fixed_chunk_size: usize,
15 pub(crate) octet_count: usize,
16 blob: Vec<u8>,
17}
18
19impl BlobStreamIn {
20 /// Creates a new `BlobStreamIn` instance with the specified number of octets and chunk size.
21 ///
22 /// # Parameters
23 /// - `octet_count`: The total number of octets (bytes) in the stream.
24 /// - `fixed_chunk_size`: The size of each chunk in the stream.
25 ///
26 /// # Panics
27 /// Will panic if `fixed_chunk_size` is zero.
28 ///
29 /// # Returns
30 /// A new `BlobStreamIn` instance.
31 #[allow(unused)]
32 #[must_use]
33 pub fn new(octet_count: usize, fixed_chunk_size: usize) -> Self {
34 assert!(
35 fixed_chunk_size > 0,
36 "fixed_chunk_size must be greater than zero"
37 );
38
39 let chunk_count = octet_count.div_ceil(fixed_chunk_size);
40 Self {
41 bit_array: BitArray::new(chunk_count),
42 fixed_chunk_size,
43 octet_count,
44 blob: vec![0u8; octet_count],
45 }
46 }
47
48 /// Returns the total number of expected chunks.
49 ///
50 /// This function provides the total count of chunks that are expected
51 /// based on the size of the data and the chunk size.
52 ///
53 /// # Returns
54 ///
55 /// The total number of chunks (`usize`) that are expected for the data.
56 #[must_use]
57 pub const fn chunk_count(&self) -> usize {
58 self.bit_array.bit_count()
59 }
60
61 /// Checks if all chunks have been received.
62 ///
63 /// # Returns
64 /// `true` if all chunks have been received; `false` otherwise.
65 #[must_use]
66 pub const fn is_complete(&self) -> bool {
67 self.bit_array.all_set()
68 }
69
70 /// Returns a reference to the complete blob if all chunks have been received.
71 ///
72 /// # Returns
73 /// An `Option` containing a reference to the blob if complete; otherwise, `None`.
74 #[must_use]
75 pub fn blob(&self) -> Option<&[u8]> {
76 self.is_complete().then(|| &self.blob[..])
77 }
78
79 /// Sets a chunk of data at the specified `chunk_index` with the provided `payload`.
80 ///
81 /// # Parameters
82 /// - `chunk_index`: The index of the chunk to set.
83 /// - `payload`: A slice of octets representing the chunk's data.
84 ///
85 /// # Errors
86 /// Returns a `BlobError` if:
87 /// - The `chunk_index` is invalid.
88 /// - The `payload` size does not match the expected size for the chunk.
89 /// - The chunk has already been set, with either the same or different contents.
90 ///
91 /// # Returns
92 /// `Ok(())` if the chunk was set successfully; otherwise, a `BlobError`.
93 pub fn set_chunk(&mut self, chunk_index: ChunkIndex, payload: &[u8]) -> Result<(), BlobError> {
94 let chunk_count = self.bit_array.bit_count();
95 if chunk_index >= chunk_count {
96 return Err(BlobError::InvalidChunkIndex(chunk_index, chunk_count));
97 }
98
99 let expected_size = if chunk_index == chunk_count - 1 {
100 // It was the last chunk
101 if self.octet_count % self.fixed_chunk_size == 0 {
102 self.fixed_chunk_size
103 } else {
104 self.octet_count % self.fixed_chunk_size
105 }
106 } else {
107 self.fixed_chunk_size
108 };
109
110 if payload.len() != expected_size {
111 return Err(BlobError::UnexpectedChunkSize(
112 expected_size,
113 payload.len(),
114 chunk_index,
115 ));
116 }
117 let octet_offset = chunk_index * self.fixed_chunk_size;
118 if octet_offset + expected_size > self.blob.len() {
119 return Err(BlobError::OutOfBounds);
120 }
121
122 if self.bit_array.get(chunk_index) {
123 // It has been set previously
124 let is_same_contents =
125 &self.blob[octet_offset..octet_offset + expected_size] == payload;
126
127 let err = if is_same_contents {
128 return Ok(());
129 } else {
130 BlobError::RedundantContentDiffers(chunk_index)
131 };
132
133 return Err(err);
134 }
135
136 self.blob[octet_offset..octet_offset + expected_size].copy_from_slice(payload);
137
138 self.bit_array.set(chunk_index);
139
140 Ok(())
141 }
142}