rusty_erasure_core/error.rs
1//! Typed errors for every public operation.
2//!
3//! ISA-L's own README states that parameters passed to its functions are *not*
4//! validated — callers are responsible for argument validity. This crate's
5//! contract is the opposite (mission plan §3): every public entry point checks
6//! its inputs and returns one of these errors instead of reading out of bounds
7//! or panicking. Fuzzers hold that line from M1 onward.
8
9use core::fmt;
10
11/// Errors from coding-matrix construction and inversion.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum MatrixError {
15 /// The requested dimensions are unusable: `k` (source shards) or `p`
16 /// (parity shards) is zero, or `k + p` exceeds 255 — the Vandermonde and
17 /// Cauchy constructions run out of distinct GF(2^8) elements past that.
18 Dimensions {
19 /// Requested source-shard count.
20 k: usize,
21 /// Requested parity-shard count.
22 p: usize,
23 },
24 /// The requested `(k, p)` falls outside ISA-L's documented safe region for
25 /// Vandermonde matrices (mission plan §3), where some recovery submatrices
26 /// are singular. Use a Cauchy matrix instead — every submatrix inverts.
27 VandermondeUnsafe {
28 /// Requested source-shard count.
29 k: usize,
30 /// Requested parity-shard count.
31 p: usize,
32 },
33 /// The submatrix selected for recovery is singular and cannot be inverted.
34 Singular,
35}
36
37impl fmt::Display for MatrixError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match *self {
40 Self::Dimensions { k, p } => write!(
41 f,
42 "unusable matrix dimensions k={k}, p={p}: need k >= 1, p >= 1, k + p <= 255"
43 ),
44 Self::VandermondeUnsafe { k, p } => write!(
45 f,
46 "k={k}, p={p} is outside the safe Vandermonde region; use Matrix::cauchy"
47 ),
48 Self::Singular => f.write_str("recovery submatrix is singular"),
49 }
50 }
51}
52
53impl core::error::Error for MatrixError {}
54
55/// Errors from encode and update operations.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[non_exhaustive]
58pub enum CodeError {
59 /// The number of shard buffers supplied does not match the coder's matrix.
60 ShardCount {
61 /// Shard count the coder's matrix requires.
62 expected: usize,
63 /// Shard count actually supplied.
64 got: usize,
65 },
66 /// A shard buffer's length disagrees with the stripe's shard length.
67 ShardLength {
68 /// Index of the offending shard.
69 index: usize,
70 /// Length every shard in this call must have.
71 expected: usize,
72 /// Length actually supplied.
73 got: usize,
74 },
75 /// A shard index argument is out of range for the coder's matrix.
76 ShardIndex {
77 /// The out-of-range index.
78 index: usize,
79 /// Number of source shards in the coder's matrix.
80 k: usize,
81 },
82}
83
84impl fmt::Display for CodeError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match *self {
87 Self::ShardCount { expected, got } => {
88 write!(f, "wrong shard count: expected {expected}, got {got}")
89 }
90 Self::ShardLength {
91 index,
92 expected,
93 got,
94 } => write!(
95 f,
96 "shard {index} has length {got}, but this stripe's shard length is {expected}"
97 ),
98 Self::ShardIndex { index, k } => {
99 write!(f, "shard index {index} out of range for k={k} sources")
100 }
101 }
102 }
103}
104
105impl core::error::Error for CodeError {}
106
107/// Errors from the recovery path.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109#[non_exhaustive]
110pub enum RecoverError {
111 /// More shards are missing than the code can repair.
112 TooManyMissing {
113 /// Number of missing shards.
114 missing: usize,
115 /// Number of parity shards (the repair capacity).
116 p: usize,
117 },
118 /// The surviving-rows submatrix could not be inverted.
119 Matrix(MatrixError),
120 /// A shard buffer failed validation.
121 Code(CodeError),
122}
123
124impl fmt::Display for RecoverError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match *self {
127 Self::TooManyMissing { missing, p } => {
128 write!(
129 f,
130 "{missing} shards missing, but only {p} parity shards exist"
131 )
132 }
133 Self::Matrix(e) => write!(f, "recovery matrix error: {e}"),
134 Self::Code(e) => write!(f, "recovery shard error: {e}"),
135 }
136 }
137}
138
139impl core::error::Error for RecoverError {
140 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
141 match self {
142 Self::Matrix(e) => Some(e),
143 Self::Code(e) => Some(e),
144 Self::TooManyMissing { .. } => None,
145 }
146 }
147}
148
149impl From<MatrixError> for RecoverError {
150 fn from(e: MatrixError) -> Self {
151 Self::Matrix(e)
152 }
153}
154
155impl From<CodeError> for RecoverError {
156 fn from(e: CodeError) -> Self {
157 Self::Code(e)
158 }
159}