xet_client/cas_client/simulation/
random_xorb.rs1use bytes::Bytes;
8use rand::prelude::*;
9use xet_core_structures::merklehash::{MerkleHash, compute_data_hash};
10use xet_core_structures::xorb_object::{
11 CompressionScheme, XORB_CHUNK_HEADER_LENGTH, XorbChunkHeader, XorbObject, XorbObjectInfoV1,
12};
13
14#[derive(Clone, Debug)]
16pub struct RandomChunkInfo {
17 pub seed: u64,
19 pub size: u32,
21 pub hash: MerkleHash,
23}
24
25#[derive(Clone, Debug)]
31pub struct RandomXorb {
32 chunks: Vec<RandomChunkInfo>,
34 xorb_object: XorbObject,
36}
37
38impl RandomXorb {
39 pub fn new(chunk_specs: &[(u64, u32)]) -> Self {
44 let chunks: Vec<RandomChunkInfo> = chunk_specs
45 .iter()
46 .map(|&(seed, size)| {
47 let data = Self::generate_chunk_data_from_seed(seed, size);
48 let hash = compute_data_hash(&data);
49 RandomChunkInfo { seed, size, hash }
50 })
51 .collect();
52
53 let xorb_obj = Self::build_xorb_object(&chunks);
55
56 Self {
57 chunks,
58 xorb_object: xorb_obj,
59 }
60 }
61
62 pub fn from_seed(seed: u64, num_chunks: u32, chunk_size: u32) -> Self {
66 use rand::prelude::*;
67
68 let mut rng = SmallRng::seed_from_u64(seed);
69 let chunk_specs: Vec<(u64, u32)> = (0..num_chunks)
70 .map(|_| {
71 let chunk_seed = rng.random::<u64>();
72 (chunk_seed, chunk_size)
73 })
74 .collect();
75
76 Self::new(&chunk_specs)
77 }
78
79 fn build_xorb_object(chunks: &[RandomChunkInfo]) -> XorbObject {
81 let num_chunks = chunks.len() as u32;
82
83 let xorb_hash = if chunks.is_empty() {
85 MerkleHash::default()
86 } else {
87 let mut hash_data = Vec::with_capacity(chunks.len() * 32);
88 for chunk in chunks {
89 hash_data.extend_from_slice(chunk.hash.as_bytes());
90 }
91 compute_data_hash(&hash_data)
92 };
93
94 let chunk_hashes: Vec<MerkleHash> = chunks.iter().map(|c| c.hash).collect();
96
97 let mut chunk_boundary_offsets = Vec::with_capacity(num_chunks as usize);
100 let mut cumulative_offset = 0u32;
101 for chunk in chunks {
102 cumulative_offset += XORB_CHUNK_HEADER_LENGTH as u32 + chunk.size;
103 chunk_boundary_offsets.push(cumulative_offset);
104 }
105
106 let mut unpacked_chunk_offsets = Vec::with_capacity(num_chunks as usize);
108 let mut cumulative_unpacked = 0u32;
109 for chunk in chunks {
110 cumulative_unpacked += chunk.size;
111 unpacked_chunk_offsets.push(cumulative_unpacked);
112 }
113
114 let mut info = XorbObjectInfoV1::default();
116 info.xorb_hash = xorb_hash;
117 info.chunk_hashes = chunk_hashes;
118 info.chunk_boundary_offsets = chunk_boundary_offsets;
119 info.unpacked_chunk_offsets = unpacked_chunk_offsets;
120 info.num_chunks = num_chunks;
121
122 info.fill_in_boundary_offsets();
124
125 let info_length = info.serialized_length() as u32;
126
127 XorbObject { info, info_length }
128 }
129
130 fn generate_chunk_data_from_seed(seed: u64, size: u32) -> Vec<u8> {
132 let mut rng = SmallRng::seed_from_u64(seed);
133 let mut data = vec![0u8; size as usize];
134 rng.fill_bytes(&mut data);
135 data
136 }
137
138 pub fn num_chunks(&self) -> u32 {
140 self.chunks.len() as u32
141 }
142
143 pub fn xorb_hash(&self) -> MerkleHash {
145 self.xorb_object.info.xorb_hash
146 }
147
148 pub fn chunk_hash(&self, idx: u32) -> Option<MerkleHash> {
150 self.chunks.get(idx as usize).map(|c| c.hash)
151 }
152
153 pub fn chunk_size(&self, idx: u32) -> Option<u32> {
155 self.chunks.get(idx as usize).map(|c| c.size)
156 }
157
158 pub fn total_uncompressed_size(&self) -> u64 {
160 self.chunks.iter().map(|c| c.size as u64).sum()
161 }
162
163 pub fn chunk_range_size(&self, start: u32, end: u32) -> u64 {
165 (start..end).filter_map(|i| self.chunk_size(i)).map(|s| s as u64).sum()
166 }
167
168 pub fn chunk_hash_sizes(&self, start: u32, end: u32) -> Vec<(MerkleHash, u64)> {
171 (start..end)
172 .filter_map(|i| {
173 let hash = self.chunk_hash(i)?;
174 let size = self.chunk_size(i)? as u64;
175 Some((hash, size))
176 })
177 .collect()
178 }
179
180 pub fn chunk_hashes_range(&self, start: u32, end: u32) -> Vec<MerkleHash> {
182 (start..end).filter_map(|i| self.chunk_hash(i)).collect()
183 }
184
185 pub fn get_chunk_data(&self, idx: u32) -> Option<Bytes> {
187 self.chunks
188 .get(idx as usize)
189 .map(|chunk| Bytes::from(Self::generate_chunk_data_from_seed(chunk.seed, chunk.size)))
190 }
191
192 pub fn get_chunk_range_data(&self, start: u32, end: u32) -> Option<Bytes> {
194 if start >= end || end > self.num_chunks() {
195 return None;
196 }
197
198 let mut data = Vec::new();
199 for idx in start..end {
200 let chunk = &self.chunks[idx as usize];
201 let chunk_data = Self::generate_chunk_data_from_seed(chunk.seed, chunk.size);
202 data.extend_from_slice(&chunk_data);
203 }
204 Some(Bytes::from(data))
205 }
206
207 pub fn get_xorb_object(&self) -> XorbObject {
211 self.xorb_object.clone()
212 }
213
214 pub fn serialized_length(&self) -> u64 {
216 let chunks_length: u64 = self
217 .chunks
218 .iter()
219 .map(|c| XORB_CHUNK_HEADER_LENGTH as u64 + c.size as u64)
220 .sum();
221
222 let footer_length = self.xorb_object.info.serialized_length() as u64 + 4; chunks_length + footer_length
225 }
226
227 pub fn get_serialized_range(&self, start: u64, end: u64) -> Bytes {
232 let total_len = self.serialized_length();
233 let end = end.min(total_len);
234
235 if start >= end {
236 return Bytes::new();
237 }
238
239 let chunks_length: u64 = self
241 .chunks
242 .iter()
243 .map(|c| XORB_CHUNK_HEADER_LENGTH as u64 + c.size as u64)
244 .sum();
245
246 let mut result = Vec::with_capacity((end - start) as usize);
247
248 let mut pos = 0u64;
250
251 for chunk in &self.chunks {
253 let chunk_serialized_len = XORB_CHUNK_HEADER_LENGTH as u64 + chunk.size as u64;
254 let chunk_end = pos + chunk_serialized_len;
255
256 if chunk_end > start && pos < end {
257 let header = XorbChunkHeader::new(CompressionScheme::None, chunk.size, chunk.size);
259 let header_bytes = header_to_bytes(&header);
260 let chunk_data = Self::generate_chunk_data_from_seed(chunk.seed, chunk.size);
261
262 let mut serialized_chunk = Vec::with_capacity(chunk_serialized_len as usize);
264 serialized_chunk.extend_from_slice(&header_bytes);
265 serialized_chunk.extend_from_slice(&chunk_data);
266
267 let overlap_start = start.saturating_sub(pos) as usize;
269 let overlap_end = ((end - pos) as usize).min(serialized_chunk.len());
270
271 if overlap_start < overlap_end {
272 result.extend_from_slice(&serialized_chunk[overlap_start..overlap_end]);
273 }
274 }
275
276 pos = chunk_end;
277 if pos >= end {
278 break;
279 }
280 }
281
282 if end > chunks_length && pos < end {
284 let mut footer_bytes = Vec::new();
285 self.xorb_object.info.serialize(&mut footer_bytes).unwrap();
286 footer_bytes.extend_from_slice(&self.xorb_object.info_length.to_le_bytes());
287
288 let footer_start_in_stream = chunks_length;
289 let overlap_start = start.saturating_sub(footer_start_in_stream) as usize;
290 let overlap_end = ((end - footer_start_in_stream) as usize).min(footer_bytes.len());
291
292 if overlap_start < overlap_end {
293 result.extend_from_slice(&footer_bytes[overlap_start..overlap_end]);
294 }
295 }
296
297 Bytes::from(result)
298 }
299
300 pub fn get_full_serialized(&self) -> Bytes {
305 self.get_serialized_range(0, self.serialized_length())
306 }
307}
308
309fn header_to_bytes(header: &XorbChunkHeader) -> [u8; XORB_CHUNK_HEADER_LENGTH] {
311 let mut bytes = [0u8; XORB_CHUNK_HEADER_LENGTH];
312 bytes[0] = 0; bytes[1..4].copy_from_slice(&header.get_compressed_length().to_le_bytes()[..3]);
314 bytes[4] = CompressionScheme::None as u8;
315 bytes[5..8].copy_from_slice(&header.get_uncompressed_length().to_le_bytes()[..3]);
316 bytes
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_random_xorb_basic() {
325 let specs = vec![(42, 1024), (123, 2048), (456, 512)];
326 let xorb = RandomXorb::new(&specs);
327
328 assert_eq!(xorb.num_chunks(), 3);
329 assert!(xorb.chunk_hash(0).is_some());
330 assert!(xorb.chunk_hash(1).is_some());
331 assert!(xorb.chunk_hash(2).is_some());
332 assert!(xorb.chunk_hash(3).is_none());
333
334 let data1 = xorb.get_chunk_data(0).unwrap();
336 let data2 = xorb.get_chunk_data(0).unwrap();
337 assert_eq!(data1, data2);
338 assert_eq!(data1.len(), 1024);
339 }
340
341 #[test]
342 fn test_random_xorb_object() {
343 let specs = vec![(1, 100), (2, 200)];
344 let xorb = RandomXorb::new(&specs);
345
346 let xorb_obj = xorb.get_xorb_object();
347 assert_eq!(xorb_obj.info.num_chunks, 2);
348 assert_eq!(xorb_obj.info.chunk_hashes.len(), 2);
349 assert_eq!(xorb_obj.info.chunk_boundary_offsets.len(), 2);
350 assert_eq!(xorb_obj.info.unpacked_chunk_offsets.len(), 2);
351
352 assert_eq!(xorb_obj.info.chunk_boundary_offsets[0], 108);
354 assert_eq!(xorb_obj.info.chunk_boundary_offsets[1], 316);
356
357 assert_eq!(xorb_obj.info.unpacked_chunk_offsets[0], 100);
359 assert_eq!(xorb_obj.info.unpacked_chunk_offsets[1], 300);
360 }
361
362 #[test]
363 fn test_random_xorb_chunk_range() {
364 let specs = vec![(1, 100), (2, 200), (3, 300)];
365 let xorb = RandomXorb::new(&specs);
366
367 let range_data = xorb.get_chunk_range_data(0, 2).unwrap();
368 assert_eq!(range_data.len(), 300); let chunk0 = xorb.get_chunk_data(0).unwrap();
371 let chunk1 = xorb.get_chunk_data(1).unwrap();
372 assert_eq!(&range_data[..100], &chunk0[..]);
373 assert_eq!(&range_data[100..], &chunk1[..]);
374 }
375
376 #[test]
377 fn test_random_xorb_serialized_length() {
378 let specs = vec![(1, 100)];
379 let xorb = RandomXorb::new(&specs);
380
381 let serialized = xorb.get_full_serialized();
382 assert_eq!(serialized.len() as u64, xorb.serialized_length());
383 }
384}