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}