Skip to main content

bucketwarden_server/
erasure_coding_support.rs

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}