use std::io::{self, Read, Seek, SeekFrom};
use std::time::Duration;
use serde::Serialize;
use crate::chunked;
use crate::error::FetchError;
pub const TAIL_PREFETCH_BYTES: u64 = 64 * 1024;
pub const READAHEAD_BYTES: u64 = 4 * 1024;
pub const MAX_TRANSFER_BUDGET: u64 = 32 * 1024 * 1024;
pub const MAX_RANGE_REQUESTS: u32 = 256;
const RANGE_REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
pub trait RangeFetcher {
fn fetch(&mut self, start: u64, end_inclusive: u64) -> Result<Vec<u8>, FetchError>;
fn total_size(&self) -> u64;
fn extra_requests(&self) -> u32 {
0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub struct RangeStats {
pub requests: u32,
pub bytes_fetched: u64,
}
struct Extent {
start: u64,
data: Vec<u8>,
}
impl Extent {
fn end(&self) -> u64 {
#[allow(clippy::as_conversions)]
self.start.saturating_add(self.data.len() as u64)
}
fn contains(&self, pos: u64) -> bool {
pos >= self.start && pos < self.end()
}
fn copy_at(&self, pos: u64, buf: &mut [u8]) -> Option<usize> {
if !self.contains(pos) {
return None;
}
let offset = usize::try_from(pos.checked_sub(self.start)?).ok()?;
let available = self.data.len().checked_sub(offset)?;
let n = buf.len().min(available);
let src = self.data.get(offset..offset.checked_add(n)?)?;
let dst = buf.get_mut(..n)?;
dst.copy_from_slice(src);
Some(n)
}
}
pub struct RangeReader<F: RangeFetcher> {
fetcher: F,
pos: u64,
window: Option<Extent>,
tail: Option<Extent>,
requests: u32,
bytes_fetched: u64,
max_requests: u32,
max_transfer_bytes: u64,
last_error: Option<FetchError>,
}
impl<F: RangeFetcher> RangeReader<F> {
#[must_use]
pub const fn new(fetcher: F) -> Self {
Self::with_limits(fetcher, MAX_RANGE_REQUESTS, MAX_TRANSFER_BUDGET)
}
#[must_use]
pub const fn with_limits(fetcher: F, max_requests: u32, max_transfer_bytes: u64) -> Self {
Self {
fetcher,
pos: 0,
window: None,
tail: None,
requests: 0,
bytes_fetched: 0,
max_requests,
max_transfer_bytes,
last_error: None,
}
}
#[must_use]
pub fn stats(&self) -> RangeStats {
RangeStats {
requests: self.requests.saturating_add(self.fetcher.extra_requests()),
bytes_fetched: self.bytes_fetched,
}
}
#[must_use]
pub fn total_size(&self) -> u64 {
self.fetcher.total_size()
}
#[must_use]
pub const fn take_last_error(&mut self) -> Option<FetchError> {
self.last_error.take()
}
fn checked_fetch(&mut self, start: u64, end_inclusive: u64) -> io::Result<Vec<u8>> {
let len = end_inclusive
.checked_sub(start)
.and_then(|d| d.checked_add(1))
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid range {start}..={end_inclusive}"),
)
})?;
if self.requests >= self.max_requests {
return Err(io::Error::other(format!(
"range request cap exceeded ({} requests): pathological archive \
layout or seek storm; refusing further fetches",
self.max_requests
)));
}
if self.bytes_fetched.saturating_add(len) > self.max_transfer_bytes {
return Err(io::Error::other(format!(
"range transfer budget exceeded ({} bytes fetched, {len} more \
requested, cap {}): metadata inspection should never read \
this much; refusing further fetches",
self.bytes_fetched, self.max_transfer_bytes
)));
}
self.requests = self.requests.saturating_add(1);
match self.fetcher.fetch(start, end_inclusive) {
Ok(data) => {
#[allow(clippy::as_conversions)]
let got = data.len() as u64;
if got != len {
return Err(io::Error::other(format!(
"range fetcher returned {got} bytes for a {len}-byte \
range ({start}..={end_inclusive})"
)));
}
self.bytes_fetched = self.bytes_fetched.saturating_add(len);
Ok(data)
}
Err(fetch_err) => {
let io_err = io::Error::other(fetch_err.to_string());
self.last_error = Some(fetch_err);
Err(io_err)
}
}
}
fn copy_cached(&self, buf: &mut [u8]) -> Option<usize> {
if let Some(w) = &self.window {
if let Some(n) = w.copy_at(self.pos, buf) {
return Some(n);
}
}
if let Some(t) = &self.tail {
if let Some(n) = t.copy_at(self.pos, buf) {
return Some(n);
}
}
None
}
fn advance_pos(&mut self, n: usize) {
#[allow(clippy::as_conversions)]
{
self.pos = self.pos.saturating_add(n as u64);
}
}
fn serve_from_cache_after_fetch(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.copy_cached(buf).ok_or_else(|| {
io::Error::other(format!(
"internal range cache inconsistency at offset {}",
self.pos
))
})?;
self.advance_pos(n);
Ok(n)
}
}
impl<F: RangeFetcher> Read for RangeReader<F> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let total = self.fetcher.total_size();
if self.pos >= total {
return Ok(0); }
if let Some(n) = self.copy_cached(buf) {
self.advance_pos(n);
return Ok(n);
}
let tail_start = total.saturating_sub(TAIL_PREFETCH_BYTES);
if self.tail.is_none() && self.pos >= tail_start {
let data = self.checked_fetch(tail_start, total.saturating_sub(1))?;
self.tail = Some(Extent {
start: tail_start,
data,
});
return self.serve_from_cache_after_fetch(buf);
}
#[allow(clippy::as_conversions)]
let want = (buf.len() as u64).max(READAHEAD_BYTES);
let mut end = self
.pos
.saturating_add(want)
.saturating_sub(1)
.min(total.saturating_sub(1));
if let Some(t) = &self.tail {
if self.pos < t.start {
end = end.min(t.start.saturating_sub(1));
}
}
let data = self.checked_fetch(self.pos, end)?;
self.window = Some(Extent {
start: self.pos,
data,
});
self.serve_from_cache_after_fetch(buf)
}
}
impl<F: RangeFetcher> Seek for RangeReader<F> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let total = self.fetcher.total_size();
let target: i128 = match pos {
SeekFrom::Start(p) => i128::from(p),
SeekFrom::End(delta) => i128::from(total).saturating_add(i128::from(delta)),
SeekFrom::Current(delta) => i128::from(self.pos).saturating_add(i128::from(delta)),
};
if target < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("seek to negative offset {target}"),
));
}
let new_pos = u64::try_from(target).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("seek offset {target} exceeds u64::MAX"),
)
})?;
self.pos = new_pos;
Ok(new_pos)
}
}
pub type HttpRangeReader = RangeReader<HttpRangeFetcher>;
pub struct HttpRangeFetcher {
handle: tokio::runtime::Handle,
client: reqwest::Client,
hf_url: String,
filename: String,
response_etag: Option<String>,
total_size: u64,
extra: u32,
}
impl HttpRangeReader {
pub async fn open(
repo_id: &str,
revision: Option<&str>,
filename: &str,
token: Option<&str>,
) -> Result<Self, FetchError> {
let fetcher = HttpRangeFetcher::open(repo_id, revision, filename, token).await?;
Ok(RangeReader::new(fetcher))
}
}
impl HttpRangeFetcher {
async fn open(
repo_id: &str,
revision: Option<&str>,
filename: &str,
token: Option<&str>,
) -> Result<Self, FetchError> {
let rev = revision.unwrap_or("main");
let hf_url = chunked::build_download_url(repo_id, rev, filename);
let client = chunked::build_client(token)?;
let info = chunked::probe_range_support(
client.clone(),
hf_url.clone(),
token.map(String::from),
)
.await?;
let Some(info) = info else {
return Err(Self::classify_no_range_support(&client, &hf_url, filename).await);
};
Ok(Self {
handle: tokio::runtime::Handle::current(),
client,
hf_url,
filename: filename.to_owned(),
response_etag: None,
total_size: info.content_length,
extra: 2, })
}
async fn classify_no_range_support(
client: &reqwest::Client,
url: &str,
filename: &str,
) -> FetchError {
let result = client
.get(url)
.header(reqwest::header::RANGE, "bytes=0-0")
.timeout(RANGE_REQUEST_TIMEOUT)
.send()
.await;
match result {
Ok(resp) => {
let status = resp.status();
if status.is_client_error() || status.is_server_error() {
FetchError::Http(format!(
"Range request for {filename} returned status {status}"
))
} else {
FetchError::Http(format!(
"server does not support Range requests for {filename}"
))
}
}
Err(e) => FetchError::Http(format!("failed to probe {filename}: {e}")),
}
}
fn fetch_once(
&self,
start: u64,
end_inclusive: u64,
) -> Result<(Vec<u8>, Option<String>), FetchError> {
let expected_len = end_inclusive
.checked_sub(start)
.and_then(|d| d.checked_add(1))
.ok_or_else(|| {
FetchError::Http(format!(
"invalid range {start}..={end_inclusive} for {}",
self.filename
))
})?;
let expected_usize = usize::try_from(expected_len).map_err(|_| {
FetchError::Http(format!(
"range length {expected_len} exceeds addressable memory for {}",
self.filename
))
})?;
let range_value = format!("bytes={start}-{end_inclusive}");
let filename = self.filename.as_str();
let total_size = self.total_size;
self.handle.block_on(async {
let resp = self
.client
.get(self.hf_url.as_str())
.header(reqwest::header::RANGE, range_value.as_str())
.timeout(RANGE_REQUEST_TIMEOUT)
.send()
.await
.map_err(|e| {
FetchError::Http(format!("failed to send Range request for {filename}: {e}"))
})?;
let status = resp.status();
if status == reqwest::StatusCode::OK {
return Err(FetchError::Http(format!(
"server ignored the Range header for {filename} (status 200 \
for bytes={start}-{end_inclusive}); refusing to read the full file"
)));
}
if status != reqwest::StatusCode::PARTIAL_CONTENT {
return Err(FetchError::Http(format!(
"Range request for {filename} returned status {status}"
)));
}
let content_range = resp
.headers()
.get(reqwest::header::CONTENT_RANGE)
.and_then(|v| v.to_str().ok())
.map(str::to_owned)
.ok_or_else(|| {
FetchError::Http(format!("missing Content-Range header for {filename}"))
})?;
let (cr_start, cr_end, cr_total) = parse_content_range(content_range.as_str())
.ok_or_else(|| {
FetchError::Http(format!(
"invalid Content-Range header for {filename}: {content_range}"
))
})?;
if cr_start != start || cr_end != end_inclusive || cr_total != total_size {
return Err(FetchError::Http(format!(
"Content-Range mismatch for {filename}: requested \
bytes={start}-{end_inclusive} of {total_size}, server answered {content_range}"
)));
}
let etag = resp
.headers()
.get(reqwest::header::ETAG)
.and_then(|v| v.to_str().ok())
.map(clean_etag);
let mut data: Vec<u8> = Vec::with_capacity(expected_usize);
let mut resp = resp;
while let Some(chunk) = resp.chunk().await.map_err(|e| {
FetchError::Http(format!("failed to read Range response for {filename}: {e}"))
})? {
if data.len().saturating_add(chunk.len()) > expected_usize {
return Err(FetchError::Http(format!(
"server sent more than the requested {expected_len} bytes \
for {filename} (bytes={start}-{end_inclusive}); aborting"
)));
}
data.extend_from_slice(&chunk);
}
if data.len() != expected_usize {
return Err(FetchError::Http(format!(
"server returned {} bytes for a {expected_len}-byte range \
of {filename} (bytes={start}-{end_inclusive})",
data.len()
)));
}
Ok((data, etag))
})
}
fn check_response_etag(&mut self, etag: Option<String>) -> Result<(), FetchError> {
if let Some(current) = etag {
match &self.response_etag {
Some(previous) if *previous != current => {
return Err(FetchError::Http(format!(
"{} changed upstream during inspect (etag {previous} \
became {current})",
self.filename
)));
}
Some(_) => {} None => self.response_etag = Some(current),
}
}
Ok(())
}
}
impl RangeFetcher for HttpRangeFetcher {
fn fetch(&mut self, start: u64, end_inclusive: u64) -> Result<Vec<u8>, FetchError> {
let (data, etag) = self.fetch_once(start, end_inclusive)?;
self.check_response_etag(etag)?;
Ok(data)
}
fn total_size(&self) -> u64 {
self.total_size
}
fn extra_requests(&self) -> u32 {
self.extra
}
}
fn parse_content_range(value: &str) -> Option<(u64, u64, u64)> {
let rest = value.strip_prefix("bytes ")?;
let (range, total) = rest.split_once('/')?;
let (start, end) = range.split_once('-')?;
Some((
start.trim().parse().ok()?,
end.trim().parse().ok()?,
total.trim().parse().ok()?,
))
}
fn clean_etag(raw: &str) -> String {
raw.strip_prefix("W/").unwrap_or(raw).replace('"', "")
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
struct InMemoryFetcher {
data: Vec<u8>,
calls: Vec<(u64, u64)>,
}
impl InMemoryFetcher {
fn new(data: Vec<u8>) -> Self {
Self {
data,
calls: Vec::new(),
}
}
}
impl RangeFetcher for InMemoryFetcher {
fn fetch(&mut self, start: u64, end_inclusive: u64) -> Result<Vec<u8>, FetchError> {
self.calls.push((start, end_inclusive));
let s = usize::try_from(start).unwrap();
let e = usize::try_from(end_inclusive).unwrap();
self.data
.get(s..=e)
.map(<[u8]>::to_vec)
.ok_or_else(|| FetchError::Http(format!("bad range {start}..={end_inclusive}")))
}
fn total_size(&self) -> u64 {
u64::try_from(self.data.len()).unwrap()
}
}
struct FailingFetcher {
size: u64,
}
impl RangeFetcher for FailingFetcher {
fn fetch(&mut self, _start: u64, _end_inclusive: u64) -> Result<Vec<u8>, FetchError> {
Err(FetchError::Http(
"Range request for x.npz returned status 403 Forbidden".to_owned(),
))
}
fn total_size(&self) -> u64 {
self.size
}
}
struct ShortFetcher {
size: u64,
}
impl RangeFetcher for ShortFetcher {
fn fetch(&mut self, _start: u64, _end_inclusive: u64) -> Result<Vec<u8>, FetchError> {
Ok(vec![0u8; 1])
}
fn total_size(&self) -> u64 {
self.size
}
}
fn sample_data(len: usize) -> Vec<u8> {
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
(0..len).map(|i| (i % 251) as u8).collect()
}
#[test]
fn seek_start_end_current_semantics() {
let mut r = RangeReader::new(InMemoryFetcher::new(sample_data(1000)));
assert_eq!(r.seek(SeekFrom::Start(10)).unwrap(), 10);
assert_eq!(r.seek(SeekFrom::Current(5)).unwrap(), 15);
assert_eq!(r.seek(SeekFrom::Current(-15)).unwrap(), 0);
assert_eq!(r.seek(SeekFrom::End(0)).unwrap(), 1000);
assert_eq!(r.seek(SeekFrom::End(-1000)).unwrap(), 0);
assert_eq!(r.seek(SeekFrom::End(50)).unwrap(), 1050);
let mut buf = [0u8; 4];
assert_eq!(r.read(&mut buf).unwrap(), 0);
}
#[test]
fn seek_negative_is_invalid_input() {
let mut r = RangeReader::new(InMemoryFetcher::new(sample_data(100)));
let err = r.seek(SeekFrom::Current(-1)).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
let err = r.seek(SeekFrom::End(-101)).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn empty_file_reads_zero_with_no_requests() {
let mut r = RangeReader::new(InMemoryFetcher::new(Vec::new()));
let mut buf = [0u8; 8];
assert_eq!(r.read(&mut buf).unwrap(), 0);
assert_eq!(r.stats().requests, 0);
}
#[test]
fn sequential_small_reads_coalesce_into_one_window_fetch() {
let mut r = RangeReader::new(InMemoryFetcher::new(sample_data(100 * 1024)));
let mut buf = [0u8; 16];
for i in 0..10 {
r.read_exact(&mut buf).unwrap();
assert_eq!(buf[0], sample_data(100 * 1024)[i * 16]);
}
assert_eq!(r.stats().requests, 1);
assert_eq!(r.fetcher.calls[0], (0, READAHEAD_BYTES - 1));
}
#[test]
fn tail_region_read_prefetches_tail_once() {
let size: u64 = 1024 * 1024; let mut r = RangeReader::new(InMemoryFetcher::new(sample_data(
usize::try_from(size).unwrap(),
)));
r.seek(SeekFrom::End(-22)).unwrap();
let mut buf = [0u8; 22];
r.read_exact(&mut buf).unwrap();
assert_eq!(r.stats().requests, 1);
assert_eq!(r.fetcher.calls[0], (size - TAIL_PREFETCH_BYTES, size - 1));
r.seek(SeekFrom::End(-4096)).unwrap();
let mut big = [0u8; 4096];
r.read_exact(&mut big).unwrap();
assert_eq!(r.stats().requests, 1);
assert_eq!(r.stats().bytes_fetched, TAIL_PREFETCH_BYTES);
}
#[test]
fn window_reuse_after_seek_back() {
let mut r = RangeReader::new(InMemoryFetcher::new(sample_data(64 * 1024)));
let mut buf = [0u8; 128];
r.read_exact(&mut buf).unwrap();
assert_eq!(r.stats().requests, 1);
r.seek(SeekFrom::Start(32)).unwrap();
r.read_exact(&mut buf).unwrap();
assert_eq!(r.stats().requests, 1);
let expected = sample_data(64 * 1024);
assert_eq!(&buf[..], &expected[32..160]);
}
#[test]
fn window_fetch_never_overlaps_cached_tail() {
let size: u64 = 200 * 1024;
let mut r = RangeReader::new(InMemoryFetcher::new(sample_data(
usize::try_from(size).unwrap(),
)));
r.seek(SeekFrom::End(-10)).unwrap();
let mut small = [0u8; 10];
r.read_exact(&mut small).unwrap();
let tail_start = size - TAIL_PREFETCH_BYTES;
r.seek(SeekFrom::Start(tail_start - 100)).unwrap();
let mut big = vec![0u8; 4096];
r.read_exact(&mut big).unwrap();
assert_eq!(r.fetcher.calls[1], (tail_start - 100, tail_start - 1));
let expected = sample_data(usize::try_from(size).unwrap());
let from = usize::try_from(tail_start - 100).unwrap();
assert_eq!(&big[..], &expected[from..from + 4096]);
}
#[test]
fn request_cap_is_enforced() {
let data = sample_data(10 * 1024 * 1024);
let mut r = RangeReader::with_limits(InMemoryFetcher::new(data), 3, u64::MAX);
let mut buf = [0u8; 8];
for i in 0u64..3 {
r.seek(SeekFrom::Start(i * 100 * 1024)).unwrap();
r.read_exact(&mut buf).unwrap();
}
r.seek(SeekFrom::Start(1024 * 1024)).unwrap();
let err = r.read(&mut buf).unwrap_err();
assert!(
err.to_string()
.contains("range request cap exceeded (3 requests)"),
"unexpected error: {err}"
);
}
#[test]
fn transfer_budget_is_enforced() {
let data = sample_data(10 * 1024 * 1024);
let mut r = RangeReader::with_limits(InMemoryFetcher::new(data), u32::MAX, 6 * 1024);
let mut buf = [0u8; 8];
r.read_exact(&mut buf).unwrap();
r.seek(SeekFrom::Start(1024 * 1024)).unwrap();
let err = r.read(&mut buf).unwrap_err();
assert!(
err.to_string().contains("range transfer budget exceeded"),
"unexpected error: {err}"
);
}
#[test]
fn fetch_error_surfaces_as_io_and_is_recoverable_typed() {
let mut r = RangeReader::new(FailingFetcher { size: 1024 });
let mut buf = [0u8; 8];
let err = r.read(&mut buf).unwrap_err();
assert!(err.to_string().contains("returned status 403"));
let typed = r.take_last_error().expect("typed error must be stored");
assert!(matches!(typed, FetchError::Http(msg)
if msg.contains("returned status 403 Forbidden")));
assert!(r.take_last_error().is_none());
}
#[test]
fn short_fetch_is_a_contract_error() {
let mut r = RangeReader::new(ShortFetcher { size: 1024 * 1024 });
let mut buf = [0u8; 8];
let err = r.read(&mut buf).unwrap_err();
assert!(
err.to_string().contains("bytes for a"),
"unexpected error: {err}"
);
}
#[test]
fn parse_content_range_accepts_the_exact_206_form() {
assert_eq!(parse_content_range("bytes 0-7/1234"), Some((0, 7, 1234)));
assert_eq!(
parse_content_range("bytes 100-199/200"),
Some((100, 199, 200))
);
}
#[test]
fn parse_content_range_rejects_deviant_forms() {
assert_eq!(parse_content_range("bytes */1234"), None);
assert_eq!(parse_content_range("bytes 0-7/*"), None);
assert_eq!(parse_content_range("0-7/1234"), None);
assert_eq!(parse_content_range("bytes 7/1234"), None);
assert_eq!(parse_content_range(""), None);
}
#[test]
fn clean_etag_strips_quotes_and_weak_prefix() {
assert_eq!(clean_etag("\"abc123\""), "abc123");
assert_eq!(clean_etag("W/\"abc123\""), "abc123");
assert_eq!(clean_etag("abc123"), "abc123");
}
fn npy_bytes(descr: &str, shape_literal: &str, data_len: usize) -> Vec<u8> {
let dict =
format!("{{'descr': '{descr}', 'fortran_order': False, 'shape': {shape_literal}, }}");
let unpadded = 10 + dict.len() + 1; let padding = (64 - unpadded % 64) % 64;
let header_len = dict.len() + padding + 1;
let mut out = Vec::with_capacity(10 + header_len + data_len);
out.extend_from_slice(b"\x93NUMPY\x01\x00");
out.extend_from_slice(&u16::try_from(header_len).unwrap().to_le_bytes());
out.extend_from_slice(dict.as_bytes());
out.extend(std::iter::repeat_n(b' ', padding));
out.push(b'\n');
out.extend(std::iter::repeat_n(0u8, data_len));
out
}
fn stored_zip(entries: &[(&str, Vec<u8>)]) -> Vec<u8> {
let mut out = Vec::new();
let mut offsets = Vec::new();
for (name, payload) in entries {
offsets.push(u32::try_from(out.len()).unwrap());
let size = u32::try_from(payload.len()).unwrap();
out.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&size.to_le_bytes()); out.extend_from_slice(&size.to_le_bytes()); out.extend_from_slice(&u16::try_from(name.len()).unwrap().to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(name.as_bytes());
out.extend_from_slice(payload);
}
let cd_offset = u32::try_from(out.len()).unwrap();
for ((name, payload), lfh_offset) in entries.iter().zip(&offsets) {
let size = u32::try_from(payload.len()).unwrap();
out.extend_from_slice(&0x0201_4b50u32.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&size.to_le_bytes()); out.extend_from_slice(&size.to_le_bytes()); out.extend_from_slice(&u16::try_from(name.len()).unwrap().to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&lfh_offset.to_le_bytes());
out.extend_from_slice(name.as_bytes());
}
let cd_size = u32::try_from(out.len()).unwrap() - cd_offset;
let n = u16::try_from(entries.len()).unwrap();
out.extend_from_slice(&0x0605_4b50u32.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&n.to_le_bytes()); out.extend_from_slice(&n.to_le_bytes()); out.extend_from_slice(&cd_size.to_le_bytes());
out.extend_from_slice(&cd_offset.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out
}
#[test]
fn npz_inspect_over_range_reader_reads_metadata_not_data() {
let npz = stored_zip(&[
("w_enc.npy", npy_bytes("<f4", "(2, 3)", 2 * 3 * 4)),
("b_dec.npy", npy_bytes("<f4", "(150, 1024)", 600 * 1024)),
]);
let total = u64::try_from(npz.len()).unwrap();
let mut reader = RangeReader::new(InMemoryFetcher::new(npz));
let info = anamnesis::inspect_npz_from_reader(&mut reader)
.expect("synthetic NPZ must inspect cleanly");
assert_eq!(info.tensors.len(), 2);
let names: Vec<&str> = info.tensors.iter().map(|t| t.name.as_str()).collect();
assert!(names.contains(&"w_enc"), "names: {names:?}");
assert!(names.contains(&"b_dec"), "names: {names:?}");
let b_dec = info.tensors.iter().find(|t| t.name == "b_dec").unwrap();
assert_eq!(b_dec.shape, vec![150, 1024]);
let stats = reader.stats();
assert!(
stats.requests <= 8,
"expected a handful of range requests, got {}",
stats.requests
);
assert!(
stats.bytes_fetched < total / 4,
"fetched {} of {total} bytes — inspect must not read tensor data",
stats.bytes_fetched
);
}
}