///|
/// Rebuild a complete set in memory. No output is returned until every
/// reconstructed file has passed its packet's whole-file checksum.
pub fn RecoverySet::repair(
self : RecoverySet,
files : Map[String, Bytes],
) -> Array[(String, Bytes)] raise {
let mut total = 0UL
for entry in self.members {
total = total + entry.length
}
if total > MaxSourceBytes.to_uint64() {
fail("use streaming repair for sets over 256 MiB")
}
let scan = self.scan(files)
let exps = self.parity.keys().to_array()
exps.sort()
let missing = []
for i = 0; i < scan.blocks.length(); i = i + 1 {
if scan.blocks[i] is None {
missing.push(i)
}
}
guard solve(
scan.blocks.length(),
missing,
exps,
exps.map(fn(e) { as_words(self.parity[e]) }),
fn(row, offset, j, weight) {
if scan.blocks[j] is Some(view) {
xor_slice(row, offset, view, weight)
}
},
)
is Some(restored) else {
fail("insufficient independent recovery slices")
}
let outputs = []
let mut pos = 0
let mut recovered = 0
for entry in self.members {
if !entry.recoverable {
continue
}
let file_blocks : Array[BytesView] = []
let count = (entry.length.to_int() + self.width - 1) / self.width
for i = 0; i < count; i = i + 1 {
let block = match scan.blocks[pos] {
Some(known) => known
None => {
let value = restored[recovered]
recovered = recovered + 1
as_bytes(value)[:]
}
}
pos = pos + 1
file_blocks.push(block)
}
// Allocate the result once instead of copying an entire file-sized Buffer.
let bytes = Bytes::makei(entry.length.to_int(), fn(i) {
file_blocks[i / self.width][i % self.width]
})
if md5(bytes) != entry.hash {
fail("restored checksum mismatch: " + entry.name)
}
outputs.push((entry.name, bytes))
}
outputs
}
///|
test "recover missing file and the final partial slice of another" {
let files = [("one", b"abcdefghij"), ("two", b"xyz")]
let s = read_set([create(files, width=4, recovery_blocks=2)])
let outputs = Map(s.repair(Map([("one", b"abcdefghXX")])))
for pair in files {
assert_eq(outputs.get(pair.0), Some(pair.1))
}
let few = read_set([create(files, width=4, recovery_blocks=1)])
assert_true(
try {
ignore(few.repair(Map([("one", b"abcdefghXX")])))
false
} catch {
_ => true
},
)
}