///|
/// Reconcile source -> remote. A Retry means both peers must rebuild summaries
/// at the returned configuration for the same frozen snapshots.
pub fn reconcile(
source : Snapshot,
remote : Snapshot,
policy : Policy,
) -> Outcome raise ReconcileError {
if source.scope != remote.scope {
raise Incompatible("dataset scope mismatch")
}
if source.config() != remote.config() {
raise Incompatible("sketch configuration mismatch")
}
let config = source.config()
if config.width > policy.max_width {
raise LimitExceeded("snapshot exceeds policy width")
}
let difference = remote.sketch.subtract(source.sketch)
let report = difference.decode(budget=policy.max_differences)
if report.complete() {
Ready(validated_plan(source, remote, report))
} else if config.width < policy.max_width &&
report.steps < policy.max_differences {
let width = if config.width * 2 > policy.max_width {
policy.max_width
} else {
config.width * 2
}
Retry(Config::new(width, seed=config.seed))
} else {
FullTransfer(report.residual)
}
}
///|
fn validated_plan(
source : Snapshot,
remote : Snapshot,
report : DecodeReport,
) -> Plan raise ReconcileError {
if !report.complete() {
raise InvalidPlan("partial recovery")
}
let mut expected = source.fingerprint
for id in report.negative {
expected = fingerprint_add(expected, id, -1)
}
for id in report.positive {
expected = fingerprint_add(expected, id, 1)
}
if expected != remote.fingerprint {
raise VerificationFailed
}
{
scope: source.scope,
source_epoch: source.epoch,
target_epoch: remote.epoch,
source: source.fingerprint,
target: remote.fingerprint,
add: report.positive.copy(),
remove: report.negative.copy(),
}
}
///|
/// Convenience for source simulations. Network applications should call reconcile
/// on received frames, send Retry requests, and implement their own transport.
pub fn synchronize(
source : Inventory,
remote : Inventory,
initial : Config,
policy : Policy,
) -> Outcome raise ReconcileError {
for config = initial {
match reconcile(source.snapshot(config), remote.snapshot(config), policy) {
Retry(next) => continue next
outcome => break outcome
}
}
}