1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
//! `RegisterBuilder` / `RegisterRefreshBuilder` — SIP_API_DESIGN_2 §3.3.
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;
/// Outbound REGISTER builder (RFC 3261 §10). Reachable via
/// [`UnifiedCoordinator::register`](crate::api::unified::UnifiedCoordinator::register).
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(),
}
}
/// Set the registration lifetime via the `Expires:` header (seconds).
pub fn with_expires(mut self, secs: u32) -> Self {
self.expires = secs;
self
}
/// Override the `From:` URI (defaults to `Config.local_uri`).
pub fn with_from_uri(mut self, s: impl Into<String>) -> Self {
self.from_uri = Some(s.into());
self
}
/// Override the `Contact:` URI being registered.
pub fn with_contact_uri(mut self, s: impl Into<String>) -> Self {
self.contact_uri = Some(s.into());
self
}
/// Route the REGISTER through an outbound proxy `Route:`.
pub fn with_outbound_proxy(mut self, s: impl Into<String>) -> Self {
self.outbound_proxy = Some(s.into());
self
}
/// Suppress the outbound proxy `Route:` even when configured.
pub fn without_outbound_proxy(mut self) -> Self {
self.suppress_outbound_proxy = true;
self
}
/// Add an RFC 3327 `Path:` header.
pub fn with_path(mut self, uri: impl Into<String>) -> Self {
self.path = Some(uri.into());
self
}
/// Set the `Contact:` `q` value (RFC 3261 preference weighting).
pub fn with_q_value(mut self, q: f32) -> Self {
self.q_value = Some(q);
self
}
/// Set the RFC 5626 `+sip.instance` Contact parameter (instance URN).
pub fn with_sip_instance(mut self, urn: impl Into<String>) -> Self {
self.sip_instance = Some(urn.into());
self
}
/// Set the RFC 5626 `reg-id` Contact parameter.
pub fn with_reg_id(mut self, id: u32) -> Self {
self.reg_id = Some(id);
self
}
/// Pre-computed `Authorization:` header value, bypassing 401-driven
/// digest computation.
pub fn with_precomputed_authorization(mut self, s: impl Into<String>) -> Self {
self.precomputed_authorization = Some(s.into());
self
}
/// Send the REGISTER, returning a [`RegistrationHandle`] for refresh.
pub async fn send(mut self) -> Result<RegistrationHandle> {
let from_uri = self
.from_uri
.clone()
.unwrap_or_else(|| self.coord.config_local_uri());
// The Contact must be the reachable transport address so the registrar
// routes inbound calls back to us — default to the bound/advertised
// address (or an explicit `Config.contact_uri`), NOT the port-less AOR
// that `from_uri` carries. Override with `with_contact_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);
// SIP_API_DESIGN_2 §10 #19 — application-staged extras (raw
// `P-Asserted-Identity`, custom `X-*`, RFC 3327 `Path`, …) ride
// through rvoip-sip-dialog's `extra_headers` channel. The empty-extras
// case (auth-retry / 423-retry / plain register) takes the same
// path; the slice is just empty.
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
}
}
/// Builder that refreshes an existing registration, reusing the
/// original Call-ID / AoR / contact while incrementing CSeq.
pub struct RegisterRefreshBuilder {
coord: Arc<UnifiedCoordinator>,
handle: RegistrationHandle,
expires: Option<u32>,
state: BuilderHeaderState,
}
impl RegisterRefreshBuilder {
pub(crate) fn new(coord: Arc<UnifiedCoordinator>, handle: RegistrationHandle) -> Self {
Self {
coord,
handle,
expires: None,
state: BuilderHeaderState::default(),
}
}
/// Override the refresh `Expires:` (seconds); defaults to the
/// original registration's interval.
pub fn with_expires(mut self, secs: u32) -> Self {
self.expires = Some(secs);
self
}
/// Refresh the registration.
///
/// Stages a `RegisterRequestOptions { refresh: true, expires,
/// extra_headers, ... }` snapshot on the registration's session
/// and dispatches `EventType::SendOutboundRegister`. The state
/// table routes to `Action::SendREGISTERWithOptions` which drains
/// the stash via the dialog-adapter mirror. Call-ID is preserved
/// (RFC 3261 §10.2.4), CSeq incremented, and the requested
/// `Expires` carried verbatim.
pub async fn send(mut self) -> Result<()> {
// Read the existing registration's metadata off the session so
// the refresh REGISTER reuses the AoR / contact / registrar of
// the original registration.
let session = self
.coord
.session_state(&self.handle.session_id)
.await
.map_err(|_| {
crate::errors::SessionError::SessionNotFound(self.handle.session_id.to_string())
})?;
let registrar_uri = session.registrar_uri.clone().unwrap_or_default();
let contact_uri = session.registration_contact.clone().unwrap_or_default();
let aor_uri = session
.local_uri
.clone()
.unwrap_or_else(|| contact_uri.clone());
let expires = self
.expires
.or(session.registration_expires)
.unwrap_or(3600);
let call_id = session.registration_call_id.clone();
let cseq = if session.registration_cseq > 0 {
Some(session.registration_cseq + 1)
} else {
None
};
let extra_headers = take_staged(&mut self.state);
let opts = Arc::new(rvoip_sip_dialog::api::unified::RegisterRequestOptions {
registrar_uri,
aor_uri,
contact_uri,
expires,
authorization: None,
proxy_authorization: None,
call_id,
cseq,
outbound_contact: None,
outbound_proxy_uri: None,
extra_headers,
refresh: true,
});
self.coord
.stage_outbound_options(
&self.handle.session_id,
crate::state_machine::executor::PendingOptionsSlot::Register(opts),
)
.await?;
self.coord
.dispatch_outbound(
&self.handle.session_id,
crate::state_table::EventType::SendOutboundRegister,
)
.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
}
}