Skip to main content

commonware_cryptography/reed_solomon/rate/
decoder_work.rs

1use crate::reed_solomon::{
2    Error, Plan,
3    engine::{SHARD_CHUNK_BYTES, Shards, ShardsRefMut},
4};
5use fixedbitset::FixedBitSet;
6
7/// Working space for [`RateDecoder`].
8///
9/// [`RateDecoder`]: crate::reed_solomon::rate::RateDecoder
10pub struct DecoderWork {
11    original_count: usize,
12    recovery_count: usize,
13    shard_bytes: usize,
14
15    /// Index in `shards` of original shard 0.
16    original_base_pos: usize,
17
18    /// Index in `shards` of recovery shard 0.
19    recovery_base_pos: usize,
20
21    original_received_count: usize,
22    recovery_received_count: usize,
23
24    /// Received flags indexed by position in `shards`, not by shard index.
25    ///
26    /// May contain extra zero bits.
27    received: FixedBitSet,
28
29    /// Decoding workspace. Original and recovery shard `i` sit at their base position plus `i`.
30    shards: Shards,
31}
32
33impl DecoderWork {
34    /// Creates a new [`DecoderWork`] with no working space allocated.
35    pub const fn new() -> Self {
36        Self {
37            original_count: 0,
38            recovery_count: 0,
39            shard_bytes: 0,
40
41            original_base_pos: 0,
42            recovery_base_pos: 0,
43
44            original_received_count: 0,
45            recovery_received_count: 0,
46            received: FixedBitSet::new(),
47            shards: Shards::new(),
48        }
49    }
50}
51
52impl Default for DecoderWork {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl DecoderWork {
59    /// Returns [`Error::PlanMismatch`] unless `plan` was built for these shard counts,
60    /// this rate, and these received shard indices.
61    pub(crate) fn validate_plan(&self, plan: &Plan, high_rate: bool) -> Result<(), Error> {
62        if plan.matches(
63            self.original_count,
64            self.recovery_count,
65            high_rate,
66            &self.received,
67        ) {
68            Ok(())
69        } else {
70            Err(Error::PlanMismatch)
71        }
72    }
73
74    /// Stores original shard `index` at `original_base_pos + index` and marks it received.
75    ///
76    /// Returns an error if `index` is out of range or already received, or if the shard is not
77    /// `shard_bytes` long.
78    pub(crate) fn add_original_shard<T: AsRef<[u8]>>(
79        &mut self,
80        index: usize,
81        original_shard: T,
82    ) -> Result<(), Error> {
83        if index >= self.original_count {
84            return Err(Error::InvalidOriginalShardIndex {
85                original_count: self.original_count,
86                index,
87            });
88        }
89
90        let pos = self.original_base_pos + index;
91        let original_shard = original_shard.as_ref();
92
93        if self.received[pos] {
94            Err(Error::DuplicateOriginalShardIndex { index })
95        } else if original_shard.len() != self.shard_bytes {
96            Err(Error::DifferentShardSize {
97                shard_bytes: self.shard_bytes,
98                got: original_shard.len(),
99            })
100        } else {
101            self.shards.insert(pos, original_shard);
102
103            self.original_received_count += 1;
104            self.received.set(pos, true);
105            Ok(())
106        }
107    }
108
109    /// Stores recovery shard `index` at `recovery_base_pos + index` and marks it received.
110    ///
111    /// Returns an error if `index` is out of range or already received, or if the shard is not
112    /// `shard_bytes` long.
113    pub(crate) fn add_recovery_shard<T: AsRef<[u8]>>(
114        &mut self,
115        index: usize,
116        recovery_shard: T,
117    ) -> Result<(), Error> {
118        if index >= self.recovery_count {
119            return Err(Error::InvalidRecoveryShardIndex {
120                recovery_count: self.recovery_count,
121                index,
122            });
123        }
124
125        let pos = self.recovery_base_pos + index;
126        let recovery_shard = recovery_shard.as_ref();
127
128        if self.received[pos] {
129            Err(Error::DuplicateRecoveryShardIndex { index })
130        } else if recovery_shard.len() != self.shard_bytes {
131            Err(Error::DifferentShardSize {
132                shard_bytes: self.shard_bytes,
133                got: recovery_shard.len(),
134            })
135        } else {
136            self.shards.insert(pos, recovery_shard);
137
138            self.recovery_received_count += 1;
139            self.received.set(pos, true);
140            Ok(())
141        }
142    }
143
144    /// Begins a decode.
145    ///
146    /// Returns the work shards, the original and recovery counts, and the received flags. Returns
147    /// `Ok(None)` if every original was received, or [`Error::NotEnoughShards`] if fewer than
148    /// `original_count` shards were received.
149    ///
150    /// The returned `FixedBitSet` may contain extra zero bits.
151    pub(crate) fn decode_begin(
152        &mut self,
153    ) -> Result<Option<(ShardsRefMut<'_>, usize, usize, &FixedBitSet)>, Error> {
154        if self.original_received_count + self.recovery_received_count < self.original_count {
155            Err(Error::NotEnoughShards {
156                original_count: self.original_count,
157                original_received_count: self.original_received_count,
158                recovery_received_count: self.recovery_received_count,
159            })
160        } else if self.original_received_count == self.original_count {
161            Ok(None)
162        } else {
163            Ok(Some((
164                self.shards.as_ref_mut(),
165                self.original_count,
166                self.recovery_count,
167                &self.received,
168            )))
169        }
170    }
171
172    /// Returns the number of original shards.
173    pub(crate) const fn original_count(&self) -> usize {
174        self.original_count
175    }
176
177    /// Configures this work for new shard counts, clears the received state, and resizes
178    /// `shards` to `work_count` shards.
179    ///
180    /// Original shard `i` is stored at `original_base_pos + i` and recovery shard `i` at
181    /// `recovery_base_pos + i`. Retained shard contents are not zeroed.
182    ///
183    /// # Panics
184    ///
185    /// Panics if `shard_bytes` is odd.
186    pub(crate) fn reset(
187        &mut self,
188        original_count: usize,
189        recovery_count: usize,
190        shard_bytes: usize,
191
192        original_base_pos: usize,
193        recovery_base_pos: usize,
194        work_count: usize,
195    ) {
196        assert!(shard_bytes.is_multiple_of(2));
197
198        self.original_count = original_count;
199        self.recovery_count = recovery_count;
200        self.shard_bytes = shard_bytes;
201
202        self.original_base_pos = original_base_pos;
203        self.recovery_base_pos = recovery_base_pos;
204
205        self.original_received_count = 0;
206        self.recovery_received_count = 0;
207
208        let max_received_pos = core::cmp::max(
209            original_base_pos + original_count,
210            recovery_base_pos + recovery_count,
211        );
212
213        self.received.clear();
214        if self.received.len() < max_received_pos {
215            self.received.grow(max_received_pos);
216        }
217
218        self.shards
219            .resize(work_count, shard_bytes.div_ceil(SHARD_CHUNK_BYTES));
220    }
221
222    /// Clears the received shards so new ones can be added under the same configuration.
223    pub(crate) fn reset_received(&mut self) {
224        self.original_received_count = 0;
225        self.recovery_received_count = 0;
226        self.received.clear();
227    }
228
229    /// Returns a restored original shard, or `None` if `index` is out of range or the shard was
230    /// received.
231    ///
232    /// This must only be called by `DecoderResult`.
233    pub(crate) fn original(&self, index: usize) -> Option<&[u8]> {
234        if index >= self.original_count {
235            return None;
236        }
237        let pos = self.original_base_pos + index;
238
239        if !self.received[pos] {
240            Some(&self.shards[pos].as_flattened()[..self.shard_bytes])
241        } else {
242            None
243        }
244    }
245
246    /// Returns a reconstructed recovery shard, or `None` if `index` is not a missing recovery
247    /// shard.
248    ///
249    /// This must only be called by `RecoveryDecoderResult`, which is produced only by a
250    /// recovery-computing decode (an original was missing and `compute_recovery` was set), so the
251    /// recovery work buffers hold canonical values.
252    pub(crate) fn recovery(&self, index: usize) -> Option<&[u8]> {
253        if index >= self.recovery_count {
254            return None;
255        }
256        let pos = self.recovery_base_pos + index;
257
258        if self.missing_original_count() > 0 && !self.received[pos] {
259            Some(&self.shards[pos].as_flattened()[..self.shard_bytes])
260        } else {
261            None
262        }
263    }
264
265    /// Undoes the last-chunk encoding of every original shard position.
266    pub(crate) fn undo_last_chunk_encoding(&mut self) {
267        self.shards.undo_last_chunk_encoding(
268            self.shard_bytes,
269            self.original_base_pos..self.original_base_pos + self.original_count,
270        );
271    }
272
273    /// Undoes the last-chunk encoding of every recovery shard position.
274    ///
275    /// Call only after a decode that reconstructed the recovery shards.
276    pub(crate) fn undo_last_chunk_encoding_recovery(&mut self) {
277        self.shards.undo_last_chunk_encoding(
278            self.shard_bytes,
279            self.recovery_base_pos..self.recovery_base_pos + self.recovery_count,
280        );
281    }
282
283    /// Returns the number of original shards not received.
284    pub(crate) const fn missing_original_count(&self) -> usize {
285        self.original_count - self.original_received_count
286    }
287
288    /// Returns the number of recovery shards.
289    pub(crate) const fn recovery_count(&self) -> usize {
290        self.recovery_count
291    }
292
293    /// Returns the number of recovery shards not received.
294    pub(crate) const fn missing_recovery_count(&self) -> usize {
295        self.recovery_count - self.recovery_received_count
296    }
297}