use gbp_core::MemberId;
use std::collections::{BTreeSet, HashMap};
#[derive(Default)]
pub struct CapabilitiesNegotiator {
advertised: HashMap<MemberId, BTreeSet<String>>,
}
impl CapabilitiesNegotiator {
pub fn new() -> Self {
Self::default()
}
pub fn advertise<I, S>(&mut self, member: MemberId, capabilities: I)
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let set: BTreeSet<String> = capabilities.into_iter().map(Into::into).collect();
self.advertised.insert(member, set);
}
pub fn forget(&mut self, member: MemberId) {
self.advertised.remove(&member);
}
pub fn capabilities_of(&self, member: MemberId) -> Option<&BTreeSet<String>> {
self.advertised.get(&member)
}
pub fn group_supports(&self, cap: &str) -> bool {
if self.advertised.is_empty() {
return false;
}
self.advertised.values().all(|set| set.contains(cap))
}
pub fn intersection(&self) -> BTreeSet<String> {
let mut iter = self.advertised.values();
let Some(first) = iter.next() else {
return BTreeSet::new();
};
let mut acc = first.clone();
for set in iter {
acc.retain(|c| set.contains(c));
}
acc
}
pub fn union(&self) -> BTreeSet<String> {
let mut acc = BTreeSet::new();
for set in self.advertised.values() {
for c in set {
acc.insert(c.clone());
}
}
acc
}
pub fn missing(&self, cap: &str) -> Vec<MemberId> {
self.advertised
.iter()
.filter_map(|(m, set)| if set.contains(cap) { None } else { Some(*m) })
.collect()
}
pub fn len(&self) -> usize {
self.advertised.len()
}
pub fn is_empty(&self) -> bool {
self.advertised.is_empty()
}
pub fn reset_for_epoch(&mut self) {
self.advertised.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intersection_is_lowest_common() {
let mut n = CapabilitiesNegotiator::new();
n.advertise(1, ["opus", "fec", "h264"]);
n.advertise(2, ["opus", "fec"]);
n.advertise(3, ["opus", "av1"]);
let common = n.intersection();
assert!(common.contains("opus"));
assert!(!common.contains("fec"));
assert!(!common.contains("h264"));
assert_eq!(n.missing("fec"), vec![3]);
}
#[test]
fn group_supports_requires_everyone() {
let mut n = CapabilitiesNegotiator::new();
n.advertise(1, ["opus"]);
n.advertise(2, ["opus"]);
assert!(n.group_supports("opus"));
n.advertise(3, [] as [&str; 0]);
assert!(!n.group_supports("opus"));
}
}