1use std::collections::HashMap;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum ChunkState {
11 Pending,
13 Written,
15 Verified,
17 Failed,
19}
20
21#[derive(Clone, Debug)]
23pub struct Chunk {
24 pub chunk_id: u64,
26 pub object_id: u64,
28 pub chunk_index: u32,
30 pub offset: u64,
32 pub size_bytes: u64,
34 pub state: ChunkState,
36 pub checksum: u64,
38}
39
40#[derive(Clone, Debug)]
42pub struct ChunkedObject {
43 pub object_id: u64,
45 pub total_size_bytes: u64,
47 pub chunk_size_bytes: u64,
49 pub chunks: Vec<Chunk>,
51}
52
53impl ChunkedObject {
54 pub fn total_chunks(&self) -> usize {
56 self.chunks.len()
57 }
58
59 pub fn written_chunks(&self) -> usize {
61 self.chunks
62 .iter()
63 .filter(|c| matches!(c.state, ChunkState::Written | ChunkState::Verified))
64 .count()
65 }
66
67 pub fn is_complete(&self) -> bool {
69 !self.chunks.is_empty()
70 && self
71 .chunks
72 .iter()
73 .all(|c| matches!(c.state, ChunkState::Written | ChunkState::Verified))
74 }
75
76 pub fn completion_pct(&self) -> f64 {
78 let total = self.total_chunks();
79 if total == 0 {
80 return 0.0;
81 }
82 (self.written_chunks() as f64 / total as f64) * 100.0
83 }
84}
85
86#[derive(Clone, Debug, Default)]
88pub struct ChunkManagerStats {
89 pub total_objects: usize,
91 pub total_chunks: usize,
93 pub written_chunks: usize,
95 pub failed_chunks: usize,
97 pub total_bytes: u64,
99}
100
101pub fn fnv1a_u64(value: u64) -> u64 {
103 const FNV_OFFSET_BASIS: u64 = 14_695_981_039_346_656_037;
104 const FNV_PRIME: u64 = 1_099_511_628_211;
105
106 let bytes = value.to_le_bytes();
107 let mut hash = FNV_OFFSET_BASIS;
108 for &byte in &bytes {
109 hash ^= byte as u64;
110 hash = hash.wrapping_mul(FNV_PRIME);
111 }
112 hash
113}
114
115pub struct StorageChunkManager {
118 pub objects: HashMap<u64, ChunkedObject>,
120 pub next_object_id: u64,
122 pub chunk_size_bytes: u64,
124}
125
126impl StorageChunkManager {
127 pub fn new(chunk_size_bytes: u64) -> Self {
129 Self {
130 objects: HashMap::new(),
131 next_object_id: 1,
132 chunk_size_bytes,
133 }
134 }
135
136 pub fn create_object(&mut self, total_size_bytes: u64) -> u64 {
140 let object_id = self.next_object_id;
141 self.next_object_id += 1;
142
143 let chunk_size = self.chunk_size_bytes;
144 let num_chunks = total_size_bytes.div_ceil(chunk_size).max(1);
145
146 let mut chunks = Vec::with_capacity(num_chunks as usize);
147 let mut remaining = total_size_bytes;
148
149 for chunk_index in 0..num_chunks {
150 let offset = chunk_index * chunk_size;
151 let size = remaining.min(chunk_size);
152 remaining = remaining.saturating_sub(size);
153
154 let checksum_input = object_id ^ chunk_index;
155 let checksum = fnv1a_u64(checksum_input);
156
157 let chunk_id = object_id.wrapping_shl(32) | (chunk_index & 0xFFFF_FFFF);
159
160 chunks.push(Chunk {
161 chunk_id,
162 object_id,
163 chunk_index: chunk_index as u32,
164 offset,
165 size_bytes: size,
166 state: ChunkState::Pending,
167 checksum,
168 });
169 }
170
171 self.objects.insert(
172 object_id,
173 ChunkedObject {
174 object_id,
175 total_size_bytes,
176 chunk_size_bytes: chunk_size,
177 chunks,
178 },
179 );
180
181 object_id
182 }
183
184 pub fn mark_written(&mut self, object_id: u64, chunk_index: u32) -> bool {
186 self.set_chunk_state(object_id, chunk_index, ChunkState::Written)
187 }
188
189 pub fn mark_verified(&mut self, object_id: u64, chunk_index: u32) -> bool {
191 self.set_chunk_state(object_id, chunk_index, ChunkState::Verified)
192 }
193
194 pub fn mark_failed(&mut self, object_id: u64, chunk_index: u32) -> bool {
196 self.set_chunk_state(object_id, chunk_index, ChunkState::Failed)
197 }
198
199 pub fn get_object(&self, object_id: u64) -> Option<&ChunkedObject> {
201 self.objects.get(&object_id)
202 }
203
204 pub fn pending_chunks(&self, object_id: u64) -> Vec<&Chunk> {
206 match self.objects.get(&object_id) {
207 None => Vec::new(),
208 Some(obj) => {
209 let mut pending: Vec<&Chunk> = obj
210 .chunks
211 .iter()
212 .filter(|c| c.state == ChunkState::Pending)
213 .collect();
214 pending.sort_by_key(|c| c.chunk_index);
215 pending
216 }
217 }
218 }
219
220 pub fn delete_object(&mut self, object_id: u64) -> bool {
222 self.objects.remove(&object_id).is_some()
223 }
224
225 pub fn stats(&self) -> ChunkManagerStats {
227 let mut stats = ChunkManagerStats {
228 total_objects: self.objects.len(),
229 ..Default::default()
230 };
231
232 for obj in self.objects.values() {
233 stats.total_chunks += obj.total_chunks();
234 stats.written_chunks += obj
235 .chunks
236 .iter()
237 .filter(|c| matches!(c.state, ChunkState::Written | ChunkState::Verified))
238 .count();
239 stats.failed_chunks += obj
240 .chunks
241 .iter()
242 .filter(|c| c.state == ChunkState::Failed)
243 .count();
244 stats.total_bytes += obj.total_size_bytes;
245 }
246
247 stats
248 }
249
250 fn set_chunk_state(&mut self, object_id: u64, chunk_index: u32, state: ChunkState) -> bool {
253 match self.objects.get_mut(&object_id) {
254 None => false,
255 Some(obj) => match obj.chunks.iter_mut().find(|c| c.chunk_index == chunk_index) {
256 None => false,
257 Some(chunk) => {
258 chunk.state = state;
259 true
260 }
261 },
262 }
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
273 fn test_new_with_chunk_size() {
274 let mgr = StorageChunkManager::new(512);
275 assert_eq!(mgr.chunk_size_bytes, 512);
276 assert!(mgr.objects.is_empty());
277 }
278
279 #[test]
280 fn test_new_default_chunk_size_constant() {
281 let mgr = StorageChunkManager::new(1_048_576);
282 assert_eq!(mgr.chunk_size_bytes, 1_048_576);
283 }
284
285 #[test]
288 fn test_create_object_returns_monotonic_ids() {
289 let mut mgr = StorageChunkManager::new(1024);
290 let id1 = mgr.create_object(1024);
291 let id2 = mgr.create_object(1024);
292 let id3 = mgr.create_object(1024);
293 assert!(id1 < id2);
294 assert!(id2 < id3);
295 }
296
297 #[test]
300 fn test_create_object_exact_chunk_count() {
301 let chunk_size = 1024_u64;
302 let mut mgr = StorageChunkManager::new(chunk_size);
303 let id = mgr.create_object(chunk_size * 4);
304 let obj = mgr.get_object(id).expect("object should exist");
305 assert_eq!(obj.total_chunks(), 4);
306 }
307
308 #[test]
311 fn test_create_object_partial_last_chunk_count() {
312 let chunk_size = 1024_u64;
313 let mut mgr = StorageChunkManager::new(chunk_size);
314 let id = mgr.create_object(chunk_size * 3 + 1);
315 let obj = mgr.get_object(id).expect("object should exist");
316 assert_eq!(obj.total_chunks(), 4);
317 }
318
319 #[test]
322 fn test_last_chunk_has_correct_smaller_size() {
323 let chunk_size = 1024_u64;
324 let remainder = 300_u64;
325 let mut mgr = StorageChunkManager::new(chunk_size);
326 let id = mgr.create_object(chunk_size * 2 + remainder);
327 let obj = mgr.get_object(id).expect("object should exist");
328 assert_eq!(obj.total_chunks(), 3);
329 let last = obj.chunks.last().expect("must have last chunk");
330 assert_eq!(last.size_bytes, remainder);
331 }
332
333 #[test]
336 fn test_chunk_offsets_are_sequential() {
337 let chunk_size = 512_u64;
338 let mut mgr = StorageChunkManager::new(chunk_size);
339 let id = mgr.create_object(chunk_size * 5);
340 let obj = mgr.get_object(id).expect("object should exist");
341 for (i, chunk) in obj.chunks.iter().enumerate() {
342 assert_eq!(chunk.offset, i as u64 * chunk_size);
343 }
344 }
345
346 #[test]
349 fn test_chunk_checksums_computed() {
350 let mut mgr = StorageChunkManager::new(256);
351 let id = mgr.create_object(256 * 3);
352 let obj = mgr.get_object(id).expect("object should exist");
353 for chunk in &obj.chunks {
354 let expected = fnv1a_u64(chunk.object_id ^ (chunk.chunk_index as u64));
355 assert_eq!(chunk.checksum, expected);
356 }
357 }
358
359 #[test]
362 fn test_mark_written_sets_written_state() {
363 let mut mgr = StorageChunkManager::new(1024);
364 let id = mgr.create_object(2048);
365 assert!(mgr.mark_written(id, 0));
366 let obj = mgr.get_object(id).expect("object should exist");
367 assert_eq!(obj.chunks[0].state, ChunkState::Written);
368 }
369
370 #[test]
371 fn test_mark_written_returns_false_for_unknown_object() {
372 let mut mgr = StorageChunkManager::new(1024);
373 assert!(!mgr.mark_written(999, 0));
374 }
375
376 #[test]
377 fn test_mark_written_returns_false_for_unknown_chunk_index() {
378 let mut mgr = StorageChunkManager::new(1024);
379 let id = mgr.create_object(1024);
380 assert!(!mgr.mark_written(id, 99));
381 }
382
383 #[test]
386 fn test_mark_verified_sets_verified_state() {
387 let mut mgr = StorageChunkManager::new(1024);
388 let id = mgr.create_object(2048);
389 assert!(mgr.mark_verified(id, 1));
390 let obj = mgr.get_object(id).expect("object should exist");
391 assert_eq!(obj.chunks[1].state, ChunkState::Verified);
392 }
393
394 #[test]
397 fn test_mark_failed_sets_failed_state() {
398 let mut mgr = StorageChunkManager::new(1024);
399 let id = mgr.create_object(2048);
400 assert!(mgr.mark_failed(id, 0));
401 let obj = mgr.get_object(id).expect("object should exist");
402 assert_eq!(obj.chunks[0].state, ChunkState::Failed);
403 }
404
405 #[test]
408 fn test_is_complete_true_when_all_written() {
409 let mut mgr = StorageChunkManager::new(1024);
410 let id = mgr.create_object(2048);
411 mgr.mark_written(id, 0);
412 mgr.mark_written(id, 1);
413 let obj = mgr.get_object(id).expect("object should exist");
414 assert!(obj.is_complete());
415 }
416
417 #[test]
418 fn test_is_complete_true_when_all_verified() {
419 let mut mgr = StorageChunkManager::new(1024);
420 let id = mgr.create_object(2048);
421 mgr.mark_verified(id, 0);
422 mgr.mark_verified(id, 1);
423 let obj = mgr.get_object(id).expect("object should exist");
424 assert!(obj.is_complete());
425 }
426
427 #[test]
428 fn test_is_complete_true_mixed_written_verified() {
429 let mut mgr = StorageChunkManager::new(1024);
430 let id = mgr.create_object(2048);
431 mgr.mark_written(id, 0);
432 mgr.mark_verified(id, 1);
433 let obj = mgr.get_object(id).expect("object should exist");
434 assert!(obj.is_complete());
435 }
436
437 #[test]
438 fn test_is_complete_false_when_pending_remain() {
439 let mut mgr = StorageChunkManager::new(1024);
440 let id = mgr.create_object(2048);
441 mgr.mark_written(id, 0);
442 let obj = mgr.get_object(id).expect("object should exist");
444 assert!(!obj.is_complete());
445 }
446
447 #[test]
450 fn test_completion_pct_correct() {
451 let mut mgr = StorageChunkManager::new(1024);
452 let id = mgr.create_object(4096); mgr.mark_written(id, 0);
454 mgr.mark_verified(id, 1);
455 let obj = mgr.get_object(id).expect("object should exist");
456 let pct = obj.completion_pct();
457 assert!((pct - 50.0).abs() < f64::EPSILON);
458 }
459
460 #[test]
461 fn test_completion_pct_zero_when_empty_chunks() {
462 let obj = ChunkedObject {
463 object_id: 1,
464 total_size_bytes: 0,
465 chunk_size_bytes: 1024,
466 chunks: Vec::new(),
467 };
468 assert_eq!(obj.completion_pct(), 0.0);
469 }
470
471 #[test]
474 fn test_written_chunks_counts_written_and_verified() {
475 let mut mgr = StorageChunkManager::new(512);
476 let id = mgr.create_object(512 * 4);
477 mgr.mark_written(id, 0);
478 mgr.mark_verified(id, 2);
479 mgr.mark_failed(id, 3);
480 let obj = mgr.get_object(id).expect("object should exist");
481 assert_eq!(obj.written_chunks(), 2);
482 }
483
484 #[test]
487 fn test_pending_chunks_returns_only_pending_sorted() {
488 let mut mgr = StorageChunkManager::new(512);
489 let id = mgr.create_object(512 * 4);
490 mgr.mark_written(id, 1);
491 mgr.mark_verified(id, 3);
492 let pending = mgr.pending_chunks(id);
493 assert_eq!(pending.len(), 2);
494 assert_eq!(pending[0].chunk_index, 0);
495 assert_eq!(pending[1].chunk_index, 2);
496 }
497
498 #[test]
499 fn test_pending_chunks_empty_for_unknown_object() {
500 let mgr = StorageChunkManager::new(512);
501 let pending = mgr.pending_chunks(999);
502 assert!(pending.is_empty());
503 }
504
505 #[test]
508 fn test_get_object_returns_some_for_existing() {
509 let mut mgr = StorageChunkManager::new(1024);
510 let id = mgr.create_object(1024);
511 assert!(mgr.get_object(id).is_some());
512 }
513
514 #[test]
515 fn test_get_object_returns_none_for_missing() {
516 let mgr = StorageChunkManager::new(1024);
517 assert!(mgr.get_object(42).is_none());
518 }
519
520 #[test]
523 fn test_delete_object_returns_true_for_existing() {
524 let mut mgr = StorageChunkManager::new(1024);
525 let id = mgr.create_object(1024);
526 assert!(mgr.delete_object(id));
527 assert!(mgr.get_object(id).is_none());
528 }
529
530 #[test]
531 fn test_delete_object_returns_false_for_missing() {
532 let mut mgr = StorageChunkManager::new(1024);
533 assert!(!mgr.delete_object(999));
534 }
535
536 #[test]
539 fn test_stats_total_objects_and_chunks() {
540 let mut mgr = StorageChunkManager::new(1024);
541 mgr.create_object(2048); mgr.create_object(4096); let s = mgr.stats();
544 assert_eq!(s.total_objects, 2);
545 assert_eq!(s.total_chunks, 6);
546 }
547
548 #[test]
549 fn test_stats_written_and_failed_chunks() {
550 let mut mgr = StorageChunkManager::new(1024);
551 let id = mgr.create_object(4096); mgr.mark_written(id, 0);
553 mgr.mark_verified(id, 1);
554 mgr.mark_failed(id, 2);
555 let s = mgr.stats();
556 assert_eq!(s.written_chunks, 2);
557 assert_eq!(s.failed_chunks, 1);
558 }
559
560 #[test]
561 fn test_stats_total_bytes() {
562 let mut mgr = StorageChunkManager::new(1024);
563 mgr.create_object(2000);
564 mgr.create_object(3000);
565 let s = mgr.stats();
566 assert_eq!(s.total_bytes, 5000);
567 }
568
569 #[test]
572 fn test_fnv1a_u64_deterministic() {
573 let h1 = fnv1a_u64(42);
574 let h2 = fnv1a_u64(42);
575 assert_eq!(h1, h2);
576 }
577
578 #[test]
579 fn test_fnv1a_u64_different_inputs_produce_different_hashes() {
580 let h1 = fnv1a_u64(0);
581 let h2 = fnv1a_u64(1);
582 assert_ne!(h1, h2);
583 }
584}