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(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(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::Sip)?;
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 endpoint
147 .inner
148 .transport_layer
149 .serve_listens()
150 .await
151 .map_err(SipError::Sip)?;
152 let inner = endpoint.inner.clone();
153 tokio::spawn(async move {
154 let _ = inner.serve().await;
155 });
156
157 let incoming = endpoint.incoming_transactions().map_err(SipError::Sip)?;
158 let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone()));
159
160 Ok(SipAgent {
161 dialog_layer,
162 incoming,
163 cancel,
164 local_ip: addr.ip(),
165 sip_port,
166 })
167 }
168
169 pub async fn next_call(&mut self) -> Option<IncomingCall> {
173 while let Some(mut tx) = self.incoming.recv().await {
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 None
255 }
256
257 pub fn sip_port(&self) -> u16 {
259 self.sip_port
260 }
261
262 pub fn shutdown(&self) {
264 self.cancel.cancel();
265 }
266
267 pub async fn register(&self, account: SipAccount) -> Result<SipRegistration, SipError> {
286 use rsipstack::dialog::authenticate::Credential;
287 use rsipstack::dialog::registration::Registration;
288
289 let registrar = rsipstack::rsip::Uri::try_from(account.registrar.as_str())
290 .map_err(|_| SipError::InvalidUri(account.registrar.clone()))?;
291 let mut registration = Registration::new(
292 self.dialog_layer.endpoint.clone(),
293 Some(Credential {
294 username: account.username.clone(),
295 password: account.password.clone(),
296 realm: account.realm.clone(),
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::Sip)?;
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 let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
966 let addr = socket.local_addr().unwrap();
967 let (seen_tx, seen_rx) = mpsc::unbounded_channel();
968 tokio::spawn(async move {
969 let mut buf = [0u8; 4096];
970 while let Ok((len, from)) = socket.recv_from(&mut buf).await {
971 let request = String::from_utf8_lossy(&buf[..len]).to_string();
972 let echoed: Vec<&str> = request
973 .lines()
974 .filter(|l| {
975 let l = l.to_ascii_lowercase();
976 ["via:", "from:", "call-id:", "cseq:"]
977 .iter()
978 .any(|h| l.starts_with(h))
979 })
980 .collect();
981 let to = request
982 .lines()
983 .find(|l| l.to_ascii_lowercase().starts_with("to:"))
984 .unwrap_or("To: <sip:unknown@invalid>");
985 let authorized = request.to_ascii_lowercase().contains("\nauthorization:");
986 let (status, extra) = if authorized {
987 let contact = request
988 .lines()
989 .find(|l| l.to_ascii_lowercase().starts_with("contact:"))
990 .unwrap_or("Contact: <sip:alice@127.0.0.1>");
991 ("200 OK", format!("{contact};expires=60\r\n"))
992 } else {
993 (
994 "401 Unauthorized",
995 "WWW-Authenticate: Digest realm=\"test\", nonce=\"n1\", algorithm=MD5\r\n"
996 .to_string(),
997 )
998 };
999 let response = format!(
1000 "SIP/2.0 {status}\r\n{}\r\n{to};tag=reg\r\n{extra}Content-Length: 0\r\n\r\n",
1001 echoed.join("\r\n")
1002 );
1003 let _ = socket.send_to(response.as_bytes(), from).await;
1004 let _ = seen_tx.send(request);
1005 }
1006 });
1007 (addr, seen_rx)
1008 }
1009
1010 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1011 async fn registers_with_digest_auth_and_unregisters() {
1012 let password = test_password();
1013 let (registrar, mut seen) = fake_registrar().await;
1014 let agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1015 .await
1016 .expect("bind agent");
1017
1018 let registration = tokio::time::timeout(
1019 Duration::from_secs(5),
1020 agent.register(
1021 SipAccount::new(format!("sip:{registrar}"), "alice", &password)
1022 .expires(Duration::from_secs(300)),
1023 ),
1024 )
1025 .await
1026 .expect("registration finishes")
1027 .expect("registrar accepts");
1028 assert_eq!(
1029 registration.state(),
1030 RegistrationState::Registered {
1031 expires: Duration::from_secs(60)
1032 },
1033 "the lifetime the registrar granted, not the one asked for"
1034 );
1035
1036 let first = seen.recv().await.unwrap();
1037 assert!(first.starts_with("REGISTER "), "{first}");
1038 assert!(!first.to_ascii_lowercase().contains("authorization:"));
1039 let answered = loop {
1040 let request = seen.recv().await.unwrap();
1041 if request.to_ascii_lowercase().contains("authorization:") {
1042 break request;
1043 }
1044 };
1045 for part in [
1046 "username=\"alice\"",
1047 "realm=\"test\"",
1048 "nonce=\"n1\"",
1049 "response=",
1050 ] {
1051 assert!(answered.contains(part), "missing {part} in {answered}");
1052 }
1053 assert!(
1054 !answered.contains(&password),
1055 "the password never goes on the wire"
1056 );
1057
1058 tokio::time::timeout(Duration::from_secs(5), registration.unregister())
1059 .await
1060 .expect("unregister finishes")
1061 .expect("registrar accepts the removal");
1062 let mut removed = false;
1063 while let Ok(request) = seen.try_recv() {
1064 removed |= request
1065 .lines()
1066 .any(|l| l.eq_ignore_ascii_case("expires: 0"));
1067 }
1068 assert!(
1069 removed,
1070 "a REGISTER with a zero lifetime removes the binding"
1071 );
1072 agent.shutdown();
1073 }
1074
1075 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1076 async fn a_refused_registration_fails_at_once() {
1077 let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1079 let registrar = socket.local_addr().unwrap();
1080 tokio::spawn(async move {
1081 let mut buf = [0u8; 4096];
1082 while let Ok((len, from)) = socket.recv_from(&mut buf).await {
1083 let request = String::from_utf8_lossy(&buf[..len]).to_string();
1084 let echoed: Vec<&str> = request
1085 .lines()
1086 .filter(|l| {
1087 let l = l.to_ascii_lowercase();
1088 ["via:", "from:", "to:", "call-id:", "cseq:"]
1089 .iter()
1090 .any(|h| l.starts_with(h))
1091 })
1092 .collect();
1093 let response = format!(
1094 "SIP/2.0 403 Forbidden\r\n{}\r\nContent-Length: 0\r\n\r\n",
1095 echoed.join("\r\n")
1096 );
1097 let _ = socket.send_to(response.as_bytes(), from).await;
1098 }
1099 });
1100 let agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1101 .await
1102 .unwrap();
1103 let result = tokio::time::timeout(
1104 Duration::from_secs(5),
1105 agent.register(SipAccount::new(
1106 format!("sip:{registrar}"),
1107 "mallory",
1108 test_password(),
1109 )),
1110 )
1111 .await
1112 .expect("finishes");
1113 assert!(
1114 matches!(result, Err(SipError::RegistrationRejected(403))),
1115 "{:?}",
1116 result.err()
1117 );
1118 agent.shutdown();
1119 }
1120
1121 async fn recv_response(socket: &UdpSocket, code: &str) -> String {
1123 let mut buf = [0u8; 4096];
1124 loop {
1125 let (len, _) = tokio::time::timeout(Duration::from_secs(3), socket.recv_from(&mut buf))
1126 .await
1127 .expect("a response")
1128 .unwrap();
1129 let text = String::from_utf8_lossy(&buf[..len]).to_string();
1130 if text.lines().next().is_some_and(|l| l.contains(code)) {
1131 return text;
1132 }
1133 }
1134 }
1135
1136 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1137 async fn an_srtp_offer_gets_encrypted_media_both_ways() {
1138 use super::super::srtp::SrtpSuite;
1139 use base64::Engine as _;
1140
1141 let tone: Vec<u8> = (0..4800i16)
1143 .flat_map(|i| (if i % 48 < 24 { 6000i16 } else { -6000 }).to_le_bytes())
1144 .collect();
1145 let (transport, control) = crate::live::scripted::ScriptedServer::new()
1146 .frame(serde_json::json!({
1147 "serverContent": { "modelTurn": { "parts": [{ "inlineData": {
1148 "mimeType": "audio/pcm;rate=24000",
1149 "data": base64::engine::general_purpose::STANDARD.encode(&tone),
1150 } }] } }
1151 }))
1152 .into_transport();
1153 let handle = crate::live::Live::builder()
1154 .connect_with_transport(transport)
1155 .await
1156 .unwrap();
1157
1158 let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1159 .await
1160 .unwrap();
1161 let target = format!("127.0.0.1:{}", agent.sip_port());
1162 let (call_tx, call_rx) = tokio::sync::oneshot::channel();
1163 tokio::spawn(async move {
1164 if let Some(incoming) = agent.next_call().await {
1165 let _ = call_tx.send(incoming);
1166 }
1167 std::future::pending::<()>().await;
1169 });
1170
1171 let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1173 let uac_port = uac.local_addr().unwrap().port();
1174 let media = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1175 let media_port = media.local_addr().unwrap().port();
1176 let caller_key = MasterKey::generate().unwrap();
1177 let sdp_body = format!(
1178 "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\
1179 m=audio {media_port} RTP/SAVP 0\r\na=rtpmap:0 PCMU/8000\r\n\
1180 a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:{}\r\n",
1181 caller_key.to_inline()
1182 );
1183 let invite = format!(
1184 "INVITE sip:gemini@{target} SIP/2.0\r\n\
1185 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKsrtp1\r\n\
1186 Max-Forwards: 70\r\n\
1187 From: <sip:probe@127.0.0.1>;tag=srtp\r\n\
1188 To: <sip:gemini@{target}>\r\n\
1189 Call-ID: srtp-1@127.0.0.1\r\n\
1190 CSeq: 1 INVITE\r\n\
1191 Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
1192 Content-Type: application/sdp\r\n\
1193 Content-Length: {}\r\n\r\n{sdp_body}",
1194 sdp_body.len()
1195 );
1196 uac.send_to(invite.as_bytes(), &target).await.unwrap();
1197 let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
1198 .await
1199 .unwrap()
1200 .unwrap();
1201 assert!(incoming.offer.secure);
1202 let _call = incoming.answer(&handle).await.expect("answers with SRTP");
1203
1204 let ok = recv_response(&uac, "200").await;
1205 let answer_sdp = ok.split("\r\n\r\n").nth(1).unwrap();
1206 let answer = sdp::parse_audio_offer(answer_sdp).expect("an SDP answer");
1207 assert!(answer.secure, "{answer_sdp}");
1208 let agent_crypto = CryptoAttribute::parse(&answer.crypto[0]).unwrap();
1209 assert_eq!(agent_crypto.tag, 1);
1210 assert_eq!(agent_crypto.suite, SrtpSuite::AesCm128HmacSha1_80);
1211 assert_ne!(
1212 agent_crypto.key, caller_key,
1213 "the agent sends under its own key"
1214 );
1215 let agent_rtp = format!("{}:{}", answer.host, answer.port);
1216
1217 let speak = |key: &MasterKey, first: u16| {
1218 let mut session = SrtpSession::new(SrtpSuite::AesCm128HmacSha1_80, key);
1219 (first..first + 15)
1220 .map(|seq| {
1221 session
1222 .protect(&rtp::build(&rtp::RtpPacket {
1223 payload_type: 0,
1224 marker: seq == first,
1225 sequence: seq,
1226 timestamp: u32::from(seq) * 160,
1227 ssrc: 0xCA11_E500,
1228 payload: vec![0x10; 160],
1229 }))
1230 .unwrap()
1231 })
1232 .collect::<Vec<_>>()
1233 };
1234 let heard = || {
1235 control
1236 .outbound_frames()
1237 .iter()
1238 .filter_map(|frame| serde_json::from_slice::<serde_json::Value>(frame).ok())
1239 .any(|message| message.pointer("/realtimeInput/audio").is_some())
1240 };
1241
1242 for packet in speak(&MasterKey::generate().unwrap(), 100) {
1244 media.send_to(&packet, &agent_rtp).await.unwrap();
1245 }
1246 tokio::time::sleep(Duration::from_millis(200)).await;
1247 assert!(
1248 !heard(),
1249 "packets that fail authentication never reach the model"
1250 );
1251
1252 for packet in speak(&caller_key, 200) {
1254 media.send_to(&packet, &agent_rtp).await.unwrap();
1255 tokio::time::sleep(Duration::from_millis(5)).await;
1256 }
1257 tokio::time::sleep(Duration::from_millis(200)).await;
1258 assert!(heard(), "decrypted caller audio reached the model");
1259
1260 control.release();
1263 let mut from_agent = SrtpSession::new(agent_crypto.suite, &agent_crypto.key);
1264 let mut buf = [0u8; 2048];
1265 let (len, _) = tokio::time::timeout(Duration::from_secs(3), media.recv_from(&mut buf))
1266 .await
1267 .expect("the agent sends media")
1268 .unwrap();
1269 let plain = from_agent
1270 .unprotect(&buf[..len])
1271 .expect("authenticates under the answered key");
1272 let packet = rtp::parse(&plain).unwrap();
1273 assert_eq!(packet.payload_type, 0);
1274 assert!(packet.payload.iter().any(|b| *b != 0xFF), "not silence");
1275 }
1276
1277 #[test]
1278 fn an_account_never_prints_its_password() {
1279 let password = test_password();
1280 let account = SipAccount::new("sip:pbx.example.com", "alice", &password);
1281 assert!(!format!("{account:?}").contains(&password));
1282 }
1283
1284 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1285 async fn answers_options_and_rings_then_rejects_an_invite() {
1286 let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1287 .await
1288 .expect("bind agent");
1289 let agent_port = agent.sip_port();
1290 let (call_tx, call_rx) = tokio::sync::oneshot::channel();
1293 tokio::spawn(async move {
1294 if let Some(incoming) = agent.next_call().await {
1295 let _ = call_tx.send(incoming);
1296 }
1297 });
1298
1299 let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1300 let uac_port = uac.local_addr().unwrap().port();
1301 let target = format!("127.0.0.1:{agent_port}");
1302 let mut buf = [0u8; 2048];
1303
1304 let options = format!(
1306 "OPTIONS sip:gemini@{target} SIP/2.0\r\n\
1307 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKopt1\r\n\
1308 Max-Forwards: 70\r\n\
1309 From: <sip:probe@127.0.0.1>;tag=opt\r\n\
1310 To: <sip:gemini@{target}>\r\n\
1311 Call-ID: options-1@127.0.0.1\r\n\
1312 CSeq: 1 OPTIONS\r\n\
1313 Content-Length: 0\r\n\r\n"
1314 );
1315 uac.send_to(options.as_bytes(), &target).await.unwrap();
1316 let status = recv_status(&uac, &mut buf).await.expect("OPTIONS response");
1317 assert!(
1318 status.contains("200"),
1319 "expected 200 to OPTIONS, got {status}"
1320 );
1321
1322 let sdp_body = "v=0\r\n\
1325 o=probe 1 1 IN IP4 127.0.0.1\r\n\
1326 s=call\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\n\
1327 m=audio 40000 RTP/AVP 0\r\n\
1328 a=rtpmap:0 PCMU/8000\r\n";
1329 let invite = format!(
1330 "INVITE sip:gemini@{target} SIP/2.0\r\n\
1331 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKinv1\r\n\
1332 Max-Forwards: 70\r\n\
1333 From: <sip:probe@127.0.0.1>;tag=inv\r\n\
1334 To: <sip:gemini@{target}>\r\n\
1335 Call-ID: invite-1@127.0.0.1\r\n\
1336 CSeq: 1 INVITE\r\n\
1337 Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
1338 Content-Type: application/sdp\r\n\
1339 Content-Length: {}\r\n\r\n{sdp_body}",
1340 sdp_body.len()
1341 );
1342 uac.send_to(invite.as_bytes(), &target).await.unwrap();
1343
1344 let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
1345 .await
1346 .expect("call surfaces")
1347 .expect("agent still running");
1348 assert_eq!(incoming.offer.port, 40_000);
1349 assert_eq!(
1350 incoming.offer.g711_payload_type(),
1351 Some(super::super::rtp::PT_PCMU)
1352 );
1353 assert!(incoming.from.contains("probe"), "from: {}", incoming.from);
1354 incoming.reject();
1355
1356 let mut saw_final = false;
1358 for _ in 0..6 {
1359 match recv_status(&uac, &mut buf).await {
1360 Some(status) => {
1361 let code: u32 = status
1362 .split_whitespace()
1363 .nth(1)
1364 .and_then(|c| c.parse().ok())
1365 .unwrap_or(0);
1366 if code >= 400 {
1367 saw_final = true;
1368 break;
1369 }
1370 }
1371 None => break,
1372 }
1373 }
1374 assert!(
1375 saw_final,
1376 "expected a final failure response to the rejected INVITE"
1377 );
1378 }
1379}