1use gbp_core::MemberId;
9use std::collections::{BTreeSet, HashMap};
10
11#[derive(Default)]
13pub struct CapabilitiesNegotiator {
14 advertised: HashMap<MemberId, BTreeSet<String>>,
15}
16
17impl CapabilitiesNegotiator {
18 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn advertise<I, S>(&mut self, member: MemberId, capabilities: I)
26 where
27 I: IntoIterator<Item = S>,
28 S: Into<String>,
29 {
30 let set: BTreeSet<String> = capabilities.into_iter().map(Into::into).collect();
31 self.advertised.insert(member, set);
32 }
33
34 pub fn forget(&mut self, member: MemberId) {
36 self.advertised.remove(&member);
37 }
38
39 pub fn capabilities_of(&self, member: MemberId) -> Option<&BTreeSet<String>> {
41 self.advertised.get(&member)
42 }
43
44 pub fn group_supports(&self, cap: &str) -> bool {
46 if self.advertised.is_empty() {
47 return false;
48 }
49 self.advertised.values().all(|set| set.contains(cap))
50 }
51
52 pub fn intersection(&self) -> BTreeSet<String> {
55 let mut iter = self.advertised.values();
56 let Some(first) = iter.next() else {
57 return BTreeSet::new();
58 };
59 let mut acc = first.clone();
60 for set in iter {
61 acc.retain(|c| set.contains(c));
62 }
63 acc
64 }
65
66 pub fn union(&self) -> BTreeSet<String> {
68 let mut acc = BTreeSet::new();
69 for set in self.advertised.values() {
70 for c in set {
71 acc.insert(c.clone());
72 }
73 }
74 acc
75 }
76
77 pub fn missing(&self, cap: &str) -> Vec<MemberId> {
79 self.advertised
80 .iter()
81 .filter_map(|(m, set)| if set.contains(cap) { None } else { Some(*m) })
82 .collect()
83 }
84
85 pub fn len(&self) -> usize {
87 self.advertised.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
92 self.advertised.is_empty()
93 }
94
95 pub fn reset_for_epoch(&mut self) {
99 self.advertised.clear();
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn intersection_is_lowest_common() {
109 let mut n = CapabilitiesNegotiator::new();
110 n.advertise(1, ["opus", "fec", "h264"]);
111 n.advertise(2, ["opus", "fec"]);
112 n.advertise(3, ["opus", "av1"]);
113 let common = n.intersection();
114 assert!(common.contains("opus"));
115 assert!(!common.contains("fec"));
116 assert!(!common.contains("h264"));
117 assert_eq!(n.missing("fec"), vec![3]);
118 }
119
120 #[test]
121 fn group_supports_requires_everyone() {
122 let mut n = CapabilitiesNegotiator::new();
123 n.advertise(1, ["opus"]);
124 n.advertise(2, ["opus"]);
125 assert!(n.group_supports("opus"));
126 n.advertise(3, [] as [&str; 0]);
127 assert!(!n.group_supports("opus"));
128 }
129}