use crate::{authorisation::AuthorisedEntry, entry::Entrylike};
use bab_rs::{
CHUNK_SIZE, WIDTH,
generic::storage::{
single_slice_store::SliceStreamResumptionInfo, verifiable_streaming::SliceStreamingOptions,
},
};
use core::cmp::Ordering;
use ufotofu::codec::Blame;
#[derive(Debug, Clone)]
pub enum SliceMetadataError {
TooManyChunks,
OutOfBounds,
OtherError(Blame),
}
#[derive(Debug, Clone)]
pub struct DropSliceMetadata {
entry: AuthorisedEntry,
first_chunk: u64,
chunk_count: u64,
left_skip: u8,
is_multislice_encoded: bool,
}
impl DropSliceMetadata {
pub fn new(
entry: AuthorisedEntry,
first_chunk: u64,
chunk_count: u64,
left_skip: u8,
is_multislice_encoded: bool,
) -> Result<Self, SliceMetadataError> {
let max_chunks = entry.payload_length().div_ceil(
CHUNK_SIZE
.try_into()
.map_err(|_| SliceMetadataError::OtherError(Blame::OurFault))?,
);
if first_chunk > max_chunks {
return Err(SliceMetadataError::OutOfBounds);
}
if first_chunk + chunk_count > max_chunks {
return Err(SliceMetadataError::TooManyChunks);
}
Ok(DropSliceMetadata {
entry,
first_chunk,
chunk_count,
left_skip,
is_multislice_encoded,
})
}
pub fn entry(&self) -> &AuthorisedEntry {
&self.entry
}
pub fn first_chunk(&self) -> u64 {
self.first_chunk
}
pub fn chunk_count(&self) -> u64 {
self.chunk_count
}
pub fn left_skip(&self) -> u8 {
self.left_skip
}
pub fn resumption_info(&self) -> SliceStreamResumptionInfo {
SliceStreamResumptionInfo {
start_chunk: self.first_chunk,
left_skip: self.left_skip,
right_skip: 0,
}
}
pub fn streaming_options(&self) -> SliceStreamingOptions {
let mut options = SliceStreamingOptions::default();
options.k = 64;
options.left_skip = self.left_skip;
options
}
pub fn expected_bytes(&self) -> u64 {
let total_chunks = self.entry.payload_length().div_ceil(CHUNK_SIZE as u64);
let label_bytes = WIDTH as u64
* labels_in_drop_slice(
self.first_chunk,
self.chunk_count,
total_chunks,
self.left_skip,
0,
);
(self.chunk_count * CHUNK_SIZE as u64)
.min(self.entry.payload_length() - (self.first_chunk * CHUNK_SIZE as u64))
+ label_bytes
}
pub fn is_complete(&self) -> bool {
self.chunk_count == self.entry().payload_length().div_ceil(CHUNK_SIZE as u64)
}
pub fn is_multislice(&self) -> bool {
self.is_multislice_encoded
}
}
fn labels_in_drop_slice(
first_chunk: u64,
chunk_count: u64,
total_chunks: u64,
left_skip: u8,
right_skip: u8,
) -> u64 {
debug_assert!(chunk_count <= total_chunks);
if chunk_count == 0 {
return 0;
}
if chunk_count == total_chunks {
return 0;
}
let left_labels = first_chunk.count_ones().saturating_sub(left_skip as u32);
let mut local_depth_at_right_edge = 0;
let mut left_leaves = total_chunks.next_power_of_two() / 2;
let mut right_leaves = total_chunks - left_leaves;
let mut split = left_leaves;
let last_chunk = first_chunk + chunk_count - 1;
while left_leaves > 0 {
match last_chunk.cmp(&split) {
Ordering::Less => {
left_leaves /= 2;
right_leaves = left_leaves;
split -= left_leaves;
}
_ => {
left_leaves = right_leaves.next_power_of_two() / 2;
right_leaves -= left_leaves;
split += left_leaves;
}
}
local_depth_at_right_edge += 1;
}
let right_labels =
(local_depth_at_right_edge - last_chunk.count_ones()).saturating_sub(right_skip as u32);
(left_labels + right_labels) as u64
}
#[test]
fn test_label_count() {
let cases = [
(0, 11, 11, 0),
(1, 10, 11, 1),
(2, 9, 11, 1),
(3, 8, 11, 2),
(4, 7, 11, 1),
(5, 6, 11, 2),
(6, 5, 11, 2),
(7, 4, 11, 3),
(8, 3, 11, 1),
(9, 2, 11, 2),
(10, 1, 11, 2),
(0, 10, 11, 1),
(1, 9, 11, 2),
(2, 8, 11, 2),
(3, 7, 11, 3),
(4, 6, 11, 2),
(5, 5, 11, 3),
(6, 4, 11, 3),
(7, 3, 11, 4),
(8, 2, 11, 2),
(9, 1, 11, 3),
(0, 9, 11, 2),
(1, 8, 11, 3),
(2, 7, 11, 3),
(3, 6, 11, 4),
(4, 5, 11, 3),
(5, 4, 11, 4),
(6, 3, 11, 4),
(7, 2, 11, 5),
(8, 1, 11, 3),
(0, 8, 11, 1),
(1, 7, 11, 2),
(2, 6, 11, 2),
(3, 5, 11, 3),
(4, 4, 11, 2),
(5, 3, 11, 3),
(6, 2, 11, 3),
(7, 1, 11, 4),
(0, 7, 11, 2),
(1, 6, 11, 3),
(2, 5, 11, 3),
(3, 4, 11, 4),
(4, 3, 11, 3),
(5, 2, 11, 4),
(6, 1, 11, 4),
(0, 6, 11, 2),
(1, 5, 11, 3),
(2, 4, 11, 3),
(3, 3, 11, 4),
(4, 2, 11, 3),
(5, 1, 11, 4),
(0, 5, 11, 3),
(1, 4, 11, 4),
(2, 3, 11, 4),
(3, 2, 11, 5),
(4, 1, 11, 4),
(0, 4, 11, 2),
(1, 3, 11, 3),
(2, 2, 11, 3),
(3, 1, 11, 4),
(0, 3, 11, 3),
(1, 2, 11, 4),
(2, 1, 11, 4),
(0, 2, 11, 3),
(1, 1, 11, 4),
(0, 1, 11, 4),
(0, 0, 11, 0),
(0, 6, 6, 0),
(1, 5, 6, 1),
(2, 4, 6, 1),
(3, 3, 6, 2),
(4, 2, 6, 1),
(5, 1, 6, 2),
(6, 0, 6, 0),
(0, 5, 6, 1),
(1, 4, 6, 2),
(2, 3, 6, 2),
(3, 2, 6, 3),
(4, 1, 6, 2),
(0, 3, 6, 2),
(1, 2, 6, 3),
(2, 1, 6, 3),
(3, 0, 6, 0),
(0, 2, 6, 2),
(1, 1, 6, 3),
(2, 0, 6, 0),
(0, 1, 6, 3),
(1, 0, 6, 0),
];
for (first, width, total, expected) in cases {
assert_eq!(
labels_in_drop_slice(first, width, total, 0, 0),
expected,
"{first}, {width}, {total}: {expected}"
);
}
}