1use std::net::{IpAddr, SocketAddr};
38use std::sync::Arc;
39use std::time::Duration;
40
41use tokio::net::UdpSocket;
42use tokio::sync::{mpsc, watch};
43use tokio::task::JoinHandle;
44use tokio_util::sync::CancellationToken;
45
46use rsipstack::EndpointBuilder;
47use rsipstack::dialog::dialog::DialogState;
48use rsipstack::dialog::dialog_layer::DialogLayer;
49use rsipstack::dialog::invite_dialog::InviteDialog;
50use rsipstack::transport::TransportLayer;
51use rsipstack::transport::udp::UdpConnection;
52
53use gemini_adk_rs::State;
54use gemini_adk_rs::live::LiveHandle;
55
56use super::bridge::{self, DtmfDeduper, FillerConfig};
57use super::g711;
58use super::rtp::{self, PT_PCMA, RtpSender, SAMPLES_PER_PACKET};
59use super::sdp::{self, AudioOffer};
60use super::srtp::{CryptoAttribute, MasterKey, SrtpSession};
61use crate::voice::{Playback, VoicePump, pump};
62
63#[derive(Debug)]
65pub enum SipError {
66 Io(std::io::Error),
68 Sip(Box<rsipstack::Error>),
70 NoAudioOffer,
72 NoCommonCodec,
74 NoCommonCrypto,
76 RegistrationRejected(u16),
78 InvalidUri(String),
80}
81
82impl std::fmt::Display for SipError {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 Self::Io(e) => write!(f, "sip io error: {e}"),
86 Self::Sip(e) => write!(f, "sip stack error: {e:?}"),
87 Self::NoAudioOffer => write!(f, "INVITE carried no answerable audio offer"),
88 Self::NoCommonCodec => write!(f, "no common G.711 codec with the caller"),
89 Self::NoCommonCrypto => write!(f, "no common SRTP crypto suite with the caller"),
90 Self::RegistrationRejected(code) => {
91 write!(f, "registrar refused the registration: {code}")
92 }
93 Self::InvalidUri(uri) => write!(f, "invalid SIP URI: {uri}"),
94 }
95 }
96}
97
98impl std::error::Error for SipError {}
99
100impl From<std::io::Error> for SipError {
101 fn from(e: std::io::Error) -> Self {
102 Self::Io(e)
103 }
104}
105
106impl From<rsipstack::Error> for SipError {
107 fn from(e: rsipstack::Error) -> Self {
108 Self::Sip(Box::new(e))
109 }
110}
111
112pub struct SipAgent {
116 dialog_layer: Arc<DialogLayer>,
117 incoming: rsipstack::transaction::TransactionReceiver,
118 cancel: CancellationToken,
119 local_ip: IpAddr,
120 sip_port: u16,
121}
122
123impl SipAgent {
124 pub async fn bind(addr: SocketAddr) -> Result<SipAgent, SipError> {
127 let cancel = CancellationToken::new();
128 let transport_layer = TransportLayer::new(cancel.child_token());
129 let udp = UdpConnection::create_connection(addr, None, Some(cancel.child_token()))
130 .await
131 .map_err(SipError::from)?;
132 let sip_port = udp
133 .get_addr()
134 .addr
135 .port
136 .as_ref()
137 .map(|p| u16::from(*p))
138 .unwrap_or(addr.port());
139 transport_layer.add_transport(udp.into());
140
141 let endpoint = EndpointBuilder::new()
142 .with_user_agent("gemini-rs")
143 .with_cancel_token(cancel.child_token())
144 .with_transport_layer(transport_layer)
145 .build();
146 let inner = endpoint.inner.clone();
149 tokio::spawn(async move {
150 let _ = inner.serve().await;
151 });
152
153 let incoming = endpoint.incoming_transactions().map_err(SipError::from)?;
154 let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone()));
155
156 Ok(SipAgent {
157 dialog_layer,
158 incoming,
159 cancel,
160 local_ip: addr.ip(),
161 sip_port,
162 })
163 }
164
165 pub async fn next_call(&mut self) -> Option<IncomingCall> {
169 loop {
170 let mut tx = tokio::select! {
171 _ = self.cancel.cancelled() => return None,
172 tx = self.incoming.recv() => tx?,
173 };
174 use rsipstack::rsip::{Method, StatusCode};
175 match tx.original.method {
176 Method::Invite => {
177 let offer = match sdp::parse_audio_offer(
178 String::from_utf8_lossy(&tx.original.body).as_ref(),
179 ) {
180 Some(offer) => offer,
181 None => {
182 let _ = tx.reply(StatusCode::NotAcceptableHere).await;
183 continue;
184 }
185 };
186 let (state_tx, state_rx) = mpsc::unbounded_channel();
187 let contact = format!(
188 "sip:gemini@{}:{};transport=udp",
189 advertised_ip(self.local_ip, &offer),
190 self.sip_port
191 );
192 let contact = match rsipstack::rsip::Uri::try_from(contact.as_str()) {
193 Ok(uri) => uri,
194 Err(_) => {
195 let _ = tx.reply(StatusCode::ServerInternalError).await;
196 continue;
197 }
198 };
199 let dialog = match self.dialog_layer.get_or_create_server_invite(
200 &tx,
201 state_tx,
202 None,
203 Some(contact),
204 ) {
205 Ok(dialog) => dialog,
206 Err(err) => {
207 tracing::warn!("rejecting INVITE: {err:?}");
208 let _ = tx.reply(StatusCode::ServerInternalError).await;
209 continue;
210 }
211 };
212 use rsipstack::rsip::HeadersExt as _;
213 let from = tx
214 .original
215 .from_header()
216 .map(std::string::ToString::to_string)
217 .unwrap_or_default();
218 let _ = dialog.ringing(None, None);
219 tokio::spawn(async move { while tx.receive().await.is_some() {} });
223 return Some(IncomingCall {
224 dialog,
225 state_rx,
226 offer,
227 from,
228 local_ip: self.local_ip,
229 dialog_layer: self.dialog_layer.clone(),
230 filler: None,
231 });
232 }
233 Method::Ack | Method::Bye | Method::Cancel | Method::Info | Method::Update => {
234 match self.dialog_layer.match_dialog(&tx) {
236 Some(mut dialog) => {
237 tokio::spawn(async move {
238 let _ = dialog.handle(&mut tx).await;
239 });
240 }
241 None => {
242 let _ = tx.reply(StatusCode::CallTransactionDoesNotExist).await;
243 }
244 }
245 }
246 Method::Options => {
247 let _ = tx.reply(StatusCode::OK).await;
248 }
249 _ => {
250 let _ = tx.reply(StatusCode::MethodNotAllowed).await;
251 }
252 }
253 }
254 }
255
256 pub fn sip_port(&self) -> u16 {
258 self.sip_port
259 }
260
261 pub fn shutdown(&self) {
263 self.cancel.cancel();
264 }
265
266 pub async fn register(&self, account: SipAccount) -> Result<SipRegistration, SipError> {
285 use rsipstack::dialog::authenticate::Credential;
286 use rsipstack::dialog::registration::Registration;
287
288 let registrar = rsipstack::rsip::Uri::try_from(account.registrar.as_str())
289 .map_err(|_| SipError::InvalidUri(account.registrar.clone()))?;
290 let mut registration = Registration::new(
291 self.dialog_layer.endpoint.clone(),
292 Some(Credential {
293 username: account.username.clone(),
294 password: account.password.clone(),
295 realm: account.realm.clone(),
296 auth_username: None,
297 }),
298 );
299 if let Some(contact) = &account.contact {
300 let uri = rsipstack::rsip::Uri::try_from(contact.as_str())
301 .map_err(|_| SipError::InvalidUri(contact.clone()))?;
302 registration.contact = Some(rsipstack::rsip::typed::Contact {
303 display_name: None,
304 uri,
305 params: vec![],
306 });
307 }
308
309 let requested = account.expires;
310 let granted = register_once(&mut registration, ®istrar, requested).await?;
311 let (state_tx, state_rx) =
312 watch::channel(RegistrationState::Registered { expires: granted });
313 let cancel = self.cancel.child_token();
314 let stop = cancel.clone();
315 let task = tokio::spawn(async move {
316 let mut next = refresh_after(granted);
317 loop {
318 tokio::select! {
319 _ = stop.cancelled() => break,
320 _ = tokio::time::sleep(next) => {}
321 }
322 match register_once(&mut registration, ®istrar, requested).await {
323 Ok(granted) => {
324 next = refresh_after(granted);
325 let _ = state_tx.send(RegistrationState::Registered { expires: granted });
326 }
327 Err(error) => {
328 tracing::warn!("SIP re-registration failed: {error}");
329 next = RETRY_AFTER;
330 let _ = state_tx.send(RegistrationState::Retrying {
331 error: error.to_string(),
332 });
333 }
334 }
335 }
336 let removed = tokio::time::timeout(
338 UNREGISTER_TIMEOUT,
339 register_once(&mut registration, ®istrar, Duration::ZERO),
340 )
341 .await
342 .unwrap_or_else(|_| Err(SipError::Io(std::io::ErrorKind::TimedOut.into())));
343 let _ = state_tx.send(RegistrationState::Unregistered);
344 removed.map(|_| ())
345 });
346 Ok(SipRegistration {
347 state: state_rx,
348 cancel: cancel.drop_guard(),
349 task,
350 })
351 }
352}
353
354const RETRY_AFTER: Duration = Duration::from_secs(30);
358const UNREGISTER_TIMEOUT: Duration = Duration::from_secs(5);
360
361#[derive(Clone)]
363pub struct SipAccount {
364 pub registrar: String,
366 pub username: String,
368 pub password: String,
370 pub realm: Option<String>,
372 pub contact: Option<String>,
376 pub expires: Duration,
378}
379
380impl SipAccount {
381 pub fn new(
383 registrar: impl Into<String>,
384 username: impl Into<String>,
385 password: impl Into<String>,
386 ) -> Self {
387 Self {
388 registrar: registrar.into(),
389 username: username.into(),
390 password: password.into(),
391 realm: None,
392 contact: None,
393 expires: Duration::from_secs(3600),
394 }
395 }
396
397 pub fn realm(mut self, realm: impl Into<String>) -> Self {
399 self.realm = Some(realm.into());
400 self
401 }
402
403 pub fn contact(mut self, contact: impl Into<String>) -> Self {
405 self.contact = Some(contact.into());
406 self
407 }
408
409 pub fn expires(mut self, expires: Duration) -> Self {
411 self.expires = expires;
412 self
413 }
414}
415
416impl std::fmt::Debug for SipAccount {
417 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418 f.debug_struct("SipAccount")
419 .field("registrar", &self.registrar)
420 .field("username", &self.username)
421 .field("password", &"[redacted]")
422 .field("realm", &self.realm)
423 .field("contact", &self.contact)
424 .field("expires", &self.expires)
425 .finish()
426 }
427}
428
429#[derive(Debug, Clone, PartialEq, Eq)]
431#[non_exhaustive]
432pub enum RegistrationState {
433 Registered {
435 expires: Duration,
437 },
438 Retrying {
441 error: String,
443 },
444 Unregistered,
446}
447
448pub struct SipRegistration {
451 state: watch::Receiver<RegistrationState>,
452 cancel: tokio_util::sync::DropGuard,
455 task: JoinHandle<Result<(), SipError>>,
456}
457
458impl SipRegistration {
459 pub fn state(&self) -> RegistrationState {
461 self.state.borrow().clone()
462 }
463
464 pub fn watch(&self) -> watch::Receiver<RegistrationState> {
467 self.state.clone()
468 }
469
470 pub async fn unregister(self) -> Result<(), SipError> {
473 drop(self.cancel);
474 match self.task.await {
475 Ok(result) => result,
476 Err(_) => Ok(()),
477 }
478 }
479}
480
481async fn register_once(
483 registration: &mut rsipstack::dialog::registration::Registration,
484 registrar: &rsipstack::rsip::Uri,
485 expires: Duration,
486) -> Result<Duration, SipError> {
487 let requested = u32::try_from(expires.as_secs()).unwrap_or(u32::MAX);
488 let response = registration
489 .register(registrar.clone(), Some(requested))
490 .await?;
491 let code = u16::from(response.status_code.clone());
492 if !(200..300).contains(&code) {
493 return Err(SipError::RegistrationRejected(code));
494 }
495 Ok(granted_expires(&response).unwrap_or(expires))
496}
497
498fn granted_expires(response: &rsipstack::rsip::Response) -> Option<Duration> {
501 use rsipstack::rsip::Header;
502 let mut header = None;
503 for h in response.headers.iter() {
504 match h {
505 Header::Contact(contact) => {
506 let text = contact.to_string().to_ascii_lowercase();
507 if let Some(value) = text
508 .split(';')
509 .find_map(|p| p.trim().strip_prefix("expires="))
510 {
511 let digits: String = value.chars().take_while(char::is_ascii_digit).collect();
512 if let Ok(secs) = digits.parse() {
513 return Some(Duration::from_secs(secs));
514 }
515 }
516 }
517 Header::Expires(expires) => {
518 header = expires.value().trim().parse().ok().map(Duration::from_secs);
519 }
520 _ => {}
521 }
522 }
523 header
524}
525
526fn refresh_after(expires: Duration) -> Duration {
529 (expires * 3 / 4).max(Duration::from_secs(5))
530}
531
532pub struct IncomingCall {
536 dialog: InviteDialog,
537 state_rx: mpsc::UnboundedReceiver<DialogState>,
538 pub offer: AudioOffer,
540 pub from: String,
542 local_ip: IpAddr,
543 dialog_layer: Arc<DialogLayer>,
544 filler: Option<FillerConfig>,
545}
546
547impl IncomingCall {
548 pub fn filler(mut self, config: FillerConfig) -> Self {
553 self.filler = Some(config);
554 self
555 }
556
557 pub async fn answer(self, handle: &LiveHandle) -> Result<SipCall, SipError> {
565 let payload_type = self.offer.g711_payload_type().ok_or_else(|| {
566 let _ = self.dialog.reject(None, None);
567 SipError::NoCommonCodec
568 })?;
569
570 let media_ip = advertised_ip(self.local_ip, &self.offer);
571 let rtp_socket = UdpSocket::bind((self.local_ip, 0)).await?;
572 let rtp_port = rtp_socket.local_addr()?.port();
573 let remote: SocketAddr = format!("{}:{}", self.offer.host, self.offer.port)
574 .parse()
575 .map_err(|_| SipError::NoAudioOffer)?;
576
577 let telephone_event_pt = self.offer.telephone_event_pt;
578 let (answer, srtp) = if self.offer.secure {
579 let Some(theirs) = self
582 .offer
583 .crypto
584 .iter()
585 .find_map(|c| CryptoAttribute::parse(c))
586 else {
587 let _ = self
588 .dialog
589 .reject(Some(rsipstack::rsip::StatusCode::NotAcceptableHere), None);
590 return Err(SipError::NoCommonCrypto);
591 };
592 let ours = CryptoAttribute {
593 tag: theirs.tag,
594 suite: theirs.suite,
595 key: MasterKey::generate()?,
596 };
597 let answer = sdp::secure_audio_answer(
598 seed() as u64,
599 &media_ip.to_string(),
600 rtp_port,
601 payload_type,
602 telephone_event_pt,
603 &ours.to_value(),
604 );
605 let srtp = SrtpPair {
606 inbound: SrtpSession::new(theirs.suite, &theirs.key),
607 outbound: SrtpSession::new(ours.suite, &ours.key),
608 };
609 (answer, Some(srtp))
610 } else {
611 let answer = sdp::audio_answer(
612 seed() as u64,
613 &media_ip.to_string(),
614 rtp_port,
615 payload_type,
616 telephone_event_pt,
617 );
618 (answer, None)
619 };
620 self.dialog
621 .accept(None, Some(answer.into_bytes()))
622 .map_err(SipError::from)?;
623 let _ = handle.state().set(bridge::KEY_CALLER, self.from.clone());
624
625 let cancel = CancellationToken::new();
626 let media = rtp_media(
627 handle,
628 Arc::new(rtp_socket),
629 remote,
630 MediaFormat {
631 payload_type,
632 telephone_event_pt,
633 },
634 self.filler,
635 srtp,
636 cancel.clone(),
637 );
638
639 let mut state_rx = self.state_rx;
641 let media_cancel = cancel.clone();
642 let dialog_id = self.dialog.id();
643 let dialog_layer = self.dialog_layer;
644 let ended = tokio::spawn(async move {
645 while let Some(state) = state_rx.recv().await {
646 if let DialogState::Terminated(_, _) = state {
647 break;
648 }
649 }
650 media_cancel.cancel();
651 dialog_layer.remove_dialog(&dialog_id);
652 });
653
654 Ok(SipCall {
655 dialog: self.dialog,
656 media,
657 cancel,
658 ended,
659 })
660 }
661
662 pub fn reject(self) {
664 let _ = self.dialog.reject(None, None);
665 }
666}
667
668pub struct SipCall {
672 dialog: InviteDialog,
673 media: MediaTasks,
674 cancel: CancellationToken,
675 ended: JoinHandle<()>,
676}
677
678impl SipCall {
679 pub async fn ended(self) {
681 let _ = self.ended.await;
682 self.media.stop().await;
683 }
684
685 pub async fn hangup(self) {
687 let _ = self.dialog.bye().await;
688 self.cancel.cancel();
689 let _ = self.ended.await;
690 self.media.stop().await;
691 }
692}
693
694struct MediaTasks {
697 pump: VoicePump,
698 inbound: JoinHandle<()>,
699 outbound: JoinHandle<()>,
700 filler: Option<JoinHandle<()>>,
701}
702
703impl MediaTasks {
704 async fn stop(self) {
705 self.inbound.abort();
706 self.outbound.abort();
707 if let Some(filler) = self.filler {
708 filler.abort();
709 }
710 self.pump.abort();
711 self.pump.join().await;
712 }
713}
714
715#[derive(Clone, Copy)]
717struct MediaFormat {
718 payload_type: u8,
720 telephone_event_pt: Option<u8>,
722}
723
724struct SrtpPair {
726 inbound: SrtpSession,
728 outbound: SrtpSession,
730}
731
732fn rtp_media(
737 handle: &LiveHandle,
738 socket: Arc<UdpSocket>,
739 remote: SocketAddr,
740 format: MediaFormat,
741 filler: Option<FillerConfig>,
742 srtp: Option<SrtpPair>,
743 cancel: CancellationToken,
744) -> MediaTasks {
745 let (srtp_in, srtp_out) = match srtp {
746 Some(pair) => (Some(pair.inbound), Some(pair.outbound)),
747 None => (None, None),
748 };
749 let (mic_tx, mic_rx) = mpsc::channel::<Vec<i16>>(64);
750 let (speaker_tx, speaker_rx) = mpsc::channel::<Playback>(64);
751 let voice_pump = pump(
752 handle,
753 mic_rx,
754 super::TWILIO_HZ,
755 speaker_tx.clone(),
756 super::TWILIO_HZ,
757 );
758 let (peer_tx, peer_rx) = watch::channel(remote);
759
760 let filler = filler.map(|config| bridge::spawn_latency_filler(handle, speaker_tx, config));
761
762 let inbound = tokio::spawn(inbound_loop(
763 socket.clone(),
764 format,
765 handle.state().clone(),
766 mic_tx,
767 peer_tx,
768 srtp_in,
769 cancel.clone(),
770 ));
771 let outbound = tokio::spawn(outbound_loop(
772 socket,
773 format.payload_type,
774 speaker_rx,
775 peer_rx,
776 srtp_out,
777 cancel,
778 ));
779
780 MediaTasks {
781 pump: voice_pump,
782 inbound,
783 outbound,
784 filler,
785 }
786}
787
788async fn inbound_loop(
789 socket: Arc<UdpSocket>,
790 format: MediaFormat,
791 state: State,
792 mic_tx: mpsc::Sender<Vec<i16>>,
793 peer_tx: watch::Sender<SocketAddr>,
794 mut srtp: Option<SrtpSession>,
795 cancel: CancellationToken,
796) {
797 let MediaFormat {
798 payload_type,
799 telephone_event_pt,
800 } = format;
801 let mut buf = [0u8; 2048];
802 let mut latched = false;
803 let mut dtmf = DtmfDeduper::default();
804 let mut guard =
807 bridge::KeypadGuard::new(state.clone(), 8_000).record_in_band(telephone_event_pt.is_none());
808 loop {
809 let (len, source) = tokio::select! {
810 _ = cancel.cancelled() => break,
811 received = socket.recv_from(&mut buf) => match received {
812 Ok(pair) => pair,
813 Err(_) => break,
814 },
815 };
816 let decrypted;
817 let datagram = match srtp.as_mut() {
818 Some(srtp) => match srtp.unprotect(&buf[..len]) {
821 Ok(plain) => {
822 decrypted = plain;
823 &decrypted[..]
824 }
825 Err(_) => continue,
826 },
827 None => &buf[..len],
828 };
829 let Some(packet) = rtp::parse(datagram) else {
830 continue; };
832 if telephone_event_pt == Some(packet.payload_type) {
833 if let Some(event) = rtp::parse_telephone_event(&packet.payload)
835 && dtmf.accept(event.end, packet.timestamp)
836 && let Some(digit) = event.digit()
837 {
838 bridge::record_dtmf(&state, digit);
839 }
840 continue;
841 }
842 if packet.payload_type != payload_type {
843 continue; }
845 if !latched {
846 let _ = peer_tx.send(source);
847 latched = true;
848 }
849 let mut samples = if payload_type == PT_PCMA {
850 g711::decode_alaw(&packet.payload)
851 } else {
852 g711::decode_ulaw(&packet.payload)
853 };
854 crate::voice::InputAudioProcessor::process_frame(&mut guard, &mut samples);
855 if mic_tx.send(samples).await.is_err() {
856 break;
857 }
858 }
859}
860
861async fn outbound_loop(
862 socket: Arc<UdpSocket>,
863 payload_type: u8,
864 mut speaker_rx: mpsc::Receiver<Playback>,
865 peer_rx: watch::Receiver<SocketAddr>,
866 mut srtp: Option<SrtpSession>,
867 cancel: CancellationToken,
868) {
869 let silence_byte: u8 = if payload_type == PT_PCMA { 0xD5 } else { 0xFF };
870 let seed = seed();
871 let mut sender = RtpSender::new(payload_type, seed, (seed >> 16) as u16, seed.rotate_left(8));
872 let mut pending: std::collections::VecDeque<i16> = std::collections::VecDeque::new();
873 let mut ticker = tokio::time::interval(Duration::from_millis(20));
874 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
875
876 loop {
877 tokio::select! {
878 _ = cancel.cancelled() => break,
879 playback = speaker_rx.recv() => match playback {
880 Some(Playback::Chunk(samples)) => pending.extend(samples),
881 Some(Playback::Flush) => pending.clear(),
883 None => break,
884 },
885 _ = ticker.tick() => {
886 if pending.is_empty() {
887 sender.skip_silence(SAMPLES_PER_PACKET as u32);
888 continue;
889 }
890 let take = pending.len().min(SAMPLES_PER_PACKET);
891 let mut payload = Vec::with_capacity(SAMPLES_PER_PACKET);
892 for sample in pending.drain(..take) {
893 payload.push(if payload_type == PT_PCMA {
894 g711::linear_to_alaw(sample)
895 } else {
896 g711::linear_to_ulaw(sample)
897 });
898 }
899 payload.resize(SAMPLES_PER_PACKET, silence_byte);
901 let mut datagram = sender.packetize(&payload, SAMPLES_PER_PACKET as u32);
902 if let Some(srtp) = srtp.as_mut() {
903 match srtp.protect(&datagram) {
904 Some(protected) => datagram = protected,
905 None => continue,
906 }
907 }
908 let target = *peer_rx.borrow();
909 if socket.send_to(&datagram, target).await.is_err() {
910 break;
911 }
912 }
913 }
914 }
915}
916
917fn advertised_ip(local: IpAddr, offer: &AudioOffer) -> IpAddr {
920 if !local.is_unspecified() {
921 return local;
922 }
923 let probe = std::net::UdpSocket::bind("0.0.0.0:0")
924 .and_then(|s| {
925 s.connect((offer.host.as_str(), offer.port))?;
926 s.local_addr()
927 })
928 .map(|a| a.ip());
929 probe.unwrap_or(local)
930}
931
932fn seed() -> u32 {
934 let nanos = std::time::SystemTime::now()
935 .duration_since(std::time::UNIX_EPOCH)
936 .map(|d| d.subsec_nanos())
937 .unwrap_or(0);
938 nanos ^ (std::process::id().rotate_left(16))
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944
945 async fn recv_status(socket: &UdpSocket, buf: &mut [u8]) -> Option<String> {
946 let deadline = Duration::from_secs(3);
947 let (len, _) = tokio::time::timeout(deadline, socket.recv_from(buf))
948 .await
949 .ok()?
950 .ok()?;
951 String::from_utf8_lossy(&buf[..len])
952 .lines()
953 .next()
954 .map(str::to_string)
955 }
956
957 fn test_password() -> String {
959 MasterKey::generate().unwrap().to_inline()
960 }
961
962 async fn fake_registrar() -> (SocketAddr, mpsc::UnboundedReceiver<String>) {
965 fake_registrar_with_expiry(60).await
966 }
967
968 async fn fake_registrar_with_expiry(
969 expires: u32,
970 ) -> (SocketAddr, mpsc::UnboundedReceiver<String>) {
971 let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
972 let addr = socket.local_addr().unwrap();
973 let (seen_tx, seen_rx) = mpsc::unbounded_channel();
974 tokio::spawn(async move {
975 let mut buf = [0u8; 4096];
976 while let Ok((len, from)) = socket.recv_from(&mut buf).await {
977 let request = String::from_utf8_lossy(&buf[..len]).to_string();
978 let echoed: Vec<&str> = request
979 .lines()
980 .filter(|l| {
981 let l = l.to_ascii_lowercase();
982 ["via:", "from:", "call-id:", "cseq:"]
983 .iter()
984 .any(|h| l.starts_with(h))
985 })
986 .collect();
987 let to = request
988 .lines()
989 .find(|l| l.to_ascii_lowercase().starts_with("to:"))
990 .unwrap_or("To: <sip:unknown@invalid>");
991 let authorized = request.to_ascii_lowercase().contains("\nauthorization:");
992 let (status, extra) = if authorized {
993 let contact = request
994 .lines()
995 .find(|l| l.to_ascii_lowercase().starts_with("contact:"))
996 .unwrap_or("Contact: <sip:alice@127.0.0.1>");
997 ("200 OK", format!("{contact};expires={expires}\r\n"))
998 } else {
999 (
1000 "401 Unauthorized",
1001 "WWW-Authenticate: Digest realm=\"test\", nonce=\"n1\", algorithm=MD5\r\n"
1002 .to_string(),
1003 )
1004 };
1005 let response = format!(
1006 "SIP/2.0 {status}\r\n{}\r\n{to};tag=reg\r\n{extra}Content-Length: 0\r\n\r\n",
1007 echoed.join("\r\n")
1008 );
1009 let _ = socket.send_to(response.as_bytes(), from).await;
1010 let _ = seen_tx.send(request);
1011 }
1012 });
1013 (addr, seen_rx)
1014 }
1015
1016 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1017 async fn registration_refreshes_and_shutdown_stops_waiting_for_calls() {
1018 let (registrar, mut seen) = fake_registrar_with_expiry(8).await;
1019 let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1020 .await
1021 .unwrap();
1022 let registration = agent
1023 .register(SipAccount::new(
1024 format!("sip:{registrar}"),
1025 "alice",
1026 test_password(),
1027 ))
1028 .await
1029 .unwrap();
1030 seen.recv().await.unwrap();
1033 seen.recv().await.unwrap();
1034 let refresh = tokio::time::timeout(Duration::from_secs(10), seen.recv())
1035 .await
1036 .expect("registration refreshes")
1037 .expect("registrar running");
1038 assert!(refresh.starts_with("REGISTER "));
1039 if !refresh.to_ascii_lowercase().contains("authorization:") {
1040 let authenticated = tokio::time::timeout(Duration::from_secs(5), seen.recv())
1041 .await
1042 .expect("refresh answers the challenge")
1043 .expect("registrar running");
1044 assert!(
1045 authenticated
1046 .to_ascii_lowercase()
1047 .contains("authorization:")
1048 );
1049 }
1050 tokio::time::timeout(Duration::from_secs(5), registration.unregister())
1051 .await
1052 .expect("unregister finishes")
1053 .expect("unregister succeeds");
1054 let cancel = agent.cancel.clone();
1055 tokio::spawn(async move {
1056 tokio::time::sleep(Duration::from_millis(50)).await;
1057 cancel.cancel();
1058 });
1059 assert!(
1060 tokio::time::timeout(Duration::from_secs(2), agent.next_call())
1061 .await
1062 .expect("shutdown wakes next_call")
1063 .is_none()
1064 );
1065 }
1066
1067 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1068 async fn registers_with_digest_auth_and_unregisters() {
1069 let password = test_password();
1070 let (registrar, mut seen) = fake_registrar().await;
1071 let agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1072 .await
1073 .expect("bind agent");
1074
1075 let registration = tokio::time::timeout(
1076 Duration::from_secs(5),
1077 agent.register(
1078 SipAccount::new(format!("sip:{registrar}"), "alice", &password)
1079 .expires(Duration::from_secs(300)),
1080 ),
1081 )
1082 .await
1083 .expect("registration finishes")
1084 .expect("registrar accepts");
1085 assert_eq!(
1086 registration.state(),
1087 RegistrationState::Registered {
1088 expires: Duration::from_secs(60)
1089 },
1090 "the lifetime the registrar granted, not the one asked for"
1091 );
1092
1093 let first = seen.recv().await.unwrap();
1094 assert!(first.starts_with("REGISTER "), "{first}");
1095 assert!(!first.to_ascii_lowercase().contains("authorization:"));
1096 let answered = loop {
1097 let request = seen.recv().await.unwrap();
1098 if request.to_ascii_lowercase().contains("authorization:") {
1099 break request;
1100 }
1101 };
1102 for part in [
1103 "username=\"alice\"",
1104 "realm=\"test\"",
1105 "nonce=\"n1\"",
1106 "response=",
1107 ] {
1108 assert!(answered.contains(part), "missing {part} in {answered}");
1109 }
1110 assert!(
1111 !answered.contains(&password),
1112 "the password never goes on the wire"
1113 );
1114
1115 tokio::time::timeout(Duration::from_secs(5), registration.unregister())
1116 .await
1117 .expect("unregister finishes")
1118 .expect("registrar accepts the removal");
1119 let mut removed = false;
1120 while let Ok(request) = seen.try_recv() {
1121 removed |= request
1122 .lines()
1123 .any(|l| l.eq_ignore_ascii_case("expires: 0"));
1124 }
1125 assert!(
1126 removed,
1127 "a REGISTER with a zero lifetime removes the binding"
1128 );
1129 agent.shutdown();
1130 }
1131
1132 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1133 async fn a_refused_registration_fails_at_once() {
1134 let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1136 let registrar = socket.local_addr().unwrap();
1137 tokio::spawn(async move {
1138 let mut buf = [0u8; 4096];
1139 while let Ok((len, from)) = socket.recv_from(&mut buf).await {
1140 let request = String::from_utf8_lossy(&buf[..len]).to_string();
1141 let echoed: Vec<&str> = request
1142 .lines()
1143 .filter(|l| {
1144 let l = l.to_ascii_lowercase();
1145 ["via:", "from:", "to:", "call-id:", "cseq:"]
1146 .iter()
1147 .any(|h| l.starts_with(h))
1148 })
1149 .collect();
1150 let response = format!(
1151 "SIP/2.0 403 Forbidden\r\n{}\r\nContent-Length: 0\r\n\r\n",
1152 echoed.join("\r\n")
1153 );
1154 let _ = socket.send_to(response.as_bytes(), from).await;
1155 }
1156 });
1157 let agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1158 .await
1159 .unwrap();
1160 let result = tokio::time::timeout(
1161 Duration::from_secs(5),
1162 agent.register(SipAccount::new(
1163 format!("sip:{registrar}"),
1164 "mallory",
1165 test_password(),
1166 )),
1167 )
1168 .await
1169 .expect("finishes");
1170 assert!(
1171 matches!(result, Err(SipError::RegistrationRejected(403))),
1172 "{:?}",
1173 result.err()
1174 );
1175 agent.shutdown();
1176 }
1177
1178 async fn recv_response(socket: &UdpSocket, code: &str) -> String {
1180 let mut buf = [0u8; 4096];
1181 loop {
1182 let (len, _) = tokio::time::timeout(Duration::from_secs(3), socket.recv_from(&mut buf))
1183 .await
1184 .expect("a response")
1185 .unwrap();
1186 let text = String::from_utf8_lossy(&buf[..len]).to_string();
1187 if text.lines().next().is_some_and(|l| l.contains(code)) {
1188 return text;
1189 }
1190 }
1191 }
1192
1193 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1194 async fn an_srtp_offer_gets_encrypted_media_both_ways() {
1195 use super::super::srtp::SrtpSuite;
1196 use base64::Engine as _;
1197
1198 let tone: Vec<u8> = (0..4800i16)
1200 .flat_map(|i| (if i % 48 < 24 { 6000i16 } else { -6000 }).to_le_bytes())
1201 .collect();
1202 let (transport, control) = crate::live::scripted::ScriptedServer::new()
1203 .frame(serde_json::json!({
1204 "serverContent": { "modelTurn": { "parts": [{ "inlineData": {
1205 "mimeType": "audio/pcm;rate=24000",
1206 "data": base64::engine::general_purpose::STANDARD.encode(&tone),
1207 } }] } }
1208 }))
1209 .into_transport();
1210 let handle = crate::live::Live::builder()
1211 .connect_with_transport(transport)
1212 .await
1213 .unwrap();
1214
1215 let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1216 .await
1217 .unwrap();
1218 let target = format!("127.0.0.1:{}", agent.sip_port());
1219 let (call_tx, call_rx) = tokio::sync::oneshot::channel();
1220 tokio::spawn(async move {
1221 if let Some(incoming) = agent.next_call().await {
1222 let _ = call_tx.send(incoming);
1223 }
1224 std::future::pending::<()>().await;
1226 });
1227
1228 let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1230 let uac_port = uac.local_addr().unwrap().port();
1231 let media = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1232 let media_port = media.local_addr().unwrap().port();
1233 let caller_key = MasterKey::generate().unwrap();
1234 let sdp_body = format!(
1235 "v=0\r\no=probe 1 1 IN IP4 127.0.0.1\r\ns=call\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\n\
1236 m=audio {media_port} RTP/SAVP 0\r\na=rtpmap:0 PCMU/8000\r\n\
1237 a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:{}\r\n",
1238 caller_key.to_inline()
1239 );
1240 let invite = format!(
1241 "INVITE sip:gemini@{target} SIP/2.0\r\n\
1242 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKsrtp1\r\n\
1243 Max-Forwards: 70\r\n\
1244 From: <sip:probe@127.0.0.1>;tag=srtp\r\n\
1245 To: <sip:gemini@{target}>\r\n\
1246 Call-ID: srtp-1@127.0.0.1\r\n\
1247 CSeq: 1 INVITE\r\n\
1248 Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
1249 Content-Type: application/sdp\r\n\
1250 Content-Length: {}\r\n\r\n{sdp_body}",
1251 sdp_body.len()
1252 );
1253 uac.send_to(invite.as_bytes(), &target).await.unwrap();
1254 let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
1255 .await
1256 .unwrap()
1257 .unwrap();
1258 assert!(incoming.offer.secure);
1259 let _call = incoming.answer(&handle).await.expect("answers with SRTP");
1260
1261 let ok = recv_response(&uac, "200").await;
1262 let answer_sdp = ok.split("\r\n\r\n").nth(1).unwrap();
1263 let answer = sdp::parse_audio_offer(answer_sdp).expect("an SDP answer");
1264 assert!(answer.secure, "{answer_sdp}");
1265 let agent_crypto = CryptoAttribute::parse(&answer.crypto[0]).unwrap();
1266 assert_eq!(agent_crypto.tag, 1);
1267 assert_eq!(agent_crypto.suite, SrtpSuite::AesCm128HmacSha1_80);
1268 assert_ne!(
1269 agent_crypto.key, caller_key,
1270 "the agent sends under its own key"
1271 );
1272 let agent_rtp = format!("{}:{}", answer.host, answer.port);
1273
1274 let speak = |key: &MasterKey, first: u16| {
1275 let mut session = SrtpSession::new(SrtpSuite::AesCm128HmacSha1_80, key);
1276 (first..first + 15)
1277 .map(|seq| {
1278 session
1279 .protect(&rtp::build(&rtp::RtpPacket {
1280 payload_type: 0,
1281 marker: seq == first,
1282 sequence: seq,
1283 timestamp: u32::from(seq) * 160,
1284 ssrc: 0xCA11_E500,
1285 payload: vec![0x10; 160],
1286 }))
1287 .unwrap()
1288 })
1289 .collect::<Vec<_>>()
1290 };
1291 let heard = || {
1292 control
1293 .outbound_frames()
1294 .iter()
1295 .filter_map(|frame| serde_json::from_slice::<serde_json::Value>(frame).ok())
1296 .any(|message| message.pointer("/realtimeInput/audio").is_some())
1297 };
1298
1299 for packet in speak(&MasterKey::generate().unwrap(), 100) {
1301 media.send_to(&packet, &agent_rtp).await.unwrap();
1302 }
1303 tokio::time::sleep(Duration::from_millis(200)).await;
1304 assert!(
1305 !heard(),
1306 "packets that fail authentication never reach the model"
1307 );
1308
1309 for packet in speak(&caller_key, 200) {
1311 media.send_to(&packet, &agent_rtp).await.unwrap();
1312 tokio::time::sleep(Duration::from_millis(5)).await;
1313 }
1314 tokio::time::sleep(Duration::from_millis(200)).await;
1315 assert!(heard(), "decrypted caller audio reached the model");
1316
1317 control.release();
1320 let mut from_agent = SrtpSession::new(agent_crypto.suite, &agent_crypto.key);
1321 let mut buf = [0u8; 2048];
1322 let (len, _) = tokio::time::timeout(Duration::from_secs(3), media.recv_from(&mut buf))
1323 .await
1324 .expect("the agent sends media")
1325 .unwrap();
1326 let plain = from_agent
1327 .unprotect(&buf[..len])
1328 .expect("authenticates under the answered key");
1329 let packet = rtp::parse(&plain).unwrap();
1330 assert_eq!(packet.payload_type, 0);
1331 assert!(packet.payload.iter().any(|b| *b != 0xFF), "not silence");
1332 }
1333
1334 #[test]
1335 fn an_account_never_prints_its_password() {
1336 let password = test_password();
1337 let account = SipAccount::new("sip:pbx.example.com", "alice", &password);
1338 assert!(!format!("{account:?}").contains(&password));
1339 }
1340
1341 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1342 async fn answers_options_and_rings_then_rejects_an_invite() {
1343 let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1344 .await
1345 .expect("bind agent");
1346 let agent_port = agent.sip_port();
1347 let (call_tx, call_rx) = tokio::sync::oneshot::channel();
1350 tokio::spawn(async move {
1351 if let Some(incoming) = agent.next_call().await {
1352 let _ = call_tx.send(incoming);
1353 }
1354 });
1355
1356 let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1357 let uac_port = uac.local_addr().unwrap().port();
1358 let target = format!("127.0.0.1:{agent_port}");
1359 let mut buf = [0u8; 2048];
1360
1361 let options = format!(
1363 "OPTIONS sip:gemini@{target} SIP/2.0\r\n\
1364 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKopt1\r\n\
1365 Max-Forwards: 70\r\n\
1366 From: <sip:probe@127.0.0.1>;tag=opt\r\n\
1367 To: <sip:gemini@{target}>\r\n\
1368 Call-ID: options-1@127.0.0.1\r\n\
1369 CSeq: 1 OPTIONS\r\n\
1370 Content-Length: 0\r\n\r\n"
1371 );
1372 uac.send_to(options.as_bytes(), &target).await.unwrap();
1373 let status = recv_status(&uac, &mut buf).await.expect("OPTIONS response");
1374 assert!(
1375 status.contains("200"),
1376 "expected 200 to OPTIONS, got {status}"
1377 );
1378
1379 let sdp_body = "v=0\r\n\
1382 o=probe 1 1 IN IP4 127.0.0.1\r\n\
1383 s=call\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\n\
1384 m=audio 40000 RTP/AVP 0\r\n\
1385 a=rtpmap:0 PCMU/8000\r\n";
1386 let invite = format!(
1387 "INVITE sip:gemini@{target} SIP/2.0\r\n\
1388 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKinv1\r\n\
1389 Max-Forwards: 70\r\n\
1390 From: <sip:probe@127.0.0.1>;tag=inv\r\n\
1391 To: <sip:gemini@{target}>\r\n\
1392 Call-ID: invite-1@127.0.0.1\r\n\
1393 CSeq: 1 INVITE\r\n\
1394 Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
1395 Content-Type: application/sdp\r\n\
1396 Content-Length: {}\r\n\r\n{sdp_body}",
1397 sdp_body.len()
1398 );
1399 uac.send_to(invite.as_bytes(), &target).await.unwrap();
1400
1401 let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
1402 .await
1403 .expect("call surfaces")
1404 .expect("agent still running");
1405 assert_eq!(incoming.offer.port, 40_000);
1406 assert_eq!(
1407 incoming.offer.g711_payload_type(),
1408 Some(super::super::rtp::PT_PCMU)
1409 );
1410 assert!(incoming.from.contains("probe"), "from: {}", incoming.from);
1411 incoming.reject();
1412
1413 let mut saw_final = false;
1415 for _ in 0..6 {
1416 match recv_status(&uac, &mut buf).await {
1417 Some(status) => {
1418 let code: u32 = status
1419 .split_whitespace()
1420 .nth(1)
1421 .and_then(|c| c.parse().ok())
1422 .unwrap_or(0);
1423 if code >= 400 {
1424 saw_final = true;
1425 break;
1426 }
1427 }
1428 None => break,
1429 }
1430 }
1431 assert!(
1432 saw_final,
1433 "expected a final failure response to the rejected INVITE"
1434 );
1435 }
1436}