///|
/// Validated hybrid files start on SHA-1 piece boundaries. Hash each selected
/// real chunk once, reusing it for SHA-256 leaves and the SHA-1 piece buffer.
fn verify_hybrid(
parsed : ParsedTorrent,
selected : Array[Bool],
lengths : Array[Int64?],
read : (Int32, Int64, Int32) -> Bytes raise CrateError,
state : VerifyState,
) -> Unit raise CrateError {
let piece = parsed.summary.piece_length
let buffer : FixedArray[Byte] = FixedArray::make(piece, 0)
let mut storage = 0L
for entry in parsed.entries {
storage += entry.length
}
// Retain the existing SHA-1-then-SHA-256 diagnostic order and detail cap.
// Only bounded SHA-256 failure details wait here, never actual file layers.
let v2 : VerifyState = {
checked: 0,
skipped: 0,
failed: 0,
unreadable: 0,
failures: [],
limit: state.limit,
control: state.control,
}
let mut global_piece = 0
for index, file in parsed.summary.files {
let count = ((file.length + piece.to_int64() - 1L) / piece.to_int64()).to_int()
if !selected[index] {
state.skipped += count
v2.skipped += count
global_piece += count
continue
}
for local_piece in 0.. 1048576 {
1048576
} else {
real_length - cursor
}
spans.push({
file: index,
offset: offset + cursor.to_int64(),
length: size,
})
cursor += size
}
let actual = if lengths[index] == Some(file.length) {
Some(
hash_v2_piece(file.length, piece, offset, (o, n) => {
let data = read_exact(read, index, o, n)
buffer.blit_from_bytes((o - offset).to_int(), data, 0, n)
data
}),
) catch {
_ => None
}
} else {
None
}
check_verification_control(state.control)
let readable = actual is Some(_)
let sha1_matches = if readable {
let remaining = storage - global_piece.to_int64() * piece.to_int64()
let hashed_length = if remaining < piece.to_int64() {
remaining.to_int()
} else {
piece
}
// A short final file may or may not have virtual trailing padding.
// Clear reused buffer bytes so previous pieces cannot leak into zeros.
for i in real_length.. false
Some(hash) =>
if count == 1 {
hash == parsed.roots[index].root
} else {
hash ==
parsed.roots[index].piece_layer[local_piece * 32:local_piece * 32 +
32].to_owned()
}
}
record_piece(
v2,
"sha256",
local_piece,
[{ file: index, offset, length: real_length, }],
sha256_matches,
readable,
)
// Later SHA-1 failures take precedence over earlier SHA-256 failures.
// Keep only the earliest SHA-256 details that could still be returned.
while state.failures.length() + v2.failures.length() > state.limit {
ignore(v2.failures.pop())
}
global_piece += 1
}
}
state.checked += v2.checked
state.skipped += v2.skipped
state.failed += v2.failed
state.unreadable += v2.unreadable
for failure in v2.failures {
if state.failures.length() >= state.limit {
break
}
state.failures.push(failure)
}
}