use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use rsip::{Header, StatusCode};
use rsipstack::dialog::dialog::DialogStateReceiver;
use rsipstack::dialog::server_dialog::ServerInviteDialog;
use rsipstack::transaction::transaction::Transaction;
use tokio::net::UdpSocket;
use tracing::{debug, info, warn};
use crate::account::SipAccount;
use crate::endpoint::SipEndpoint;
use crate::sdp::{build_sdp, parse_sdp, RemoteMedia};
type BoxError = Box<dyn std::error::Error + Send + Sync>;
pub struct AcceptedCall {
pub dialog: ServerInviteDialog,
pub remote_media: RemoteMedia,
pub rtp_socket: Arc<UdpSocket>,
pub local_rtp_addr: SocketAddr,
pub state_rx: DialogStateReceiver,
}
pub struct PendingCall {
pub dialog: ServerInviteDialog,
pub remote_media: RemoteMedia,
pub state_rx: DialogStateReceiver,
local_ip: IpAddr,
}
impl PendingCall {
pub async fn accept(self) -> Result<AcceptedCall, BoxError> {
let rtp_socket = UdpSocket::bind("0.0.0.0:0").await?;
let local_rtp_addr = rtp_socket.local_addr()?;
let rtp_port = local_rtp_addr.port();
info!(local_ip = %self.local_ip, rtp_port, "bound RTP socket");
let sdp_answer = build_sdp(self.local_ip, rtp_port);
debug!("SDP answer:\n{}", String::from_utf8_lossy(&sdp_answer));
let headers = vec![Header::ContentType("application/sdp".into())];
self.dialog.accept(Some(headers), Some(sdp_answer))?;
info!("sent 200 OK with SDP answer");
Ok(AcceptedCall {
dialog: self.dialog,
remote_media: self.remote_media,
rtp_socket: Arc::new(rtp_socket),
local_rtp_addr,
state_rx: self.state_rx,
})
}
pub fn reject(self, status: StatusCode) -> Result<(), BoxError> {
if status.code() < 300 {
return Err(format!("reject() got 2xx status {status}").into());
}
self.dialog.reject(Some(status.clone()), None)?;
info!(%status, "rejected inbound INVITE");
Ok(())
}
}
pub struct Callee {
account: SipAccount,
endpoint: Arc<SipEndpoint>,
}
impl Callee {
pub fn new(account: SipAccount, endpoint: Arc<SipEndpoint>) -> Self {
Self { account, endpoint }
}
pub async fn handle_pending(&self, mut tx: Transaction) -> Result<PendingCall, BoxError> {
let remote_media = parse_sdp(&tx.original.body)?;
info!(
remote_addr = %remote_media.addr,
remote_port = remote_media.port,
payload_type = remote_media.payload_type,
"parsed SDP offer",
);
let (state_sender, state_rx) = self.endpoint.dialog_layer.new_dialog_state_channel();
let contact_uri: rsip::Uri = format!(
"sip:{}@{}",
self.account.username, self.endpoint.sip_addr.addr
)
.try_into()?;
let dialog = self.endpoint.dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(contact_uri),
)?;
let dialog_for_handler = dialog.clone();
tokio::spawn(async move {
let mut dialog = dialog_for_handler;
if let Err(e) = dialog.handle(&mut tx).await {
warn!("INVITE transaction handle error: {e}");
}
});
Ok(PendingCall {
dialog,
remote_media,
state_rx,
local_ip: self.endpoint.local_ip(),
})
}
pub async fn accept_transaction(&self, tx: Transaction) -> Result<AcceptedCall, BoxError> {
self.handle_pending(tx).await?.accept().await
}
pub async fn reject_transaction(
&self,
mut tx: Transaction,
status: StatusCode,
) -> Result<(), BoxError> {
if status.code() < 300 {
return Err(format!("reject_transaction got 2xx status {status}").into());
}
let (state_sender, _state_rx) = self.endpoint.dialog_layer.new_dialog_state_channel();
let contact_uri: rsip::Uri = format!(
"sip:{}@{}",
self.account.username, self.endpoint.sip_addr.addr
)
.try_into()?;
let dialog = self.endpoint.dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(contact_uri),
)?;
dialog.reject(Some(status.clone()), None)?;
info!(%status, "rejected inbound INVITE");
tokio::spawn(async move {
let mut dialog = dialog;
if let Err(e) = dialog.handle(&mut tx).await {
debug!("rejected INVITE handle error (expected for some flows): {e}");
}
});
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reject_with_2xx_is_an_error() {
let ok = StatusCode::OK;
assert!(ok.code() < 300);
let busy = StatusCode::BusyHere;
assert!(busy.code() >= 300);
}
}