use std::sync::Arc;
use rvoip_sip_core::types::Method;
use crate::api::headers::{take_staged, BuilderHeaderState, SipRequestOptions};
use crate::api::unified::{RegistrationHandle, UnifiedCoordinator};
use crate::errors::Result;
use crate::session_registry::SessionRegistryHandle;
pub struct RegisterBuilder {
coord: Arc<UnifiedCoordinator>,
registrar: String,
user: String,
password: String,
expires: u32,
from_uri: Option<String>,
contact_uri: Option<String>,
outbound_proxy: Option<String>,
suppress_outbound_proxy: bool,
path: Option<String>,
q_value: Option<f32>,
sip_instance: Option<String>,
reg_id: Option<u32>,
precomputed_authorization: Option<String>,
state: BuilderHeaderState,
}
impl RegisterBuilder {
pub(crate) fn new(
coord: Arc<UnifiedCoordinator>,
registrar: impl Into<String>,
user: impl Into<String>,
password: impl Into<String>,
) -> Self {
Self {
coord,
registrar: registrar.into(),
user: user.into(),
password: password.into(),
expires: 3600,
from_uri: None,
contact_uri: None,
outbound_proxy: None,
suppress_outbound_proxy: false,
path: None,
q_value: None,
sip_instance: None,
reg_id: None,
precomputed_authorization: None,
state: BuilderHeaderState::default(),
}
}
pub fn with_expires(mut self, secs: u32) -> Self {
self.expires = secs;
self
}
pub fn with_from_uri(mut self, s: impl Into<String>) -> Self {
self.from_uri = Some(s.into());
self
}
pub fn with_contact_uri(mut self, s: impl Into<String>) -> Self {
self.contact_uri = Some(s.into());
self
}
pub fn with_outbound_proxy(mut self, s: impl Into<String>) -> Self {
self.outbound_proxy = Some(s.into());
self
}
pub fn without_outbound_proxy(mut self) -> Self {
self.suppress_outbound_proxy = true;
self
}
pub fn with_path(mut self, uri: impl Into<String>) -> Self {
self.path = Some(uri.into());
self
}
pub fn with_q_value(mut self, q: f32) -> Self {
self.q_value = Some(q);
self
}
pub fn with_sip_instance(mut self, urn: impl Into<String>) -> Self {
self.sip_instance = Some(urn.into());
self
}
pub fn with_reg_id(mut self, id: u32) -> Self {
self.reg_id = Some(id);
self
}
pub fn with_precomputed_authorization(mut self, s: impl Into<String>) -> Self {
self.precomputed_authorization = Some(s.into());
self
}
pub async fn send(mut self) -> Result<RegistrationHandle> {
let from_uri = self
.from_uri
.clone()
.unwrap_or_else(|| self.coord.config_local_uri());
let contact_uri = self
.contact_uri
.clone()
.unwrap_or_else(|| self.coord.config_contact_uri(&self.user));
let extra_headers = take_staged(&mut self.state);
self.coord
.register_with_extras(
&self.registrar,
&from_uri,
&contact_uri,
&self.user,
&self.password,
self.expires,
extra_headers,
)
.await
}
}
impl SipRequestOptions for RegisterBuilder {
fn method(&self) -> Method {
Method::Register
}
fn header_state_mut(&mut self) -> &mut BuilderHeaderState {
&mut self.state
}
fn header_state(&self) -> &BuilderHeaderState {
&self.state
}
}
pub struct RegisterRefreshBuilder {
coord: Arc<UnifiedCoordinator>,
handle: RegistrationHandle,
lifecycle_handle: Option<SessionRegistryHandle>,
expires: Option<u32>,
state: BuilderHeaderState,
}
impl RegisterRefreshBuilder {
pub(crate) fn new(
coord: Arc<UnifiedCoordinator>,
handle: RegistrationHandle,
lifecycle_handle: Option<SessionRegistryHandle>,
) -> Self {
Self {
coord,
handle,
lifecycle_handle,
expires: None,
state: BuilderHeaderState::default(),
}
}
pub fn with_expires(mut self, secs: u32) -> Self {
self.expires = Some(secs);
self
}
pub async fn send(mut self) -> Result<()> {
let lifecycle_handle = self.lifecycle_handle.take().ok_or_else(|| {
crate::errors::SessionError::SessionNotFound(format!(
"Session {} has no exact registration refresh authority",
self.handle.session_id
))
})?;
if lifecycle_handle.session_id() != &self.handle.session_id {
return Err(crate::errors::SessionError::InvalidTransition(
"captured registration refresh authority does not match its session".to_string(),
));
}
let extra_headers = take_staged(&mut self.state);
self.coord
.dispatch_registration_refresh_exact(&lifecycle_handle, self.expires, extra_headers)
.await?;
Ok(())
}
}
impl SipRequestOptions for RegisterRefreshBuilder {
fn method(&self) -> Method {
Method::Register
}
fn header_state_mut(&mut self) -> &mut BuilderHeaderState {
&mut self.state
}
fn header_state(&self) -> &BuilderHeaderState {
&self.state
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::unified::Config;
use crate::state_table::Role;
use crate::types::SessionId;
use std::time::Duration;
#[tokio::test]
async fn refresh_builder_cannot_cross_a_reused_session_generation() {
let coordinator = UnifiedCoordinator::new(Config::local("refresh-builder-generation", 0))
.await
.expect("create refresh builder coordinator");
let store = Arc::clone(&coordinator.helpers.state_machine.store);
let session_id = SessionId("refresh-builder-reused-id".to_string());
let created_a = store
.create_session(session_id.clone(), Role::UAC, false)
.await
.expect("create registration generation A");
let handle_a = created_a
.lifecycle_handle
.clone()
.expect("capture generation A lifecycle");
let public_handle = RegistrationHandle {
session_id: session_id.clone(),
};
let builder = coordinator.refresh(&public_handle);
store
.remove_session_exact(&handle_a)
.await
.expect("retire registration generation A");
assert!(store.authority().elapse_reuse_horizon_for_test(&session_id));
let created_b = store
.create_session(session_id.clone(), Role::UAC, false)
.await
.expect("create registration generation B");
let handle_b = created_b
.lifecycle_handle
.clone()
.expect("capture generation B lifecycle");
store
.update_session_exact_with(&handle_b, None, |session| {
session.registration_call_id = Some("generation-b-call-id".to_string());
session.registration_cseq = 77;
})
.expect("mark generation B registration identity");
let before = store
.get_session_snapshot_exact(&handle_b)
.expect("read generation B before stale builder");
assert!(builder.send().await.is_err());
let after = store
.get_session_snapshot_exact(&handle_b)
.expect("read generation B after stale builder");
assert_eq!(after.revision(), before.revision());
assert_eq!(after.registration_call_id, before.registration_call_id);
assert_eq!(after.registration_cseq, 77);
coordinator
.shutdown_gracefully(Some(Duration::from_secs(1)))
.await
.expect("shutdown refresh builder coordinator");
}
}