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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use crate::reed_solomon::{
Error, Plan,
engine::{SHARD_CHUNK_BYTES, Shards, ShardsRefMut},
};
use fixedbitset::FixedBitSet;
/// Working space for [`RateDecoder`].
///
/// [`RateDecoder`]: crate::reed_solomon::rate::RateDecoder
pub struct DecoderWork {
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
/// Index in `shards` of original shard 0.
original_base_pos: usize,
/// Index in `shards` of recovery shard 0.
recovery_base_pos: usize,
original_received_count: usize,
recovery_received_count: usize,
/// Received flags indexed by position in `shards`, not by shard index.
///
/// May contain extra zero bits.
received: FixedBitSet,
/// Decoding workspace. Original and recovery shard `i` sit at their base position plus `i`.
shards: Shards,
}
impl DecoderWork {
/// Creates a new [`DecoderWork`] with no working space allocated.
pub const fn new() -> Self {
Self {
original_count: 0,
recovery_count: 0,
shard_bytes: 0,
original_base_pos: 0,
recovery_base_pos: 0,
original_received_count: 0,
recovery_received_count: 0,
received: FixedBitSet::new(),
shards: Shards::new(),
}
}
}
impl Default for DecoderWork {
fn default() -> Self {
Self::new()
}
}
impl DecoderWork {
/// Returns [`Error::PlanMismatch`] unless `plan` was built for these shard counts,
/// this rate, and these received shard indices.
pub(crate) fn validate_plan(&self, plan: &Plan, high_rate: bool) -> Result<(), Error> {
if plan.matches(
self.original_count,
self.recovery_count,
high_rate,
&self.received,
) {
Ok(())
} else {
Err(Error::PlanMismatch)
}
}
/// Stores original shard `index` at `original_base_pos + index` and marks it received.
///
/// Returns an error if `index` is out of range or already received, or if the shard is not
/// `shard_bytes` long.
pub(crate) fn add_original_shard<T: AsRef<[u8]>>(
&mut self,
index: usize,
original_shard: T,
) -> Result<(), Error> {
if index >= self.original_count {
return Err(Error::InvalidOriginalShardIndex {
original_count: self.original_count,
index,
});
}
let pos = self.original_base_pos + index;
let original_shard = original_shard.as_ref();
if self.received[pos] {
Err(Error::DuplicateOriginalShardIndex { index })
} else if original_shard.len() != self.shard_bytes {
Err(Error::DifferentShardSize {
shard_bytes: self.shard_bytes,
got: original_shard.len(),
})
} else {
self.shards.insert(pos, original_shard);
self.original_received_count += 1;
self.received.set(pos, true);
Ok(())
}
}
/// Stores recovery shard `index` at `recovery_base_pos + index` and marks it received.
///
/// Returns an error if `index` is out of range or already received, or if the shard is not
/// `shard_bytes` long.
pub(crate) fn add_recovery_shard<T: AsRef<[u8]>>(
&mut self,
index: usize,
recovery_shard: T,
) -> Result<(), Error> {
if index >= self.recovery_count {
return Err(Error::InvalidRecoveryShardIndex {
recovery_count: self.recovery_count,
index,
});
}
let pos = self.recovery_base_pos + index;
let recovery_shard = recovery_shard.as_ref();
if self.received[pos] {
Err(Error::DuplicateRecoveryShardIndex { index })
} else if recovery_shard.len() != self.shard_bytes {
Err(Error::DifferentShardSize {
shard_bytes: self.shard_bytes,
got: recovery_shard.len(),
})
} else {
self.shards.insert(pos, recovery_shard);
self.recovery_received_count += 1;
self.received.set(pos, true);
Ok(())
}
}
/// Begins a decode.
///
/// Returns the work shards, the original and recovery counts, and the received flags. Returns
/// `Ok(None)` if every original was received, or [`Error::NotEnoughShards`] if fewer than
/// `original_count` shards were received.
///
/// The returned `FixedBitSet` may contain extra zero bits.
pub(crate) fn decode_begin(
&mut self,
) -> Result<Option<(ShardsRefMut<'_>, usize, usize, &FixedBitSet)>, Error> {
if self.original_received_count + self.recovery_received_count < self.original_count {
Err(Error::NotEnoughShards {
original_count: self.original_count,
original_received_count: self.original_received_count,
recovery_received_count: self.recovery_received_count,
})
} else if self.original_received_count == self.original_count {
Ok(None)
} else {
Ok(Some((
self.shards.as_ref_mut(),
self.original_count,
self.recovery_count,
&self.received,
)))
}
}
/// Returns the number of original shards.
pub(crate) const fn original_count(&self) -> usize {
self.original_count
}
/// Configures this work for new shard counts, clears the received state, and resizes
/// `shards` to `work_count` shards.
///
/// Original shard `i` is stored at `original_base_pos + i` and recovery shard `i` at
/// `recovery_base_pos + i`. Retained shard contents are not zeroed.
///
/// # Panics
///
/// Panics if `shard_bytes` is odd.
pub(crate) fn reset(
&mut self,
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
original_base_pos: usize,
recovery_base_pos: usize,
work_count: usize,
) {
assert!(shard_bytes.is_multiple_of(2));
self.original_count = original_count;
self.recovery_count = recovery_count;
self.shard_bytes = shard_bytes;
self.original_base_pos = original_base_pos;
self.recovery_base_pos = recovery_base_pos;
self.original_received_count = 0;
self.recovery_received_count = 0;
let max_received_pos = core::cmp::max(
original_base_pos + original_count,
recovery_base_pos + recovery_count,
);
self.received.clear();
if self.received.len() < max_received_pos {
self.received.grow(max_received_pos);
}
self.shards
.resize(work_count, shard_bytes.div_ceil(SHARD_CHUNK_BYTES));
}
/// Clears the received shards so new ones can be added under the same configuration.
pub(crate) fn reset_received(&mut self) {
self.original_received_count = 0;
self.recovery_received_count = 0;
self.received.clear();
}
/// Returns a restored original shard, or `None` if `index` is out of range or the shard was
/// received.
///
/// This must only be called by `DecoderResult`.
pub(crate) fn original(&self, index: usize) -> Option<&[u8]> {
if index >= self.original_count {
return None;
}
let pos = self.original_base_pos + index;
if !self.received[pos] {
Some(&self.shards[pos].as_flattened()[..self.shard_bytes])
} else {
None
}
}
/// Returns a reconstructed recovery shard, or `None` if `index` is not a missing recovery
/// shard.
///
/// This must only be called by `RecoveryDecoderResult`, which is produced only by a
/// recovery-computing decode (an original was missing and `compute_recovery` was set), so the
/// recovery work buffers hold canonical values.
pub(crate) fn recovery(&self, index: usize) -> Option<&[u8]> {
if index >= self.recovery_count {
return None;
}
let pos = self.recovery_base_pos + index;
if self.missing_original_count() > 0 && !self.received[pos] {
Some(&self.shards[pos].as_flattened()[..self.shard_bytes])
} else {
None
}
}
/// Undoes the last-chunk encoding of every original shard position.
pub(crate) fn undo_last_chunk_encoding(&mut self) {
self.shards.undo_last_chunk_encoding(
self.shard_bytes,
self.original_base_pos..self.original_base_pos + self.original_count,
);
}
/// Undoes the last-chunk encoding of every recovery shard position.
///
/// Call only after a decode that reconstructed the recovery shards.
pub(crate) fn undo_last_chunk_encoding_recovery(&mut self) {
self.shards.undo_last_chunk_encoding(
self.shard_bytes,
self.recovery_base_pos..self.recovery_base_pos + self.recovery_count,
);
}
/// Returns the number of original shards not received.
pub(crate) const fn missing_original_count(&self) -> usize {
self.original_count - self.original_received_count
}
/// Returns the number of recovery shards.
pub(crate) const fn recovery_count(&self) -> usize {
self.recovery_count
}
/// Returns the number of recovery shards not received.
pub(crate) const fn missing_recovery_count(&self) -> usize {
self.recovery_count - self.recovery_received_count
}
}