use crate::chain::InterceptorChain;
use crate::noop::NoopInterceptor;
use crate::{BoxedInterceptor, Interceptor};
#[derive(Default)]
pub struct Registry {
interceptors: Vec<Box<dyn Interceptor>>,
rtcp_readable: bool,
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, interceptor: impl Interceptor + 'static) -> Self {
self.interceptors.push(Box::new(interceptor));
self
}
pub fn with_boxed(mut self, boxed_interceptor: BoxedInterceptor) -> Self {
self.interceptors.push(boxed_interceptor);
self
}
pub fn with_rtcp_readable(mut self) -> Self {
self.rtcp_readable = true;
self
}
pub fn build(mut self) -> impl Interceptor {
self.interceptors
.push(Box::new(NoopInterceptor::new(self.rtcp_readable)));
InterceptorChain::new(self.interceptors)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::StreamInfo;
use crate::{AttributedPacket, Packet, TaggedPacket};
use sansio::Protocol;
use shared::TransportContext;
use shared::error::Error;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::Instant;
#[derive(Clone, Default)]
struct Log(Arc<Mutex<Vec<&'static str>>>);
struct Marker {
name: &'static str,
log: Log,
read_queue: VecDeque<TaggedPacket>,
write_queue: VecDeque<TaggedPacket>,
}
impl Marker {
fn new(name: &'static str, log: Log) -> Self {
Self {
name,
log,
read_queue: VecDeque::new(),
write_queue: VecDeque::new(),
}
}
}
impl Protocol<TaggedPacket, TaggedPacket, ()> for Marker {
type Rout = TaggedPacket;
type Wout = TaggedPacket;
type Eout = ();
type Error = Error;
type Time = Instant;
fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
self.log.0.lock().unwrap().push(self.name);
self.read_queue.push_back(msg);
Ok(())
}
fn poll_read(&mut self) -> Option<Self::Rout> {
self.read_queue.pop_front()
}
fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
self.log.0.lock().unwrap().push(self.name);
self.write_queue.push_back(msg);
Ok(())
}
fn poll_write(&mut self) -> Option<Self::Wout> {
self.write_queue.pop_front()
}
fn handle_timeout(&mut self, _now: Instant) -> Result<(), Self::Error> {
Ok(())
}
fn poll_timeout(&mut self) -> Option<Self::Time> {
None
}
}
impl Interceptor for Marker {
fn bind_local_stream(&mut self, _info: &StreamInfo) {}
fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
}
fn packet() -> TaggedPacket {
TaggedPacket {
now: Instant::now(),
transport: TransportContext::default(),
message: AttributedPacket::new(Packet::Rtp(rtp::Packet::default())),
}
}
fn chain(log: &Log) -> impl Interceptor {
Registry::new()
.with(Marker::new("wire", log.clone()))
.with(Marker::new("middle", log.clone()))
.with(Marker::new("app", log.clone()))
.build()
}
#[test]
fn read_runs_in_the_order_stages_were_added() {
let log = Log::default();
let mut chain = chain(&log);
chain.handle_read(packet()).unwrap();
while chain.poll_read().is_some() {}
assert_eq!(vec!["wire", "middle", "app"], *log.0.lock().unwrap());
}
#[test]
fn write_runs_in_reverse() {
let log = Log::default();
let mut chain = chain(&log);
chain.handle_write(packet()).unwrap();
while chain.poll_write().is_some() {}
assert_eq!(vec!["app", "middle", "wire"], *log.0.lock().unwrap());
}
#[test]
fn a_registry_with_nothing_added_still_has_the_terminus() {
let mut chain = Registry::new().build();
chain
.handle_read(TaggedPacket {
now: Instant::now(),
transport: TransportContext::default(),
message: AttributedPacket::new(Packet::Rtcp(vec![])),
})
.unwrap();
assert!(
chain.poll_read().is_none(),
"inbound RTCP stops before the application"
);
}
#[test]
fn the_terminus_is_application_most() {
let log = Log::default();
let mut chain = Registry::new()
.with(Marker::new("wire", log.clone()))
.build();
chain
.handle_read(TaggedPacket {
now: Instant::now(),
transport: TransportContext::default(),
message: AttributedPacket::new(Packet::Rtcp(vec![])),
})
.unwrap();
assert_eq!(
vec!["wire"],
*log.0.lock().unwrap(),
"the stage saw the RTCP packet; the terminus dropped it afterwards"
);
assert!(chain.poll_read().is_none());
}
}