use irontide_core::Lengths;
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileSegment {
pub file_index: usize,
pub file_offset: u64,
pub len: u32,
}
#[derive(Debug, Clone)]
pub struct FileMap {
file_offsets: Vec<u64>,
file_lengths: Vec<u64>,
lengths: Lengths,
}
impl FileMap {
pub fn new(file_lengths: Vec<u64>, lengths: Lengths) -> Self {
let mut file_offsets = Vec::with_capacity(file_lengths.len());
let mut cumulative = 0u64;
for &len in &file_lengths {
file_offsets.push(cumulative);
cumulative += len;
}
FileMap {
file_offsets,
file_lengths,
lengths,
}
}
pub fn byte_range_to_segments(&self, offset: u64, length: u32) -> SmallVec<[FileSegment; 4]> {
if length == 0 || self.file_lengths.is_empty() {
return SmallVec::new();
}
let mut segments = SmallVec::new();
let mut remaining = length as u64;
let mut pos = offset;
while remaining > 0 {
let file_idx = match self.file_offsets.binary_search(&pos) {
Ok(i) => i,
Err(i) => i.saturating_sub(1),
};
if file_idx >= self.file_lengths.len() {
break;
}
let file_start = self.file_offsets[file_idx];
let file_len = self.file_lengths[file_idx];
let file_offset = pos - file_start;
let available = file_len - file_offset;
let take = remaining.min(available);
if take > 0 {
segments.push(FileSegment {
file_index: file_idx,
file_offset,
len: take as u32,
});
}
pos += take;
remaining -= take;
}
segments
}
pub fn chunk_segments(
&self,
piece: u32,
begin: u32,
length: u32,
) -> SmallVec<[FileSegment; 4]> {
let abs_offset = self.lengths.piece_offset(piece) + begin as u64;
self.byte_range_to_segments(abs_offset, length)
}
pub fn piece_size(&self, piece: u32) -> u32 {
self.lengths.piece_size(piece)
}
pub fn piece_segments(&self, piece: u32) -> SmallVec<[FileSegment; 4]> {
let abs_offset = self.lengths.piece_offset(piece);
let piece_size = self.lengths.piece_size(piece);
self.byte_range_to_segments(abs_offset, piece_size)
}
pub fn num_files(&self) -> usize {
self.file_lengths.len()
}
pub fn file_length(&self, index: usize) -> u64 {
self.file_lengths[index]
}
}
#[cfg(test)]
mod tests {
use super::*;
use irontide_core::Lengths;
#[test]
fn single_file() {
let lengths = Lengths::new(1048576, 262144, 16384);
let fm = FileMap::new(vec![1048576], lengths);
let segs = fm.piece_segments(0);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].file_index, 0);
assert_eq!(segs[0].file_offset, 0);
assert_eq!(segs[0].len, 262144);
}
#[test]
fn multi_file_no_span() {
let lengths = Lengths::new(524288, 262144, 16384);
let fm = FileMap::new(vec![262144, 262144], lengths);
let segs0 = fm.piece_segments(0);
assert_eq!(segs0.len(), 1);
assert_eq!(segs0[0].file_index, 0);
let segs1 = fm.piece_segments(1);
assert_eq!(segs1.len(), 1);
assert_eq!(segs1[0].file_index, 1);
}
#[test]
fn chunk_spans_boundary() {
let lengths = Lengths::new(300, 300, 150);
let fm = FileMap::new(vec![100, 200], lengths);
let segs = fm.chunk_segments(0, 0, 150);
assert_eq!(segs.len(), 2);
assert_eq!(
segs[0],
FileSegment {
file_index: 0,
file_offset: 0,
len: 100
}
);
assert_eq!(
segs[1],
FileSegment {
file_index: 1,
file_offset: 0,
len: 50
}
);
}
#[test]
fn piece_spans_three_files() {
let lengths = Lengths::new(300, 300, 16384);
let fm = FileMap::new(vec![100, 50, 150], lengths);
let segs = fm.piece_segments(0);
assert_eq!(segs.len(), 3);
assert_eq!(
segs[0],
FileSegment {
file_index: 0,
file_offset: 0,
len: 100
}
);
assert_eq!(
segs[1],
FileSegment {
file_index: 1,
file_offset: 0,
len: 50
}
);
assert_eq!(
segs[2],
FileSegment {
file_index: 2,
file_offset: 0,
len: 150
}
);
}
#[test]
fn last_piece_shorter() {
let lengths = Lengths::new(500, 300, 16384);
let fm = FileMap::new(vec![500], lengths);
let segs = fm.piece_segments(1);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].file_offset, 300);
assert_eq!(segs[0].len, 200);
}
#[test]
fn zero_length_file() {
let lengths = Lengths::new(100, 100, 16384);
let fm = FileMap::new(vec![0, 100], lengths);
let segs = fm.piece_segments(0);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].file_index, 1);
assert_eq!(segs[0].file_offset, 0);
assert_eq!(segs[0].len, 100);
}
#[test]
fn byte_range_single() {
let lengths = Lengths::new(1000, 500, 16384);
let fm = FileMap::new(vec![1000], lengths);
let segs = fm.byte_range_to_segments(100, 50);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].file_offset, 100);
assert_eq!(segs[0].len, 50);
}
#[test]
fn piece_segments_second_piece_multi_file() {
let lengths = Lengths::new(1000, 500, 16384);
let fm = FileMap::new(vec![400, 600], lengths);
let segs = fm.piece_segments(1);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].file_index, 1);
assert_eq!(segs[0].file_offset, 100);
assert_eq!(segs[0].len, 500);
}
#[test]
fn smallvec_spills_on_many_files() {
let lengths = Lengths::new(300, 300, 16384);
let fm = FileMap::new(vec![50, 50, 50, 50, 50, 50], lengths);
let segs = fm.piece_segments(0);
assert_eq!(segs.len(), 6);
for (i, seg) in segs.iter().enumerate() {
assert_eq!(seg.file_index, i, "segment {i} wrong file_index");
assert_eq!(seg.file_offset, 0, "segment {i} wrong file_offset");
assert_eq!(seg.len, 50, "segment {i} wrong len");
}
}
}