1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use irontide_core::{Id20, Id32};
use crate::Result;
/// Storage backend for torrent piece data.
///
/// `&self` (not `&mut self`) — implementations use interior mutability
/// so they can be shared via `Arc` across threads.
///
/// Parameters use wire-protocol coordinates directly:
/// `(piece, begin, length)` matching BEP 3 request/piece messages.
pub trait TorrentStorage: Send + Sync {
/// Write a chunk of data at `(piece, begin)`.
///
/// # Errors
///
/// Returns an error if the underlying I/O write fails.
fn write_chunk(&self, piece: u32, begin: u32, data: &[u8]) -> Result<()>;
/// Read a chunk of data from `(piece, begin, length)`.
///
/// # Errors
///
/// Returns an error if the underlying I/O read fails.
fn read_chunk(&self, piece: u32, begin: u32, length: u32) -> Result<Vec<u8>>;
/// Read an entire piece.
///
/// # Errors
///
/// Returns an error if the underlying I/O read fails.
fn read_piece(&self, piece: u32) -> Result<Vec<u8>>;
/// Verify a piece by comparing its SHA1 hash against `expected`.
///
/// Default implementation reads the full piece and hashes it.
///
/// # Errors
///
/// Returns an error if reading the piece data fails.
fn verify_piece(&self, piece: u32, expected: &Id20) -> Result<bool> {
let data = self.read_piece(piece)?;
Ok(irontide_core::sha1(&data) == *expected)
}
/// Verify a piece by comparing its SHA-256 hash against `expected` (v2).
///
/// Default implementation reads the full piece and hashes it.
///
/// # Errors
///
/// Returns an error if reading the piece data fails.
fn verify_piece_v2(&self, piece: u32, expected: &Id32) -> Result<bool> {
let data = self.read_piece(piece)?;
Ok(irontide_core::sha256(&data) == *expected)
}
/// Hash a single 16 KiB block with SHA-256 for Merkle verification.
///
/// Default implementation reads the chunk and hashes it.
///
/// # Errors
///
/// Returns an error if reading the chunk data fails.
fn hash_block(&self, piece: u32, begin: u32, length: u32) -> Result<Id32> {
let data = self.read_chunk(piece, begin, length)?;
Ok(irontide_core::sha256(&data))
}
/// Write a block from two slices without concatenation (vectored write).
///
/// The two slices `s0` and `s1` represent contiguous data that may be split
/// across a ring-buffer wrap boundary. The total write length is
/// `s0.len() + s1.len()`.
///
/// Default implementation concatenates and delegates to [`write_chunk`].
/// Backends with file-level I/O should override this to avoid the copy.
///
/// [`write_chunk`]: TorrentStorage::write_chunk
///
/// # Errors
///
/// Returns an error if the underlying I/O write fails.
fn write_chunk_vectored(&self, piece: u32, begin: u32, s0: &[u8], s1: &[u8]) -> Result<()> {
if s1.is_empty() {
self.write_chunk(piece, begin, s0)
} else {
let mut combined = Vec::with_capacity(s0.len() + s1.len());
combined.extend_from_slice(s0);
combined.extend_from_slice(s1);
self.write_chunk(piece, begin, &combined)
}
}
/// Return filesystem metadata for `io_uring` fd management.
///
/// Filesystem-backed implementations return the base directory, file paths,
/// and file map. Non-filesystem backends (memory, mmap) return `None`.
fn filesystem_info(
&self,
) -> Option<(
&std::path::Path,
&[std::path::PathBuf],
&crate::file_map::FileMap,
)> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::MemoryStorage;
use irontide_core::{Lengths, sha256};
#[test]
fn verify_piece_v2_correct_hash() {
let data = vec![0xABu8; 32768];
let lengths = Lengths::new(32768, 32768, 16384);
let storage = MemoryStorage::new(lengths);
storage.write_chunk(0, 0, &data[..16384]).unwrap();
storage.write_chunk(0, 16384, &data[16384..]).unwrap();
let expected = sha256(&data);
assert!(storage.verify_piece_v2(0, &expected).unwrap());
}
#[test]
fn verify_piece_v2_wrong_hash() {
let data = vec![0xABu8; 16384];
let lengths = Lengths::new(16384, 16384, 16384);
let storage = MemoryStorage::new(lengths);
storage.write_chunk(0, 0, &data).unwrap();
let wrong = Id32::ZERO;
assert!(!storage.verify_piece_v2(0, &wrong).unwrap());
}
#[test]
fn hash_block_returns_correct_sha256() {
let data = vec![0xCDu8; 16384];
let lengths = Lengths::new(16384, 16384, 16384);
let storage = MemoryStorage::new(lengths);
storage.write_chunk(0, 0, &data).unwrap();
let hash = storage.hash_block(0, 0, 16384).unwrap();
assert_eq!(hash, sha256(&data));
}
#[test]
fn hash_block_partial_last_block() {
let data = vec![0xEFu8; 20000];
let lengths = Lengths::new(20000, 20000, 16384);
let storage = MemoryStorage::new(lengths);
storage.write_chunk(0, 0, &data[..16384]).unwrap();
storage.write_chunk(0, 16384, &data[16384..]).unwrap();
let hash = storage.hash_block(0, 16384, 3616).unwrap();
assert_eq!(hash, sha256(&data[16384..]));
}
}