├── .dockerignore ├── assets ├── example.pcma ├── example.pcmu ├── ringback.pcma └── ringback.pcmu ├── .gitignore ├── src ├── transport │ ├── tests │ │ ├── mod.rs │ │ ├── test_listener_api.rs │ │ ├── test_sipaddr.rs │ │ ├── test_udp.rs │ │ ├── test_via_received.rs │ │ └── test_stream_encoding.rs │ ├── mod.rs │ ├── channel.rs │ ├── tcp_listener.rs │ ├── tcp.rs │ ├── sip_addr.rs │ ├── udp.rs │ └── stream.rs ├── dialog │ ├── tests │ │ ├── mod.rs │ │ ├── test_prack.rs │ │ ├── test_server_dialog.rs │ │ └── test_authenticate.rs │ ├── mod.rs │ ├── dialog_layer.rs │ └── authenticate.rs ├── error.rs ├── transaction │ ├── tests │ │ ├── mod.rs │ │ ├── test_endpoint.rs │ │ ├── test_server.rs │ │ ├── test_transaction_states.rs │ │ └── test_client.rs │ ├── key.rs │ ├── timer.rs │ ├── message.rs │ └── mod.rs ├── lib.rs └── rsip_ext.rs ├── docs └── README.md ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── Dockerfile.sipproxy ├── Cargo.toml ├── examples └── client │ ├── stun.rs │ └── play_file.rs └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /assets/example.pcma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/restsend/rsipstack/HEAD/assets/example.pcma -------------------------------------------------------------------------------- /assets/example.pcmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/restsend/rsipstack/HEAD/assets/example.pcmu -------------------------------------------------------------------------------- /assets/ringback.pcma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/restsend/rsipstack/HEAD/assets/ringback.pcma -------------------------------------------------------------------------------- /assets/ringback.pcmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/restsend/rsipstack/HEAD/assets/ringback.pcmu -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /pkg 4 | .env 5 | .env.* 6 | *~ 7 | prompt.txt 8 | /test/web/.next 9 | /test/web/node_modules -------------------------------------------------------------------------------- /src/transport/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod test_listener_api; 2 | pub mod test_sipaddr; 3 | pub mod test_stream_encoding; 4 | pub mod test_udp; 5 | pub mod test_via_received; 6 | -------------------------------------------------------------------------------- /src/dialog/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod test_authenticate; 2 | mod test_client_dialog; 3 | mod test_dialog_layer; 4 | mod test_dialog_states; 5 | mod test_prack; 6 | mod test_server_dialog; 7 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # rsipstack - Rust SIP Stack 2 | 3 | ## Timer vs async task 4 | 5 | rsipstack is designed for proxy scenarios, aiming to reduce unnecessary task creation while improving concurrent performance. -------------------------------------------------------------------------------- /src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod channel; 2 | pub mod connection; 3 | pub mod sip_addr; 4 | pub mod stream; 5 | pub mod tcp; 6 | pub mod tcp_listener; 7 | pub mod tls; 8 | pub mod transport_layer; 9 | pub mod udp; 10 | pub mod websocket; 11 | pub use connection::SipConnection; 12 | pub use connection::TransportEvent; 13 | pub use sip_addr::SipAddr; 14 | pub use tcp_listener::TcpListenerConnection; 15 | pub use tls::{TlsConfig, TlsListenerConnection}; 16 | pub use transport_layer::TransportLayer; 17 | pub use websocket::WebSocketListenerConnection; 18 | 19 | #[cfg(test)] 20 | pub mod tests; 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Build 19 | run: cargo build --verbose 20 | - name: Run tests 21 | run: cargo test --verbose 22 | 23 | lint: 24 | name: Rustfmt / Clippy 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | 31 | - name: Fmt 32 | run: cargo fmt --all -- --check 33 | 34 | cargo-shear: 35 | name: 'cargo shear' 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - run: rustup show 40 | - name: Install cargo-binstall 41 | uses: cargo-bins/cargo-binstall@main 42 | - name: Install cargo-shear 43 | run: cargo binstall --no-confirm cargo-shear 44 | - run: cargo shear 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Restsend 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Dockerfile.sipproxy: -------------------------------------------------------------------------------- 1 | FROM rust:bookworm AS rust-builder 2 | RUN mkdir /build 3 | ADD . /build/ 4 | WORKDIR /build 5 | RUN mkdir -p .cargo && echo '[source.crates-io]\nreplace-with = "rsproxy-sparse"\n[source.rsproxy-sparse]\nregistry = "sparse+https://rsproxy.cn/index/"' > .cargo/config.toml 6 | RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | RUN --mount=type=cache,target=/build/.cargo/registry \ 8 | --mount=type=cache,target=/build/rsipstack/target/release/incremental\ 9 | --mount=type=cache,target=/build/rsipstack/target/release/build\ 10 | cargo build --release --example proxy 11 | 12 | FROM debian:bookworm 13 | LABEL maintainer="shenjindi@fourz.cn" 14 | RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 15 | RUN --mount=type=cache,target=/var/apt apt-get update && apt-get install -y ca-certificates tzdata 16 | ENV DEBIAN_FRONTEND=noninteractive 17 | ENV LANG=C.UTF-8 18 | 19 | RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 20 | 21 | WORKDIR /app 22 | COPY --from=rust-builder /build/target/release/examples/proxy /app/proxy 23 | 24 | EXPOSE 25060/UDP 25 | ENTRYPOINT ["/app/proxy"] -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{dialog::DialogId, transaction::key::TransactionKey, transport::SipAddr}; 2 | use thiserror::Error as ThisError; 3 | 4 | #[derive(Debug, ThisError)] 5 | pub enum Error { 6 | #[error("SIP message error: {0}")] 7 | SipMessageError(#[from] rsip::Error), 8 | 9 | #[error("DNS resolution error: {0}")] 10 | DnsResolutionError(String), 11 | 12 | #[error("Transport layer error: {0}: {1}")] 13 | TransportLayerError(String, SipAddr), 14 | 15 | #[error("Transaction error: {0}: {1}")] 16 | TransactionError(String, TransactionKey), 17 | 18 | #[error("Endpoint error: {0}")] 19 | EndpointError(String), 20 | 21 | #[error("Dialog error:{2}({0})")] 22 | DialogError(String, DialogId, rsip::StatusCode), 23 | 24 | #[error("I/O error: {0}")] 25 | IoError(#[from] std::io::Error), 26 | 27 | #[error("Address parse error: {0}")] 28 | AddrParseError(#[from] std::net::AddrParseError), 29 | 30 | #[error("WebSocket error: {0}")] 31 | WebSocketError(#[from] tokio_tungstenite::tungstenite::Error), 32 | 33 | #[error("Error: {0}")] 34 | Error(String), 35 | } 36 | 37 | impl From> for Error { 38 | fn from(e: tokio::sync::mpsc::error::SendError) -> Self { 39 | Error::Error(e.to_string()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/transport/tests/test_listener_api.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{SipAddr, TcpListenerConnection, WebSocketListenerConnection}, 3 | Result, 4 | }; 5 | 6 | /// Test new TcpListenerConnection API 7 | #[tokio::test] 8 | async fn test_tcp_listener_connection_api() -> Result<()> { 9 | // Create TCP listener connection with a specific port to avoid conflicts 10 | let socket_addr: std::net::SocketAddr = "127.0.0.1:0".parse()?; 11 | let local_addr = SipAddr::new(rsip::transport::Transport::Tcp, socket_addr.into()); 12 | let tcp_listener = TcpListenerConnection::new(local_addr, None).await?; 13 | 14 | // Get the address (should be the same as input since we don't bind in new()) 15 | let bound_addr = tcp_listener.get_addr().clone(); 16 | 17 | println!( 18 | "Successfully created TCP listener using new API: {:?}", 19 | bound_addr 20 | ); 21 | 22 | // Test that we can get the address 23 | assert_eq!(bound_addr.r#type, Some(rsip::transport::Transport::Tcp)); 24 | assert_eq!(bound_addr.addr.host.to_string(), "127.0.0.1"); 25 | 26 | Ok(()) 27 | } 28 | 29 | /// Test new WebSocketListenerConnection API 30 | #[cfg(feature = "websocket")] 31 | #[tokio::test] 32 | async fn test_websocket_listener_connection_api() -> Result<()> { 33 | // Create WebSocket listener connection 34 | let socket_addr: std::net::SocketAddr = "127.0.0.1:0".parse()?; 35 | let local_addr = SipAddr::new(rsip::transport::Transport::Ws, socket_addr.into()); 36 | let ws_listener = WebSocketListenerConnection::new(local_addr, None, false).await?; 37 | 38 | // Get the address (should be the same as input since we don't bind in new()) 39 | let bound_addr = ws_listener.get_addr().clone(); 40 | 41 | println!( 42 | "Successfully created WebSocket listener using new API: {:?}", 43 | bound_addr 44 | ); 45 | 46 | // Test that we can get the address 47 | assert_eq!(bound_addr.r#type, Some(rsip::transport::Transport::Ws)); 48 | assert_eq!(bound_addr.addr.host.to_string(), "127.0.0.1"); 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsipstack" 3 | version = "0.2.99" 4 | edition = "2021" 5 | description = "SIP Stack Rust library for building SIP applications" 6 | license = "MIT" 7 | repository = "https://github.com/restsend/rsipstack" 8 | readme = "README.md" 9 | keywords = ["sip", "voip", "telephony", "sipstack"] 10 | authors = ["kui"] 11 | categories = ["network-programming", "multimedia"] 12 | 13 | 14 | [lib] 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | [[bin]] 18 | name = "bench_ua" 19 | path = "src/bin/bench_ua.rs" 20 | 21 | [dependencies] 22 | async-trait = "0.1.89" 23 | futures = "0.3.31" 24 | rsip = { version = "0.4.0" } 25 | thiserror = "2.0.17" 26 | tracing = "0.1.43" 27 | tokio-util = { version = "0.7.17", features = ["full"] } 28 | tracing-subscriber = { version = "0.3.20", features = ["local-time"] } 29 | rand = { version = "0.9.2" } 30 | get_if_addrs = "0.5.3" 31 | rsip-dns = { version = "0.1.4", features = ["trust-dns"], optional = true } 32 | bytes = "1.11.0" 33 | futures-util = "0.3.31" 34 | tokio-tungstenite = { version = "0.28.0", optional = true } 35 | tokio-rustls = { version = "0.26.4", optional = true } 36 | rustls-pemfile = { version = "2.2.0", optional = true } 37 | rustls = "0.23.35" 38 | clap = { version = "4.5.53", features = ["derive"] } 39 | nom = "8.0.0" 40 | 41 | [features] 42 | default = ["rustls", "websocket", "rsip-dns"] 43 | rustls = ["tokio-rustls", "rustls-pemfile"] 44 | websocket = ["tokio-tungstenite"] 45 | rsip-dns = ["dep:rsip-dns"] 46 | all-transports = ["rustls", "websocket"] 47 | 48 | [target.'cfg(target_arch = "wasm32")'.dependencies] 49 | tokio = { version = "1.47.1", features = ["time", "sync", "macros", "io-util"] } 50 | 51 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 52 | tokio = { version = "1.48.0", features = ["full"] } 53 | 54 | [dev-dependencies] 55 | dotenv = "0.15" 56 | sdp-rs = "0.2.1" 57 | rtp-rs = "0.6.0" 58 | stun-rs = "0.1.11" 59 | axum = { version = "0.8.7", features = ["ws"] } 60 | tower = "0.5.2" 61 | tower-http = { version = "0.6.7", features = ["fs", "cors"] } 62 | http = "1.4.0" 63 | 64 | [[example]] 65 | name = "client" 66 | path = "examples/client/main.rs" 67 | 68 | [[example]] 69 | name = "proxy" 70 | path = "examples/proxy.rs" 71 | -------------------------------------------------------------------------------- /src/transport/tests/test_sipaddr.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::{SipAddr, SipConnection}; 2 | use rsip::{headers::*, prelude::HeadersExt, HostWithPort, SipMessage}; 3 | 4 | #[test] 5 | fn test_via_received() { 6 | let register_req = rsip::message::Request { 7 | method: rsip::method::Method::Register, 8 | uri: rsip::Uri { 9 | scheme: Some(rsip::Scheme::Sip), 10 | host_with_port: rsip::HostWithPort::try_from("127.0.0.1:2025") 11 | .expect("host_port parse") 12 | .into(), 13 | ..Default::default() 14 | }, 15 | headers: vec![Via::new("SIP/2.0/TLS restsend.com:5061;branch=z9hG4bKnashd92").into()] 16 | .into(), 17 | version: rsip::Version::V2, 18 | body: Default::default(), 19 | }; 20 | 21 | let (_, parse_addr) = 22 | SipConnection::parse_target_from_via(®ister_req.via_header().expect("via_header")) 23 | .expect("get_target_socketaddr"); 24 | 25 | let addr = HostWithPort { 26 | host: "restsend.com".parse().unwrap(), 27 | port: Some(5061.into()), 28 | }; 29 | assert_eq!(parse_addr, addr); 30 | 31 | let addr = "127.0.0.1:1234".parse().unwrap(); 32 | let msg = SipConnection::update_msg_received( 33 | register_req.into(), 34 | addr, 35 | rsip::transport::Transport::Udp, 36 | ) 37 | .expect("update_msg_received"); 38 | 39 | match msg { 40 | SipMessage::Request(req) => { 41 | let (_, parse_addr) = 42 | SipConnection::parse_target_from_via(&req.via_header().expect("via_header")) 43 | .expect("get_target_socketaddr"); 44 | assert_eq!(parse_addr, addr.into()); 45 | } 46 | _ => {} 47 | } 48 | } 49 | 50 | #[test] 51 | fn test_sipaddr() { 52 | let addr = "sip:proxy1.example.org:25060;transport=tcp"; 53 | let uri = rsip::Uri::try_from(addr).expect("parse uri"); 54 | let sipaddr = SipAddr::try_from(&uri).expect("SipAddr::try_from"); 55 | assert_eq!(sipaddr.r#type, Some(rsip::transport::Transport::Tcp)); 56 | assert_eq!( 57 | sipaddr.addr, 58 | rsip::HostWithPort { 59 | host: "proxy1.example.org".parse().unwrap(), 60 | port: Some(25060.into()), 61 | } 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/transaction/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{endpoint::Endpoint, EndpointBuilder}; 2 | use crate::{ 3 | transport::{udp::UdpConnection, TransportLayer}, 4 | Result, 5 | }; 6 | use tokio_util::sync::CancellationToken; 7 | 8 | mod test_client; 9 | mod test_endpoint; 10 | mod test_server; 11 | mod test_transaction_states; 12 | 13 | pub(super) async fn create_test_endpoint(addr: Option<&str>) -> Result { 14 | let token = CancellationToken::new(); 15 | let tl = TransportLayer::new(token.child_token()); 16 | 17 | if let Some(addr) = addr { 18 | let peer = UdpConnection::create_connection(addr.parse()?, None, None).await?; 19 | tl.add_transport(peer.into()); 20 | } 21 | 22 | let endpoint = EndpointBuilder::new() 23 | .with_user_agent("rsipstack-test") 24 | .with_transport_layer(tl) 25 | .build(); 26 | Ok(endpoint) 27 | } 28 | #[cfg(test)] 29 | mod tests { 30 | use crate::{ 31 | rsip_ext::extract_uri_from_contact, 32 | transaction::{make_via_branch, random_text}, 33 | }; 34 | #[test] 35 | fn test_random_text() { 36 | let text = random_text(10); 37 | assert_eq!(text.len(), 10); 38 | let branch = make_via_branch(); 39 | let branch = branch.to_string(); 40 | assert_eq!(branch.len(), 27); // ;branch=z9hG4bK 41 | } 42 | 43 | #[test] 44 | fn test_linphone_contact() { 45 | let line = "sip:bob@localhost;transport=udp"; 46 | let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact"); 47 | assert_eq!(contact_uri.to_string(), "sip:bob@localhost"); 48 | 49 | let line = ";expires=3600;+org.linphone.specs=\"lime\""; 50 | let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact"); 51 | assert_eq!(contact_uri.to_string(), "sip:bob@localhost"); 52 | 53 | let line = ";message-expires=2419200;+sip.instance=\"\""; 54 | let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact"); 55 | assert_eq!(contact_uri.to_string(), "sip:bob@restsend.com"); 56 | let line = ";+org.linphone.specs=\"ephemeral/1.1,groupchat/1.1,lime\""; 57 | let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact"); 58 | assert_eq!( 59 | contact_uri.to_string(), 60 | "sip:linphone@domain.com;gr=urn:uuid:c8651907-f0b2-0027-bdbd-ce4ed05c9ef4" 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/transport/channel.rs: -------------------------------------------------------------------------------- 1 | use tokio_util::sync::CancellationToken; 2 | 3 | use super::{ 4 | connection::{TransportReceiver, TransportSender}, 5 | SipAddr, SipConnection, 6 | }; 7 | use crate::Result; 8 | use std::sync::{Arc, Mutex}; 9 | 10 | struct ChannelInner { 11 | incoming: Mutex>, 12 | outgoing: TransportSender, 13 | addr: SipAddr, 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct ChannelConnection { 18 | inner: Arc, 19 | cancel_token: Option, 20 | } 21 | 22 | impl ChannelConnection { 23 | pub async fn create_connection( 24 | incoming: TransportReceiver, 25 | outgoing: TransportSender, 26 | addr: SipAddr, 27 | cancel_token: Option, 28 | ) -> Result { 29 | let t = ChannelConnection { 30 | inner: Arc::new(ChannelInner { 31 | incoming: Mutex::new(Some(incoming)), 32 | outgoing, 33 | addr, 34 | }), 35 | cancel_token, 36 | }; 37 | Ok(t) 38 | } 39 | 40 | pub async fn send(&self, msg: rsip::SipMessage) -> crate::Result<()> { 41 | let transport = SipConnection::Channel(self.clone()); 42 | let source = self.get_addr().clone(); 43 | self.inner 44 | .outgoing 45 | .send(super::TransportEvent::Incoming(msg, transport, source)) 46 | .map_err(|e| e.into()) 47 | } 48 | 49 | pub fn get_addr(&self) -> &SipAddr { 50 | &self.inner.addr 51 | } 52 | 53 | pub async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 54 | let mut incoming = match self.inner.clone().incoming.lock().unwrap().take() { 55 | Some(incoming) => incoming, 56 | None => { 57 | return Err(crate::Error::Error( 58 | "ChannelTransport::serve_loop called twice".to_string(), 59 | )); 60 | } 61 | }; 62 | while let Some(event) = incoming.recv().await { 63 | sender.send(event)?; 64 | } 65 | Ok(()) 66 | } 67 | pub async fn close(&self) -> Result<()> { 68 | Ok(()) 69 | } 70 | pub fn cancel_token(&self) -> Option { 71 | self.cancel_token.clone() 72 | } 73 | } 74 | 75 | impl std::fmt::Display for ChannelConnection { 76 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 77 | write!(f, "*:*") 78 | } 79 | } 80 | 81 | impl std::fmt::Debug for ChannelConnection { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 | write!(f, "*:*") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/transport/tests/test_udp.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::{KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE}, 4 | udp::UdpConnection, 5 | TransportEvent, 6 | }, 7 | Result, 8 | }; 9 | use std::time::Duration; 10 | use tokio::{select, sync::mpsc::unbounded_channel, time::sleep}; 11 | 12 | #[tokio::test] 13 | async fn test_udp_keepalive() -> Result<()> { 14 | let peer_bob = UdpConnection::create_connection("127.0.0.1:0".parse()?, None, None).await?; 15 | let peer_alice = UdpConnection::create_connection("127.0.0.1:0".parse()?, None, None).await?; 16 | let (alice_tx, _) = unbounded_channel::(); 17 | 18 | let bob_loop = async { 19 | sleep(Duration::from_millis(20)).await; // wait for serve_loop to start 20 | // send keep alive 21 | peer_bob 22 | .send_raw(KEEPALIVE_REQUEST, peer_alice.get_addr()) 23 | .await 24 | .expect("send_raw"); 25 | // wait for keep alive response 26 | let buf = &mut [0u8; 2048]; 27 | let (n, _) = peer_bob.recv_raw(buf).await.expect("recv_raw"); 28 | assert_eq!(&buf[..n], KEEPALIVE_RESPONSE); 29 | }; 30 | 31 | select! { 32 | _ = peer_alice.serve_loop(alice_tx) => { 33 | assert!(false, "serve_loop exited"); 34 | } 35 | _ = bob_loop => {} 36 | _= sleep(Duration::from_millis(200)) => { 37 | assert!(false, "timeout waiting for keep alive response"); 38 | } 39 | }; 40 | Ok(()) 41 | } 42 | 43 | #[tokio::test] 44 | async fn test_udp_recv_sip_message() -> Result<()> { 45 | let peer_bob = UdpConnection::create_connection("127.0.0.1:0".parse()?, None, None).await?; 46 | let peer_alice = UdpConnection::create_connection("127.0.0.1:0".parse()?, None, None).await?; 47 | let (alice_tx, _) = unbounded_channel(); 48 | let (bob_tx, mut bob_rx) = unbounded_channel(); 49 | 50 | let send_loop = async { 51 | sleep(Duration::from_millis(20)).await; // wait for serve_loop to start 52 | let msg_1 = "REGISTER sip:bob@restsend.com SIP/2.0\r\nVia: SIP/2.0/UDP 127.0.0.1:5061;branch=z9hG4bKnashd92\r\nCSeq: 1 REGISTER\r\n\r\n"; 53 | peer_alice 54 | .send_raw(msg_1.as_bytes(), peer_bob.get_addr()) 55 | .await 56 | .expect("send_raw"); 57 | sleep(Duration::from_secs(3)).await; 58 | }; 59 | 60 | select! { 61 | _ = peer_alice.serve_loop(alice_tx) => { 62 | assert!(false, "alice serve_loop exited"); 63 | } 64 | _ = peer_bob.serve_loop(bob_tx) => { 65 | assert!(false, "bob serve_loop exited"); 66 | } 67 | _ = send_loop => { 68 | assert!(false, "send_loop exited"); 69 | } 70 | event = bob_rx.recv() => { 71 | match event { 72 | Some(TransportEvent::Incoming(msg, connection, from)) => { 73 | assert!(msg.is_request()); 74 | assert_eq!(from, peer_alice.get_addr().to_owned()); 75 | assert_eq!(connection.get_addr(), peer_bob.get_addr()); 76 | } 77 | _ => { 78 | assert!(false, "unexpected event"); 79 | } 80 | } 81 | } 82 | _= sleep(Duration::from_millis(500)) => { 83 | assert!(false, "timeout waiting"); 84 | } 85 | }; 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /src/transport/tcp_listener.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::tcp::TcpConnection; 2 | use crate::transport::transport_layer::TransportLayerInnerRef; 3 | use crate::transport::SipAddr; 4 | use crate::transport::SipConnection; 5 | use crate::Result; 6 | use std::fmt; 7 | use std::{net::SocketAddr, sync::Arc}; 8 | use tokio::net::TcpListener; 9 | use tracing::{info, warn}; 10 | pub struct TcpListenerConnectionInner { 11 | pub local_addr: SipAddr, 12 | pub external: Option, 13 | } 14 | 15 | #[derive(Clone)] 16 | pub struct TcpListenerConnection { 17 | pub inner: Arc, 18 | } 19 | 20 | impl TcpListenerConnection { 21 | pub async fn new(local_addr: SipAddr, external: Option) -> Result { 22 | let inner = TcpListenerConnectionInner { 23 | local_addr, 24 | external: external.map(|addr| SipAddr { 25 | r#type: Some(rsip::transport::Transport::Tcp), 26 | addr: addr.into(), 27 | }), 28 | }; 29 | Ok(TcpListenerConnection { 30 | inner: Arc::new(inner), 31 | }) 32 | } 33 | 34 | pub async fn serve_listener( 35 | &self, 36 | transport_layer_inner: TransportLayerInnerRef, 37 | ) -> Result<()> { 38 | let listener = TcpListener::bind(self.inner.local_addr.get_socketaddr()?).await?; 39 | tokio::spawn(async move { 40 | loop { 41 | let (stream, remote_addr) = match listener.accept().await { 42 | Ok((stream, remote_addr)) => (stream, remote_addr), 43 | Err(e) => { 44 | warn!("Failed to accept connection: {:?}", e); 45 | continue; 46 | } 47 | }; 48 | let local_addr = SipAddr { 49 | r#type: Some(rsip::transport::Transport::Tcp), 50 | addr: remote_addr.into(), 51 | }; 52 | let tcp_connection = match TcpConnection::from_stream( 53 | stream, 54 | local_addr.clone(), 55 | Some(transport_layer_inner.cancel_token.child_token()), 56 | ) { 57 | Ok(tcp_connection) => tcp_connection, 58 | Err(e) => { 59 | warn!("Failed to create TCP connection: {:?}", e); 60 | continue; 61 | } 62 | }; 63 | let sip_connection = SipConnection::Tcp(tcp_connection.clone()); 64 | transport_layer_inner.add_connection(sip_connection.clone()); 65 | info!(?local_addr, "new tcp connection"); 66 | } 67 | }); 68 | Ok(()) 69 | } 70 | } 71 | 72 | impl TcpListenerConnection { 73 | pub fn get_addr(&self) -> &SipAddr { 74 | if let Some(external) = &self.inner.external { 75 | external 76 | } else { 77 | &self.inner.local_addr 78 | } 79 | } 80 | 81 | pub async fn close(&self) -> Result<()> { 82 | Ok(()) 83 | } 84 | } 85 | 86 | impl fmt::Display for TcpListenerConnection { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | write!(f, "TCP Listener {}", self.get_addr()) 89 | } 90 | } 91 | 92 | impl fmt::Debug for TcpListenerConnection { 93 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 94 | fmt::Display::fmt(self, f) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/client/stun.rs: -------------------------------------------------------------------------------- 1 | use get_if_addrs::get_if_addrs; 2 | use rsipstack::transport::{udp::UdpConnection, SipAddr}; 3 | use rsipstack::{Error, Result}; 4 | use std::net::{IpAddr, SocketAddr}; 5 | use std::time::Duration; 6 | use stun_rs::{ 7 | attributes::stun::XorMappedAddress, methods::BINDING, MessageClass, MessageDecoderBuilder, 8 | MessageEncoderBuilder, StunMessageBuilder, 9 | }; 10 | use tokio::net::lookup_host; 11 | use tokio::select; 12 | use tokio::time::sleep; 13 | use tracing::info; 14 | 15 | pub fn get_first_non_loopback_interface() -> Result { 16 | for i in get_if_addrs()? { 17 | if !i.is_loopback() { 18 | match i.addr { 19 | get_if_addrs::IfAddr::V4(ref addr) => return Ok(std::net::IpAddr::V4(addr.ip)), 20 | _ => continue, 21 | } 22 | } 23 | } 24 | Err(Error::Error("No IPV4 interface found".to_string())) 25 | } 26 | 27 | pub async fn external_by_stun( 28 | conn: &mut UdpConnection, 29 | stun_server: &str, 30 | expires: Duration, 31 | ) -> Result { 32 | info!("getting external IP by STUN server: {}", stun_server); 33 | let msg = StunMessageBuilder::new(BINDING, MessageClass::Request).build(); 34 | 35 | let encoder = MessageEncoderBuilder::default().build(); 36 | let mut buffer: [u8; 150] = [0x00; 150]; 37 | encoder 38 | .encode(&mut buffer, &msg) 39 | .map_err(|e| crate::Error::Error(e.to_string()))?; 40 | 41 | let mut addrs = lookup_host(stun_server).await?; 42 | let target = addrs 43 | .next() 44 | .ok_or_else(|| crate::Error::Error("STUN server address not found".to_string()))?; 45 | 46 | conn.send_raw( 47 | &buffer, 48 | &SipAddr { 49 | addr: target.into(), 50 | r#type: None, 51 | }, 52 | ) 53 | .await?; 54 | 55 | let mut buf = [0u8; 2048]; 56 | let (len, _) = select! { 57 | _ = sleep(expires) => { 58 | info!("stun timeout {}", stun_server); 59 | return Err(Error::Error("STUN server timeout".to_string())); 60 | } 61 | r = conn.recv_raw(&mut buf) => { 62 | r? 63 | } 64 | }; 65 | 66 | let decoder = MessageDecoderBuilder::default().build(); 67 | let (resp, _) = decoder 68 | .decode(&buf[..len]) 69 | .map_err(|e| crate::Error::Error(e.to_string()))?; 70 | 71 | let xor_addr = resp 72 | .get::() 73 | .ok_or(crate::Error::Error( 74 | "XorMappedAddress attribute not found".to_string(), 75 | ))? 76 | .as_xor_mapped_address() 77 | .map_err(|e| crate::Error::Error(e.to_string()))?; 78 | let socket: &SocketAddr = xor_addr.socket_address(); 79 | info!("external IP: {}", socket); 80 | conn.external = Some(SipAddr { 81 | r#type: Some(rsip::transport::Transport::Udp), 82 | addr: socket.to_owned().into(), 83 | }); 84 | Ok(socket.clone()) 85 | } 86 | 87 | #[tokio::test] 88 | async fn test_external_with_stun() -> Result<()> { 89 | let addrs = tokio::net::lookup_host("restsend.com:3478").await?; 90 | for addr in addrs { 91 | info!("stun server: {}", addr); 92 | } 93 | let mut peer_bob = UdpConnection::create_connection("0.0.0.0:0".parse()?, None).await?; 94 | let expires = Duration::from_secs(5); 95 | external_by_stun(&mut peer_bob, "restsend.com:3478", expires).await?; 96 | info!("external IP: {:?}", peer_bob.get_addr()); 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /src/transaction/tests/test_endpoint.rs: -------------------------------------------------------------------------------- 1 | use rsip::headers::*; 2 | use std::{sync::Arc, time::Duration}; 3 | use tokio::{select, time::sleep}; 4 | 5 | fn assert_send() {} 6 | 7 | #[test] 8 | fn test_send() { 9 | assert_send::(); 10 | } 11 | 12 | #[tokio::test] 13 | async fn test_endpoint_serve() { 14 | let endpoint = Arc::new( 15 | super::create_test_endpoint(None) 16 | .await 17 | .expect("create_test_endpoint"), 18 | ); 19 | let endpoint_ref = endpoint.clone(); 20 | tokio::spawn(async move { 21 | endpoint_ref.serve().await; 22 | }); 23 | 24 | let mut incoming = endpoint 25 | .incoming_transactions() 26 | .expect("incoming_transactions"); 27 | select! { 28 | _ = async { 29 | sleep(Duration::from_millis(100)).await; 30 | endpoint.shutdown(); 31 | } => { 32 | } 33 | _ = async { 34 | while let Some(_) = incoming.recv().await { 35 | // Handle transaction 36 | break; // Exit for example 37 | } 38 | } => { 39 | assert!(false, "must not reach here"); 40 | } 41 | } 42 | endpoint.shutdown(); 43 | } 44 | 45 | #[tokio::test] 46 | async fn test_endpoint_recvrequests() { 47 | let addr = "127.0.0.1:0"; 48 | let endpoint = super::create_test_endpoint(Some(addr)) 49 | .await 50 | .expect("create_test_endpoint"); 51 | 52 | let addr = endpoint 53 | .get_addrs() 54 | .get(0) 55 | .expect("must has connection") 56 | .to_owned(); 57 | 58 | let send_loop = async { 59 | let test_conn = crate::transport::udp::UdpConnection::create_connection( 60 | "127.0.0.1:0".parse().unwrap(), 61 | None, 62 | None, 63 | ) 64 | .await 65 | .expect("create_connection"); 66 | let register_req = rsip::message::Request { 67 | method: rsip::method::Method::Register, 68 | uri: rsip::Uri { 69 | scheme: Some(rsip::Scheme::Sips), 70 | auth: Some(rsip::Auth { 71 | user: "bob".to_string(), 72 | password: None, 73 | }), 74 | host_with_port: rsip::HostWithPort::try_from("restsend.com") 75 | .expect("host_port parse") 76 | .into(), 77 | ..Default::default() 78 | }, 79 | headers: vec![ 80 | Via::new("SIP/2.0/TLS restsend.com:5061;branch=z9hG4bKnashd92").into(), 81 | CSeq::new("1 REGISTER").into(), 82 | From::new("Bob ;tag=ja743ks76zlflH").into(), 83 | CallId::new("1j9FpLxk3uxtm8tn@restsend.com").into(), 84 | ] 85 | .into(), 86 | version: rsip::Version::V2, 87 | body: Default::default(), 88 | }; 89 | let buf: String = register_req.try_into().expect("try_into"); 90 | test_conn 91 | .send_raw(&buf.as_bytes(), &addr) 92 | .await 93 | .expect("send_raw"); 94 | sleep(Duration::from_secs(1)).await; 95 | }; 96 | 97 | let incoming_loop = async { 98 | let mut incoming = endpoint 99 | .incoming_transactions() 100 | .expect("incoming_transactions"); 101 | incoming.recv().await.expect("incoming").original.clone() 102 | }; 103 | 104 | select! { 105 | _ = send_loop => { 106 | assert!(false, "must not reach here"); 107 | } 108 | _ = endpoint.serve()=> {} 109 | req = incoming_loop => { 110 | assert_eq!(req.method, rsip::method::Method::Register); 111 | assert_eq!(req.uri.to_string(), "sips:bob@restsend.com"); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/dialog/tests/test_prack.rs: -------------------------------------------------------------------------------- 1 | use super::test_dialog_states::{create_invite_request, create_test_endpoint}; 2 | use crate::dialog::{dialog::DialogInner, server_dialog::ServerInviteDialog, DialogId}; 3 | use crate::transaction::{ 4 | key::{TransactionKey, TransactionRole}, 5 | transaction::Transaction, 6 | }; 7 | use crate::transport::{ 8 | channel::ChannelConnection, connection::TransportEvent, SipAddr, SipConnection, 9 | }; 10 | use rsip::headers::*; 11 | use rsip::{Header, Method, Request, SipMessage, StatusCode}; 12 | use std::convert::TryFrom; 13 | use std::sync::Arc; 14 | use tokio::sync::mpsc::unbounded_channel; 15 | use tokio::time::{timeout, Duration}; 16 | 17 | #[tokio::test] 18 | async fn server_dialog_handles_prack_request() -> crate::Result<()> { 19 | let endpoint = create_test_endpoint().await?; 20 | let (state_sender, _state_receiver) = unbounded_channel(); 21 | let (tu_sender, _tu_receiver) = unbounded_channel(); 22 | 23 | let dialog_id = DialogId { 24 | call_id: "test-call-prack".to_string(), 25 | from_tag: "alice-tag".to_string(), 26 | to_tag: "bob-tag".to_string(), 27 | }; 28 | 29 | let invite_req = create_invite_request(&dialog_id.from_tag, "", &dialog_id.call_id); 30 | 31 | let dialog_inner = DialogInner::new( 32 | TransactionRole::Server, 33 | dialog_id.clone(), 34 | invite_req, 35 | endpoint.inner.clone(), 36 | state_sender, 37 | None, 38 | Some(rsip::Uri::try_from("sip:bob@bob.example.com:5060")?), 39 | tu_sender, 40 | )?; 41 | 42 | let mut server_dialog = ServerInviteDialog { 43 | inner: Arc::new(dialog_inner), 44 | }; 45 | 46 | // Build PRACK request 47 | let prack_request = Request { 48 | method: Method::PRack, 49 | uri: rsip::Uri::try_from("sip:bob@example.com:5060")?, 50 | headers: vec![ 51 | Via::new("SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKprack01").into(), 52 | CSeq::new("2 PRACK").into(), 53 | From::new(&format!( 54 | "Alice ;tag={}", 55 | dialog_id.from_tag 56 | )) 57 | .into(), 58 | To::new(&format!( 59 | "Bob ;tag={}", 60 | dialog_id.to_tag 61 | )) 62 | .into(), 63 | CallId::new(&dialog_id.call_id).into(), 64 | Header::Other("RAck".into(), "1 1 INVITE".into()), 65 | Contact::new("").into(), 66 | MaxForwards::new("70").into(), 67 | Header::ContentLength((0u32).into()), 68 | ] 69 | .into(), 70 | version: rsip::Version::V2, 71 | body: vec![], 72 | }; 73 | 74 | let key = TransactionKey::from_request(&prack_request, TransactionRole::Server)?; 75 | 76 | let (_, incoming_rx) = unbounded_channel(); 77 | let (transport_tx, mut transport_rx) = unbounded_channel(); 78 | 79 | let sip_addr: SipAddr = rsip::HostWithPort::try_from("127.0.0.1:5060")?.into(); 80 | 81 | let channel = 82 | ChannelConnection::create_connection(incoming_rx, transport_tx, sip_addr.clone(), None) 83 | .await?; 84 | let connection = SipConnection::Channel(channel); 85 | 86 | let mut tx = 87 | Transaction::new_server(key, prack_request, endpoint.inner.clone(), Some(connection)); 88 | tx.destination = Some(sip_addr.clone()); 89 | 90 | server_dialog.handle(&mut tx).await?; 91 | 92 | let event = timeout(Duration::from_secs(1), transport_rx.recv()) 93 | .await 94 | .expect("timeout waiting for PRACK response") 95 | .expect("transport event"); 96 | match event { 97 | TransportEvent::Incoming(SipMessage::Response(resp), _, _) => { 98 | assert_eq!(resp.status_code, StatusCode::OK); 99 | } 100 | other => panic!("unexpected transport event: {other:?}"), 101 | } 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /src/transaction/tests/test_server.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::SipConnection; 2 | use crate::{ 3 | transport::{udp::UdpConnection, TransportLayer}, 4 | EndpointBuilder, 5 | }; 6 | use rsip::headers::*; 7 | use std::time::Duration; 8 | use tokio::{select, time::sleep}; 9 | use tokio_util::sync::CancellationToken; 10 | use tracing::info; 11 | 12 | #[tokio::test] 13 | async fn test_server_transaction() { 14 | let token = CancellationToken::new(); 15 | 16 | let mock_conn = 17 | UdpConnection::create_connection("127.0.0.1:0".parse().expect("parse addr"), None, None) 18 | .await 19 | .expect("create_connection"); 20 | 21 | let mock_conn_sip: SipConnection = mock_conn.into(); 22 | let addr = mock_conn_sip.get_addr().clone(); 23 | 24 | let tl = TransportLayer::new(token.child_token()); 25 | tl.add_transport(mock_conn_sip.clone()); 26 | 27 | let endpoint = EndpointBuilder::new() 28 | .with_user_agent("rsipstack-test") 29 | .with_transport_layer(tl) 30 | .build(); 31 | 32 | let client_conn = 33 | UdpConnection::create_connection("127.0.0.1:0".parse().expect("parse addr"), None, None) 34 | .await 35 | .expect("create client connection"); 36 | 37 | let client_conn_sip: SipConnection = client_conn.into(); 38 | 39 | let send_loop = async { 40 | sleep(Duration::from_millis(50)).await; 41 | 42 | let register_req = rsip::message::Request { 43 | method: rsip::method::Method::Register, 44 | uri: rsip::Uri { 45 | scheme: Some(rsip::Scheme::Sip), 46 | host_with_port: rsip::HostWithPort::try_from(addr.addr.to_string()) 47 | .expect("host_port parse") 48 | .into(), 49 | ..Default::default() 50 | }, 51 | headers: vec![ 52 | Via::new(&format!( 53 | "SIP/2.0/UDP {};branch=z9hG4bKnashd92", 54 | client_conn_sip.get_addr().addr 55 | )) 56 | .into(), 57 | CSeq::new("1 REGISTER").into(), 58 | From::new("Bob ;tag=ja743ks76zlflH").into(), 59 | CallId::new("1j9FpLxk3uxtm8tn@restsend.com").into(), 60 | ] 61 | .into(), 62 | version: rsip::Version::V2, 63 | body: Default::default(), 64 | }; 65 | 66 | client_conn_sip 67 | .send(register_req.into(), Some(&addr)) 68 | .await 69 | .expect("send"); 70 | 71 | sleep(Duration::from_millis(100)).await; 72 | info!("Message sent, waiting for responses..."); 73 | }; 74 | 75 | let incoming_loop = async { 76 | let mut incoming = endpoint 77 | .incoming_transactions() 78 | .expect("incoming_transactions"); 79 | let mut tx = incoming.recv().await.expect("incoming"); 80 | assert_eq!(tx.original.method, rsip::method::Method::Register); 81 | let headers = tx.original.headers.clone(); 82 | let done_response = rsip::Response { 83 | status_code: rsip::StatusCode::OK, 84 | version: rsip::Version::V2, 85 | headers, 86 | ..Default::default() 87 | }; 88 | tx.send_trying().await.expect("send_trying"); 89 | tx.respond(done_response).await.expect("respond 200"); 90 | 91 | assert!(tx 92 | .endpoint_inner 93 | .finished_transactions 94 | .read() 95 | .unwrap() 96 | .contains_key(&tx.key)); 97 | sleep(Duration::from_secs(2)).await; 98 | }; 99 | 100 | select! { 101 | _ = send_loop => { 102 | } 103 | _ = endpoint.serve()=> {} 104 | _ = incoming_loop => { 105 | assert!(false, "must not reach here"); 106 | } 107 | _ = sleep(Duration::from_secs(1)) => { 108 | assert!(false, "timeout waiting"); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/transport/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::TransportSender, 4 | sip_addr::SipAddr, 5 | stream::{StreamConnection, StreamConnectionInner}, 6 | SipConnection, 7 | }, 8 | Result, 9 | }; 10 | use rsip::SipMessage; 11 | use std::{fmt, sync::Arc}; 12 | use tokio::net::TcpStream; 13 | use tokio_util::sync::CancellationToken; 14 | use tracing::info; 15 | 16 | type TcpInner = 17 | StreamConnectionInner, tokio::io::WriteHalf>; 18 | 19 | #[derive(Clone)] 20 | pub struct TcpConnection { 21 | pub inner: Arc, 22 | pub cancel_token: Option, 23 | } 24 | 25 | impl TcpConnection { 26 | pub async fn connect( 27 | remote: &SipAddr, 28 | cancel_token: Option, 29 | ) -> Result { 30 | let socket_addr = remote.get_socketaddr()?; 31 | let stream = TcpStream::connect(socket_addr).await?; 32 | 33 | let local_addr = SipAddr { 34 | r#type: Some(rsip::transport::Transport::Tcp), 35 | addr: SipConnection::resolve_bind_address(stream.local_addr()?).into(), 36 | }; 37 | 38 | let (read_half, write_half) = tokio::io::split(stream); 39 | 40 | let connection = TcpConnection { 41 | inner: Arc::new(StreamConnectionInner::new( 42 | local_addr.clone(), 43 | remote.clone(), 44 | read_half, 45 | write_half, 46 | )), 47 | cancel_token, 48 | }; 49 | 50 | info!( 51 | "Created TCP client connection: {} -> {}", 52 | local_addr, remote 53 | ); 54 | 55 | Ok(connection) 56 | } 57 | 58 | pub fn from_stream( 59 | stream: TcpStream, 60 | local_addr: SipAddr, 61 | cancel_token: Option, 62 | ) -> Result { 63 | let remote_addr = stream.peer_addr()?; 64 | let remote_sip_addr = SipAddr { 65 | r#type: Some(rsip::transport::Transport::Tcp), 66 | addr: remote_addr.into(), 67 | }; 68 | 69 | let (read_half, write_half) = tokio::io::split(stream); 70 | 71 | let connection = TcpConnection { 72 | inner: Arc::new(StreamConnectionInner::new( 73 | local_addr, 74 | remote_sip_addr, 75 | read_half, 76 | write_half, 77 | )), 78 | cancel_token, 79 | }; 80 | 81 | info!( 82 | "Created TCP server connection: {} <- {}", 83 | connection.inner.local_addr, remote_addr 84 | ); 85 | 86 | Ok(connection) 87 | } 88 | 89 | pub fn cancel_token(&self) -> Option { 90 | self.cancel_token.clone() 91 | } 92 | } 93 | 94 | #[async_trait::async_trait] 95 | impl StreamConnection for TcpConnection { 96 | fn get_addr(&self) -> &SipAddr { 97 | &self.inner.remote_addr 98 | } 99 | 100 | async fn send_message(&self, msg: SipMessage) -> Result<()> { 101 | self.inner.send_message(msg).await 102 | } 103 | 104 | async fn send_raw(&self, data: &[u8]) -> Result<()> { 105 | self.inner.send_raw(data).await 106 | } 107 | 108 | async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 109 | let sip_connection = SipConnection::Tcp(self.clone()); 110 | self.inner.serve_loop(sender, sip_connection).await 111 | } 112 | 113 | async fn close(&self) -> Result<()> { 114 | self.inner.close().await 115 | } 116 | } 117 | 118 | impl fmt::Display for TcpConnection { 119 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 120 | write!( 121 | f, 122 | "TCP {} -> {}", 123 | self.inner.local_addr, self.inner.remote_addr 124 | ) 125 | } 126 | } 127 | 128 | impl fmt::Debug for TcpConnection { 129 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 130 | fmt::Display::fmt(self, f) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/dialog/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use rsip::{ 3 | prelude::{HeadersExt, UntypedHeader}, 4 | Request, Response, 5 | }; 6 | 7 | pub mod authenticate; 8 | pub mod client_dialog; 9 | pub mod dialog; 10 | pub mod dialog_layer; 11 | pub mod invitation; 12 | pub mod registration; 13 | pub mod server_dialog; 14 | 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | /// SIP Dialog Identifier 19 | /// 20 | /// `DialogId` uniquely identifies a SIP dialog. According to RFC 3261, a dialog is 21 | /// identified by the Call-ID, local tag, and remote tag. 22 | /// 23 | /// # Fields 24 | /// 25 | /// * `call_id` - The Call-ID header field value from SIP messages, identifying a call session 26 | /// * `from_tag` - The tag parameter from the From header field, identifying the dialog initiator 27 | /// * `to_tag` - The tag parameter from the To header field, identifying the dialog recipient 28 | /// 29 | /// # Examples 30 | /// 31 | /// ```rust 32 | /// use rsipstack::dialog::DialogId; 33 | /// 34 | /// let dialog_id = DialogId { 35 | /// call_id: "1234567890@example.com".to_string(), 36 | /// from_tag: "alice-tag-123".to_string(), 37 | /// to_tag: "bob-tag-456".to_string(), 38 | /// }; 39 | /// 40 | /// println!("Dialog ID: {}", dialog_id); 41 | /// ``` 42 | /// 43 | /// # Notes 44 | /// 45 | /// - During early dialog establishment, `to_tag` may be an empty string 46 | /// - Dialog ID remains constant throughout the dialog lifetime 47 | /// - Used for managing and routing SIP messages at the dialog layer 48 | #[derive(Clone, Debug)] 49 | pub struct DialogId { 50 | pub call_id: String, 51 | pub from_tag: String, 52 | pub to_tag: String, 53 | } 54 | 55 | impl PartialEq for DialogId { 56 | fn eq(&self, other: &DialogId) -> bool { 57 | if self.call_id != other.call_id { 58 | return false; 59 | } 60 | if self.from_tag == other.from_tag && self.to_tag == other.to_tag { 61 | return true; 62 | } 63 | if self.from_tag == other.to_tag && self.to_tag == other.from_tag { 64 | return true; 65 | } 66 | false 67 | } 68 | } 69 | 70 | impl Eq for DialogId {} 71 | impl std::hash::Hash for DialogId { 72 | fn hash(&self, state: &mut H) { 73 | self.call_id.hash(state); 74 | if self.from_tag > self.to_tag { 75 | self.from_tag.hash(state); 76 | self.to_tag.hash(state); 77 | } else { 78 | self.to_tag.hash(state); 79 | self.from_tag.hash(state); 80 | } 81 | } 82 | } 83 | 84 | impl TryFrom<&Request> for DialogId { 85 | type Error = crate::Error; 86 | 87 | fn try_from(request: &Request) -> Result { 88 | let call_id = request.call_id_header()?.value().to_string(); 89 | 90 | let from_tag = match request.from_header()?.tag()? { 91 | Some(tag) => tag.value().to_string(), 92 | None => return Err(Error::Error("from tag not found".to_string())), 93 | }; 94 | 95 | let to_tag = match request.to_header()?.tag()? { 96 | Some(tag) => tag.value().to_string(), 97 | None => "".to_string(), 98 | }; 99 | 100 | Ok(DialogId { 101 | call_id, 102 | from_tag, 103 | to_tag, 104 | }) 105 | } 106 | } 107 | 108 | impl TryFrom<&Response> for DialogId { 109 | type Error = crate::Error; 110 | 111 | fn try_from(resp: &Response) -> Result { 112 | let call_id = resp.call_id_header()?.value().to_string(); 113 | 114 | let from_tag = match resp.from_header()?.tag()? { 115 | Some(tag) => tag.value().to_string(), 116 | None => return Err(Error::Error("from tag not found".to_string())), 117 | }; 118 | 119 | let to_tag = match resp.to_header()?.tag()? { 120 | Some(tag) => tag.value().to_string(), 121 | None => return Err(Error::Error("to tag not found".to_string())), 122 | }; 123 | 124 | Ok(DialogId { 125 | call_id, 126 | from_tag, 127 | to_tag, 128 | }) 129 | } 130 | } 131 | 132 | impl std::fmt::Display for DialogId { 133 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 134 | if self.from_tag > self.to_tag { 135 | write!(f, "{}-{}-{}", self.call_id, self.from_tag, self.to_tag) 136 | } else { 137 | write!(f, "{}-{}-{}", self.call_id, self.to_tag, self.from_tag) 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/dialog/tests/test_server_dialog.rs: -------------------------------------------------------------------------------- 1 | use rsip::prelude::{HeadersExt, ToTypedHeader}; 2 | use std::net::{IpAddr, Ipv4Addr}; 3 | use std::sync::Arc; 4 | use tokio::sync::mpsc::unbounded_channel; 5 | 6 | use crate::{ 7 | dialog::{ 8 | dialog::DialogInner, 9 | server_dialog::ServerInviteDialog, 10 | tests::test_dialog_states::{create_invite_request, create_test_endpoint}, 11 | DialogId, 12 | }, 13 | transaction::{key::TransactionRole, transaction::TransactionEvent}, 14 | transport::SipAddr, 15 | }; 16 | 17 | #[tokio::test] 18 | async fn test_dialog_make_request() -> crate::Result<()> { 19 | // Create dialog ID 20 | let dialog_id = DialogId { 21 | call_id: "test-call-id-123".to_string(), 22 | from_tag: "alice-tag-456".to_string(), 23 | to_tag: "bob-tag-789".to_string(), 24 | }; 25 | 26 | let endpoint = create_test_endpoint().await?; 27 | let (tu_sender, _tu_receiver) = unbounded_channel(); 28 | let (state_sender, _state_receiver) = unbounded_channel(); 29 | // Create INVITE request 30 | let invite_req = create_invite_request("alice-tag-456", "", "test-call-id-123"); 31 | // Create dialog inner 32 | let dialog_inner = DialogInner::new( 33 | TransactionRole::Client, 34 | dialog_id.clone(), 35 | invite_req.clone(), 36 | endpoint.inner.clone(), 37 | state_sender, 38 | None, 39 | Some(rsip::Uri::try_from("sip:alice@alice.example.com:5060")?), 40 | tu_sender, 41 | ) 42 | .expect("Failed to create dialog inner"); 43 | 44 | let bye = dialog_inner 45 | .make_request_with_vias( 46 | rsip::Method::Bye, 47 | None, 48 | dialog_inner 49 | .build_vias_from_request() 50 | .expect("Failed to build vias"), 51 | None, 52 | None, 53 | ) 54 | .expect("Failed to make request"); 55 | assert_eq!(bye.method, rsip::Method::Bye); 56 | 57 | assert_eq!( 58 | bye.via_header() 59 | .expect("not via header") 60 | .typed()? 61 | .received()?, 62 | "172.0.0.1".parse().ok() 63 | ); 64 | assert!( 65 | bye.via_header().expect("not via header").typed()?.branch() 66 | != invite_req 67 | .via_header() 68 | .expect("not via header") 69 | .typed()? 70 | .branch() 71 | ); 72 | Ok(()) 73 | } 74 | 75 | #[tokio::test] 76 | async fn test_accept_with_public_contact_preserves_contact_header() -> crate::Result<()> { 77 | // Create dialog ID 78 | let dialog_id = DialogId { 79 | call_id: "test-call-id-contact".to_string(), 80 | from_tag: "alice-tag-456".to_string(), 81 | to_tag: "bob-tag-789".to_string(), 82 | }; 83 | 84 | let endpoint = create_test_endpoint().await?; 85 | let (tu_sender, mut tu_receiver) = unbounded_channel(); 86 | let (state_sender, _state_receiver) = unbounded_channel(); 87 | 88 | // Create INVITE request 89 | let invite_req = create_invite_request("alice-tag-456", "", "test-call-id-contact"); 90 | 91 | // Create server dialog inner 92 | let dialog_inner = DialogInner::new( 93 | TransactionRole::Server, 94 | dialog_id.clone(), 95 | invite_req, 96 | endpoint.inner.clone(), 97 | state_sender, 98 | None, 99 | None, 100 | tu_sender, 101 | ) 102 | .expect("Failed to create dialog inner"); 103 | 104 | let server_dialog = ServerInviteDialog { 105 | inner: Arc::new(dialog_inner), 106 | }; 107 | 108 | // Define the public address we want to use 109 | let public_address = Some(rsip::HostWithPort { 110 | host: IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)).into(), 111 | port: Some(5060.into()), 112 | }); 113 | 114 | // Define local address as fallback 115 | let local_address: SipAddr = rsip::HostWithPort::try_from("127.0.0.1:5060")?.into(); 116 | 117 | // Accept with public contact 118 | server_dialog.accept_with_public_contact( 119 | "bob", 120 | public_address.clone(), 121 | &local_address, 122 | None, 123 | None, 124 | )?; 125 | 126 | // Receive the response from the transaction event channel 127 | let event = tu_receiver 128 | .recv() 129 | .await 130 | .expect("Should receive transaction event"); 131 | 132 | match event { 133 | TransactionEvent::Respond(response) => { 134 | // Verify status code is 200 OK 135 | assert_eq!(response.status_code, rsip::StatusCode::OK); 136 | 137 | // Extract and verify Contact header 138 | let contact_header = response 139 | .contact_header() 140 | .expect("Response should have Contact header") 141 | .typed() 142 | .expect("Contact header should be parseable"); 143 | 144 | // Verify the Contact URI matches the public address we provided 145 | assert_eq!( 146 | contact_header.uri.host_with_port.host, 147 | public_address.as_ref().unwrap().host 148 | ); 149 | assert_eq!( 150 | contact_header.uri.host_with_port.port, 151 | public_address.as_ref().unwrap().port 152 | ); 153 | 154 | // Verify the username in the Contact URI 155 | assert_eq!(contact_header.uri.auth.as_ref().unwrap().user, "bob"); 156 | } 157 | _other => panic!("Expected TransactionEvent::Respond, got different event type"), 158 | } 159 | 160 | Ok(()) 161 | } 162 | -------------------------------------------------------------------------------- /examples/client/play_file.rs: -------------------------------------------------------------------------------- 1 | use crate::{get_first_non_loopback_interface, MediaSessionOption}; 2 | use rsipstack::transport::udp::UdpConnection; 3 | use rsipstack::Result; 4 | use rsipstack::{transport::SipAddr, Error}; 5 | use rtp_rs::RtpPacketBuilder; 6 | use std::net::SocketAddr; 7 | use std::time::Duration; 8 | use tokio::select; 9 | use tokio_util::sync::CancellationToken; 10 | use tracing::info; 11 | 12 | pub async fn build_rtp_conn( 13 | opt: &MediaSessionOption, 14 | ssrc: u32, 15 | payload_type: u8, 16 | ) -> Result<(UdpConnection, String)> { 17 | let addr = get_first_non_loopback_interface()?; 18 | let mut conn = None; 19 | 20 | for p in 0..100 { 21 | let port = opt.rtp_start_port + p * 2; 22 | if let Ok(c) = UdpConnection::create_connection( 23 | format!("{:?}:{}", addr, port).parse()?, 24 | opt.external_ip 25 | .as_ref() 26 | .map(|ip| ip.parse::().expect("Invalid external IP")), 27 | Some(opt.cancel_token.clone()), 28 | ) 29 | .await 30 | { 31 | conn = Some(c); 32 | break; 33 | } else { 34 | info!("Failed to bind RTP socket on port: {}", port); 35 | } 36 | } 37 | 38 | if conn.is_none() { 39 | return Err(Error::Error("Failed to bind RTP socket".to_string())); 40 | } 41 | 42 | let conn = conn.unwrap(); 43 | let codec = payload_type; 44 | let codec_name = match codec { 45 | 0 => "PCMU", 46 | 8 => "PCMA", 47 | _ => "Unknown", 48 | }; 49 | let socketaddr: SocketAddr = conn.get_addr().addr.to_owned().try_into()?; 50 | let sdp = format!( 51 | "v=0\r\n\ 52 | o=- 0 0 IN IP4 {}\r\n\ 53 | s=rsipstack example\r\n\ 54 | c=IN IP4 {}\r\n\ 55 | t=0 0\r\n\ 56 | m=audio {} RTP/AVP {codec}\r\n\ 57 | a=rtpmap:{codec} {codec_name}/8000\r\n\ 58 | a=ssrc:{ssrc}\r\n\ 59 | a=sendrecv\r\n", 60 | socketaddr.ip(), 61 | socketaddr.ip(), 62 | socketaddr.port(), 63 | ); 64 | info!("RTP socket: {:?} {}", conn.get_addr(), sdp); 65 | Ok((conn, sdp)) 66 | } 67 | 68 | pub async fn play_echo(conn: UdpConnection, token: CancellationToken) -> Result<()> { 69 | select! { 70 | _ = token.cancelled() => { 71 | info!("RTP session cancelled"); 72 | } 73 | _ = async { 74 | loop { 75 | let mut mbuf = vec![0; 1500]; 76 | let (len, addr) = match conn.recv_raw(&mut mbuf).await { 77 | Ok(r) => r, 78 | Err(e) => { 79 | info!("Failed to receive RTP: {:?}", e); 80 | break; 81 | } 82 | }; 83 | match conn.send_raw(&mbuf[..len], &addr).await { 84 | Ok(_) => {}, 85 | Err(e) => { 86 | info!("Failed to send RTP: {:?}", e); 87 | break; 88 | } 89 | } 90 | } 91 | } => { 92 | info!("playback finished, hangup"); 93 | } 94 | }; 95 | Ok(()) 96 | } 97 | 98 | pub async fn play_audio_file( 99 | conn: UdpConnection, 100 | token: CancellationToken, 101 | ssrc: u32, 102 | filename: &str, 103 | mut ts: u32, 104 | mut seq: u16, 105 | peer_addr: String, 106 | payload_type: u8, 107 | ) -> Result<(u32, u16)> { 108 | select! { 109 | _ = token.cancelled() => { 110 | info!("RTP session cancelled"); 111 | } 112 | _ = async { 113 | let peer_addr = SipAddr{ 114 | addr: peer_addr.try_into().expect("peer_addr"), 115 | r#type: Some(rsip::transport::Transport::Udp), 116 | }; 117 | let sample_size = 160; 118 | let mut ticker = tokio::time::interval(Duration::from_millis(20)); 119 | let ext = match payload_type { 120 | 8 => "pcma", 121 | 0 => "pcmu", 122 | _ => { 123 | info!("Unsupported codec type: {}", payload_type); 124 | return; 125 | } 126 | }; 127 | let file_name = format!("./assets/{filename}.{ext}"); 128 | info!("Playing {filename} file: {} payload_type:{} sample_size:{}", file_name, payload_type, sample_size); 129 | let example_data = tokio::fs::read(file_name).await.expect("read file"); 130 | 131 | for chunk in example_data.chunks(sample_size) { 132 | let result = match RtpPacketBuilder::new() 133 | .payload_type(payload_type) 134 | .ssrc(ssrc) 135 | .sequence(seq.into()) 136 | .timestamp(ts) 137 | .payload(&chunk) 138 | .build() { 139 | Ok(r) => r, 140 | Err(e) => { 141 | info!("Failed to build RTP packet: {:?}", e); 142 | break; 143 | } 144 | }; 145 | ts += chunk.len() as u32; 146 | seq += 1; 147 | match conn.send_raw(&result, &peer_addr).await { 148 | Ok(_) => {}, 149 | Err(e) => { 150 | info!("Failed to send RTP: {:?}", e); 151 | break; 152 | } 153 | } 154 | ticker.tick().await; 155 | } 156 | } => { 157 | info!("playback finished, hangup"); 158 | } 159 | }; 160 | Ok((ts, seq)) 161 | } 162 | -------------------------------------------------------------------------------- /src/transaction/key.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use rsip::headers::UntypedHeader; 3 | use rsip::typed::Via; 4 | use rsip::{ 5 | param::Tag, 6 | prelude::{HeadersExt, ToTypedHeader}, 7 | Method, 8 | }; 9 | use rsip::{Request, Response}; 10 | use std::fmt::Write; 11 | use std::hash::Hash; 12 | 13 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 14 | pub enum TransactionRole { 15 | Client, 16 | Server, 17 | } 18 | 19 | impl std::fmt::Display for TransactionRole { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | match self { 22 | TransactionRole::Client => write!(f, "c"), 23 | TransactionRole::Server => write!(f, "s"), 24 | } 25 | } 26 | } 27 | 28 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 29 | pub struct TransactionKey(String); 30 | 31 | impl std::fmt::Display for TransactionKey { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | write!(f, "{}", self.0) 34 | } 35 | } 36 | 37 | impl TransactionKey { 38 | pub fn from_request(req: &Request, role: TransactionRole) -> Result { 39 | let via = req.via_header()?.typed()?; 40 | let mut method = req.method().clone(); 41 | 42 | if matches!(method, Method::Ack | Method::Cancel) && role == TransactionRole::Server { 43 | method = Method::Invite; 44 | } 45 | 46 | let from_tag = req 47 | .from_header()? 48 | .tag()? 49 | .ok_or(Error::Error("from tags missing".to_string()))?; 50 | let call_id = req.call_id_header()?.value(); 51 | let cseq = req.cseq_header()?.seq()?; 52 | Self::build_key(role, via, method, cseq, from_tag, call_id) 53 | } 54 | 55 | pub fn from_response(resp: &Response, role: TransactionRole) -> Result { 56 | let via = resp.via_header()?.typed()?; 57 | let cseq = resp.cseq_header()?; 58 | let method = cseq.method()?; 59 | let from_tag = resp 60 | .from_header()? 61 | .tag()? 62 | .ok_or(Error::Error("from tags missing".to_string()))?; 63 | let call_id = resp.call_id_header()?.value(); 64 | 65 | Self::build_key(role, via, method, cseq.seq()?, from_tag, call_id) 66 | } 67 | 68 | pub(super) fn build_key( 69 | role: TransactionRole, 70 | via: Via, 71 | method: Method, 72 | cseq: u32, 73 | from_tag: Tag, 74 | call_id: &str, 75 | ) -> Result { 76 | let mut key = String::new(); 77 | match via.branch() { 78 | Some(branch) => { 79 | write!( 80 | &mut key, 81 | "{}.{}_{}_{}_{}_{}", 82 | role, method, cseq, call_id, from_tag, branch 83 | ) 84 | } 85 | None => { 86 | write!( 87 | &mut key, 88 | "{}.{}_{}_{}_{}_{}.2543", 89 | role, method, cseq, call_id, from_tag, via.uri.host_with_port 90 | ) 91 | } 92 | } 93 | .map_err(|e| Error::Error(e.to_string()))?; 94 | Ok(TransactionKey(key)) 95 | } 96 | } 97 | 98 | #[test] 99 | fn test_transaction_key() -> Result<()> { 100 | use rsip::headers::*; 101 | let register_req = rsip::message::Request { 102 | method: rsip::method::Method::Register, 103 | uri: rsip::Uri { 104 | scheme: Some(rsip::Scheme::Sips), 105 | host_with_port: rsip::Domain::from("restsend.com").into(), 106 | ..Default::default() 107 | }, 108 | headers: vec![ 109 | Via::new("SIP/2.0/TLS sip.restsend.com:5061;branch=z9hG4bKnashd92").into(), 110 | CSeq::new("2 REGISTER").into(), 111 | From::new("Bob ;tag=ja743ks76zlflH").into(), 112 | CallId::new("1j9FpLxk3uxtm8tn@sip.restsend.com").into(), 113 | ] 114 | .into(), 115 | version: rsip::Version::V2, 116 | body: Default::default(), 117 | }; 118 | let key = TransactionKey::from_request(®ister_req, TransactionRole::Client)?; 119 | assert_eq!( 120 | key, 121 | TransactionKey( 122 | "c.REGISTER_2_1j9FpLxk3uxtm8tn@sip.restsend.com_ja743ks76zlflH_z9hG4bKnashd92" 123 | .to_string() 124 | ) 125 | ); 126 | let register_resp = rsip::message::Response { 127 | status_code: rsip::StatusCode::OK, 128 | version: rsip::Version::V2, 129 | headers: vec![ 130 | Via::new("SIP/2.0/TLS client.sip.restsend.com:5061;branch=z9hG4bKnashd92").into(), 131 | CSeq::new("2 REGISTER").into(), 132 | From::new("Bob ;tag=ja743ks76zlflH").into(), 133 | CallId::new("1j9FpLxk3uxtm8tn@sip.restsend.com").into(), 134 | ] 135 | .into(), 136 | body: Default::default(), 137 | }; 138 | let key = TransactionKey::from_response(®ister_resp, TransactionRole::Server)?; 139 | assert_eq!( 140 | key, 141 | TransactionKey( 142 | "s.REGISTER_2_1j9FpLxk3uxtm8tn@sip.restsend.com_ja743ks76zlflH_z9hG4bKnashd92" 143 | .to_string() 144 | ) 145 | ); 146 | 147 | let mut ack_req = register_req.clone(); 148 | ack_req.method = Method::Ack; 149 | ack_req.headers.unique_push(CSeq::new("2 ACK").into()); 150 | 151 | let key = TransactionKey::from_request(&ack_req, TransactionRole::Server)?; 152 | assert_eq!( 153 | key, 154 | TransactionKey( 155 | "s.INVITE_2_1j9FpLxk3uxtm8tn@sip.restsend.com_ja743ks76zlflH_z9hG4bKnashd92" 156 | .to_string() 157 | ) 158 | ); 159 | Ok(()) 160 | } 161 | -------------------------------------------------------------------------------- /src/dialog/tests/test_authenticate.rs: -------------------------------------------------------------------------------- 1 | //! Authentication tests 2 | //! 3 | //! Tests for SIP authentication handling, including Via header parameter updates 4 | 5 | use crate::dialog::authenticate::{handle_client_authenticate, Credential}; 6 | use crate::transaction::{ 7 | endpoint::EndpointBuilder, 8 | key::{TransactionKey, TransactionRole}, 9 | transaction::Transaction, 10 | }; 11 | use crate::transport::TransportLayer; 12 | use rsip::headers::*; 13 | use rsip::prelude::{HeadersExt, ToTypedHeader}; 14 | use rsip::{Request, Response, StatusCode}; 15 | use tokio_util::sync::CancellationToken; 16 | 17 | async fn create_test_endpoint() -> crate::Result { 18 | let token = CancellationToken::new(); 19 | let tl = TransportLayer::new(token.child_token()); 20 | let endpoint = EndpointBuilder::new() 21 | .with_user_agent("rsipstack-test") 22 | .with_transport_layer(tl) 23 | .build(); 24 | Ok(endpoint) 25 | } 26 | 27 | fn create_request_with_branch(branch: &str) -> Request { 28 | Request { 29 | method: rsip::Method::Register, 30 | uri: rsip::Uri::try_from("sip:example.com:5060").unwrap(), 31 | headers: vec![ 32 | Via::new(&format!( 33 | "SIP/2.0/UDP alice.example.com:5060;branch={}", 34 | branch 35 | )) 36 | .into(), 37 | CSeq::new("1 REGISTER").into(), 38 | From::new("Alice ;tag=1928301774").into(), 39 | To::new("Bob ").into(), 40 | CallId::new("a84b4c76e66710@pc33.atlanta.com").into(), 41 | MaxForwards::new("70").into(), 42 | ] 43 | .into(), 44 | version: rsip::Version::V2, 45 | body: vec![], 46 | } 47 | } 48 | 49 | fn create_401_response() -> Response { 50 | Response { 51 | status_code: StatusCode::Unauthorized, 52 | version: rsip::Version::V2, 53 | headers: vec![ 54 | Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(), 55 | CSeq::new("1 REGISTER").into(), 56 | From::new("Alice ;tag=1928301774").into(), 57 | To::new("Bob ").into(), 58 | CallId::new("a84b4c76e66710@pc33.atlanta.com").into(), 59 | WwwAuthenticate::new( 60 | r#"Digest realm="example.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", algorithm=MD5, qop="auth""#, 61 | ) 62 | .into(), 63 | ] 64 | .into(), 65 | body: vec![], 66 | } 67 | } 68 | 69 | #[tokio::test] 70 | async fn test_authenticate_via_header_branch_update() -> crate::Result<()> { 71 | let endpoint = create_test_endpoint().await?; 72 | 73 | // Create a request with a specific branch parameter 74 | let original_branch = "z9hG4bKoriginal123"; 75 | let original_req = create_request_with_branch(original_branch); 76 | 77 | // Verify the original request has the branch 78 | let original_via = original_req 79 | .via_header() 80 | .expect("Request should have Via header") 81 | .typed() 82 | .expect("Via header should be parseable"); 83 | let original_branch_param = original_via 84 | .params 85 | .iter() 86 | .find(|p| matches!(p, rsip::Param::Branch(_))) 87 | .expect("Original request should have branch parameter"); 88 | let original_branch_value = match original_branch_param { 89 | rsip::Param::Branch(b) => b.to_string(), 90 | _ => unreachable!(), 91 | }; 92 | assert_eq!(original_branch_value, original_branch); 93 | 94 | // Create transaction 95 | let key = TransactionKey::from_request(&original_req, TransactionRole::Client)?; 96 | let tx = Transaction::new_client(key, original_req, endpoint.inner.clone(), None); 97 | 98 | // Create 401 response 99 | let resp = create_401_response(); 100 | 101 | // Create credential 102 | let cred = Credential { 103 | username: "alice".to_string(), 104 | password: "secret123".to_string(), 105 | realm: None, 106 | }; 107 | 108 | // Call handle_client_authenticate 109 | let new_tx = handle_client_authenticate(2, tx, resp, &cred).await?; 110 | 111 | // Verify the new request has updated Via header 112 | let new_via = new_tx 113 | .original 114 | .via_header() 115 | .expect("New request should have Via header") 116 | .typed() 117 | .expect("Via header should be parseable"); 118 | 119 | // Verify old branch is removed 120 | let old_branch_exists = new_via 121 | .params 122 | .iter() 123 | .any(|p| matches!(p, rsip::Param::Branch(b) if b.to_string() == original_branch_value)); 124 | assert!( 125 | !old_branch_exists, 126 | "Old branch parameter should be removed from Via header" 127 | ); 128 | 129 | // Verify new branch is added (and different from old one) 130 | let new_branch_param = new_via 131 | .params 132 | .iter() 133 | .find(|p| matches!(p, rsip::Param::Branch(_))) 134 | .expect("New request should have a new branch parameter"); 135 | let new_branch_value = match new_branch_param { 136 | rsip::Param::Branch(b) => b.to_string(), 137 | _ => unreachable!(), 138 | }; 139 | assert_ne!( 140 | new_branch_value, original_branch_value, 141 | "New branch should be different from old branch" 142 | ); 143 | assert!( 144 | new_branch_value.starts_with("z9hG4bK"), 145 | "New branch should start with z9hG4bK" 146 | ); 147 | 148 | // Verify rport parameter is added 149 | let has_rport = new_via.params.iter().any( 150 | |p| matches!(p, rsip::Param::Other(key, _) if key.value().eq_ignore_ascii_case("rport")), 151 | ); 152 | assert!( 153 | has_rport, 154 | "Via header should have rport parameter after authentication" 155 | ); 156 | 157 | Ok(()) 158 | } 159 | -------------------------------------------------------------------------------- /src/transport/sip_addr.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use rsip::{host_with_port, HostWithPort, Transport}; 3 | use std::{fmt, hash::Hash, net::SocketAddr}; 4 | 5 | /// SIP Address 6 | /// 7 | /// `SipAddr` represents a SIP network address that combines a host/port 8 | /// with an optional transport protocol. It provides a unified way to 9 | /// handle SIP addressing across different transport types. 10 | /// 11 | /// # Fields 12 | /// 13 | /// * `r#type` - Optional transport protocol (UDP, TCP, TLS, WS, WSS) 14 | /// * `addr` - Host and port information 15 | /// 16 | /// # Transport Types 17 | /// 18 | /// * `UDP` - User Datagram Protocol (unreliable) 19 | /// * `TCP` - Transmission Control Protocol (reliable) 20 | /// * `TLS` - Transport Layer Security over TCP (reliable, encrypted) 21 | /// * `WS` - WebSocket (reliable) 22 | /// * `WSS` - WebSocket Secure (reliable, encrypted) 23 | /// 24 | /// # Examples 25 | /// 26 | /// ```rust 27 | /// use rsipstack::transport::SipAddr; 28 | /// use rsip::transport::Transport; 29 | /// use std::net::SocketAddr; 30 | /// 31 | /// // Create from socket address 32 | /// let socket_addr: SocketAddr = "192.168.1.100:5060".parse().unwrap(); 33 | /// let sip_addr = SipAddr::from(socket_addr); 34 | /// 35 | /// // Create with specific transport 36 | /// let sip_addr = SipAddr::new( 37 | /// Transport::Tcp, 38 | /// rsip::HostWithPort::try_from("example.com:5060").unwrap() 39 | /// ); 40 | /// 41 | /// // Convert to socket address (for IP addresses) 42 | /// if let Ok(socket_addr) = sip_addr.get_socketaddr() { 43 | /// println!("Socket address: {}", socket_addr); 44 | /// } 45 | /// ``` 46 | /// 47 | /// # Usage in SIP 48 | /// 49 | /// SipAddr is used throughout the stack for: 50 | /// * Via header processing 51 | /// * Contact header handling 52 | /// * Route and Record-Route processing 53 | /// * Transport layer addressing 54 | /// * Connection management 55 | /// 56 | /// # Conversion 57 | /// 58 | /// SipAddr can be converted to/from: 59 | /// * `SocketAddr` (for IP addresses only) 60 | /// * `rsip::Uri` (SIP URI format) 61 | /// * `rsip::HostWithPort` (host/port only) 62 | #[derive(Debug, Eq, PartialEq, Clone, Default)] 63 | pub struct SipAddr { 64 | pub r#type: Option, 65 | pub addr: HostWithPort, 66 | } 67 | 68 | impl fmt::Display for SipAddr { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | match self { 71 | SipAddr { 72 | r#type: Some(r#type), 73 | addr, 74 | } => write!(f, "{} {}", r#type, addr), 75 | SipAddr { r#type: None, addr } => write!(f, "{}", addr), 76 | } 77 | } 78 | } 79 | 80 | impl Hash for SipAddr { 81 | fn hash(&self, state: &mut H) { 82 | self.r#type.hash(state); 83 | match self.addr.host { 84 | host_with_port::Host::Domain(ref domain) => domain.hash(state), 85 | host_with_port::Host::IpAddr(ref ip_addr) => ip_addr.hash(state), 86 | } 87 | self.addr.port.map(|port| port.value().hash(state)); 88 | } 89 | } 90 | 91 | impl SipAddr { 92 | pub fn new(transport: rsip::transport::Transport, addr: HostWithPort) -> Self { 93 | SipAddr { 94 | r#type: Some(transport), 95 | addr, 96 | } 97 | } 98 | 99 | pub fn get_socketaddr(&self) -> Result { 100 | match &self.addr.host { 101 | host_with_port::Host::Domain(domain) => Err(crate::Error::Error(format!( 102 | "Cannot convert domain {} to SocketAddr", 103 | domain 104 | ))), 105 | host_with_port::Host::IpAddr(ip_addr) => { 106 | let port = self.addr.port.map_or(5060, |p| p.value().to_owned()); 107 | Ok(SocketAddr::new(ip_addr.to_owned(), port)) 108 | } 109 | } 110 | } 111 | } 112 | 113 | impl From for rsip::HostWithPort { 114 | fn from(val: SipAddr) -> Self { 115 | val.addr 116 | } 117 | } 118 | 119 | impl From for rsip::Uri { 120 | fn from(val: SipAddr) -> Self { 121 | Self::from(&val) 122 | } 123 | } 124 | 125 | impl From<&SipAddr> for rsip::Uri { 126 | fn from(addr: &SipAddr) -> Self { 127 | let params = match addr.r#type { 128 | Some(Transport::Tcp) => vec![rsip::Param::Transport(Transport::Tcp)], 129 | Some(Transport::Tls) => vec![rsip::Param::Transport(Transport::Tls)], 130 | Some(Transport::Ws) => vec![rsip::Param::Transport(Transport::Ws)], 131 | Some(Transport::Wss) => vec![rsip::Param::Transport(Transport::Wss)], 132 | Some(Transport::TlsSctp) => vec![rsip::Param::Transport(Transport::TlsSctp)], 133 | Some(Transport::Sctp) => vec![rsip::Param::Transport(Transport::Sctp)], 134 | _ => vec![], 135 | }; 136 | let scheme = match addr.r#type { 137 | Some(rsip::transport::Transport::Wss) 138 | | Some(rsip::transport::Transport::Tls) 139 | | Some(rsip::transport::Transport::TlsSctp) => rsip::Scheme::Sips, 140 | _ => rsip::Scheme::Sip, 141 | }; 142 | rsip::Uri { 143 | scheme: Some(scheme), 144 | host_with_port: addr.addr.clone(), 145 | params, 146 | ..Default::default() 147 | } 148 | } 149 | } 150 | 151 | impl From for SipAddr { 152 | fn from(addr: SocketAddr) -> Self { 153 | let host_with_port = HostWithPort { 154 | host: addr.ip().into(), 155 | port: Some(addr.port().into()), 156 | }; 157 | SipAddr { 158 | r#type: None, 159 | addr: host_with_port, 160 | } 161 | } 162 | } 163 | 164 | impl From for SipAddr { 165 | fn from(host_with_port: rsip::host_with_port::HostWithPort) -> Self { 166 | SipAddr { 167 | r#type: None, 168 | addr: host_with_port, 169 | } 170 | } 171 | } 172 | 173 | impl TryFrom<&rsip::Uri> for SipAddr { 174 | type Error = crate::Error; 175 | 176 | fn try_from(uri: &rsip::Uri) -> Result { 177 | let transport = uri.transport().cloned(); 178 | Ok(SipAddr { 179 | r#type: transport, 180 | addr: uri.host_with_port.clone(), 181 | }) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/transaction/tests/test_transaction_states.rs: -------------------------------------------------------------------------------- 1 | //! Transaction state transition tests 2 | //! 3 | //! This module contains comprehensive tests for transaction state transitions 4 | //! according to RFC 3261 Section 17. 5 | 6 | use super::create_test_endpoint; 7 | use crate::transaction::{ 8 | key::{TransactionKey, TransactionRole}, 9 | transaction::Transaction, 10 | TransactionState, TransactionType, 11 | }; 12 | use rsip::headers::*; 13 | 14 | /// Test helper to create a mock request 15 | fn create_test_request(method: rsip::Method, branch: &str) -> rsip::Request { 16 | rsip::Request { 17 | method, 18 | uri: rsip::Uri::try_from("sip:test.example.com:5060").unwrap(), 19 | headers: vec![ 20 | Via::new(&format!( 21 | "SIP/2.0/UDP test.example.com:5060;branch={}", 22 | branch 23 | )) 24 | .into(), 25 | CSeq::new(&format!("1 {}", method)).into(), 26 | From::new("Alice ;tag=1928301774").into(), 27 | To::new("Bob ").into(), 28 | CallId::new("a84b4c76e66710@pc33.atlanta.com").into(), 29 | MaxForwards::new("70").into(), 30 | ] 31 | .into(), 32 | version: rsip::Version::V2, 33 | body: Default::default(), 34 | } 35 | } 36 | 37 | #[tokio::test] 38 | async fn test_client_invite_transaction_creation() -> crate::Result<()> { 39 | let endpoint = create_test_endpoint(Some("127.0.0.1:0")).await?; 40 | 41 | // Create INVITE request 42 | let invite_req = create_test_request(rsip::Method::Invite, "z9hG4bKnashds"); 43 | let key = TransactionKey::from_request(&invite_req, TransactionRole::Client)?; 44 | 45 | let tx = Transaction::new_client( 46 | key.clone(), 47 | invite_req.clone(), 48 | endpoint.inner.clone(), 49 | None, // No connection needed for basic tests 50 | ); 51 | 52 | // Initial state should be Calling 53 | assert_eq!(tx.state, TransactionState::Nothing); 54 | assert_eq!(tx.transaction_type, TransactionType::ClientInvite); 55 | 56 | Ok(()) 57 | } 58 | 59 | #[tokio::test] 60 | async fn test_client_non_invite_transaction_creation() -> crate::Result<()> { 61 | let endpoint = create_test_endpoint(Some("127.0.0.1:0")).await?; 62 | 63 | // Create REGISTER request (non-INVITE) 64 | let register_req = create_test_request(rsip::Method::Register, "z9hG4bKnashds"); 65 | let key = TransactionKey::from_request(®ister_req, TransactionRole::Client)?; 66 | 67 | let tx = Transaction::new_client( 68 | key.clone(), 69 | register_req.clone(), 70 | endpoint.inner.clone(), 71 | None, 72 | ); 73 | 74 | // Initial state should be Calling 75 | assert_eq!(tx.state, TransactionState::Nothing); 76 | assert_eq!(tx.transaction_type, TransactionType::ClientNonInvite); 77 | 78 | Ok(()) 79 | } 80 | 81 | #[tokio::test] 82 | async fn test_server_invite_transaction_creation() -> crate::Result<()> { 83 | let endpoint = create_test_endpoint(Some("127.0.0.1:0")).await?; 84 | 85 | // Create INVITE request for server 86 | let invite_req = create_test_request(rsip::Method::Invite, "z9hG4bKnashds"); 87 | let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?; 88 | 89 | let tx = Transaction::new_server( 90 | key.clone(), 91 | invite_req.clone(), 92 | endpoint.inner.clone(), 93 | None, 94 | ); 95 | 96 | // Initial state should be Trying 97 | assert_eq!(tx.state, TransactionState::Trying); 98 | assert_eq!(tx.transaction_type, TransactionType::ServerInvite); 99 | 100 | Ok(()) 101 | } 102 | 103 | #[tokio::test] 104 | async fn test_server_non_invite_transaction_creation() -> crate::Result<()> { 105 | let endpoint = create_test_endpoint(Some("127.0.0.1:0")).await?; 106 | 107 | // Create REGISTER request for server 108 | let register_req = create_test_request(rsip::Method::Register, "z9hG4bKnashds"); 109 | let key = TransactionKey::from_request(®ister_req, TransactionRole::Server)?; 110 | 111 | let tx = Transaction::new_server( 112 | key.clone(), 113 | register_req.clone(), 114 | endpoint.inner.clone(), 115 | None, 116 | ); 117 | 118 | // Initial state should be Trying 119 | assert_eq!(tx.state, TransactionState::Trying); 120 | assert_eq!(tx.transaction_type, TransactionType::ServerNonInvite); 121 | 122 | Ok(()) 123 | } 124 | 125 | #[tokio::test] 126 | async fn test_transaction_key_generation() -> crate::Result<()> { 127 | // Test transaction key generation for different roles 128 | let invite_req = create_test_request(rsip::Method::Invite, "z9hG4bKnashds"); 129 | 130 | let client_key = TransactionKey::from_request(&invite_req, TransactionRole::Client)?; 131 | let server_key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?; 132 | 133 | // Keys should be different for different roles 134 | assert_ne!(client_key, server_key); 135 | 136 | // Same request and role should generate same key 137 | let client_key2 = TransactionKey::from_request(&invite_req, TransactionRole::Client)?; 138 | assert_eq!(client_key, client_key2); 139 | 140 | Ok(()) 141 | } 142 | 143 | #[tokio::test] 144 | async fn test_transaction_types() -> crate::Result<()> { 145 | let endpoint = create_test_endpoint(Some("127.0.0.1:0")).await?; 146 | 147 | // Test INVITE transaction type 148 | let invite_req = create_test_request(rsip::Method::Invite, "z9hG4bKnashds"); 149 | let invite_key = TransactionKey::from_request(&invite_req, TransactionRole::Client)?; 150 | let invite_tx = Transaction::new_client(invite_key, invite_req, endpoint.inner.clone(), None); 151 | assert_eq!(invite_tx.transaction_type, TransactionType::ClientInvite); 152 | 153 | // Test non-INVITE transaction type 154 | let register_req = create_test_request(rsip::Method::Register, "z9hG4bKnashds2"); 155 | let register_key = TransactionKey::from_request(®ister_req, TransactionRole::Client)?; 156 | let register_tx = 157 | Transaction::new_client(register_key, register_req, endpoint.inner.clone(), None); 158 | assert_eq!( 159 | register_tx.transaction_type, 160 | TransactionType::ClientNonInvite 161 | ); 162 | 163 | Ok(()) 164 | } 165 | -------------------------------------------------------------------------------- /src/transport/tests/test_via_received.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::SipConnection; 2 | use rsip::{headers::*, prelude::HeadersExt, SipMessage, Transport}; 3 | use std::net::SocketAddr; 4 | 5 | /// Test Via received parameter handling for different transport protocols 6 | #[test] 7 | fn test_via_received_udp() { 8 | let register_req = create_test_request("SIP/2.0/UDP"); 9 | let addr: SocketAddr = "192.168.1.100:5060".parse().unwrap(); 10 | 11 | let msg = SipConnection::update_msg_received(register_req.into(), addr, Transport::Udp) 12 | .expect("update_msg_received for UDP"); 13 | 14 | match msg { 15 | SipMessage::Request(req) => { 16 | let via_header = req.via_header().expect("via header"); 17 | let typed_via = via_header.typed().expect("typed via"); 18 | 19 | // UDP should always add received parameter 20 | assert!( 21 | typed_via 22 | .params 23 | .iter() 24 | .any(|p| matches!(p, rsip::Param::Received(_))), 25 | "UDP should add received parameter" 26 | ); 27 | assert!( 28 | typed_via.params.iter().any(|p| matches!( 29 | p, rsip::Param::Other(key, Some(_)) if key.value().eq_ignore_ascii_case("rport") 30 | )), 31 | "UDP should add rport parameter" 32 | ); 33 | } 34 | _ => panic!("Expected request message"), 35 | } 36 | } 37 | 38 | #[test] 39 | fn test_via_received_tcp() { 40 | let register_req = create_test_request("SIP/2.0/TCP"); 41 | let addr: SocketAddr = "127.0.0.1:5060".parse().unwrap(); // Same as Via header 42 | 43 | let msg = SipConnection::update_msg_received(register_req.into(), addr, Transport::Tcp) 44 | .expect("update_msg_received for TCP"); 45 | 46 | match msg { 47 | SipMessage::Request(req) => { 48 | let via_header = req.via_header().expect("via header"); 49 | let typed_via = via_header.typed().expect("typed via"); 50 | 51 | // TCP should not add received parameter if source matches Via 52 | assert!( 53 | !typed_via 54 | .params 55 | .iter() 56 | .any(|p| matches!(p, rsip::Param::Received(_))), 57 | "TCP should not add received parameter when addresses match" 58 | ); 59 | } 60 | _ => panic!("Expected request message"), 61 | } 62 | } 63 | 64 | #[test] 65 | fn test_via_received_tcp_different_addr() { 66 | let register_req = create_test_request("SIP/2.0/TCP"); 67 | let addr: SocketAddr = "192.168.1.100:5060".parse().unwrap(); // Different from Via header 68 | 69 | let msg = SipConnection::update_msg_received(register_req.into(), addr, Transport::Tcp) 70 | .expect("update_msg_received for TCP"); 71 | 72 | match msg { 73 | SipMessage::Request(req) => { 74 | let via_header = req.via_header().expect("via header"); 75 | let typed_via = via_header.typed().expect("typed via"); 76 | 77 | // TCP should add received parameter if source differs from Via 78 | assert!( 79 | typed_via 80 | .params 81 | .iter() 82 | .any(|p| matches!(p, rsip::Param::Received(_))), 83 | "TCP should add received parameter when addresses differ" 84 | ); 85 | } 86 | _ => panic!("Expected request message"), 87 | } 88 | } 89 | 90 | #[test] 91 | fn test_via_received_tls() { 92 | let register_req = create_test_request("SIP/2.0/TLS"); 93 | let addr: SocketAddr = "192.168.1.100:5061".parse().unwrap(); 94 | 95 | let msg = SipConnection::update_msg_received(register_req.into(), addr, Transport::Tls) 96 | .expect("update_msg_received for TLS"); 97 | 98 | match msg { 99 | SipMessage::Request(req) => { 100 | let via_header = req.via_header().expect("via header"); 101 | let typed_via = via_header.typed().expect("typed via"); 102 | 103 | // TLS should add received parameter only if host differs 104 | assert!( 105 | typed_via 106 | .params 107 | .iter() 108 | .any(|p| matches!(p, rsip::Param::Received(_))), 109 | "TLS should add received parameter when host differs" 110 | ); 111 | } 112 | _ => panic!("Expected request message"), 113 | } 114 | } 115 | 116 | #[test] 117 | fn test_via_received_ws() { 118 | let register_req = create_test_request("SIP/2.0/WS"); 119 | let addr: SocketAddr = "192.168.1.100:80".parse().unwrap(); 120 | 121 | let msg = SipConnection::update_msg_received(register_req.into(), addr, Transport::Ws) 122 | .expect("update_msg_received for WS"); 123 | 124 | match msg { 125 | SipMessage::Request(req) => { 126 | let via_header = req.via_header().expect("via header"); 127 | let typed_via = via_header.typed().expect("typed via"); 128 | 129 | // WS should handle received parameter like other connection-oriented protocols 130 | assert!( 131 | typed_via 132 | .params 133 | .iter() 134 | .any(|p| matches!(p, rsip::Param::Received(_))), 135 | "WS should add received parameter when host differs" 136 | ); 137 | } 138 | _ => panic!("Expected request message"), 139 | } 140 | } 141 | 142 | #[test] 143 | fn test_via_response_not_modified() { 144 | let response = rsip::message::Response { 145 | status_code: rsip::StatusCode::try_from(200).unwrap(), 146 | headers: vec![Via::new("SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK-test").into()].into(), 147 | version: rsip::Version::V2, 148 | body: Default::default(), 149 | }; 150 | 151 | let addr: SocketAddr = "192.168.1.100:5060".parse().unwrap(); 152 | 153 | let msg = SipConnection::update_msg_received(response.into(), addr, Transport::Udp) 154 | .expect("update_msg_received for response"); 155 | 156 | // Response messages should not be modified 157 | match msg { 158 | SipMessage::Response(_) => { 159 | // This is expected - responses are not modified 160 | } 161 | _ => panic!("Expected response message"), 162 | } 163 | } 164 | 165 | fn create_test_request(via_proto: &str) -> rsip::message::Request { 166 | rsip::message::Request { 167 | method: rsip::method::Method::Register, 168 | uri: rsip::Uri { 169 | scheme: Some(rsip::Scheme::Sip), 170 | host_with_port: rsip::HostWithPort::try_from("example.com:5060") 171 | .expect("host_port parse"), 172 | ..Default::default() 173 | }, 174 | headers: vec![ 175 | Via::new(&format!("{} 127.0.0.1:5060;branch=z9hG4bK-test", via_proto)).into(), 176 | ] 177 | .into(), 178 | version: rsip::Version::V2, 179 | body: Default::default(), 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/transaction/timer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeMap, HashMap}, 3 | sync::{ 4 | atomic::{AtomicU64, Ordering}, 5 | Condvar, Mutex, MutexGuard, 6 | }, 7 | time::{Duration, Instant}, 8 | }; 9 | 10 | use tokio::sync::Notify; 11 | 12 | #[derive(Debug, PartialEq, Eq, Clone)] 13 | struct TimerKey { 14 | task_id: u64, 15 | execute_at: Instant, 16 | } 17 | 18 | impl Ord for TimerKey { 19 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 20 | self.execute_at 21 | .cmp(&other.execute_at) 22 | .then_with(|| self.task_id.cmp(&other.task_id)) 23 | } 24 | } 25 | 26 | impl PartialOrd for TimerKey { 27 | fn partial_cmp(&self, other: &Self) -> Option { 28 | Some(self.cmp(other)) 29 | } 30 | } 31 | 32 | pub struct Timer { 33 | state: Mutex>, 34 | condvar: Condvar, 35 | notify: Notify, 36 | last_task_id: AtomicU64, 37 | } 38 | 39 | impl Timer { 40 | pub fn new() -> Self { 41 | Timer { 42 | state: Mutex::new(TimerState::new()), 43 | condvar: Condvar::new(), 44 | notify: Notify::new(), 45 | last_task_id: AtomicU64::new(1), 46 | } 47 | } 48 | 49 | pub fn len(&self) -> usize { 50 | self.lock_state().tasks.len() 51 | } 52 | 53 | pub fn timeout(&self, duration: Duration, value: T) -> u64 { 54 | self.timeout_at(Instant::now() + duration, value) 55 | } 56 | 57 | pub fn timeout_at(&self, execute_at: Instant, value: T) -> u64 { 58 | let task_id = self.last_task_id.fetch_add(1, Ordering::Relaxed); 59 | let mut state = self.lock_state(); 60 | let key = TimerKey { 61 | task_id, 62 | execute_at, 63 | }; 64 | let should_notify = match state.tasks.keys().next() { 65 | Some(head) => key < head.clone(), 66 | None => true, 67 | }; 68 | 69 | state.tasks.insert(key.clone(), value); 70 | state.id_to_tasks.insert(task_id, execute_at); 71 | drop(state); 72 | 73 | if should_notify { 74 | self.condvar.notify_all(); 75 | self.notify.notify_waiters(); 76 | } else { 77 | self.condvar.notify_one(); 78 | self.notify.notify_one(); 79 | } 80 | task_id 81 | } 82 | 83 | pub fn cancel(&self, task_id: u64) -> Option { 84 | let mut state = self.lock_state(); 85 | let execute_at = match state.id_to_tasks.remove(&task_id) { 86 | Some(execute_at) => execute_at, 87 | None => return None, 88 | }; 89 | 90 | let key = TimerKey { 91 | task_id, 92 | execute_at, 93 | }; 94 | 95 | let was_head = state 96 | .tasks 97 | .iter() 98 | .next() 99 | .map(|(head, _)| head == &key) 100 | .unwrap_or(false); 101 | 102 | let removed = state.tasks.remove(&key); 103 | drop(state); 104 | 105 | if removed.is_some() { 106 | if was_head { 107 | self.condvar.notify_all(); 108 | self.notify.notify_waiters(); 109 | } else { 110 | self.condvar.notify_one(); 111 | self.notify.notify_one(); 112 | } 113 | } 114 | 115 | removed 116 | } 117 | 118 | pub fn poll(&self, now: Instant) -> Vec { 119 | let mut state = self.lock_state(); 120 | Self::collect_ready(&mut state, now) 121 | } 122 | 123 | pub async fn wait_for_ready(&self) -> Vec { 124 | loop { 125 | let (ready, next_deadline) = { 126 | let mut state = self.lock_state(); 127 | let now = Instant::now(); 128 | let ready = Self::collect_ready(&mut state, now); 129 | let next_deadline = if ready.is_empty() { 130 | state.tasks.keys().next().map(|key| key.execute_at) 131 | } else { 132 | None 133 | }; 134 | (ready, next_deadline) 135 | }; 136 | 137 | if !ready.is_empty() { 138 | return ready; 139 | } 140 | 141 | match next_deadline { 142 | Some(deadline) => { 143 | let now = Instant::now(); 144 | let wait_duration = deadline.checked_duration_since(now).unwrap_or_default(); 145 | 146 | tokio::select! { 147 | _ = tokio::time::sleep(wait_duration) => {}, 148 | _ = self.notify.notified() => {}, 149 | } 150 | } 151 | None => { 152 | self.notify.notified().await; 153 | } 154 | } 155 | } 156 | } 157 | 158 | pub fn next_deadline(&self) -> Option { 159 | self.lock_state() 160 | .tasks 161 | .iter() 162 | .next() 163 | .map(|(key, _)| key.execute_at) 164 | } 165 | } 166 | 167 | #[test] 168 | fn test_timer() { 169 | use std::time::Duration; 170 | let timer = Timer::new(); 171 | let now = Instant::now(); 172 | let task_id = timer.timeout_at(now, "task1"); 173 | assert_eq!(task_id, 1); 174 | assert_eq!(timer.cancel(task_id), Some("task1")); 175 | assert_eq!(timer.cancel(task_id), None); 176 | 177 | timer.timeout_at(now, "task2"); 178 | let must_hass_task_2 = timer.poll(now + Duration::from_secs(1)); 179 | assert_eq!(must_hass_task_2.len(), 1); 180 | 181 | timer.timeout_at(now + Duration::from_millis(1001), "task3"); 182 | let non_tasks = timer.poll(now + Duration::from_secs(1)); 183 | assert_eq!(non_tasks.len(), 0); 184 | assert_eq!(timer.len(), 1); 185 | } 186 | 187 | #[tokio::test] 188 | async fn wait_for_ready_async_returns_ready() { 189 | let timer = Timer::new(); 190 | timer.timeout(Duration::from_millis(50), "ready"); 191 | 192 | let ready = tokio::time::timeout(Duration::from_secs(1), timer.wait_for_ready()) 193 | .await 194 | .expect("wait_for_ready_async timed out"); 195 | assert_eq!(ready, vec!["ready"]); 196 | } 197 | 198 | #[tokio::test] 199 | async fn wait_for_ready_async_wakes_on_new_timer() { 200 | use std::sync::Arc; 201 | 202 | let timer = Arc::new(Timer::new()); 203 | timer.timeout(Duration::from_secs(5), "late"); 204 | 205 | let worker = Arc::clone(&timer); 206 | let wait_handle = tokio::spawn(async move { worker.wait_for_ready().await }); 207 | 208 | tokio::time::sleep(Duration::from_millis(100)).await; 209 | timer.timeout(Duration::from_millis(200), "early"); 210 | 211 | let ready = tokio::time::timeout(Duration::from_secs(2), wait_handle) 212 | .await 213 | .expect("wait_for_ready_async task timed out") 214 | .expect("wait_for_ready_async task panicked"); 215 | 216 | assert_eq!(ready, vec!["early"]); 217 | } 218 | 219 | impl Timer { 220 | fn lock_state(&self) -> MutexGuard<'_, TimerState> { 221 | match self.state.lock() { 222 | Ok(guard) => guard, 223 | Err(poisoned) => poisoned.into_inner(), 224 | } 225 | } 226 | 227 | fn collect_ready(state: &mut TimerState, now: Instant) -> Vec { 228 | let mut ready = Vec::new(); 229 | 230 | while let Some(key) = state.tasks.keys().next().cloned() { 231 | if key.execute_at > now { 232 | break; 233 | } 234 | 235 | if let Some(value) = state.tasks.remove(&key) { 236 | state.id_to_tasks.remove(&key.task_id); 237 | ready.push(value); 238 | } 239 | } 240 | 241 | ready 242 | } 243 | } 244 | 245 | struct TimerState { 246 | tasks: BTreeMap, 247 | id_to_tasks: HashMap, 248 | } 249 | 250 | impl TimerState { 251 | fn new() -> Self { 252 | Self { 253 | tasks: BTreeMap::new(), 254 | id_to_tasks: HashMap::new(), 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/transaction/tests/test_client.rs: -------------------------------------------------------------------------------- 1 | use crate::rsip_ext::RsipResponseExt; 2 | use crate::transaction::key::{TransactionKey, TransactionRole}; 3 | use crate::transaction::transaction::Transaction; 4 | use crate::transport::udp::UdpConnection; 5 | use crate::transport::SipAddr; 6 | use crate::{transport::TransportEvent, Result}; 7 | use rsip::{headers::*, Header, Response, SipMessage, Uri}; 8 | use std::convert::TryFrom; 9 | use std::time::Duration; 10 | use tokio::{select, sync::mpsc::unbounded_channel, time::sleep}; 11 | use tracing::info; 12 | 13 | #[tokio::test] 14 | async fn test_client_transaction() -> Result<()> { 15 | let endpoint = super::create_test_endpoint(Some("127.0.0.1:0")).await?; 16 | let server_addr = endpoint 17 | .get_addrs() 18 | .get(0) 19 | .expect("must has connection") 20 | .to_owned(); 21 | info!("server addr: {}", server_addr); 22 | 23 | let peer_server = UdpConnection::create_connection("127.0.0.1:0".parse()?, None, None).await?; 24 | let peer_server_loop = async { 25 | let (sender, mut recevier) = unbounded_channel(); 26 | select! { 27 | _ = async { 28 | if let Some(event) = recevier.recv().await { 29 | match event { 30 | TransportEvent::Incoming(msg, connection, _) => { 31 | info!("recv request: {}", msg); 32 | assert!(msg.is_request()); 33 | match msg { 34 | SipMessage::Request(req) => { 35 | let headers = req.headers.clone(); 36 | let response = SipMessage::Response(rsip::message::Response { 37 | version: rsip::Version::V2, 38 | status_code:rsip::StatusCode::Trying, 39 | headers: headers.clone(), 40 | body: Default::default(), 41 | }); 42 | connection.send(response, None).await.expect("send trying"); 43 | sleep(Duration::from_millis(100)).await; 44 | 45 | let response = SipMessage::Response(rsip::message::Response { 46 | version: rsip::Version::V2, 47 | status_code:rsip::StatusCode::OK, 48 | headers, 49 | body: Default::default(), 50 | }); 51 | connection.send(response, None).await.expect("send Ok"); 52 | sleep(Duration::from_secs(1)).await; 53 | } 54 | _ => { 55 | assert!(false, "must not reach here"); 56 | } 57 | } 58 | } 59 | _ => {} 60 | } 61 | } else { 62 | assert!(false, "must not reach here"); 63 | } 64 | } => {} 65 | _ = peer_server.serve_loop(sender) => { 66 | assert!(false, "must not reach here"); 67 | } 68 | } 69 | }; 70 | 71 | let recv_loop = async { 72 | let register_req = rsip::message::Request { 73 | method: rsip::method::Method::Register, 74 | uri: rsip::Uri { 75 | scheme: Some(rsip::Scheme::Sip), 76 | host_with_port: peer_server.get_addr().addr.clone(), 77 | ..Default::default() 78 | }, 79 | headers: vec![ 80 | Via::new("SIP/2.0/TLS restsend.com:5061;branch=z9hG4bKnashd92").into(), 81 | CSeq::new("1 REGISTER").into(), 82 | From::new("Bob ;tag=ja743ks76zlflH").into(), 83 | CallId::new("1j9FpLxk3uxtm8tn@restsend.com").into(), 84 | ] 85 | .into(), 86 | version: rsip::Version::V2, 87 | body: Default::default(), 88 | }; 89 | 90 | let key = TransactionKey::from_request(®ister_req, TransactionRole::Client) 91 | .expect("client_transaction"); 92 | let mut tx = Transaction::new_client(key, register_req, endpoint.inner.clone(), None); 93 | tx.send().await.expect("send request"); 94 | 95 | while let Some(resp) = tx.receive().await { 96 | info!("Received response: {:?}", resp); 97 | } 98 | }; 99 | 100 | select! { 101 | _ = recv_loop => {} 102 | _ = peer_server_loop => { 103 | assert!(false, "must not reach here"); 104 | } 105 | _ = endpoint.serve() => { 106 | assert!(false, "must not reach here"); 107 | } 108 | _ = sleep(Duration::from_secs(1)) => { 109 | assert!(false, "timeout waiting"); 110 | } 111 | } 112 | Ok(()) 113 | } 114 | 115 | #[tokio::test] 116 | async fn test_make_ack_uses_contact_and_reversed_route_order() -> Result<()> { 117 | let endpoint = super::create_test_endpoint(None).await?; 118 | 119 | let raw_response = "SIP/2.0 200 OK\r\n\ 120 | Via: SIP/2.0/TCP uac.example.com:5060;branch=z9hG4bK1\r\n\ 121 | Record-Route: \r\n\ 122 | Record-Route: \r\n\ 123 | From: ;tag=from-tag\r\n\ 124 | To: ;tag=to-tag\r\n\ 125 | Call-ID: callid@example.com\r\n\ 126 | CSeq: 1 INVITE\r\n\ 127 | Contact: \r\n\ 128 | Content-Length: 0\r\n\r\n"; 129 | 130 | let response = Response::try_from(raw_response)?; 131 | let request_uri = response.remote_uri(None)?; 132 | let ack = endpoint.inner.make_ack(&response, request_uri)?; 133 | 134 | let expected_uri = Uri::try_from("sip:uas@192.0.2.55:5080;transport=tcp")?; 135 | assert_eq!(ack.uri, expected_uri, "ACK must target the remote Contact"); 136 | 137 | // Check Content-Length 138 | let content_length: String = ack 139 | .headers 140 | .iter() 141 | .filter_map(|header| match header { 142 | Header::ContentLength(content_length) => Some(content_length.value().to_string()), 143 | _ => None, 144 | }) 145 | .next() 146 | .expect("ACK must include a Content-Length header"); 147 | 148 | assert_eq!(content_length, "0", "Content-Length of ACK must be 0"); 149 | let routes: Vec = ack 150 | .headers 151 | .iter() 152 | .filter_map(|header| match header { 153 | Header::Route(route) => Some(route.value().to_string()), 154 | _ => None, 155 | }) 156 | .collect(); 157 | 158 | assert_eq!( 159 | routes, 160 | vec![ 161 | "".to_string(), 162 | "".to_string() 163 | ], 164 | "ACK Route headers must follow the reversed Record-Route order" 165 | ); 166 | 167 | Ok(()) 168 | } 169 | 170 | #[tokio::test] 171 | async fn test_make_ack_uses_contact_with_ob() -> Result<()> { 172 | let endpoint = super::create_test_endpoint(None).await?; 173 | 174 | let raw_response = "SIP/2.0 200 OK\r\n\ 175 | Via: SIP/2.0/TCP uac.example.com:5060;branch=z9hG4bK1;rport=15060;received=1.2.3.4;\r\n\ 176 | From: ;tag=from-tag\r\n\ 177 | To: ;tag=to-tag\r\n\ 178 | Call-ID: callid@example.com\r\n\ 179 | CSeq: 1 INVITE\r\n\ 180 | Contact: \r\n\ 181 | Content-Length: 0\r\n\r\n"; 182 | 183 | let response = Response::try_from(raw_response)?; 184 | let dest = SipAddr { 185 | r#type: Some(rsip::transport::Transport::Tcp), 186 | addr: "1.2.3.4:15060".try_into()?, 187 | }; 188 | let request_uri = dest.try_into().expect("to uri failed"); 189 | let ack = endpoint.inner.make_ack(&response, request_uri)?; 190 | let expected_uri = Uri::try_from("sip:1.2.3.4:15060;transport=tcp")?; 191 | assert_eq!(ack.uri, expected_uri, "ACK must target the remote Contact"); 192 | Ok(()) 193 | } 194 | -------------------------------------------------------------------------------- /src/transport/udp.rs: -------------------------------------------------------------------------------- 1 | use super::{connection::TransportSender, SipAddr, SipConnection}; 2 | use crate::{ 3 | transport::{ 4 | connection::{KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE, MAX_UDP_BUF_SIZE}, 5 | TransportEvent, 6 | }, 7 | Result, 8 | }; 9 | use std::{net::SocketAddr, sync::Arc}; 10 | use tokio::net::UdpSocket; 11 | use tokio_util::sync::CancellationToken; 12 | use tracing::{debug, info, warn}; 13 | pub struct UdpInner { 14 | pub conn: UdpSocket, 15 | pub addr: SipAddr, 16 | } 17 | 18 | #[derive(Clone)] 19 | pub struct UdpConnection { 20 | pub external: Option, 21 | cancel_token: Option, 22 | inner: Arc, 23 | } 24 | 25 | impl UdpConnection { 26 | pub async fn attach( 27 | inner: UdpInner, 28 | external: Option, 29 | cancel_token: Option, 30 | ) -> Self { 31 | UdpConnection { 32 | external: external.map(|addr| SipAddr { 33 | r#type: Some(rsip::transport::Transport::Udp), 34 | addr: SipConnection::resolve_bind_address(addr).into(), 35 | }), 36 | inner: Arc::new(inner), 37 | cancel_token, 38 | } 39 | } 40 | 41 | pub async fn create_connection( 42 | local: SocketAddr, 43 | external: Option, 44 | cancel_token: Option, 45 | ) -> Result { 46 | let conn = UdpSocket::bind(local).await?; 47 | 48 | let addr = SipAddr { 49 | r#type: Some(rsip::transport::Transport::Udp), 50 | addr: SipConnection::resolve_bind_address(conn.local_addr()?).into(), 51 | }; 52 | 53 | let t = UdpConnection { 54 | external: external.map(|addr| SipAddr { 55 | r#type: Some(rsip::transport::Transport::Udp), 56 | addr: addr.into(), 57 | }), 58 | inner: Arc::new(UdpInner { addr, conn }), 59 | cancel_token, 60 | }; 61 | info!("created UDP connection: {} external: {:?}", t, external); 62 | Ok(t) 63 | } 64 | 65 | pub async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 66 | let mut buf = vec![0u8; MAX_UDP_BUF_SIZE]; 67 | loop { 68 | let (len, addr) = tokio::select! { 69 | // Check for cancellation on each iteration 70 | _ = async { 71 | if let Some(ref cancel_token) = self.cancel_token { 72 | cancel_token.cancelled().await; 73 | } else { 74 | // If no cancel token, wait forever 75 | std::future::pending::<()>().await; 76 | } 77 | } => { 78 | debug!("UDP serve_loop cancelled"); 79 | return Ok(()); 80 | } 81 | // Receive UDP packets 82 | result = self.inner.conn.recv_from(&mut buf) => { 83 | match result { 84 | Ok((len, addr)) => (len, addr), 85 | Err(e) => { 86 | warn!("error receiving UDP packet: {}", e); 87 | continue; 88 | } 89 | } 90 | } 91 | }; 92 | 93 | match &buf[..len] { 94 | KEEPALIVE_REQUEST => { 95 | self.inner.conn.send_to(KEEPALIVE_RESPONSE, addr).await.ok(); 96 | continue; 97 | } 98 | KEEPALIVE_RESPONSE => continue, 99 | _ => { 100 | if buf.iter().all(|&b| b.is_ascii_whitespace()) { 101 | continue; 102 | } 103 | } 104 | } 105 | 106 | let undecoded = match std::str::from_utf8(&buf[..len]) { 107 | Ok(s) => s, 108 | Err(e) => { 109 | info!( 110 | "decoding text from: {} error: {} buf: {:?}", 111 | addr, 112 | e, 113 | &buf[..len] 114 | ); 115 | continue; 116 | } 117 | }; 118 | 119 | let msg = match rsip::SipMessage::try_from(undecoded) { 120 | Ok(msg) => msg, 121 | Err(e) => { 122 | info!( 123 | "error parsing SIP message from: {} error: {} buf: {}", 124 | addr, e, undecoded 125 | ); 126 | continue; 127 | } 128 | }; 129 | 130 | let msg = match SipConnection::update_msg_received( 131 | msg, 132 | addr, 133 | rsip::transport::Transport::Udp, 134 | ) { 135 | Ok(msg) => msg, 136 | Err(e) => { 137 | info!( 138 | "error updating SIP via from: {} error: {:?} buf: {}", 139 | addr, e, undecoded 140 | ); 141 | continue; 142 | } 143 | }; 144 | 145 | debug!( 146 | len, src=%addr,dest=%self.get_addr(), message=undecoded, 147 | "udp received" 148 | ); 149 | 150 | sender.send(TransportEvent::Incoming( 151 | msg, 152 | SipConnection::Udp(self.clone()), 153 | SipAddr { 154 | r#type: Some(rsip::transport::Transport::Udp), 155 | addr: addr.into(), 156 | }, 157 | ))?; 158 | } 159 | } 160 | 161 | pub async fn send( 162 | &self, 163 | msg: rsip::SipMessage, 164 | destination: Option<&SipAddr>, 165 | ) -> crate::Result<()> { 166 | let destination = match destination { 167 | Some(addr) => addr.get_socketaddr(), 168 | None => SipConnection::get_destination(&msg), 169 | }?; 170 | let buf = msg.to_string(); 171 | 172 | debug!(len=buf.len(), src=%self.get_addr(), 173 | dest=%destination, message=%buf, 174 | "udp send"); 175 | 176 | self.inner 177 | .conn 178 | .send_to(buf.as_bytes(), destination) 179 | .await 180 | .map_err(|e| { 181 | crate::Error::TransportLayerError(e.to_string(), self.get_addr().to_owned()) 182 | }) 183 | .map(|_| ()) 184 | } 185 | 186 | pub async fn send_raw(&self, buf: &[u8], destination: &SipAddr) -> Result<()> { 187 | //trace!("send_raw {} -> {}", buf.len(), target); 188 | self.inner 189 | .conn 190 | .send_to(buf, destination.get_socketaddr()?) 191 | .await 192 | .map_err(|e| { 193 | crate::Error::TransportLayerError(e.to_string(), self.get_addr().to_owned()) 194 | }) 195 | .map(|_| ()) 196 | } 197 | 198 | pub async fn recv_raw(&self, buf: &mut [u8]) -> Result<(usize, SipAddr)> { 199 | let (len, addr) = self.inner.conn.recv_from(buf).await?; 200 | // trace!("received {} -> {}", len, addr); 201 | Ok(( 202 | len, 203 | SipAddr { 204 | r#type: Some(rsip::transport::Transport::Udp), 205 | addr: addr.into(), 206 | }, 207 | )) 208 | } 209 | 210 | pub fn get_addr(&self) -> &SipAddr { 211 | if let Some(external) = &self.external { 212 | external 213 | } else { 214 | &self.inner.addr 215 | } 216 | } 217 | pub fn cancel_token(&self) -> Option { 218 | self.cancel_token.clone() 219 | } 220 | } 221 | 222 | impl std::fmt::Display for UdpConnection { 223 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 224 | match self.inner.conn.local_addr() { 225 | Ok(addr) => write!(f, "{}", addr), 226 | Err(_) => write!(f, "*:*"), 227 | } 228 | } 229 | } 230 | 231 | impl std::fmt::Debug for UdpConnection { 232 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 233 | write!(f, "{}", self.inner.addr) 234 | } 235 | } 236 | 237 | impl Drop for UdpInner { 238 | fn drop(&mut self) { 239 | info!("dropping UDP transport: {}", self.addr); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // A SIP stack in Rust 2 | 3 | //! # RSIPStack - A SIP Stack Implementation in Rust 4 | //! 5 | //! RSIPStack is a comprehensive Session Initiation Protocol (SIP) implementation 6 | //! written in Rust. It provides a complete SIP stack with support for multiple 7 | //! transport protocols, transaction management, dialog handling, and more. 8 | //! 9 | //! ## Features 10 | //! 11 | //! * **Complete SIP Implementation** - Full RFC 3261 compliance 12 | //! * **Multiple Transports** - UDP, TCP, TLS, WebSocket support 13 | //! * **Transaction Layer** - Automatic retransmissions and timer management 14 | //! * **Dialog Management** - Full dialog state machine implementation 15 | //! * **Async/Await Support** - Built on Tokio for high performance 16 | //! * **Type Safety** - Leverages Rust's type system for protocol correctness 17 | //! * **Extensible** - Modular design for easy customization 18 | //! 19 | //! ## Architecture 20 | //! 21 | //! The stack is organized into several layers following the SIP specification: 22 | //! 23 | //! ```text 24 | //! ┌─────────────────────────────────────┐ 25 | //! │ Application Layer │ 26 | //! ├─────────────────────────────────────┤ 27 | //! │ Dialog Layer │ 28 | //! ├─────────────────────────────────────┤ 29 | //! │ Transaction Layer │ 30 | //! ├─────────────────────────────────────┤ 31 | //! │ Transport Layer │ 32 | //! └─────────────────────────────────────┘ 33 | //! ``` 34 | //! 35 | //! ## Quick Start 36 | //! 37 | //! ### Creating a SIP Endpoint 38 | //! 39 | //! ```rust,no_run 40 | //! use rsipstack::EndpointBuilder; 41 | //! use tokio_util::sync::CancellationToken; 42 | //! 43 | //! #[tokio::main] 44 | //! async fn main() -> Result<(), Box> { 45 | //! // Create a SIP endpoint 46 | //! let endpoint = EndpointBuilder::new() 47 | //! .with_user_agent("MyApp/1.0") 48 | //! .build(); 49 | //! 50 | //! // Get incoming transactions 51 | //! let mut incoming = endpoint.incoming_transactions().expect("incoming_transactions"); 52 | //! 53 | //! // Start the endpoint (in production, you'd run this in a separate task) 54 | //! // let endpoint_inner = endpoint.inner.clone(); 55 | //! // tokio::spawn(async move { 56 | //! // endpoint_inner.serve().await.ok(); 57 | //! // }); 58 | //! 59 | //! // Process incoming requests 60 | //! while let Some(transaction) = incoming.recv().await { 61 | //! // Handle the transaction 62 | //! println!("Received: {}", transaction.original.method); 63 | //! break; // Exit for example 64 | //! } 65 | //! 66 | //! Ok(()) 67 | //! } 68 | //! ``` 69 | //! 70 | //! ### Sending SIP Requests 71 | //! 72 | //! ```rust,no_run 73 | //! use rsipstack::dialog::dialog_layer::DialogLayer; 74 | //! use rsipstack::dialog::invitation::InviteOption; 75 | //! use rsipstack::transaction::endpoint::EndpointInner; 76 | //! use std::sync::Arc; 77 | //! 78 | //! # async fn example() -> rsipstack::Result<()> { 79 | //! # let endpoint: Arc = todo!(); 80 | //! # let state_sender = todo!(); 81 | //! # let sdp_body = vec![]; 82 | //! // Create a dialog layer 83 | //! let dialog_layer = DialogLayer::new(endpoint.clone()); 84 | //! 85 | //! // Send an INVITE 86 | //! let invite_option = InviteOption { 87 | //! caller: rsip::Uri::try_from("sip:alice@example.com")?, 88 | //! callee: rsip::Uri::try_from("sip:bob@example.com")?, 89 | //! contact: rsip::Uri::try_from("sip:alice@myhost.com:5060")?, 90 | //! content_type: Some("application/sdp".to_string()), 91 | //! offer: Some(sdp_body), 92 | //! ..Default::default() 93 | //! }; 94 | //! 95 | //! let (dialog, response) = dialog_layer.do_invite(invite_option, state_sender).await?; 96 | //! # Ok(()) 97 | //! # } 98 | //! ``` 99 | //! 100 | //! ## Core Components 101 | //! 102 | //! ### Transport Layer 103 | //! 104 | //! The transport layer handles network communication across different protocols: 105 | //! 106 | //! * [`SipConnection`](transport::SipConnection) - Abstraction over transport protocols 107 | //! * [`SipAddr`](transport::SipAddr) - SIP addressing with transport information 108 | //! * [`TransportLayer`](transport::TransportLayer) - Transport management 109 | //! 110 | //! ### Transaction Layer 111 | //! 112 | //! The transaction layer provides reliable message delivery: 113 | //! 114 | //! * [`Transaction`](transaction::transaction::Transaction) - SIP transaction implementation 115 | //! * [`Endpoint`](transaction::Endpoint) - SIP endpoint for transaction management 116 | //! * [`TransactionState`](transaction::TransactionState) - Transaction state machine 117 | //! 118 | //! ### Dialog Layer 119 | //! 120 | //! The dialog layer manages SIP dialogs and sessions: 121 | //! 122 | //! * [`Dialog`](dialog::dialog::Dialog) - SIP dialog representation 123 | //! * [`DialogId`](dialog::DialogId) - Dialog identification 124 | //! * [`DialogState`](dialog::dialog::DialogState) - Dialog state management 125 | //! 126 | //! ## Error Handling 127 | //! 128 | //! The stack uses a comprehensive error type that covers all layers: 129 | //! 130 | //! ```rust 131 | //! use rsipstack::{Result, Error}; 132 | //! 133 | //! fn handle_sip_error(error: Error) { 134 | //! match error { 135 | //! Error::TransportLayerError(msg, addr) => { 136 | //! eprintln!("Transport error at {msg}: {addr}"); 137 | //! }, 138 | //! Error::TransactionError(msg, key) => { 139 | //! eprintln!("Transaction error {msg}: {key}"); 140 | //! }, 141 | //! Error::DialogError(msg, id, code) => { 142 | //! eprintln!("Dialog error {msg}: {id} (Status code: {code})"); 143 | //! }, 144 | //! _ => eprintln!("Other error: {}", error), 145 | //! } 146 | //! } 147 | //! ``` 148 | //! 149 | //! ## Configuration 150 | //! 151 | //! The stack can be configured for different use cases: 152 | //! 153 | //! ### Basic UDP Server 154 | //! 155 | //! ```rust,no_run 156 | //! use rsipstack::EndpointBuilder; 157 | //! use rsipstack::transport::{TransportLayer, udp::UdpConnection}; 158 | //! use tokio_util::sync::CancellationToken; 159 | //! 160 | //! # async fn example() -> rsipstack::Result<()> { 161 | //! # let cancel_token = CancellationToken::new(); 162 | //! let transport_layer = TransportLayer::new(cancel_token.child_token()); 163 | //! let udp_conn = UdpConnection::create_connection("0.0.0.0:5060".parse()?, None, Some(cancel_token.child_token())).await?; 164 | //! transport_layer.add_transport(udp_conn.into()); 165 | //! 166 | //! let endpoint = EndpointBuilder::new() 167 | //! .with_transport_layer(transport_layer) 168 | //! .build(); 169 | //! # Ok(()) 170 | //! # } 171 | //! ``` 172 | //! 173 | //! ### Secure TLS Server 174 | //! 175 | //! ```rust,no_run 176 | //! #[cfg(feature = "rustls")] 177 | //! use rsipstack::transport::tls::{TlsConnection, TlsConfig}; 178 | //! use rsipstack::transport::TransportLayer; 179 | //! 180 | //! # async fn example() -> rsipstack::Result<()> { 181 | //! # let cert_pem = vec![]; 182 | //! # let key_pem = vec![]; 183 | //! # let transport_layer: TransportLayer = todo!(); 184 | //! // Configure TLS transport 185 | //! let tls_config = TlsConfig { 186 | //! cert: Some(cert_pem), 187 | //! key: Some(key_pem), 188 | //! ..Default::default() 189 | //! }; 190 | //! 191 | //! // TLS connections would be created using the TLS configuration 192 | //! // let tls_conn = TlsConnection::serve_listener(...).await?; 193 | //! # Ok(()) 194 | //! # } 195 | //! ``` 196 | //! 197 | //! ## Standards Compliance 198 | //! 199 | //! RSIPStack implements the following RFCs: 200 | //! 201 | //! * **RFC 3261** - SIP: Session Initiation Protocol (core specification) 202 | //! * **RFC 3581** - Symmetric Response Routing (rport) 203 | //! * **RFC 6026** - Correct Transaction Handling for 2xx Responses to INVITE 204 | //! 205 | //! ## Performance 206 | //! 207 | //! The stack is designed for high performance: 208 | //! 209 | //! * **Zero-copy parsing** where possible 210 | //! * **Async I/O** with Tokio for scalability 211 | //! * **Efficient timer management** for large numbers of transactions 212 | //! * **Memory-safe** with Rust's ownership system 213 | //! 214 | //! ## Testing 215 | //! 216 | //! Comprehensive test suite covering: 217 | //! 218 | //! * Unit tests for all components 219 | //! * Integration tests for protocol compliance 220 | //! * Performance benchmarks 221 | //! * Interoperability testing 222 | //! 223 | //! ## Examples 224 | //! 225 | //! See the `examples/` directory for complete working examples: 226 | //! 227 | //! * Simple SIP client 228 | //! * SIP proxy server 229 | //! * WebSocket SIP gateway 230 | //! * Load testing tools 231 | 232 | pub type Result = std::result::Result; 233 | pub use crate::error::Error; 234 | pub mod dialog; 235 | pub mod error; 236 | pub mod transaction; 237 | pub mod transport; 238 | pub use transaction::EndpointBuilder; 239 | pub mod rsip_ext; 240 | 241 | pub const VERSION: &str = concat!("rsipstack/", env!("CARGO_PKG_VERSION")); 242 | -------------------------------------------------------------------------------- /src/dialog/dialog_layer.rs: -------------------------------------------------------------------------------- 1 | use super::authenticate::Credential; 2 | use super::dialog::DialogStateSender; 3 | use super::{dialog::Dialog, server_dialog::ServerInviteDialog, DialogId}; 4 | use crate::dialog::dialog::{DialogInner, DialogStateReceiver}; 5 | use crate::transaction::key::TransactionRole; 6 | use crate::transaction::make_tag; 7 | use crate::transaction::{endpoint::EndpointInnerRef, transaction::Transaction}; 8 | use crate::Result; 9 | use rsip::prelude::HeadersExt; 10 | use rsip::Request; 11 | use std::sync::atomic::{AtomicU32, Ordering}; 12 | use std::{ 13 | collections::HashMap, 14 | sync::{Arc, RwLock}, 15 | }; 16 | use tracing::info; 17 | 18 | /// Internal Dialog Layer State 19 | /// 20 | /// `DialogLayerInner` contains the core state for managing multiple SIP dialogs. 21 | /// It maintains a registry of active dialogs and tracks sequence numbers for 22 | /// dialog creation. 23 | /// 24 | /// # Fields 25 | /// 26 | /// * `last_seq` - Atomic counter for generating unique sequence numbers 27 | /// * `dialogs` - Thread-safe map of active dialogs indexed by DialogId 28 | /// 29 | /// # Thread Safety 30 | /// 31 | /// This structure is designed to be shared across multiple threads safely: 32 | /// * `last_seq` uses atomic operations for lock-free increments 33 | /// * `dialogs` uses RwLock for concurrent read access with exclusive writes 34 | pub struct DialogLayerInner { 35 | pub(super) last_seq: AtomicU32, 36 | pub(super) dialogs: RwLock>, 37 | } 38 | pub type DialogLayerInnerRef = Arc; 39 | 40 | /// SIP Dialog Layer 41 | /// 42 | /// `DialogLayer` provides high-level dialog management functionality for SIP 43 | /// applications. It handles dialog creation, lookup, and lifecycle management 44 | /// while coordinating with the transaction layer. 45 | /// 46 | /// # Key Responsibilities 47 | /// 48 | /// * Creating and managing SIP dialogs 49 | /// * Dialog identification and routing 50 | /// * Dialog state tracking and cleanup 51 | /// * Integration with transaction layer 52 | /// * Sequence number management 53 | /// 54 | /// # Usage Patterns 55 | /// 56 | /// ## Server-side Dialog Creation 57 | /// 58 | /// ```rust,no_run 59 | /// use rsipstack::dialog::dialog_layer::DialogLayer; 60 | /// use rsipstack::transaction::endpoint::EndpointInner; 61 | /// use std::sync::Arc; 62 | /// 63 | /// # fn example() -> rsipstack::Result<()> { 64 | /// # let endpoint: Arc = todo!(); 65 | /// # let transaction = todo!(); 66 | /// # let state_sender = todo!(); 67 | /// # let credential = None; 68 | /// # let contact_uri = None; 69 | /// // Create dialog layer 70 | /// let dialog_layer = DialogLayer::new(endpoint.clone()); 71 | /// 72 | /// // Handle incoming INVITE transaction 73 | /// let server_dialog = dialog_layer.get_or_create_server_invite( 74 | /// &transaction, 75 | /// state_sender, 76 | /// credential, 77 | /// contact_uri 78 | /// )?; 79 | /// 80 | /// // Accept the call 81 | /// server_dialog.accept(None, None)?; 82 | /// # Ok(()) 83 | /// # } 84 | /// ``` 85 | /// 86 | /// ## Dialog Lookup and Routing 87 | /// 88 | /// ```rust,no_run 89 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 90 | /// # async fn example() -> rsipstack::Result<()> { 91 | /// # let dialog_layer: DialogLayer = todo!(); 92 | /// # let request = todo!(); 93 | /// # let transaction = todo!(); 94 | /// // Find existing dialog for incoming request 95 | /// if let Some(mut dialog) = dialog_layer.match_dialog(&request) { 96 | /// // Route to existing dialog 97 | /// dialog.handle(transaction).await?; 98 | /// } else { 99 | /// // Create new dialog or reject 100 | /// } 101 | /// # Ok(()) 102 | /// # } 103 | /// ``` 104 | /// 105 | /// ## Dialog Cleanup 106 | /// 107 | /// ```rust,no_run 108 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 109 | /// # fn example() { 110 | /// # let dialog_layer: DialogLayer = todo!(); 111 | /// # let dialog_id = todo!(); 112 | /// // Remove completed dialog 113 | /// dialog_layer.remove_dialog(&dialog_id); 114 | /// # } 115 | /// ``` 116 | /// 117 | /// # Dialog Lifecycle 118 | /// 119 | /// 1. **Creation** - Dialog created from incoming INVITE or outgoing request 120 | /// 2. **Early State** - Dialog exists but not yet confirmed 121 | /// 3. **Confirmed** - Dialog established with 2xx response and ACK 122 | /// 4. **Active** - Dialog can exchange in-dialog requests 123 | /// 5. **Terminated** - Dialog ended with BYE or error 124 | /// 6. **Cleanup** - Dialog removed from layer 125 | /// 126 | /// # Thread Safety 127 | /// 128 | /// DialogLayer is thread-safe and can be shared across multiple tasks: 129 | /// * Dialog lookup operations are concurrent 130 | /// * Dialog creation is serialized when needed 131 | /// * Automatic cleanup prevents memory leaks 132 | pub struct DialogLayer { 133 | pub endpoint: EndpointInnerRef, 134 | pub inner: DialogLayerInnerRef, 135 | } 136 | 137 | impl DialogLayer { 138 | pub fn new(endpoint: EndpointInnerRef) -> Self { 139 | Self { 140 | endpoint, 141 | inner: Arc::new(DialogLayerInner { 142 | last_seq: AtomicU32::new(0), 143 | dialogs: RwLock::new(HashMap::new()), 144 | }), 145 | } 146 | } 147 | 148 | pub fn get_or_create_server_invite( 149 | &self, 150 | tx: &Transaction, 151 | state_sender: DialogStateSender, 152 | credential: Option, 153 | local_contact: Option, 154 | ) -> Result { 155 | let mut id = DialogId::try_from(&tx.original)?; 156 | if !id.to_tag.is_empty() { 157 | let dlg = self.inner.dialogs.read().unwrap().get(&id).cloned(); 158 | match dlg { 159 | Some(Dialog::ServerInvite(dlg)) => return Ok(dlg), 160 | _ => { 161 | return Err(crate::Error::DialogError( 162 | "the dialog not found".to_string(), 163 | id, 164 | rsip::StatusCode::CallTransactionDoesNotExist, 165 | )); 166 | } 167 | } 168 | } 169 | id.to_tag = make_tag().to_string(); // generate to tag 170 | 171 | let mut local_contact = local_contact; 172 | if local_contact.is_none() { 173 | local_contact = self 174 | .build_local_contact(credential.as_ref().map(|cred| cred.username.clone()), None) 175 | .ok(); 176 | } 177 | 178 | let dlg_inner = DialogInner::new( 179 | TransactionRole::Server, 180 | id.clone(), 181 | tx.original.clone(), 182 | self.endpoint.clone(), 183 | state_sender, 184 | credential, 185 | local_contact, 186 | tx.tu_sender.clone(), 187 | )?; 188 | 189 | *dlg_inner.remote_contact.lock().unwrap() = tx.original.contact_header().ok().cloned(); 190 | 191 | let dialog = ServerInviteDialog { 192 | inner: Arc::new(dlg_inner), 193 | }; 194 | self.inner 195 | .dialogs 196 | .write() 197 | .unwrap() 198 | .insert(id.clone(), Dialog::ServerInvite(dialog.clone())); 199 | info!(%id, "server invite dialog created"); 200 | Ok(dialog) 201 | } 202 | 203 | pub fn increment_last_seq(&self) -> u32 { 204 | self.inner.last_seq.fetch_add(1, Ordering::Relaxed); 205 | self.inner.last_seq.load(Ordering::Relaxed) 206 | } 207 | 208 | pub fn len(&self) -> usize { 209 | self.inner.dialogs.read().unwrap().len() 210 | } 211 | 212 | pub fn all_dialog_ids(&self) -> Vec { 213 | self.inner 214 | .dialogs 215 | .read() 216 | .unwrap() 217 | .keys() 218 | .cloned() 219 | .collect::>() 220 | } 221 | 222 | pub fn get_dialog(&self, id: &DialogId) -> Option { 223 | match self.inner.dialogs.read() { 224 | Ok(dialogs) => match dialogs.get(id) { 225 | Some(dialog) => Some(dialog.clone()), 226 | None => None, 227 | }, 228 | Err(_) => None, 229 | } 230 | } 231 | 232 | pub fn remove_dialog(&self, id: &DialogId) { 233 | info!(%id, "remove dialog"); 234 | self.inner 235 | .dialogs 236 | .write() 237 | .unwrap() 238 | .remove(id) 239 | .map(|d| d.on_remove()); 240 | } 241 | 242 | pub fn match_dialog(&self, req: &Request) -> Option { 243 | let id = DialogId::try_from(req).ok()?; 244 | self.get_dialog(&id) 245 | } 246 | 247 | pub fn new_dialog_state_channel(&self) -> (DialogStateSender, DialogStateReceiver) { 248 | tokio::sync::mpsc::unbounded_channel() 249 | } 250 | 251 | pub fn build_local_contact( 252 | &self, 253 | username: Option, 254 | params: Option>, 255 | ) -> Result { 256 | let addr = self 257 | .endpoint 258 | .transport_layer 259 | .get_addrs() 260 | .first() 261 | .ok_or(crate::Error::EndpointError("not sipaddrs".to_string()))? 262 | .clone(); 263 | 264 | let scheme = if matches!(addr.r#type, Some(rsip::Transport::Tls)) { 265 | rsip::Scheme::Sips 266 | } else { 267 | rsip::Scheme::Sip 268 | }; 269 | 270 | let mut params = params.unwrap_or_default(); 271 | if !matches!(addr.r#type, Some(rsip::Transport::Udp) | None) { 272 | addr.r#type.map(|t| params.push(rsip::Param::Transport(t))); 273 | } 274 | let auth = username.map(|user| rsip::Auth { 275 | user, 276 | password: None, 277 | }); 278 | Ok(rsip::Uri { 279 | scheme: Some(scheme), 280 | auth, 281 | host_with_port: addr.addr.clone().into(), 282 | params, 283 | ..Default::default() 284 | }) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/dialog/authenticate.rs: -------------------------------------------------------------------------------- 1 | use super::DialogId; 2 | use crate::transaction::key::{TransactionKey, TransactionRole}; 3 | use crate::transaction::transaction::Transaction; 4 | use crate::transaction::{make_via_branch, random_text, CNONCE_LEN}; 5 | use crate::Result; 6 | use rsip::headers::auth::{AuthQop, Qop}; 7 | use rsip::prelude::{HasHeaders, HeadersExt, ToTypedHeader}; 8 | use rsip::services::DigestGenerator; 9 | use rsip::typed::{Authorization, ProxyAuthorization}; 10 | use rsip::{Header, Param, Response}; 11 | 12 | /// SIP Authentication Credentials 13 | /// 14 | /// `Credential` contains the authentication information needed for SIP 15 | /// digest authentication. This is used when a SIP server challenges 16 | /// a request with a 401 Unauthorized or 407 Proxy Authentication Required 17 | /// response. 18 | /// 19 | /// # Fields 20 | /// 21 | /// * `username` - The username for authentication 22 | /// * `password` - The password for authentication 23 | /// * `realm` - Optional authentication realm (extracted from challenge) 24 | /// 25 | /// # Examples 26 | /// 27 | /// ## Basic Usage 28 | /// 29 | /// ```rust,no_run 30 | /// # use rsipstack::dialog::authenticate::Credential; 31 | /// # fn example() -> rsipstack::Result<()> { 32 | /// let credential = Credential { 33 | /// username: "alice".to_string(), 34 | /// password: "secret123".to_string(), 35 | /// realm: Some("example.com".to_string()), 36 | /// }; 37 | /// # Ok(()) 38 | /// # } 39 | /// ``` 40 | /// 41 | /// ## Usage with Registration 42 | /// 43 | /// ```rust,no_run 44 | /// # use rsipstack::dialog::authenticate::Credential; 45 | /// # fn example() -> rsipstack::Result<()> { 46 | /// let credential = Credential { 47 | /// username: "alice".to_string(), 48 | /// password: "secret123".to_string(), 49 | /// realm: None, // Will be extracted from server challenge 50 | /// }; 51 | /// 52 | /// // Use credential with registration 53 | /// // let registration = Registration::new(endpoint.inner.clone(), Some(credential)); 54 | /// # Ok(()) 55 | /// # } 56 | /// ``` 57 | /// 58 | /// ## Usage with INVITE 59 | /// 60 | /// ```rust,no_run 61 | /// # use rsipstack::dialog::authenticate::Credential; 62 | /// # use rsipstack::dialog::invitation::InviteOption; 63 | /// # fn example() -> rsipstack::Result<()> { 64 | /// # let sdp_bytes = vec![]; 65 | /// # let credential = Credential { 66 | /// # username: "alice".to_string(), 67 | /// # password: "secret123".to_string(), 68 | /// # realm: Some("example.com".to_string()), 69 | /// # }; 70 | /// let invite_option = InviteOption { 71 | /// caller: rsip::Uri::try_from("sip:alice@example.com")?, 72 | /// callee: rsip::Uri::try_from("sip:bob@example.com")?, 73 | /// content_type: Some("application/sdp".to_string()), 74 | /// offer: Some(sdp_bytes), 75 | /// contact: rsip::Uri::try_from("sip:alice@192.168.1.100:5060")?, 76 | /// credential: Some(credential), 77 | /// ..Default::default() 78 | /// }; 79 | /// # Ok(()) 80 | /// # } 81 | /// ``` 82 | #[derive(Clone)] 83 | pub struct Credential { 84 | pub username: String, 85 | pub password: String, 86 | pub realm: Option, 87 | } 88 | 89 | /// Handle client-side authentication challenge 90 | /// 91 | /// This function processes a 401 Unauthorized or 407 Proxy Authentication Required 92 | /// response and creates a new transaction with proper authentication headers. 93 | /// It implements SIP digest authentication according to RFC 3261 and RFC 2617. 94 | /// 95 | /// # Parameters 96 | /// 97 | /// * `new_seq` - New CSeq number for the authenticated request 98 | /// * `tx` - Original transaction that received the authentication challenge 99 | /// * `resp` - Authentication challenge response (401 or 407) 100 | /// * `cred` - User credentials for authentication 101 | /// 102 | /// # Returns 103 | /// 104 | /// * `Ok(Transaction)` - New transaction with authentication headers 105 | /// * `Err(Error)` - Failed to process authentication challenge 106 | /// 107 | /// # Examples 108 | /// 109 | /// ## Automatic Authentication Handling 110 | /// 111 | /// ```rust,no_run 112 | /// # use rsipstack::dialog::authenticate::{handle_client_authenticate, Credential}; 113 | /// # use rsipstack::transaction::transaction::Transaction; 114 | /// # use rsip::Response; 115 | /// # async fn example() -> rsipstack::Result<()> { 116 | /// # let new_seq = 1u32; 117 | /// # let original_tx: Transaction = todo!(); 118 | /// # let auth_challenge_response: Response = todo!(); 119 | /// # let credential = Credential { 120 | /// # username: "alice".to_string(), 121 | /// # password: "secret123".to_string(), 122 | /// # realm: Some("example.com".to_string()), 123 | /// # }; 124 | /// // This is typically called automatically by dialog methods 125 | /// let new_tx = handle_client_authenticate( 126 | /// new_seq, 127 | /// original_tx, 128 | /// auth_challenge_response, 129 | /// &credential 130 | /// ).await?; 131 | /// 132 | /// // Send the authenticated request 133 | /// new_tx.send().await?; 134 | /// # Ok(()) 135 | /// # } 136 | /// ``` 137 | /// 138 | /// ## Manual Authentication Flow 139 | /// 140 | /// ```rust,no_run 141 | /// # use rsipstack::dialog::authenticate::{handle_client_authenticate, Credential}; 142 | /// # use rsipstack::transaction::transaction::Transaction; 143 | /// # use rsip::{SipMessage, StatusCode, Response}; 144 | /// # async fn example() -> rsipstack::Result<()> { 145 | /// # let mut tx: Transaction = todo!(); 146 | /// # let credential = Credential { 147 | /// # username: "alice".to_string(), 148 | /// # password: "secret123".to_string(), 149 | /// # realm: Some("example.com".to_string()), 150 | /// # }; 151 | /// # let new_seq = 2u32; 152 | /// // Send initial request 153 | /// tx.send().await?; 154 | /// 155 | /// while let Some(message) = tx.receive().await { 156 | /// match message { 157 | /// SipMessage::Response(resp) => { 158 | /// match resp.status_code { 159 | /// StatusCode::Unauthorized | StatusCode::ProxyAuthenticationRequired => { 160 | /// // Handle authentication challenge 161 | /// let auth_tx = handle_client_authenticate( 162 | /// new_seq, tx, resp, &credential 163 | /// ).await?; 164 | /// 165 | /// // Send authenticated request 166 | /// auth_tx.send().await?; 167 | /// tx = auth_tx; 168 | /// }, 169 | /// StatusCode::OK => { 170 | /// println!("Request successful"); 171 | /// break; 172 | /// }, 173 | /// _ => { 174 | /// println!("Request failed: {}", resp.status_code); 175 | /// break; 176 | /// } 177 | /// } 178 | /// }, 179 | /// _ => {} 180 | /// } 181 | /// } 182 | /// # Ok(()) 183 | /// # } 184 | /// ``` 185 | /// 186 | /// This function handles SIP authentication challenges and creates authenticated requests. 187 | pub async fn handle_client_authenticate( 188 | new_seq: u32, 189 | tx: Transaction, 190 | resp: Response, 191 | cred: &Credential, 192 | ) -> Result { 193 | let header = match resp.www_authenticate_header() { 194 | Some(h) => Header::WwwAuthenticate(h.clone()), 195 | None => { 196 | let code = resp.status_code.clone(); 197 | let proxy_header = rsip::header_opt!(resp.headers().iter(), Header::ProxyAuthenticate); 198 | let proxy_header = proxy_header.ok_or(crate::Error::DialogError( 199 | "missing proxy/www authenticate".to_string(), 200 | DialogId::try_from(&tx.original)?, 201 | code, 202 | ))?; 203 | Header::ProxyAuthenticate(proxy_header.clone()) 204 | } 205 | }; 206 | 207 | let mut new_req = tx.original.clone(); 208 | new_req.cseq_header_mut()?.mut_seq(new_seq)?; 209 | 210 | let challenge = match &header { 211 | Header::WwwAuthenticate(h) => h.typed()?, 212 | Header::ProxyAuthenticate(h) => h.typed()?.0, 213 | _ => unreachable!(), 214 | }; 215 | 216 | let cnonce = random_text(CNONCE_LEN); 217 | let auth_qop = match challenge.qop { 218 | Some(Qop::Auth) => Some(AuthQop::Auth { cnonce, nc: 1 }), 219 | Some(Qop::AuthInt) => Some(AuthQop::AuthInt { cnonce, nc: 1 }), 220 | _ => None, 221 | }; 222 | 223 | // Use MD5 as default algorithm if none specified (RFC 2617 compatibility) 224 | let algorithm = challenge 225 | .algorithm 226 | .unwrap_or(rsip::headers::auth::Algorithm::Md5); 227 | 228 | let response = DigestGenerator { 229 | username: cred.username.as_str(), 230 | password: cred.password.as_str(), 231 | algorithm, 232 | nonce: challenge.nonce.as_str(), 233 | method: &tx.original.method, 234 | qop: auth_qop.as_ref(), 235 | uri: &tx.original.uri, 236 | realm: challenge.realm.as_str(), 237 | } 238 | .compute(); 239 | 240 | let auth = Authorization { 241 | scheme: challenge.scheme, 242 | username: cred.username.clone(), 243 | realm: challenge.realm, 244 | nonce: challenge.nonce, 245 | uri: tx.original.uri.clone(), 246 | response, 247 | algorithm: Some(algorithm), 248 | opaque: challenge.opaque, 249 | qop: auth_qop, 250 | }; 251 | 252 | let mut via_header = tx.original.via_header()?.clone().typed()?; 253 | let params = &mut via_header.params; 254 | params.retain(|p| !matches!(p, rsip::Param::Branch(_))); 255 | params.push(make_via_branch()); 256 | params.push(Param::Other("rport".into(), None)); 257 | new_req.headers_mut().unique_push(via_header.into()); 258 | 259 | new_req.headers_mut().retain(|h| { 260 | !matches!( 261 | h, 262 | Header::ProxyAuthenticate(_) 263 | | Header::Authorization(_) 264 | | Header::WwwAuthenticate(_) 265 | | Header::ProxyAuthorization(_) 266 | ) 267 | }); 268 | 269 | match header { 270 | Header::WwwAuthenticate(_) => { 271 | new_req.headers_mut().unique_push(auth.into()); 272 | } 273 | Header::ProxyAuthenticate(_) => { 274 | new_req 275 | .headers_mut() 276 | .unique_push(ProxyAuthorization(auth).into()); 277 | } 278 | _ => unreachable!(), 279 | } 280 | let key = TransactionKey::from_request(&new_req, TransactionRole::Client)?; 281 | let mut new_tx = Transaction::new_client( 282 | key, 283 | new_req, 284 | tx.endpoint_inner.clone(), 285 | tx.connection.clone(), 286 | ); 287 | new_tx.destination = tx.destination.clone(); 288 | Ok(new_tx) 289 | } 290 | -------------------------------------------------------------------------------- /src/transport/stream.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::{TransportSender, KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE}, 4 | SipAddr, SipConnection, TransportEvent, 5 | }, 6 | Result, 7 | }; 8 | use bytes::{Buf, BytesMut}; 9 | use rsip::SipMessage; 10 | use tokio::{ 11 | io::{AsyncRead, AsyncWrite, AsyncWriteExt}, 12 | sync::Mutex, 13 | }; 14 | use tokio_util::codec::{Decoder, Encoder}; 15 | use tracing::{debug, info, warn}; 16 | 17 | pub(super) const MAX_SIP_MESSAGE_SIZE: usize = 65535; 18 | const CL_FULL_NAME: &[u8] = b"content-length"; 19 | const CL_SHORT_NAME: &[u8] = b"l"; 20 | 21 | pub struct SipCodec {} 22 | 23 | impl SipCodec { 24 | pub fn new() -> Self { 25 | Self {} 26 | } 27 | } 28 | 29 | impl Default for SipCodec { 30 | fn default() -> Self { 31 | Self::new() 32 | } 33 | } 34 | 35 | #[derive(Debug, Clone)] 36 | pub enum SipCodecType { 37 | Message(SipMessage), 38 | KeepaliveRequest, 39 | KeepaliveResponse, 40 | } 41 | 42 | impl std::fmt::Display for SipCodecType { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 44 | match self { 45 | SipCodecType::Message(msg) => write!(f, "{}", msg), 46 | SipCodecType::KeepaliveRequest => write!(f, "Keepalive Request"), 47 | SipCodecType::KeepaliveResponse => write!(f, "Keepalive Response"), 48 | } 49 | } 50 | } 51 | 52 | impl Decoder for SipCodec { 53 | type Item = SipCodecType; 54 | type Error = crate::Error; 55 | 56 | fn decode(&mut self, src: &mut BytesMut) -> Result> { 57 | if src.len() >= 4 && &src[0..4] == KEEPALIVE_REQUEST { 58 | src.advance(4); 59 | return Ok(Some(SipCodecType::KeepaliveRequest)); 60 | } 61 | 62 | if src.len() >= 2 && &src[0..2] == KEEPALIVE_RESPONSE { 63 | src.advance(2); 64 | return Ok(Some(SipCodecType::KeepaliveResponse)); 65 | } 66 | 67 | if let Some(headers_end) = src.windows(4).position(|w| w == b"\r\n\r\n") { 68 | let headers = &src[..headers_end + 4]; // include CRLFCRLF 69 | 70 | // Parse Content-Length as u32 without UTF-8 conversion 71 | let mut content_length: usize = 0; 72 | let mut start = 0; 73 | while start < headers.len() { 74 | // find end of line 75 | let mut end = start; 76 | while end < headers.len() && headers[end] != b'\n' { 77 | end += 1; 78 | } 79 | 80 | let mut line = &headers[start..end]; 81 | if let Some(&b'\r') = line.last() { 82 | line = &line[..line.len().saturating_sub(1)]; 83 | } 84 | 85 | if let Some(colon) = line.iter().position(|&b| b == b':') { 86 | let header = &line[..colon]; 87 | let is_cl = if header.len() == CL_FULL_NAME.len() 88 | && header 89 | .iter() 90 | .zip(CL_FULL_NAME.iter()) 91 | .all(|(&a, &b)| a.to_ascii_lowercase() == b) 92 | { 93 | true 94 | } else if header.len() == CL_SHORT_NAME.len() 95 | && header 96 | .iter() 97 | .zip(CL_SHORT_NAME.iter()) 98 | .all(|(&a, &b)| a.to_ascii_lowercase() == b) 99 | { 100 | true 101 | } else { 102 | false 103 | }; 104 | 105 | if is_cl { 106 | // parse value 107 | let value_buf = &line[colon + 1..]; 108 | content_length = std::str::from_utf8(value_buf) 109 | .map_err(|_| crate::Error::Error("Invalid Content-Length".to_string()))? 110 | .trim() 111 | .parse() 112 | .map_err(|_| { 113 | crate::Error::Error("Invalid Content-Length value".to_string()) 114 | })?; 115 | break; 116 | } 117 | } 118 | 119 | start = if end < headers.len() { end + 1 } else { end }; 120 | } 121 | 122 | let total_len = headers_end + 4 + content_length; 123 | 124 | if src.len() >= total_len { 125 | let msg_data = src.split_to(total_len); // consume full message 126 | let msg = SipMessage::try_from(&msg_data[..])?; 127 | return Ok(Some(SipCodecType::Message(msg))); 128 | } 129 | } 130 | 131 | if src.len() > MAX_SIP_MESSAGE_SIZE { 132 | return Err(crate::Error::Error("SIP message too large".to_string())); 133 | } 134 | Ok(None) 135 | } 136 | } 137 | 138 | impl Encoder for SipCodec { 139 | type Error = crate::Error; 140 | 141 | fn encode(&mut self, item: SipMessage, dst: &mut BytesMut) -> Result<()> { 142 | let data = item.to_string(); 143 | dst.extend_from_slice(data.as_bytes()); 144 | Ok(()) 145 | } 146 | } 147 | 148 | pub struct StreamConnectionInner 149 | where 150 | R: AsyncRead + Unpin + Send, 151 | W: AsyncWrite + Unpin + Send, 152 | { 153 | pub local_addr: SipAddr, 154 | pub remote_addr: SipAddr, 155 | pub read_half: Mutex>, 156 | pub write_half: Mutex, 157 | } 158 | 159 | impl StreamConnectionInner 160 | where 161 | R: AsyncRead + Unpin + Send, 162 | W: AsyncWrite + Unpin + Send, 163 | { 164 | pub fn new(local_addr: SipAddr, remote_addr: SipAddr, read_half: R, write_half: W) -> Self { 165 | Self { 166 | local_addr, 167 | remote_addr, 168 | read_half: Mutex::new(Some(read_half)), 169 | write_half: Mutex::new(write_half), 170 | } 171 | } 172 | 173 | pub async fn send_message(&self, msg: SipMessage) -> Result<()> { 174 | send_to_stream(&self.write_half, msg).await 175 | } 176 | 177 | pub async fn send_raw(&self, data: &[u8]) -> Result<()> { 178 | send_raw_to_stream(&self.write_half, data).await 179 | } 180 | 181 | pub async fn serve_loop( 182 | &self, 183 | sender: TransportSender, 184 | connection: SipConnection, 185 | ) -> Result<()> { 186 | let mut read_half = match self.read_half.lock().await.take() { 187 | Some(read_half) => read_half, 188 | None => { 189 | warn!("Connection closed"); 190 | return Ok(()); 191 | } 192 | }; 193 | 194 | let remote_addr = self.remote_addr.clone(); 195 | 196 | let mut codec = SipCodec::new(); 197 | let mut buffer = BytesMut::with_capacity(MAX_SIP_MESSAGE_SIZE); 198 | let mut read_buf = [0u8; MAX_SIP_MESSAGE_SIZE]; 199 | 200 | loop { 201 | use tokio::io::AsyncReadExt; 202 | match read_half.read(&mut read_buf).await { 203 | Ok(0) => { 204 | info!("Connection closed: {}", self.local_addr); 205 | break; 206 | } 207 | Ok(n) => { 208 | buffer.extend_from_slice(&read_buf[0..n]); 209 | 210 | loop { 211 | match codec.decode(&mut buffer)? { 212 | Some(msg) => match msg { 213 | SipCodecType::Message(sip_msg) => { 214 | debug!("Received message from {}: {}", remote_addr, sip_msg); 215 | let remote_socket_addr = remote_addr.get_socketaddr()?; 216 | let sip_msg = SipConnection::update_msg_received( 217 | sip_msg, 218 | remote_socket_addr, 219 | remote_addr.r#type.unwrap_or_default(), 220 | )?; 221 | 222 | if let Err(e) = sender.send(TransportEvent::Incoming( 223 | sip_msg, 224 | connection.clone(), 225 | remote_addr.clone(), 226 | )) { 227 | warn!("Error sending incoming message: {:?}", e); 228 | return Err(e.into()); 229 | } 230 | } 231 | SipCodecType::KeepaliveRequest => { 232 | self.send_raw(KEEPALIVE_RESPONSE).await?; 233 | } 234 | SipCodecType::KeepaliveResponse => {} 235 | }, 236 | None => { 237 | // Need more data 238 | break; 239 | } 240 | } 241 | } 242 | } 243 | Err(e) => { 244 | warn!("Error reading from stream: {}", e); 245 | break; 246 | } 247 | } 248 | } 249 | Ok(()) 250 | } 251 | 252 | pub async fn close(&self) -> Result<()> { 253 | let mut write_half = self.write_half.lock().await; 254 | write_half 255 | .shutdown() 256 | .await 257 | .map_err(|e| crate::Error::Error(format!("Failed to shutdown write half: {}", e)))?; 258 | Ok(()) 259 | } 260 | } 261 | 262 | #[async_trait::async_trait] 263 | pub trait StreamConnection: Send + Sync + 'static { 264 | fn get_addr(&self) -> &SipAddr; 265 | async fn send_message(&self, msg: SipMessage) -> Result<()>; 266 | async fn send_raw(&self, data: &[u8]) -> Result<()>; 267 | async fn serve_loop(&self, sender: TransportSender) -> Result<()>; 268 | async fn close(&self) -> Result<()>; 269 | } 270 | 271 | pub async fn send_to_stream(write_half: &Mutex, msg: SipMessage) -> Result<()> 272 | where 273 | W: AsyncWrite + Unpin + Send, 274 | { 275 | send_raw_to_stream(write_half, msg.to_string().as_bytes()).await 276 | } 277 | 278 | pub async fn send_raw_to_stream(write_half: &Mutex, data: &[u8]) -> Result<()> 279 | where 280 | W: AsyncWrite + Unpin + Send, 281 | { 282 | let mut lock = write_half.lock().await; 283 | lock.write_all(data).await?; 284 | lock.flush().await?; 285 | Ok(()) 286 | } 287 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsipstack - A SIP Stack written in Rust 2 | 3 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/restsend/rsipstack) 4 | 5 | A RFC 3261/3262 compliant SIP stack written in Rust. The goal of this project is to provide a high-performance, reliable, and easy-to-use SIP stack that can be used in various scenarios. 6 | 7 | ## Features 8 | 9 | - **RFC 3261/3262 Compliant**: Full compliance with SIP specification 10 | - **Multiple Transport Support**: UDP, TCP, TLS, WebSocket (TLS/WebSocket require the `rustls` and `websocket` features, enabled by default) 11 | - **Transaction Layer**: Complete SIP transaction state machine 12 | - **Dialog Layer**: SIP dialog management 13 | - **Reliable Provisionals**: PRACK (RFC 3262 / 100rel) support 14 | - **Digest Authentication**: Built-in authentication support 15 | - **High Performance**: Built with Rust for maximum performance 16 | - **Easy to Use**: Simple and intuitive API design 17 | 18 | ## TODO 19 | - [x] Transport support 20 | - [x] UDP 21 | - [x] TCP 22 | - [x] TLS 23 | - [x] WebSocket 24 | - [x] Digest Authentication 25 | - [x] Transaction Layer 26 | - [x] Dialog Layer 27 | - [ ] WASM target 28 | 29 | ## Use Cases 30 | 31 | This SIP stack can be used in various scenarios, including but not limited to: 32 | 33 | - Integration with WebRTC for browser-based communication, such as WebRTC SBC. 34 | - Building custom SIP proxies or registrars 35 | - Building custom SIP user agents (SIP.js alternative) 36 | 37 | ## Why Rust? 38 | 39 | We are a group of developers who are passionate about SIP and Rust. We believe that Rust is a great language for building high-performance network applications, and we want to bring the power of Rust to the SIP/WebRTC/SFU world. 40 | 41 | ## Quick Start Examples 42 | 43 | ### SIP Proxy Server 44 | 45 | A stateful SIP proxy that routes calls between registered users: 46 | 47 | ```bash 48 | # Run proxy server 49 | cargo run --example proxy -- --port 25060 --addr 127.0.0.1 50 | 51 | # Run with external IP 52 | cargo run --example proxy -- --port 25060 --external-ip 1.2.3.4 53 | ``` 54 | 55 | This example demonstrates: 56 | - SIP user registration and location service 57 | - Call routing between registered users 58 | - Transaction forwarding and response handling 59 | - Session management for active calls 60 | - Handling INVITE, BYE, REGISTER, and ACK methods 61 | 62 | ### SIP User Agent Client 63 | 64 | A complete SIP client with registration, calling, and media support: 65 | 66 | ```bash 67 | # Local demo proxy 68 | cargo run --example client -- --port 25061 --sip-server 127.0.0.1:25060 --auto-answer 69 | 70 | # Register with a SIP server 71 | cargo run --example client -- --sip-server sip.example.com --user alice --password secret --auto-answer 72 | ``` 73 | 74 | ## API Usage Guide 75 | 76 | ### 1. Simple SIP Connection 77 | 78 | ```rust 79 | use rsipstack::transport::{udp::UdpConnection, SipAddr}; 80 | use tokio_util::sync::CancellationToken; 81 | 82 | // Create UDP connection bound to an ephemeral local port 83 | let cancel_token = CancellationToken::new(); 84 | let connection = UdpConnection::create_connection( 85 | "0.0.0.0:0".parse()?, 86 | None, 87 | Some(cancel_token.child_token()), 88 | ) 89 | .await?; 90 | 91 | // Prepare the remote target 92 | let target_addr = SipAddr::new( 93 | rsip::transport::Transport::Udp, 94 | rsip::HostWithPort::try_from("127.0.0.1:5060")?, 95 | ); 96 | 97 | // Send raw SIP message 98 | let sip_message = "OPTIONS sip:test@example.com SIP/2.0\r\n..."; 99 | connection 100 | .send_raw(sip_message.as_bytes(), &target_addr) 101 | .await?; 102 | ``` 103 | 104 | ### 2. Using Transport Listeners 105 | 106 | ```rust 107 | use rsipstack::transport::{ 108 | SipAddr, TcpListenerConnection, TransportEvent, TransportLayer, 109 | }; 110 | use tokio_util::sync::CancellationToken; 111 | 112 | // Build a transport layer and register listeners 113 | let cancel_token = CancellationToken::new(); 114 | let transport_layer = TransportLayer::new(cancel_token.clone()); 115 | 116 | let tcp_listener = TcpListenerConnection::new( 117 | SipAddr::new( 118 | rsip::transport::Transport::Tcp, 119 | rsip::HostWithPort::try_from("0.0.0.0:5060")?, 120 | ), 121 | None, 122 | ) 123 | .await?; 124 | transport_layer.add_transport(tcp_listener.into()); 125 | 126 | // Access the transport event stream 127 | let mut events = transport_layer 128 | .inner 129 | .transport_rx 130 | .lock() 131 | .unwrap() 132 | .take() 133 | .expect("transport receiver"); 134 | 135 | tokio::spawn(async move { 136 | while let Some(event) = events.recv().await { 137 | match event { 138 | TransportEvent::New(connection) => println!("New connection: {}", connection), 139 | TransportEvent::Incoming(msg, connection, source) => { 140 | println!("Received message from {}: {}", source, msg); 141 | // Use `connection` to reply if needed 142 | } 143 | TransportEvent::Closed(connection) => { 144 | println!("Connection closed: {}", connection); 145 | } 146 | } 147 | } 148 | }); 149 | 150 | // Start accepting connections (this is normally driven by `Endpoint::serve`) 151 | transport_layer 152 | .serve_listens() 153 | .await 154 | .expect("failed to start listeners"); 155 | ``` 156 | 157 | To add TLS or WebSocket listeners, construct a `TlsListenerConnection` or 158 | `WebSocketListenerConnection` and register it with `transport_layer.add_transport(...)`. 159 | 160 | ### 3. Using Endpoint and Transactions 161 | 162 | ```rust 163 | use rsipstack::{EndpointBuilder, transport::TransportLayer}; 164 | use tokio_util::sync::CancellationToken; 165 | 166 | // Build endpoint with transport layer 167 | let cancel_token = CancellationToken::new(); 168 | let transport_layer = TransportLayer::new(cancel_token.clone()); 169 | let endpoint = EndpointBuilder::new() 170 | .with_transport_layer(transport_layer) 171 | .with_cancel_token(cancel_token.clone()) 172 | .build(); 173 | 174 | // Start endpoint background task 175 | let endpoint_inner = endpoint.inner.clone(); 176 | tokio::spawn(async move { 177 | if let Err(err) = endpoint_inner.serve().await { 178 | eprintln!("endpoint stopped: {err}"); 179 | } 180 | }); 181 | 182 | // Handle incoming transactions 183 | let mut incoming = endpoint 184 | .incoming_transactions() 185 | .expect("transaction receiver available"); 186 | while let Some(transaction) = incoming.recv().await { 187 | // Process transaction based on method 188 | match transaction.original.method { 189 | rsip::Method::Register => { 190 | transaction.reply(rsip::StatusCode::OK).await?; 191 | } 192 | rsip::Method::Options => { 193 | transaction.reply(rsip::StatusCode::OK).await?; 194 | } 195 | // ... handle other methods 196 | } 197 | } 198 | ``` 199 | 200 | ### 4. Creating a User Agent Client 201 | 202 | ```rust 203 | use rsipstack::dialog::{DialogLayer, registration::Registration}; 204 | use rsipstack::dialog::authenticate::Credential; 205 | use rsipstack::dialog::invitation::InviteOption; 206 | use std::sync::Arc; 207 | use tokio::sync::mpsc::unbounded_channel; 208 | 209 | // Create dialog layer 210 | let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone())); 211 | 212 | // Register with server 213 | let credential = Credential { 214 | username: "alice".to_string(), 215 | password: "secret".to_string(), 216 | realm: None, 217 | }; 218 | 219 | let mut registration = Registration::new(endpoint.inner.clone(), Some(credential.clone())); 220 | let response = registration.register("sip:registrar.example.com".parse()?, None).await?; 221 | 222 | // Make outgoing call 223 | let invite_option = InviteOption { 224 | callee: "sip:bob@example.com".parse()?, 225 | caller: "sip:alice@example.com".parse()?, 226 | content_type: None, 227 | offer: None, 228 | contact: "sip:alice@192.168.1.100:5060".parse()?, 229 | credential: Some(credential), 230 | headers: None, 231 | }; 232 | 233 | let (state_sender, _state_receiver) = unbounded_channel(); 234 | let (invite_dialog, response) = dialog_layer.do_invite(invite_option, state_sender).await?; 235 | ``` 236 | 237 | ### 5. Implementing a Proxy 238 | 239 | ```rust 240 | use rsipstack::transaction::{Transaction, key::{TransactionKey, TransactionRole}}; 241 | use rsipstack::rsip_ext::RsipHeadersExt; 242 | use rsip::prelude::HeadersExt; 243 | use std::collections::HashMap; 244 | 245 | // Handle incoming requests 246 | while let Some(mut transaction) = incoming.recv().await { 247 | match transaction.original.method { 248 | rsip::Method::Register => { 249 | // Store user registration 250 | let user = User::try_from(&transaction.original)?; 251 | users.insert(user.username.clone(), user); 252 | transaction.reply(rsip::StatusCode::OK).await?; 253 | } 254 | rsip::Method::Invite => { 255 | // Route call to registered user 256 | let callee = transaction.original.to_header()?.uri()?.auth 257 | .map(|a| a.user) 258 | .unwrap_or_default(); 259 | if let Some(target) = users.get(&callee) { 260 | // Create new client transaction for forwarding 261 | let mut forwarded_req = transaction.original.clone(); 262 | let via = transaction.endpoint_inner.get_via(None, None)?; 263 | forwarded_req.headers.push_front(via.into()); 264 | 265 | let key = TransactionKey::from_request(&forwarded_req, TransactionRole::Client)?; 266 | let mut forwarded_tx = Transaction::new_client( 267 | key, 268 | forwarded_req, 269 | transaction.endpoint_inner.clone(), 270 | None 271 | ); 272 | forwarded_tx.destination = Some(target.destination.clone()); 273 | forwarded_tx.send().await?; 274 | } else { 275 | transaction.reply(rsip::StatusCode::NotFound).await?; 276 | } 277 | } 278 | // ... handle other methods 279 | } 280 | } 281 | ``` 282 | 283 | ## Running Tests 284 | 285 | ### Unit Tests 286 | ```bash 287 | cargo test 288 | ``` 289 | 290 | 291 | ### Benchmark Tests 292 | ```bash 293 | # Run server 294 | cargo run -r --bin bench_ua -- -m server -p 5060 295 | 296 | # Run client with 1000 calls 297 | cargo run -r --bin bench_ua -- -m client -p 5061 -s 127.0.0.1:5060 -c 1000 298 | ``` 299 | 300 | The test monitor: 301 | 302 | ```bash 303 | === SIP Benchmark UA Stats === 304 | Dialogs: 9992 305 | Active Calls: 9983 306 | Rejected Calls: 0 307 | Failed Calls: 0 308 | Total Calls: 250276 309 | Calls/Second: 1501 310 | ============================ 311 | ``` 312 | 313 | ## Documentation 314 | 315 | - [API Documentation](https://docs.rs/rsipstack) 316 | - [Examples](./examples/) 317 | 318 | ## Contributing 319 | 320 | We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. 321 | 322 | ## License 323 | 324 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /src/transaction/message.rs: -------------------------------------------------------------------------------- 1 | use super::{endpoint::EndpointInner, make_call_id}; 2 | use crate::{transaction::make_via_branch, Result}; 3 | use rsip::{ 4 | header, 5 | headers::{ContentLength, Route}, 6 | prelude::{ToTypedHeader, UntypedHeader}, 7 | Error, Header, Request, Response, StatusCode, 8 | }; 9 | 10 | impl EndpointInner { 11 | /// Create a SIP request message 12 | /// 13 | /// Constructs a properly formatted SIP request with all required headers 14 | /// according to RFC 3261. This method is used internally by the endpoint 15 | /// to create outgoing SIP requests for various purposes. 16 | /// 17 | /// # Parameters 18 | /// 19 | /// * `method` - SIP method (INVITE, REGISTER, BYE, etc.) 20 | /// * `req_uri` - Request-URI indicating the target of the request 21 | /// * `via` - Via header for response routing 22 | /// * `from` - From header identifying the request originator 23 | /// * `to` - To header identifying the request target 24 | /// * `seq` - CSeq sequence number for the request 25 | /// 26 | /// # Returns 27 | /// 28 | /// A complete SIP request with all mandatory headers 29 | /// 30 | /// # Generated Headers 31 | /// 32 | /// The method automatically includes these mandatory headers: 33 | /// * **Via** - Response routing information 34 | /// * **Call-ID** - Unique identifier for the call/session 35 | /// * **From** - Request originator with tag parameter 36 | /// * **To** - Request target (tag added by recipient) 37 | /// * **CSeq** - Command sequence with method and number 38 | /// * **Max-Forwards** - Hop count limit (set to 70) 39 | /// * **User-Agent** - Endpoint identification 40 | /// 41 | /// # Examples 42 | /// 43 | /// ```rust,no_run 44 | /// # use rsipstack::transaction::endpoint::EndpointInner; 45 | /// # async fn example(endpoint: &EndpointInner) -> rsipstack::Result<()> { 46 | /// // Create an INVITE request 47 | /// let via = endpoint.get_via(None, None)?; 48 | /// let from = rsip::typed::From { 49 | /// display_name: None, 50 | /// uri: rsip::Uri::try_from("sip:alice@example.com")?, 51 | /// params: vec![rsip::Param::Tag("alice-tag".into())], 52 | /// }; 53 | /// let to = rsip::typed::To { 54 | /// display_name: None, 55 | /// uri: rsip::Uri::try_from("sip:bob@example.com")?, 56 | /// params: vec![], 57 | /// }; 58 | /// 59 | /// let request = endpoint.make_request( 60 | /// rsip::Method::Invite, 61 | /// rsip::Uri::try_from("sip:bob@example.com")?, 62 | /// via, 63 | /// from, 64 | /// to, 65 | /// 1, 66 | /// None, 67 | /// ); 68 | /// # Ok(()) 69 | /// # } 70 | /// ``` 71 | /// 72 | /// # Usage Context 73 | /// 74 | /// This method is typically used by: 75 | /// * Dialog layer for creating in-dialog requests 76 | /// * Registration module for REGISTER requests 77 | /// * Transaction layer for creating client transactions 78 | /// * Application layer for custom request types 79 | /// 80 | /// # Header Ordering 81 | /// 82 | /// Headers are added in the order specified by RFC 3261 recommendations: 83 | /// 1. Via (topmost first) 84 | /// 2. Call-ID 85 | /// 3. From 86 | /// 4. To 87 | /// 5. CSeq 88 | /// 6. Max-Forwards 89 | /// 7. User-Agent 90 | /// 91 | /// Additional headers can be added after creation using the headers API. 92 | pub fn make_request( 93 | &self, 94 | method: rsip::Method, 95 | req_uri: rsip::Uri, 96 | via: rsip::typed::Via, 97 | from: rsip::typed::From, 98 | to: rsip::typed::To, 99 | seq: u32, 100 | call_id: Option, 101 | ) -> rsip::Request { 102 | let call_id = call_id.unwrap_or_else(|| make_call_id(self.option.callid_suffix.as_deref())); 103 | let headers = vec![ 104 | Header::Via(via.into()), 105 | Header::CallId(call_id), 106 | Header::From(from.into()), 107 | Header::To(to.into()), 108 | Header::CSeq(rsip::typed::CSeq { seq, method }.into()), 109 | Header::MaxForwards(70.into()), 110 | Header::UserAgent(self.user_agent.clone().into()), 111 | ]; 112 | rsip::Request { 113 | method, 114 | uri: req_uri, 115 | headers: headers.into(), 116 | body: vec![], 117 | version: rsip::Version::V2, 118 | } 119 | } 120 | 121 | /// Create a SIP response message 122 | /// 123 | /// Constructs a properly formatted SIP response based on the received 124 | /// request. This method copies appropriate headers from the request 125 | /// and adds the response-specific information according to RFC 3261. 126 | /// 127 | /// # Parameters 128 | /// 129 | /// * `req` - Original request being responded to 130 | /// * `status_code` - SIP response status code (1xx-6xx) 131 | /// * `body` - Optional response body content 132 | /// 133 | /// # Returns 134 | /// 135 | /// A complete SIP response ready to be sent 136 | /// 137 | /// # Header Processing 138 | /// 139 | /// The method processes headers as follows: 140 | /// * **Copied from request**: Via, Call-ID, From, To, CSeq, Max-Forwards 141 | /// * **Added by endpoint**: User-Agent 142 | /// * **Filtered out**: All other headers from the request 143 | /// 144 | /// Additional response-specific headers should be added after creation. 145 | /// 146 | /// # Examples 147 | /// 148 | /// ## Success Response 149 | /// 150 | /// ```rust,no_run 151 | /// # use rsipstack::transaction::endpoint::EndpointInner; 152 | /// # fn example(endpoint: &EndpointInner, request: &rsip::Request, sdp_answer: String) { 153 | /// let response = endpoint.make_response( 154 | /// &request, 155 | /// rsip::StatusCode::OK, 156 | /// Some(sdp_answer.into_bytes()) 157 | /// ); 158 | /// # } 159 | /// ``` 160 | /// 161 | /// ## Error Response 162 | /// 163 | /// ```rust,no_run 164 | /// # use rsipstack::transaction::endpoint::EndpointInner; 165 | /// # fn example(endpoint: &EndpointInner, request: &rsip::Request) { 166 | /// let response = endpoint.make_response( 167 | /// &request, 168 | /// rsip::StatusCode::NotFound, 169 | /// None 170 | /// ); 171 | /// # } 172 | /// ``` 173 | /// 174 | /// ## Provisional Response 175 | /// 176 | /// ```rust,no_run 177 | /// # use rsipstack::transaction::endpoint::EndpointInner; 178 | /// # fn example(endpoint: &EndpointInner, request: &rsip::Request) { 179 | /// let response = endpoint.make_response( 180 | /// &request, 181 | /// rsip::StatusCode::Ringing, 182 | /// None 183 | /// ); 184 | /// # } 185 | /// ``` 186 | /// 187 | /// # Response Categories 188 | /// 189 | /// * **1xx Provisional** - Request received, processing continues 190 | /// * **2xx Success** - Request successfully received, understood, and accepted 191 | /// * **3xx Redirection** - Further action needed to complete request 192 | /// * **4xx Client Error** - Request contains bad syntax or cannot be fulfilled 193 | /// * **5xx Server Error** - Server failed to fulfill valid request 194 | /// * **6xx Global Failure** - Request cannot be fulfilled at any server 195 | /// 196 | /// # Usage Context 197 | /// 198 | /// This method is used by: 199 | /// * Server transactions to create responses 200 | /// * Dialog layer for dialog-specific responses 201 | /// * Application layer for handling incoming requests 202 | /// * Error handling for protocol violations 203 | /// 204 | /// # Header Compliance 205 | /// 206 | /// The response includes all headers required by RFC 3261: 207 | /// * Via headers are copied exactly (for response routing) 208 | /// * Call-ID is preserved (dialog/transaction identification) 209 | /// * From/To headers maintain dialog state 210 | /// * CSeq is copied for transaction matching 211 | /// * User-Agent identifies the responding endpoint 212 | /// 213 | /// # Content Handling 214 | /// 215 | /// * If body is provided, Content-Length should be added separately 216 | /// * Content-Type should be added for non-empty bodies 217 | /// * Body encoding is handled by the application layer 218 | pub fn make_response( 219 | &self, 220 | req: &Request, 221 | status_code: StatusCode, 222 | body: Option>, 223 | ) -> Response { 224 | let mut headers = req.headers.clone(); 225 | headers.retain(|h| { 226 | matches!( 227 | h, 228 | Header::Via(_) 229 | | Header::CallId(_) 230 | | Header::From(_) 231 | | Header::To(_) 232 | | Header::CSeq(_) 233 | ) 234 | }); 235 | headers.push(Header::ContentLength( 236 | body.as_ref().map_or(0u32, |b| b.len() as u32).into(), 237 | )); 238 | headers.unique_push(Header::UserAgent(self.user_agent.clone().into())); 239 | Response { 240 | status_code, 241 | version: req.version().clone(), 242 | headers, 243 | body: body.unwrap_or_default(), 244 | } 245 | } 246 | 247 | pub fn make_ack(&self, resp: &Response, request_uri: rsip::Uri) -> Result { 248 | let mut headers = resp.headers.clone(); 249 | if matches!(resp.status_code.kind(), rsip::StatusCodeKind::Successful) { 250 | //For non-2xx final responses (3xx–6xx), the ACK stays within the original INVITE client transaction. 251 | if let Ok(top_most_via) = header!( 252 | headers.iter_mut(), 253 | Header::Via, 254 | Error::missing_header("Via") 255 | ) { 256 | if let Ok(mut typed_via) = top_most_via.typed() { 257 | typed_via.params.clear(); 258 | typed_via.params.push(make_via_branch()); 259 | *top_most_via = typed_via.into(); 260 | } 261 | } 262 | } 263 | // update route set from Record-Route header 264 | let mut route_set = Vec::new(); 265 | for header in resp.headers.iter() { 266 | if let Header::RecordRoute(record_route) = header { 267 | route_set.push(Header::Route(Route::from(record_route.value()))); 268 | } 269 | } 270 | route_set.reverse(); 271 | headers.extend(route_set); 272 | 273 | headers.retain(|h| { 274 | matches!( 275 | h, 276 | Header::Via(_) 277 | | Header::CallId(_) 278 | | Header::From(_) 279 | | Header::To(_) 280 | | Header::CSeq(_) 281 | | Header::Route(_) 282 | ) 283 | }); 284 | headers.push(Header::MaxForwards(70.into())); 285 | headers.iter_mut().for_each(|h| { 286 | if let Header::CSeq(cseq) = h { 287 | cseq.mut_method(rsip::Method::Ack).ok(); 288 | } 289 | }); 290 | headers.push(Header::ContentLength(ContentLength::default())); // 0 because of vec![] below 291 | headers.unique_push(Header::UserAgent(self.user_agent.clone().into())); 292 | Ok(rsip::Request { 293 | method: rsip::Method::Ack, 294 | uri: request_uri, 295 | headers: headers.into(), 296 | body: vec![], 297 | version: rsip::Version::V2, 298 | }) 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/transaction/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::{SipAddr, SipConnection}; 2 | use key::TransactionKey; 3 | use std::time::Duration; 4 | use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; 5 | use transaction::Transaction; 6 | 7 | pub mod endpoint; 8 | pub mod key; 9 | pub mod message; 10 | mod timer; 11 | pub mod transaction; 12 | pub use endpoint::Endpoint; 13 | pub use endpoint::EndpointBuilder; 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | pub const TO_TAG_LEN: usize = 8; 18 | pub const BRANCH_LEN: usize = 12; 19 | pub const CNONCE_LEN: usize = 8; 20 | pub const CALL_ID_LEN: usize = 22; 21 | pub struct IncomingRequest { 22 | pub request: rsip::Request, 23 | pub connection: SipConnection, 24 | pub from: SipAddr, 25 | } 26 | 27 | pub type TransactionReceiver = UnboundedReceiver; 28 | pub type TransactionSender = UnboundedSender; 29 | 30 | /// SIP Transaction State 31 | /// 32 | /// `TransactionState` represents the various states a SIP transaction can be in 33 | /// during its lifecycle. These states implement the transaction state machines 34 | /// defined in RFC 3261 for both client and server transactions. 35 | /// 36 | /// # States 37 | /// 38 | /// * `Nothing` - Initial state for client transactions created 39 | /// * `Calling` - Initial state for client transactions when request is sent or received 40 | /// * `Trying` - Request has been sent/received, waiting for response/processing 41 | /// * `Proceeding` - Provisional response received/sent (1xx except 100 Trying) 42 | /// * `Completed` - Final response received/sent, waiting for ACK (INVITE) or cleanup 43 | /// * `Confirmed` - ACK received/sent for INVITE transactions 44 | /// * `Terminated` - Transaction has completed and is being cleaned up 45 | /// 46 | /// # State Transitions 47 | /// 48 | /// ## Client Non-INVITE Transaction 49 | /// ```text 50 | /// Nothing → Calling → Trying → Proceeding → Completed → Terminated 51 | /// ``` 52 | /// 53 | /// ## Client INVITE Transaction 54 | /// ```text 55 | /// Nothing → Calling → Trying → Proceeding → Completed → Terminated 56 | /// ↓ 57 | /// Confirmed → Terminated 58 | /// ``` 59 | /// 60 | /// ## Server Transactions 61 | /// ```text 62 | /// Calling → Trying → Proceeding → Completed → Terminated 63 | /// ↓ 64 | /// Confirmed → Terminated (INVITE only) 65 | /// ``` 66 | /// 67 | /// # Examples 68 | /// 69 | /// ```rust 70 | /// use rsipstack::transaction::TransactionState; 71 | /// 72 | /// let state = TransactionState::Proceeding; 73 | /// match state { 74 | /// TransactionState::Nothing => println!("Transaction starting"), 75 | /// TransactionState::Calling => println!("Request sent"), 76 | /// TransactionState::Trying => println!("Request sent/received"), 77 | /// TransactionState::Proceeding => println!("Provisional response"), 78 | /// TransactionState::Completed => println!("Final response"), 79 | /// TransactionState::Confirmed => println!("ACK received/sent"), 80 | /// TransactionState::Terminated => println!("Transaction complete"), 81 | /// } 82 | /// ``` 83 | #[derive(Debug, Clone, PartialEq)] 84 | pub enum TransactionState { 85 | Nothing, 86 | Calling, 87 | Trying, 88 | Proceeding, 89 | Completed, 90 | Confirmed, 91 | Terminated, 92 | } 93 | 94 | impl std::fmt::Display for TransactionState { 95 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 96 | match self { 97 | TransactionState::Nothing => write!(f, "Nothing"), 98 | TransactionState::Calling => write!(f, "Calling"), 99 | TransactionState::Trying => write!(f, "Trying"), 100 | TransactionState::Proceeding => write!(f, "Proceeding"), 101 | TransactionState::Completed => write!(f, "Completed"), 102 | TransactionState::Confirmed => write!(f, "Confirmed"), 103 | TransactionState::Terminated => write!(f, "Terminated"), 104 | } 105 | } 106 | } 107 | /// SIP Transaction Type 108 | /// 109 | /// `TransactionType` distinguishes between the four types of SIP transactions 110 | /// as defined in RFC 3261. Each type has different behavior for retransmissions, 111 | /// timers, and state transitions. 112 | /// 113 | /// # Types 114 | /// 115 | /// * `ClientInvite` - Client-side INVITE transaction (UAC INVITE) 116 | /// * `ClientNonInvite` - Client-side non-INVITE transaction (UAC non-INVITE) 117 | /// * `ServerInvite` - Server-side INVITE transaction (UAS INVITE) 118 | /// * `ServerNonInvite` - Server-side non-INVITE transaction (UAS non-INVITE) 119 | /// 120 | /// # Characteristics 121 | /// 122 | /// ## Client INVITE 123 | /// * Longer timeouts due to human interaction 124 | /// * ACK handling for 2xx responses 125 | /// * CANCEL support for early termination 126 | /// 127 | /// ## Client Non-INVITE 128 | /// * Shorter timeouts for automated responses 129 | /// * No ACK required 130 | /// * Simpler state machine 131 | /// 132 | /// ## Server INVITE 133 | /// * Must handle ACK for final responses 134 | /// * Supports provisional responses 135 | /// * Complex retransmission rules 136 | /// 137 | /// ## Server Non-INVITE 138 | /// * Simple request/response pattern 139 | /// * No ACK handling 140 | /// * Faster completion 141 | /// 142 | /// # Examples 143 | /// 144 | /// ```rust 145 | /// use rsipstack::transaction::TransactionType; 146 | /// use rsip::Method; 147 | /// 148 | /// fn get_transaction_type(method: &Method, is_client: bool) -> TransactionType { 149 | /// match (method, is_client) { 150 | /// (Method::Invite, true) => TransactionType::ClientInvite, 151 | /// (Method::Invite, false) => TransactionType::ServerInvite, 152 | /// (_, true) => TransactionType::ClientNonInvite, 153 | /// (_, false) => TransactionType::ServerNonInvite, 154 | /// } 155 | /// } 156 | /// ``` 157 | #[derive(Debug, PartialEq)] 158 | pub enum TransactionType { 159 | ClientInvite, 160 | ClientNonInvite, 161 | ServerInvite, 162 | ServerNonInvite, 163 | } 164 | impl std::fmt::Display for TransactionType { 165 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 166 | match self { 167 | TransactionType::ClientInvite => write!(f, "ClientInvite"), 168 | TransactionType::ClientNonInvite => write!(f, "ClientNonInvite"), 169 | TransactionType::ServerInvite => write!(f, "ServerInvite"), 170 | TransactionType::ServerNonInvite => write!(f, "ServerNonInvite"), 171 | } 172 | } 173 | } 174 | /// SIP Transaction Timers 175 | /// 176 | /// `TransactionTimer` represents the various timers used in SIP transactions 177 | /// as defined in RFC 3261. These timers ensure reliable message delivery 178 | /// and proper transaction cleanup. 179 | /// 180 | /// # Timer Types 181 | /// 182 | /// * `TimerA` - Retransmission timer for client transactions (unreliable transport) 183 | /// * `TimerB` - Transaction timeout timer for client transactions 184 | /// * `TimerD` - Wait timer for response retransmissions (client) 185 | /// * `TimerE` - Retransmission timer for non-INVITE server transactions 186 | /// * `TimerF` - Transaction timeout timer for non-INVITE server transactions 187 | /// * `TimerK` - Wait timer for ACK (server INVITE transactions) 188 | /// * `TimerG` - Retransmission timer for INVITE server transactions 189 | /// * `TimerCleanup` - Internal cleanup timer for transaction removal 190 | /// 191 | /// # Timer Values (RFC 3261) 192 | /// 193 | /// * T1 = 500ms (RTT estimate) 194 | /// * T2 = 4s (maximum retransmit interval) 195 | /// * T4 = 5s (maximum duration a message will remain in the network) 196 | /// 197 | /// ## Timer Calculations 198 | /// * Timer A: starts at T1, doubles each retransmission up to T2 199 | /// * Timer B: 64*T1 (32 seconds) 200 | /// * Timer D: 32 seconds for unreliable, 0 for reliable transports 201 | /// * Timer E: starts at T1, doubles up to T2 202 | /// * Timer F: 64*T1 (32 seconds) 203 | /// * Timer G: starts at T1, doubles up to T2 204 | /// * Timer K: T4 for unreliable, 0 for reliable transports 205 | /// 206 | /// # Examples 207 | /// 208 | /// ```rust 209 | /// use rsipstack::transaction::{TransactionTimer, key::{TransactionKey, TransactionRole}}; 210 | /// use std::time::Duration; 211 | /// 212 | /// # fn example() -> rsipstack::Result<()> { 213 | /// // Create a mock request to generate a transaction key 214 | /// let request = rsip::Request { 215 | /// method: rsip::Method::Register, 216 | /// uri: rsip::Uri::try_from("sip:example.com")?, 217 | /// headers: vec![ 218 | /// rsip::Header::Via("SIP/2.0/UDP example.com:5060;branch=z9hG4bKnashds".into()), 219 | /// rsip::Header::CSeq("1 REGISTER".into()), 220 | /// rsip::Header::From("Alice ;tag=1928301774".into()), 221 | /// rsip::Header::CallId("a84b4c76e66710@pc33.atlanta.com".into()), 222 | /// ].into(), 223 | /// version: rsip::Version::V2, 224 | /// body: Default::default(), 225 | /// }; 226 | /// let key = TransactionKey::from_request(&request, TransactionRole::Client)?; 227 | /// 228 | /// let timer = TransactionTimer::TimerA(key.clone(), Duration::from_millis(500)); 229 | /// match timer { 230 | /// TransactionTimer::TimerA(key, duration) => { 231 | /// println!("Timer A fired for transaction {}", key); 232 | /// }, 233 | /// TransactionTimer::TimerB(key) => { 234 | /// println!("Transaction {} timed out", key); 235 | /// }, 236 | /// _ => {} 237 | /// } 238 | /// # Ok(()) 239 | /// # } 240 | /// ``` 241 | /// 242 | /// # Usage 243 | /// 244 | /// Timers are automatically managed by the transaction layer: 245 | /// * Started when entering appropriate states 246 | /// * Cancelled when leaving states or receiving responses 247 | /// * Fire events that drive state machine transitions 248 | /// * Handle retransmissions and timeouts 249 | pub enum TransactionTimer { 250 | TimerA(TransactionKey, Duration), 251 | TimerB(TransactionKey), 252 | TimerC(TransactionKey), 253 | TimerD(TransactionKey), 254 | TimerK(TransactionKey), 255 | TimerG(TransactionKey, Duration), 256 | TimerCleanup(TransactionKey), 257 | } 258 | 259 | impl TransactionTimer { 260 | pub fn key(&self) -> &TransactionKey { 261 | match self { 262 | TransactionTimer::TimerA(key, _) => key, 263 | TransactionTimer::TimerB(key) => key, 264 | TransactionTimer::TimerC(key) => key, 265 | TransactionTimer::TimerD(key) => key, 266 | TransactionTimer::TimerG(key, _) => key, 267 | TransactionTimer::TimerK(key) => key, 268 | TransactionTimer::TimerCleanup(key) => key, 269 | } 270 | } 271 | } 272 | 273 | impl std::fmt::Display for TransactionTimer { 274 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 275 | match self { 276 | TransactionTimer::TimerA(key, duration) => { 277 | write!(f, "TimerA: {} {}", key, duration.as_millis()) 278 | } 279 | TransactionTimer::TimerB(key) => write!(f, "TimerB: {}", key), 280 | TransactionTimer::TimerC(key) => write!(f, "TimerC: {}", key), 281 | TransactionTimer::TimerD(key) => write!(f, "TimerD: {}", key), 282 | TransactionTimer::TimerG(key, duration) => { 283 | write!(f, "TimerG: {} {}", key, duration.as_millis()) 284 | } 285 | TransactionTimer::TimerK(key) => write!(f, "TimerK: {}", key), 286 | TransactionTimer::TimerCleanup(key) => write!(f, "TimerCleanup: {}", key), 287 | } 288 | } 289 | } 290 | 291 | pub fn make_via_branch() -> rsip::Param { 292 | rsip::Param::Branch(format!("z9hG4bK{}", random_text(BRANCH_LEN)).into()) 293 | } 294 | 295 | pub fn make_call_id(domain: Option<&str>) -> rsip::headers::CallId { 296 | format!( 297 | "{}@{}", 298 | random_text(CALL_ID_LEN), 299 | domain.unwrap_or("restsend.com") 300 | ) 301 | .into() 302 | } 303 | 304 | pub fn make_tag() -> rsip::param::Tag { 305 | random_text(TO_TAG_LEN).into() 306 | } 307 | 308 | #[cfg(not(target_family = "wasm"))] 309 | pub fn random_text(count: usize) -> String { 310 | use rand::Rng; 311 | rand::rng() 312 | .sample_iter(rand::distr::Alphanumeric) 313 | .take(count) 314 | .map(char::from) 315 | .collect::() 316 | } 317 | 318 | #[cfg(target_family = "wasm")] 319 | pub fn random_text(count: usize) -> String { 320 | (0..count) 321 | .map(|_| { 322 | let r = js_sys::Math::random(); 323 | let c = (r * 16.0) as u8; 324 | if c < 10 { 325 | (c + 48) as char 326 | } else { 327 | (c + 87) as char 328 | } 329 | }) 330 | .collect() 331 | } 332 | -------------------------------------------------------------------------------- /src/rsip_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::{SipAddr, SipConnection}; 2 | use crate::{Error, Result}; 3 | use nom::{ 4 | branch::alt, 5 | bytes::complete::{is_not, take_until}, 6 | character::complete::{char, multispace0}, 7 | combinator::{map, opt, rest}, 8 | multi::separated_list0, 9 | sequence::{delimited, preceded}, 10 | IResult, Parser, 11 | }; 12 | use rsip::prelude::ToTypedHeader; 13 | use rsip::{ 14 | message::HasHeaders, 15 | prelude::{HeadersExt, UntypedHeader}, 16 | Method, 17 | }; 18 | use std::str::FromStr; 19 | 20 | pub trait RsipResponseExt { 21 | fn reason_phrase(&self) -> Option<&str>; 22 | fn via_received(&self) -> Option; 23 | fn content_type(&self) -> Option; 24 | fn remote_uri(&self, destination: Option<&SipAddr>) -> Result; 25 | } 26 | 27 | impl RsipResponseExt for rsip::Response { 28 | fn reason_phrase(&self) -> Option<&str> { 29 | let headers = self.headers(); 30 | for header in headers.iter() { 31 | if let rsip::Header::Other(name, value) = header { 32 | if name.eq_ignore_ascii_case("reason") { 33 | return Some(value); 34 | } 35 | } 36 | if let rsip::Header::ErrorInfo(reason) = header { 37 | return Some(reason.value()); 38 | } 39 | } 40 | None 41 | } 42 | /// Parse the received address from the Via header 43 | /// 44 | /// This function extracts the received address from the Via header 45 | /// and returns it as a HostWithPort struct. 46 | fn via_received(&self) -> Option { 47 | let via = self.via_header().ok()?; 48 | SipConnection::parse_target_from_via(via) 49 | .map(|(_, host_with_port)| host_with_port) 50 | .ok() 51 | } 52 | fn content_type(&self) -> Option { 53 | let headers = self.headers(); 54 | for header in headers.iter() { 55 | if let rsip::Header::ContentType(content_type) = header { 56 | return Some(content_type.clone()); 57 | } 58 | } 59 | None 60 | } 61 | 62 | fn remote_uri(&self, destination: Option<&SipAddr>) -> Result { 63 | let contact = self.contact_header()?; 64 | // update remote uri 65 | let mut contact_uri = if let Ok(typed_contact) = contact.typed() { 66 | typed_contact.uri 67 | } else { 68 | let mut uri = extract_uri_from_contact(contact.value())?; 69 | uri.headers.clear(); 70 | uri 71 | }; 72 | 73 | for param in contact_uri.params.iter() { 74 | if let rsip::Param::Other(name, _) = param { 75 | if !name.to_string().eq_ignore_ascii_case("ob") { 76 | continue; 77 | } 78 | contact_uri.params.clear(); 79 | if let Some(dest) = destination { 80 | contact_uri.host_with_port = dest.addr.clone(); 81 | dest.r#type 82 | .as_ref() 83 | .map(|t| contact_uri.params.push(rsip::Param::Transport(t.clone()))); 84 | } 85 | break; 86 | } 87 | } 88 | Ok(contact_uri) 89 | } 90 | } 91 | 92 | pub trait RsipHeadersExt { 93 | fn push_front(&mut self, header: rsip::Header); 94 | } 95 | 96 | impl RsipHeadersExt for rsip::Headers { 97 | fn push_front(&mut self, header: rsip::Header) { 98 | let mut headers = self.iter().cloned().collect::>(); 99 | headers.insert(0, header); 100 | *self = headers.into(); 101 | } 102 | } 103 | 104 | #[macro_export] 105 | macro_rules! header_pop { 106 | ($iter:expr, $header:path) => { 107 | let mut first = true; 108 | $iter.retain(|h| { 109 | if first && matches!(h, $header(_)) { 110 | first = false; 111 | false 112 | } else { 113 | true 114 | } 115 | }); 116 | }; 117 | } 118 | 119 | pub fn extract_uri_from_contact(line: &str) -> Result { 120 | if let Ok(uri) = rsip::headers::Contact::from(line).uri() { 121 | return Ok(uri); 122 | } 123 | 124 | let tokenizer = CustomContactTokenizer::from_str(line)?; 125 | let mut uri = rsip::Uri::try_from(tokenizer.uri()).map_err(Error::from)?; 126 | uri.params.retain(|p| { 127 | if let rsip::Param::Transport(rsip::Transport::Udp) = p { 128 | false 129 | } else { 130 | true 131 | } 132 | }); 133 | apply_tokenizer_params(&mut uri, &tokenizer); 134 | return Ok(uri); 135 | } 136 | 137 | fn apply_tokenizer_params(uri: &mut rsip::Uri, tokenizer: &CustomContactTokenizer) { 138 | for (name, value) in tokenizer.params.iter().map(|p| (p.name, p.value)) { 139 | if name.eq_ignore_ascii_case("transport") { 140 | continue; 141 | } 142 | let mut updated = false; 143 | for param in uri.params.iter_mut() { 144 | if let rsip::Param::Other(key, existing_value) = param { 145 | if key.value().eq_ignore_ascii_case(name) { 146 | *existing_value = 147 | value.map(|v| rsip::param::OtherParamValue::new(v.to_string())); 148 | updated = true; 149 | break; 150 | } 151 | } 152 | } 153 | if !updated { 154 | uri.params.push(rsip::Param::Other( 155 | rsip::param::OtherParam::new(name), 156 | value.map(|v| rsip::param::OtherParamValue::new(v.to_string())), 157 | )); 158 | } 159 | } 160 | } 161 | 162 | pub fn destination_from_request(request: &rsip::Request) -> Option { 163 | request 164 | .headers 165 | .iter() 166 | .find_map(|header| match header { 167 | rsip::Header::Route(route) => route 168 | .typed() 169 | .ok() 170 | .map(|r| { 171 | r.uris() 172 | .first() 173 | .map(|u| SipAddr::try_from(&u.uri).ok()) 174 | .flatten() 175 | }) 176 | .flatten(), 177 | _ => None, 178 | }) 179 | .or_else(|| SipAddr::try_from(&request.uri).ok()) 180 | } 181 | 182 | fn split_header_line(raw: &str) -> Option<(&str, &str)> { 183 | raw.split_once(':') 184 | .map(|(name, value)| (name.trim(), value.trim())) 185 | } 186 | 187 | pub fn header_value_case_insensitive(headers: &rsip::Headers, name: &str) -> Option { 188 | headers.iter().find_map(|header| { 189 | let raw = header.to_string(); 190 | let (header_name, header_value) = split_header_line(&raw)?; 191 | if header_name.eq_ignore_ascii_case(name) { 192 | Some(header_value.to_string()) 193 | } else { 194 | None 195 | } 196 | }) 197 | } 198 | 199 | pub fn header_tokens_case_insensitive(headers: &rsip::Headers, name: &str) -> Vec { 200 | header_value_case_insensitive(headers, name) 201 | .map(|value| { 202 | value 203 | .split(',') 204 | .map(|token| token.trim()) 205 | .filter(|token| !token.is_empty()) 206 | .map(|token| token.to_string()) 207 | .collect::>() 208 | }) 209 | .unwrap_or_default() 210 | } 211 | 212 | pub fn header_contains_token(headers: &rsip::Headers, name: &str, token: &str) -> bool { 213 | header_tokens_case_insensitive(headers, name) 214 | .into_iter() 215 | .any(|value| value.eq_ignore_ascii_case(token)) 216 | } 217 | 218 | pub fn parse_rseq_header(headers: &rsip::Headers) -> Option { 219 | header_value_case_insensitive(headers, "RSeq") 220 | .and_then(|value| value.split_whitespace().next().map(str::to_string)) 221 | .and_then(|token| token.parse::().ok()) 222 | } 223 | 224 | pub fn parse_rack_header(headers: &rsip::Headers) -> Option<(u32, u32, Method)> { 225 | let value = header_value_case_insensitive(headers, "RAck")?; 226 | let mut items = value.split_whitespace(); 227 | let rseq = items.next()?.parse::().ok()?; 228 | let cseq = items.next()?.parse::().ok()?; 229 | let method_str = items.next()?; 230 | let method = Method::from_str(method_str).ok()?; 231 | Some((rseq, cseq, method)) 232 | } 233 | 234 | #[derive(Debug)] 235 | pub(crate) struct CustomContactTokenizer<'a> { 236 | uri: &'a str, 237 | params: Vec>, 238 | } 239 | 240 | #[derive(Debug)] 241 | struct CustomContactParamToken<'a> { 242 | name: &'a str, 243 | value: Option<&'a str>, 244 | } 245 | 246 | impl<'a> CustomContactTokenizer<'a> { 247 | pub(crate) fn from_str(input: &'a str) -> super::Result { 248 | let trimmed = input.trim(); 249 | if trimmed.is_empty() { 250 | return Err(Error::Error("empty contact header".into())); 251 | } 252 | 253 | match custom_contact_tokenize(trimmed) { 254 | Ok((_rem, tokenizer)) => Ok(tokenizer), 255 | Err(_) => Ok(Self::from_plain(trimmed)), 256 | } 257 | } 258 | 259 | fn from_plain(uri: &'a str) -> Self { 260 | Self { 261 | uri, 262 | params: custom_contact_parse_params(uri), 263 | } 264 | } 265 | 266 | pub(crate) fn uri(&self) -> &'a str { 267 | self.uri 268 | } 269 | } 270 | 271 | fn custom_contact_tokenize<'a>(input: &'a str) -> IResult<&'a str, CustomContactTokenizer<'a>> { 272 | alt(( 273 | custom_contact_with_brackets, 274 | custom_contact_without_brackets, 275 | )) 276 | .parse(input) 277 | } 278 | 279 | fn custom_contact_with_brackets<'a>( 280 | input: &'a str, 281 | ) -> IResult<&'a str, CustomContactTokenizer<'a>> { 282 | let (input, _) = multispace0(input)?; 283 | let (input, _) = opt(take_until("<")).parse(input)?; 284 | let (input, _) = char('<').parse(input)?; 285 | let (input, uri) = take_until(">").parse(input)?; 286 | let (input, _) = char('>').parse(input)?; 287 | 288 | let uri = uri.trim(); 289 | let params = custom_contact_parse_params(uri); 290 | 291 | Ok((input, CustomContactTokenizer { uri, params })) 292 | } 293 | 294 | fn custom_contact_without_brackets<'a>( 295 | input: &'a str, 296 | ) -> IResult<&'a str, CustomContactTokenizer<'a>> { 297 | let (input, uri) = map(rest, |s: &str| s.trim()).parse(input)?; 298 | let params = custom_contact_parse_params(uri); 299 | Ok((input, CustomContactTokenizer { uri, params })) 300 | } 301 | 302 | fn custom_contact_parse_params<'a>(uri: &'a str) -> Vec> { 303 | let path = uri.split_once('?').map_or(uri, |(path, _)| path); 304 | if let Some(idx) = path.find(';') { 305 | let params_str = &path[idx + 1..]; 306 | if params_str.is_empty() { 307 | return Vec::new(); 308 | } 309 | 310 | match separated_list0(char(';'), custom_contact_param).parse(params_str) { 311 | Ok((_, params)) => params.into_iter().filter(|p| !p.name.is_empty()).collect(), 312 | Err(_) => Vec::new(), 313 | } 314 | } else { 315 | Vec::new() 316 | } 317 | } 318 | 319 | fn custom_contact_param<'a>(input: &'a str) -> IResult<&'a str, CustomContactParamToken<'a>> { 320 | let (input, _) = multispace0(input)?; 321 | let (input, name) = map(is_not("=; \t\r\n?"), |v: &str| v.trim()).parse(input)?; 322 | let (input, value) = opt(preceded( 323 | char('='), 324 | alt(( 325 | delimited(char('"'), take_until("\""), char('"')), 326 | map(is_not("; \t\r\n?"), |v: &str| v.trim()), 327 | )), 328 | )) 329 | .parse(input)?; 330 | 331 | Ok((input, CustomContactParamToken { name, value })) 332 | } 333 | 334 | #[test] 335 | fn test_rsip_headers_ext() { 336 | use rsip::{Header, Headers}; 337 | let mut headers: Headers = vec![ 338 | Header::Via("SIP/2.0/TCP".into()), 339 | Header::Via("SIP/2.0/UDP".into()), 340 | Header::Via("SIP/2.0/WSS".into()), 341 | ] 342 | .into(); 343 | let via = Header::Via("SIP/2.0/TLS".into()); 344 | headers.push_front(via); 345 | assert_eq!(headers.iter().count(), 4); 346 | 347 | header_pop!(headers, Header::Via); 348 | assert_eq!(headers.iter().count(), 3); 349 | 350 | assert_eq!( 351 | headers.iter().collect::>(), 352 | vec![ 353 | &Header::Via("SIP/2.0/TCP".into()), 354 | &Header::Via("SIP/2.0/UDP".into()), 355 | &Header::Via("SIP/2.0/WSS".into()) 356 | ] 357 | ); 358 | } 359 | -------------------------------------------------------------------------------- /src/transport/tests/test_stream_encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::{ 2 | connection::{KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE}, 3 | stream::SipCodec, 4 | }; 5 | use bytes::BytesMut; 6 | use rsip::{ 7 | prelude::{HeadersExt, UntypedHeader}, 8 | SipMessage, 9 | }; 10 | use tokio_util::codec::{Decoder, Encoder}; 11 | 12 | /// Test SipCodec decoding of single message 13 | #[test] 14 | fn test_sip_codec_single_message() { 15 | let mut codec = SipCodec::new(); 16 | let mut buffer = BytesMut::new(); 17 | 18 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 19 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 20 | From: ;tag=test\r\n\ 21 | To: \r\n\ 22 | Call-ID: test-call-id\r\n\ 23 | CSeq: 1 REGISTER\r\n\ 24 | Contact: \r\n\ 25 | Max-Forwards: 70\r\n\ 26 | Content-Length: 0\r\n\r\n"; 27 | 28 | buffer.extend_from_slice(test_message.as_bytes()); 29 | 30 | let result = codec.decode(&mut buffer).expect("decode should succeed"); 31 | assert!(result.is_some(), "Should decode a message"); 32 | 33 | let msg = result.unwrap(); 34 | match msg { 35 | crate::transport::stream::SipCodecType::Message(SipMessage::Request(req)) => { 36 | assert_eq!(req.method, rsip::Method::Register); 37 | } 38 | _ => panic!("Expected request message"), 39 | } 40 | 41 | // Buffer should be empty after consuming the message 42 | assert_eq!( 43 | buffer.len(), 44 | 0, 45 | "Buffer should be empty after consuming message" 46 | ); 47 | } 48 | 49 | /// Test SipCodec handling of TCP packet fragmentation 50 | #[test] 51 | fn test_sip_codec_fragmented_message() { 52 | let mut codec = SipCodec::new(); 53 | let mut buffer = BytesMut::new(); 54 | 55 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 56 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 57 | From: ;tag=test\r\n\ 58 | To: \r\n\ 59 | Call-ID: test-call-id\r\n\ 60 | CSeq: 1 REGISTER\r\n\ 61 | Contact: \r\n\ 62 | Max-Forwards: 70\r\n\ 63 | Content-Length: 0\r\n\r\n"; 64 | 65 | // Send message in fragments 66 | let fragment1 = &test_message[..50]; 67 | let fragment2 = &test_message[50..100]; 68 | let fragment3 = &test_message[100..]; 69 | 70 | // First fragment 71 | buffer.extend_from_slice(fragment1.as_bytes()); 72 | let result = codec.decode(&mut buffer).expect("decode should not fail"); 73 | assert!(result.is_none(), "Should not decode incomplete message"); 74 | 75 | // Second fragment 76 | buffer.extend_from_slice(fragment2.as_bytes()); 77 | let result = codec.decode(&mut buffer).expect("decode should not fail"); 78 | assert!(result.is_none(), "Should not decode incomplete message"); 79 | 80 | // Third fragment completes the message 81 | buffer.extend_from_slice(fragment3.as_bytes()); 82 | let result = codec.decode(&mut buffer).expect("decode should succeed"); 83 | assert!(result.is_some(), "Should decode complete message"); 84 | 85 | let msg = result.unwrap(); 86 | match msg { 87 | crate::transport::stream::SipCodecType::Message(SipMessage::Request(req)) => { 88 | assert_eq!(req.method, rsip::Method::Register); 89 | } 90 | _ => panic!("Expected request message"), 91 | } 92 | } 93 | 94 | /// Test SipCodec handling of multiple messages in one buffer (TCP packet coalescing) 95 | #[test] 96 | fn test_sip_codec_multiple_messages() { 97 | let mut codec = SipCodec::new(); 98 | let mut buffer = BytesMut::new(); 99 | 100 | let message1 = "REGISTER sip:example.com SIP/2.0\r\n\ 101 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test1\r\n\ 102 | From: ;tag=test1\r\n\ 103 | To: \r\n\ 104 | Call-ID: test-call-id-1\r\n\ 105 | CSeq: 1 REGISTER\r\n\ 106 | Contact: \r\n\ 107 | Max-Forwards: 70\r\n\ 108 | Content-Length: 0\r\n\r\n"; 109 | 110 | let message2 = "REGISTER sip:example.com SIP/2.0\r\n\ 111 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test2\r\n\ 112 | From: ;tag=test2\r\n\ 113 | To: \r\n\ 114 | Call-ID: test-call-id-2\r\n\ 115 | CSeq: 1 REGISTER\r\n\ 116 | Contact: \r\n\ 117 | Max-Forwards: 70\r\n\ 118 | Content-Length: 0\r\n\r\n"; 119 | 120 | // Add both messages to buffer 121 | buffer.extend_from_slice(message1.as_bytes()); 122 | buffer.extend_from_slice(message2.as_bytes()); 123 | 124 | // First decode 125 | let result1 = codec 126 | .decode(&mut buffer) 127 | .expect("first decode should succeed"); 128 | assert!(result1.is_some(), "Should decode first message"); 129 | 130 | let msg1 = result1.unwrap(); 131 | match msg1 { 132 | crate::transport::stream::SipCodecType::Message(SipMessage::Request(req)) => { 133 | assert_eq!(req.call_id_header().unwrap().value(), "test-call-id-1"); 134 | } 135 | _ => panic!("Expected request message"), 136 | } 137 | 138 | // Second decode 139 | let result2 = codec 140 | .decode(&mut buffer) 141 | .expect("second decode should succeed"); 142 | assert!(result2.is_some(), "Should decode second message"); 143 | 144 | let msg2 = result2.unwrap(); 145 | match msg2 { 146 | crate::transport::stream::SipCodecType::Message(SipMessage::Request(req)) => { 147 | assert_eq!(req.call_id_header().unwrap().value(), "test-call-id-2"); 148 | } 149 | _ => panic!("Expected request message"), 150 | } 151 | 152 | // Buffer should be empty after consuming both messages 153 | assert_eq!( 154 | buffer.len(), 155 | 0, 156 | "Buffer should be empty after consuming all messages" 157 | ); 158 | } 159 | 160 | /// Test SipCodec keepalive handling 161 | #[test] 162 | fn test_sip_codec_keepalive() { 163 | let mut codec = SipCodec::new(); 164 | let mut buffer = BytesMut::new(); 165 | 166 | // Test keepalive request 167 | buffer.extend_from_slice(KEEPALIVE_REQUEST); 168 | let result = codec.decode(&mut buffer).expect("decode should succeed"); 169 | assert!(result.is_some(), "Should decode keepalive request"); 170 | 171 | match result.unwrap() { 172 | crate::transport::stream::SipCodecType::KeepaliveRequest => { 173 | // Expected 174 | } 175 | _ => panic!("Expected keepalive request"), 176 | } 177 | assert_eq!(buffer.len(), 0, "Keepalive should be consumed from buffer"); 178 | 179 | // Test keepalive response 180 | buffer.extend_from_slice(KEEPALIVE_RESPONSE); 181 | let result = codec.decode(&mut buffer).expect("decode should succeed"); 182 | assert!(result.is_some(), "Should decode keepalive response"); 183 | 184 | match result.unwrap() { 185 | crate::transport::stream::SipCodecType::KeepaliveResponse => { 186 | // Expected 187 | } 188 | _ => panic!("Expected keepalive response"), 189 | } 190 | assert_eq!(buffer.len(), 0, "Keepalive should be consumed from buffer"); 191 | } 192 | 193 | /// Test SipCodec encoding 194 | #[test] 195 | fn test_sip_codec_encoding() { 196 | let mut codec = SipCodec::new(); 197 | let mut buffer = BytesMut::new(); 198 | 199 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 200 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 201 | From: ;tag=test\r\n\ 202 | To: \r\n\ 203 | Call-ID: test-call-id\r\n\ 204 | CSeq: 1 REGISTER\r\n\ 205 | Contact: \r\n\ 206 | Max-Forwards: 70\r\n\ 207 | Content-Length: 0\r\n\r\n"; 208 | 209 | let sip_message = SipMessage::try_from(test_message).expect("parse SIP message"); 210 | 211 | codec 212 | .encode(sip_message, &mut buffer) 213 | .expect("encode should succeed"); 214 | 215 | assert_eq!( 216 | String::from_utf8_lossy(&buffer), 217 | test_message, 218 | "Encoded message should match original" 219 | ); 220 | } 221 | 222 | /// Test error handling for malformed messages 223 | #[test] 224 | fn test_sip_codec_malformed_message() { 225 | let mut codec = SipCodec::new(); 226 | let mut buffer = BytesMut::new(); 227 | 228 | let malformed_message = "INVALID MESSAGE\r\n\r\n"; 229 | 230 | buffer.extend_from_slice(malformed_message.as_bytes()); 231 | 232 | let result = codec.decode(&mut buffer); 233 | assert!(result.is_err(), "Should error on malformed message"); 234 | 235 | // Buffer should be cleared or partially consumed 236 | // The exact behavior depends on implementation 237 | } 238 | 239 | /// Test message size limit 240 | #[test] 241 | fn test_sip_codec_size_limit() { 242 | let mut codec = SipCodec::new(); 243 | let mut buffer = BytesMut::new(); 244 | 245 | // Create a message larger than the limit without complete headers (no \r\n\r\n) 246 | let large_content = "A".repeat(70000); 247 | let incomplete_large_message = format!( 248 | "MESSAGE sip:example.com SIP/2.0\r\n\ 249 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 250 | From: ;tag=test\r\n\ 251 | To: \r\n\ 252 | Call-ID: test-call-id\r\n\ 253 | CSeq: 1 MESSAGE\r\n\ 254 | Content-Type: text/plain\r\n\ 255 | Content-Length: {}\r\n{}", 256 | large_content.len(), 257 | large_content // No \r\n\r\n ending to trigger size check 258 | ); 259 | 260 | buffer.extend_from_slice(incomplete_large_message.as_bytes()); 261 | 262 | let result = codec.decode(&mut buffer); 263 | assert!(result.is_err(), "Should error on oversized message"); 264 | } 265 | 266 | /// Test SipCodec handling of multiple messages in one buffer (TCP packet coalescing) 267 | #[test] 268 | fn test_sip_codec_multiple_messages_with_bodies() { 269 | let mut codec = SipCodec::new(); 270 | let mut buffer = BytesMut::new(); 271 | 272 | let message1 = "REGISTER sip:example.com SIP/2.0\r\n\ 273 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test1\r\n\ 274 | From: ;tag=test1\r\n\ 275 | To: \r\n\ 276 | Call-ID: test-call-id-1\r\n\ 277 | CSeq: 1 REGISTER\r\n\ 278 | Contact: \r\n\ 279 | Max-Forwards: 70\r\n\ 280 | Content-Length: 11\r\n\r\nHello world"; 281 | 282 | let message2 = "REGISTER sip:example.com SIP/2.0\r\n\ 283 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test2\r\n\ 284 | From: ;tag=test2\r\n\ 285 | To: \r\n\ 286 | Call-ID: test-call-id-2\r\n\ 287 | CSeq: 1 REGISTER\r\n\ 288 | Contact: \r\n\ 289 | Max-Forwards: 70\r\n\ 290 | Content-Length: 6\r\n\r\nfoobar"; 291 | 292 | // Add both messages to buffer 293 | buffer.extend_from_slice(message1.as_bytes()); 294 | buffer.extend_from_slice(message2.as_bytes()); 295 | 296 | // First decode 297 | let result1 = codec 298 | .decode(&mut buffer) 299 | .expect("first decode should succeed"); 300 | assert!(result1.is_some(), "Should decode first message"); 301 | 302 | let msg1 = result1.unwrap(); 303 | match msg1 { 304 | crate::transport::stream::SipCodecType::Message(SipMessage::Request(req)) => { 305 | assert_eq!(req.call_id_header().unwrap().value(), "test-call-id-1"); 306 | } 307 | _ => panic!("Expected request message"), 308 | } 309 | 310 | // Second decode 311 | let result2 = codec 312 | .decode(&mut buffer) 313 | .expect("second decode should succeed"); 314 | assert!(result2.is_some(), "Should decode second message"); 315 | 316 | let msg2 = result2.unwrap(); 317 | match msg2 { 318 | crate::transport::stream::SipCodecType::Message(SipMessage::Request(req)) => { 319 | assert_eq!(req.call_id_header().unwrap().value(), "test-call-id-2"); 320 | } 321 | _ => panic!("Expected request message"), 322 | } 323 | 324 | // Buffer should be empty after consuming both messages 325 | assert_eq!( 326 | buffer.len(), 327 | 0, 328 | "Buffer should be empty after consuming all messages" 329 | ); 330 | } 331 | --------------------------------------------------------------------------------