use crate::client::ClientId;
use rtc::rtp_transceiver::{RTCRtpSenderId, SSRC};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct ForwardKey {
pub(crate) publisher: ClientId,
pub(crate) mid: String,
}
#[derive(Debug, Default)]
pub(crate) struct ForwardTable {
entries: HashMap<ForwardKey, HashMap<ClientId, RTCRtpSenderId>>,
ssrc_index: HashMap<SSRC, ForwardKey>,
}
impl ForwardTable {
pub(crate) fn has_subscriber(&self, key: &ForwardKey, subscriber: &ClientId) -> bool {
self.entries
.get(key)
.is_some_and(|subs| subs.contains_key(subscriber))
}
pub(crate) fn insert(
&mut self,
key: ForwardKey,
subscriber: ClientId,
sender_id: RTCRtpSenderId,
) {
self.entries
.entry(key)
.or_default()
.insert(subscriber, sender_id);
}
pub(crate) fn bind_ssrc(&mut self, ssrc: SSRC, key: ForwardKey) {
self.ssrc_index.insert(ssrc, key);
}
pub(crate) fn route_by_ssrc(
&self,
ssrc: SSRC,
) -> Option<(&ForwardKey, &HashMap<ClientId, RTCRtpSenderId>)> {
let key = self.ssrc_index.get(&ssrc)?;
let subscribers = self.entries.get(key)?;
Some((key, subscribers))
}
pub(crate) fn retain(
&mut self,
desired: &HashSet<ForwardKey>,
live_clients: &HashSet<ClientId>,
removed: &mut Vec<(ClientId, RTCRtpSenderId)>,
) {
self.entries.retain(|key, subs| {
let key_alive = desired.contains(key) && live_clients.contains(&key.publisher);
subs.retain(|subscriber, sender| {
let keep = key_alive && live_clients.contains(subscriber);
if !keep {
removed.push((*subscriber, *sender));
}
keep
});
!subs.is_empty()
});
let entries = &self.entries;
self.ssrc_index.retain(|_, key| entries.contains_key(key));
}
pub(crate) fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub(crate) fn clear(&mut self) {
self.entries.clear();
self.ssrc_index.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn key(publisher: ClientId, mid: &str) -> ForwardKey {
ForwardKey {
publisher,
mid: mid.to_owned(),
}
}
#[test]
fn routes_bound_ssrc_to_subscriber_senders() {
let mut table = ForwardTable::default();
let k = key(1, "0");
table.insert(k.clone(), 2, RTCRtpSenderId::from(7));
table.bind_ssrc(1111, k.clone());
let (routed_key, subscribers) = table.route_by_ssrc(1111).expect("ssrc should route");
assert_eq!(routed_key, &k);
assert_eq!(subscribers.get(&2), Some(&RTCRtpSenderId::from(7)));
assert!(table.route_by_ssrc(2222).is_none());
}
#[test]
fn simulcast_layers_bind_to_the_same_key() {
let mut table = ForwardTable::default();
let k = key(1, "0");
table.insert(k.clone(), 2, RTCRtpSenderId::from(7));
table.bind_ssrc(1111, k.clone());
table.bind_ssrc(1112, k.clone());
assert!(table.route_by_ssrc(1111).is_some());
assert!(table.route_by_ssrc(1112).is_some());
}
#[test]
fn retain_prunes_ssrc_bindings_with_their_key() {
let mut table = ForwardTable::default();
let k = key(1, "0");
table.insert(k.clone(), 2, RTCRtpSenderId::from(7));
table.bind_ssrc(1111, k.clone());
let mut removed = Vec::new();
let desired = HashSet::from([k]);
let live = HashSet::from([2]);
table.retain(&desired, &live, &mut removed);
assert_eq!(removed, vec![(2, RTCRtpSenderId::from(7))]);
assert!(table.route_by_ssrc(1111).is_none());
assert!(table.is_empty());
}
}