use decdn_bao_range::{AlignedRange, CHUNK_GROUP_BYTES, RangeVerifyError, align_range};
pub(crate) const MIN_SPLIT_SIZE: u64 = 16 * 1024 * 1024;
pub(crate) const MIN_VICTIM_KEEP: u64 = crate::ClientRangedStore::INGEST_CHECKPOINT_BYTES;
pub(crate) const MIN_STOLEN: u64 = MIN_SPLIT_SIZE / 2;
const _: () = assert!(MIN_VICTIM_KEEP + MIN_STOLEN <= MIN_SPLIT_SIZE);
fn victim_keeps(left: u64, stealer: Option<u64>, victim: Option<u64>) -> Option<u64> {
match (stealer, victim) {
(Some(stealer), Some(victim)) if stealer > 0 && victim > 0 => {
let kept =
u128::from(left) * u128::from(victim) / (u128::from(stealer) + u128::from(victim));
let kept = u64::try_from(kept).unwrap_or(left);
(left.saturating_sub(kept) >= MIN_STOLEN).then_some(kept)
}
_ => Some(left / 2),
}
}
pub(crate) fn without_runs(runs: &[(u64, u64)], cut: &[(u64, u64)]) -> Vec<(u64, u64)> {
let mut out = Vec::with_capacity(runs.len());
for &(start, len) in runs {
let mut pieces = vec![(start, start.saturating_add(len))];
for &(cut_start, cut_len) in cut {
let cut_end = cut_start.saturating_add(cut_len);
pieces = pieces
.into_iter()
.flat_map(|(s, e)| [(s, e.min(cut_start)), (s.max(cut_end), e)])
.filter(|&(s, e)| s < e)
.collect();
}
out.extend(pieces.into_iter().map(|(s, e)| (s, e - s)));
}
out
}
fn canonicalize_ranges(
spans: &[(u64, u64)],
total_bytes: u64,
) -> Result<Vec<(u64, u64)>, RangeVerifyError> {
let mut canon: Vec<(u64, u64)> = Vec::with_capacity(spans.len());
for &(start, len) in spans {
if len == 0 {
continue;
}
let oob = || RangeVerifyError::RangeOutOfBounds {
offset: start,
len,
blob_size: total_bytes,
};
let raw_end = start.checked_add(len).ok_or_else(oob)?;
if start >= total_bytes || raw_end > total_bytes {
return Err(oob());
}
let canon_start = (start / CHUNK_GROUP_BYTES) * CHUNK_GROUP_BYTES;
let canon_end = raw_end
.div_ceil(CHUNK_GROUP_BYTES)
.saturating_mul(CHUNK_GROUP_BYTES)
.min(total_bytes);
if canon_end > canon_start {
canon.push((canon_start, canon_end));
}
}
canon.sort_unstable_by_key(|&(s, _)| s);
let mut merged: Vec<(u64, u64)> = Vec::with_capacity(canon.len());
for (s, e) in canon.drain(..) {
if let Some(last) = merged.last_mut()
&& s <= last.1
{
last.1 = last.1.max(e);
continue;
}
merged.push((s, e));
}
Ok(merged)
}
pub(crate) fn split_evenly(
start: u64,
len: u64,
k: usize,
total_bytes: u64,
) -> anyhow::Result<Vec<AlignedRange>> {
if len == 0 || k == 0 {
return Ok(Vec::new());
}
let Some(end) = start.checked_add(len) else {
anyhow::bail!("split_evenly: start + len overflows for start={start} len={len}");
};
let target = len.div_ceil(k as u64).max(1);
let mut out: Vec<AlignedRange> = Vec::new();
let mut off = start;
while off < end {
let want = if out.len() + 1 >= k {
end - off
} else {
target.min(end - off)
};
let seg = align_range(off, want, total_bytes)?;
let seg_end = seg.fetch_end().min(end);
if seg_end <= off {
break;
}
let aligned = if seg_end == seg.fetch_end() {
seg
} else {
align_range(off, seg_end - off, total_bytes)?
};
out.push(aligned);
off = seg_end;
}
Ok(out)
}
fn missing_within(missing: &[(u64, u64)], start: u64, end: u64) -> Vec<(u64, u64)> {
missing
.iter()
.map(|&(s, l)| (s.max(start), s.saturating_add(l).min(end)))
.filter(|&(s, e)| s < e)
.collect()
}
pub(crate) fn steal_split(
remaining: &[(u64, u64)],
missing: &[(u64, u64)],
total_bytes: u64,
covered_from: impl Fn(u64, u64) -> Option<u64>,
stealer_rate: Option<u64>,
victim_rate: impl Fn(usize) -> Option<u64>,
) -> anyhow::Result<Option<(usize, AlignedRange)>> {
let missing_of = |start: u64, end: u64| {
missing_within(missing, start, end)
.iter()
.map(|&(s, e)| e - s)
.fold(0, u64::saturating_add)
};
let Some((victim, &(start, len), from)) = remaining
.iter()
.enumerate()
.filter_map(|(i, r @ &(s, l))| {
let from = covered_from(s, l)?;
let end = s.saturating_add(l);
let stealable = missing_of(from, end);
(missing_of(s, end) >= MIN_SPLIT_SIZE && stealable >= MIN_STOLEN)
.then_some((i, r, from, stealable))
})
.max_by_key(|&(_, _, _, stealable)| stealable)
.map(|(i, r, from, _)| (i, r, from))
else {
return Ok(None);
};
let Some((canon_start, canon_end)) = canonicalize_ranges(&[(start, len)], total_bytes)?
.first()
.copied()
else {
return Ok(None);
};
let runs: Vec<(u64, u64)> = canonicalize_ranges(
&missing_within(missing, canon_start, canon_end)
.iter()
.map(|&(s, e)| (s, e - s))
.collect::<Vec<_>>(),
total_bytes,
)?;
let Some(&(first_missing, _)) = runs.first() else {
return Ok(None);
};
let run_bytes = runs
.iter()
.map(|&(s, e)| e - s)
.fold(0, u64::saturating_add);
let Some(keep) = victim_keeps(run_bytes, stealer_rate, victim_rate(victim)) else {
return Ok(None);
};
let keep = keep
.max(MIN_VICTIM_KEEP)
.min(run_bytes.saturating_sub(MIN_STOLEN));
let mut before = 0u64;
let mut split = None;
for &(s, e) in &runs {
if before.saturating_add(e - s) > keep {
split = Some(s + (keep - before));
break;
}
before = before.saturating_add(e - s);
}
let Some(split) = split else {
return Ok(None);
};
let split = split.max(from);
if split >= canon_end {
return Ok(None);
}
let tail = align_range(split, canon_end - split, total_bytes)?;
if tail.fetch_start() <= first_missing || tail.fetch_start() >= canon_end {
return Ok(None);
}
Ok(Some((victim, tail)))
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)] mod tests;