#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lengths {
total_length: u64,
piece_length: u64,
chunk_size: u32,
num_pieces: u32,
last_piece_size: u32,
}
pub const DEFAULT_CHUNK_SIZE: u32 = 16384;
impl Lengths {
pub fn new(total_length: u64, piece_length: u64, chunk_size: u32) -> Self {
assert!(piece_length > 0, "piece_length must be > 0");
assert!(chunk_size > 0, "chunk_size must be > 0");
let num_pieces = if total_length == 0 {
0
} else {
total_length.div_ceil(piece_length) as u32
};
let last_piece_size = if num_pieces == 0 {
0
} else {
let remainder = total_length % piece_length;
if remainder == 0 {
piece_length as u32
} else {
remainder as u32
}
};
Lengths {
total_length,
piece_length,
chunk_size,
num_pieces,
last_piece_size,
}
}
pub fn total_length(&self) -> u64 {
self.total_length
}
pub fn piece_length(&self) -> u64 {
self.piece_length
}
pub fn chunk_size(&self) -> u32 {
self.chunk_size
}
pub fn num_pieces(&self) -> u32 {
self.num_pieces
}
#[inline]
pub fn piece_size(&self, piece_index: u32) -> u32 {
if piece_index >= self.num_pieces {
0
} else if piece_index == self.num_pieces - 1 {
self.last_piece_size
} else {
self.piece_length as u32
}
}
#[inline]
pub fn chunks_in_piece(&self, piece_index: u32) -> u32 {
let piece_size = self.piece_size(piece_index) as u64;
if piece_size == 0 {
return 0;
}
piece_size.div_ceil(self.chunk_size as u64) as u32
}
#[inline]
pub fn chunk_info(&self, piece_index: u32, chunk_index: u32) -> Option<(u32, u32)> {
let piece_size = self.piece_size(piece_index);
if piece_size == 0 {
return None;
}
let offset = chunk_index * self.chunk_size;
if offset >= piece_size {
return None;
}
let remaining = piece_size - offset;
let len = remaining.min(self.chunk_size);
Some((offset, len))
}
pub fn piece_offset(&self, piece_index: u32) -> u64 {
piece_index as u64 * self.piece_length
}
pub fn byte_to_piece(&self, byte_offset: u64) -> Option<(u32, u32)> {
if byte_offset >= self.total_length {
return None;
}
let piece_index = (byte_offset / self.piece_length) as u32;
let offset_in_piece = (byte_offset % self.piece_length) as u32;
Some((piece_index, offset_in_piece))
}
pub fn file_pieces(&self, file_offset: u64, file_length: u64) -> Option<(u32, u32)> {
if file_length == 0 || file_offset >= self.total_length {
return None;
}
let first = (file_offset / self.piece_length) as u32;
let last_byte = file_offset + file_length - 1;
let last = (last_byte.min(self.total_length - 1) / self.piece_length) as u32;
Some((first, last))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_lengths() -> Lengths {
Lengths::new(1048576, 262144, 16384)
}
#[test]
fn num_pieces_exact_division() {
let l = make_lengths();
assert_eq!(l.num_pieces(), 4); }
#[test]
fn num_pieces_with_remainder() {
let l = Lengths::new(1000000, 262144, 16384);
assert_eq!(l.num_pieces(), 4); }
#[test]
fn piece_size_regular() {
let l = make_lengths();
assert_eq!(l.piece_size(0), 262144);
assert_eq!(l.piece_size(1), 262144);
assert_eq!(l.piece_size(3), 262144); }
#[test]
fn piece_size_last_piece_shorter() {
let l = Lengths::new(1000000, 262144, 16384);
assert_eq!(l.piece_size(0), 262144);
assert_eq!(l.piece_size(3), 1000000 - 3 * 262144); }
#[test]
fn piece_size_out_of_bounds() {
let l = make_lengths();
assert_eq!(l.piece_size(4), 0);
assert_eq!(l.piece_size(100), 0);
}
#[test]
fn chunks_in_piece() {
let l = make_lengths();
assert_eq!(l.chunks_in_piece(0), 16); }
#[test]
fn chunks_in_last_piece() {
let l = Lengths::new(1000000, 262144, 16384);
let last_piece_size = 1000000 - 3 * 262144; let expected_chunks = (last_piece_size + 16383) / 16384; assert_eq!(l.chunks_in_piece(3), expected_chunks as u32);
}
#[test]
fn chunk_info_regular() {
let l = make_lengths();
assert_eq!(l.chunk_info(0, 0), Some((0, 16384)));
assert_eq!(l.chunk_info(0, 1), Some((16384, 16384)));
assert_eq!(l.chunk_info(0, 15), Some((15 * 16384, 16384)));
}
#[test]
fn chunk_info_last_chunk_shorter() {
let l = Lengths::new(100000, 50000, 16384);
assert_eq!(l.chunk_info(0, 3), Some((49152, 848)));
}
#[test]
fn chunk_info_out_of_bounds() {
let l = make_lengths();
assert_eq!(l.chunk_info(0, 16), None); assert_eq!(l.chunk_info(4, 0), None); }
#[test]
fn piece_offset() {
let l = make_lengths();
assert_eq!(l.piece_offset(0), 0);
assert_eq!(l.piece_offset(1), 262144);
assert_eq!(l.piece_offset(3), 786432);
}
#[test]
fn byte_to_piece() {
let l = make_lengths();
assert_eq!(l.byte_to_piece(0), Some((0, 0)));
assert_eq!(l.byte_to_piece(262143), Some((0, 262143)));
assert_eq!(l.byte_to_piece(262144), Some((1, 0)));
assert_eq!(l.byte_to_piece(1048575), Some((3, 262143)));
assert_eq!(l.byte_to_piece(1048576), None); }
#[test]
fn file_pieces_spanning() {
let l = make_lengths();
assert_eq!(l.file_pieces(100000, 500000), Some((0, 2)));
}
#[test]
fn file_pieces_single_piece() {
let l = make_lengths();
assert_eq!(l.file_pieces(262144, 100), Some((1, 1)));
}
#[test]
fn file_pieces_entire_torrent() {
let l = make_lengths();
assert_eq!(l.file_pieces(0, 1048576), Some((0, 3)));
}
#[test]
fn zero_length_torrent() {
let l = Lengths::new(0, 262144, 16384);
assert_eq!(l.num_pieces(), 0);
}
#[test]
fn tiny_torrent() {
let l = Lengths::new(1, 262144, 16384);
assert_eq!(l.num_pieces(), 1);
assert_eq!(l.piece_size(0), 1);
assert_eq!(l.chunks_in_piece(0), 1);
assert_eq!(l.chunk_info(0, 0), Some((0, 1)));
}
}