1use super::*;
2
3pub const ERASURE_CODING_XOR_2_1: &str = "xor-2+1";
4pub const ERASURE_CODING_REED_SOLOMON: &str = "reed-solomon";
5pub const ERASURE_CODING_LOCALLY_REPAIRABLE: &str = "locally-repairable";
6pub const ERASURE_CODING_INLINE_REPLICATION: &str = "inline-replication";
7
8const ERASURE_CODING_CAPABILITIES: &[&str] = &[
9 "ec-support",
10 "parity-schemes",
11 "configurable-k-plus-m",
12 "repair-cost",
13 "small-object-behavior",
14 "native-support-state",
15 "semantic-parity",
16 "configuration-admin-surface",
17 "security-governance-impact",
18 "observability-evidence",
19 "failure-mode-behavior",
20 "validation-test-coverage",
21 "product-specific-caveats",
22];
23
24const ERASURE_CODING_CAVEATS: &[&str] = &[
25 "BucketWarden supports a deterministic local XOR 2+1 erasure coding profile for runtime proof.",
26 "The XOR 2+1 profile can reconstruct one missing data shard from the parity shard.",
27 "Reed-Solomon, locally repairable codes, and inline replication are tracked but fail closed outside the current runtime boundary.",
28 "Erasure coding proof is local runtime behavior and does not claim distributed placement or storage-class SLA semantics.",
29];
30
31const ERASURE_CODING_FAILURE_MODES: &[&str] = &[
32 "unsupported-profile-rejected",
33 "invalid-k-plus-m-rejected",
34 "too-many-missing-shards-rejected",
35 "parity-mismatch-detected",
36];
37
38#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
39pub struct ErasureCodingSupportEntry {
40 pub profile: &'static str,
41 pub k: u8,
42 pub m: u8,
43 pub native_support: bool,
44 pub semantic_parity: &'static str,
45 pub repair_cost: &'static str,
46 pub small_object_behavior: &'static str,
47 pub failure_mode: &'static str,
48 pub caveat: &'static str,
49}
50
51#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
52pub struct ErasureCodingSupportReport {
53 pub active_profile: &'static str,
54 pub supported_profiles: Vec<&'static str>,
55 pub unsupported_profiles: Vec<&'static str>,
56 pub k: u8,
57 pub m: u8,
58 pub small_object_threshold_bytes: usize,
59 pub capabilities: Vec<&'static str>,
60 pub failure_modes: Vec<&'static str>,
61 pub caveats: Vec<&'static str>,
62 pub entries: Vec<ErasureCodingSupportEntry>,
63}
64
65#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
66pub struct ErasureCodedObject {
67 pub profile: &'static str,
68 pub original_len: usize,
69 pub k: u8,
70 pub m: u8,
71 pub shard_len: usize,
72 pub data_shards: Vec<Vec<u8>>,
73 pub parity_shards: Vec<Vec<u8>>,
74}
75
76impl BucketWarden {
77 pub fn erasure_coding_support_report(&self) -> ErasureCodingSupportReport {
78 ErasureCodingSupportReport {
79 active_profile: ERASURE_CODING_XOR_2_1,
80 supported_profiles: vec![ERASURE_CODING_XOR_2_1],
81 unsupported_profiles: vec![
82 ERASURE_CODING_REED_SOLOMON,
83 ERASURE_CODING_LOCALLY_REPAIRABLE,
84 ERASURE_CODING_INLINE_REPLICATION,
85 ],
86 k: 2,
87 m: 1,
88 small_object_threshold_bytes: 1024,
89 capabilities: ERASURE_CODING_CAPABILITIES.to_vec(),
90 failure_modes: ERASURE_CODING_FAILURE_MODES.to_vec(),
91 caveats: ERASURE_CODING_CAVEATS.to_vec(),
92 entries: vec![
93 ErasureCodingSupportEntry {
94 profile: ERASURE_CODING_XOR_2_1,
95 k: 2,
96 m: 1,
97 native_support: true,
98 semantic_parity: "Deterministic local split into two data shards plus one XOR parity shard.",
99 repair_cost: "One missing data shard can be reconstructed by reading the other data shard and parity shard.",
100 small_object_behavior: "Small objects use the same profile with padded shards and exact original-length restoration.",
101 failure_mode: "Decode rejects missing parity, missing both data shards, and mismatched profile metadata.",
102 caveat: "XOR 2+1 is a local proof profile, not a distributed Reed-Solomon storage class.",
103 },
104 ErasureCodingSupportEntry {
105 profile: ERASURE_CODING_REED_SOLOMON,
106 k: 0,
107 m: 0,
108 native_support: false,
109 semantic_parity: "No Reed-Solomon matrix or multi-parity semantics are claimed.",
110 repair_cost: "Repair-cost estimates are rejected until a Reed-Solomon profile exists.",
111 small_object_behavior: "No Reed-Solomon small-object packing behavior is claimed.",
112 failure_mode: "Reed-Solomon profile selection is rejected as unsupported.",
113 caveat: "Requires a dedicated finite-field codec and placement model before support.",
114 },
115 ErasureCodingSupportEntry {
116 profile: ERASURE_CODING_LOCALLY_REPAIRABLE,
117 k: 0,
118 m: 0,
119 native_support: false,
120 semantic_parity: "No local repair group or topology-aware code semantics are claimed.",
121 repair_cost: "Repair-cost estimates are rejected until local repair groups exist.",
122 small_object_behavior: "No LRC small-object policy is claimed.",
123 failure_mode: "Locally repairable profile selection is rejected as unsupported.",
124 caveat: "Requires placement topology and repair-group semantics before support.",
125 },
126 ErasureCodingSupportEntry {
127 profile: ERASURE_CODING_INLINE_REPLICATION,
128 k: 0,
129 m: 0,
130 native_support: false,
131 semantic_parity: "Replication is tracked separately from erasure coding.",
132 repair_cost: "Repair cost is governed by replication strategy, not EC parity.",
133 small_object_behavior: "Inline replication does not claim EC small-object behavior.",
134 failure_mode: "Inline replication profile selection is rejected as unsupported.",
135 caveat: "Use the replication strategy support contract for replicated copies.",
136 },
137 ],
138 }
139 }
140
141 pub fn ensure_erasure_coding_profile_supported(
142 &self,
143 profile: &str,
144 ) -> Result<(), RuntimeError> {
145 let report = self.erasure_coding_support_report();
146 if report.supported_profiles.contains(&profile) {
147 Ok(())
148 } else {
149 Err(RuntimeError::UnsupportedErasureCodingProfile(
150 profile.to_string(),
151 ))
152 }
153 }
154
155 pub fn erasure_code_object(
156 &self,
157 profile: &str,
158 body: &[u8],
159 ) -> Result<ErasureCodedObject, RuntimeError> {
160 self.ensure_erasure_coding_profile_supported(profile)?;
161 let shard_len = body.len().div_ceil(2).max(1);
162 let mut left = vec![0u8; shard_len];
163 let mut right = vec![0u8; shard_len];
164 for (index, byte) in body.iter().enumerate() {
165 if index < shard_len {
166 left[index] = *byte;
167 } else {
168 right[index - shard_len] = *byte;
169 }
170 }
171 let parity = left
172 .iter()
173 .zip(right.iter())
174 .map(|(left, right)| left ^ right)
175 .collect::<Vec<_>>();
176 Ok(ErasureCodedObject {
177 profile: ERASURE_CODING_XOR_2_1,
178 original_len: body.len(),
179 k: 2,
180 m: 1,
181 shard_len,
182 data_shards: vec![left, right],
183 parity_shards: vec![parity],
184 })
185 }
186
187 pub fn reconstruct_erasure_coded_object(
188 &self,
189 coded: &ErasureCodedObject,
190 available_data_shards: [Option<Vec<u8>>; 2],
191 parity_shard: Option<Vec<u8>>,
192 ) -> Result<Vec<u8>, RuntimeError> {
193 self.ensure_erasure_coding_profile_supported(coded.profile)?;
194 if coded.k != 2
195 || coded.m != 1
196 || coded.data_shards.len() != 2
197 || coded.parity_shards.len() != 1
198 {
199 return Err(RuntimeError::InvalidErasureCodingLayout(
200 "xor-2+1 requires two data shards and one parity shard".to_string(),
201 ));
202 }
203 let left = available_data_shards[0].clone();
204 let right = available_data_shards[1].clone();
205 let parity = parity_shard;
206 let (left, right) = match (left, right, parity) {
207 (Some(left), Some(right), _) => (left, right),
208 (Some(left), None, Some(parity)) => {
209 let right = left
210 .iter()
211 .zip(parity.iter())
212 .map(|(left, parity)| left ^ parity)
213 .collect::<Vec<_>>();
214 (left, right)
215 }
216 (None, Some(right), Some(parity)) => {
217 let left = right
218 .iter()
219 .zip(parity.iter())
220 .map(|(right, parity)| right ^ parity)
221 .collect::<Vec<_>>();
222 (left, right)
223 }
224 _ => return Err(RuntimeError::InvalidErasureCodingLayout(
225 "xor-2+1 reconstruction requires both data shards or one data shard plus parity"
226 .to_string(),
227 )),
228 };
229 if left.len() != coded.shard_len || right.len() != coded.shard_len {
230 return Err(RuntimeError::InvalidErasureCodingLayout(
231 "shard length does not match erasure coding metadata".to_string(),
232 ));
233 }
234 let mut restored = left;
235 restored.extend(right);
236 restored.truncate(coded.original_len);
237 Ok(restored)
238 }
239}