use super::{
authenticate::Credential,
client_dialog::ClientInviteDialog,
dialog::{DialogInner, DialogStateSender},
dialog_layer::DialogLayer,
};
use crate::sip::{
prelude::{HeadersExt, ToTypedHeader},
Request, Response, SipMessage, StatusCodeKind,
};
use crate::{
dialog::{
dialog::{Dialog, DialogState, TerminatedReason},
dialog_layer::DialogLayerInnerRef,
DialogId,
},
transaction::{
key::{TransactionKey, TransactionRole},
make_tag,
transaction::Transaction,
},
transport::SipAddr,
Result,
};
use futures::FutureExt;
use std::sync::Arc;
use tracing::{debug, info, warn};
#[derive(Default, Clone)]
pub struct InviteOption {
pub caller_display_name: Option<String>,
pub caller_params: Vec<crate::sip::uri::Param>,
pub caller: crate::sip::Uri,
pub callee: crate::sip::Uri,
pub destination: Option<SipAddr>,
pub content_type: Option<String>,
pub offer: Option<Vec<u8>>,
pub contact: crate::sip::Uri,
pub credential: Option<Credential>,
pub headers: Option<Vec<crate::sip::Header>>,
pub support_prack: bool,
pub call_id: Option<String>,
}
pub struct DialogGuard {
pub dialog_layer_inner: DialogLayerInnerRef,
pub id: DialogId,
}
impl DialogGuard {
pub fn new(dialog_layer: &Arc<DialogLayer>, id: DialogId) -> Self {
Self {
dialog_layer_inner: dialog_layer.inner.clone(),
id,
}
}
}
impl Drop for DialogGuard {
fn drop(&mut self) {
let dlg = match self.dialog_layer_inner.dialogs.remove(&self.id.to_string()) {
Some((_, dlg)) => dlg,
None => return,
};
let _ = tokio::spawn(async move {
if let Err(e) = dlg.hangup().await {
info!(id = %dlg.id(), error = %e, "failed to hangup dialog");
}
});
}
}
pub(super) struct DialogGuardForUnconfirmed<'a> {
pub dialog_layer_inner: &'a DialogLayerInnerRef,
pub id: &'a DialogId,
invite_tx: Option<Transaction>,
}
impl<'a> Drop for DialogGuardForUnconfirmed<'a> {
fn drop(&mut self) {
if let Some((_, dlg)) = self.dialog_layer_inner.dialogs.remove(&self.id.to_string()) {
debug!(%self.id, "unconfirmed dialog dropped, cancelling it");
let invite_tx = self.invite_tx.take();
let _ = tokio::spawn(async move {
if let Dialog::ClientInvite(ref client_dialog) = dlg {
if client_dialog.inner.can_cancel() {
if let Err(e) = client_dialog.cancel().await {
warn!(id = %client_dialog.id(), error = %e, "dialog cancel failed");
return;
}
if let Some(mut invite_tx) = invite_tx {
let duration = tokio::time::Duration::from_secs(2);
let timeout = tokio::time::sleep(duration);
tokio::pin!(timeout);
loop {
tokio::select! {
_ = &mut timeout => break,
msg = invite_tx.receive() => {
if let Some(msg) = msg{
if let SipMessage::Response(resp) = msg {
if resp.status_code.kind() != StatusCodeKind::Provisional {
debug!(
id = %client_dialog.id(),
status = %resp.status_code,
"received final response"
);
break;
}
}
}else{
break;
}
}
}
}
}
let _ = client_dialog.inner.transition(DialogState::Terminated(
client_dialog.id(),
TerminatedReason::UacCancel,
));
debug!(id = %client_dialog.id(), "dialog terminated");
return;
}
}
if let Err(e) = dlg.hangup().await {
info!(id = %dlg.id(), error = %e, "failed to hangup unconfirmed dialog");
}
});
}
}
}
pub type InviteAsyncResult = Result<(DialogId, Option<Response>)>;
impl DialogLayer {
pub fn make_invite_request(&self, opt: &InviteOption) -> Result<Request> {
let last_seq = self.increment_last_seq();
let to = crate::sip::typed::To {
display_name: None,
uri: opt.callee.clone(),
params: vec![],
};
let recipient = to.uri.clone();
let from = crate::sip::typed::From {
display_name: opt.caller_display_name.clone(),
uri: opt.caller.clone(),
params: opt.caller_params.clone(),
}
.with_tag(make_tag());
let call_id = opt
.call_id
.as_ref()
.map(|id| crate::sip::headers::CallId::from(id.clone()));
let via = self.endpoint.get_via(None, None)?;
let mut request = self.endpoint.make_request(
crate::sip::Method::Invite,
recipient,
via,
from,
to,
last_seq,
call_id,
);
let contact = crate::sip::typed::Contact {
display_name: None,
uri: opt.contact.clone(),
params: vec![],
};
request
.headers
.unique_push(crate::sip::Header::Contact(contact.into()));
request.headers.unique_push(crate::sip::Header::ContentType(
opt.content_type
.clone()
.unwrap_or("application/sdp".to_string())
.into(),
));
if opt.support_prack {
request
.headers
.unique_push(crate::sip::Header::Supported("100rel".into()));
}
if let Some(headers) = opt.headers.as_ref() {
for header in headers {
match header {
crate::sip::Header::MaxForwards(_) => {
request.headers.unique_push(header.clone())
}
_ => request.headers.push(header.clone()),
}
}
}
Ok(request)
}
pub async fn do_invite(
&self,
opt: InviteOption,
state_sender: DialogStateSender,
) -> Result<(ClientInviteDialog, Option<Response>)> {
let (dialog, tx) = self.create_client_invite_dialog(opt, state_sender)?;
let id = dialog.id();
self.inner
.dialogs
.insert(id.to_string(), Dialog::ClientInvite(dialog.clone()));
debug!(%id, "client invite dialog created");
let mut guard = DialogGuardForUnconfirmed {
dialog_layer_inner: &self.inner,
id: &id,
invite_tx: Some(tx),
};
let tx = guard
.invite_tx
.as_mut()
.expect("transcation should be avaible");
let r = dialog.process_invite(tx).boxed().await;
self.inner.dialogs.remove(&id.to_string());
match r {
Ok((new_dialog_id, resp)) => {
match resp {
Some(ref r)
if r.status_code.kind() == crate::sip::StatusCodeKind::Successful =>
{
debug!(
"client invite dialog confirmed: {} => {}",
id, new_dialog_id
);
self.inner.dialogs.insert(
new_dialog_id.to_string(),
Dialog::ClientInvite(dialog.clone()),
);
}
_ => {}
}
return Ok((dialog, resp));
}
Err(e) => {
return Err(e);
}
}
}
pub fn do_invite_async(
self: &Arc<Self>,
opt: InviteOption,
state_sender: DialogStateSender,
) -> Result<(
ClientInviteDialog,
tokio::task::JoinHandle<InviteAsyncResult>,
)> {
let (dialog, mut tx) = self.create_client_invite_dialog(opt, state_sender)?;
let id0 = dialog.id();
self.inner
.dialogs
.insert(id0.to_string(), Dialog::ClientInvite(dialog.clone()));
debug!(%id0, "client invite dialog created (async)");
let inner = self.inner.clone();
let dialog_clone = dialog.clone();
let handle = tokio::spawn(async move {
let r = dialog_clone.process_invite(&mut tx).boxed().await;
inner.dialogs.remove(&id0.to_string());
match &r {
Ok((new_id, resp_opt)) => {
let is_2xx = resp_opt
.as_ref()
.map(|resp| {
resp.status_code.kind() == crate::sip::StatusCodeKind::Successful
})
.unwrap_or(false);
if is_2xx {
debug!("client invite dialog confirmed: {} => {}", id0, new_id);
inner.dialogs.insert(
new_id.to_string(),
Dialog::ClientInvite(dialog_clone.clone()),
);
}
}
Err(e) => debug!(%id0, error = %e, "async invite failed"),
}
r
});
Ok((dialog, handle))
}
pub fn create_client_invite_dialog(
&self,
opt: InviteOption,
state_sender: DialogStateSender,
) -> Result<(ClientInviteDialog, Transaction)> {
let mut request = self.make_invite_request(&opt)?;
request.body = opt.offer.unwrap_or_default();
request
.headers
.unique_push(crate::sip::Header::ContentLength(
(request.body.len() as u32).into(),
));
let key = TransactionKey::from_request(&request, TransactionRole::Client)?;
let mut tx = Transaction::new_client(key, request.clone(), self.endpoint.clone(), None);
if opt.destination.is_some() {
tx.destination = opt.destination;
} else {
if let Some(route) = tx.original.route_header() {
if let Some(first_route) = route.typed().ok() {
tx.destination = SipAddr::try_from(&first_route.uri).ok();
}
}
}
let id = DialogId::try_from(&tx)?;
let dlg_inner = DialogInner::new(
TransactionRole::Client,
id.clone(),
request.clone(),
self.endpoint.clone(),
state_sender,
opt.credential,
Some(opt.contact),
tx.tu_sender.clone(),
)?;
if let Some(destination) = &tx.destination {
let uri = destination.clone().into();
*dlg_inner.remote_uri.lock() = uri;
}
let dialog = ClientInviteDialog {
inner: Arc::new(dlg_inner),
};
Ok((dialog, tx))
}
}