extern crate alloc;
use alloc::vec::Vec;
pub type WriterGuidBytes = [u8; 16];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OwnershipCandidate {
pub guid: WriterGuidBytes,
pub strength: i32,
pub alive: bool,
}
#[must_use]
pub fn resolve_strongest(candidates: &[OwnershipCandidate]) -> Option<OwnershipCandidate> {
let mut alive: Vec<OwnershipCandidate> =
candidates.iter().copied().filter(|c| c.alive).collect();
if alive.is_empty() {
return None;
}
alive.sort_by(|a, b| {
b.strength
.cmp(&a.strength)
.then_with(|| a.guid.cmp(&b.guid))
});
alive.first().copied()
}
#[derive(Debug, Clone, Default)]
pub struct OwnershipResolver {
candidates: Vec<OwnershipCandidate>,
current: Option<WriterGuidBytes>,
}
impl OwnershipResolver {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn upsert(&mut self, candidate: OwnershipCandidate) {
if let Some(existing) = self
.candidates
.iter_mut()
.find(|c| c.guid == candidate.guid)
{
*existing = candidate;
} else {
self.candidates.push(candidate);
}
self.recompute();
}
pub fn remove(&mut self, guid: &WriterGuidBytes) {
self.candidates.retain(|c| &c.guid != guid);
self.recompute();
}
#[must_use]
pub fn is_strongest(&self, guid: &WriterGuidBytes) -> bool {
self.current.as_ref() == Some(guid)
}
#[must_use]
pub fn accept(&self, writer_guid: &WriterGuidBytes) -> bool {
self.is_strongest(writer_guid)
}
#[must_use]
pub fn current(&self) -> Option<WriterGuidBytes> {
self.current
}
fn recompute(&mut self) {
self.current = resolve_strongest(&self.candidates).map(|c| c.guid);
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
fn cand(byte: u8, strength: i32, alive: bool) -> OwnershipCandidate {
OwnershipCandidate {
guid: [byte; 16],
strength,
alive,
}
}
#[test]
fn resolve_picks_highest_strength() {
let cs = [
cand(0xAA, 5, true),
cand(0xBB, 10, true),
cand(0xCC, 1, true),
];
assert_eq!(resolve_strongest(&cs).unwrap().guid, [0xBB; 16]);
}
#[test]
fn resolve_ignores_non_alive() {
let cs = [
cand(0xAA, 100, false), cand(0xBB, 5, true),
];
assert_eq!(resolve_strongest(&cs).unwrap().guid, [0xBB; 16]);
}
#[test]
fn resolve_returns_none_if_all_dead() {
let cs = [cand(0xAA, 10, false), cand(0xBB, 5, false)];
assert!(resolve_strongest(&cs).is_none());
}
#[test]
fn tie_breaker_uses_smallest_guid() {
let cs = [cand(0xBB, 5, true), cand(0xAA, 5, true)];
assert_eq!(resolve_strongest(&cs).unwrap().guid, [0xAA; 16]);
}
#[test]
fn resolver_upsert_tracks_strongest() {
let mut r = OwnershipResolver::new();
r.upsert(cand(0xAA, 5, true));
r.upsert(cand(0xBB, 10, true));
assert!(r.is_strongest(&[0xBB; 16]));
assert!(!r.is_strongest(&[0xAA; 16]));
}
#[test]
fn resolver_strength_change_triggers_swap() {
let mut r = OwnershipResolver::new();
r.upsert(cand(0xAA, 5, true));
r.upsert(cand(0xBB, 10, true));
assert!(r.is_strongest(&[0xBB; 16]));
r.upsert(cand(0xBB, 1, true));
assert!(r.is_strongest(&[0xAA; 16]));
}
#[test]
fn resolver_remove_triggers_recompute() {
let mut r = OwnershipResolver::new();
r.upsert(cand(0xAA, 5, true));
r.upsert(cand(0xBB, 10, true));
assert!(r.is_strongest(&[0xBB; 16]));
r.remove(&[0xBB; 16]);
assert!(r.is_strongest(&[0xAA; 16]));
}
#[test]
fn resolver_dead_strongest_falls_through() {
let mut r = OwnershipResolver::new();
r.upsert(cand(0xAA, 10, true));
r.upsert(cand(0xBB, 5, true));
assert!(r.is_strongest(&[0xAA; 16]));
r.upsert(cand(0xAA, 10, false));
assert!(r.is_strongest(&[0xBB; 16]));
}
#[test]
fn resolver_accept_helper() {
let mut r = OwnershipResolver::new();
r.upsert(cand(0xAA, 10, true));
assert!(r.accept(&[0xAA; 16]));
assert!(!r.accept(&[0xBB; 16]));
}
#[test]
fn empty_resolver_accepts_nothing() {
let r = OwnershipResolver::new();
assert!(!r.accept(&[0xAA; 16]));
assert!(r.current().is_none());
}
}