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}