use crate::constants::{BLOCK_HEADER_SIZE, BLOCK_SIZE};
pub fn is_block_boundary(pos: u64) -> bool {
pos.is_multiple_of(BLOCK_SIZE)
}
pub fn round_down_to_block_boundary(pos: u64) -> u64 {
pos - (pos % BLOCK_SIZE)
}
pub fn remaining_in_block(pos: u64) -> u64 {
let offset = pos % BLOCK_SIZE;
if offset == 0 { 0 } else { BLOCK_SIZE - offset }
}
pub const USABLE_BLOCK_SIZE: u64 = BLOCK_SIZE - BLOCK_HEADER_SIZE;
#[cfg_attr(not(test), allow(dead_code))]
pub fn is_possible_chunk_boundary(pos: u64) -> bool {
remaining_in_block(pos) < USABLE_BLOCK_SIZE
}
pub fn round_up_to_possible_chunk_boundary(pos: u64) -> u64 {
pos.saturating_add(remaining_in_block(pos).saturating_sub(USABLE_BLOCK_SIZE - 1))
}
pub fn canonical_chunk_address(pos: u64) -> u64 {
if pos % BLOCK_SIZE == BLOCK_HEADER_SIZE {
pos - BLOCK_HEADER_SIZE
} else {
pos
}
}
pub fn chunk_end(chunk_begin: u64, data_size: u64, num_records: u64) -> u64 {
let begin = canonical_chunk_address(chunk_begin);
let end_from_data = add_with_overhead(
begin,
crate::constants::CHUNK_HEADER_SIZE.saturating_add(data_size),
);
let end_from_records = round_up_to_possible_chunk_boundary(begin.saturating_add(num_records));
end_from_data.max(end_from_records)
}
pub fn remaining_in_block_header(pos: u64) -> usize {
let offset = pos % BLOCK_SIZE;
if offset < BLOCK_HEADER_SIZE {
(BLOCK_HEADER_SIZE - offset) as usize
} else {
0
}
}
pub fn add_with_overhead(chunk_begin: u64, length: u64) -> u64 {
let mut pos = chunk_begin;
let mut remaining = length;
loop {
if remaining == 0 {
break;
}
let header_skip = remaining_in_block_header(pos) as u64;
pos += header_skip;
let avail = remaining_in_block(pos);
if avail == 0 {
pos += BLOCK_HEADER_SIZE;
continue;
}
if remaining <= avail {
pos += remaining;
remaining = 0;
} else {
remaining -= avail;
pos += avail;
}
}
pos
}
pub fn distance_without_overhead(chunk_begin: u64, pos: u64) -> u64 {
let mut cur = chunk_begin;
let mut data_bytes = 0u64;
while cur < pos {
let header_skip = remaining_in_block_header(cur) as u64;
cur += header_skip;
if cur >= pos {
break;
}
let avail = remaining_in_block(cur);
let avail = if avail == 0 {
BLOCK_SIZE - BLOCK_HEADER_SIZE
} else {
avail
};
let step = avail.min(pos - cur);
data_bytes += step;
cur += step;
}
data_bytes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_block_boundary() {
assert!(is_block_boundary(0));
assert!(is_block_boundary(65536));
assert!(is_block_boundary(131072));
assert!(!is_block_boundary(1));
assert!(!is_block_boundary(65535));
assert!(!is_block_boundary(65537));
}
#[test]
fn test_round_down_to_block_boundary() {
assert_eq!(round_down_to_block_boundary(0), 0);
assert_eq!(round_down_to_block_boundary(1), 0);
assert_eq!(round_down_to_block_boundary(65535), 0);
assert_eq!(round_down_to_block_boundary(65536), 65536);
assert_eq!(round_down_to_block_boundary(65537), 65536);
}
#[test]
fn test_remaining_in_block() {
assert_eq!(remaining_in_block(0), 0);
assert_eq!(remaining_in_block(1), 65535);
assert_eq!(remaining_in_block(65535), 1);
assert_eq!(remaining_in_block(65536), 0);
assert_eq!(remaining_in_block(65537), 65535);
}
#[test]
fn test_is_possible_chunk_boundary() {
assert!(is_possible_chunk_boundary(0));
assert!(!is_possible_chunk_boundary(1));
assert!(!is_possible_chunk_boundary(23));
assert!(!is_possible_chunk_boundary(24));
assert!(is_possible_chunk_boundary(25));
assert!(is_possible_chunk_boundary(65535));
assert!(is_possible_chunk_boundary(65536));
assert!(!is_possible_chunk_boundary(65536 + 24));
assert!(is_possible_chunk_boundary(65536 + 25));
}
#[test]
fn test_round_up_to_possible_chunk_boundary() {
assert_eq!(round_up_to_possible_chunk_boundary(0), 0);
assert_eq!(round_up_to_possible_chunk_boundary(1), 25);
assert_eq!(round_up_to_possible_chunk_boundary(23), 25);
assert_eq!(round_up_to_possible_chunk_boundary(24), 25);
assert_eq!(round_up_to_possible_chunk_boundary(25), 25);
assert_eq!(round_up_to_possible_chunk_boundary(100), 100);
assert_eq!(round_up_to_possible_chunk_boundary(65536), 65536);
assert_eq!(round_up_to_possible_chunk_boundary(65536 + 1), 65536 + 25);
assert_eq!(round_up_to_possible_chunk_boundary(65536 + 24), 65536 + 25);
assert_eq!(round_up_to_possible_chunk_boundary(65536 + 25), 65536 + 25);
assert_eq!(round_up_to_possible_chunk_boundary(131084), 131097);
}
#[test]
fn test_canonical_chunk_address() {
assert_eq!(canonical_chunk_address(0), 0);
assert_eq!(canonical_chunk_address(24), 0);
assert_eq!(canonical_chunk_address(25), 25);
assert_eq!(canonical_chunk_address(64), 64);
assert_eq!(canonical_chunk_address(65536), 65536);
assert_eq!(canonical_chunk_address(65536 + 24), 65536);
assert_eq!(canonical_chunk_address(65536 + 25), 65536 + 25);
}
#[test]
fn test_chunk_end_equal_addressing_invariant() {
for (data_size, num_records) in [
(0u64, 0u64),
(53, 131019),
(100, 1),
(70_000, 3),
(10, 70_000),
] {
for boundary in [0u64, 65536, 131072] {
assert_eq!(
chunk_end(boundary, data_size, num_records),
chunk_end(boundary + 24, data_size, num_records),
"boundary={boundary} data_size={data_size} num_records={num_records}"
);
}
}
}
#[test]
fn test_chunk_end_acceptance_points() {
assert_eq!(chunk_end(0, 0, 0), 64);
assert_eq!(chunk_end(64, 53, 131019), 131097);
}
#[test]
fn test_add_with_overhead_matches_cpp_closed_form() {
fn cpp_add_with_overhead(chunk_begin: u64, length: u64) -> u64 {
let usable = BLOCK_SIZE - BLOCK_HEADER_SIZE;
let num_overhead_blocks = (length + (chunk_begin + usable - 1) % BLOCK_SIZE) / usable;
chunk_begin + length + num_overhead_blocks * BLOCK_HEADER_SIZE
}
let begins = [0u64, 25, 64, 1000, 65535, 65536, 65536 + 25, 131072, 131097];
let lengths = [
0u64, 1, 40, 93, 65471, 65472, 65473, 65512, 65513, 131024, 200_000,
];
for &b in &begins {
assert!(
is_possible_chunk_boundary(b),
"test input {b} not canonical"
);
for &l in &lengths {
assert_eq!(
add_with_overhead(b, l),
cpp_add_with_overhead(b, l),
"begin={b} len={l}"
);
}
}
}
#[test]
fn test_remaining_in_block_header() {
assert_eq!(remaining_in_block_header(0), 24);
assert_eq!(remaining_in_block_header(1), 23);
assert_eq!(remaining_in_block_header(23), 1);
assert_eq!(remaining_in_block_header(24), 0);
assert_eq!(remaining_in_block_header(65536), 24);
assert_eq!(remaining_in_block_header(65536 + 24), 0);
}
#[test]
fn test_add_with_overhead_simple() {
assert_eq!(add_with_overhead(24, 100), 124);
}
#[test]
fn test_add_with_overhead_zero_length() {
assert_eq!(add_with_overhead(24, 0), 24);
assert_eq!(add_with_overhead(100, 0), 100);
}
#[test]
fn test_add_with_overhead_crosses_block_boundary() {
let start = 24u64;
let length = 65512 + 100; let result = add_with_overhead(start, length);
assert_eq!(result, 65536 + 24 + 100);
}
#[test]
fn test_add_with_overhead_exact_block_end() {
let start = 24u64;
let length = 65512u64;
let result = add_with_overhead(start, length);
assert_eq!(result, 65536);
}
#[test]
fn test_distance_without_overhead_simple() {
assert_eq!(distance_without_overhead(24, 124), 100);
}
#[test]
fn test_distance_without_overhead_crosses_block() {
let end = 65536 + 24 + 100;
assert_eq!(distance_without_overhead(24, end as u64), 65512 + 100);
}
#[test]
fn test_add_distance_roundtrip() {
let starts = [24u64, 100, 65536 + 24, 131072 + 24];
let lengths = [0u64, 1, 65512, 65513, 200_000];
for &start in &starts {
for &len in &lengths {
let end = add_with_overhead(start, len);
let recovered = distance_without_overhead(start, end);
assert_eq!(
recovered, len,
"round-trip failed for start={start}, len={len}"
);
}
}
}
}