use anyhow::Result;
use bytes::BytesMut;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use webrtc::data_channel::{DataChannel, DataChannelEvent, RTCDataChannelInit};
use webrtc::peer_connection::{
PeerConnection, PeerConnectionBuilder, PeerConnectionEventHandler, RTCIceGatheringState,
RTCPeerConnectionState,
};
use webrtc::runtime::{Runtime, Sender, channel};
mod common;
use common::{block_on, runtime, timeout};
#[path = "../examples/custom-runtime/my_runtime.rs"]
mod my_runtime;
const LABEL: &str = "cross-runtime";
const PAYLOAD: &[u8] = b"hello across runtimes";
struct Handler {
gather_tx: Sender<()>,
connected_tx: Sender<()>,
announced: Arc<Mutex<Option<String>>>,
announced_tx: Sender<Arc<dyn DataChannel>>,
received: Arc<Mutex<Vec<u8>>>,
received_tx: Sender<()>,
runtime: Arc<dyn Runtime>,
}
#[async_trait::async_trait]
impl PeerConnectionEventHandler for Handler {
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
if state == RTCIceGatheringState::Complete {
let _ = self.gather_tx.try_send(());
}
}
async fn on_connection_state_change(&self, state: RTCPeerConnectionState) {
if state == RTCPeerConnectionState::Connected {
let _ = self.connected_tx.try_send(());
}
}
async fn on_data_channel(&self, dc: Arc<dyn DataChannel>) {
*self.announced.lock().unwrap() = Some(dc.label().await.unwrap_or_default());
let _ = self.announced_tx.try_send(dc.clone());
let received = Arc::clone(&self.received);
let received_tx = self.received_tx.clone();
self.runtime.spawn(Box::pin(async move {
while let Some(event) = dc.poll().await {
match event {
DataChannelEvent::OnMessage(msg) => {
received.lock().unwrap().extend_from_slice(&msg.data);
let _ = received_tx.try_send(());
}
DataChannelEvent::OnClose => break,
_ => {}
}
}
}));
}
}
struct Peer {
pc: Box<dyn PeerConnection>,
runtime: Arc<dyn Runtime>,
announced: Arc<Mutex<Option<String>>>,
received: Arc<Mutex<Vec<u8>>>,
gather_rx: webrtc::runtime::Receiver<()>,
connected_rx: webrtc::runtime::Receiver<()>,
announced_rx: webrtc::runtime::Receiver<Arc<dyn DataChannel>>,
received_rx: webrtc::runtime::Receiver<()>,
}
async fn build_peer(runtime: Arc<dyn Runtime>) -> Result<Peer> {
let (gather_tx, gather_rx) = channel::<()>(1);
let (connected_tx, connected_rx) = channel::<()>(1);
let (announced_tx, announced_rx) = channel::<Arc<dyn DataChannel>>(4);
let (received_tx, received_rx) = channel::<()>(4);
let announced = Arc::new(Mutex::new(None));
let received = Arc::new(Mutex::new(Vec::new()));
let pc = PeerConnectionBuilder::new()
.with_handler(Arc::new(Handler {
gather_tx,
connected_tx,
announced: announced.clone(),
announced_tx,
received: received.clone(),
received_tx,
runtime: runtime.clone(),
}))
.with_runtime(runtime.clone())
.with_udp_addrs(vec!["127.0.0.1:0".to_string()])
.build()
.await?;
Ok(Peer {
pc: Box::new(pc),
runtime,
announced,
received,
gather_rx,
connected_rx,
announced_rx,
received_rx,
})
}
#[test]
fn test_two_peers_on_different_runtimes_interoperate() {
block_on(run()).unwrap();
}
async fn run() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.is_test(true)
.try_init()
.ok();
let builtin: Arc<dyn Runtime> = runtime();
let custom: Arc<dyn Runtime> = Arc::new(my_runtime::MyRuntime::new());
assert_ne!(
builtin.name(),
custom.name(),
"test is meaningless unless the two runtimes differ"
);
assert_eq!(
custom.name(),
"my-runtime",
"answerer must be on the out-of-tree runtime"
);
log::info!(
"offerer runtime = {}, answerer runtime = {}",
builtin.name(),
custom.name()
);
let mut offerer = build_peer(builtin.clone()).await?;
let mut answerer = build_peer(custom.clone()).await?;
let dc = offerer
.pc
.create_data_channel(LABEL, Some(RTCDataChannelInit::default()))
.await?;
let (open_tx, mut open_rx) = channel::<()>(1);
{
let dc = dc.clone();
offerer.runtime.spawn(Box::pin(async move {
while let Some(event) = dc.poll().await {
match event {
DataChannelEvent::OnOpen => {
let _ = open_tx.try_send(());
}
DataChannelEvent::OnClose => break,
_ => {}
}
}
}));
}
let offer = offerer.pc.create_offer(None).await?;
offerer.pc.set_local_description(offer).await?;
let _ = timeout(Duration::from_secs(5), offerer.gather_rx.recv()).await;
let offer_sdp = offerer
.pc
.local_description()
.await
.expect("offerer local description");
answerer.pc.set_remote_description(offer_sdp).await?;
let answer = answerer.pc.create_answer(None).await?;
answerer.pc.set_local_description(answer).await?;
let _ = timeout(Duration::from_secs(5), answerer.gather_rx.recv()).await;
let answer_sdp = answerer
.pc
.local_description()
.await
.expect("answerer local description");
offerer.pc.set_remote_description(answer_sdp).await?;
timeout(Duration::from_secs(20), offerer.connected_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("offerer (built-in runtime) never reached Connected"))?;
timeout(Duration::from_secs(10), answerer.connected_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("answerer (custom runtime) never reached Connected"))?;
timeout(Duration::from_secs(10), open_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("data channel never opened"))?;
timeout(Duration::from_secs(10), answerer.announced_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("custom-runtime peer never saw the announced channel"))?;
assert_eq!(
answerer.announced.lock().unwrap().as_deref(),
Some(LABEL),
"custom-runtime peer should see the offerer's channel label"
);
dc.send(BytesMut::from(PAYLOAD)).await?;
timeout(Duration::from_secs(10), answerer.received_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("payload never arrived on the custom-runtime peer"))?;
let got = answerer.received.lock().unwrap().clone();
assert_eq!(
got.as_slice(),
PAYLOAD,
"custom-runtime peer received the wrong bytes"
);
assert_eq!(offerer.runtime.name(), builtin.name());
assert_eq!(answerer.runtime.name(), "my-runtime");
assert!(
!Arc::ptr_eq(&offerer.runtime, &answerer.runtime),
"the two peers must hold distinct runtime instances"
);
offerer.pc.close().await?;
answerer.pc.close().await?;
Ok(())
}
#[test]
fn test_custom_runtime_serves_multiple_connections() {
block_on(async {
let custom: Arc<dyn Runtime> = Arc::new(my_runtime::MyRuntime::new());
let built = AtomicUsize::new(0);
for _ in 0..3 {
let peer = build_peer(custom.clone())
.await
.expect("build a peer on the shared custom runtime");
let _ = timeout(Duration::from_secs(5), {
let mut rx = peer.gather_rx;
async move { rx.recv().await }
})
.await;
built.fetch_add(1, Ordering::Relaxed);
peer.pc.close().await.expect("close");
}
assert_eq!(
built.load(Ordering::Relaxed),
3,
"one custom runtime instance should serve several connections"
);
});
}