#![forbid(unsafe_code)]
use crate::core::extent::ChunkId;
use crate::core::representation::Representation;
use crate::store::{ExtentUpdate, Store, StoreError};
pub const REBASE_DEPTH_THRESHOLD: u8 = 2;
pub const fn depth_of(desc: &Representation) -> u8 {
match desc {
Representation::ExactRef { .. }
| Representation::BaseResidual { .. }
| Representation::SequenceDict { .. } => 1,
_ => 0,
}
}
pub fn chain_depth(store: &Store, desc: &Representation) -> u8 {
let limits = *store.limits();
fn walk(store: &Store, limits: &crate::core::limits::Limits, id: ChunkId) -> u8 {
let mut max_depth = 0u8;
let mut stack: Vec<(ChunkId, u8)> = vec![(id, 0u8)];
let mut visited: std::collections::HashSet<ChunkId> = std::collections::HashSet::new();
while let Some((cur, d)) = stack.pop() {
if d >= limits.max_reference_depth || !visited.insert(cur) {
continue;
}
let Some(desc_bytes) = store.chunk_descriptor(&cur).ok().flatten() else {
continue;
};
let Ok(next_desc) = crate::format::descriptor::decode(
&desc_bytes,
limits.max_descriptor_bytes,
limits.max_inline_bytes,
limits.max_palette,
limits.max_period,
limits.max_chunk_size,
) else {
continue;
};
let mut nexts: Vec<ChunkId> = Vec::new();
match &next_desc {
Representation::ExactRef { target, .. } => nexts.push(*target),
Representation::BaseResidual { base, .. } => nexts.push(*base),
Representation::SequenceDict { dictionary, .. } => nexts.push(*dictionary),
Representation::SequenceSharedDict {
dictionary, shared, ..
} => {
if !dictionary.is_zero() {
nexts.push(*dictionary);
}
nexts.push(*shared);
}
_ => {}
}
for n in nexts {
stack.push((n, d.saturating_add(1)));
max_depth = max_depth.max(d.saturating_add(1));
}
}
max_depth
}
match desc {
Representation::ExactRef { target, .. } => walk(store, &limits, *target).saturating_add(1),
Representation::BaseResidual { base, .. } => walk(store, &limits, *base).saturating_add(1),
Representation::SequenceDict { dictionary, .. } => {
walk(store, &limits, *dictionary).saturating_add(1)
}
Representation::SequenceSharedDict {
dictionary, shared, ..
} => {
let d = if dictionary.is_zero() {
0
} else {
walk(store, &limits, *dictionary)
};
let s = walk(store, &limits, *shared);
d.max(s).saturating_add(1)
}
_ => 0,
}
}
pub fn chain_contains(
store: &Store,
base: &crate::core::candidate::BaseChunk,
target: &ChunkId,
) -> bool {
let limits = *store.limits();
let mut stack: Vec<(ChunkId, u8)> = vec![(base.id, 0)];
let mut visited: std::collections::HashSet<ChunkId> = std::collections::HashSet::new();
while let Some((cur_id, depth)) = stack.pop() {
if &cur_id == target {
return true;
}
if depth >= limits.max_reference_depth || !visited.insert(cur_id) {
continue;
}
let Some(desc_bytes) = store.chunk_descriptor(&cur_id).ok().flatten() else {
continue;
};
let Ok(desc) = crate::format::descriptor::decode(
&desc_bytes,
limits.max_descriptor_bytes,
limits.max_inline_bytes,
limits.max_palette,
limits.max_period,
limits.max_chunk_size,
) else {
continue;
};
let mut nexts: Vec<ChunkId> = Vec::new();
match &desc {
Representation::ExactRef { target: t, .. } => nexts.push(*t),
Representation::BaseResidual { base: b, .. } => nexts.push(*b),
Representation::SequenceDict { dictionary: d, .. } => nexts.push(*d),
Representation::SequenceSharedDict {
dictionary, shared, ..
} => {
if !dictionary.is_zero() {
nexts.push(*dictionary);
}
nexts.push(*shared);
}
_ => {}
}
for n in nexts {
stack.push((n, depth.saturating_add(1)));
}
}
false
}
pub fn flatten_if_deep(
store: &Store,
start: u64,
desc: &Representation,
bytes: &[u8],
cid: &ChunkId,
) -> Result<Option<ExtentUpdate>, StoreError> {
if chain_depth(store, desc) < REBASE_DEPTH_THRESHOLD {
return Ok(None);
}
let limits = *store.limits();
let policy = *store.policy();
let update = Store::encode_chunk(bytes, start, *cid, &limits, &policy)?;
let resolver = crate::optimizer::search::CandidateResolver::new(
store,
update
.objects
.iter()
.map(|o| (o.id, o.payload.clone()))
.collect(),
None,
);
let back = crate::core::materialize::materialize_to_vec(&update.descriptor, &resolver, &limits)
.map_err(|e| StoreError::Descriptor(e.to_string()))?;
if back != bytes {
return Ok(None); }
Ok(Some(update))
}