1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_possible_wrap,
4 clippy::cast_sign_loss,
5 reason = "M175: BEP 52 Merkle tree — base/index/count widths fixed by spec; piece arithmetic bounded by num_pieces (u32)"
6)]
7
8use std::time::Instant;
20
21use crate::hash_request::HashRequest;
22use crate::merkle_state::{MerkleTreeState, SetBlockResult};
23use crate::{Id32, MerkleTree};
24
25#[derive(Debug, Clone)]
27pub struct FileHashInfo {
28 pub root: Id32,
30 pub num_blocks: u32,
32 pub num_pieces: u32,
34}
35
36#[derive(Debug)]
38pub struct AddHashesResult {
39 pub valid: bool,
41 pub hash_passed: Vec<u32>,
43 pub hash_failed: Vec<u32>,
45}
46
47struct PieceHashRequest {
48 last_request: Option<Instant>,
49 have: bool,
50}
51
52struct BlockHashRequest {
53 file_index: usize,
54 piece: u32,
55}
56
57pub struct HashPicker {
62 pub(crate) trees: Vec<MerkleTreeState>,
63 file_roots: Vec<Id32>,
64 piece_requests: Vec<Vec<PieceHashRequest>>,
65 block_requests: Vec<BlockHashRequest>,
66 piece_layer: u32,
67 blocks_per_piece: u32,
68}
69
70impl HashPicker {
71 #[must_use]
75 pub fn new(files: &[FileHashInfo], blocks_per_piece: u32) -> Self {
76 let piece_layer = blocks_per_piece.trailing_zeros();
77 let trees: Vec<_> = files
78 .iter()
79 .map(|f| MerkleTreeState::new(f.root, f.num_blocks, f.num_pieces))
80 .collect();
81
82 let piece_requests: Vec<Vec<PieceHashRequest>> = files
83 .iter()
84 .map(|f| {
85 let chunks = f.num_pieces.div_ceil(512) as usize;
86 (0..chunks)
87 .map(|_| PieceHashRequest {
88 last_request: None,
89 have: false,
90 })
91 .collect()
92 })
93 .collect();
94
95 let file_roots = files.iter().map(|f| f.root).collect();
96
97 Self {
98 trees,
99 file_roots,
100 piece_requests,
101 block_requests: Vec::new(),
102 piece_layer,
103 blocks_per_piece,
104 }
105 }
106
107 pub fn pick_hashes(&self, has_piece: impl Fn(u32) -> bool) -> Option<HashRequest> {
112 if let Some(req) = self.block_requests.first() {
114 let first_block = req.piece * self.blocks_per_piece;
115 return Some(HashRequest {
116 file_root: self.file_roots[req.file_index],
117 base: 0,
118 index: first_block,
119 count: self.blocks_per_piece,
120 proof_layers: self.piece_layer + 1,
121 });
122 }
123
124 for (file_idx, requests) in self.piece_requests.iter().enumerate() {
126 for (chunk_idx, req) in requests.iter().enumerate() {
127 if req.have {
128 continue;
129 }
130
131 let first_piece = chunk_idx as u32 * 512;
133 let has_relevant = (first_piece..)
134 .take(512)
135 .take_while(|&p| p < self.trees[file_idx].num_pieces())
136 .any(&has_piece);
137
138 if !has_relevant {
139 continue;
140 }
141
142 let remaining = self.trees[file_idx]
143 .num_pieces()
144 .saturating_sub(first_piece);
145 let count = 512.min(remaining.next_power_of_two());
146
147 let tree_depth = self.trees[file_idx]
149 .num_blocks()
150 .next_power_of_two()
151 .trailing_zeros();
152 let proof_layers = tree_depth.saturating_sub(self.piece_layer);
153
154 return Some(HashRequest {
155 file_root: self.file_roots[file_idx],
156 base: self.piece_layer,
157 index: first_piece,
158 count,
159 proof_layers,
160 });
161 }
162 }
163
164 None
165 }
166
167 pub fn add_hashes(
177 &mut self,
178 req: &HashRequest,
179 hashes: &[Id32],
180 ) -> Result<AddHashesResult, crate::Error> {
181 let file_idx = self
182 .file_roots
183 .iter()
184 .position(|r| *r == req.file_root)
185 .ok_or_else(|| crate::Error::InvalidTorrent("unknown file root".into()))?;
186
187 if req.base == self.piece_layer {
188 let base_count = req.count as usize;
190
191 if hashes.len() < base_count {
192 return Ok(AddHashesResult {
193 valid: false,
194 hash_passed: Vec::new(),
195 hash_failed: Vec::new(),
196 });
197 }
198
199 let base_hashes = &hashes[..base_count];
200 let uncle_hashes = &hashes[base_count..];
201
202 if req.proof_layers > 0 && !uncle_hashes.is_empty() {
204 let sub_root = MerkleTree::root_from_hashes(base_hashes);
205 let leaf_index = req.index as usize / base_count;
206 if !MerkleTree::verify_proof(
207 self.trees[file_idx].root(),
208 sub_root,
209 leaf_index,
210 uncle_hashes,
211 ) {
212 return Ok(AddHashesResult {
213 valid: false,
214 hash_passed: Vec::new(),
215 hash_failed: Vec::new(),
216 });
217 }
218 }
219
220 let verified = self.trees[file_idx]
222 .set_piece_hashes_and_verify(base_hashes.to_vec(), self.blocks_per_piece);
223
224 let chunk_idx = req.index as usize / 512;
226 if chunk_idx < self.piece_requests[file_idx].len() {
227 self.piece_requests[file_idx][chunk_idx].have = true;
228 }
229
230 Ok(AddHashesResult {
231 valid: true,
232 hash_passed: verified,
233 hash_failed: Vec::new(),
234 })
235 } else {
236 let mut hash_passed = Vec::new();
238 let mut hash_failed = Vec::new();
239
240 for (i, hash) in hashes.iter().enumerate() {
241 let block_index = req.index + i as u32;
242 match self.trees[file_idx].set_block_hash(block_index, *hash) {
243 SetBlockResult::Ok => {
244 let piece = block_index / self.blocks_per_piece;
245 if !hash_passed.contains(&piece) {
246 hash_passed.push(piece);
247 }
248 }
249 SetBlockResult::HashFailed => {
250 let piece = block_index / self.blocks_per_piece;
251 if !hash_failed.contains(&piece) {
252 hash_failed.push(piece);
253 }
254 }
255 SetBlockResult::Unknown => {}
256 }
257 }
258
259 self.block_requests.retain(|r| {
261 r.file_index != file_idx || r.piece != req.index / self.blocks_per_piece
262 });
263
264 Ok(AddHashesResult {
265 valid: true,
266 hash_passed,
267 hash_failed,
268 })
269 }
270 }
271
272 pub fn set_block_hash(
276 &mut self,
277 file_index: usize,
278 block_index: u32,
279 hash: Id32,
280 ) -> SetBlockResult {
281 if file_index >= self.trees.len() {
282 return SetBlockResult::HashFailed;
283 }
284 self.trees[file_index].set_block_hash(block_index, hash)
285 }
286
287 pub fn verify_block_hashes(&mut self, piece: u32) {
289 let file_index = 0;
291 if self.trees.is_empty() {
292 return;
293 }
294
295 let existing = self
296 .block_requests
297 .iter()
298 .any(|r| r.file_index == file_index && r.piece == piece);
299
300 if !existing {
301 self.block_requests
302 .push(BlockHashRequest { file_index, piece });
303 }
304 }
305
306 pub fn hashes_rejected(&mut self, req: &HashRequest) {
308 if req.base == self.piece_layer {
309 let chunk_idx = req.index as usize / 512;
310 let file_idx = self.file_roots.iter().position(|r| *r == req.file_root);
311 if let Some(fi) = file_idx
312 && chunk_idx < self.piece_requests[fi].len()
313 {
314 self.piece_requests[fi][chunk_idx].last_request = None;
315 }
316 }
317 }
318
319 #[must_use]
321 pub fn have_piece_hash(&self, file_index: usize, piece: u32) -> bool {
322 self.trees
323 .get(file_index)
324 .is_some_and(|t| t.has_piece_hash(piece))
325 }
326
327 #[must_use]
329 pub fn piece_verified(&self, file_index: usize, piece: u32) -> bool {
330 self.trees
331 .get(file_index)
332 .is_some_and(|t| t.piece_verified(piece, self.blocks_per_piece))
333 }
334
335 #[must_use]
337 pub fn tree_depth(&self, file_index: usize) -> Option<u32> {
338 self.trees
339 .get(file_index)
340 .map(|t| t.num_blocks().next_power_of_two().trailing_zeros())
341 }
342
343 pub fn load_piece_layers(
352 &mut self,
353 piece_layers: &std::collections::BTreeMap<Id32, Vec<u8>>,
354 ) -> Vec<u32> {
355 let mut verified = Vec::new();
356 for (file_idx, root) in self.file_roots.iter().enumerate() {
357 let Some(layer_bytes) = piece_layers.get(root) else {
358 continue;
359 };
360 if layer_bytes.len() % 32 != 0 {
362 continue;
363 }
364 let hashes: Vec<Id32> = layer_bytes
365 .chunks_exact(32)
366 .map(|chunk| {
367 let mut arr = [0u8; 32];
368 arr.copy_from_slice(chunk);
369 Id32(arr)
370 })
371 .collect();
372
373 let num_chunks = self.piece_requests.get(file_idx).map_or(0, Vec::len);
375 for chunk_idx in 0..num_chunks {
376 self.piece_requests[file_idx][chunk_idx].have = true;
377 }
378
379 if let Some(tree) = self.trees.get_mut(file_idx) {
381 let v = tree.set_piece_hashes_and_verify(hashes, self.blocks_per_piece);
382 verified.extend(v);
383 }
384 }
385 verified
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::{Id32, MerkleTree, sha256};
393
394 fn make_file_info(root: Id32, num_blocks: u32, num_pieces: u32) -> FileHashInfo {
395 FileHashInfo {
396 root,
397 num_blocks,
398 num_pieces,
399 }
400 }
401
402 fn make_block_hashes(n: usize) -> Vec<Id32> {
403 (0..n).map(|i| sha256(&[i as u8])).collect()
404 }
405
406 #[test]
407 fn pick_piece_layer_first() {
408 let root = sha256(b"file1");
409 let files = vec![make_file_info(root, 1024, 64)];
410 let picker = HashPicker::new(&files, 16); let req = picker.pick_hashes(|p| p == 0);
413 assert!(req.is_some());
414 let req = req.unwrap();
415 assert_eq!(req.file_root, root);
416 assert_eq!(req.base, 4); }
419
420 #[test]
421 fn pick_returns_none_when_complete() {
422 let block_hashes = make_block_hashes(4);
423 let tree = MerkleTree::from_leaves(&block_hashes);
424 let root = tree.root();
425 let files = vec![make_file_info(root, 4, 2)];
426 let mut picker = HashPicker::new(&files, 2);
427
428 let pieces = tree.piece_layer(2).to_vec();
430 picker.trees[0].set_piece_hashes(pieces);
431
432 for (i, h) in block_hashes.iter().enumerate() {
434 picker.trees[0].set_block_hash(i as u32, *h);
435 }
436
437 picker.piece_requests[0][0].have = true;
439
440 assert!(picker.pick_hashes(|_| true).is_none());
441 }
442
443 #[test]
444 fn add_hashes_populates_tree() {
445 let block_hashes = make_block_hashes(4);
446 let tree = MerkleTree::from_leaves(&block_hashes);
447 let root = tree.root();
448 let pieces = tree.piece_layer(2).to_vec();
449 let files = vec![make_file_info(root, 4, 2)];
450 let mut picker = HashPicker::new(&files, 2);
451
452 assert!(!picker.have_piece_hash(0, 0));
453
454 let req = HashRequest {
455 file_root: root,
456 base: 1, index: 0,
458 count: 2,
459 proof_layers: 0,
460 };
461 let result = picker.add_hashes(&req, &pieces);
462 assert!(result.is_ok());
463 assert!(result.unwrap().valid);
464 assert!(picker.have_piece_hash(0, 0));
465 assert!(picker.have_piece_hash(0, 1));
466 }
467
468 #[test]
469 fn set_block_hash_delegates_to_tree() {
470 let block_hashes = make_block_hashes(4);
471 let tree = MerkleTree::from_leaves(&block_hashes);
472 let root = tree.root();
473 let pieces = tree.piece_layer(2).to_vec();
474 let files = vec![make_file_info(root, 4, 2)];
475 let mut picker = HashPicker::new(&files, 2);
476
477 picker.trees[0].set_piece_hashes(pieces);
479
480 let r0 = picker.set_block_hash(0, 0, block_hashes[0]);
482 assert_eq!(r0, SetBlockResult::Unknown);
483
484 let r1 = picker.set_block_hash(0, 1, block_hashes[1]);
485 assert_eq!(r1, SetBlockResult::Ok);
486
487 assert!(picker.piece_verified(0, 0));
488 }
489
490 #[test]
491 fn verify_block_hashes_adds_request() {
492 let root = sha256(b"file1");
493 let files = vec![make_file_info(root, 1024, 64)];
494 let mut picker = HashPicker::new(&files, 16);
495
496 picker.verify_block_hashes(5);
498
499 let req = picker.pick_hashes(|_| true);
500 assert!(req.is_some());
501 let req = req.unwrap();
502 assert_eq!(req.base, 0);
504 }
505
506 #[test]
507 fn add_hashes_with_valid_proof_accepted() {
508 let block_hashes = make_block_hashes(8);
510 let tree = MerkleTree::from_leaves(&block_hashes);
511 let root = tree.root();
512 let piece_hashes = tree.piece_layer(2).to_vec();
513
514 let files = vec![make_file_info(root, 8, 4)];
515 let mut picker = HashPicker::new(&files, 2);
516
517 let uncle = MerkleTree::root_from_hashes(&piece_hashes[2..]);
522
523 let mut hashes_with_proof = piece_hashes[0..2].to_vec();
524 hashes_with_proof.push(uncle);
525
526 let req = HashRequest {
527 file_root: root,
528 base: 1, index: 0,
530 count: 2,
531 proof_layers: 1,
532 };
533 let result = picker.add_hashes(&req, &hashes_with_proof).unwrap();
534 assert!(result.valid);
535 assert!(picker.have_piece_hash(0, 0));
536 }
537
538 #[test]
539 fn add_hashes_with_invalid_proof_rejected() {
540 let block_hashes = make_block_hashes(8);
541 let tree = MerkleTree::from_leaves(&block_hashes);
542 let root = tree.root();
543 let piece_hashes = tree.piece_layer(2).to_vec();
544
545 let files = vec![make_file_info(root, 8, 4)];
546 let mut picker = HashPicker::new(&files, 2);
547
548 let wrong_uncle = sha256(b"wrong");
550 let mut hashes_with_bad_proof = piece_hashes[0..2].to_vec();
551 hashes_with_bad_proof.push(wrong_uncle);
552
553 let req = HashRequest {
554 file_root: root,
555 base: 1,
556 index: 0,
557 count: 2,
558 proof_layers: 1,
559 };
560 let result = picker.add_hashes(&req, &hashes_with_bad_proof).unwrap();
561 assert!(!result.valid);
562 assert!(!picker.have_piece_hash(0, 0));
564 }
565
566 #[test]
567 fn pick_hashes_with_callback() {
568 let root = sha256(b"file1");
569 let files = vec![make_file_info(root, 1024, 64)];
570 let picker = HashPicker::new(&files, 16);
571
572 assert!(picker.pick_hashes(|_| false).is_none());
574
575 assert!(picker.pick_hashes(|p| p == 0).is_some());
577 }
578}