use super::{
authenticate::{handle_client_authenticate, Credential},
DialogId,
};
use crate::sip::prelude::HeadersExt;
use crate::sip::{Header, Param, Response, SipMessage, StatusCode};
use crate::{
transaction::{
endpoint::EndpointInnerRef,
key::{TransactionKey, TransactionRole},
make_call_id, make_tag,
transaction::Transaction,
},
transport::{SipAddr, SipConnection},
Result,
};
use tracing::debug;
pub struct Registration {
pub last_seq: u32,
pub endpoint: EndpointInnerRef,
pub credential: Option<Credential>,
pub contact: Option<crate::sip::typed::Contact>,
pub allow: Option<crate::sip::headers::Allow>,
pub public_address: Option<crate::sip::HostWithPort>,
pub call_id: crate::sip::headers::CallId,
pub outbound_proxy: Option<std::net::SocketAddr>,
}
impl Registration {
pub fn new(endpoint: EndpointInnerRef, credential: Option<Credential>) -> Self {
let call_id = make_call_id(endpoint.option.callid_suffix.as_deref());
Self {
last_seq: 0,
endpoint,
credential,
contact: None,
allow: None,
public_address: None,
call_id,
outbound_proxy: None,
}
}
pub fn discovered_public_address(&self) -> Option<crate::sip::HostWithPort> {
self.public_address.clone()
}
pub fn expires(&self) -> u32 {
self.contact
.as_ref()
.and_then(|c| c.expires())
.unwrap_or(50)
}
pub async fn register(
&mut self,
server: crate::sip::Uri,
expires: Option<u32>,
) -> Result<Response> {
self.last_seq += 1;
let mut to = crate::sip::typed::To {
display_name: None,
uri: server.clone(),
params: vec![],
};
if let Some(cred) = &self.credential {
to.uri.auth = Some(crate::sip::Auth {
user: cred.username.clone(),
password: None,
});
}
let from = crate::sip::typed::From {
display_name: None,
uri: to.uri.clone(),
params: vec![],
}
.with_tag(make_tag());
let via = self.endpoint.get_via(None, None)?;
let mut contact = self.contact.clone().unwrap_or_else(|| {
let contact_host_with_port = self
.public_address
.clone()
.unwrap_or_else(|| via.uri.host_with_port.clone());
crate::sip::typed::Contact {
display_name: None,
uri: crate::sip::Uri {
auth: to.uri.auth.clone(),
scheme: Some(crate::sip::Scheme::Sip),
host_with_port: contact_host_with_port,
params: vec![],
headers: vec![],
},
params: vec![],
}
});
if expires.is_some() {
contact.params.retain(|p| !matches!(p, Param::Expires(_)));
}
let mut request = self.endpoint.make_request(
crate::sip::Method::Register,
server,
via,
from,
to,
self.last_seq,
None,
);
let contact_for_retry = contact.clone();
request.headers.unique_push(self.call_id.clone().into());
request.headers.unique_push(contact.into());
if let Some(allow) = &self.allow {
request.headers.unique_push(allow.clone().into());
}
request
.headers
.unique_push(Header::Supported("path, outbound".into()));
if let Some(expires) = expires {
request
.headers
.unique_push(crate::sip::headers::Expires::from(expires).into());
}
let key = TransactionKey::from_request(&request, TransactionRole::Client)?;
let mut tx = Transaction::new_client(key, request, self.endpoint.clone(), None);
if let Some(proxy) = &self.outbound_proxy {
let mut dest = SipAddr::from(*proxy);
if let Some(Param::Transport(t)) = tx
.original
.uri()
.params
.iter()
.find(|p| matches!(p, Param::Transport(_)))
{
dest.r#type = Some(t.clone());
}
tx.destination = Some(dest);
}
tx.send().await?;
let mut auth_sent = false;
while let Some(msg) = tx.receive().await {
match msg {
SipMessage::Response(resp) => match resp.status_code {
StatusCode::Trying => {
continue;
}
StatusCode::ProxyAuthenticationRequired | StatusCode::Unauthorized => {
let received = resp.via_header().ok().and_then(|via| {
SipConnection::parse_target_from_via(via)
.ok()
.map(|(_, host_with_port)| host_with_port)
});
if self.public_address != received {
debug!(
old = ?self.public_address,
new = ?received,
"updated public address from 401 response"
);
self.public_address = received;
if let Some(ref mut contact) = self.contact {
if let Some(ref pa) = self.public_address {
contact.uri.host_with_port = pa.clone();
}
} else {
self.contact = None;
}
}
if auth_sent {
debug!(status = %resp.status_code, "received auth response after auth sent");
return Ok(resp);
}
if let Some(cred) = &self.credential {
self.last_seq += 1;
tx = handle_client_authenticate(self.last_seq, &tx, resp, cred).await?;
if let Some(ref pa) = self.public_address {
let new_contact_uri = crate::sip::Uri {
auth: contact_for_retry.uri.auth.clone(),
scheme: Some(crate::sip::Scheme::Sip),
host_with_port: pa.clone(),
params: contact_for_retry.uri.params.clone(),
headers: vec![],
};
let mut new_contact = contact_for_retry.clone();
new_contact.uri = new_contact_uri;
tx.original
.headers
.retain(|h| !matches!(h, crate::sip::Header::Contact(_)));
tx.original.headers.unique_push(new_contact.into());
}
tx.send().await?;
auth_sent = true;
continue;
} else {
debug!(status = %resp.status_code, "received auth response without credential");
return Ok(resp);
}
}
StatusCode::OK => {
let received = resp.via_header().ok().and_then(|via| {
SipConnection::parse_target_from_via(via)
.ok()
.map(|(_, host_with_port)| host_with_port)
});
if self.public_address != received {
debug!(
old = ?self.public_address,
new = ?received,
"discovered public IP"
);
self.public_address = received;
}
debug!(
status = %resp.status_code,
contact = ?self.contact.as_ref().map(|c| c.uri.to_string()),
"registration do_request done"
);
return Ok(resp);
}
_ => {
debug!(status = %resp.status_code, "registration do_request done");
return Ok(resp);
}
},
_ => break,
}
}
return Err(crate::Error::DialogError(
"registration transaction is already terminated".to_string(),
DialogId::try_from(&tx)?,
StatusCode::BadRequest,
));
}
pub fn create_nat_aware_contact(
username: &str,
public_address: Option<crate::sip::HostWithPort>,
local_address: &SipAddr,
) -> crate::sip::typed::Contact {
let contact_host_with_port = public_address.unwrap_or_else(|| local_address.clone().into());
let params = vec![];
crate::sip::typed::Contact {
display_name: None,
uri: crate::sip::Uri {
scheme: Some(crate::sip::Scheme::Sip),
auth: Some(crate::sip::Auth {
user: username.to_string(),
password: None,
}),
host_with_port: contact_host_with_port,
params,
headers: vec![],
},
params: vec![],
}
}
}