use super::*;
pub const ERASURE_CODING_XOR_2_1: &str = "xor-2+1";
pub const ERASURE_CODING_REED_SOLOMON: &str = "reed-solomon";
pub const ERASURE_CODING_LOCALLY_REPAIRABLE: &str = "locally-repairable";
pub const ERASURE_CODING_INLINE_REPLICATION: &str = "inline-replication";
const ERASURE_CODING_CAPABILITIES: &[&str] = &[
"ec-support",
"parity-schemes",
"configurable-k-plus-m",
"repair-cost",
"small-object-behavior",
"native-support-state",
"semantic-parity",
"configuration-admin-surface",
"security-governance-impact",
"observability-evidence",
"failure-mode-behavior",
"validation-test-coverage",
"product-specific-caveats",
];
const ERASURE_CODING_CAVEATS: &[&str] = &[
"BucketWarden supports a deterministic local XOR 2+1 erasure coding profile for runtime proof.",
"The XOR 2+1 profile can reconstruct one missing data shard from the parity shard.",
"Reed-Solomon, locally repairable codes, and inline replication are tracked but fail closed outside the current runtime boundary.",
"Erasure coding proof is local runtime behavior and does not claim distributed placement or storage-class SLA semantics.",
];
const ERASURE_CODING_FAILURE_MODES: &[&str] = &[
"unsupported-profile-rejected",
"invalid-k-plus-m-rejected",
"too-many-missing-shards-rejected",
"parity-mismatch-detected",
];
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ErasureCodingSupportEntry {
pub profile: &'static str,
pub k: u8,
pub m: u8,
pub native_support: bool,
pub semantic_parity: &'static str,
pub repair_cost: &'static str,
pub small_object_behavior: &'static str,
pub failure_mode: &'static str,
pub caveat: &'static str,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ErasureCodingSupportReport {
pub active_profile: &'static str,
pub supported_profiles: Vec<&'static str>,
pub unsupported_profiles: Vec<&'static str>,
pub k: u8,
pub m: u8,
pub small_object_threshold_bytes: usize,
pub capabilities: Vec<&'static str>,
pub failure_modes: Vec<&'static str>,
pub caveats: Vec<&'static str>,
pub entries: Vec<ErasureCodingSupportEntry>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ErasureCodedObject {
pub profile: &'static str,
pub original_len: usize,
pub k: u8,
pub m: u8,
pub shard_len: usize,
pub data_shards: Vec<Vec<u8>>,
pub parity_shards: Vec<Vec<u8>>,
}
impl BucketWarden {
pub fn erasure_coding_support_report(&self) -> ErasureCodingSupportReport {
ErasureCodingSupportReport {
active_profile: ERASURE_CODING_XOR_2_1,
supported_profiles: vec![ERASURE_CODING_XOR_2_1],
unsupported_profiles: vec![
ERASURE_CODING_REED_SOLOMON,
ERASURE_CODING_LOCALLY_REPAIRABLE,
ERASURE_CODING_INLINE_REPLICATION,
],
k: 2,
m: 1,
small_object_threshold_bytes: 1024,
capabilities: ERASURE_CODING_CAPABILITIES.to_vec(),
failure_modes: ERASURE_CODING_FAILURE_MODES.to_vec(),
caveats: ERASURE_CODING_CAVEATS.to_vec(),
entries: vec![
ErasureCodingSupportEntry {
profile: ERASURE_CODING_XOR_2_1,
k: 2,
m: 1,
native_support: true,
semantic_parity: "Deterministic local split into two data shards plus one XOR parity shard.",
repair_cost: "One missing data shard can be reconstructed by reading the other data shard and parity shard.",
small_object_behavior: "Small objects use the same profile with padded shards and exact original-length restoration.",
failure_mode: "Decode rejects missing parity, missing both data shards, and mismatched profile metadata.",
caveat: "XOR 2+1 is a local proof profile, not a distributed Reed-Solomon storage class.",
},
ErasureCodingSupportEntry {
profile: ERASURE_CODING_REED_SOLOMON,
k: 0,
m: 0,
native_support: false,
semantic_parity: "No Reed-Solomon matrix or multi-parity semantics are claimed.",
repair_cost: "Repair-cost estimates are rejected until a Reed-Solomon profile exists.",
small_object_behavior: "No Reed-Solomon small-object packing behavior is claimed.",
failure_mode: "Reed-Solomon profile selection is rejected as unsupported.",
caveat: "Requires a dedicated finite-field codec and placement model before support.",
},
ErasureCodingSupportEntry {
profile: ERASURE_CODING_LOCALLY_REPAIRABLE,
k: 0,
m: 0,
native_support: false,
semantic_parity: "No local repair group or topology-aware code semantics are claimed.",
repair_cost: "Repair-cost estimates are rejected until local repair groups exist.",
small_object_behavior: "No LRC small-object policy is claimed.",
failure_mode: "Locally repairable profile selection is rejected as unsupported.",
caveat: "Requires placement topology and repair-group semantics before support.",
},
ErasureCodingSupportEntry {
profile: ERASURE_CODING_INLINE_REPLICATION,
k: 0,
m: 0,
native_support: false,
semantic_parity: "Replication is tracked separately from erasure coding.",
repair_cost: "Repair cost is governed by replication strategy, not EC parity.",
small_object_behavior: "Inline replication does not claim EC small-object behavior.",
failure_mode: "Inline replication profile selection is rejected as unsupported.",
caveat: "Use the replication strategy support contract for replicated copies.",
},
],
}
}
pub fn ensure_erasure_coding_profile_supported(
&self,
profile: &str,
) -> Result<(), RuntimeError> {
let report = self.erasure_coding_support_report();
if report.supported_profiles.contains(&profile) {
Ok(())
} else {
Err(RuntimeError::UnsupportedErasureCodingProfile(
profile.to_string(),
))
}
}
pub fn erasure_code_object(
&self,
profile: &str,
body: &[u8],
) -> Result<ErasureCodedObject, RuntimeError> {
self.ensure_erasure_coding_profile_supported(profile)?;
let shard_len = body.len().div_ceil(2).max(1);
let mut left = vec![0u8; shard_len];
let mut right = vec![0u8; shard_len];
for (index, byte) in body.iter().enumerate() {
if index < shard_len {
left[index] = *byte;
} else {
right[index - shard_len] = *byte;
}
}
let parity = left
.iter()
.zip(right.iter())
.map(|(left, right)| left ^ right)
.collect::<Vec<_>>();
Ok(ErasureCodedObject {
profile: ERASURE_CODING_XOR_2_1,
original_len: body.len(),
k: 2,
m: 1,
shard_len,
data_shards: vec![left, right],
parity_shards: vec![parity],
})
}
pub fn reconstruct_erasure_coded_object(
&self,
coded: &ErasureCodedObject,
available_data_shards: [Option<Vec<u8>>; 2],
parity_shard: Option<Vec<u8>>,
) -> Result<Vec<u8>, RuntimeError> {
self.ensure_erasure_coding_profile_supported(coded.profile)?;
if coded.k != 2
|| coded.m != 1
|| coded.data_shards.len() != 2
|| coded.parity_shards.len() != 1
{
return Err(RuntimeError::InvalidErasureCodingLayout(
"xor-2+1 requires two data shards and one parity shard".to_string(),
));
}
let left = available_data_shards[0].clone();
let right = available_data_shards[1].clone();
let parity = parity_shard;
let (left, right) = match (left, right, parity) {
(Some(left), Some(right), _) => (left, right),
(Some(left), None, Some(parity)) => {
let right = left
.iter()
.zip(parity.iter())
.map(|(left, parity)| left ^ parity)
.collect::<Vec<_>>();
(left, right)
}
(None, Some(right), Some(parity)) => {
let left = right
.iter()
.zip(parity.iter())
.map(|(right, parity)| right ^ parity)
.collect::<Vec<_>>();
(left, right)
}
_ => return Err(RuntimeError::InvalidErasureCodingLayout(
"xor-2+1 reconstruction requires both data shards or one data shard plus parity"
.to_string(),
)),
};
if left.len() != coded.shard_len || right.len() != coded.shard_len {
return Err(RuntimeError::InvalidErasureCodingLayout(
"shard length does not match erasure coding metadata".to_string(),
));
}
let mut restored = left;
restored.extend(right);
restored.truncate(coded.original_len);
Ok(restored)
}
}