ftth_rsipstack/dialog/registration.rs
1use super::{
2 authenticate::{handle_client_authenticate, Credential},
3 DialogId,
4};
5use crate::rsip;
6use crate::{
7 rsip_ext::RsipResponseExt,
8 transaction::{
9 endpoint::EndpointInnerRef,
10 key::{TransactionKey, TransactionRole},
11 make_tag,
12 transaction::Transaction,
13 },
14 transport::SipAddr,
15 Result,
16};
17use rsip::{
18 prelude::{HeadersExt, ToTypedHeader},
19 Response, SipMessage, StatusCode,
20};
21use tracing::{debug, info};
22
23/// SIP Registration Client
24///
25/// `Registration` provides functionality for SIP user agent registration
26/// with a SIP registrar server. Registration is the process by which a
27/// SIP user agent informs a registrar server of its current location
28/// and availability for receiving calls.
29///
30/// # Key Features
31///
32/// * **User Registration** - Registers user agent with SIP registrar
33/// * **Authentication Support** - Handles digest authentication challenges
34/// * **Contact Management** - Manages contact URI and expiration
35/// * **DNS Resolution** - Resolves registrar server addresses
36/// * **Automatic Retry** - Handles authentication challenges automatically
37///
38/// # Registration Process
39///
40/// 1. **DNS Resolution** - Resolves registrar server address
41/// 2. **REGISTER Request** - Sends initial REGISTER request
42/// 3. **Authentication** - Handles 401/407 challenges if needed
43/// 4. **Confirmation** - Receives 200 OK with registration details
44/// 5. **Refresh** - Periodically refreshes registration before expiration
45///
46/// # Examples
47///
48/// ## Basic Registration
49///
50/// ```rust,no_run
51/// # use ftth_rsipstack::dialog::registration::Registration;
52/// # use ftth_rsipstack::dialog::authenticate::Credential;
53/// # use ftth_rsipstack::transaction::endpoint::Endpoint;
54/// # async fn example() -> ftth_rsipstack::Result<()> {
55/// # let endpoint: Endpoint = todo!();
56/// let credential = Credential {
57/// username: "alice".to_string(),
58/// password: "secret123".to_string(),
59/// realm: Some("example.com".to_string()),
60/// };
61///
62/// let mut registration = Registration::new(endpoint.inner.clone(), Some(credential));
63/// let server = rsip::Uri::try_from("sip:sip.example.com").unwrap();
64/// let response = registration.register(server.clone(), None).await?;
65///
66/// if response.status_code == rsip::StatusCode::OK {
67/// println!("Registration successful");
68/// println!("Expires in: {} seconds", registration.expires());
69/// }
70/// # Ok(())
71/// }
72/// ```
73///
74/// ## Registration Loop
75///
76/// ```rust,no_run
77/// # use ftth_rsipstack::dialog::registration::Registration;
78/// # use ftth_rsipstack::dialog::authenticate::Credential;
79/// # use ftth_rsipstack::transaction::endpoint::Endpoint;
80/// # use std::time::Duration;
81/// # async fn example() -> ftth_rsipstack::Result<()> {
82/// # let endpoint: Endpoint = todo!();
83/// # let credential: Credential = todo!();
84/// # let server = rsip::Uri::try_from("sip:sip.example.com").unwrap();
85/// let mut registration = Registration::new(endpoint.inner.clone(), Some(credential));
86///
87/// loop {
88/// match registration.register(server.clone(), None).await {
89/// Ok(response) if response.status_code == rsip::StatusCode::OK => {
90/// let expires = registration.expires();
91/// println!("Registered for {} seconds", expires);
92///
93/// // Re-register before expiration (with some margin)
94/// tokio::time::sleep(Duration::from_secs((expires * 3 / 4) as u64)).await;
95/// },
96/// Ok(response) => {
97/// eprintln!("Registration failed: {}", response.status_code);
98/// tokio::time::sleep(Duration::from_secs(30)).await;
99/// },
100/// Err(e) => {
101/// eprintln!("Registration error: {}", e);
102/// tokio::time::sleep(Duration::from_secs(30)).await;
103/// }
104/// }
105/// }
106/// # Ok(())
107/// # }
108/// ```
109///
110/// # Thread Safety
111///
112/// Registration is not thread-safe and should be used from a single task.
113/// The sequence number and state are managed internally and concurrent
114/// access could lead to protocol violations.
115pub struct Registration {
116 pub last_seq: u32,
117 pub endpoint: EndpointInnerRef,
118 pub credential: Option<Credential>,
119 pub contact: Option<rsip::typed::Contact>,
120 pub allow: rsip::headers::Allow,
121 /// Public address detected by the server (IP and port)
122 pub public_address: Option<rsip::HostWithPort>,
123}
124
125impl Registration {
126 /// Create a new registration client
127 ///
128 /// Creates a new Registration instance for registering with a SIP server.
129 /// The registration will use the provided endpoint for network communication
130 /// and credentials for authentication if required.
131 ///
132 /// # Parameters
133 ///
134 /// * `endpoint` - Reference to the SIP endpoint for network operations
135 /// * `credential` - Optional authentication credentials
136 ///
137 /// # Returns
138 ///
139 /// A new Registration instance ready to perform registration
140 ///
141 /// # Examples
142 ///
143 /// ```rust,no_run
144 /// # use ftth_rsipstack::dialog::registration::Registration;
145 /// # use ftth_rsipstack::dialog::authenticate::Credential;
146 /// # use ftth_rsipstack::transaction::endpoint::Endpoint;
147 /// # fn example() {
148 /// # let endpoint: Endpoint = todo!();
149 /// // Registration without authentication
150 /// let registration = Registration::new(endpoint.inner.clone(), None);
151 ///
152 /// // Registration with authentication
153 /// let credential = Credential {
154 /// username: "alice".to_string(),
155 /// password: "secret123".to_string(),
156 /// realm: Some("example.com".to_string()),
157 /// };
158 /// let registration = Registration::new(endpoint.inner.clone(), Some(credential));
159 /// # }
160 /// ```
161 pub fn new(endpoint: EndpointInnerRef, credential: Option<Credential>) -> Self {
162 Self {
163 last_seq: 0,
164 endpoint,
165 credential,
166 contact: None,
167 allow: Default::default(),
168 public_address: None,
169 }
170 }
171
172 /// Get the discovered public address
173 ///
174 /// Returns the public IP address and port discovered during the registration
175 /// process. The SIP server indicates the client's public address through
176 /// the 'received' and 'rport' parameters in Via headers.
177 ///
178 /// This is essential for NAT traversal, as it allows the client to use
179 /// the correct public address in Contact headers and SDP for subsequent
180 /// dialogs and media sessions.
181 ///
182 /// # Returns
183 ///
184 /// * `Some((ip, port))` - The discovered public IP address and port
185 /// * `None` - No public address has been discovered yet
186 ///
187 /// # Examples
188 ///
189 /// ```rust,no_run
190 /// # use ftth_rsipstack::dialog::registration::Registration;
191 /// # async fn example() {
192 /// # let registration: Registration = todo!();
193 /// if let Some(public_address) = registration.discovered_public_address() {
194 /// println!("Public address: {}", public_address);
195 /// // Use this address for Contact headers in dialogs
196 /// } else {
197 /// println!("No public address discovered yet");
198 /// }
199 /// # }
200 /// ```
201 pub fn discovered_public_address(&self) -> Option<rsip::HostWithPort> {
202 self.public_address.clone()
203 }
204
205 /// Get the registration expiration time
206 ///
207 /// Returns the expiration time in seconds for the current registration.
208 /// This value is extracted from the Contact header's expires parameter
209 /// in the last successful registration response.
210 ///
211 /// # Returns
212 ///
213 /// Expiration time in seconds (default: 50 if not set)
214 ///
215 /// # Examples
216 ///
217 /// ```rust,no_run
218 /// # use ftth_rsipstack::dialog::registration::Registration;
219 /// # use std::time::Duration;
220 /// # async fn example() {
221 /// # let registration: Registration = todo!();
222 /// let expires = registration.expires();
223 /// println!("Registration expires in {} seconds", expires);
224 ///
225 /// // Schedule re-registration before expiration
226 /// let refresh_time = expires * 3 / 4; // 75% of expiration time
227 /// tokio::time::sleep(Duration::from_secs(refresh_time as u64)).await;
228 /// # }
229 /// ```
230 pub fn expires(&self) -> u32 {
231 self.contact
232 .as_ref()
233 .and_then(|c| c.expires())
234 .map(|e| e.seconds().unwrap_or(50))
235 .unwrap_or(50)
236 }
237
238 /// Perform SIP registration with the server
239 ///
240 /// Sends a REGISTER request to the specified SIP server to register
241 /// the user agent's current location. This method handles the complete
242 /// registration process including DNS resolution, authentication
243 /// challenges, and response processing.
244 ///
245 /// # Parameters
246 ///
247 /// * `server` - SIP server hostname or IP address (e.g., "sip.example.com")
248 ///
249 /// # Returns
250 ///
251 /// * `Ok(Response)` - Final response from the registration server
252 /// * `Err(Error)` - Registration failed due to network or protocol error
253 ///
254 /// # Registration Flow
255 ///
256 /// 1. **DNS Resolution** - Resolves server address and transport
257 /// 2. **Request Creation** - Creates REGISTER request with proper headers
258 /// 3. **Initial Send** - Sends the registration request
259 /// 4. **Authentication** - Handles 401/407 challenges if credentials provided
260 /// 5. **Response Processing** - Returns final response (200 OK or error)
261 ///
262 /// # Response Codes
263 ///
264 /// * `200 OK` - Registration successful
265 /// * `401 Unauthorized` - Authentication required (handled automatically)
266 /// * `403 Forbidden` - Registration not allowed
267 /// * `404 Not Found` - User not found
268 /// * `423 Interval Too Brief` - Requested expiration too short
269 ///
270 /// # Examples
271 ///
272 /// ## Successful Registration
273 ///
274 /// ```rust,no_run
275 /// # use ftth_rsipstack::dialog::registration::Registration;
276 /// # use rsip::prelude::HeadersExt;
277 /// # async fn example() -> ftth_rsipstack::Result<()> {
278 /// # let mut registration: Registration = todo!();
279 /// let server = rsip::Uri::try_from("sip:sip.example.com").unwrap();
280 /// let response = registration.register(server, None).await?;
281 ///
282 /// match response.status_code {
283 /// rsip::StatusCode::OK => {
284 /// println!("Registration successful");
285 /// // Extract registration details from response
286 /// if let Ok(_contact) = response.contact_header() {
287 /// println!("Registration confirmed");
288 /// }
289 /// },
290 /// rsip::StatusCode::Forbidden => {
291 /// println!("Registration forbidden");
292 /// },
293 /// _ => {
294 /// println!("Registration failed: {}", response.status_code);
295 /// }
296 /// }
297 /// # Ok(())
298 /// # }
299 /// ```
300 ///
301 /// ## Error Handling
302 ///
303 /// ```rust,no_run
304 /// # use ftth_rsipstack::dialog::registration::Registration;
305 /// # use ftth_rsipstack::Error;
306 /// # async fn example() {
307 /// # let mut registration: Registration = todo!();
308 /// # let server = rsip::Uri::try_from("sip:sip.example.com").unwrap();
309 /// match registration.register(server, None).await {
310 /// Ok(response) => {
311 /// // Handle response based on status code
312 /// },
313 /// Err(Error::DnsResolutionError(msg)) => {
314 /// eprintln!("DNS resolution failed: {}", msg);
315 /// },
316 /// Err(Error::TransportLayerError(msg, addr)) => {
317 /// eprintln!("Network error to {}: {}", addr, msg);
318 /// },
319 /// Err(e) => {
320 /// eprintln!("Registration error: {}", e);
321 /// }
322 /// }
323 /// # }
324 /// ```
325 ///
326 /// # Authentication
327 ///
328 /// If credentials are provided during Registration creation, this method
329 /// will automatically handle authentication challenges:
330 ///
331 /// 1. Send initial REGISTER request
332 /// 2. Receive 401/407 challenge with authentication parameters
333 /// 3. Calculate authentication response using provided credentials
334 /// 4. Resend REGISTER with Authorization header
335 /// 5. Receive final response
336 ///
337 /// # Contact Header
338 ///
339 /// The method will automatically update the Contact header with the public
340 /// address discovered during the registration process. This is essential
341 /// for proper NAT traversal in SIP communications.
342 ///
343 /// If you want to use a specific Contact header, you can set it manually
344 /// before calling this method.
345 ///
346 pub async fn register(&mut self, server: rsip::Uri, expires: Option<u32>) -> Result<Response> {
347 self.last_seq += 1;
348
349 let mut to = rsip::typed::To {
350 display_name: None,
351 uri: server.clone(),
352 params: vec![],
353 };
354
355 if let Some(cred) = &self.credential {
356 to.uri.auth = Some(rsip::auth::Auth {
357 user: cred.username.clone(),
358 password: None,
359 });
360 }
361
362 let form = rsip::typed::From {
363 display_name: None,
364 uri: to.uri.clone(),
365 params: vec![],
366 }
367 .with_tag(make_tag());
368
369 let via = self.endpoint.get_via(None, None)?;
370
371 // Contact address selection priority:
372 // 1. Contact header from REGISTER response (highest priority)
373 // - Most accurate as it reflects server's view of client address
374 // 2. Public address discovered during registration
375 // - Address detected from Via received parameter
376 // 3. Local non-loopback address (lowest priority)
377 // - Only used for initial registration attempt
378 // - Will be replaced by server-discovered address after first response
379 let contact = self.contact.clone().unwrap_or_else(|| {
380 let contact_host_with_port = self
381 .public_address
382 .clone()
383 .unwrap_or_else(|| via.uri.host_with_port.clone());
384 rsip::typed::Contact {
385 display_name: None,
386 uri: rsip::Uri {
387 auth: to.uri.auth.clone(),
388 scheme: Some(rsip::Scheme::Sip),
389 host_with_port: contact_host_with_port,
390 params: vec![],
391 headers: vec![],
392 },
393 params: vec![],
394 }
395 });
396 let mut request = self.endpoint.make_request(
397 rsip::Method::Register,
398 server,
399 via,
400 form,
401 to,
402 self.last_seq,
403 );
404
405 request.headers.unique_push(contact.into());
406 request.headers.unique_push(self.allow.clone().into());
407 if let Some(expires) = expires {
408 request
409 .headers
410 .unique_push(rsip::headers::Expires::from(expires).into());
411 }
412
413 let key = TransactionKey::from_request(&request, TransactionRole::Client)?;
414 let mut tx = Transaction::new_client(key, request, self.endpoint.clone(), None);
415
416 tx.send().await?;
417 let mut auth_sent = false;
418
419 while let Some(msg) = tx.receive().await {
420 match msg {
421 SipMessage::Response(resp) => match resp.status_code {
422 StatusCode::Trying => {
423 continue;
424 }
425 StatusCode::ProxyAuthenticationRequired | StatusCode::Unauthorized => {
426 let received = resp.via_received();
427 if self.public_address != received {
428 info!(
429 "Updated public address from 401 response, will use in authenticated request: {:?} -> {:?}",
430 self.public_address, received
431 );
432 self.public_address = received;
433 self.contact = None;
434 }
435
436 if auth_sent {
437 debug!("received {} response after auth sent", resp.status_code);
438 return Ok(resp);
439 }
440
441 if let Some(cred) = &self.credential {
442 self.last_seq += 1;
443
444 // Handle authentication with the existing transaction
445 // The contact will be updated in the next registration cycle if needed
446 tx = handle_client_authenticate(self.last_seq, tx, resp, cred).await?;
447
448 tx.send().await?;
449 auth_sent = true;
450 continue;
451 } else {
452 debug!("received {} response without credential", resp.status_code);
453 return Ok(resp);
454 }
455 }
456 StatusCode::OK => {
457 // Check if server indicated our public IP in Via header
458 let received = resp.via_received();
459 // Update contact header from response
460
461 match resp.contact_header() {
462 Ok(contact) => {
463 self.contact = contact.typed().ok();
464 }
465 Err(_) => {}
466 };
467 if self.public_address != received {
468 info!(
469 "Discovered public IP, will use for future registrations and calls: {:?} -> {:?}",
470 self.public_address, received
471 );
472 self.public_address = received;
473 }
474 info!(
475 "registration do_request done: {:?} {:?}",
476 resp.status_code,
477 self.contact.as_ref().map(|c| c.uri.to_string())
478 );
479 return Ok(resp);
480 }
481 _ => {
482 info!("registration do_request done: {:?}", resp.status_code);
483 return Ok(resp);
484 }
485 },
486 _ => break,
487 }
488 }
489 return Err(crate::Error::DialogError(
490 "registration transaction is already terminated".to_string(),
491 DialogId::try_from(&tx.original)?,
492 StatusCode::BadRequest,
493 ));
494 }
495
496 /// Create a NAT-aware Contact header with public address
497 ///
498 /// Creates a Contact header suitable for use in SIP dialogs that takes into
499 /// account the public address discovered during registration. This is essential
500 /// for proper NAT traversal in SIP communications.
501 ///
502 /// # Parameters
503 ///
504 /// * `username` - SIP username for the Contact URI
505 /// * `public_address` - Optional public address to use (IP and port)
506 /// * `local_address` - Fallback local address if no public address available
507 ///
508 /// # Returns
509 ///
510 /// A Contact header with appropriate address for NAT traversal
511 ///
512 /// # Examples
513 ///
514 /// ```rust,no_run
515 /// # use ftth_rsipstack::dialog::registration::Registration;
516 /// # use std::net::{IpAddr, Ipv4Addr};
517 /// # use ftth_rsipstack::transport::SipAddr;
518 /// # fn example() {
519 /// # let local_addr: SipAddr = todo!();
520 /// let contact = Registration::create_nat_aware_contact(
521 /// "alice",
522 /// Some(rsip::HostWithPort {
523 /// host: IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)).into(),
524 /// port: Some(5060.into()),
525 /// }),
526 /// &local_addr,
527 /// );
528 /// # }
529 /// ```
530 pub fn create_nat_aware_contact(
531 username: &str,
532 public_address: Option<rsip::HostWithPort>,
533 local_address: &SipAddr,
534 ) -> rsip::typed::Contact {
535 let contact_host_with_port = public_address.unwrap_or_else(|| local_address.clone().into());
536 let params = vec![];
537
538 // Don't add 'ob' parameter as it may confuse some SIP proxies
539 // and prevent proper ACK routing
540 // if public_address.is_some() {
541 // params.push(Param::Other("ob".into(), None));
542 // }
543
544 rsip::typed::Contact {
545 display_name: None,
546 uri: rsip::Uri {
547 scheme: Some(rsip::Scheme::Sip),
548 auth: Some(rsip::Auth {
549 user: username.to_string(),
550 password: None,
551 }),
552 host_with_port: contact_host_with_port,
553 params,
554 headers: vec![],
555 },
556 params: vec![],
557 }
558 }
559}