use omnipaxos::ballot_leader_election::Ballot;
use tsoracle_core::Epoch;
#[must_use]
pub fn encode_epoch(ballot: Ballot) -> Epoch {
Epoch(
(u128::from(ballot.config_id) << 96)
| (u128::from(ballot.n) << 64)
| u128::from(ballot.pid),
)
}
#[must_use]
pub fn decode_epoch(epoch: Epoch) -> (u32, u32, u64) {
let raw = epoch.0;
let config_id = (raw >> 96) as u32;
let n = (raw >> 64) as u32;
let pid = raw as u64;
(config_id, n, pid)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn ballot(config_id: u32, n: u32, pid: u64) -> Ballot {
Ballot {
config_id,
n,
priority: 0,
pid,
}
}
#[test]
fn encode_then_decode_round_trip() {
let (config_id, n, pid) = decode_epoch(encode_epoch(ballot(7, 42, 3)));
assert_eq!((config_id, n, pid), (7, 42, 3));
}
#[test]
fn encode_is_lossless_at_full_field_widths() {
let (config_id, n, pid) = decode_epoch(encode_epoch(ballot(u32::MAX, u32::MAX, u64::MAX)));
assert_eq!((config_id, n, pid), (u32::MAX, u32::MAX, u64::MAX));
}
#[test]
fn higher_config_id_dominates_lower_round() {
let early = encode_epoch(ballot(1, u32::MAX, 5));
let later = encode_epoch(ballot(2, 0, 5));
assert!(
later > early,
"config_id bump must outrank a saturated round"
);
}
#[test]
fn higher_round_dominates_within_same_config() {
let earlier = encode_epoch(ballot(1, 5, 9));
let later = encode_epoch(ballot(1, 6, 1));
assert!(later > earlier, "round bump must outrank a pid change");
}
#[test]
fn distinct_pids_at_same_round_have_distinct_epochs() {
assert_ne!(encode_epoch(ballot(1, 5, 2)), encode_epoch(ballot(1, 5, 3)));
}
#[test]
fn formerly_colliding_ballots_now_have_distinct_epochs() {
assert_ne!(
encode_epoch(ballot(1, 7, 1)),
encode_epoch(ballot(1, 7, 65537))
);
assert_ne!(
encode_epoch(ballot(1, 7, 2)),
encode_epoch(ballot(65537, 7, 2))
);
}
#[test]
fn priority_is_intentionally_excluded() {
let with_priority = Ballot {
config_id: 1,
n: 5,
priority: 99,
pid: 2,
};
let without_priority = Ballot {
config_id: 1,
n: 5,
priority: 0,
pid: 2,
};
assert_eq!(encode_epoch(with_priority), encode_epoch(without_priority));
}
proptest! {
#[test]
fn encode_decode_is_lossless(
config_id in any::<u32>(),
n in any::<u32>(),
pid in any::<u64>(),
) {
let recovered = decode_epoch(encode_epoch(ballot(config_id, n, pid)));
prop_assert_eq!(recovered, (config_id, n, pid));
}
#[test]
fn encode_preserves_lexicographic_order(
c1 in any::<u32>(), n1 in any::<u32>(), p1 in any::<u64>(),
c2 in any::<u32>(), n2 in any::<u32>(), p2 in any::<u64>(),
) {
let e1 = encode_epoch(ballot(c1, n1, p1));
let e2 = encode_epoch(ballot(c2, n2, p2));
prop_assert_eq!(e1.cmp(&e2), (c1, n1, p1).cmp(&(c2, n2, p2)));
}
}
}