use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use bytes::Bytes;
use tokio::sync::OnceCell;
use xet_client::cas_client::Client;
use xet_client::cas_types::{ChunkRange, FileRange, HttpRange};
use xet_client::chunk_cache::ChunkCache;
use xet_core_structures::merklehash::MerkleHash;
use xet_runtime::core::xet_config;
use xet_runtime::utils::UniqueId;
use super::super::FileReconstructionError;
use super::super::data_writer::DataFuture;
use super::super::error::Result;
use super::retrieval_urls::TermBlockRetrievalURLs;
use super::xorb_block::{XorbBlock, XorbBlockData, XorbReference};
use crate::progress_tracking::ItemProgressUpdater;
#[derive(Clone)]
pub struct FileTerm {
pub byte_range: FileRange,
pub xorb_chunk_range: ChunkRange,
pub xorb_block_start_index: usize,
pub offset_into_first_range: u64,
pub xorb_block: Arc<XorbBlock>,
pub url_info: Arc<TermBlockRetrievalURLs>,
}
impl FileTerm {
pub fn extract_bytes(&self, xorb_block_data: &XorbBlockData) -> Bytes {
let (_, start_byte_offset) = xorb_block_data.chunk_offsets[self.xorb_block_start_index];
let start_byte_offset = start_byte_offset + self.offset_into_first_range as usize;
let expected_size = (self.byte_range.end - self.byte_range.start) as usize;
let end_byte_offset = start_byte_offset + expected_size;
xorb_block_data.data.slice(start_byte_offset..end_byte_offset)
}
pub async fn get_data_task(
&self,
client: Arc<dyn Client>,
progress_updater: Option<Arc<ItemProgressUpdater>>,
chunk_cache: Option<Arc<dyn ChunkCache>>,
) -> Result<DataFuture> {
if let Some(xorb_block_data) = self.xorb_block.data.get() {
let bytes = self.extract_bytes(xorb_block_data);
return Ok(Box::pin(async move { Ok(bytes) }));
}
let file_term = self.clone();
let url_info = self.url_info.clone();
let xorb_block = self.xorb_block.clone();
let task = tokio::task::spawn(async move {
let xorb_block_data = xorb_block
.retrieve_data(client, url_info, progress_updater, chunk_cache)
.await?;
Ok(file_term.extract_bytes(&xorb_block_data))
});
Ok(Box::pin(async move { task.await? }))
}
}
struct FileTermEntry {
byte_range: FileRange,
xorb_chunk_range: ChunkRange,
offset_into_first_range: u64,
xorb_block_index: usize,
xorb_block_start_index: usize,
}
pub async fn retrieve_file_term_block(
client: Arc<dyn Client>,
file_hash: MerkleHash,
query_file_byte_range: FileRange,
) -> Result<Option<(FileRange, u64, Vec<FileTerm>)>> {
let Some(raw_reconstruction) = client.get_reconstruction(&file_hash, Some(query_file_byte_range)).await? else {
return Ok(None);
};
let acquisition_id = UniqueId::new();
let mut file_term_data = Vec::<FileTermEntry>::with_capacity(raw_reconstruction.terms.len());
let mut xorb_blocks: Vec<XorbBlock> = Vec::new();
let mut xorb_block_retrieval_urls = Vec::<(String, Vec<HttpRange>)>::new();
let mut xorb_index_lookup = HashMap::<(MerkleHash, u32), usize>::new();
let mut cur_file_byte_offset = query_file_byte_range.start;
let enable_multirange = xet_config().client.enable_multirange_fetching;
for (local_term_index, term) in raw_reconstruction.terms.iter().enumerate() {
let xorb_hash: MerkleHash = term.hash.into();
let Some(xorb_descriptor) = raw_reconstruction.xorbs.get(&term.hash) else {
return Err(FileReconstructionError::CorruptedReconstruction(format!(
"Xorb info not found for xorb hash {xorb_hash:?}"
)));
};
let xorb_block_index = 'find_xorb_block: {
for fetch_entry in xorb_descriptor.iter() {
if enable_multirange {
let term_contained = fetch_entry
.ranges
.iter()
.any(|r| r.chunks.start <= term.range.start && term.range.end <= r.chunks.end);
if !term_contained {
continue;
}
let first_chunk_start = fetch_entry.ranges[0].chunks.start;
let index = match xorb_index_lookup.entry((xorb_hash, first_chunk_start)) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let new_index = xorb_blocks.len();
let chunk_ranges: Vec<ChunkRange> = fetch_entry.ranges.iter().map(|r| r.chunks).collect();
let http_ranges: Vec<HttpRange> = fetch_entry.ranges.iter().map(|r| r.bytes).collect();
xorb_blocks.push(XorbBlock {
xorb_hash,
chunk_ranges,
xorb_block_index: new_index,
references: vec![],
uncompressed_size_if_known: None,
data: OnceCell::new(),
});
xorb_block_retrieval_urls.push((fetch_entry.url.clone(), http_ranges));
entry.insert(new_index);
new_index
},
};
break 'find_xorb_block index;
} else {
for range in &fetch_entry.ranges {
if range.chunks.start <= term.range.start && term.range.end <= range.chunks.end {
let index = match xorb_index_lookup.entry((xorb_hash, range.chunks.start)) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let new_index = xorb_blocks.len();
xorb_blocks.push(XorbBlock {
xorb_hash,
chunk_ranges: vec![range.chunks],
xorb_block_index: new_index,
references: vec![],
uncompressed_size_if_known: None,
data: OnceCell::new(),
});
xorb_block_retrieval_urls.push((fetch_entry.url.clone(), vec![range.bytes]));
entry.insert(new_index);
new_index
},
};
break 'find_xorb_block index;
}
}
}
}
return Err(FileReconstructionError::CorruptedReconstruction(format!(
"No xorb fetch entry found for file term {local_term_index:?} in xorb info for xorb hash {xorb_hash:?}"
)));
};
let offset_into_first_range = if local_term_index == 0 {
raw_reconstruction.offset_into_first_range
} else {
0
};
let term_byte_size = term.unpacked_length as u64 - offset_into_first_range;
xorb_blocks[xorb_block_index].references.push(XorbReference {
term_chunks: term.range,
uncompressed_size: term.unpacked_length as usize,
});
let xorb_block_start_index = {
let chunk_start = term.range.start;
let chunk_ranges = &xorb_blocks[xorb_block_index].chunk_ranges;
let mut idx = 0;
let mut found = false;
for range in chunk_ranges {
if chunk_start >= range.start && chunk_start < range.end {
idx += (chunk_start - range.start) as usize;
found = true;
break;
}
idx += (range.end - range.start) as usize;
}
if !found {
return Err(FileReconstructionError::CorruptedReconstruction(format!(
"chunk_start {chunk_start} not found in chunk_ranges {chunk_ranges:?} for file term {local_term_index}"
)));
}
idx
};
file_term_data.push(FileTermEntry {
byte_range: FileRange::new(cur_file_byte_offset, cur_file_byte_offset + term_byte_size),
xorb_chunk_range: term.range,
offset_into_first_range,
xorb_block_index,
xorb_block_start_index,
});
cur_file_byte_offset += term_byte_size;
}
for block in &mut xorb_blocks {
block.references.sort_by_key(|r| r.term_chunks.start);
block.uncompressed_size_if_known =
XorbBlock::determine_size_if_possible(&block.chunk_ranges, &block.references);
}
if cur_file_byte_offset > query_file_byte_range.end {
let last_term_shrinkage = cur_file_byte_offset - query_file_byte_range.end;
debug_assert!(!file_term_data.is_empty());
if let Some(entry) = file_term_data.last_mut() {
entry.byte_range.end -= last_term_shrinkage;
}
}
let actual_range = FileRange::new(
file_term_data.first().map(|e| e.byte_range.start).unwrap_or(0),
file_term_data.last().map(|e| e.byte_range.end).unwrap_or(0),
);
let total_transfer_bytes: u64 = xorb_block_retrieval_urls
.iter()
.flat_map(|(_, ranges)| ranges)
.map(|r| r.length())
.sum();
let url_info =
Arc::new(TermBlockRetrievalURLs::new(file_hash, actual_range, acquisition_id, xorb_block_retrieval_urls));
let xorb_blocks_arc: Vec<Arc<XorbBlock>> = xorb_blocks.into_iter().map(Arc::new).collect();
let file_terms: Vec<FileTerm> = file_term_data
.into_iter()
.map(|entry| FileTerm {
byte_range: entry.byte_range,
xorb_chunk_range: entry.xorb_chunk_range,
xorb_block_start_index: entry.xorb_block_start_index,
offset_into_first_range: entry.offset_into_first_range,
xorb_block: xorb_blocks_arc[entry.xorb_block_index].clone(),
url_info: url_info.clone(),
})
.collect();
Ok(Some((actual_range, total_transfer_bytes, file_terms)))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use more_asserts::assert_le;
use xet_client::cas_client::{ClientTestingUtils, LocalClient, RandomFileContents};
use xet_client::cas_types::{ChunkRange, FileRange};
use xet_runtime::utils::UniqueId;
use super::*;
const TEST_CHUNK_SIZE: usize = 101;
fn verify_xorb_block_references(file_terms: &[FileTerm]) {
for file_term in file_terms {
let refs = &file_term.xorb_block.references;
assert!(
refs.iter().any(|r| r.term_chunks == file_term.xorb_chunk_range),
"xorb_chunk_range {:?} must be in block references {:?}",
file_term.xorb_chunk_range,
refs.as_slice()
);
}
let mut seen_blocks = std::collections::HashSet::new();
for file_term in file_terms {
if seen_blocks.insert(file_term.xorb_block.xorb_block_index) {
let refs = &file_term.xorb_block.references;
for w in refs.windows(2) {
assert_le!(w[0].term_chunks.start, w[1].term_chunks.start);
}
}
}
}
async fn setup_test_file(term_spec: &[(u64, (u64, u64))]) -> (Arc<LocalClient>, RandomFileContents) {
let client = LocalClient::temporary().await.unwrap();
let file_contents = client.upload_random_file(term_spec, TEST_CHUNK_SIZE).await.unwrap();
(client, file_contents)
}
async fn retrieve_and_verify(
client: &Arc<LocalClient>,
file_contents: &RandomFileContents,
requested_range: Option<FileRange>,
) {
let requested_range = requested_range.unwrap_or_else(|| FileRange::new(0, file_contents.data.len() as u64));
let dyn_client: Arc<dyn Client> = client.clone();
let (returned_range, _, file_terms) =
retrieve_file_term_block(dyn_client.clone(), file_contents.file_hash, requested_range)
.await
.expect("retrieve_file_term_block should succeed")
.expect("file_terms should not be None for valid range");
assert_eq!(returned_range, requested_range);
let mut current_pos = requested_range.start;
let mut file_term_data_offset = 0usize;
let mut expected_term_idx = 0;
let mut byte_offset = 0u64;
for (idx, term) in file_contents.terms.iter().enumerate() {
let term_end = byte_offset + term.data.len() as u64;
if term_end > requested_range.start {
expected_term_idx = idx;
file_term_data_offset = (requested_range.start - byte_offset) as usize;
break;
}
byte_offset = term_end;
}
let mut seen_xorb_indices = std::collections::HashSet::new();
let mut reconstructed_data = Vec::with_capacity((requested_range.end - requested_range.start) as usize);
let mut term_count = 0;
for file_term in &file_terms {
assert_eq!(file_term.byte_range.start, current_pos);
assert!(file_term.byte_range.end > file_term.byte_range.start);
assert_le!(file_term.byte_range.end, requested_range.end);
seen_xorb_indices.insert(file_term.xorb_block.xorb_block_index);
let xorb_block = &file_term.xorb_block;
let term_in_some_range = xorb_block
.chunk_ranges
.iter()
.any(|cr| file_term.xorb_chunk_range.start >= cr.start && file_term.xorb_chunk_range.end <= cr.end);
assert!(
term_in_some_range,
"term chunk range {:?} not within any block chunk range {:?}",
file_term.xorb_chunk_range, xorb_block.chunk_ranges
);
if expected_term_idx < file_contents.terms.len() {
let expected_term = &file_contents.terms[expected_term_idx];
assert_eq!(xorb_block.xorb_hash, expected_term.xorb_hash);
if file_term_data_offset == 0 {
assert_eq!(file_term.xorb_chunk_range.start, expected_term.chunk_start);
}
}
assert!(file_contents.xorbs.contains_key(&file_term.xorb_block.xorb_hash));
let data_future = file_term.get_data_task(dyn_client.clone(), None, None).await.unwrap();
let data = data_future.await.unwrap();
let expected_size = (file_term.byte_range.end - file_term.byte_range.start) as usize;
assert_eq!(data.len(), expected_size, "Term {term_count} data size mismatch");
reconstructed_data.extend_from_slice(&data);
current_pos = file_term.byte_range.end;
expected_term_idx += 1;
file_term_data_offset = 0;
term_count += 1;
}
assert_eq!(current_pos, requested_range.end);
if requested_range.start == 0 && requested_range.end == file_contents.data.len() as u64 {
assert_eq!(term_count, file_contents.terms.len());
}
let expected_data = &file_contents.data[requested_range.start as usize..requested_range.end as usize];
assert_eq!(reconstructed_data.len(), expected_data.len());
assert_eq!(reconstructed_data, expected_data);
verify_xorb_block_references(&file_terms);
}
#[tokio::test]
async fn test_xorb_block_references_exact() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (2, 4)), (1, (4, 6))]).await;
let file_range = FileRange::new(0, file_contents.data.len() as u64);
let dyn_client: Arc<dyn Client> = client.clone();
let (_, _, file_terms) = retrieve_file_term_block(dyn_client, file_contents.file_hash, file_range)
.await
.unwrap()
.unwrap();
verify_xorb_block_references(&file_terms);
assert_eq!(file_terms.len(), 3);
let block = &file_terms[0].xorb_block;
let ref_ranges: Vec<ChunkRange> = block.references.iter().map(|r| r.term_chunks).collect();
let expected = vec![ChunkRange::new(0, 2), ChunkRange::new(2, 4), ChunkRange::new(4, 6)];
assert_eq!(ref_ranges, expected);
let (client2, file_contents2) = setup_test_file(&[(1, (0, 5)), (1, (0, 5))]).await;
let file_range2 = FileRange::new(0, file_contents2.data.len() as u64);
let dyn_client2: Arc<dyn Client> = client2.clone();
let (_, _, file_terms2) = retrieve_file_term_block(dyn_client2, file_contents2.file_hash, file_range2)
.await
.unwrap()
.unwrap();
verify_xorb_block_references(&file_terms2);
let block2 = &file_terms2[0].xorb_block;
let ref_ranges2: Vec<ChunkRange> = block2.references.iter().map(|r| r.term_chunks).collect();
let expected2 = vec![ChunkRange::new(0, 5), ChunkRange::new(0, 5)];
assert_eq!(ref_ranges2, expected2);
}
#[tokio::test]
async fn test_single_xorb_full_range() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_multiple_terms_same_xorb() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (2, 4)), (1, (4, 6))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_multiple_xorbs() {
let (client, file_contents) = setup_test_file(&[(1, (0, 3)), (2, (0, 2)), (3, (0, 4))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_overlapping_chunk_ranges() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5)), (1, (1, 3)), (1, (2, 4))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_partial_range_middle() {
let (client, file_contents) = setup_test_file(&[(1, (0, 10))]).await;
let file_len = file_contents.data.len() as u64;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(file_len / 4, file_len * 3 / 4))).await;
}
#[tokio::test]
async fn test_partial_range_start() {
let (client, file_contents) = setup_test_file(&[(1, (0, 10))]).await;
let file_len = file_contents.data.len() as u64;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(0, file_len / 2))).await;
}
#[tokio::test]
async fn test_partial_range_end() {
let (client, file_contents) = setup_test_file(&[(1, (0, 10))]).await;
let file_len = file_contents.data.len() as u64;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(file_len / 2, file_len))).await;
}
#[tokio::test]
async fn test_beyond_file_end() {
let (client, file_contents) = setup_test_file(&[(1, (0, 3))]).await;
let file_len = file_contents.data.len() as u64;
let beyond_range = FileRange::new(file_len + 1000, file_len + 2000);
let dyn_client: Arc<dyn Client> = client.clone();
let result = retrieve_file_term_block(dyn_client, file_contents.file_hash, beyond_range).await;
match result {
Ok(None) => {},
Ok(Some((_, _, file_terms))) => assert!(file_terms.is_empty()),
Err(_) => {},
}
}
#[tokio::test]
async fn test_interleaved_xorbs() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (2, (0, 2)), (1, (2, 4)), (2, (2, 4))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_non_contiguous_chunks() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (4, 6))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_adjacent_chunks() {
let (client, file_contents) = setup_test_file(&[(1, (0, 3)), (1, (3, 5))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_single_chunk_terms() {
let (client, file_contents) =
setup_test_file(&[(1, (0, 1)), (1, (1, 2)), (1, (2, 3)), (2, (0, 1)), (2, (1, 2))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_large_file_many_xorbs() {
let term_spec: Vec<(u64, (u64, u64))> = (1..=10).map(|i| (i, (0, 3))).collect();
let (client, file_contents) = setup_test_file(&term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_xorb_block_deduplication() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5)), (1, (0, 5))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_retrieval_url_acquisition() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
let file_range = FileRange::new(0, file_contents.data.len() as u64);
let dyn_client: Arc<dyn Client> = client.clone();
let (_, _, file_terms) = retrieve_file_term_block(dyn_client, file_contents.file_hash, file_range)
.await
.unwrap()
.unwrap();
let file_term = &file_terms[0];
let xorb_block_index = file_term.xorb_block.xorb_block_index;
let (unique_id, url, http_ranges) = file_term.url_info.get_retrieval_url(xorb_block_index).await;
assert!(!url.is_empty());
assert!(!http_ranges.is_empty());
assert!(http_ranges[0].start <= http_ranges[0].end);
assert!(unique_id != UniqueId::null());
}
#[tokio::test]
async fn test_complex_mixed_pattern() {
let term_spec = &[
(1, (0, 3)),
(2, (0, 2)),
(1, (3, 5)),
(3, (1, 4)),
(2, (4, 6)),
(1, (0, 2)),
];
let (client, file_contents) = setup_test_file(term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_repeated_xorb_different_ranges() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (3, 5)), (1, (1, 3)), (1, (4, 6))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_single_chunk_file() {
let (client, file_contents) = setup_test_file(&[(1, (0, 1))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_many_small_terms_from_different_xorbs() {
let term_spec: Vec<(u64, (u64, u64))> = (1..=20).map(|i| (i, (0, 1))).collect();
let (client, file_contents) = setup_test_file(&term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_range_few_bytes_before_end() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
let file_len = file_contents.data.len() as u64;
let range = FileRange::new(0, file_len - 3);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
let range = FileRange::new(0, file_len - 1);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
}
#[tokio::test]
async fn test_range_few_bytes_after_start() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
let file_len = file_contents.data.len() as u64;
let range = FileRange::new(3, file_len);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
let range = FileRange::new(1, file_len);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
}
#[tokio::test]
async fn test_range_few_bytes_offset_both_ends() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
let file_len = file_contents.data.len() as u64;
let range = FileRange::new(2, file_len - 2);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
let range = FileRange::new(file_len / 2 - 1, file_len / 2 + 1);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
}
#[tokio::test]
async fn test_range_single_byte_at_various_positions() {
let (client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
let file_len = file_contents.data.len() as u64;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(0, 1))).await;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(file_len - 1, file_len))).await;
let mid = file_len / 2;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(mid, mid + 1))).await;
}
#[tokio::test]
async fn test_multi_term_range_ends_mid_chunk() {
let (client, file_contents) = setup_test_file(&[(1, (0, 3)), (2, (0, 3)), (3, (0, 3))]).await;
let file_len = file_contents.data.len() as u64;
let range = FileRange::new(0, file_len - 5);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
}
#[tokio::test]
async fn test_multi_term_range_starts_mid_chunk() {
let (client, file_contents) = setup_test_file(&[(1, (0, 3)), (2, (0, 3)), (3, (0, 3))]).await;
let file_len = file_contents.data.len() as u64;
let range = FileRange::new(5, file_len);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
}
#[tokio::test]
async fn test_triple_disjoint_same_xorb() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (4, 6)), (1, (8, 10))]).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_triple_disjoint_partial_range_across_gap() {
let (client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (4, 6)), (1, (8, 10))]).await;
let file_len = file_contents.data.len() as u64;
let range = FileRange::new(file_len / 4, file_len * 3 / 4);
retrieve_and_verify(&client, &file_contents, Some(range)).await;
}
#[tokio::test]
async fn test_two_xorbs_interleaved_disjoint() {
let term_spec = &[(1, (0, 2)), (2, (0, 2)), (1, (4, 6)), (2, (4, 6))];
let (client, file_contents) = setup_test_file(term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_two_xorbs_interleaved_disjoint_partial() {
let term_spec = &[(1, (0, 2)), (2, (0, 2)), (1, (4, 6)), (2, (4, 6))];
let (client, file_contents) = setup_test_file(term_spec).await;
let file_len = file_contents.data.len() as u64;
retrieve_and_verify(&client, &file_contents, Some(FileRange::new(file_len / 3, file_len * 2 / 3))).await;
}
#[tokio::test]
async fn test_four_single_chunk_disjoint() {
let term_spec = &[(1, (0, 1)), (1, (3, 4)), (1, (6, 7)), (1, (9, 10))];
let (client, file_contents) = setup_test_file(term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_contiguous_then_disjoint() {
let term_spec = &[(1, (0, 2)), (1, (2, 4)), (1, (8, 10))];
let (client, file_contents) = setup_test_file(term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
#[tokio::test]
async fn test_three_xorbs_complex_disjoint() {
let term_spec = &[
(1, (0, 2)),
(2, (0, 3)),
(3, (2, 5)),
(1, (5, 8)),
(2, (6, 8)),
(3, (0, 2)),
];
let (client, file_contents) = setup_test_file(term_spec).await;
retrieve_and_verify(&client, &file_contents, None).await;
}
}