1use std::collections::HashMap;
2use std::collections::hash_map::Entry;
3use std::sync::Arc;
4
5use bytes::Bytes;
6use tokio::sync::OnceCell;
7#[cfg(target_family = "wasm")]
8use tokio_with_wasm::alias as tokio;
9use xet_client::cas_client::Client;
10use xet_client::cas_types::{ChunkRange, FileRange, HttpRange};
11use xet_client::chunk_cache::ChunkCache;
12use xet_core_structures::merklehash::MerkleHash;
13use xet_runtime::core::XetContext;
14use xet_runtime::utils::UniqueId;
15
16use super::super::FileReconstructionError;
17use super::super::data_writer::DataFuture;
18use super::super::error::Result;
19use super::retrieval_urls::TermBlockRetrievalURLs;
20use super::xorb_block::{XorbBlock, XorbBlockData, XorbReference};
21use crate::progress_tracking::ItemProgressUpdater;
22#[derive(Clone)]
25pub struct FileTerm {
26 pub byte_range: FileRange,
28
29 pub xorb_chunk_range: ChunkRange,
31
32 pub xorb_block_start_index: usize,
34
35 pub offset_into_first_range: u64,
37
38 pub xorb_block: Arc<XorbBlock>,
40
41 pub url_info: Arc<TermBlockRetrievalURLs>,
43}
44
45impl FileTerm {
46 pub fn extract_bytes(&self, xorb_block_data: &XorbBlockData) -> Bytes {
47 let (_, start_byte_offset) = xorb_block_data.chunk_offsets[self.xorb_block_start_index];
48 let start_byte_offset = start_byte_offset + self.offset_into_first_range as usize;
49 let expected_size = (self.byte_range.end - self.byte_range.start) as usize;
50 let end_byte_offset = start_byte_offset + expected_size;
51
52 xorb_block_data.data.slice(start_byte_offset..end_byte_offset)
53 }
54
55 pub async fn get_data_task(
62 &self,
63 ctx: XetContext,
64 client: Arc<dyn Client>,
65 progress_updater: Option<Arc<ItemProgressUpdater>>,
66 chunk_cache: Option<Arc<dyn ChunkCache>>,
67 ) -> Result<DataFuture> {
68 if let Some(xorb_block_data) = self.xorb_block.data.get() {
70 let bytes = self.extract_bytes(xorb_block_data);
71 return Ok(Box::pin(async move { Ok(bytes) }));
72 }
73
74 let file_term = self.clone();
75 let url_info = self.url_info.clone();
76 let xorb_block = self.xorb_block.clone();
77
78 let task = tokio::task::spawn(async move {
79 let xorb_block_data = xorb_block
80 .retrieve_data(ctx, client, url_info, progress_updater, chunk_cache)
81 .await?;
82 Ok(file_term.extract_bytes(&xorb_block_data))
83 });
84
85 Ok(Box::pin(async move { task.await? }))
86 }
87}
88
89struct FileTermEntry {
95 byte_range: FileRange,
97 xorb_chunk_range: ChunkRange,
99 offset_into_first_range: u64,
102 xorb_block_index: usize,
104 xorb_block_start_index: usize,
106}
107
108pub async fn retrieve_file_term_block(
114 ctx: &XetContext,
115 client: Arc<dyn Client>,
116 file_hash: MerkleHash,
117 query_file_byte_range: FileRange,
118) -> Result<Option<(FileRange, u64, Vec<FileTerm>)>> {
119 let Some(raw_reconstruction) = client.get_reconstruction(&file_hash, Some(query_file_byte_range)).await? else {
121 return Ok(None);
123 };
124
125 let acquisition_id = UniqueId::new();
127
128 let mut file_term_data = Vec::<FileTermEntry>::with_capacity(raw_reconstruction.terms.len());
133
134 let mut xorb_blocks: Vec<XorbBlock> = Vec::new();
138 let mut xorb_block_retrieval_urls = Vec::<(String, Vec<HttpRange>)>::new();
139
140 let mut xorb_index_lookup = HashMap::<(MerkleHash, u32), usize>::new();
144
145 let mut cur_file_byte_offset = query_file_byte_range.start;
147
148 let enable_multirange = ctx.config.client.enable_multirange_fetching;
149
150 for (local_term_index, term) in raw_reconstruction.terms.iter().enumerate() {
151 let xorb_hash: MerkleHash = term.hash.into();
152
153 let Some(xorb_descriptor) = raw_reconstruction.xorbs.get(&term.hash) else {
154 return Err(FileReconstructionError::CorruptedReconstruction(format!(
155 "Xorb info not found for xorb hash {xorb_hash:?}"
156 )));
157 };
158
159 let xorb_block_index = 'find_xorb_block: {
167 for fetch_entry in xorb_descriptor.iter() {
168 if enable_multirange {
169 let term_contained = fetch_entry
170 .ranges
171 .iter()
172 .any(|r| r.chunks.start <= term.range.start && term.range.end <= r.chunks.end);
173
174 if !term_contained {
175 continue;
176 }
177
178 let first_chunk_start = fetch_entry.ranges[0].chunks.start;
179
180 let index = match xorb_index_lookup.entry((xorb_hash, first_chunk_start)) {
181 Entry::Occupied(entry) => *entry.get(),
182 Entry::Vacant(entry) => {
183 let new_index = xorb_blocks.len();
184
185 let chunk_ranges: Vec<ChunkRange> = fetch_entry.ranges.iter().map(|r| r.chunks).collect();
186 let http_ranges: Vec<HttpRange> = fetch_entry.ranges.iter().map(|r| r.bytes).collect();
187
188 xorb_blocks.push(XorbBlock {
189 xorb_hash,
190 chunk_ranges,
191 xorb_block_index: new_index,
192 references: vec![],
193 uncompressed_size_if_known: None,
194 data: OnceCell::new(),
195 });
196
197 xorb_block_retrieval_urls.push((fetch_entry.url.clone(), http_ranges));
198
199 entry.insert(new_index);
200 new_index
201 },
202 };
203
204 break 'find_xorb_block index;
205 } else {
206 for range in &fetch_entry.ranges {
207 if range.chunks.start <= term.range.start && term.range.end <= range.chunks.end {
208 let index = match xorb_index_lookup.entry((xorb_hash, range.chunks.start)) {
209 Entry::Occupied(entry) => *entry.get(),
210 Entry::Vacant(entry) => {
211 let new_index = xorb_blocks.len();
212
213 xorb_blocks.push(XorbBlock {
214 xorb_hash,
215 chunk_ranges: vec![range.chunks],
216 xorb_block_index: new_index,
217 references: vec![],
218 uncompressed_size_if_known: None,
219 data: OnceCell::new(),
220 });
221
222 xorb_block_retrieval_urls.push((fetch_entry.url.clone(), vec![range.bytes]));
223
224 entry.insert(new_index);
225 new_index
226 },
227 };
228
229 break 'find_xorb_block index;
230 }
231 }
232 }
233 }
234 return Err(FileReconstructionError::CorruptedReconstruction(format!(
235 "No xorb fetch entry found for file term {local_term_index:?} in xorb info for xorb hash {xorb_hash:?}"
236 )));
237 };
238
239 let offset_into_first_range = if local_term_index == 0 {
242 raw_reconstruction.offset_into_first_range
243 } else {
244 0
245 };
246
247 let term_byte_size = term.unpacked_length as u64 - offset_into_first_range;
250
251 xorb_blocks[xorb_block_index].references.push(XorbReference {
254 term_chunks: term.range,
255 uncompressed_size: term.unpacked_length as usize,
256 });
257
258 let xorb_block_start_index = {
264 let chunk_start = term.range.start;
265 let chunk_ranges = &xorb_blocks[xorb_block_index].chunk_ranges;
266 let mut idx = 0;
267 let mut found = false;
268 for range in chunk_ranges {
269 if chunk_start >= range.start && chunk_start < range.end {
270 idx += (chunk_start - range.start) as usize;
271 found = true;
272 break;
273 }
274 idx += (range.end - range.start) as usize;
275 }
276 if !found {
277 return Err(FileReconstructionError::CorruptedReconstruction(format!(
278 "chunk_start {chunk_start} not found in chunk_ranges {chunk_ranges:?} for file term {local_term_index}"
279 )));
280 }
281 idx
282 };
283
284 file_term_data.push(FileTermEntry {
285 byte_range: FileRange::new(cur_file_byte_offset, cur_file_byte_offset + term_byte_size),
286 xorb_chunk_range: term.range,
287 offset_into_first_range,
288 xorb_block_index,
289 xorb_block_start_index,
290 });
291
292 cur_file_byte_offset += term_byte_size;
293 }
294
295 for block in &mut xorb_blocks {
298 block.references.sort_by_key(|r| r.term_chunks.start);
299 block.uncompressed_size_if_known =
300 XorbBlock::determine_size_if_possible(&block.chunk_ranges, &block.references);
301 }
302
303 if cur_file_byte_offset > query_file_byte_range.end {
306 let last_term_shrinkage = cur_file_byte_offset - query_file_byte_range.end;
307
308 debug_assert!(!file_term_data.is_empty());
309
310 if let Some(entry) = file_term_data.last_mut() {
311 entry.byte_range.end -= last_term_shrinkage;
312 }
313 }
314
315 let actual_range = FileRange::new(
318 file_term_data.first().map(|e| e.byte_range.start).unwrap_or(0),
319 file_term_data.last().map(|e| e.byte_range.end).unwrap_or(0),
320 );
321
322 let total_transfer_bytes: u64 = xorb_block_retrieval_urls
324 .iter()
325 .flat_map(|(_, ranges)| ranges)
326 .map(|r| r.length())
327 .sum();
328
329 let url_info =
332 Arc::new(TermBlockRetrievalURLs::new(file_hash, actual_range, acquisition_id, xorb_block_retrieval_urls));
333
334 let xorb_blocks_arc: Vec<Arc<XorbBlock>> = xorb_blocks.into_iter().map(Arc::new).collect();
337
338 let file_terms: Vec<FileTerm> = file_term_data
339 .into_iter()
340 .map(|entry| FileTerm {
341 byte_range: entry.byte_range,
342 xorb_chunk_range: entry.xorb_chunk_range,
343 xorb_block_start_index: entry.xorb_block_start_index,
344 offset_into_first_range: entry.offset_into_first_range,
345 xorb_block: xorb_blocks_arc[entry.xorb_block_index].clone(),
346 url_info: url_info.clone(),
347 })
348 .collect();
349
350 Ok(Some((actual_range, total_transfer_bytes, file_terms)))
351}
352
353#[cfg(test)]
354mod tests {
355 use std::sync::Arc;
356
357 use more_asserts::assert_le;
358 use xet_client::cas_client::{ClientTestingUtils, LocalClient, RandomFileContents};
359 use xet_client::cas_types::{ChunkRange, FileRange};
360 use xet_runtime::core::XetContext;
361 use xet_runtime::utils::UniqueId;
362
363 use super::*;
364
365 const TEST_CHUNK_SIZE: usize = 101;
366
367 fn verify_xorb_block_references(file_terms: &[FileTerm]) {
368 for file_term in file_terms {
369 let refs = &file_term.xorb_block.references;
370 assert!(
371 refs.iter().any(|r| r.term_chunks == file_term.xorb_chunk_range),
372 "xorb_chunk_range {:?} must be in block references {:?}",
373 file_term.xorb_chunk_range,
374 refs.as_slice()
375 );
376 }
377 let mut seen_blocks = std::collections::HashSet::new();
378 for file_term in file_terms {
379 if seen_blocks.insert(file_term.xorb_block.xorb_block_index) {
380 let refs = &file_term.xorb_block.references;
381 for w in refs.windows(2) {
382 assert_le!(w[0].term_chunks.start, w[1].term_chunks.start);
383 }
384 }
385 }
386 }
387
388 async fn setup_test_file(term_spec: &[(u64, (u64, u64))]) -> (XetContext, Arc<LocalClient>, RandomFileContents) {
391 let ctx = XetContext::default().unwrap();
392 let client = LocalClient::temporary(ctx.clone()).await.unwrap();
393 let file_contents = client.upload_random_file(term_spec, TEST_CHUNK_SIZE).await.unwrap();
394 (ctx, client, file_contents)
395 }
396
397 async fn retrieve_and_verify(
409 ctx: &XetContext,
410 client: &Arc<LocalClient>,
411 file_contents: &RandomFileContents,
412 requested_range: Option<FileRange>,
413 ) {
414 let requested_range = requested_range.unwrap_or_else(|| FileRange::new(0, file_contents.data.len() as u64));
415 let dyn_client: Arc<dyn Client> = client.clone();
416
417 let (returned_range, _, file_terms) =
418 retrieve_file_term_block(ctx, dyn_client.clone(), file_contents.file_hash, requested_range)
419 .await
420 .expect("retrieve_file_term_block should succeed")
421 .expect("file_terms should not be None for valid range");
422
423 assert_eq!(returned_range, requested_range);
425
426 let mut current_pos = requested_range.start;
428 let mut file_term_data_offset = 0usize;
429
430 let mut expected_term_idx = 0;
432 let mut byte_offset = 0u64;
433 for (idx, term) in file_contents.terms.iter().enumerate() {
434 let term_end = byte_offset + term.data.len() as u64;
435 if term_end > requested_range.start {
436 expected_term_idx = idx;
437 file_term_data_offset = (requested_range.start - byte_offset) as usize;
438 break;
439 }
440 byte_offset = term_end;
441 }
442
443 let mut seen_xorb_indices = std::collections::HashSet::new();
445
446 let mut reconstructed_data = Vec::with_capacity((requested_range.end - requested_range.start) as usize);
448 let mut term_count = 0;
449
450 for file_term in &file_terms {
451 assert_eq!(file_term.byte_range.start, current_pos);
453 assert!(file_term.byte_range.end > file_term.byte_range.start);
454 assert_le!(file_term.byte_range.end, requested_range.end);
455
456 seen_xorb_indices.insert(file_term.xorb_block.xorb_block_index);
458
459 let xorb_block = &file_term.xorb_block;
462 let term_in_some_range = xorb_block
463 .chunk_ranges
464 .iter()
465 .any(|cr| file_term.xorb_chunk_range.start >= cr.start && file_term.xorb_chunk_range.end <= cr.end);
466 assert!(
467 term_in_some_range,
468 "term chunk range {:?} not within any block chunk range {:?}",
469 file_term.xorb_chunk_range, xorb_block.chunk_ranges
470 );
471
472 if expected_term_idx < file_contents.terms.len() {
474 let expected_term = &file_contents.terms[expected_term_idx];
475
476 assert_eq!(xorb_block.xorb_hash, expected_term.xorb_hash);
478
479 if file_term_data_offset == 0 {
481 assert_eq!(file_term.xorb_chunk_range.start, expected_term.chunk_start);
482 }
483 }
484
485 assert!(file_contents.xorbs.contains_key(&file_term.xorb_block.xorb_hash));
487
488 let data_future = file_term
490 .get_data_task(ctx.clone(), dyn_client.clone(), None, None)
491 .await
492 .unwrap();
493 let data = data_future.await.unwrap();
494
495 let expected_size = (file_term.byte_range.end - file_term.byte_range.start) as usize;
497 assert_eq!(data.len(), expected_size, "Term {term_count} data size mismatch");
498
499 reconstructed_data.extend_from_slice(&data);
500
501 current_pos = file_term.byte_range.end;
502 expected_term_idx += 1;
503 file_term_data_offset = 0;
504 term_count += 1;
505 }
506
507 assert_eq!(current_pos, requested_range.end);
509
510 if requested_range.start == 0 && requested_range.end == file_contents.data.len() as u64 {
512 assert_eq!(term_count, file_contents.terms.len());
513 }
514
515 let expected_data = &file_contents.data[requested_range.start as usize..requested_range.end as usize];
517 assert_eq!(reconstructed_data.len(), expected_data.len());
518 assert_eq!(reconstructed_data, expected_data);
519
520 verify_xorb_block_references(&file_terms);
521 }
522
523 #[tokio::test]
526 async fn test_xorb_block_references_exact() {
527 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (2, 4)), (1, (4, 6))]).await;
528 let file_range = FileRange::new(0, file_contents.data.len() as u64);
529 let dyn_client: Arc<dyn Client> = client.clone();
530 let (_, _, file_terms) = retrieve_file_term_block(&runtime, dyn_client, file_contents.file_hash, file_range)
531 .await
532 .unwrap()
533 .unwrap();
534 verify_xorb_block_references(&file_terms);
535 assert_eq!(file_terms.len(), 3);
536 let block = &file_terms[0].xorb_block;
537 let ref_ranges: Vec<ChunkRange> = block.references.iter().map(|r| r.term_chunks).collect();
538 let expected = vec![ChunkRange::new(0, 2), ChunkRange::new(2, 4), ChunkRange::new(4, 6)];
539 assert_eq!(ref_ranges, expected);
540
541 let (runtime2, client2, file_contents2) = setup_test_file(&[(1, (0, 5)), (1, (0, 5))]).await;
542 let file_range2 = FileRange::new(0, file_contents2.data.len() as u64);
543 let dyn_client2: Arc<dyn Client> = client2.clone();
544 let (_, _, file_terms2) =
545 retrieve_file_term_block(&runtime2, dyn_client2, file_contents2.file_hash, file_range2)
546 .await
547 .unwrap()
548 .unwrap();
549 verify_xorb_block_references(&file_terms2);
550 let block2 = &file_terms2[0].xorb_block;
551 let ref_ranges2: Vec<ChunkRange> = block2.references.iter().map(|r| r.term_chunks).collect();
552 let expected2 = vec![ChunkRange::new(0, 5), ChunkRange::new(0, 5)];
553 assert_eq!(ref_ranges2, expected2);
554 }
555
556 #[tokio::test]
557 async fn test_single_xorb_full_range() {
558 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
559 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
560 }
561
562 #[tokio::test]
563 async fn test_multiple_terms_same_xorb() {
564 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (2, 4)), (1, (4, 6))]).await;
565 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
566 }
567
568 #[tokio::test]
569 async fn test_multiple_xorbs() {
570 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 3)), (2, (0, 2)), (3, (0, 4))]).await;
571 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
572 }
573
574 #[tokio::test]
575 async fn test_overlapping_chunk_ranges() {
576 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5)), (1, (1, 3)), (1, (2, 4))]).await;
577 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
578 }
579
580 #[tokio::test]
581 async fn test_partial_range_middle() {
582 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 10))]).await;
583 let file_len = file_contents.data.len() as u64;
584 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(file_len / 4, file_len * 3 / 4)))
585 .await;
586 }
587
588 #[tokio::test]
589 async fn test_partial_range_start() {
590 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 10))]).await;
591 let file_len = file_contents.data.len() as u64;
592 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(0, file_len / 2))).await;
593 }
594
595 #[tokio::test]
596 async fn test_partial_range_end() {
597 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 10))]).await;
598 let file_len = file_contents.data.len() as u64;
599 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(file_len / 2, file_len))).await;
600 }
601
602 #[tokio::test]
603 async fn test_beyond_file_end() {
604 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 3))]).await;
605 let file_len = file_contents.data.len() as u64;
606 let beyond_range = FileRange::new(file_len + 1000, file_len + 2000);
607
608 let dyn_client: Arc<dyn Client> = client.clone();
609 let result = retrieve_file_term_block(&runtime, dyn_client, file_contents.file_hash, beyond_range).await;
610
611 match result {
612 Ok(None) => {},
613 Ok(Some((_, _, file_terms))) => assert!(file_terms.is_empty()),
614 Err(_) => {},
615 }
616 }
617
618 #[tokio::test]
619 async fn test_interleaved_xorbs() {
620 let (runtime, client, file_contents) =
621 setup_test_file(&[(1, (0, 2)), (2, (0, 2)), (1, (2, 4)), (2, (2, 4))]).await;
622 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
623 }
624
625 #[tokio::test]
626 async fn test_non_contiguous_chunks() {
627 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (4, 6))]).await;
628 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
629 }
630
631 #[tokio::test]
632 async fn test_adjacent_chunks() {
633 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 3)), (1, (3, 5))]).await;
634 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
635 }
636
637 #[tokio::test]
638 async fn test_single_chunk_terms() {
639 let (runtime, client, file_contents) =
640 setup_test_file(&[(1, (0, 1)), (1, (1, 2)), (1, (2, 3)), (2, (0, 1)), (2, (1, 2))]).await;
641 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
642 }
643
644 #[tokio::test]
645 async fn test_large_file_many_xorbs() {
646 let term_spec: Vec<(u64, (u64, u64))> = (1..=10).map(|i| (i, (0, 3))).collect();
647 let (runtime, client, file_contents) = setup_test_file(&term_spec).await;
648 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
649 }
650
651 #[tokio::test]
652 async fn test_xorb_block_deduplication() {
653 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5)), (1, (0, 5))]).await;
654 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
655 }
656
657 #[tokio::test]
658 async fn test_retrieval_url_acquisition() {
659 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
660 let file_range = FileRange::new(0, file_contents.data.len() as u64);
661 let dyn_client: Arc<dyn Client> = client.clone();
662
663 let (_, _, file_terms) = retrieve_file_term_block(&runtime, dyn_client, file_contents.file_hash, file_range)
664 .await
665 .unwrap()
666 .unwrap();
667
668 let file_term = &file_terms[0];
670 let xorb_block_index = file_term.xorb_block.xorb_block_index;
671 let (unique_id, url, http_ranges) = file_term.url_info.get_retrieval_url(xorb_block_index).await;
672
673 assert!(!url.is_empty());
674 assert!(!http_ranges.is_empty());
675 assert!(http_ranges[0].start <= http_ranges[0].end);
676 assert!(unique_id != UniqueId::null());
677 }
678
679 #[tokio::test]
680 async fn test_complex_mixed_pattern() {
681 let term_spec = &[
682 (1, (0, 3)),
683 (2, (0, 2)),
684 (1, (3, 5)),
685 (3, (1, 4)),
686 (2, (4, 6)),
687 (1, (0, 2)),
688 ];
689 let (runtime, client, file_contents) = setup_test_file(term_spec).await;
690 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
691 }
692
693 #[tokio::test]
694 async fn test_repeated_xorb_different_ranges() {
695 let (runtime, client, file_contents) =
696 setup_test_file(&[(1, (0, 2)), (1, (3, 5)), (1, (1, 3)), (1, (4, 6))]).await;
697 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
698 }
699
700 #[tokio::test]
701 async fn test_single_chunk_file() {
702 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 1))]).await;
703 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
704 }
705
706 #[tokio::test]
707 async fn test_many_small_terms_from_different_xorbs() {
708 let term_spec: Vec<(u64, (u64, u64))> = (1..=20).map(|i| (i, (0, 1))).collect();
709 let (runtime, client, file_contents) = setup_test_file(&term_spec).await;
710 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
711 }
712
713 #[tokio::test]
714 async fn test_range_few_bytes_before_end() {
715 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
716 let file_len = file_contents.data.len() as u64;
717
718 let range = FileRange::new(0, file_len - 3);
719 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
720
721 let range = FileRange::new(0, file_len - 1);
722 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
723 }
724
725 #[tokio::test]
726 async fn test_range_few_bytes_after_start() {
727 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
728 let file_len = file_contents.data.len() as u64;
729
730 let range = FileRange::new(3, file_len);
731 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
732
733 let range = FileRange::new(1, file_len);
734 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
735 }
736
737 #[tokio::test]
738 async fn test_range_few_bytes_offset_both_ends() {
739 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
740 let file_len = file_contents.data.len() as u64;
741
742 let range = FileRange::new(2, file_len - 2);
743 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
744
745 let range = FileRange::new(file_len / 2 - 1, file_len / 2 + 1);
746 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
747 }
748
749 #[tokio::test]
750 async fn test_range_single_byte_at_various_positions() {
751 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 5))]).await;
752 let file_len = file_contents.data.len() as u64;
753
754 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(0, 1))).await;
755
756 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(file_len - 1, file_len))).await;
757
758 let mid = file_len / 2;
759 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(mid, mid + 1))).await;
760 }
761
762 #[tokio::test]
763 async fn test_multi_term_range_ends_mid_chunk() {
764 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 3)), (2, (0, 3)), (3, (0, 3))]).await;
765 let file_len = file_contents.data.len() as u64;
766
767 let range = FileRange::new(0, file_len - 5);
768 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
769 }
770
771 #[tokio::test]
772 async fn test_multi_term_range_starts_mid_chunk() {
773 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 3)), (2, (0, 3)), (3, (0, 3))]).await;
774 let file_len = file_contents.data.len() as u64;
775
776 let range = FileRange::new(5, file_len);
777 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
778 }
779
780 #[tokio::test]
785 async fn test_triple_disjoint_same_xorb() {
786 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (4, 6)), (1, (8, 10))]).await;
787 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
788 }
789
790 #[tokio::test]
792 async fn test_triple_disjoint_partial_range_across_gap() {
793 let (runtime, client, file_contents) = setup_test_file(&[(1, (0, 2)), (1, (4, 6)), (1, (8, 10))]).await;
794 let file_len = file_contents.data.len() as u64;
795 let range = FileRange::new(file_len / 4, file_len * 3 / 4);
796 retrieve_and_verify(&runtime, &client, &file_contents, Some(range)).await;
797 }
798
799 #[tokio::test]
801 async fn test_two_xorbs_interleaved_disjoint() {
802 let term_spec = &[(1, (0, 2)), (2, (0, 2)), (1, (4, 6)), (2, (4, 6))];
803 let (runtime, client, file_contents) = setup_test_file(term_spec).await;
804 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
805 }
806
807 #[tokio::test]
809 async fn test_two_xorbs_interleaved_disjoint_partial() {
810 let term_spec = &[(1, (0, 2)), (2, (0, 2)), (1, (4, 6)), (2, (4, 6))];
811 let (runtime, client, file_contents) = setup_test_file(term_spec).await;
812 let file_len = file_contents.data.len() as u64;
813 retrieve_and_verify(&runtime, &client, &file_contents, Some(FileRange::new(file_len / 3, file_len * 2 / 3)))
814 .await;
815 }
816
817 #[tokio::test]
819 async fn test_four_single_chunk_disjoint() {
820 let term_spec = &[(1, (0, 1)), (1, (3, 4)), (1, (6, 7)), (1, (9, 10))];
821 let (runtime, client, file_contents) = setup_test_file(term_spec).await;
822 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
823 }
824
825 #[tokio::test]
828 async fn test_contiguous_then_disjoint() {
829 let term_spec = &[(1, (0, 2)), (1, (2, 4)), (1, (8, 10))];
830 let (runtime, client, file_contents) = setup_test_file(term_spec).await;
831 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
832 }
833
834 #[tokio::test]
836 async fn test_three_xorbs_complex_disjoint() {
837 let term_spec = &[
838 (1, (0, 2)),
839 (2, (0, 3)),
840 (3, (2, 5)),
841 (1, (5, 8)),
842 (2, (6, 8)),
843 (3, (0, 2)),
844 ];
845 let (runtime, client, file_contents) = setup_test_file(term_spec).await;
846 retrieve_and_verify(&runtime, &client, &file_contents, None).await;
847 }
848}