1use std::net::{IpAddr, Ipv4Addr, SocketAddr};
37use std::sync::Arc;
38use std::time::Duration;
39
40use rsipstack::dialog::authenticate::Credential;
41use rsipstack::dialog::dialog::{DialogState, DialogStateReceiver, DialogStateSender};
42use rsipstack::dialog::dialog_layer::DialogLayer;
43use rsipstack::dialog::invitation::InviteOption;
44use rsipstack::dialog::server_dialog::ServerInviteDialog;
45use rsipstack::sip as rsip;
46use rsipstack::sip::prelude::HeadersExt;
47use rsipstack::transaction::endpoint::EndpointInnerRef;
48use rsipstack::transaction::TransactionReceiver;
49use rsipstack::transport::{udp::UdpConnection, TransportLayer};
50use rsipstack::EndpointBuilder;
51use tokio::net::UdpSocket;
52use tokio::sync::{mpsc, Mutex};
53use tokio_util::sync::CancellationToken;
54
55use crate::error::FlowcatError;
56use crate::sip::sdp;
57use crate::sip::transport::SipTransport;
58
59const DEFAULT_SIP_PORT: u16 = 5060;
61pub const DEFAULT_RTP_PORT_BASE: u16 = 16000;
65pub const DEFAULT_RTP_PORT_TRIES: u16 = 200;
72const MIN_REREGISTER_SECS: u64 = 30;
74
75#[derive(Debug, Clone)]
77pub struct SipConfig {
78 pub server: String,
80 pub login: String,
82 pub password: String,
84 pub caller_id: String,
86 pub public_ip: Option<Ipv4Addr>,
89 pub sip_port: Option<u16>,
91 pub rtp_port_base: u16,
94 pub rtp_port_tries: u16,
98}
99
100pub struct InboundInvite {
107 pub call_id: String,
109 pub from: String,
111 pub to_did: String,
113 offer: sdp::SdpMedia,
115 dialog: ServerInviteDialog,
117 advertise_ip: Ipv4Addr,
119 rtp_port_base: u16,
122 rtp_port_tries: u16,
123 state_rx: DialogStateReceiver,
125}
126
127impl InboundInvite {
128 pub async fn answer(self) -> Result<SipTransport, FlowcatError> {
135 let codec = self.offer.codec;
136 let (rtp_sock, rtp_port) = bind_rtp_socket(self.rtp_port_base, self.rtp_port_tries).await?;
137 let answer_sdp = sdp::build_answer(self.advertise_ip, rtp_port, codec);
138
139 let peer = SocketAddr::new(IpAddr::V4(self.offer.ip), self.offer.port);
141
142 let headers = vec![rsip::Header::ContentType("application/sdp".into())];
144 self.dialog
145 .accept(Some(headers), Some(answer_sdp.into_bytes()))
146 .map_err(|e| FlowcatError::Transport(format!("SIP accept failed: {e}")))?;
147
148 let hangup = CancellationToken::new();
149 spawn_dialog_watch(self.state_rx, hangup.clone());
150
151 Ok(SipTransport::start(
152 rtp_sock,
153 peer,
154 codec,
155 self.call_id,
156 hangup,
157 ))
158 }
159
160 pub fn reject(self, code: Option<rsip::StatusCode>) {
162 let _ = self
163 .dialog
164 .reject(Some(code.unwrap_or(rsip::StatusCode::BusyHere)), None);
165 }
166}
167
168pub struct SipAgent {
170 cfg: SipConfig,
171 endpoint_inner: EndpointInnerRef,
172 dialog_layer: Arc<DialogLayer>,
173 inbound_rx: Mutex<mpsc::Receiver<InboundInvite>>,
175 advertise_ip: Ipv4Addr,
177 cancel: CancellationToken,
180}
181
182impl SipAgent {
183 pub async fn start(cfg: SipConfig) -> Result<Self, FlowcatError> {
190 let cancel = CancellationToken::new();
191 let sip_port = cfg.sip_port.unwrap_or(DEFAULT_SIP_PORT);
192
193 let local: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), sip_port);
197 let external = cfg
198 .public_ip
199 .map(|ip| SocketAddr::new(IpAddr::V4(ip), sip_port));
200
201 let transport_layer = TransportLayer::new(cancel.clone());
202 let conn = UdpConnection::create_connection(local, external, Some(cancel.child_token()))
203 .await
204 .map_err(|e| FlowcatError::Transport(format!("bind SIP UDP {local}: {e}")))?;
205 transport_layer.add_transport(conn.into());
206
207 let endpoint = EndpointBuilder::new()
208 .with_user_agent("flowcat")
209 .with_cancel_token(cancel.clone())
210 .with_transport_layer(transport_layer)
211 .build();
212
213 let endpoint_inner = endpoint.inner.clone();
214 let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone()));
215
216 let advertise_ip = match cfg.public_ip {
218 Some(ip) => ip,
219 None => local_advertise_ip(&endpoint),
220 };
221
222 let incoming = endpoint
223 .incoming_transactions()
224 .map_err(|e| FlowcatError::Transport(format!("incoming_transactions: {e}")))?;
225
226 let serve_cancel = cancel.clone();
228 tokio::spawn(async move {
229 tokio::select! {
230 _ = endpoint.serve() => {}
231 _ = serve_cancel.cancelled() => {}
232 }
233 });
234
235 let (inbound_tx, inbound_rx) = mpsc::channel::<InboundInvite>(16);
237 tokio::spawn(incoming_pump(
238 dialog_layer.clone(),
239 incoming,
240 inbound_tx,
241 advertise_ip,
242 cfg.rtp_port_base,
243 cfg.rtp_port_tries,
244 cancel.clone(),
245 ));
246
247 if !cfg.password.is_empty() {
249 tokio::spawn(register_loop(
250 endpoint_inner.clone(),
251 cfg.clone(),
252 cancel.clone(),
253 ));
254 }
255
256 Ok(Self {
257 cfg,
258 endpoint_inner,
259 dialog_layer,
260 inbound_rx: Mutex::new(inbound_rx),
261 advertise_ip,
262 cancel,
263 })
264 }
265
266 pub async fn register(&self) -> Result<u32, FlowcatError> {
270 let credential = Credential {
271 username: self.cfg.login.clone(),
272 password: self.cfg.password.clone(),
273 realm: None,
274 };
275 let server = parse_server_uri(&self.cfg.server)?;
276 let mut reg = rsipstack::dialog::registration::Registration::new(
277 self.endpoint_inner.clone(),
278 Some(credential),
279 );
280 let resp = reg
281 .register(server, None)
282 .await
283 .map_err(|e| FlowcatError::Transport(format!("REGISTER failed: {e}")))?;
284 if resp.status_code != rsip::StatusCode::OK {
285 return Err(FlowcatError::Transport(format!(
286 "REGISTER rejected: {}",
287 resp.status_code
288 )));
289 }
290 Ok(reg.expires())
291 }
292
293 pub async fn next_inbound(&self) -> Option<InboundInvite> {
295 self.inbound_rx.lock().await.recv().await
296 }
297
298 pub async fn originate(
302 &self,
303 to_e164: &str,
304 caller_id: Option<&str>,
305 ) -> Result<SipTransport, FlowcatError> {
306 let server = parse_server_uri(&self.cfg.server)?;
307 let host = server.host_with_port.clone();
308 let caller_user = caller_id.unwrap_or(&self.cfg.caller_id);
309
310 let caller = make_uri(caller_user, host.clone());
312 let callee = make_uri(to_e164, host.clone());
313 let contact = make_uri(caller_user, host.clone());
314
315 let (rtp_sock, rtp_port) =
317 bind_rtp_socket(self.cfg.rtp_port_base, self.cfg.rtp_port_tries).await?;
318 let offer = sdp::build_offer(self.advertise_ip, rtp_port);
319
320 let credential = Credential {
321 username: self.cfg.login.clone(),
322 password: self.cfg.password.clone(),
323 realm: None,
324 };
325 let invite = InviteOption {
326 caller,
327 callee,
328 contact,
329 content_type: Some("application/sdp".to_string()),
330 offer: Some(offer.into_bytes()),
331 credential: Some(credential),
332 ..Default::default()
333 };
334
335 let (state_tx, state_rx) = self.dialog_layer.new_dialog_state_channel();
336 let (dialog, resp) = self
337 .dialog_layer
338 .do_invite(invite, state_tx)
339 .await
340 .map_err(|e| FlowcatError::Transport(format!("INVITE failed: {e}")))?;
341 let resp =
342 resp.ok_or_else(|| FlowcatError::Transport("INVITE got no final response".into()))?;
343 if resp.status_code != rsip::StatusCode::OK {
344 return Err(FlowcatError::Transport(format!(
345 "outbound call not answered: {}",
346 resp.status_code
347 )));
348 }
349
350 let answer_body = String::from_utf8_lossy(resp.body());
352 let media = sdp::parse(&answer_body)
353 .map_err(|e| FlowcatError::Transport(format!("bad answer SDP: {e}")))?;
354 let peer = SocketAddr::new(IpAddr::V4(media.ip), media.port);
355
356 let call_id = dialog.id().call_id.to_string();
357 let hangup = CancellationToken::new();
358 spawn_dialog_watch(state_rx, hangup.clone());
359
360 Ok(SipTransport::start(
361 rtp_sock,
362 peer,
363 media.codec,
364 call_id,
365 hangup,
366 ))
367 }
368
369 pub fn shutdown(&self) {
371 self.cancel.cancel();
372 }
373}
374
375impl Drop for SipAgent {
376 fn drop(&mut self) {
377 self.cancel.cancel();
378 }
379}
380
381fn spawn_dialog_watch(mut state_rx: DialogStateReceiver, hangup: CancellationToken) {
384 tokio::spawn(async move {
385 while let Some(state) = state_rx.recv().await {
386 if let DialogState::Terminated(id, reason) = state {
387 tracing::debug!(%id, ?reason, "SIP dialog terminated");
388 hangup.cancel();
389 break;
390 }
391 }
392 hangup.cancel();
394 });
395}
396
397async fn incoming_pump(
402 dialog_layer: Arc<DialogLayer>,
403 mut incoming: TransactionReceiver,
404 inbound_tx: mpsc::Sender<InboundInvite>,
405 advertise_ip: Ipv4Addr,
406 rtp_port_base: u16,
407 rtp_port_tries: u16,
408 cancel: CancellationToken,
409) {
410 loop {
411 let mut tx = tokio::select! {
412 _ = cancel.cancelled() => break,
413 t = incoming.recv() => match t {
414 Some(t) => t,
415 None => break,
416 },
417 };
418
419 let has_to_tag = tx
421 .original
422 .to_header()
423 .ok()
424 .and_then(|to| to.tag().ok().flatten())
425 .is_some();
426 if has_to_tag {
427 if let Some(mut d) = dialog_layer.match_dialog(&tx) {
428 tokio::spawn(async move {
429 let _ = d.handle(&mut tx).await;
430 });
431 } else {
432 let _ = tx
433 .reply(rsip::StatusCode::CallTransactionDoesNotExist)
434 .await;
435 }
436 continue;
437 }
438
439 match tx.original.method {
442 rsip::Method::Invite => {
443 if let Err(e) = handle_new_invite(
444 &dialog_layer,
445 tx,
446 &inbound_tx,
447 advertise_ip,
448 rtp_port_base,
449 rtp_port_tries,
450 )
451 .await
452 {
453 tracing::debug!(error = %e, "failed to set up inbound INVITE");
454 }
455 }
456 rsip::Method::Ack => { }
457 _ => {
458 let _ = tx.reply(rsip::StatusCode::OK).await;
459 }
460 }
461 }
462}
463
464async fn handle_new_invite(
468 dialog_layer: &Arc<DialogLayer>,
469 tx: rsipstack::transaction::transaction::Transaction,
470 inbound_tx: &mpsc::Sender<InboundInvite>,
471 advertise_ip: Ipv4Addr,
472 rtp_port_base: u16,
473 rtp_port_tries: u16,
474) -> Result<(), FlowcatError> {
475 let mut tx = tx;
476 let from = uri_user(tx.original.from_header().ok().and_then(|h| h.uri().ok()));
479 let to_did = uri_user(tx.original.to_header().ok().and_then(|h| h.uri().ok()));
480 let call_id = tx
481 .original
482 .call_id_header()
483 .map(|c| c.to_string())
484 .unwrap_or_default();
485
486 let offer_body = String::from_utf8_lossy(tx.original.body());
488 let offer = match sdp::parse(&offer_body) {
489 Ok(m) => m,
490 Err(e) => {
491 tracing::debug!(error = %e, "rejecting INVITE with unusable SDP offer");
492 let _ = tx.reply(rsip::StatusCode::NotAcceptableHere).await;
493 return Err(FlowcatError::Transport(format!("bad offer SDP: {e}")));
494 }
495 };
496
497 let (state_tx, state_rx): (DialogStateSender, DialogStateReceiver) =
499 dialog_layer.new_dialog_state_channel();
500 let dialog = dialog_layer
501 .get_or_create_server_invite(&tx, state_tx, None, None)
502 .map_err(|e| FlowcatError::Transport(format!("get_or_create_server_invite: {e}")))?;
503
504 let mut dialog_for_handle = dialog.clone();
507 tokio::spawn(async move {
508 let _ = dialog_for_handle.handle(&mut tx).await;
509 });
510
511 let invite = InboundInvite {
512 call_id,
513 from,
514 to_did,
515 offer,
516 dialog,
517 advertise_ip,
518 rtp_port_base,
519 rtp_port_tries,
520 state_rx,
521 };
522 inbound_tx
523 .send(invite)
524 .await
525 .map_err(|_| FlowcatError::Transport("inbound channel closed".into()))
526}
527
528async fn register_loop(
530 endpoint_inner: EndpointInnerRef,
531 cfg: SipConfig,
532 cancel: CancellationToken,
533) {
534 let credential = Credential {
535 username: cfg.login.clone(),
536 password: cfg.password.clone(),
537 realm: None,
538 };
539 let server = match parse_server_uri(&cfg.server) {
540 Ok(u) => u,
541 Err(e) => {
542 tracing::error!(error = %e, "SIP register loop: bad server URI; not registering");
543 return;
544 }
545 };
546 let mut reg =
547 rsipstack::dialog::registration::Registration::new(endpoint_inner, Some(credential));
548 loop {
549 match reg.register(server.clone(), None).await {
550 Ok(resp) if resp.status_code == rsip::StatusCode::OK => {
551 let expires = reg.expires();
552 tracing::info!(
553 expires = (expires as u64).max(MIN_REREGISTER_SECS),
554 "SIP registered"
555 );
556 tokio::select! {
557 _ = cancel.cancelled() => return,
558 _ = tokio::time::sleep(Duration::from_secs(reregister_delay_secs(expires))) => {}
559 }
560 }
561 Ok(resp) => {
562 tracing::warn!(status = %resp.status_code, "SIP register rejected; retrying");
563 tokio::select! {
564 _ = cancel.cancelled() => return,
565 _ = tokio::time::sleep(Duration::from_secs(MIN_REREGISTER_SECS)) => {}
566 }
567 }
568 Err(e) => {
569 tracing::warn!(error = %e, "SIP register error; retrying");
570 tokio::select! {
571 _ = cancel.cancelled() => return,
572 _ = tokio::time::sleep(Duration::from_secs(MIN_REREGISTER_SECS)) => {}
573 }
574 }
575 }
576 }
577}
578
579fn parse_server_uri(server: &str) -> Result<rsip::Uri, FlowcatError> {
585 let s = if server.starts_with("sip:") || server.starts_with("sips:") {
586 server.to_string()
587 } else {
588 format!("sip:{server}")
589 };
590 rsip::Uri::try_from(s.as_str())
591 .map_err(|e| FlowcatError::Transport(format!("bad SIP server URI {server:?}: {e}")))
592}
593
594fn make_uri(user: &str, host_with_port: rsip::HostWithPort) -> rsip::Uri {
596 rsip::Uri {
597 scheme: Some(rsip::Scheme::Sip),
598 auth: Some(rsip::Auth {
599 user: user.to_string(),
600 password: None,
601 }),
602 host_with_port,
603 params: vec![],
604 headers: vec![],
605 }
606}
607
608fn uri_user(uri: Option<rsip::Uri>) -> String {
610 uri.and_then(|u| u.auth.map(|a| a.user)).unwrap_or_default()
611}
612
613fn reregister_delay_secs(expires: u32) -> u64 {
617 (expires as u64).max(MIN_REREGISTER_SECS) * 3 / 4
618}
619
620fn local_advertise_ip(endpoint: &rsipstack::transaction::endpoint::Endpoint) -> Ipv4Addr {
623 endpoint
624 .get_addrs()
625 .first()
626 .and_then(|a| SocketAddr::try_from(a.addr.clone()).ok())
627 .and_then(|sa| match sa.ip() {
628 IpAddr::V4(v4) => Some(v4),
629 IpAddr::V6(_) => None,
630 })
631 .unwrap_or(Ipv4Addr::LOCALHOST)
632}
633
634async fn bind_rtp_socket(base: u16, tries: u16) -> Result<(UdpSocket, u16), FlowcatError> {
637 let base = base & !1;
641 for i in 0..tries {
642 let Some(port) = i.checked_mul(2).and_then(|off| base.checked_add(off)) else {
645 break;
646 };
647 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port);
648 if let Ok(sock) = UdpSocket::bind(addr).await {
649 return Ok((sock, port));
650 }
651 }
652 Err(FlowcatError::Transport(format!(
653 "no free RTP port in {}..{}",
654 base,
655 base.saturating_add(tries.saturating_mul(2))
656 )))
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662
663 #[test]
664 fn parse_server_uri_adds_scheme() {
665 let u = parse_server_uri("sip.zadarma.com").unwrap();
666 assert_eq!(u.scheme, Some(rsip::Scheme::Sip));
667 assert!(u.to_string().contains("sip.zadarma.com"));
669 }
670
671 #[test]
672 fn parse_server_uri_keeps_explicit_scheme() {
673 let u = parse_server_uri("sip:1.2.3.4:5070").unwrap();
674 assert!(u.to_string().contains("1.2.3.4"));
675 }
676
677 #[test]
678 fn make_uri_sets_user_and_scheme() {
679 let host = rsip::HostWithPort::try_from("sip.example.com:5060").unwrap();
680 let u = make_uri("+15551234567", host);
681 assert_eq!(u.auth.as_ref().unwrap().user, "+15551234567");
682 assert_eq!(u.scheme, Some(rsip::Scheme::Sip));
683 }
684
685 #[test]
686 fn uri_user_extracts_number_or_empty() {
687 let host = rsip::HostWithPort::try_from("h:5060").unwrap();
688 let u = make_uri("18005551212", host);
689 assert_eq!(uri_user(Some(u)), "18005551212");
690 assert_eq!(uri_user(None), "");
691 }
692
693 fn z_host() -> rsip::HostWithPort {
695 rsip::HostWithPort::try_from("pbx.zadarma.com:5060").unwrap()
696 }
697
698 #[test]
699 fn uri_user_preserves_zadarma_did_verbatim() {
700 assert_eq!(
703 uri_user(Some(make_uri("12025550100", z_host()))),
704 "12025550100"
705 );
706 }
707
708 #[test]
709 fn uri_user_keeps_leading_plus() {
710 assert_eq!(
713 uri_user(Some(make_uri("+12025550100", z_host()))),
714 "+12025550100"
715 );
716 }
717
718 #[test]
719 fn uri_user_extension_and_junk_pass_through_for_fail_closed_404() {
720 assert_eq!(uri_user(Some(make_uri("100", z_host()))), "100");
723 assert_eq!(
724 uri_user(Some(make_uri("nmap-probe", z_host()))),
725 "nmap-probe"
726 );
727 }
728
729 #[test]
730 fn uri_user_bare_host_uri_yields_empty() {
731 let uri = rsip::Uri::try_from("sip:pbx.zadarma.com").unwrap();
733 assert_eq!(uri_user(Some(uri)), "");
734 }
735
736 #[test]
738 fn reregister_delay_uses_three_quarters_of_expiry() {
739 assert_eq!(reregister_delay_secs(50), 37);
741 }
742
743 #[test]
744 fn reregister_delay_floors_short_expiry() {
745 assert_eq!(reregister_delay_secs(0), 30 * 3 / 4);
746 assert_eq!(reregister_delay_secs(10), 30 * 3 / 4);
747 }
748
749 #[test]
750 fn reregister_delay_scales_long_expiry_without_overflow() {
751 assert_eq!(reregister_delay_secs(3600), 2700);
752 assert_eq!(reregister_delay_secs(u32::MAX), (u32::MAX as u64) * 3 / 4);
753 }
754
755 #[tokio::test]
757 async fn bind_rtp_socket_returns_distinct_ports() {
758 let (s1, p1) = bind_rtp_socket(DEFAULT_RTP_PORT_BASE, DEFAULT_RTP_PORT_TRIES)
759 .await
760 .unwrap();
761 let (s2, p2) = bind_rtp_socket(DEFAULT_RTP_PORT_BASE, DEFAULT_RTP_PORT_TRIES)
762 .await
763 .unwrap();
764 assert_ne!(p1, p2);
765 assert_eq!(p1 % 2, 0);
767 assert_eq!(p2 % 2, 0);
768 drop((s1, s2));
769 }
770
771 #[tokio::test]
774 async fn bind_rtp_socket_honors_custom_range() {
775 let base = 31000u16;
776 let tries = 4u16;
777 let (s, p) = bind_rtp_socket(base, tries).await.unwrap();
778 assert!(p >= base && p < base + tries * 2, "port {p} out of range");
779 assert_eq!(p % 2, 0);
780 drop(s);
781 }
782
783 #[tokio::test]
786 async fn bind_rtp_socket_yields_even_port_even_for_odd_base() {
787 let (s, p) = bind_rtp_socket(31001, 8).await.unwrap(); assert_eq!(
789 p % 2,
790 0,
791 "RTP port {p} is odd despite odd base (RFC 3550 wants even)"
792 );
793 drop(s);
794 }
795}