Skip to main content

rvoip_sip/api/
endpoint.rs

1//! Simplified endpoint API for softphones, PBX accounts, demos, and IVR legs.
2//!
3//! [`Endpoint`] is the easiest rvoip-sip surface to start with. It wraps
4//! [`StreamPeer`], keeps the existing [`SessionHandle`] and [`IncomingCall`]
5//! types, and adds only the account/profile conveniences that SIP applications
6//! usually need first.
7//!
8//! For PBX or SBC integrations that require non-standard or vendor INVITE
9//! headers, call `endpoint.invite(to).with_extra_headers(...).send()` to
10//! attach a caller-supplied `Vec<TypedHeader>` to the first INVITE.
11
12#![deny(missing_docs)]
13
14use std::fmt;
15use std::fs;
16use std::net::{IpAddr, Ipv4Addr, SocketAddr};
17use std::path::{Path, PathBuf};
18use std::str::FromStr;
19use std::sync::Arc;
20use std::time::Duration;
21
22use serde::Deserialize;
23use tokio::sync::Mutex;
24
25use rvoip_sip_core::types::uri::{Scheme, Uri};
26
27use crate::api::audio::{AudioReceiver, AudioSender, AudioStream};
28use crate::api::events::Event;
29use crate::api::handle::{CallId, SessionHandle};
30use crate::api::incoming::{IncomingCall, IncomingCallGuard};
31use crate::api::performance::PerformanceConfig;
32use crate::api::stream_peer::{EventReceiver, PeerControl, StreamPeer};
33use crate::api::unified::{
34    Config, MediaMode, Registration, RegistrationHandle, RegistrationInfo, RegistrationStatus,
35    SipTlsMode,
36};
37use crate::auth::SipClientAuth;
38use crate::errors::{Result, SessionError};
39use crate::types::Credentials;
40
41/// A simplified SIP endpoint built on top of [`StreamPeer`].
42///
43/// Use `Endpoint` when an application wants a compact softphone/PBX-account
44/// style API without losing access to the underlying stream/control objects.
45/// Advanced applications can call [`control`](Self::control) or
46/// [`into_stream_peer`](Self::into_stream_peer) and continue with the lower
47/// level APIs.
48pub struct Endpoint {
49    peer: StreamPeer,
50    registration: Option<Registration>,
51    registration_handle: SharedRegistrationHandle,
52    registrar: Option<String>,
53    transport: EndpointTransport,
54}
55
56impl Endpoint {
57    /// Start a new [`EndpointBuilder`].
58    ///
59    /// # Examples
60    ///
61    /// ```rust,no_run
62    /// # async fn example() -> rvoip_sip::Result<()> {
63    /// let endpoint = rvoip_sip::Endpoint::builder()
64    ///     .name("alice")
65    ///     .build()
66    ///     .await?;
67    /// endpoint.shutdown().await?;
68    /// # Ok(())
69    /// # }
70    /// ```
71    pub fn builder() -> EndpointBuilder {
72        EndpointBuilder::new()
73    }
74
75    /// Build and start an endpoint from a serde-friendly configuration object.
76    ///
77    /// # Examples
78    ///
79    /// ```rust,no_run
80    /// # async fn example(config: rvoip_sip::EndpointConfig) -> rvoip_sip::Result<()> {
81    /// let endpoint = rvoip_sip::Endpoint::from_config(config).await?;
82    /// endpoint.shutdown().await?;
83    /// # Ok(())
84    /// # }
85    /// ```
86    pub async fn from_config(config: EndpointConfig) -> Result<Self> {
87        EndpointBuilder::from_config(config)?.build().await
88    }
89
90    /// Load endpoint configuration from a JSON file and start the endpoint.
91    ///
92    /// # Examples
93    ///
94    /// ```rust,no_run
95    /// # async fn example() -> rvoip_sip::Result<()> {
96    /// let endpoint = rvoip_sip::Endpoint::from_json_file("alice.json").await?;
97    /// endpoint.shutdown().await?;
98    /// # Ok(())
99    /// # }
100    /// ```
101    pub async fn from_json_file(path: impl AsRef<Path>) -> Result<Self> {
102        let text = fs::read_to_string(path.as_ref()).map_err(|err| {
103            SessionError::ConfigError(format!(
104                "failed to read endpoint JSON config '{}': {err}",
105                path.as_ref().display()
106            ))
107        })?;
108        let config = serde_json::from_str::<EndpointConfig>(&text).map_err(|err| {
109            SessionError::ConfigError(format!(
110                "failed to parse endpoint JSON config '{}': {err}",
111                path.as_ref().display()
112            ))
113        })?;
114        Self::from_config(config).await
115    }
116
117    /// Register the configured account with its registrar.
118    ///
119    /// Repeated calls return the existing registration handle. Build the
120    /// endpoint with [`EndpointBuilder::account`],
121    /// [`EndpointBuilder::password`], and [`EndpointBuilder::registrar`] or
122    /// with [`EndpointBuilder::endpoint_account`] before calling this method.
123    pub async fn register(&mut self) -> Result<RegistrationHandle> {
124        let mut stored = self.registration_handle.lock().await;
125        if let Some(handle) = stored.as_ref() {
126            return Ok(handle.clone());
127        }
128
129        let registration = self.registration.clone().ok_or_else(|| {
130            SessionError::ConfigError(
131                "Endpoint has no complete registration account; set account, password, and registrar"
132                    .to_string(),
133            )
134        })?;
135        let mut b = self
136            .peer
137            .register(
138                registration.registrar.clone(),
139                registration.username.clone(),
140                registration.password.clone(),
141            )
142            .with_expires(registration.expires);
143        if let Some(from) = registration.from_uri.clone() {
144            b = b.with_from_uri(from);
145        }
146        if let Some(contact) = registration.contact_uri.clone() {
147            b = b.with_contact_uri(contact);
148        }
149        let handle = b.send().await?;
150        *stored = Some(handle.clone());
151        Ok(handle)
152    }
153
154    /// Register the configured account and wait for registrar confirmation.
155    pub async fn register_and_wait(
156        &mut self,
157        timeout: Option<Duration>,
158    ) -> Result<EndpointRegistrationInfo> {
159        let mut events = self.events().await?;
160        let handle = self.register().await?;
161        wait_for_registration_result(&mut events, &handle, timeout).await
162    }
163
164    /// Unregister the current account if it has been registered.
165    ///
166    /// Calling this on an endpoint that has not registered is a no-op.
167    pub async fn unregister(&mut self) -> Result<()> {
168        let mut stored = self.registration_handle.lock().await;
169        if let Some(handle) = stored.take() {
170            self.peer.unregister(&handle).await?;
171        }
172        Ok(())
173    }
174
175    /// Initiate an outgoing call and wait for it to answer.
176    pub async fn call_and_wait(
177        &self,
178        target: &str,
179        timeout: Option<Duration>,
180    ) -> Result<EndpointCall> {
181        let call_id = self.invite(target)?.send().await?;
182        let call = self.wrap_call(call_id);
183        call.wait_for_answered(timeout).await
184    }
185
186    /// Wait for the next incoming call.
187    pub async fn wait_for_incoming(&mut self) -> Result<EndpointIncomingCall> {
188        let incoming = self.peer.wait_for_incoming().await?;
189        Ok(EndpointIncomingCall::new(
190            incoming,
191            self.registrar.clone(),
192            self.transport,
193        ))
194    }
195
196    /// Subscribe to endpoint-level events without consuming the endpoint.
197    pub async fn events(&self) -> Result<EndpointEvents> {
198        let events = self.peer.control().subscribe_events().await?;
199        Ok(EndpointEvents::new(
200            events,
201            self.peer.control().clone(),
202            self.registrar.clone(),
203            self.transport,
204        ))
205    }
206
207    /// Split the endpoint into cloneable controls and an endpoint event stream.
208    pub fn split(self) -> (EndpointControl, EndpointEvents) {
209        let registration = self.registration;
210        let registration_handle = self.registration_handle;
211        let registrar = self.registrar;
212        let transport = self.transport;
213        let (control, events) = self.peer.split();
214        let endpoint_control = EndpointControl::new(
215            control.clone(),
216            registration,
217            registration_handle,
218            registrar.clone(),
219            transport,
220        );
221        let endpoint_events = EndpointEvents::new(events, control, registrar, transport);
222        (endpoint_control, endpoint_events)
223    }
224
225    /// Access the command half of the wrapped [`StreamPeer`].
226    pub fn control(&self) -> &PeerControl {
227        self.peer.control()
228    }
229
230    /// Resolve a dial target the same way [`invite`](Self::invite) does.
231    ///
232    /// This is useful for logging or for handing the resolved URI to a lower
233    /// level API.
234    pub fn resolve_target(&self, target: &str) -> Result<String> {
235        normalize_target(self.registrar.as_deref(), target, self.transport)
236    }
237
238    /// Begin building an outbound INVITE from this endpoint's
239    /// registered AOR (or `local_uri`). Resolves bare extensions
240    /// through the configured registrar. Returns an
241    /// [`OutboundCallBuilder`](crate::api::send::OutboundCallBuilder).
242    ///
243    /// Returns `Err` only if the target can't be normalized into a SIP
244    /// URI (e.g. a bare extension without a configured registrar).
245    pub fn invite(&self, target: &str) -> Result<crate::api::send::OutboundCallBuilder> {
246        let resolved = self.resolve_target(target)?;
247        Ok(self.peer.control().invite(resolved))
248    }
249
250    /// Materialize an [`EndpointCall`] for a `CallId` returned by
251    /// [`invite(...).send()`](Self::invite). Pairs with `invite()` the
252    /// same way the unified coordinator's `session(...)` pairs with its
253    /// bare builder — gives back the rich call wrapper around the raw
254    /// [`SessionHandle`].
255    pub fn wrap_call(&self, call_id: crate::api::handle::CallId) -> EndpointCall {
256        let coord = self.peer.control().coordinator().clone();
257        EndpointCall::new(
258            crate::api::handle::SessionHandle::new(call_id, coord),
259            self.registrar.clone(),
260            self.transport,
261        )
262    }
263
264    /// Consume this endpoint and return the wrapped [`StreamPeer`].
265    pub fn into_stream_peer(self) -> StreamPeer {
266        self.peer
267    }
268
269    /// Gracefully unregister and shut down the endpoint.
270    pub async fn shutdown(self) -> Result<()> {
271        self.peer.shutdown().await
272    }
273}
274
275type SharedRegistrationHandle = Arc<Mutex<Option<RegistrationHandle>>>;
276
277/// Cloneable command half returned by [`Endpoint::split`].
278#[derive(Clone)]
279pub struct EndpointControl {
280    control: PeerControl,
281    registration: Option<Registration>,
282    registration_handle: SharedRegistrationHandle,
283    registrar: Option<String>,
284    transport: EndpointTransport,
285}
286
287impl EndpointControl {
288    fn new(
289        control: PeerControl,
290        registration: Option<Registration>,
291        registration_handle: SharedRegistrationHandle,
292        registrar: Option<String>,
293        transport: EndpointTransport,
294    ) -> Self {
295        Self {
296            control,
297            registration,
298            registration_handle,
299            registrar,
300            transport,
301        }
302    }
303
304    /// Register the configured account.
305    pub async fn register(&self) -> Result<()> {
306        let mut stored = self.registration_handle.lock().await;
307        if stored.is_some() {
308            return Ok(());
309        }
310        let registration = self.registration.clone().ok_or_else(|| {
311            SessionError::ConfigError(
312                "Endpoint has no complete registration account; set account, password, and registrar"
313                    .to_string(),
314            )
315        })?;
316        let mut b = self
317            .control
318            .coordinator()
319            .register(
320                registration.registrar,
321                registration.username,
322                registration.password,
323            )
324            .with_expires(registration.expires);
325        if let Some(from) = registration.from_uri {
326            b = b.with_from_uri(from);
327        }
328        if let Some(contact) = registration.contact_uri {
329            b = b.with_contact_uri(contact);
330        }
331        let handle = b.send().await?;
332        *stored = Some(handle);
333        Ok(())
334    }
335
336    /// Register and wait for a registrar success or failure event.
337    pub async fn register_and_wait(
338        &self,
339        timeout: Option<Duration>,
340    ) -> Result<EndpointRegistrationInfo> {
341        let mut events = self.events().await?;
342        self.register().await?;
343        let handle = self
344            .registration_handle
345            .lock()
346            .await
347            .clone()
348            .ok_or_else(|| SessionError::Other("registration handle missing".to_string()))?;
349        wait_for_registration_result(&mut events, &handle, timeout).await
350    }
351
352    /// Return the current registration information, if this endpoint registered.
353    pub async fn registration_info(&self) -> Result<Option<EndpointRegistrationInfo>> {
354        let handle = self.registration_handle.lock().await.clone();
355        match handle {
356            Some(handle) => self
357                .control
358                .coordinator()
359                .registration_info(&handle)
360                .await
361                .map(EndpointRegistrationInfo::from)
362                .map(Some),
363            None => Ok(None),
364        }
365    }
366
367    /// Unregister the current account, if registered.
368    pub async fn unregister(&self) -> Result<()> {
369        if let Some(handle) = self.registration_handle.lock().await.take() {
370            self.control.coordinator().unregister(&handle).await?;
371        }
372        Ok(())
373    }
374
375    /// Unregister and wait for registrar confirmation.
376    pub async fn unregister_and_wait(&self, timeout: Option<Duration>) -> Result<()> {
377        if let Some(handle) = self.registration_handle.lock().await.take() {
378            self.control
379                .coordinator()
380                .unregister_and_wait(&handle, timeout)
381                .await?;
382        }
383        Ok(())
384    }
385
386    /// Subscribe to Endpoint-level events.
387    pub async fn events(&self) -> Result<EndpointEvents> {
388        let events = self.control.subscribe_events().await?;
389        Ok(EndpointEvents::new(
390            events,
391            self.control.clone(),
392            self.registrar.clone(),
393            self.transport,
394        ))
395    }
396
397    /// Resolve a dial target using this endpoint's account context.
398    pub fn resolve_target(&self, target: &str) -> Result<String> {
399        normalize_target(self.registrar.as_deref(), target, self.transport)
400    }
401
402    /// Begin building an outbound INVITE from this endpoint's
403    /// account context. Resolves bare extensions through the configured
404    /// registrar.
405    pub fn invite(&self, target: &str) -> Result<crate::api::send::OutboundCallBuilder> {
406        let resolved = self.resolve_target(target)?;
407        Ok(self.control.invite(resolved))
408    }
409
410    /// Materialize an [`EndpointCall`] for a `CallId` returned by
411    /// [`invite(...).send()`](Self::invite).
412    pub fn wrap_call(&self, call_id: crate::api::handle::CallId) -> EndpointCall {
413        let coord = self.control.coordinator().clone();
414        EndpointCall::new(
415            crate::api::handle::SessionHandle::new(call_id, coord),
416            self.registrar.clone(),
417            self.transport,
418        )
419    }
420
421    /// Gracefully shut down the endpoint runtime.
422    pub async fn shutdown(&self) -> Result<()> {
423        self.control.coordinator().shutdown_gracefully(None).await
424    }
425}
426
427/// Endpoint-level event stream returned by [`Endpoint::split`] and [`Endpoint::events`].
428pub struct EndpointEvents {
429    events: EventReceiver,
430    control: PeerControl,
431    registrar: Option<String>,
432    transport: EndpointTransport,
433}
434
435impl EndpointEvents {
436    fn new(
437        events: EventReceiver,
438        control: PeerControl,
439        registrar: Option<String>,
440        transport: EndpointTransport,
441    ) -> Self {
442        Self {
443            events,
444            control,
445            registrar,
446            transport,
447        }
448    }
449
450    /// Wait for the next endpoint event.
451    pub async fn next(&mut self) -> Result<Option<EndpointEvent>> {
452        Ok(self.events.next().await.map(|event| self.map_event(event)))
453    }
454
455    /// Return the next endpoint event if one is ready immediately.
456    pub fn try_next(&mut self) -> Option<EndpointEvent> {
457        self.events.try_next().map(|event| self.map_event(event))
458    }
459
460    fn map_event(&self, event: Event) -> EndpointEvent {
461        match event {
462            Event::IncomingCall {
463                call_id,
464                from,
465                to,
466                sdp,
467            } => {
468                let incoming =
469                    IncomingCall::new(call_id, from, to, sdp, self.control.coordinator().clone());
470                EndpointEvent::IncomingCall(EndpointIncomingCall::new(
471                    incoming,
472                    self.registrar.clone(),
473                    self.transport,
474                ))
475            }
476            Event::CallProgress {
477                call_id,
478                status_code,
479                reason,
480                sdp,
481            } => EndpointEvent::CallProgress {
482                call_id: EndpointCallId(call_id),
483                status_code,
484                reason,
485                has_sdp: sdp.is_some(),
486            },
487            Event::CallAnswered { call_id, sdp } => EndpointEvent::CallAnswered {
488                call: EndpointCall::new(
489                    SessionHandle::new(call_id, self.control.coordinator().clone()),
490                    self.registrar.clone(),
491                    self.transport,
492                ),
493                has_sdp: sdp.is_some(),
494            },
495            Event::CallEnded { call_id, reason } => EndpointEvent::CallEnded {
496                call_id: EndpointCallId(call_id),
497                reason,
498            },
499            Event::CallFailed {
500                call_id,
501                status_code,
502                reason,
503            } => EndpointEvent::CallFailed {
504                call_id: EndpointCallId(call_id),
505                status_code,
506                reason,
507            },
508            Event::CallCancelled { call_id } => EndpointEvent::CallCancelled {
509                call_id: EndpointCallId(call_id),
510            },
511            Event::CallOnHold { call_id } => EndpointEvent::LocalHold {
512                call_id: EndpointCallId(call_id),
513            },
514            Event::CallResumed { call_id } => EndpointEvent::LocalResume {
515                call_id: EndpointCallId(call_id),
516            },
517            Event::RemoteCallOnHold { call_id } => EndpointEvent::RemoteHold {
518                call_id: EndpointCallId(call_id),
519            },
520            Event::RemoteCallResumed { call_id } => EndpointEvent::RemoteResume {
521                call_id: EndpointCallId(call_id),
522            },
523            Event::DtmfReceived { call_id, digit } => EndpointEvent::DtmfReceived {
524                call_id: EndpointCallId(call_id),
525                digit,
526            },
527            Event::RegistrationSuccess {
528                registrar,
529                expires,
530                contact,
531            } => EndpointEvent::RegistrationChanged(EndpointRegistrationInfo {
532                status: EndpointRegistrationStatus::Registered,
533                registrar: Some(registrar),
534                contact: Some(contact),
535                expires_secs: Some(expires),
536                accepted_expires_secs: Some(expires),
537                next_refresh_in: None,
538                retry_count: 0,
539                last_failure: None,
540            }),
541            Event::RegistrationFailed {
542                registrar,
543                status_code,
544                reason,
545            } => EndpointEvent::RegistrationChanged(EndpointRegistrationInfo {
546                status: EndpointRegistrationStatus::Failed,
547                registrar: Some(registrar),
548                contact: None,
549                expires_secs: None,
550                accepted_expires_secs: None,
551                next_refresh_in: None,
552                retry_count: 0,
553                last_failure: Some(format!("{status_code} {reason}")),
554            }),
555            Event::UnregistrationSuccess { registrar } => {
556                EndpointEvent::RegistrationChanged(EndpointRegistrationInfo {
557                    status: EndpointRegistrationStatus::Unregistered,
558                    registrar: Some(registrar),
559                    contact: None,
560                    expires_secs: None,
561                    accepted_expires_secs: None,
562                    next_refresh_in: None,
563                    retry_count: 0,
564                    last_failure: None,
565                })
566            }
567            Event::UnregistrationFailed { registrar, reason } => {
568                EndpointEvent::RegistrationChanged(EndpointRegistrationInfo {
569                    status: EndpointRegistrationStatus::Failed,
570                    registrar: Some(registrar),
571                    contact: None,
572                    expires_secs: None,
573                    accepted_expires_secs: None,
574                    next_refresh_in: None,
575                    retry_count: 0,
576                    last_failure: Some(reason),
577                })
578            }
579            Event::NetworkError { call_id, error } => EndpointEvent::NetworkError {
580                call_id: call_id.map(EndpointCallId),
581                error,
582            },
583            Event::SipTrace(trace) => EndpointEvent::SipTrace(EndpointSipTrace {
584                direction: trace.direction,
585                transport: trace.transport,
586                local_addr: trace.local_addr,
587                remote_addr: trace.remote_addr,
588                timestamp_unix_millis: trace.timestamp_unix_millis,
589                start_line: trace.start_line,
590                sip_call_id: trace.sip_call_id,
591                session_id: trace.session_id.map(EndpointCallId),
592                raw_message: trace.raw_message,
593                original_len: trace.original_len,
594                truncated: trace.truncated,
595                redacted: trace.redacted,
596            }),
597            other => EndpointEvent::Info {
598                call_id: other.call_id().cloned().map(EndpointCallId),
599                message: format!("{other:?}"),
600            },
601        }
602    }
603}
604
605/// Endpoint-level event type for softphone applications.
606pub enum EndpointEvent {
607    /// A new inbound call is ringing.
608    IncomingCall(EndpointIncomingCall),
609    /// An outgoing call received provisional progress.
610    CallProgress {
611        /// Call identifier.
612        call_id: EndpointCallId,
613        /// SIP status code.
614        status_code: u16,
615        /// SIP reason phrase.
616        reason: String,
617        /// Whether the event included SDP.
618        has_sdp: bool,
619    },
620    /// A call was answered and is now controllable.
621    CallAnswered {
622        /// Active call handle.
623        call: EndpointCall,
624        /// Whether the event included SDP.
625        has_sdp: bool,
626    },
627    /// A call ended.
628    CallEnded {
629        /// Call identifier.
630        call_id: EndpointCallId,
631        /// End reason.
632        reason: String,
633    },
634    /// A call failed.
635    CallFailed {
636        /// Call identifier.
637        call_id: EndpointCallId,
638        /// SIP status code.
639        status_code: u16,
640        /// Failure reason.
641        reason: String,
642    },
643    /// A ringing incoming call was cancelled by the caller.
644    CallCancelled {
645        /// Call identifier.
646        call_id: EndpointCallId,
647    },
648    /// Local hold completed.
649    LocalHold {
650        /// Call identifier.
651        call_id: EndpointCallId,
652    },
653    /// Local resume completed.
654    LocalResume {
655        /// Call identifier.
656        call_id: EndpointCallId,
657    },
658    /// Remote hold was observed.
659    RemoteHold {
660        /// Call identifier.
661        call_id: EndpointCallId,
662    },
663    /// Remote resume was observed.
664    RemoteResume {
665        /// Call identifier.
666        call_id: EndpointCallId,
667    },
668    /// DTMF was received.
669    DtmfReceived {
670        /// Call identifier.
671        call_id: EndpointCallId,
672        /// Received digit.
673        digit: char,
674    },
675    /// Registration state changed.
676    RegistrationChanged(EndpointRegistrationInfo),
677    /// SIP message observed at the transport boundary.
678    SipTrace(EndpointSipTrace),
679    /// A network error occurred.
680    NetworkError {
681        /// Call identifier, when known.
682        call_id: Option<EndpointCallId>,
683        /// Error text.
684        error: String,
685    },
686    /// Informational event not otherwise modeled by the endpoint facade.
687    Info {
688        /// Call identifier, when known.
689        call_id: Option<EndpointCallId>,
690        /// Human-readable event summary.
691        message: String,
692    },
693}
694
695/// Endpoint-level SIP trace event.
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub struct EndpointSipTrace {
698    /// Inbound or outbound at the local transport boundary.
699    pub direction: crate::api::events::SipTraceDirection,
700    /// Transport flavour, for example `UDP`, `TCP`, or `TLS`.
701    pub transport: String,
702    /// Local socket address.
703    pub local_addr: String,
704    /// Remote socket address.
705    pub remote_addr: String,
706    /// Milliseconds since Unix epoch when the trace event was created.
707    pub timestamp_unix_millis: u64,
708    /// SIP start line.
709    pub start_line: String,
710    /// Wire-level SIP `Call-ID` header value when present.
711    pub sip_call_id: Option<String>,
712    /// Endpoint call/session id after mapping, when known.
713    pub session_id: Option<EndpointCallId>,
714    /// Redacted, optionally body-stripped SIP message text.
715    pub raw_message: String,
716    /// Original rendered message byte length before redaction/body stripping/truncation.
717    pub original_len: usize,
718    /// Whether `raw_message` was truncated for bounded diagnostics.
719    pub truncated: bool,
720    /// Whether sensitive headers were redacted.
721    pub redacted: bool,
722}
723
724/// Opaque call identifier for Endpoint applications.
725#[derive(Debug, Clone, PartialEq, Eq, Hash)]
726pub struct EndpointCallId(CallId);
727
728impl fmt::Display for EndpointCallId {
729    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730        self.0.fmt(f)
731    }
732}
733
734/// Active call handle returned by Endpoint APIs.
735#[derive(Clone)]
736pub struct EndpointCall {
737    handle: SessionHandle,
738    registrar: Option<String>,
739    transport: EndpointTransport,
740}
741
742impl EndpointCall {
743    fn new(handle: SessionHandle, registrar: Option<String>, transport: EndpointTransport) -> Self {
744        Self {
745            handle,
746            registrar,
747            transport,
748        }
749    }
750
751    /// Return this call's opaque identifier.
752    pub fn id(&self) -> EndpointCallId {
753        EndpointCallId(self.handle.id().clone())
754    }
755
756    /// Return the underlying session handle for advanced operations that are
757    /// not yet modeled directly on the endpoint facade.
758    pub fn as_session_handle(&self) -> &SessionHandle {
759        &self.handle
760    }
761
762    /// Wait for this outgoing call to be answered.
763    pub async fn wait_for_answered(&self, timeout: Option<Duration>) -> Result<Self> {
764        let handle = self.handle.wait_for_answered(timeout).await?;
765        Ok(Self::new(handle, self.registrar.clone(), self.transport))
766    }
767
768    /// Wait for this call to end.
769    pub async fn wait_for_end(&self, timeout: Option<Duration>) -> Result<String> {
770        self.handle.wait_for_end(timeout).await
771    }
772
773    /// Hang up the call.
774    pub async fn hangup(&self) -> Result<()> {
775        self.handle.hangup().await
776    }
777
778    /// Hang up the call and wait for teardown.
779    pub async fn hangup_and_wait(&self, timeout: Option<Duration>) -> Result<String> {
780        self.handle.hangup_and_wait(timeout).await
781    }
782
783    /// Put the call on local hold.
784    pub async fn hold(&self) -> Result<()> {
785        self.handle.hold().await
786    }
787
788    /// Resume a locally held call.
789    pub async fn resume(&self) -> Result<()> {
790        self.handle.resume().await
791    }
792
793    /// Mute local microphone media for the call.
794    pub async fn mute(&self) -> Result<()> {
795        self.handle.mute().await
796    }
797
798    /// Unmute local microphone media for the call.
799    pub async fn unmute(&self) -> Result<()> {
800        self.handle.unmute().await
801    }
802
803    /// Send an RFC 4733 DTMF digit.
804    pub async fn send_dtmf(&self, digit: char) -> Result<()> {
805        self.handle.send_dtmf(digit).await
806    }
807
808    /// Blind-transfer the call using Endpoint target resolution.
809    pub async fn transfer(&self, target: &str) -> Result<()> {
810        let target = normalize_target(self.registrar.as_deref(), target, self.transport)?;
811        self.handle.transfer_blind(&target).await
812    }
813
814    /// Open the call's bidirectional audio stream.
815    pub async fn audio(&self) -> Result<EndpointAudio> {
816        self.handle.audio().await.map(EndpointAudio::new)
817    }
818}
819
820impl fmt::Debug for EndpointCall {
821    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
822        f.debug_struct("EndpointCall")
823            .field("id", &self.id().to_string())
824            .finish()
825    }
826}
827
828/// Inbound call presented by [`EndpointEvent::IncomingCall`].
829pub struct EndpointIncomingCall {
830    incoming: IncomingCall,
831    registrar: Option<String>,
832    transport: EndpointTransport,
833}
834
835impl EndpointIncomingCall {
836    fn new(
837        incoming: IncomingCall,
838        registrar: Option<String>,
839        transport: EndpointTransport,
840    ) -> Self {
841        Self {
842            incoming,
843            registrar,
844            transport,
845        }
846    }
847
848    /// Return the inbound call identifier.
849    pub fn id(&self) -> EndpointCallId {
850        EndpointCallId(self.incoming.call_id.clone())
851    }
852
853    /// Return the caller URI.
854    pub fn from(&self) -> &str {
855        &self.incoming.from
856    }
857
858    /// Return the called URI.
859    pub fn to(&self) -> &str {
860        &self.incoming.to
861    }
862
863    /// Answer the incoming call.
864    pub async fn answer(self) -> Result<EndpointCall> {
865        let handle = self.incoming.accept().await?;
866        Ok(EndpointCall::new(handle, self.registrar, self.transport))
867    }
868
869    /// Alias for [`answer`](Self::answer).
870    pub async fn accept(self) -> Result<EndpointCall> {
871        self.answer().await
872    }
873
874    /// Defer the incoming call decision and return a guard.
875    pub fn defer(self, watchdog: Duration) -> IncomingCallGuard {
876        self.incoming.defer(watchdog)
877    }
878
879    /// Reject the call with 603 Decline.
880    pub async fn decline(self) -> Result<()> {
881        self.reject(603, "Decline").await
882    }
883
884    /// Reject the call with 486 Busy Here.
885    pub async fn busy(self) -> Result<()> {
886        self.reject(486, "Busy Here").await
887    }
888
889    /// Reject the call with an explicit SIP status and reason phrase.
890    pub async fn reject(self, status: u16, reason: &str) -> Result<()> {
891        self.incoming.reject(status, reason);
892        Ok(())
893    }
894
895    /// Redirect the caller to another SIP URI with `302 Moved Temporarily`.
896    pub async fn redirect_to(self, target: impl Into<String>) -> Result<()> {
897        self.incoming.redirect_to(target).await
898    }
899
900    /// Redirect the caller with an explicit 3xx status and Contact list.
901    pub async fn redirect_with_contacts<I, S>(self, status: u16, contacts: I) -> Result<()>
902    where
903        I: IntoIterator<Item = S>,
904        S: Into<String>,
905    {
906        self.incoming.redirect_with_contacts(status, contacts).await
907    }
908}
909
910impl fmt::Debug for EndpointIncomingCall {
911    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
912        f.debug_struct("EndpointIncomingCall")
913            .field("id", &self.id().to_string())
914            .field("from", &self.from())
915            .field("to", &self.to())
916            .finish()
917    }
918}
919
920/// Bidirectional endpoint audio stream for a call.
921pub struct EndpointAudio {
922    stream: AudioStream,
923}
924
925impl EndpointAudio {
926    fn new(stream: AudioStream) -> Self {
927        Self { stream }
928    }
929
930    /// Split the audio stream into sender and receiver halves.
931    pub fn split(self) -> (EndpointAudioSender, EndpointAudioReceiver) {
932        let (sender, receiver) = self.stream.split();
933        (
934            EndpointAudioSender { sender },
935            EndpointAudioReceiver { receiver },
936        )
937    }
938}
939
940/// Send half of endpoint call audio.
941#[derive(Clone)]
942pub struct EndpointAudioSender {
943    sender: AudioSender,
944}
945
946impl EndpointAudioSender {
947    /// Send one audio frame to the remote party.
948    pub async fn send(&self, frame: EndpointAudioFrame) -> Result<()> {
949        self.sender.send(frame.into()).await
950    }
951
952    /// Return whether the underlying audio channel is open.
953    pub fn is_open(&self) -> bool {
954        self.sender.is_open()
955    }
956}
957
958/// Receive half of endpoint call audio.
959pub struct EndpointAudioReceiver {
960    receiver: AudioReceiver,
961}
962
963impl EndpointAudioReceiver {
964    /// Wait for the next audio frame from the remote party.
965    pub async fn recv(&mut self) -> Option<EndpointAudioFrame> {
966        self.receiver.recv().await.map(EndpointAudioFrame::from)
967    }
968
969    /// Try to receive an audio frame without blocking.
970    pub fn try_recv(&mut self) -> Option<EndpointAudioFrame> {
971        self.receiver.try_recv().map(EndpointAudioFrame::from)
972    }
973}
974
975/// Mono or interleaved PCM16 audio frame used by Endpoint audio.
976#[derive(Debug, Clone, Deserialize)]
977pub struct EndpointAudioFrame {
978    /// PCM16 samples, interleaved when channels is greater than one.
979    pub samples: Vec<i16>,
980    /// Sample rate in Hz.
981    pub sample_rate: u32,
982    /// Number of channels.
983    pub channels: u8,
984    /// RTP-style timestamp.
985    pub timestamp: u32,
986}
987
988impl EndpointAudioFrame {
989    /// Create a new endpoint audio frame.
990    pub fn new(samples: Vec<i16>, sample_rate: u32, channels: u8, timestamp: u32) -> Self {
991        Self {
992            samples,
993            sample_rate,
994            channels,
995            timestamp,
996        }
997    }
998
999    /// Create a 20 ms, 8 kHz mono PCM16 frame.
1000    pub fn pcmu_sized_mono_8khz(samples: Vec<i16>, timestamp: u32) -> Self {
1001        Self::new(samples, 8_000, 1, timestamp)
1002    }
1003
1004    /// Return samples per channel.
1005    pub fn samples_per_channel(&self) -> usize {
1006        self.samples.len() / self.channels.max(1) as usize
1007    }
1008}
1009
1010impl From<EndpointAudioFrame> for rvoip_media_core::types::AudioFrame {
1011    fn from(frame: EndpointAudioFrame) -> Self {
1012        rvoip_media_core::types::AudioFrame::new(
1013            frame.samples,
1014            frame.sample_rate,
1015            frame.channels,
1016            frame.timestamp,
1017        )
1018    }
1019}
1020
1021impl From<rvoip_media_core::types::AudioFrame> for EndpointAudioFrame {
1022    fn from(frame: rvoip_media_core::types::AudioFrame) -> Self {
1023        Self {
1024            samples: frame.samples,
1025            sample_rate: frame.sample_rate,
1026            channels: frame.channels,
1027            timestamp: frame.timestamp,
1028        }
1029    }
1030}
1031
1032/// Registration state exposed by Endpoint.
1033#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1034pub enum EndpointRegistrationStatus {
1035    /// REGISTER is in progress.
1036    Registering,
1037    /// The registrar accepted the binding.
1038    Registered,
1039    /// Unregister is in progress.
1040    Unregistering,
1041    /// No active binding is known.
1042    Unregistered,
1043    /// The most recent registration operation failed.
1044    Failed,
1045}
1046
1047/// Registration lifecycle snapshot exposed by Endpoint.
1048#[derive(Debug, Clone)]
1049pub struct EndpointRegistrationInfo {
1050    /// Coarse registration status.
1051    pub status: EndpointRegistrationStatus,
1052    /// Registrar URI.
1053    pub registrar: Option<String>,
1054    /// Contact URI currently registered.
1055    pub contact: Option<String>,
1056    /// Requested expiry.
1057    pub expires_secs: Option<u32>,
1058    /// Registrar-accepted expiry.
1059    pub accepted_expires_secs: Option<u32>,
1060    /// Duration until the next automatic refresh.
1061    pub next_refresh_in: Option<Duration>,
1062    /// Retry count for the current or last registration flow.
1063    pub retry_count: u32,
1064    /// Last failure, if any.
1065    pub last_failure: Option<String>,
1066}
1067
1068impl From<RegistrationInfo> for EndpointRegistrationInfo {
1069    fn from(info: RegistrationInfo) -> Self {
1070        Self {
1071            status: match info.status {
1072                RegistrationStatus::Registering => EndpointRegistrationStatus::Registering,
1073                RegistrationStatus::Registered => EndpointRegistrationStatus::Registered,
1074                RegistrationStatus::Unregistering => EndpointRegistrationStatus::Unregistering,
1075                RegistrationStatus::Unregistered => EndpointRegistrationStatus::Unregistered,
1076                RegistrationStatus::Failed => EndpointRegistrationStatus::Failed,
1077            },
1078            registrar: info.registrar,
1079            contact: info.contact,
1080            expires_secs: info.expires_secs,
1081            accepted_expires_secs: info.accepted_expires_secs,
1082            next_refresh_in: info.next_refresh_in,
1083            retry_count: info.retry_count,
1084            last_failure: info.last_failure,
1085        }
1086    }
1087}
1088
1089/// Canonical PBX-style SIP account and Digest-auth configuration.
1090///
1091/// `SipAccount` is the high-level account shape shared by endpoint,
1092/// stream-peer, callback-peer, registration, and challenged outbound-request
1093/// flows. `username` is the address-of-record user. `auth_username` is only
1094/// needed when the Digest username differs from the AOR user.
1095///
1096/// Use [`EndpointBuilder::auth`] with [`SipClientAuth`]
1097/// for non-Digest schemes such as Bearer, Basic, or AKA.
1098#[derive(Debug, Clone)]
1099pub struct SipAccount {
1100    /// SIP URI of the registrar, for example `sip:pbx.example.com` or
1101    /// `sips:pbx.example.com:5061`.
1102    pub registrar: String,
1103    /// Address-of-record user, usually the extension or SIP username.
1104    pub username: String,
1105    /// Optional Digest-auth username when it differs from [`username`](Self::username).
1106    pub auth_username: Option<String>,
1107    /// Digest-auth password.
1108    pub password: String,
1109    /// Registration expiry in seconds.
1110    pub expires: u32,
1111    /// Optional From/AoR URI override.
1112    pub from_uri: Option<String>,
1113    /// Optional Contact URI override.
1114    pub contact_uri: Option<String>,
1115}
1116
1117impl SipAccount {
1118    /// Create a complete SIP account.
1119    pub fn new(
1120        registrar: impl Into<String>,
1121        username: impl Into<String>,
1122        password: impl Into<String>,
1123    ) -> Self {
1124        Self {
1125            registrar: registrar.into(),
1126            username: username.into(),
1127            auth_username: None,
1128            password: password.into(),
1129            expires: 3600,
1130            from_uri: None,
1131            contact_uri: None,
1132        }
1133    }
1134
1135    /// Return the Digest-auth username, falling back to the AOR username.
1136    pub fn effective_auth_username(&self) -> &str {
1137        self.auth_username
1138            .as_deref()
1139            .unwrap_or(self.username.as_str())
1140    }
1141
1142    /// Build reusable Digest credentials for challenged outbound requests.
1143    pub fn credentials(&self) -> Credentials {
1144        Credentials::new(self.effective_auth_username(), self.password.clone())
1145    }
1146
1147    /// Build the registration model represented by this account.
1148    pub fn registration(&self) -> Registration {
1149        let mut registration = Registration::new(
1150            self.registrar.clone(),
1151            self.effective_auth_username().to_string(),
1152            self.password.clone(),
1153        )
1154        .expires(self.expires);
1155        if let Some(from_uri) = &self.from_uri {
1156            registration = registration.from_uri(from_uri.clone());
1157        }
1158        if let Some(contact_uri) = &self.contact_uri {
1159            registration = registration.contact_uri(contact_uri.clone());
1160        }
1161        registration
1162    }
1163
1164    /// Build the compatibility endpoint account model represented by this account.
1165    pub fn endpoint_account(&self) -> EndpointAccount {
1166        EndpointAccount {
1167            registrar: self.registrar.clone(),
1168            username: self.username.clone(),
1169            auth_username: self.auth_username.clone(),
1170            password: self.password.clone(),
1171            expires: self.expires,
1172            from_uri: self.from_uri.clone(),
1173            contact_uri: self.contact_uri.clone(),
1174        }
1175    }
1176
1177    /// Set the Digest-auth username.
1178    pub fn auth_username(mut self, username: impl Into<String>) -> Self {
1179        self.auth_username = Some(username.into());
1180        self
1181    }
1182
1183    /// Set the registration expiry in seconds.
1184    pub fn expires(mut self, seconds: u32) -> Self {
1185        self.expires = seconds;
1186        self
1187    }
1188
1189    /// Override the SIP From/AoR URI.
1190    pub fn from_uri(mut self, uri: impl Into<String>) -> Self {
1191        self.from_uri = Some(uri.into());
1192        self
1193    }
1194
1195    /// Override the SIP Contact URI.
1196    pub fn contact_uri(mut self, uri: impl Into<String>) -> Self {
1197        self.contact_uri = Some(uri.into());
1198        self
1199    }
1200}
1201
1202/// Account information used by [`EndpointBuilder`].
1203///
1204/// `EndpointAccount` describes the SIP registrar credentials and optional
1205/// identity overrides. It maps directly to [`Registration`] plus the default
1206/// INVITE digest credentials stored on [`Config`].
1207///
1208/// Prefer [`SipAccount`] for new code. `EndpointAccount` is retained for
1209/// backwards compatibility.
1210#[derive(Debug, Clone)]
1211pub struct EndpointAccount {
1212    /// SIP URI of the registrar, for example `sip:pbx.example.com` or
1213    /// `sips:pbx.example.com:5061`.
1214    pub registrar: String,
1215    /// Address-of-record user, usually the extension or SIP username.
1216    pub username: String,
1217    /// Optional digest-auth username when it differs from [`username`](Self::username).
1218    pub auth_username: Option<String>,
1219    /// Digest-auth password.
1220    pub password: String,
1221    /// Registration expiry in seconds.
1222    pub expires: u32,
1223    /// Optional From/AoR URI override.
1224    pub from_uri: Option<String>,
1225    /// Optional Contact URI override.
1226    pub contact_uri: Option<String>,
1227}
1228
1229impl EndpointAccount {
1230    /// Create a complete endpoint account.
1231    ///
1232    /// # Examples
1233    ///
1234    /// ```
1235    /// let account = rvoip_sip::EndpointAccount::new(
1236    ///     "sip:pbx.example.com",
1237    ///     "1001",
1238    ///     "secret",
1239    /// );
1240    /// assert_eq!(account.expires, 3600);
1241    /// ```
1242    pub fn new(
1243        registrar: impl Into<String>,
1244        username: impl Into<String>,
1245        password: impl Into<String>,
1246    ) -> Self {
1247        Self {
1248            registrar: registrar.into(),
1249            username: username.into(),
1250            auth_username: None,
1251            password: password.into(),
1252            expires: 3600,
1253            from_uri: None,
1254            contact_uri: None,
1255        }
1256    }
1257
1258    /// Set the digest-auth username.
1259    pub fn auth_username(mut self, username: impl Into<String>) -> Self {
1260        self.auth_username = Some(username.into());
1261        self
1262    }
1263
1264    /// Set the registration expiry in seconds.
1265    pub fn expires(mut self, seconds: u32) -> Self {
1266        self.expires = seconds;
1267        self
1268    }
1269
1270    /// Override the SIP From/AoR URI.
1271    pub fn from_uri(mut self, uri: impl Into<String>) -> Self {
1272        self.from_uri = Some(uri.into());
1273        self
1274    }
1275
1276    /// Override the SIP Contact URI.
1277    pub fn contact_uri(mut self, uri: impl Into<String>) -> Self {
1278        self.contact_uri = Some(uri.into());
1279        self
1280    }
1281}
1282
1283impl From<SipAccount> for EndpointAccount {
1284    fn from(account: SipAccount) -> Self {
1285        account.endpoint_account()
1286    }
1287}
1288
1289impl From<EndpointAccount> for SipAccount {
1290    fn from(account: EndpointAccount) -> Self {
1291        Self {
1292            registrar: account.registrar,
1293            username: account.username,
1294            auth_username: account.auth_username,
1295            password: account.password,
1296            expires: account.expires,
1297            from_uri: account.from_uri,
1298            contact_uri: account.contact_uri,
1299        }
1300    }
1301}
1302
1303/// Serde-friendly endpoint configuration for CLI tools and simple apps.
1304#[derive(Debug, Clone, Default, Deserialize)]
1305#[serde(rename_all = "camelCase")]
1306pub struct EndpointConfig {
1307    /// Display/configuration name.
1308    pub name: Option<String>,
1309    /// Deployment profile shortcut.
1310    pub profile: Option<EndpointProfileName>,
1311    /// Top-level bind shortcut.
1312    pub bind: Option<SocketAddr>,
1313    /// Top-level advertised SIP address shortcut.
1314    pub advertise: Option<SocketAddr>,
1315    /// SIP account configuration.
1316    pub account: Option<EndpointAccountConfig>,
1317    /// Network and signalling settings.
1318    pub network: Option<EndpointNetworkConfig>,
1319    /// Media settings.
1320    pub media: Option<EndpointMediaConfig>,
1321    /// Performance profile settings.
1322    pub performance: Option<PerformanceConfig>,
1323    /// Whether automatic `180 Ringing` is sent for inbound INVITEs.
1324    pub auto_180_ringing: Option<bool>,
1325    /// Whether automatic `100 Trying` timer tasks are armed for inbound INVITEs.
1326    pub auto_100_trying: Option<bool>,
1327    /// Whether inbound INVITEs are immediately accepted before app callbacks.
1328    pub fast_auto_accept_incoming_calls: Option<bool>,
1329    /// Cleanup-stage timing diagnostics.
1330    pub cleanup_diagnostics: Option<bool>,
1331    /// Per-operation cleanup diagnostic event logs.
1332    pub cleanup_diagnostic_events: Option<bool>,
1333    /// App-facing event buffer capacity.
1334    pub app_event_channel_capacity: Option<usize>,
1335    /// Per-transaction command channel capacity.
1336    pub sip_transaction_command_channel_capacity: Option<usize>,
1337    /// Server-side inbound call admission limit.
1338    pub server_call_admission_limit: Option<usize>,
1339    /// Soft threshold where server-side admission starts pacing.
1340    pub server_call_admission_soft_limit: Option<usize>,
1341    /// Delay in milliseconds while above the soft admission threshold.
1342    pub server_call_admission_pacing_delay_ms: Option<u64>,
1343    /// Retry-After seconds for server overload rejections.
1344    pub server_overload_retry_after_secs: Option<u32>,
1345    /// RSS growth threshold used by perf soak release gates.
1346    #[cfg(feature = "perf-tests")]
1347    pub perf_max_rss_growth_mb_per_hr: Option<f64>,
1348    /// SRTP negotiation diagnostic log lines.
1349    pub srtp_diagnostics: Option<bool>,
1350    /// RTP packet diagnostic log lines.
1351    pub rtp_diagnostics: Option<bool>,
1352    /// SDP media diagnostic log lines.
1353    pub media_sdp_diagnostics: Option<bool>,
1354    /// SIP trace diagnostics.
1355    pub sip_trace: Option<crate::api::events::SipTraceConfig>,
1356    /// Whether an application should register immediately after startup.
1357    pub register_on_start: Option<bool>,
1358}
1359
1360/// Serde-friendly SIP account settings.
1361#[derive(Debug, Clone, Deserialize)]
1362#[serde(rename_all = "camelCase")]
1363pub struct EndpointAccountConfig {
1364    /// SIP registrar URI.
1365    pub registrar: String,
1366    /// SIP username or extension.
1367    pub username: String,
1368    /// Optional digest username when it differs from username.
1369    pub auth_username: Option<String>,
1370    /// Digest password.
1371    pub password: String,
1372    /// Registration expiry in seconds.
1373    pub expires: Option<u32>,
1374    /// Optional From/AoR URI override.
1375    pub from_uri: Option<String>,
1376    /// Optional Contact URI override.
1377    pub contact_uri: Option<String>,
1378}
1379
1380impl TryFrom<EndpointAccountConfig> for EndpointAccount {
1381    type Error = SessionError;
1382
1383    fn try_from(config: EndpointAccountConfig) -> Result<Self> {
1384        let mut account = EndpointAccount::new(config.registrar, config.username, config.password);
1385        if let Some(auth_username) = config.auth_username {
1386            account = account.auth_username(auth_username);
1387        }
1388        if let Some(expires) = config.expires {
1389            account = account.expires(expires);
1390        }
1391        if let Some(from_uri) = config.from_uri {
1392            account = account.from_uri(from_uri);
1393        }
1394        if let Some(contact_uri) = config.contact_uri {
1395            account = account.contact_uri(contact_uri);
1396        }
1397        Ok(account)
1398    }
1399}
1400
1401/// Serde-friendly network and signalling settings.
1402#[derive(Debug, Clone, Default, Deserialize)]
1403#[serde(rename_all = "camelCase")]
1404pub struct EndpointNetworkConfig {
1405    /// SIP bind address.
1406    pub bind: Option<SocketAddr>,
1407    /// Advertised SIP address.
1408    pub advertise: Option<SocketAddr>,
1409    /// Preferred signalling transport.
1410    pub transport: Option<EndpointTransport>,
1411    /// STUN server for media public-address discovery.
1412    pub stun: Option<String>,
1413    /// Outbound proxy URI.
1414    pub outbound_proxy: Option<String>,
1415    /// SIP instance URN for registered-flow profiles.
1416    pub sip_instance: Option<String>,
1417    /// TLS listener bind address.
1418    pub tls_bind: Option<SocketAddr>,
1419    /// TLS certificate path.
1420    pub tls_cert_path: Option<PathBuf>,
1421    /// TLS private key path.
1422    pub tls_key_path: Option<PathBuf>,
1423    /// Optional UDP parse worker count.
1424    pub udp_parse_workers: Option<usize>,
1425    /// Optional per-worker UDP parse queue capacity.
1426    pub udp_parse_queue_capacity: Option<usize>,
1427}
1428
1429/// Serde-friendly media settings.
1430#[derive(Debug, Clone, Default, Deserialize)]
1431#[serde(rename_all = "camelCase")]
1432pub struct EndpointMediaConfig {
1433    /// Public media address as an IP address or socket address string.
1434    pub public_address: Option<String>,
1435    /// RTP media port range start.
1436    pub port_start: Option<u16>,
1437    /// RTP media port range end.
1438    pub port_end: Option<u16>,
1439    /// Whether real media-core RTP allocation is enabled.
1440    pub enabled: Option<bool>,
1441    /// SDP RTP port to advertise when media is disabled.
1442    pub signaling_only_rtp_port: Option<u16>,
1443    /// SRTP negotiation policy.
1444    pub srtp: Option<EndpointSrtpMode>,
1445}
1446
1447/// Serde-friendly deployment profile names.
1448#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
1449#[serde(rename_all = "kebab-case")]
1450pub enum EndpointProfileName {
1451    /// Local loopback development.
1452    Local,
1453    /// Directly reachable LAN/PBX endpoint.
1454    LanPbx,
1455    /// UDP Asterisk/PBX endpoint.
1456    AsteriskUdp,
1457    /// Asterisk TLS and mandatory SRTP registered flow.
1458    AsteriskTlsSrtp,
1459    /// FreeSWITCH internal profile.
1460    FreeswitchInternal,
1461    /// FreeSWITCH TLS and SRTP reachable-contact profile.
1462    FreeswitchTlsSrtp,
1463    /// Carrier/SBC profile.
1464    CarrierSbc,
1465}
1466
1467impl From<EndpointProfileName> for EndpointProfile {
1468    fn from(profile: EndpointProfileName) -> Self {
1469        match profile {
1470            EndpointProfileName::Local => EndpointProfile::Local,
1471            EndpointProfileName::LanPbx => EndpointProfile::LanPbx,
1472            EndpointProfileName::AsteriskUdp => EndpointProfile::AsteriskUdp,
1473            EndpointProfileName::AsteriskTlsSrtp => EndpointProfile::AsteriskTlsSrtpRegisteredFlow,
1474            EndpointProfileName::FreeswitchInternal => EndpointProfile::FreeSwitchInternal,
1475            EndpointProfileName::FreeswitchTlsSrtp => {
1476                EndpointProfile::FreeSwitchTlsSrtpReachableContact
1477            }
1478            EndpointProfileName::CarrierSbc => EndpointProfile::CarrierSbc,
1479        }
1480    }
1481}
1482
1483/// Preferred signalling transport for endpoint-generated SIP URIs.
1484#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
1485#[serde(rename_all = "lowercase")]
1486pub enum EndpointTransport {
1487    /// UDP signalling.
1488    Udp,
1489    /// TCP signalling.
1490    Tcp,
1491    /// TLS signalling with `sips:` targets.
1492    Tls,
1493}
1494
1495/// SRTP policy for endpoint media negotiation.
1496#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
1497#[serde(rename_all = "lowercase")]
1498pub enum EndpointSrtpMode {
1499    /// Do not offer SRTP.
1500    Off,
1501    /// Offer SRTP but allow RTP fallback.
1502    Offer,
1503    /// Require SRTP.
1504    Required,
1505}
1506
1507/// Deployment profile used by [`EndpointBuilder`].
1508///
1509/// These variants intentionally mirror the existing [`Config`] profile
1510/// constructors so `Endpoint` remains a convenience layer, not a second SIP
1511/// configuration system.
1512#[derive(Debug, Clone)]
1513pub enum EndpointProfile {
1514    /// Local loopback development profile.
1515    Local,
1516    /// Directly reachable LAN PBX endpoint.
1517    LanPbx,
1518    /// UDP Asterisk/PBX endpoint profile.
1519    AsteriskUdp,
1520    /// Asterisk TLS + mandatory SDES-SRTP with symmetric registered-flow reuse.
1521    AsteriskTlsSrtpRegisteredFlow,
1522    /// FreeSWITCH/Sofia internal LAN profile.
1523    FreeSwitchInternal,
1524    /// FreeSWITCH TLS + mandatory SDES-SRTP with a directly reachable TLS Contact.
1525    FreeSwitchTlsSrtpReachableContact,
1526    /// Carrier/SBC style TLS registered-flow operation with outbound proxy.
1527    CarrierSbc,
1528    /// Fully custom config; builder account and registration conveniences still apply.
1529    Custom(Config),
1530}
1531
1532impl Default for EndpointProfile {
1533    fn default() -> Self {
1534        Self::Local
1535    }
1536}
1537
1538/// Builder for [`Endpoint`].
1539///
1540/// The builder first selects a deployment profile, then applies account,
1541/// registration, media-port, and custom configuration overrides before
1542/// starting the wrapped [`StreamPeer`].
1543pub struct EndpointBuilder {
1544    name: Option<String>,
1545    profile: EndpointProfile,
1546    bind_addr: Option<SocketAddr>,
1547    advertised_addr: Option<SocketAddr>,
1548    tls_bind_addr: Option<SocketAddr>,
1549    tls_cert_path: Option<std::path::PathBuf>,
1550    tls_key_path: Option<std::path::PathBuf>,
1551    media_port_start: Option<u16>,
1552    media_port_end: Option<u16>,
1553    media_public_addr: Option<SocketAddr>,
1554    media_mode: Option<MediaMode>,
1555    stun_server: Option<String>,
1556    outbound_proxy_uri: Option<String>,
1557    sip_instance: Option<String>,
1558    transport: EndpointTransport,
1559    sip_udp_parse_workers: Option<usize>,
1560    sip_udp_parse_queue_capacity: Option<usize>,
1561    performance: Option<PerformanceConfig>,
1562    srtp_mode: Option<EndpointSrtpMode>,
1563    auto_180_ringing: Option<bool>,
1564    auto_100_trying: Option<bool>,
1565    fast_auto_accept_incoming_calls: Option<bool>,
1566    cleanup_diagnostics: Option<bool>,
1567    cleanup_diagnostic_events: Option<bool>,
1568    app_event_channel_capacity: Option<usize>,
1569    sip_transaction_command_channel_capacity: Option<usize>,
1570    server_call_admission_limit: Option<usize>,
1571    server_call_admission_soft_limit: Option<usize>,
1572    server_call_admission_pacing_delay_ms: Option<u64>,
1573    server_overload_retry_after_secs: Option<u32>,
1574    #[cfg(feature = "perf-tests")]
1575    perf_max_rss_growth_mb_per_hr: Option<f64>,
1576    srtp_diagnostics: Option<bool>,
1577    rtp_diagnostics: Option<bool>,
1578    media_sdp_diagnostics: Option<bool>,
1579    account_username: Option<String>,
1580    auth_username: Option<String>,
1581    password: Option<String>,
1582    auth: Option<SipClientAuth>,
1583    registrar: Option<String>,
1584    expires: u32,
1585    sip_trace: Option<crate::api::events::SipTraceConfig>,
1586    from_uri: Option<String>,
1587    contact_uri: Option<String>,
1588    configurators: Vec<Box<dyn FnOnce(&mut Config) + Send>>,
1589}
1590
1591impl EndpointBuilder {
1592    /// Create a builder with the local profile.
1593    pub fn new() -> Self {
1594        Self {
1595            name: None,
1596            profile: EndpointProfile::Local,
1597            bind_addr: None,
1598            advertised_addr: None,
1599            tls_bind_addr: None,
1600            tls_cert_path: None,
1601            tls_key_path: None,
1602            media_port_start: None,
1603            media_port_end: None,
1604            media_public_addr: None,
1605            media_mode: None,
1606            stun_server: None,
1607            outbound_proxy_uri: None,
1608            sip_instance: None,
1609            transport: EndpointTransport::Udp,
1610            sip_udp_parse_workers: None,
1611            sip_udp_parse_queue_capacity: None,
1612            performance: None,
1613            srtp_mode: None,
1614            auto_180_ringing: None,
1615            auto_100_trying: None,
1616            fast_auto_accept_incoming_calls: None,
1617            cleanup_diagnostics: None,
1618            cleanup_diagnostic_events: None,
1619            app_event_channel_capacity: None,
1620            sip_transaction_command_channel_capacity: None,
1621            server_call_admission_limit: None,
1622            server_call_admission_soft_limit: None,
1623            server_call_admission_pacing_delay_ms: None,
1624            server_overload_retry_after_secs: None,
1625            #[cfg(feature = "perf-tests")]
1626            perf_max_rss_growth_mb_per_hr: None,
1627            srtp_diagnostics: None,
1628            rtp_diagnostics: None,
1629            media_sdp_diagnostics: None,
1630            account_username: None,
1631            auth_username: None,
1632            password: None,
1633            auth: None,
1634            registrar: None,
1635            expires: 3600,
1636            sip_trace: None,
1637            from_uri: None,
1638            contact_uri: None,
1639            configurators: Vec::new(),
1640        }
1641    }
1642
1643    /// Create a builder from a serde-friendly endpoint configuration object.
1644    pub fn from_config(config: EndpointConfig) -> Result<Self> {
1645        let mut builder = EndpointBuilder::new();
1646
1647        if let Some(name) = config.name {
1648            builder = builder.name(name);
1649        }
1650        if let Some(profile) = config.profile {
1651            builder = builder.profile(profile.into());
1652        }
1653        if let Some(performance) = config.performance {
1654            builder = builder.performance_config(performance);
1655        }
1656        if let Some(bind) = config.bind.or(config.network.as_ref().and_then(|n| n.bind)) {
1657            builder = builder.bind_addr(bind);
1658        }
1659        if let Some(advertise) = config
1660            .advertise
1661            .or(config.network.as_ref().and_then(|n| n.advertise))
1662        {
1663            builder = builder.advertised_addr(advertise);
1664        }
1665
1666        if let Some(account) = config.account {
1667            builder = builder.endpoint_account(account.try_into()?);
1668        }
1669        if let Some(auto_180_ringing) = config.auto_180_ringing {
1670            builder = builder.auto_180_ringing(auto_180_ringing);
1671        }
1672        if let Some(auto_100_trying) = config.auto_100_trying {
1673            builder = builder.auto_100_trying(auto_100_trying);
1674        }
1675        if let Some(fast_auto_accept) = config.fast_auto_accept_incoming_calls {
1676            builder = builder.fast_auto_accept_incoming_calls(fast_auto_accept);
1677        }
1678        if let Some(cleanup_diagnostics) = config.cleanup_diagnostics {
1679            builder = builder.cleanup_diagnostics(cleanup_diagnostics);
1680        }
1681        if let Some(cleanup_diagnostic_events) = config.cleanup_diagnostic_events {
1682            builder = builder.cleanup_diagnostic_events(cleanup_diagnostic_events);
1683        }
1684        if let Some(capacity) = config.app_event_channel_capacity {
1685            builder = builder.app_event_channel_capacity(capacity);
1686        }
1687        if let Some(capacity) = config.sip_transaction_command_channel_capacity {
1688            builder = builder.sip_transaction_command_channel_capacity(capacity);
1689        }
1690        if let Some(limit) = config.server_call_admission_limit {
1691            builder = builder.server_call_admission_limit(limit);
1692        }
1693        if let Some(limit) = config.server_call_admission_soft_limit {
1694            builder = builder.server_call_admission_soft_limit(limit);
1695        }
1696        if let Some(delay_ms) = config.server_call_admission_pacing_delay_ms {
1697            builder = builder.server_call_admission_pacing_delay_ms(delay_ms);
1698        }
1699        if let Some(seconds) = config.server_overload_retry_after_secs {
1700            builder = builder.server_overload_retry_after_secs(seconds);
1701        }
1702        #[cfg(feature = "perf-tests")]
1703        if let Some(limit) = config.perf_max_rss_growth_mb_per_hr {
1704            builder = builder.perf_max_rss_growth_mb_per_hr(limit);
1705        }
1706        if let Some(srtp_diagnostics) = config.srtp_diagnostics {
1707            builder = builder.srtp_diagnostics(srtp_diagnostics);
1708        }
1709        if let Some(rtp_diagnostics) = config.rtp_diagnostics {
1710            builder = builder.rtp_diagnostics(rtp_diagnostics);
1711        }
1712        if let Some(media_sdp_diagnostics) = config.media_sdp_diagnostics {
1713            builder = builder.media_sdp_diagnostics(media_sdp_diagnostics);
1714        }
1715
1716        if let Some(network) = config.network {
1717            if let Some(transport) = network.transport {
1718                builder = builder.transport(transport);
1719            }
1720            if let Some(stun) = network.stun {
1721                builder = builder.stun_server(stun);
1722            }
1723            if let Some(proxy) = network.outbound_proxy {
1724                builder = builder.outbound_proxy(proxy);
1725            }
1726            if let Some(instance) = network.sip_instance {
1727                builder = builder.sip_instance(instance);
1728            }
1729            if let Some(tls_bind) = network.tls_bind {
1730                builder = builder.tls_bind_addr(tls_bind);
1731            }
1732            if let Some(path) = network.tls_cert_path {
1733                builder = builder.tls_cert_path(path);
1734            }
1735            if let Some(path) = network.tls_key_path {
1736                builder = builder.tls_key_path(path);
1737            }
1738            if let Some(workers) = network.udp_parse_workers {
1739                builder = builder.sip_udp_parse_workers(workers);
1740            }
1741            if let Some(capacity) = network.udp_parse_queue_capacity {
1742                builder = builder.sip_udp_parse_queue_capacity(capacity);
1743            }
1744        }
1745
1746        if let Some(media) = config.media {
1747            if let Some(public) = media.public_address {
1748                builder = builder.media_public_addr(parse_media_public_address(&public)?);
1749            }
1750            if let Some(start) = media.port_start {
1751                let end = media.port_end.unwrap_or(start);
1752                builder = builder.media_ports(start, end);
1753            } else if let Some(end) = media.port_end {
1754                builder = builder.media_ports(Config::DEFAULT_MEDIA_PORT_START, end);
1755            }
1756            if let Some(srtp) = media.srtp {
1757                builder = builder.srtp(srtp);
1758            }
1759            if media.enabled == Some(false) || media.signaling_only_rtp_port.is_some() {
1760                builder = builder.signaling_only_media(media.signaling_only_rtp_port.unwrap_or(9));
1761            } else if media.enabled == Some(true) {
1762                builder = builder.media_enabled(true);
1763            }
1764        }
1765
1766        if let Some(sip_trace) = config.sip_trace {
1767            builder = builder.sip_trace(sip_trace);
1768        }
1769
1770        Ok(builder)
1771    }
1772
1773    /// Set the display/configuration name.
1774    pub fn name(mut self, name: impl Into<String>) -> Self {
1775        self.name = Some(name.into());
1776        self
1777    }
1778
1779    /// Set the SIP account username or extension.
1780    pub fn account(mut self, username: impl Into<String>) -> Self {
1781        self.account_username = Some(username.into());
1782        self
1783    }
1784
1785    /// Set all account fields at once.
1786    pub fn endpoint_account(mut self, account: EndpointAccount) -> Self {
1787        self.registrar = Some(account.registrar);
1788        self.account_username = Some(account.username);
1789        self.auth_username = account.auth_username;
1790        self.password = Some(account.password);
1791        self.expires = account.expires;
1792        self.from_uri = account.from_uri;
1793        self.contact_uri = account.contact_uri;
1794        self
1795    }
1796
1797    /// Set all account and Digest-auth fields at once.
1798    pub fn sip_account(self, account: SipAccount) -> Self {
1799        self.endpoint_account(account.into())
1800    }
1801
1802    /// Set the digest-auth username when it differs from the account username.
1803    pub fn auth_username(mut self, username: impl Into<String>) -> Self {
1804        self.auth_username = Some(username.into());
1805        self
1806    }
1807
1808    /// Set the digest-auth password.
1809    pub fn password(mut self, password: impl Into<String>) -> Self {
1810        self.password = Some(password.into());
1811        self
1812    }
1813
1814    /// Set general UAC SIP auth for outbound 401/407 retry.
1815    ///
1816    /// Use [`SipClientAuth::any`] when the peer may offer multiple schemes and
1817    /// the UAC should negotiate among Digest, Bearer, Basic, and AKA options.
1818    pub fn auth(mut self, auth: SipClientAuth) -> Self {
1819        self.auth = Some(auth);
1820        self
1821    }
1822
1823    /// Set Bearer auth for UAC outbound 401/407 retry.
1824    pub fn bearer_token(mut self, token: impl Into<String>) -> Self {
1825        self.auth = Some(SipClientAuth::bearer_token(token));
1826        self
1827    }
1828
1829    /// Set Basic auth for UAC outbound 401/407 retry.
1830    ///
1831    /// Basic remains cleartext-disabled unless the auth value explicitly opts
1832    /// in via [`SipClientAuth::allow_basic_over_cleartext`].
1833    pub fn basic_credentials(
1834        mut self,
1835        username: impl Into<String>,
1836        password: impl Into<String>,
1837    ) -> Self {
1838        self.auth = Some(SipClientAuth::basic(username, password));
1839        self
1840    }
1841
1842    /// Set the SIP registrar URI.
1843    pub fn registrar(mut self, registrar: impl Into<String>) -> Self {
1844        self.registrar = Some(registrar.into());
1845        self
1846    }
1847
1848    /// Set the registration expiry in seconds.
1849    pub fn expires(mut self, seconds: u32) -> Self {
1850        self.expires = seconds;
1851        self
1852    }
1853
1854    /// Select a deployment profile.
1855    pub fn profile(mut self, profile: EndpointProfile) -> Self {
1856        self.profile = profile;
1857        self
1858    }
1859
1860    /// Apply a serde-friendly configuration object to this builder.
1861    pub fn config(self, config: EndpointConfig) -> Result<Self> {
1862        let mut configured = EndpointBuilder::from_config(config)?;
1863        if self.name.is_some() {
1864            configured.name = self.name;
1865        }
1866        if self.sip_trace.is_some() {
1867            configured.sip_trace = self.sip_trace;
1868        }
1869        Ok(configured)
1870    }
1871
1872    /// Set the SIP bind address.
1873    pub fn bind_addr(mut self, addr: SocketAddr) -> Self {
1874        self.bind_addr = Some(addr);
1875        self
1876    }
1877
1878    /// Set the SIP advertised/public address.
1879    pub fn advertised_addr(mut self, addr: SocketAddr) -> Self {
1880        self.advertised_addr = Some(addr);
1881        self
1882    }
1883
1884    /// Set the SIP TLS listener bind address.
1885    pub fn tls_bind_addr(mut self, addr: SocketAddr) -> Self {
1886        self.tls_bind_addr = Some(addr);
1887        self
1888    }
1889
1890    /// Set the TLS listener certificate path.
1891    pub fn tls_cert_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
1892        self.tls_cert_path = Some(path.into());
1893        self
1894    }
1895
1896    /// Set the TLS listener private-key path.
1897    pub fn tls_key_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
1898        self.tls_key_path = Some(path.into());
1899        self
1900    }
1901
1902    /// Set the RTP media port range.
1903    pub fn media_ports(mut self, start: u16, end: u16) -> Self {
1904        self.media_port_start = Some(start);
1905        self.media_port_end = Some(end);
1906        self
1907    }
1908
1909    /// Enable or disable real media-core RTP allocation.
1910    pub fn media_enabled(mut self, enabled: bool) -> Self {
1911        self.media_mode = Some(if enabled {
1912            MediaMode::Enabled
1913        } else {
1914            MediaMode::SignalingOnly { sdp_rtp_port: 9 }
1915        });
1916        self
1917    }
1918
1919    /// Skip media-core RTP allocation while still generating SDP.
1920    pub fn signaling_only_media(mut self, sdp_rtp_port: u16) -> Self {
1921        self.media_mode = Some(MediaMode::SignalingOnly { sdp_rtp_port });
1922        self
1923    }
1924
1925    /// Set the public RTP media address advertised in SDP.
1926    pub fn media_public_addr(mut self, addr: SocketAddr) -> Self {
1927        self.media_public_addr = Some(addr);
1928        self
1929    }
1930
1931    /// Set a public RTP media IP address, leaving the negotiated media port dynamic.
1932    pub fn media_public_ip(mut self, addr: IpAddr) -> Self {
1933        self.media_public_addr = Some(SocketAddr::new(addr, 0));
1934        self
1935    }
1936
1937    /// Set a STUN server for best-effort media public-address discovery.
1938    pub fn stun_server(mut self, server: impl Into<String>) -> Self {
1939        self.stun_server = Some(server.into());
1940        self
1941    }
1942
1943    /// Set an outbound proxy URI for carrier/SBC-style operation.
1944    pub fn outbound_proxy(mut self, uri: impl Into<String>) -> Self {
1945        self.outbound_proxy_uri = Some(uri.into());
1946        self
1947    }
1948
1949    /// Set the RFC 5626 SIP instance URN used by registered-flow profiles.
1950    pub fn sip_instance(mut self, urn: impl Into<String>) -> Self {
1951        self.sip_instance = Some(urn.into());
1952        self
1953    }
1954
1955    /// Set the preferred signalling transport for generated SIP URIs.
1956    pub fn transport(mut self, transport: EndpointTransport) -> Self {
1957        self.transport = transport;
1958        self
1959    }
1960
1961    /// Set the UDP parse worker count.
1962    pub fn sip_udp_parse_workers(mut self, workers: usize) -> Self {
1963        self.sip_udp_parse_workers = Some(workers);
1964        self
1965    }
1966
1967    /// Set the per-worker UDP parse queue capacity.
1968    pub fn sip_udp_parse_queue_capacity(mut self, capacity: usize) -> Self {
1969        self.sip_udp_parse_queue_capacity = Some(capacity);
1970        self
1971    }
1972
1973    /// Apply a YAML-backed performance recipe.
1974    pub fn performance_config(mut self, performance: PerformanceConfig) -> Self {
1975        self.performance = Some(performance);
1976        self
1977    }
1978
1979    /// Apply the PBX media server performance recipe.
1980    pub fn pbx_media_server_performance(mut self, capacity: usize) -> Self {
1981        self.performance = Some(PerformanceConfig::pbx_media_server(capacity));
1982        self
1983    }
1984
1985    /// Apply the signaling-only high-performance server recipe.
1986    pub fn signaling_only_server_high_performance(mut self, capacity: usize) -> Self {
1987        self.performance = Some(PerformanceConfig::signaling_only_server_high_performance(
1988            capacity,
1989        ));
1990        self
1991    }
1992
1993    /// Apply the signaling-only high-performance server recipe with an explicit SDP RTP port.
1994    pub fn signaling_only_server_high_performance_with_port(
1995        mut self,
1996        capacity: usize,
1997        sdp_rtp_port: u16,
1998    ) -> Self {
1999        self.performance = Some(
2000            PerformanceConfig::signaling_only_server_high_performance(capacity)
2001                .with_signaling_only_rtp_port(sdp_rtp_port),
2002        );
2003        self
2004    }
2005
2006    /// Enable or disable automatic `180 Ringing` on inbound INVITEs.
2007    pub fn auto_180_ringing(mut self, enabled: bool) -> Self {
2008        self.auto_180_ringing = Some(enabled);
2009        self
2010    }
2011
2012    /// Enable or disable automatic `100 Trying` timer tasks on inbound INVITEs.
2013    pub fn auto_100_trying(mut self, enabled: bool) -> Self {
2014        self.auto_100_trying = Some(enabled);
2015        self
2016    }
2017
2018    /// Enable or disable immediate session-path accept for inbound INVITEs.
2019    pub fn fast_auto_accept_incoming_calls(mut self, enabled: bool) -> Self {
2020        self.fast_auto_accept_incoming_calls = Some(enabled);
2021        self
2022    }
2023
2024    /// Enable or disable cleanup-stage timing diagnostics.
2025    pub fn cleanup_diagnostics(mut self, enabled: bool) -> Self {
2026        self.cleanup_diagnostics = Some(enabled);
2027        self
2028    }
2029
2030    /// Enable or disable per-operation cleanup diagnostic event logs.
2031    pub fn cleanup_diagnostic_events(mut self, enabled: bool) -> Self {
2032        self.cleanup_diagnostic_events = Some(enabled);
2033        self
2034    }
2035
2036    /// Set app-facing event buffer capacity.
2037    pub fn app_event_channel_capacity(mut self, capacity: usize) -> Self {
2038        self.app_event_channel_capacity = Some(capacity);
2039        self
2040    }
2041
2042    /// Set the per-transaction command channel capacity.
2043    pub fn sip_transaction_command_channel_capacity(mut self, capacity: usize) -> Self {
2044        self.sip_transaction_command_channel_capacity = Some(capacity);
2045        self
2046    }
2047
2048    /// Set the server-side inbound call admission limit.
2049    pub fn server_call_admission_limit(mut self, limit: usize) -> Self {
2050        self.server_call_admission_limit = Some(limit);
2051        self
2052    }
2053
2054    /// Set the soft threshold where server-side admission starts pacing.
2055    pub fn server_call_admission_soft_limit(mut self, limit: usize) -> Self {
2056        self.server_call_admission_soft_limit = Some(limit);
2057        self
2058    }
2059
2060    /// Set the delay in milliseconds while above the soft admission threshold.
2061    pub fn server_call_admission_pacing_delay_ms(mut self, delay_ms: u64) -> Self {
2062        self.server_call_admission_pacing_delay_ms = Some(delay_ms);
2063        self
2064    }
2065
2066    /// Set the `Retry-After` value used for server overload rejections.
2067    pub fn server_overload_retry_after_secs(mut self, seconds: u32) -> Self {
2068        self.server_overload_retry_after_secs = Some(seconds);
2069        self
2070    }
2071
2072    /// Set the RSS growth threshold used by perf soak release gates.
2073    #[cfg(feature = "perf-tests")]
2074    pub fn perf_max_rss_growth_mb_per_hr(mut self, limit: f64) -> Self {
2075        self.perf_max_rss_growth_mb_per_hr = Some(limit);
2076        self
2077    }
2078
2079    /// Enable or disable SRTP negotiation diagnostic log lines.
2080    pub fn srtp_diagnostics(mut self, enabled: bool) -> Self {
2081        self.srtp_diagnostics = Some(enabled);
2082        self
2083    }
2084
2085    /// Enable or disable RTP packet diagnostic log lines.
2086    pub fn rtp_diagnostics(mut self, enabled: bool) -> Self {
2087        self.rtp_diagnostics = Some(enabled);
2088        self
2089    }
2090
2091    /// Enable or disable SDP media diagnostic log lines.
2092    pub fn media_sdp_diagnostics(mut self, enabled: bool) -> Self {
2093        self.media_sdp_diagnostics = Some(enabled);
2094        self
2095    }
2096
2097    /// Set the SRTP offer policy.
2098    pub fn srtp(mut self, mode: EndpointSrtpMode) -> Self {
2099        self.srtp_mode = Some(mode);
2100        self
2101    }
2102
2103    /// Enable SIP transport-boundary tracing with default redaction.
2104    pub fn enable_sip_trace(mut self) -> Self {
2105        self.sip_trace = Some(crate::api::events::SipTraceConfig::enabled());
2106        self
2107    }
2108
2109    /// Set SIP transport-boundary trace policy.
2110    pub fn sip_trace(mut self, config: crate::api::events::SipTraceConfig) -> Self {
2111        self.sip_trace = Some(config);
2112        self
2113    }
2114
2115    /// Override the From/AoR URI used for registration and outgoing calls.
2116    pub fn from_uri(mut self, uri: impl Into<String>) -> Self {
2117        self.from_uri = Some(uri.into());
2118        self
2119    }
2120
2121    /// Override the Contact URI used for registration and dialog Contact generation.
2122    pub fn contact_uri(mut self, uri: impl Into<String>) -> Self {
2123        self.contact_uri = Some(uri.into());
2124        self
2125    }
2126
2127    /// Mutate the generated [`Config`] immediately before the endpoint starts.
2128    pub fn configure(mut self, f: impl FnOnce(&mut Config) + Send + 'static) -> Self {
2129        self.configurators.push(Box::new(f));
2130        self
2131    }
2132
2133    /// Build and start the endpoint.
2134    pub async fn build(self) -> Result<Endpoint> {
2135        let parts = self.build_parts()?;
2136        let peer = StreamPeer::with_config(parts.config).await?;
2137        Ok(Endpoint {
2138            peer,
2139            registration: parts.registration,
2140            registration_handle: Arc::new(Mutex::new(None)),
2141            registrar: parts.registrar,
2142            transport: parts.transport,
2143        })
2144    }
2145
2146    fn build_parts(self) -> Result<EndpointParts> {
2147        let mut config = self.profile_config()?;
2148        let registrar = self
2149            .registrar
2150            .clone()
2151            .map(|uri| apply_transport_to_uri(&uri, self.transport, true));
2152        let account_username = self.account_username.clone();
2153
2154        if let (Some(username), Some(password)) = (&account_username, &self.password) {
2155            let auth_username = self.auth_username.as_deref().unwrap_or(username);
2156            config.credentials = Some(Credentials::new(auth_username, password));
2157        }
2158        if let Some(auth) = self.auth {
2159            config.auth = Some(auth);
2160        }
2161
2162        if let Some(performance) = self.performance {
2163            config = config.try_with_performance_config(performance)?;
2164        }
2165
2166        if self.media_port_start.is_some() || self.media_port_end.is_some() {
2167            let media_port_start = self.media_port_start.unwrap_or(config.media_port_start);
2168            let media_port_end = self.media_port_end.unwrap_or(config.media_port_end);
2169            config = config.with_media_ports(media_port_start, media_port_end);
2170        }
2171        if let Some(addr) = self.media_public_addr {
2172            config.media_public_addr = Some(addr);
2173        }
2174        if let Some(mode) = self.media_mode {
2175            config.media_mode = mode;
2176        }
2177        if let Some(stun) = self.stun_server {
2178            config.stun_server = Some(stun);
2179        }
2180        if let Some(outbound_proxy) = self.outbound_proxy_uri.as_ref() {
2181            config.outbound_proxy_uri =
2182                Some(apply_transport_to_uri(outbound_proxy, self.transport, true));
2183        }
2184        if let Some(srtp_mode) = self.srtp_mode {
2185            match srtp_mode {
2186                EndpointSrtpMode::Off => {
2187                    config.offer_srtp = false;
2188                    config.srtp_required = false;
2189                }
2190                EndpointSrtpMode::Offer => {
2191                    config.offer_srtp = true;
2192                    config.srtp_required = false;
2193                }
2194                EndpointSrtpMode::Required => {
2195                    config.offer_srtp = true;
2196                    config.srtp_required = true;
2197                }
2198            }
2199        }
2200        if let Some(sip_trace) = self.sip_trace {
2201            config.sip_trace = sip_trace;
2202        }
2203        if let Some(workers) = self.sip_udp_parse_workers {
2204            config.sip_udp_parse_workers = Some(workers);
2205        }
2206        if let Some(capacity) = self.sip_udp_parse_queue_capacity {
2207            config.sip_udp_parse_queue_capacity = Some(capacity);
2208        }
2209        if let Some(auto_180_ringing) = self.auto_180_ringing {
2210            config.auto_180_ringing = auto_180_ringing;
2211        }
2212        if let Some(auto_100_trying) = self.auto_100_trying {
2213            config.auto_100_trying = auto_100_trying;
2214        }
2215        if let Some(fast_auto_accept) = self.fast_auto_accept_incoming_calls {
2216            config.fast_auto_accept_incoming_calls = fast_auto_accept;
2217        }
2218        if let Some(cleanup_diagnostics) = self.cleanup_diagnostics {
2219            config.cleanup_diagnostics = cleanup_diagnostics;
2220        }
2221        if let Some(cleanup_diagnostic_events) = self.cleanup_diagnostic_events {
2222            config.cleanup_diagnostic_events = cleanup_diagnostic_events;
2223        }
2224        if let Some(capacity) = self.app_event_channel_capacity {
2225            config = config.with_app_event_channel_capacity(capacity);
2226        }
2227        if let Some(capacity) = self.sip_transaction_command_channel_capacity {
2228            config = config.with_sip_transaction_command_channel_capacity(capacity);
2229        }
2230        if let Some(limit) = self.server_call_admission_limit {
2231            config = config.with_server_call_admission_limit(limit);
2232        }
2233        if let Some(limit) = self.server_call_admission_soft_limit {
2234            config = config.with_server_call_admission_soft_limit(limit);
2235        }
2236        if let Some(delay_ms) = self.server_call_admission_pacing_delay_ms {
2237            config = config.with_server_call_admission_pacing_delay_ms(delay_ms);
2238        }
2239        if let Some(seconds) = self.server_overload_retry_after_secs {
2240            config = config.with_server_overload_retry_after_secs(seconds);
2241        }
2242        #[cfg(feature = "perf-tests")]
2243        if let Some(limit) = self.perf_max_rss_growth_mb_per_hr {
2244            config.perf_max_rss_growth_mb_per_hr = Some(limit);
2245        }
2246        if let Some(srtp_diagnostics) = self.srtp_diagnostics {
2247            config.srtp_diagnostics = srtp_diagnostics;
2248        }
2249        if let Some(rtp_diagnostics) = self.rtp_diagnostics {
2250            config.rtp_diagnostics = rtp_diagnostics;
2251        }
2252        if let Some(media_sdp_diagnostics) = self.media_sdp_diagnostics {
2253            config.media_sdp_diagnostics = media_sdp_diagnostics;
2254        }
2255        if self.transport == EndpointTransport::Tls && config.sip_tls_mode == SipTlsMode::Disabled {
2256            config.sip_tls_mode = SipTlsMode::ClientOnly;
2257        }
2258
2259        let derived_from_uri = match (&self.from_uri, &account_username, &registrar) {
2260            (Some(uri), _, _) => Some(uri.clone()),
2261            (None, Some(username), Some(registrar)) => Some(account_aor_uri(registrar, username)?),
2262            _ => None,
2263        };
2264        if let Some(from_uri) = &derived_from_uri {
2265            config.local_uri = from_uri.clone();
2266        }
2267
2268        if let Some(contact_uri) = &self.contact_uri {
2269            config.contact_uri = Some(contact_uri.clone());
2270        }
2271
2272        for configure in self.configurators {
2273            configure(&mut config);
2274        }
2275
2276        let registration = match (
2277            registrar.as_ref(),
2278            account_username.as_ref(),
2279            self.password.as_ref(),
2280        ) {
2281            (Some(registrar), Some(username), Some(password)) => {
2282                let auth_username = self.auth_username.as_deref().unwrap_or(username);
2283                let mut registration = Registration::new(
2284                    registrar.clone(),
2285                    auth_username.to_string(),
2286                    password.clone(),
2287                )
2288                .expires(self.expires);
2289                if let Some(from_uri) = derived_from_uri {
2290                    registration = registration.from_uri(from_uri);
2291                }
2292                if let Some(contact_uri) = self.contact_uri {
2293                    registration = registration.contact_uri(contact_uri);
2294                }
2295                Some(registration)
2296            }
2297            _ => None,
2298        };
2299
2300        Ok(EndpointParts {
2301            config,
2302            registration,
2303            registrar,
2304            transport: self.transport,
2305        })
2306    }
2307
2308    fn profile_config(&self) -> Result<Config> {
2309        let name = self
2310            .name
2311            .as_deref()
2312            .or(self.account_username.as_deref())
2313            .unwrap_or("endpoint");
2314
2315        match &self.profile {
2316            EndpointProfile::Local => {
2317                let bind = self
2318                    .bind_addr
2319                    .unwrap_or_else(|| SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5060));
2320                if bind.ip().is_loopback() {
2321                    Ok(Config::local(name, bind.port()))
2322                } else {
2323                    let mut config = Config::on(name, bind.ip(), bind.port());
2324                    config.bind_addr = bind;
2325                    Ok(config)
2326                }
2327            }
2328            EndpointProfile::LanPbx => {
2329                let bind = self.bind_addr.unwrap_or_else(default_udp_bind);
2330                let advertised = self.advertised_addr.ok_or_else(|| {
2331                    SessionError::ConfigError(
2332                        "EndpointProfile::LanPbx requires advertised_addr".to_string(),
2333                    )
2334                })?;
2335                Ok(Config::lan_pbx(name, bind, advertised))
2336            }
2337            EndpointProfile::AsteriskUdp => {
2338                let bind = self.bind_addr.unwrap_or_else(default_udp_bind);
2339                if let Some(advertised) = self.advertised_addr {
2340                    Ok(Config::lan_pbx(name, bind, advertised))
2341                } else if bind.ip().is_loopback() {
2342                    let mut config = Config::local(name, bind.port());
2343                    config.bind_addr = bind;
2344                    Ok(config)
2345                } else if bind.ip().is_unspecified() {
2346                    Err(SessionError::ConfigError(
2347                        "EndpointProfile::AsteriskUdp with an unspecified bind address requires advertised_addr"
2348                            .to_string(),
2349                    ))
2350                } else {
2351                    let mut config = Config::on(name, bind.ip(), bind.port());
2352                    config.bind_addr = bind;
2353                    Ok(config)
2354                }
2355            }
2356            EndpointProfile::AsteriskTlsSrtpRegisteredFlow => {
2357                let bind = self.bind_addr.unwrap_or_else(default_tls_bind);
2358                Ok(Config::asterisk_tls_registered_flow(
2359                    name,
2360                    bind,
2361                    self.sip_instance
2362                        .clone()
2363                        .unwrap_or_else(generate_sip_instance),
2364                ))
2365            }
2366            EndpointProfile::FreeSwitchInternal => {
2367                let bind = self.bind_addr.unwrap_or_else(default_udp_bind);
2368                Ok(Config::freeswitch_internal(name, bind))
2369            }
2370            EndpointProfile::FreeSwitchTlsSrtpReachableContact => {
2371                let bind = self.bind_addr.unwrap_or_else(default_udp_bind);
2372                let tls_bind = self.tls_bind_addr.unwrap_or_else(default_tls_bind);
2373                let cert = self.tls_cert_path.clone().ok_or_else(|| {
2374                    SessionError::ConfigError(
2375                        "EndpointProfile::FreeSwitchTlsSrtpReachableContact requires tls_cert_path"
2376                            .to_string(),
2377                    )
2378                })?;
2379                let key = self.tls_key_path.clone().ok_or_else(|| {
2380                    SessionError::ConfigError(
2381                        "EndpointProfile::FreeSwitchTlsSrtpReachableContact requires tls_key_path"
2382                            .to_string(),
2383                    )
2384                })?;
2385                Ok(Config::freeswitch_tls_srtp_reachable_contact(
2386                    name, bind, tls_bind, cert, key,
2387                ))
2388            }
2389            EndpointProfile::CarrierSbc => {
2390                let bind = self.bind_addr.unwrap_or_else(default_tls_bind);
2391                let public = self.advertised_addr.ok_or_else(|| {
2392                    SessionError::ConfigError(
2393                        "EndpointProfile::CarrierSbc requires advertised_addr".to_string(),
2394                    )
2395                })?;
2396                let outbound_proxy = self.outbound_proxy_uri.clone().ok_or_else(|| {
2397                    SessionError::ConfigError(
2398                        "EndpointProfile::CarrierSbc requires outbound_proxy".to_string(),
2399                    )
2400                })?;
2401                Ok(Config::carrier_sbc(
2402                    name,
2403                    bind,
2404                    public,
2405                    outbound_proxy,
2406                    self.sip_instance
2407                        .clone()
2408                        .unwrap_or_else(generate_sip_instance),
2409                ))
2410            }
2411            EndpointProfile::Custom(config) => Ok(config.clone()),
2412        }
2413    }
2414}
2415
2416impl Default for EndpointBuilder {
2417    fn default() -> Self {
2418        Self::new()
2419    }
2420}
2421
2422struct EndpointParts {
2423    config: Config,
2424    registration: Option<Registration>,
2425    registrar: Option<String>,
2426    transport: EndpointTransport,
2427}
2428
2429fn default_udp_bind() -> SocketAddr {
2430    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 5060)
2431}
2432
2433fn default_tls_bind() -> SocketAddr {
2434    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 5061)
2435}
2436
2437fn generate_sip_instance() -> String {
2438    format!("urn:uuid:{}", uuid::Uuid::new_v4())
2439}
2440
2441async fn wait_for_registration_result(
2442    events: &mut EndpointEvents,
2443    handle: &RegistrationHandle,
2444    timeout: Option<Duration>,
2445) -> Result<EndpointRegistrationInfo> {
2446    let coordinator = events.control.coordinator().clone();
2447    let registrar = coordinator
2448        .registration_info(handle)
2449        .await?
2450        .registrar
2451        .unwrap_or_default();
2452    let fut = async {
2453        loop {
2454            match events.next().await? {
2455                Some(EndpointEvent::RegistrationChanged(info))
2456                    if registrar.is_empty()
2457                        || info.registrar.as_deref() == Some(registrar.as_str()) =>
2458                {
2459                    if info.status == EndpointRegistrationStatus::Registered {
2460                        return coordinator
2461                            .registration_info(handle)
2462                            .await
2463                            .map(EndpointRegistrationInfo::from);
2464                    }
2465                    if info.status == EndpointRegistrationStatus::Failed {
2466                        return Err(SessionError::Other(format!(
2467                            "registration failed for {}: {}",
2468                            info.registrar.unwrap_or_default(),
2469                            info.last_failure
2470                                .unwrap_or_else(|| "unknown error".to_string())
2471                        )));
2472                    }
2473                }
2474                Some(_) => {}
2475                None => {
2476                    return Err(SessionError::Other(
2477                        "event stream closed while waiting for registration".to_string(),
2478                    ));
2479                }
2480            }
2481        }
2482    };
2483
2484    match timeout {
2485        Some(duration) => tokio::time::timeout(duration, fut)
2486            .await
2487            .map_err(|_| SessionError::Timeout("register_and_wait timed out".to_string()))?,
2488        None => fut.await,
2489    }
2490}
2491
2492fn normalize_target(
2493    registrar: Option<&str>,
2494    target: &str,
2495    transport: EndpointTransport,
2496) -> Result<String> {
2497    let target = target.trim();
2498    if target.is_empty() {
2499        return Err(SessionError::InvalidInput(
2500            "call target must not be empty".to_string(),
2501        ));
2502    }
2503
2504    let lower = target.to_ascii_lowercase();
2505    if lower.starts_with("sip:") || lower.starts_with("sips:") || lower.starts_with("tel:") {
2506        return Ok(apply_transport_to_uri(target, transport, false));
2507    }
2508
2509    let registrar = registrar.ok_or_else(|| {
2510        SessionError::ConfigError(
2511            "bare call targets require EndpointBuilder::registrar".to_string(),
2512        )
2513    })?;
2514    let registrar = apply_transport_to_uri(registrar, transport, true);
2515    let mut registrar_uri = parse_uri(&registrar, "registrar")?;
2516
2517    if target.contains('@') {
2518        return Ok(format!("{}:{}", registrar_uri.scheme, target));
2519    }
2520
2521    registrar_uri.user = Some(target.to_string());
2522    registrar_uri.password = None;
2523    registrar_uri.headers.clear();
2524    Ok(registrar_uri.to_string())
2525}
2526
2527fn apply_transport_to_uri(
2528    uri: &str,
2529    transport: EndpointTransport,
2530    registrar_or_proxy: bool,
2531) -> String {
2532    match transport {
2533        EndpointTransport::Udp => uri.to_string(),
2534        EndpointTransport::Tcp => {
2535            if uri.contains(";transport=") {
2536                uri.to_string()
2537            } else {
2538                format!("{uri};transport=tcp")
2539            }
2540        }
2541        EndpointTransport::Tls => {
2542            let tls_uri = if uri.to_ascii_lowercase().starts_with("sip:") {
2543                format!("sips:{}", &uri[4..])
2544            } else {
2545                uri.to_string()
2546            };
2547            if registrar_or_proxy || tls_uri.contains(";transport=") {
2548                tls_uri
2549            } else {
2550                format!("{tls_uri};transport=tls")
2551            }
2552        }
2553    }
2554}
2555
2556fn parse_media_public_address(value: &str) -> Result<SocketAddr> {
2557    if let Ok(addr) = value.parse::<SocketAddr>() {
2558        return Ok(addr);
2559    }
2560    let ip = value.parse::<IpAddr>().map_err(|err| {
2561        SessionError::InvalidInput(format!("invalid media public address '{value}': {err}"))
2562    })?;
2563    Ok(SocketAddr::new(ip, 0))
2564}
2565
2566fn account_aor_uri(registrar: &str, username: &str) -> Result<String> {
2567    let mut uri = parse_uri(registrar, "registrar")?;
2568    uri.user = Some(username.to_string());
2569    uri.password = None;
2570    uri.port = None;
2571    uri.parameters.clear();
2572    uri.headers.clear();
2573    Ok(uri.to_string())
2574}
2575
2576fn parse_uri(value: &str, label: &str) -> Result<Uri> {
2577    let uri = Uri::from_str(value).map_err(|err| {
2578        SessionError::InvalidInput(format!("invalid {label} URI '{value}': {err}"))
2579    })?;
2580    match uri.scheme {
2581        Scheme::Sip | Scheme::Sips => Ok(uri),
2582        _ => Err(SessionError::InvalidInput(format!(
2583            "{label} URI must use sip: or sips:"
2584        ))),
2585    }
2586}
2587
2588#[cfg(test)]
2589mod tests {
2590    use super::*;
2591    use crate::api::unified::{SipContactMode, SipTlsMode};
2592
2593    #[test]
2594    fn endpoint_builder_maps_asterisk_tls_profile() {
2595        let parts = Endpoint::builder()
2596            .name("alice")
2597            .account("1001")
2598            .password("secret")
2599            .registrar("sips:pbx.example.test:5061;transport=tls")
2600            .profile(EndpointProfile::AsteriskTlsSrtpRegisteredFlow)
2601            .sip_instance("urn:uuid:00000000-0000-0000-0000-000000000001")
2602            .build_parts()
2603            .unwrap();
2604
2605        assert_eq!(parts.config.sip_tls_mode, SipTlsMode::ClientOnly);
2606        assert_eq!(
2607            parts.config.sip_contact_mode,
2608            SipContactMode::RegisteredFlowSymmetric
2609        );
2610        assert!(parts.config.offer_srtp);
2611        assert!(parts.config.srtp_required);
2612        assert_eq!(parts.config.local_uri, "sips:1001@pbx.example.test");
2613        assert!(parts.registration.is_some());
2614    }
2615
2616    #[test]
2617    fn endpoint_builder_creates_registration_defaults() {
2618        let parts = Endpoint::builder()
2619            .account("1001")
2620            .auth_username("auth1001")
2621            .password("secret")
2622            .registrar("sip:pbx.example.test")
2623            .contact_uri("sip:1001@192.0.2.10:5060")
2624            .expires(600)
2625            .build_parts()
2626            .unwrap();
2627
2628        let registration = parts.registration.unwrap();
2629        assert_eq!(registration.registrar, "sip:pbx.example.test");
2630        assert_eq!(registration.username, "auth1001");
2631        assert_eq!(registration.password, "secret");
2632        assert_eq!(registration.expires, 600);
2633        assert_eq!(
2634            registration.from_uri.as_deref(),
2635            Some("sip:1001@pbx.example.test")
2636        );
2637        assert_eq!(
2638            registration.contact_uri.as_deref(),
2639            Some("sip:1001@192.0.2.10:5060")
2640        );
2641    }
2642
2643    #[test]
2644    fn sip_account_derives_compatible_registration_endpoint_account_and_credentials() {
2645        let account = SipAccount::new("sip:pbx.example.test", "1001", "secret")
2646            .auth_username("auth1001")
2647            .expires(600)
2648            .from_uri("sip:1001@pbx.example.test")
2649            .contact_uri("sip:1001@192.0.2.10:5060");
2650
2651        let credentials = account.credentials();
2652        assert_eq!(credentials.username, "auth1001");
2653        assert_eq!(credentials.password, "secret");
2654
2655        let registration = account.registration();
2656        assert_eq!(registration.registrar, "sip:pbx.example.test");
2657        assert_eq!(registration.username, "auth1001");
2658        assert_eq!(registration.password, "secret");
2659        assert_eq!(registration.expires, 600);
2660        assert_eq!(
2661            registration.from_uri.as_deref(),
2662            Some("sip:1001@pbx.example.test")
2663        );
2664        assert_eq!(
2665            registration.contact_uri.as_deref(),
2666            Some("sip:1001@192.0.2.10:5060")
2667        );
2668
2669        let endpoint_account = account.endpoint_account();
2670        assert_eq!(endpoint_account.username, "1001");
2671        assert_eq!(endpoint_account.auth_username.as_deref(), Some("auth1001"));
2672
2673        let legacy: EndpointAccount = account.clone().into();
2674        let round_trip: SipAccount = legacy.into();
2675        assert_eq!(round_trip.effective_auth_username(), "auth1001");
2676        assert_eq!(round_trip.username, "1001");
2677    }
2678
2679    #[test]
2680    fn endpoint_normalizes_bare_extension_through_registrar() {
2681        let target = normalize_target(
2682            Some("sips:pbx.example.test:5061;transport=tls"),
2683            "1002",
2684            EndpointTransport::Udp,
2685        )
2686        .unwrap();
2687        assert_eq!(target, "sips:1002@pbx.example.test:5061;transport=tls");
2688    }
2689
2690    #[test]
2691    fn endpoint_leaves_full_sip_uri_unchanged() {
2692        let target = normalize_target(
2693            Some("sips:pbx.example.test:5061"),
2694            "sip:bob@example.test",
2695            EndpointTransport::Udp,
2696        )
2697        .unwrap();
2698        assert_eq!(target, "sip:bob@example.test");
2699    }
2700
2701    #[test]
2702    fn endpoint_requires_registrar_for_bare_target() {
2703        let err = normalize_target(None, "1002", EndpointTransport::Udp).unwrap_err();
2704        assert!(err.to_string().contains("registrar"));
2705    }
2706
2707    #[test]
2708    fn endpoint_transport_rewrites_tls_target() {
2709        let target = normalize_target(
2710            Some("sip:pbx.example.test:5060"),
2711            "1002",
2712            EndpointTransport::Tls,
2713        )
2714        .unwrap();
2715        assert_eq!(target, "sips:1002@pbx.example.test:5060");
2716    }
2717
2718    #[test]
2719    fn endpoint_json_config_maps_builder_fields() {
2720        let config = serde_json::from_str::<EndpointConfig>(
2721            r#"{
2722                "name": "alice",
2723                "profile": "asterisk-udp",
2724                "auto180Ringing": false,
2725                "auto100Trying": false,
2726                "fastAutoAcceptIncomingCalls": true,
2727                "cleanupDiagnostics": true,
2728                "cleanupDiagnosticEvents": true,
2729                "appEventChannelCapacity": 512,
2730                "srtpDiagnostics": true,
2731                "rtpDiagnostics": true,
2732                "mediaSdpDiagnostics": true,
2733                "account": {
2734                    "username": "1001",
2735                    "password": "secret",
2736                    "registrar": "sip:pbx.example.test"
2737                },
2738                "network": {
2739                    "bind": "127.0.0.1:5060",
2740                    "transport": "tcp",
2741                    "stun": "stun.example.test:3478",
2742                    "udpParseWorkers": 4,
2743                    "udpParseQueueCapacity": 8192
2744                },
2745                "media": {
2746                    "publicAddress": "192.0.2.10",
2747                    "enabled": false,
2748                    "signalingOnlyRtpPort": 9,
2749                    "srtp": "offer"
2750                }
2751            }"#,
2752        )
2753        .unwrap();
2754
2755        let parts = EndpointBuilder::from_config(config)
2756            .unwrap()
2757            .build_parts()
2758            .unwrap();
2759        assert_eq!(parts.transport, EndpointTransport::Tcp);
2760        assert_eq!(
2761            parts.config.stun_server.as_deref(),
2762            Some("stun.example.test:3478")
2763        );
2764        assert!(parts.config.offer_srtp);
2765        assert!(!parts.config.srtp_required);
2766        assert!(!parts.config.auto_180_ringing);
2767        assert!(!parts.config.auto_100_trying);
2768        assert!(parts.config.fast_auto_accept_incoming_calls);
2769        assert!(parts.config.cleanup_diagnostics);
2770        assert!(parts.config.cleanup_diagnostic_events);
2771        assert_eq!(parts.config.global_event_channel_capacity, 512);
2772        assert_eq!(parts.config.session_event_dispatcher_channel_capacity, 512);
2773        assert!(parts.config.srtp_diagnostics);
2774        assert!(parts.config.rtp_diagnostics);
2775        assert!(parts.config.media_sdp_diagnostics);
2776        assert_eq!(parts.config.sip_udp_parse_workers, Some(4));
2777        assert_eq!(parts.config.sip_udp_parse_queue_capacity, Some(8192));
2778        assert_eq!(
2779            parts.config.media_mode,
2780            MediaMode::SignalingOnly { sdp_rtp_port: 9 }
2781        );
2782        assert_eq!(
2783            parts.config.media_public_addr,
2784            Some("192.0.2.10:0".parse().unwrap())
2785        );
2786        assert_eq!(
2787            parts.registrar.as_deref(),
2788            Some("sip:pbx.example.test;transport=tcp")
2789        );
2790    }
2791
2792    #[test]
2793    fn endpoint_json_performance_profile_maps_into_config() {
2794        let config = serde_json::from_str::<EndpointConfig>(
2795            r#"{
2796                "name": "perf",
2797                "performance": {
2798                    "profile": "pbx-media-server",
2799                    "capacity": 2000
2800                },
2801                "network": {
2802                    "udpParseWorkers": 2
2803                },
2804                "sipTransactionCommandChannelCapacity": 256,
2805                "serverCallAdmissionLimit": 3000,
2806                "serverCallAdmissionSoftLimit": 2500,
2807                "serverCallAdmissionPacingDelayMs": 3,
2808                "serverOverloadRetryAfterSecs": 2
2809            }"#,
2810        )
2811        .unwrap();
2812
2813        let parts = EndpointBuilder::from_config(config)
2814            .unwrap()
2815            .build_parts()
2816            .unwrap();
2817        assert!(parts.config.fast_auto_accept_incoming_calls);
2818        assert_eq!(parts.config.media_mode, MediaMode::Enabled);
2819        assert_eq!(parts.config.media_port_start, 16_384);
2820        assert_eq!(parts.config.media_port_capacity, Some(49_152));
2821        assert_eq!(parts.config.media_session_capacity, Some(2_000));
2822        assert_eq!(parts.config.sip_udp_parse_workers, Some(2));
2823        assert_eq!(
2824            parts.config.sip_udp_parse_dispatch,
2825            Some(rvoip_sip_transport::UdpParseDispatch::RoundRobin)
2826        );
2827        assert_eq!(
2828            parts.config.sip_transaction_command_channel_capacity,
2829            Some(256)
2830        );
2831        assert_eq!(parts.config.server_call_capacity, Some(2_000));
2832        assert_eq!(parts.config.server_call_admission_limit, Some(3_000));
2833        assert_eq!(parts.config.server_call_admission_soft_limit, Some(2_500));
2834        assert_eq!(parts.config.server_call_admission_pacing_delay_ms, Some(3));
2835        assert_eq!(parts.config.server_overload_retry_after_secs, Some(2));
2836    }
2837
2838    #[test]
2839    fn endpoint_json_endpoint_performance_recipe_is_default_shape() {
2840        let config = serde_json::from_str::<EndpointConfig>(
2841            r#"{
2842                "name": "softphone",
2843                "performance": {
2844                    "profile": "endpoint"
2845                }
2846            }"#,
2847        )
2848        .unwrap();
2849
2850        let parts = EndpointBuilder::from_config(config)
2851            .unwrap()
2852            .build_parts()
2853            .unwrap();
2854        assert!(parts.config.auto_180_ringing);
2855        assert!(parts.config.auto_100_trying);
2856        assert!(!parts.config.fast_auto_accept_incoming_calls);
2857        assert_eq!(parts.config.media_mode, MediaMode::Enabled);
2858        assert_eq!(parts.config.sip_udp_parse_workers, None);
2859        assert_eq!(parts.config.sip_transaction_command_channel_capacity, None);
2860    }
2861
2862    #[test]
2863    fn endpoint_json_signaling_only_performance_profile_maps_into_config() {
2864        let config = serde_json::from_str::<EndpointConfig>(
2865            r#"{
2866                "name": "perf",
2867                "performance": {
2868                    "profile": "signaling-only-server-high-performance",
2869                    "capacity": 2000,
2870                    "signalingOnlyRtpPort": 4000
2871                }
2872            }"#,
2873        )
2874        .unwrap();
2875
2876        let parts = EndpointBuilder::from_config(config)
2877            .unwrap()
2878            .build_parts()
2879            .unwrap();
2880        assert_eq!(
2881            parts.config.media_mode,
2882            MediaMode::SignalingOnly { sdp_rtp_port: 4000 }
2883        );
2884        assert_eq!(parts.config.sip_udp_parse_workers, Some(4));
2885        assert_eq!(
2886            parts.config.sip_transaction_command_channel_capacity,
2887            Some(128)
2888        );
2889        assert_eq!(parts.config.server_call_capacity, Some(2_000));
2890        assert_eq!(parts.config.server_call_admission_limit, Some(2_000));
2891        assert_eq!(parts.config.server_call_admission_soft_limit, Some(1_800));
2892        assert_eq!(parts.config.server_call_admission_pacing_delay_ms, Some(1));
2893    }
2894
2895    #[test]
2896    fn endpoint_json_config_accepts_partial_sip_trace_config() {
2897        let config = serde_json::from_str::<EndpointConfig>(
2898            r#"{
2899                "sipTrace": {
2900                    "enabled": true,
2901                    "redactSensitiveHeaders": false
2902                }
2903            }"#,
2904        )
2905        .unwrap();
2906
2907        let trace = config.sip_trace.unwrap();
2908        assert!(trace.enabled);
2909        assert_eq!(
2910            trace.capacity,
2911            crate::api::events::SipTraceConfig::DEFAULT_CAPACITY
2912        );
2913        assert!(!trace.redact_sensitive_headers);
2914        assert!(trace.include_body);
2915    }
2916
2917    #[test]
2918    fn sip_client_example_stays_on_endpoint_surface() {
2919        let source = [
2920            include_str!("../../examples/sip_client/main.rs"),
2921            include_str!("../../examples/sip_client/audio.rs"),
2922            include_str!("../../examples/sip_client/config.rs"),
2923            include_str!("../../examples/sip_client/runtime.rs"),
2924            include_str!("../../examples/sip_client/smoke.rs"),
2925            include_str!("../../examples/sip_client/ui.rs"),
2926        ]
2927        .join("\n");
2928        for banned in [
2929            "StreamPeer",
2930            "PeerControl",
2931            "UnifiedCoordinator",
2932            "RegistrationHandle",
2933            "SessionHandle",
2934            "SipTlsMode",
2935            "rvoip_media_core",
2936        ] {
2937            assert!(
2938                !source.contains(banned),
2939                "sip_client example must not reference lower-level API {banned}"
2940            );
2941        }
2942    }
2943
2944    #[test]
2945    fn endpoint_audio_roundtrip_stays_on_endpoint_surface() {
2946        let source = include_str!("../../examples/endpoint/04_audio_roundtrip/main.rs");
2947        for banned in [
2948            "StreamPeer",
2949            "PeerControl",
2950            "UnifiedCoordinator",
2951            "RegistrationHandle",
2952            "SessionHandle",
2953            "as_session_handle",
2954            "rvoip_media_core",
2955        ] {
2956            assert!(
2957                !source.contains(banned),
2958                "endpoint audio roundtrip example must not reference lower-level API {banned}"
2959            );
2960        }
2961    }
2962}