use std::collections::HashMap;
use bytes::Bytes;
use rand::prelude::*;
use xet_core_structures::MerkleHashMap;
use xet_core_structures::merklehash::{MerkleHash, compute_data_hash, file_hash_with_salt};
use xet_core_structures::metadata_shard::file_structs::{FileDataSequenceEntry, FileDataSequenceHeader, MDBFileInfo};
use xet_core_structures::metadata_shard::shard_in_memory::MDBInMemoryShard;
use xet_core_structures::xorb_object::{Chunk, RawXorbData, SerializedXorbObject};
use super::super::interface::Client;
use crate::error::Result;
#[derive(Clone, Debug)]
pub struct FileTermReference {
pub xorb_hash: MerkleHash,
pub chunk_start: u32,
pub chunk_end: u32,
pub data: Bytes,
pub chunk_hashes: Vec<MerkleHash>,
}
#[derive(Clone, Debug)]
pub struct RandomFileContents {
pub file_hash: MerkleHash,
pub data: Bytes,
pub xorbs: MerkleHashMap<RawXorbData>,
pub terms: Vec<FileTermReference>,
}
impl RandomFileContents {
pub fn term_matches(&self, term_index: usize, data: &[u8]) -> bool {
if term_index >= self.terms.len() {
return false;
}
let term = &self.terms[term_index];
term.data == data
}
pub fn term_data(&self, term_index: usize) -> Option<&Bytes> {
self.terms.get(term_index).map(|t| &t.data)
}
pub fn term_xorb_hash(&self, term_index: usize) -> Option<MerkleHash> {
self.terms.get(term_index).map(|t| t.xorb_hash)
}
pub fn term_chunk_range(&self, term_index: usize) -> Option<(u32, u32)> {
self.terms.get(term_index).map(|t| (t.chunk_start, t.chunk_end))
}
}
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
pub trait ClientTestingUtils: Client + Send + Sync {
async fn upload_random_file(
&self,
term_spec: &[(u64, (u64, u64))],
chunk_size: usize,
) -> Result<RandomFileContents> {
let mut xorb_num_chunks = HashMap::<u64, u64>::new();
for &(xorb_seed, (_chunk_idx_start, chunk_idx_end)) in term_spec {
let c: &mut u64 = xorb_num_chunks.entry(xorb_seed).or_default();
*c = (*c).max(chunk_idx_end);
}
let mut shard = MDBInMemoryShard::default();
let mut xorb_data = HashMap::<u64, RawXorbData>::new();
for (&xorb_seed, n_chunks) in xorb_num_chunks.iter() {
let mut rng = SmallRng::seed_from_u64(xorb_seed);
let n_chunks = *n_chunks as usize;
let mut chunks = Vec::with_capacity(n_chunks);
for _idx in 0..n_chunks {
let n = rng.random_range((chunk_size / 2 + 1)..chunk_size);
let n_left = chunk_size - n;
let mut rng_data = vec![0u8; n];
rng.fill_bytes(&mut rng_data);
let mut buf = vec![0u8; chunk_size];
buf[..n].copy_from_slice(&rng_data[..n]);
buf[n..].copy_from_slice(&rng_data[..n_left]);
let hash = compute_data_hash(&buf);
chunks.push(Chunk {
hash,
data: Bytes::from(buf),
});
}
let raw_xorb = RawXorbData::from_chunks(&chunks, vec![0]);
shard.add_xorb_block(raw_xorb.xorb_info.clone())?;
let serialized_xorb = SerializedXorbObject::from_xorb(raw_xorb.clone(), true)?;
let upload_permit = self.acquire_upload_permit().await?;
self.upload_xorb("default", serialized_xorb, None, upload_permit).await?;
xorb_data.insert(xorb_seed, raw_xorb);
}
let mut file_segments = Vec::new();
let mut file_data = Vec::new();
let mut chunk_file_hashes = Vec::new();
let mut term_infos = Vec::new();
for &(xorb_seed, (chunk_idx_start, chunk_idx_end)) in term_spec {
let raw_xorb = xorb_data.get(&xorb_seed).unwrap();
let xorb_h = raw_xorb.hash();
let (c_lb, c_ub) = (chunk_idx_start as usize, chunk_idx_end as usize);
let mut n_bytes = 0;
let mut term_data = Vec::new();
let mut term_chunk_hashes = Vec::new();
for i in c_lb..c_ub {
let chunk_bytes = &raw_xorb.data[i];
let chunk_hash = raw_xorb.xorb_info.chunks[i].chunk_hash;
file_data.extend_from_slice(chunk_bytes);
term_data.extend_from_slice(chunk_bytes);
n_bytes += chunk_bytes.len();
chunk_file_hashes.push((chunk_hash, chunk_bytes.len() as u64));
term_chunk_hashes.push(chunk_hash);
}
file_segments.push(FileDataSequenceEntry::new(
xorb_h,
n_bytes,
chunk_idx_start as usize,
chunk_idx_end as usize,
));
term_infos.push(FileTermReference {
xorb_hash: xorb_h,
chunk_start: chunk_idx_start as u32,
chunk_end: chunk_idx_end as u32,
data: Bytes::from(term_data),
chunk_hashes: term_chunk_hashes,
});
}
let file_hash = file_hash_with_salt(&chunk_file_hashes, &[0; 32]);
shard.add_file_reconstruction_info(MDBFileInfo {
metadata: FileDataSequenceHeader::new(file_hash, file_segments.len(), false, false),
segments: file_segments,
verification: vec![],
metadata_ext: None,
})?;
let upload_permit = self.acquire_upload_permit().await?;
self.upload_shard(shard.to_bytes()?.into(), upload_permit).await?;
let xorbs = xorb_data.into_values().map(|x| (x.hash(), x)).collect();
Ok(RandomFileContents {
file_hash,
data: Bytes::from(file_data),
xorbs,
terms: term_infos,
})
}
}
impl<T: ?Sized + Client + Send + Sync> ClientTestingUtils for T {}