tatara_engine/p2p/
chunk.rs1use serde::{Deserialize, Serialize};
2
3pub const DEFAULT_CHUNK_SIZE: usize = 256 * 1024;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Chunk {
9 pub hash: String,
11 pub data: Vec<u8>,
13 pub size: usize,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ChunkManifest {
20 pub root_hash: String,
22 pub total_size: usize,
24 pub chunks: Vec<String>,
26 pub content_type: String,
28 pub label: String,
30}
31
32impl Chunk {
33 pub fn new(data: Vec<u8>) -> Self {
34 let hash = blake3_hash(&data);
35 let size = data.len();
36 Self { hash, data, size }
37 }
38
39 pub fn verify(&self) -> bool {
40 blake3_hash(&self.data) == self.hash
41 }
42}
43
44impl ChunkManifest {
45 pub fn from_data(data: &[u8], content_type: &str, label: &str) -> (Self, Vec<Chunk>) {
47 let root_hash = blake3_hash(data);
48 let mut chunks = Vec::new();
49 let mut chunk_hashes = Vec::new();
50
51 for chunk_data in data.chunks(DEFAULT_CHUNK_SIZE) {
52 let chunk = Chunk::new(chunk_data.to_vec());
53 chunk_hashes.push(chunk.hash.clone());
54 chunks.push(chunk);
55 }
56
57 let manifest = Self {
58 root_hash,
59 total_size: data.len(),
60 chunks: chunk_hashes,
61 content_type: content_type.to_string(),
62 label: label.to_string(),
63 };
64
65 (manifest, chunks)
66 }
67
68 pub fn reassemble(&self, chunks: &[Chunk]) -> Result<Vec<u8>, String> {
70 if chunks.len() != self.chunks.len() {
71 return Err(format!(
72 "Expected {} chunks, got {}",
73 self.chunks.len(),
74 chunks.len()
75 ));
76 }
77
78 let mut data = Vec::with_capacity(self.total_size);
79
80 for (i, chunk) in chunks.iter().enumerate() {
81 if chunk.hash != self.chunks[i] {
82 return Err(format!(
83 "Chunk {} hash mismatch: expected {}, got {}",
84 i, self.chunks[i], chunk.hash
85 ));
86 }
87 if !chunk.verify() {
88 return Err(format!("Chunk {} content verification failed", i));
89 }
90 data.extend_from_slice(&chunk.data);
91 }
92
93 let actual_hash = blake3_hash(&data);
95 if actual_hash != self.root_hash {
96 return Err(format!(
97 "Root hash mismatch: expected {}, got {}",
98 self.root_hash, actual_hash
99 ));
100 }
101
102 Ok(data)
103 }
104}
105
106fn blake3_hash(data: &[u8]) -> String {
107 let hash = blake3::hash(data);
108 hash.to_hex().to_string()
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_chunk_roundtrip() {
117 let data = b"Hello, tatara! This is some test data for chunking.";
118 let (manifest, chunks) = ChunkManifest::from_data(data, "test", "test-data");
119
120 assert_eq!(manifest.total_size, data.len());
121 assert_eq!(manifest.chunks.len(), 1); let reassembled = manifest.reassemble(&chunks).unwrap();
124 assert_eq!(reassembled, data);
125 }
126
127 #[test]
128 fn test_multi_chunk_roundtrip() {
129 let data: Vec<u8> = (0..DEFAULT_CHUNK_SIZE * 3 + 100)
131 .map(|i| (i % 256) as u8)
132 .collect();
133
134 let (manifest, chunks) = ChunkManifest::from_data(&data, "test", "big-data");
135
136 assert_eq!(manifest.chunks.len(), 4); assert_eq!(manifest.total_size, data.len());
138
139 let reassembled = manifest.reassemble(&chunks).unwrap();
140 assert_eq!(reassembled, data);
141 }
142
143 #[test]
144 fn test_chunk_verification() {
145 let chunk = Chunk::new(b"test data".to_vec());
146 assert!(chunk.verify());
147
148 let mut bad_chunk = chunk.clone();
149 bad_chunk.data = b"tampered".to_vec();
150 assert!(!bad_chunk.verify());
151 }
152}