Skip to main content

commonware_cryptography/reed_solomon/rate/
decoder_work.rs

1use crate::reed_solomon::{
2    engine::{Shards, ShardsRefMut, SHARD_CHUNK_BYTES},
3    Error,
4};
5use fixedbitset::FixedBitSet;
6
7// ======================================================================
8// DecoderWork - PUBLIC
9
10/// Working space for [`RateDecoder`].
11///
12/// [`RateDecoder`]: crate::reed_solomon::rate::RateDecoder
13pub struct DecoderWork {
14    original_count: usize,
15    recovery_count: usize,
16    shard_bytes: usize,
17
18    original_base_pos: usize,
19    recovery_base_pos: usize,
20
21    original_received_count: usize,
22    recovery_received_count: usize,
23    // May contain extra zero bits.
24    received: FixedBitSet,
25    shards: Shards,
26}
27
28impl DecoderWork {
29    /// Creates new [`DecoderWork`] which initially
30    /// has no working space allocated.
31    pub const fn new() -> Self {
32        Self {
33            original_count: 0,
34            recovery_count: 0,
35            shard_bytes: 0,
36
37            original_base_pos: 0,
38            recovery_base_pos: 0,
39
40            original_received_count: 0,
41            recovery_received_count: 0,
42            received: FixedBitSet::new(),
43            shards: Shards::new(),
44        }
45    }
46}
47
48// ======================================================================
49// DecoderWork - IMPL Default
50
51impl Default for DecoderWork {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57// ======================================================================
58// DecoderWork - CRATE
59
60impl DecoderWork {
61    pub(crate) fn add_original_shard<T: AsRef<[u8]>>(
62        &mut self,
63        index: usize,
64        original_shard: T,
65    ) -> Result<(), Error> {
66        let pos = self.original_base_pos + index;
67        let original_shard = original_shard.as_ref();
68
69        if index >= self.original_count {
70            Err(Error::InvalidOriginalShardIndex {
71                original_count: self.original_count,
72                index,
73            })
74        } else if self.received[pos] {
75            Err(Error::DuplicateOriginalShardIndex { index })
76        } else if original_shard.len() != self.shard_bytes {
77            Err(Error::DifferentShardSize {
78                shard_bytes: self.shard_bytes,
79                got: original_shard.len(),
80            })
81        } else {
82            self.shards.insert(pos, original_shard);
83
84            self.original_received_count += 1;
85            self.received.set(pos, true);
86            Ok(())
87        }
88    }
89
90    pub(crate) fn add_recovery_shard<T: AsRef<[u8]>>(
91        &mut self,
92        index: usize,
93        recovery_shard: T,
94    ) -> Result<(), Error> {
95        let pos = self.recovery_base_pos + index;
96        let recovery_shard = recovery_shard.as_ref();
97
98        if index >= self.recovery_count {
99            Err(Error::InvalidRecoveryShardIndex {
100                recovery_count: self.recovery_count,
101                index,
102            })
103        } else if self.received[pos] {
104            Err(Error::DuplicateRecoveryShardIndex { index })
105        } else if recovery_shard.len() != self.shard_bytes {
106            Err(Error::DifferentShardSize {
107                shard_bytes: self.shard_bytes,
108                got: recovery_shard.len(),
109            })
110        } else {
111            self.shards.insert(pos, recovery_shard);
112
113            self.recovery_received_count += 1;
114            self.received.set(pos, true);
115            Ok(())
116        }
117    }
118
119    // Begin decode.
120    // - Returned `FixedBitSet` may contain extra zero bits.
121    pub(crate) fn decode_begin(
122        &mut self,
123    ) -> Result<Option<(ShardsRefMut<'_>, usize, usize, &FixedBitSet)>, Error> {
124        if self.original_received_count + self.recovery_received_count < self.original_count {
125            Err(Error::NotEnoughShards {
126                original_count: self.original_count,
127                original_received_count: self.original_received_count,
128                recovery_received_count: self.recovery_received_count,
129            })
130        } else if self.original_received_count == self.original_count {
131            Ok(None)
132        } else {
133            Ok(Some((
134                self.shards.as_ref_mut(),
135                self.original_count,
136                self.recovery_count,
137                &self.received,
138            )))
139        }
140    }
141
142    pub(crate) const fn original_count(&self) -> usize {
143        self.original_count
144    }
145
146    pub(crate) fn reset(
147        &mut self,
148        original_count: usize,
149        recovery_count: usize,
150        shard_bytes: usize,
151
152        original_base_pos: usize,
153        recovery_base_pos: usize,
154        work_count: usize,
155    ) {
156        assert!(shard_bytes.is_multiple_of(2));
157
158        self.original_count = original_count;
159        self.recovery_count = recovery_count;
160        self.shard_bytes = shard_bytes;
161
162        self.original_base_pos = original_base_pos;
163        self.recovery_base_pos = recovery_base_pos;
164
165        self.original_received_count = 0;
166        self.recovery_received_count = 0;
167
168        let max_received_pos = core::cmp::max(
169            original_base_pos + original_count,
170            recovery_base_pos + recovery_count,
171        );
172
173        self.received.clear();
174        if self.received.len() < max_received_pos {
175            self.received.grow(max_received_pos);
176        }
177
178        self.shards
179            .resize(work_count, shard_bytes.div_ceil(SHARD_CHUNK_BYTES));
180    }
181
182    pub(crate) fn reset_received(&mut self) {
183        self.original_received_count = 0;
184        self.recovery_received_count = 0;
185        self.received.clear();
186    }
187
188    // This must only be called by `DecoderResult`.
189    pub(crate) fn original(&self, index: usize) -> Option<&[u8]> {
190        let pos = self.original_base_pos + index;
191
192        if index < self.original_count && !self.received[pos] {
193            Some(&self.shards[pos].as_flattened()[..self.shard_bytes])
194        } else {
195            None
196        }
197    }
198
199    // This must only be called by `RecoveryDecoderResult`, which is produced only by a
200    // recovery-computing decode (an original was missing and `compute_recovery` was set), so the
201    // recovery work buffers hold canonical values. Returns a reconstructed recovery shard, or `None`
202    // if `index` is not a missing recovery shard.
203    pub(crate) fn recovery(&self, index: usize) -> Option<&[u8]> {
204        let pos = self.recovery_base_pos + index;
205
206        if self.missing_original_count() > 0 && index < self.recovery_count && !self.received[pos] {
207            Some(&self.shards[pos].as_flattened()[..self.shard_bytes])
208        } else {
209            None
210        }
211    }
212
213    pub(crate) fn undo_last_chunk_encoding(&mut self) {
214        self.shards.undo_last_chunk_encoding(
215            self.shard_bytes,
216            self.original_base_pos..self.original_base_pos + self.original_count,
217        );
218    }
219
220    pub(crate) fn undo_last_chunk_encoding_recovery(&mut self) {
221        self.shards.undo_last_chunk_encoding(
222            self.shard_bytes,
223            self.recovery_base_pos..self.recovery_base_pos + self.recovery_count,
224        );
225    }
226
227    pub(crate) const fn missing_original_count(&self) -> usize {
228        self.original_count - self.original_received_count
229    }
230
231    pub(crate) const fn recovery_count(&self) -> usize {
232        self.recovery_count
233    }
234
235    pub(crate) const fn missing_recovery_count(&self) -> usize {
236        self.recovery_count - self.recovery_received_count
237    }
238}