use std::net::SocketAddr;
use std::time::Duration;
use tokio::time::{Instant, timeout};
use crate::distributed::PeerId;
use crate::signaling_client::{ServerMessage, SignalingClient, SignalingError};
use crate::str0m_net::{Str0mNet, Str0mNetError};
const POLL_TICK: Duration = Duration::from_millis(25);
#[derive(Debug)]
pub enum WebrtcSignalingError {
Signaling(Box<SignalingError>),
Str0mNet(Box<Str0mNetError>),
Closed,
Timeout,
}
impl std::fmt::Display for WebrtcSignalingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Signaling(e) => write!(f, "webrtc signaling: {e}"),
Self::Str0mNet(e) => write!(f, "webrtc signaling str0m: {e}"),
Self::Closed => write!(f, "webrtc signaling: connection closed mid-handshake"),
Self::Timeout => write!(f, "webrtc signaling: data channel did not open in time"),
}
}
}
impl std::error::Error for WebrtcSignalingError {}
impl From<SignalingError> for WebrtcSignalingError {
fn from(e: SignalingError) -> Self {
Self::Signaling(Box::new(e))
}
}
impl From<Str0mNetError> for WebrtcSignalingError {
fn from(e: Str0mNetError) -> Self {
Self::Str0mNet(Box::new(e))
}
}
pub async fn offer_to_peer(
client: &mut SignalingClient,
peer: PeerId,
bind: SocketAddr,
open_timeout: Duration,
) -> Result<Str0mNet, WebrtcSignalingError> {
let (net, offer_sdp) = Str0mNet::offer(bind)?;
client.offer(peer, offer_sdp).await?;
client.ice(peer, net.local_candidate().to_string()).await?;
let deadline = Instant::now() + open_timeout;
pump_until_open(client, &net, deadline).await?;
Ok(net)
}
pub async fn answer_next_offer(
client: &mut SignalingClient,
bind: SocketAddr,
open_timeout: Duration,
) -> Result<(PeerId, Str0mNet), WebrtcSignalingError> {
let deadline = Instant::now() + open_timeout;
let mut early_candidates: Vec<String> = Vec::new();
let (peer, offer_sdp) = loop {
match recv_before(client, deadline).await? {
ServerMessage::Offer { from, sdp } => break (from, sdp),
ServerMessage::Ice { candidate, .. } => early_candidates.push(candidate),
_ => {}
}
};
let (net, answer_sdp) = Str0mNet::answer(bind, &offer_sdp)?;
client.answer(peer, answer_sdp).await?;
client.ice(peer, net.local_candidate().to_string()).await?;
for candidate in early_candidates {
net.add_remote_candidate(&candidate)?;
}
pump_until_open(client, &net, deadline).await?;
Ok((peer, net))
}
async fn recv_before(
client: &mut SignalingClient,
deadline: Instant,
) -> Result<ServerMessage, WebrtcSignalingError> {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Err(WebrtcSignalingError::Timeout);
}
match timeout(remaining, client.recv()).await {
Ok(Some(Ok(msg))) => Ok(msg),
Ok(Some(Err(e))) => Err(e.into()),
Ok(None) => Err(WebrtcSignalingError::Closed),
Err(_elapsed) => Err(WebrtcSignalingError::Timeout),
}
}
async fn pump_until_open(
client: &mut SignalingClient,
net: &Str0mNet,
deadline: Instant,
) -> Result<(), WebrtcSignalingError> {
while !net.is_open() {
let now = Instant::now();
if now >= deadline {
return Err(WebrtcSignalingError::Timeout);
}
let slice = (deadline - now).min(POLL_TICK);
match timeout(slice, client.recv()).await {
Ok(Some(Ok(msg))) => apply_handshake_frame(net, msg)?,
Ok(Some(Err(e))) => return Err(e.into()),
Ok(None) => return Err(WebrtcSignalingError::Closed),
Err(_elapsed) => {}
}
}
Ok(())
}
fn apply_handshake_frame(net: &Str0mNet, msg: ServerMessage) -> Result<(), WebrtcSignalingError> {
match msg {
ServerMessage::Answer { sdp, .. } => net.accept_answer(&sdp)?,
ServerMessage::Ice { candidate, .. } => net.add_remote_candidate(&candidate)?,
_ => {}
}
Ok(())
}