1use std::net::{IpAddr, SocketAddr};
37use std::sync::Arc;
38use std::time::Duration;
39
40use tokio::net::UdpSocket;
41use tokio::sync::{mpsc, watch};
42use tokio::task::JoinHandle;
43use tokio_util::sync::CancellationToken;
44
45use rsipstack::EndpointBuilder;
46use rsipstack::dialog::dialog::DialogState;
47use rsipstack::dialog::dialog_layer::DialogLayer;
48use rsipstack::dialog::invite_dialog::InviteDialog;
49use rsipstack::transport::TransportLayer;
50use rsipstack::transport::udp::UdpConnection;
51
52use gemini_adk_rs::State;
53use gemini_adk_rs::live::LiveHandle;
54
55use super::bridge::{self, DtmfDeduper, FillerConfig};
56use super::g711;
57use super::rtp::{self, PT_PCMA, RtpSender, SAMPLES_PER_PACKET};
58use super::sdp::{self, AudioOffer};
59use crate::voice::{Playback, VoicePump, pump};
60
61#[derive(Debug)]
63pub enum SipError {
64 Io(std::io::Error),
66 Sip(rsipstack::Error),
68 NoAudioOffer,
70 NoCommonCodec,
72}
73
74impl std::fmt::Display for SipError {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match self {
77 Self::Io(e) => write!(f, "sip io error: {e}"),
78 Self::Sip(e) => write!(f, "sip stack error: {e:?}"),
79 Self::NoAudioOffer => write!(f, "INVITE carried no answerable audio offer"),
80 Self::NoCommonCodec => write!(f, "no common G.711 codec with the caller"),
81 }
82 }
83}
84
85impl std::error::Error for SipError {}
86
87impl From<std::io::Error> for SipError {
88 fn from(e: std::io::Error) -> Self {
89 Self::Io(e)
90 }
91}
92
93impl From<rsipstack::Error> for SipError {
94 fn from(e: rsipstack::Error) -> Self {
95 Self::Sip(e)
96 }
97}
98
99pub struct SipAgent {
103 dialog_layer: Arc<DialogLayer>,
104 incoming: rsipstack::transaction::TransactionReceiver,
105 cancel: CancellationToken,
106 local_ip: IpAddr,
107 sip_port: u16,
108}
109
110impl SipAgent {
111 pub async fn bind(addr: SocketAddr) -> Result<SipAgent, SipError> {
114 let cancel = CancellationToken::new();
115 let transport_layer = TransportLayer::new(cancel.child_token());
116 let udp = UdpConnection::create_connection(addr, None, Some(cancel.child_token()))
117 .await
118 .map_err(SipError::Sip)?;
119 let sip_port = udp
120 .get_addr()
121 .addr
122 .port
123 .as_ref()
124 .map(|p| u16::from(*p))
125 .unwrap_or(addr.port());
126 transport_layer.add_transport(udp.into());
127
128 let endpoint = EndpointBuilder::new()
129 .with_user_agent("gemini-rs")
130 .with_cancel_token(cancel.child_token())
131 .with_transport_layer(transport_layer)
132 .build();
133 endpoint
134 .inner
135 .transport_layer
136 .serve_listens()
137 .await
138 .map_err(SipError::Sip)?;
139 let inner = endpoint.inner.clone();
140 tokio::spawn(async move {
141 let _ = inner.serve().await;
142 });
143
144 let incoming = endpoint.incoming_transactions().map_err(SipError::Sip)?;
145 let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone()));
146
147 Ok(SipAgent {
148 dialog_layer,
149 incoming,
150 cancel,
151 local_ip: addr.ip(),
152 sip_port,
153 })
154 }
155
156 pub async fn next_call(&mut self) -> Option<IncomingCall> {
160 while let Some(mut tx) = self.incoming.recv().await {
161 use rsipstack::rsip::{Method, StatusCode};
162 match tx.original.method {
163 Method::Invite => {
164 let offer = match sdp::parse_audio_offer(
165 String::from_utf8_lossy(&tx.original.body).as_ref(),
166 ) {
167 Some(offer) => offer,
168 None => {
169 let _ = tx.reply(StatusCode::NotAcceptableHere).await;
170 continue;
171 }
172 };
173 let (state_tx, state_rx) = mpsc::unbounded_channel();
174 let contact = format!(
175 "sip:gemini@{}:{};transport=udp",
176 advertised_ip(self.local_ip, &offer),
177 self.sip_port
178 );
179 let contact = match rsipstack::rsip::Uri::try_from(contact.as_str()) {
180 Ok(uri) => uri,
181 Err(_) => {
182 let _ = tx.reply(StatusCode::ServerInternalError).await;
183 continue;
184 }
185 };
186 let dialog = match self.dialog_layer.get_or_create_server_invite(
187 &tx,
188 state_tx,
189 None,
190 Some(contact),
191 ) {
192 Ok(dialog) => dialog,
193 Err(err) => {
194 tracing::warn!("rejecting INVITE: {err:?}");
195 let _ = tx.reply(StatusCode::ServerInternalError).await;
196 continue;
197 }
198 };
199 use rsipstack::rsip::HeadersExt as _;
200 let from = tx
201 .original
202 .from_header()
203 .map(std::string::ToString::to_string)
204 .unwrap_or_default();
205 let _ = dialog.ringing(None, None);
206 tokio::spawn(async move { while tx.receive().await.is_some() {} });
210 return Some(IncomingCall {
211 dialog,
212 state_rx,
213 offer,
214 from,
215 local_ip: self.local_ip,
216 dialog_layer: self.dialog_layer.clone(),
217 filler: None,
218 });
219 }
220 Method::Ack | Method::Bye | Method::Cancel | Method::Info | Method::Update => {
221 match self.dialog_layer.match_dialog(&tx) {
223 Some(mut dialog) => {
224 tokio::spawn(async move {
225 let _ = dialog.handle(&mut tx).await;
226 });
227 }
228 None => {
229 let _ = tx.reply(StatusCode::CallTransactionDoesNotExist).await;
230 }
231 }
232 }
233 Method::Options => {
234 let _ = tx.reply(StatusCode::OK).await;
235 }
236 _ => {
237 let _ = tx.reply(StatusCode::MethodNotAllowed).await;
238 }
239 }
240 }
241 None
242 }
243
244 pub fn sip_port(&self) -> u16 {
246 self.sip_port
247 }
248
249 pub fn shutdown(&self) {
251 self.cancel.cancel();
252 }
253}
254
255pub struct IncomingCall {
259 dialog: InviteDialog,
260 state_rx: mpsc::UnboundedReceiver<DialogState>,
261 pub offer: AudioOffer,
263 pub from: String,
265 local_ip: IpAddr,
266 dialog_layer: Arc<DialogLayer>,
267 filler: Option<FillerConfig>,
268}
269
270impl IncomingCall {
271 pub fn filler(mut self, config: FillerConfig) -> Self {
276 self.filler = Some(config);
277 self
278 }
279
280 pub async fn answer(self, handle: &LiveHandle) -> Result<SipCall, SipError> {
288 let payload_type = self.offer.g711_payload_type().ok_or_else(|| {
289 let _ = self.dialog.reject(None, None);
290 SipError::NoCommonCodec
291 })?;
292
293 let media_ip = advertised_ip(self.local_ip, &self.offer);
294 let rtp_socket = UdpSocket::bind((self.local_ip, 0)).await?;
295 let rtp_port = rtp_socket.local_addr()?.port();
296 let remote: SocketAddr = format!("{}:{}", self.offer.host, self.offer.port)
297 .parse()
298 .map_err(|_| SipError::NoAudioOffer)?;
299
300 let telephone_event_pt = self.offer.telephone_event_pt;
301 let answer = sdp::audio_answer(
302 seed() as u64,
303 &media_ip.to_string(),
304 rtp_port,
305 payload_type,
306 telephone_event_pt,
307 );
308 self.dialog
309 .accept(None, Some(answer.into_bytes()))
310 .map_err(SipError::Sip)?;
311 let _ = handle.state().set(bridge::KEY_CALLER, self.from.clone());
312
313 let cancel = CancellationToken::new();
314 let media = rtp_media(
315 handle,
316 Arc::new(rtp_socket),
317 remote,
318 payload_type,
319 telephone_event_pt,
320 self.filler,
321 cancel.clone(),
322 );
323
324 let mut state_rx = self.state_rx;
326 let media_cancel = cancel.clone();
327 let dialog_id = self.dialog.id();
328 let dialog_layer = self.dialog_layer;
329 let ended = tokio::spawn(async move {
330 while let Some(state) = state_rx.recv().await {
331 if let DialogState::Terminated(_, _) = state {
332 break;
333 }
334 }
335 media_cancel.cancel();
336 dialog_layer.remove_dialog(&dialog_id);
337 });
338
339 Ok(SipCall {
340 dialog: self.dialog,
341 media,
342 cancel,
343 ended,
344 })
345 }
346
347 pub fn reject(self) {
349 let _ = self.dialog.reject(None, None);
350 }
351}
352
353pub struct SipCall {
357 dialog: InviteDialog,
358 media: MediaTasks,
359 cancel: CancellationToken,
360 ended: JoinHandle<()>,
361}
362
363impl SipCall {
364 pub async fn ended(self) {
366 let _ = self.ended.await;
367 self.media.stop().await;
368 }
369
370 pub async fn hangup(self) {
372 let _ = self.dialog.bye().await;
373 self.cancel.cancel();
374 let _ = self.ended.await;
375 self.media.stop().await;
376 }
377}
378
379struct MediaTasks {
382 pump: VoicePump,
383 inbound: JoinHandle<()>,
384 outbound: JoinHandle<()>,
385 filler: Option<JoinHandle<()>>,
386}
387
388impl MediaTasks {
389 async fn stop(self) {
390 self.inbound.abort();
391 self.outbound.abort();
392 if let Some(filler) = self.filler {
393 filler.abort();
394 }
395 self.pump.abort();
396 self.pump.join().await;
397 }
398}
399
400fn rtp_media(
405 handle: &LiveHandle,
406 socket: Arc<UdpSocket>,
407 remote: SocketAddr,
408 payload_type: u8,
409 telephone_event_pt: Option<u8>,
410 filler: Option<FillerConfig>,
411 cancel: CancellationToken,
412) -> MediaTasks {
413 let (mic_tx, mic_rx) = mpsc::channel::<Vec<i16>>(64);
414 let (speaker_tx, speaker_rx) = mpsc::channel::<Playback>(64);
415 let voice_pump = pump(
416 handle,
417 mic_rx,
418 super::TWILIO_HZ,
419 speaker_tx.clone(),
420 super::TWILIO_HZ,
421 );
422 let (peer_tx, peer_rx) = watch::channel(remote);
423
424 let filler = filler.map(|config| bridge::spawn_latency_filler(handle, speaker_tx, config));
425
426 let inbound = tokio::spawn(inbound_loop(
427 socket.clone(),
428 payload_type,
429 telephone_event_pt,
430 handle.state().clone(),
431 mic_tx,
432 peer_tx,
433 cancel.clone(),
434 ));
435 let outbound = tokio::spawn(outbound_loop(
436 socket,
437 payload_type,
438 speaker_rx,
439 peer_rx,
440 cancel,
441 ));
442
443 MediaTasks {
444 pump: voice_pump,
445 inbound,
446 outbound,
447 filler,
448 }
449}
450
451async fn inbound_loop(
452 socket: Arc<UdpSocket>,
453 payload_type: u8,
454 telephone_event_pt: Option<u8>,
455 state: State,
456 mic_tx: mpsc::Sender<Vec<i16>>,
457 peer_tx: watch::Sender<SocketAddr>,
458 cancel: CancellationToken,
459) {
460 let mut buf = [0u8; 2048];
461 let mut latched = false;
462 let mut dtmf = DtmfDeduper::default();
463 loop {
464 let (len, source) = tokio::select! {
465 _ = cancel.cancelled() => break,
466 received = socket.recv_from(&mut buf) => match received {
467 Ok(pair) => pair,
468 Err(_) => break,
469 },
470 };
471 let Some(packet) = rtp::parse(&buf[..len]) else {
472 continue; };
474 if telephone_event_pt == Some(packet.payload_type) {
475 if let Some(event) = rtp::parse_telephone_event(&packet.payload)
477 && dtmf.accept(event.end, packet.timestamp)
478 && let Some(digit) = event.digit()
479 {
480 bridge::record_dtmf(&state, digit);
481 }
482 continue;
483 }
484 if packet.payload_type != payload_type {
485 continue; }
487 if !latched {
488 let _ = peer_tx.send(source);
489 latched = true;
490 }
491 let samples = if payload_type == PT_PCMA {
492 g711::decode_alaw(&packet.payload)
493 } else {
494 g711::decode_ulaw(&packet.payload)
495 };
496 if mic_tx.send(samples).await.is_err() {
497 break;
498 }
499 }
500}
501
502async fn outbound_loop(
503 socket: Arc<UdpSocket>,
504 payload_type: u8,
505 mut speaker_rx: mpsc::Receiver<Playback>,
506 peer_rx: watch::Receiver<SocketAddr>,
507 cancel: CancellationToken,
508) {
509 let silence_byte: u8 = if payload_type == PT_PCMA { 0xD5 } else { 0xFF };
510 let seed = seed();
511 let mut sender = RtpSender::new(payload_type, seed, (seed >> 16) as u16, seed.rotate_left(8));
512 let mut pending: std::collections::VecDeque<i16> = std::collections::VecDeque::new();
513 let mut ticker = tokio::time::interval(Duration::from_millis(20));
514 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
515
516 loop {
517 tokio::select! {
518 _ = cancel.cancelled() => break,
519 playback = speaker_rx.recv() => match playback {
520 Some(Playback::Chunk(samples)) => pending.extend(samples),
521 Some(Playback::Flush) => pending.clear(),
523 None => break,
524 },
525 _ = ticker.tick() => {
526 if pending.is_empty() {
527 sender.skip_silence(SAMPLES_PER_PACKET as u32);
528 continue;
529 }
530 let take = pending.len().min(SAMPLES_PER_PACKET);
531 let mut payload = Vec::with_capacity(SAMPLES_PER_PACKET);
532 for sample in pending.drain(..take) {
533 payload.push(if payload_type == PT_PCMA {
534 g711::linear_to_alaw(sample)
535 } else {
536 g711::linear_to_ulaw(sample)
537 });
538 }
539 payload.resize(SAMPLES_PER_PACKET, silence_byte);
541 let datagram = sender.packetize(&payload, SAMPLES_PER_PACKET as u32);
542 let target = *peer_rx.borrow();
543 if socket.send_to(&datagram, target).await.is_err() {
544 break;
545 }
546 }
547 }
548 }
549}
550
551fn advertised_ip(local: IpAddr, offer: &AudioOffer) -> IpAddr {
554 if !local.is_unspecified() {
555 return local;
556 }
557 let probe = std::net::UdpSocket::bind("0.0.0.0:0")
558 .and_then(|s| {
559 s.connect((offer.host.as_str(), offer.port))?;
560 s.local_addr()
561 })
562 .map(|a| a.ip());
563 probe.unwrap_or(local)
564}
565
566fn seed() -> u32 {
568 let nanos = std::time::SystemTime::now()
569 .duration_since(std::time::UNIX_EPOCH)
570 .map(|d| d.subsec_nanos())
571 .unwrap_or(0);
572 nanos ^ (std::process::id().rotate_left(16))
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578
579 async fn recv_status(socket: &UdpSocket, buf: &mut [u8]) -> Option<String> {
580 let deadline = Duration::from_secs(3);
581 let (len, _) = tokio::time::timeout(deadline, socket.recv_from(buf))
582 .await
583 .ok()?
584 .ok()?;
585 String::from_utf8_lossy(&buf[..len])
586 .lines()
587 .next()
588 .map(str::to_string)
589 }
590
591 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
592 async fn answers_options_and_rings_then_rejects_an_invite() {
593 let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
594 .await
595 .expect("bind agent");
596 let agent_port = agent.sip_port();
597 let (call_tx, call_rx) = tokio::sync::oneshot::channel();
600 tokio::spawn(async move {
601 if let Some(incoming) = agent.next_call().await {
602 let _ = call_tx.send(incoming);
603 }
604 });
605
606 let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
607 let uac_port = uac.local_addr().unwrap().port();
608 let target = format!("127.0.0.1:{agent_port}");
609 let mut buf = [0u8; 2048];
610
611 let options = format!(
613 "OPTIONS sip:gemini@{target} SIP/2.0\r\n\
614 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKopt1\r\n\
615 Max-Forwards: 70\r\n\
616 From: <sip:probe@127.0.0.1>;tag=opt\r\n\
617 To: <sip:gemini@{target}>\r\n\
618 Call-ID: options-1@127.0.0.1\r\n\
619 CSeq: 1 OPTIONS\r\n\
620 Content-Length: 0\r\n\r\n"
621 );
622 uac.send_to(options.as_bytes(), &target).await.unwrap();
623 let status = recv_status(&uac, &mut buf).await.expect("OPTIONS response");
624 assert!(
625 status.contains("200"),
626 "expected 200 to OPTIONS, got {status}"
627 );
628
629 let sdp_body = "v=0\r\n\
632 o=probe 1 1 IN IP4 127.0.0.1\r\n\
633 s=call\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\n\
634 m=audio 40000 RTP/AVP 0\r\n\
635 a=rtpmap:0 PCMU/8000\r\n";
636 let invite = format!(
637 "INVITE sip:gemini@{target} SIP/2.0\r\n\
638 Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKinv1\r\n\
639 Max-Forwards: 70\r\n\
640 From: <sip:probe@127.0.0.1>;tag=inv\r\n\
641 To: <sip:gemini@{target}>\r\n\
642 Call-ID: invite-1@127.0.0.1\r\n\
643 CSeq: 1 INVITE\r\n\
644 Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
645 Content-Type: application/sdp\r\n\
646 Content-Length: {}\r\n\r\n{sdp_body}",
647 sdp_body.len()
648 );
649 uac.send_to(invite.as_bytes(), &target).await.unwrap();
650
651 let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
652 .await
653 .expect("call surfaces")
654 .expect("agent still running");
655 assert_eq!(incoming.offer.port, 40_000);
656 assert_eq!(
657 incoming.offer.g711_payload_type(),
658 Some(super::super::rtp::PT_PCMU)
659 );
660 assert!(incoming.from.contains("probe"), "from: {}", incoming.from);
661 incoming.reject();
662
663 let mut saw_final = false;
665 for _ in 0..6 {
666 match recv_status(&uac, &mut buf).await {
667 Some(status) => {
668 let code: u32 = status
669 .split_whitespace()
670 .nth(1)
671 .and_then(|c| c.parse().ok())
672 .unwrap_or(0);
673 if code >= 400 {
674 saw_final = true;
675 break;
676 }
677 }
678 None => break,
679 }
680 }
681 assert!(
682 saw_final,
683 "expected a final failure response to the rejected INVITE"
684 );
685 }
686}