///|
pub(all) struct FileStatus {
name : String
healthy : Bool
missing : Bool
damaged_slices : Array[Int32]
recoverable : Bool
}
///|
priv struct Scan {
status : Array[FileStatus]
blocks : Array[BytesView?]
}
///|
fn RecoverySet::scan(self : RecoverySet, files : Map[String, Bytes]) -> Scan {
let blocks = []
let status = []
for m in self.members {
let source = files.get(m.name)
let bad = []
let count = if m.recoverable {
((m.length + self.width.to_uint64() - 1UL) / self.width.to_uint64()).to_int()
} else {
0
}
for i = 0; i < count; i = i + 1 {
let start64 = i.to_uint64() * self.width.to_uint64()
let end64 = minimum(start64 + self.width.to_uint64(), m.length)
if source is Some(b) && end64 <= b.length().to_uint64() {
let start = start64.to_int()
let end = end64.to_int()
let block = if end - start == self.width {
b[start:end]
} else {
slice_at(b[start:end].to_owned(), 0, self.width)
}
let offset = 16 + i * 20
if crc32(block) == le32(m.sums, offset + 16) &&
md5(block) == m.sums[offset:offset + 16].to_owned() {
blocks.push(Some(block))
continue
}
}
bad.push(i)
blocks.push(None)
}
let healthy = source is Some(bytes) &&
bytes.length().to_uint64() == m.length &&
bad.is_empty() &&
md5(bytes) == m.hash
status.push({
name: m.name,
healthy,
missing: source is None,
damaged_slices: bad,
recoverable: m.recoverable,
})
}
{ status, blocks, }
}
///|
pub fn RecoverySet::verify(
self : RecoverySet,
files : Map[String, Bytes],
) -> Array[FileStatus] {
self.scan(files).status
}
///|
test "identify missing, truncated and appended input" {
let s = read_set([create([("a", b"12345")], width=4)])
assert_eq(s.verify(Map([]))[0].damaged_slices, [0, 1])
assert_eq(s.verify(Map([("a", b"1234")]))[0].damaged_slices, [1])
assert_false(s.verify(Map([("a", b"12345extra")]))[0].healthy)
assert_true(s.verify(Map([("a", b"12345")]))[0].healthy)
}