├── .dockerignore ├── .gitignore ├── Cargo.toml ├── Dockerfile.sipproxy ├── LICENSE ├── README.md ├── assets └── example.pcmu ├── docs └── README.md ├── examples ├── client │ ├── main.rs │ ├── play_file.rs │ └── stun.rs ├── proxy.rs └── simple_connection │ └── main.rs └── src ├── bin └── bench_ua.rs ├── dialog ├── authenticate.rs ├── client_dialog.rs ├── dialog.rs ├── dialog_layer.rs ├── invitation.rs ├── mod.rs ├── registration.rs ├── server_dialog.rs └── tests │ ├── mod.rs │ ├── test_client_dialog.rs │ ├── test_dialog_layer.rs │ └── test_dialog_states.rs ├── error.rs ├── lib.rs ├── rsip_ext.rs ├── transaction ├── endpoint.rs ├── key.rs ├── message.rs ├── mod.rs ├── tests │ ├── mod.rs │ ├── test_client.rs │ ├── test_endpoint.rs │ ├── test_server.rs │ └── test_transaction_states.rs ├── timer.rs └── transaction.rs └── transport ├── channel.rs ├── connection.rs ├── mod.rs ├── sip_addr.rs ├── stream.rs ├── tcp.rs ├── tests ├── mod.rs ├── test_connection_management.rs ├── test_sipaddr.rs ├── test_stream_encoding.rs ├── test_udp.rs ├── test_via_received.rs └── transport_tests.rs ├── tls.rs ├── transport_layer.rs ├── udp.rs └── websocket.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsipstack" 3 | version = "0.2.10" 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.88" 23 | futures = "0.3.31" 24 | log = "0.4.27" 25 | rsip = { version = "0.4.0" } 26 | tracing = "0.1.41" 27 | wasm-bindgen = "0.2.84" 28 | # The `console_error_panic_hook` crate provides better debugging of panics by 29 | # logging them with `console.error`. This is great for development, but requires 30 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 31 | # code size when deploying. 32 | console_error_panic_hook = { version = "0.1.7", optional = true } 33 | wasm-bindgen-futures = "0.4.45" 34 | tokio-util = { version = "0.7.15", features = ["full"] } 35 | tracing-subscriber = { version = "0.3.19", features = ["local-time"] } 36 | uuid = { version = "1.16.0", features = ["v4"] } 37 | rand = { version = "0.9.1" } 38 | get_if_addrs = "0.5.3" 39 | rsip-dns = { version = "0.1.4", features = ["trust-dns"] } 40 | bytes = "1.10.1" 41 | futures-util = "0.3.30" 42 | tokio-tungstenite = { version = "0.26.2", optional = true } 43 | tokio-rustls = { version = "0.26.2", optional = true } 44 | rustls-pemfile = { version = "2.2.0", optional = true } 45 | webpki-roots = { version = "1.0.0", optional = true } 46 | rustls = "0.23.23" 47 | clap = { version = "4.5.37", features = ["derive"] } 48 | 49 | [features] 50 | default = ["console_error_panic_hook", "rustls", "websocket"] 51 | rustls = ["tokio-rustls", "rustls-pemfile", "webpki-roots"] 52 | websocket = ["tokio-tungstenite"] 53 | all-transports = ["rustls", "websocket"] 54 | 55 | [target.'cfg(target_arch = "wasm32")'.dependencies] 56 | tokio = { version = "1.44.2", features = ["time", "sync", "macros", "io-util"] } 57 | getrandom = { version = "0.3.2" } 58 | uuid = { version = "1.16.0" } 59 | 60 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 61 | tokio = { version = "1.44.2", features = ["full"] } 62 | 63 | [dev-dependencies] 64 | wasm-bindgen-test = "0.3.50" 65 | dotenv = "0.15" 66 | sdp-rs = "0.2.1" 67 | rtp-rs = "0.6.0" 68 | stun-rs = "0.1.11" 69 | openai-api-rs = "6.0.3" 70 | base64 = "0.22.1" 71 | serde = "1.0.217" 72 | serde_json = "1.0.140" 73 | dasp = { version = "0.11", features = ["all"] } 74 | 75 | # Examples configuration 76 | [[example]] 77 | name = "simple_connection" 78 | path = "examples/simple_connection/main.rs" 79 | 80 | [[example]] 81 | name = "client" 82 | path = "examples/client/main.rs" 83 | 84 | [[example]] 85 | name = "proxy" 86 | path = "examples/proxy.rs" 87 | 88 | [profile.release] 89 | #strip = true 90 | opt-level = "z" 91 | lto = true 92 | codegen-units = 1 93 | panic = "abort" 94 | 95 | [package.metadata.wasm-pack.profile.release] 96 | wasm-opt = true 97 | -------------------------------------------------------------------------------- /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"] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsipstack - A SIP Stack written in Rust 2 | 3 | **WIP** This is a work in progress and is not yet ready for production use. 4 | 5 | A RFC 3261 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 Compliant**: Full compliance with SIP specification 10 | - **Multiple Transport Support**: UDP, TCP, TLS, WebSocket 11 | - **Transaction Layer**: Complete SIP transaction state machine 12 | - **Dialog Layer**: SIP dialog management 13 | - **Digest Authentication**: Built-in authentication support 14 | - **High Performance**: Built with Rust for maximum performance 15 | - **Easy to Use**: Simple and intuitive API design 16 | 17 | ## TODO 18 | - [x] Transport support 19 | - [x] UDP 20 | - [x] TCP 21 | - [x] TLS 22 | - [x] WebSocket 23 | - [x] Digest Authentication 24 | - [x] Transaction Layer 25 | - [x] Dialog Layer 26 | - [ ] WASM target 27 | 28 | ## Use Cases 29 | 30 | This SIP stack can be used in various scenarios, including but not limited to: 31 | 32 | - Integration with WebRTC for browser-based communication, such as WebRTC SBC. 33 | - Building custom SIP proxies or registrars 34 | - Building custom SIP user agents (SIP.js alternative) 35 | 36 | ## Why Rust? 37 | 38 | 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. 39 | 40 | ## Quick Start Examples 41 | 42 | ### 1. Simple SIP Connection 43 | 44 | The most basic way to use rsipstack is through direct SIP connections, supporting both UDP and TCP transports: 45 | 46 | ```bash 47 | # Run as UDP server (default) 48 | cargo run --example simple_connection 49 | 50 | # Run as UDP client sending messages to a server 51 | cargo run --example simple_connection -- --mode client --target 127.0.0.1:5060 --port 5061 52 | 53 | # Run as TCP server 54 | cargo run --example simple_connection -- --transport tcp --mode server --port 5060 55 | 56 | ``` 57 | 58 | This example demonstrates: 59 | - Creating UDP/TCP connections and listeners 60 | - Sending raw SIP messages (OPTIONS, MESSAGE, REGISTER) 61 | - Handling incoming SIP requests and responses 62 | - Basic SIP message parsing and creation 63 | 64 | ### 2. SIP Proxy Server 65 | 66 | A stateful SIP proxy that routes calls between registered users: 67 | 68 | ```bash 69 | # Run proxy server 70 | cargo run --example proxy -- --port 25060 --addr 127.0.0.1 71 | 72 | # Run with external IP 73 | cargo run --example proxy -- --port 25060 --external-ip 1.2.3.4 74 | ``` 75 | 76 | This example demonstrates: 77 | - SIP user registration and location service 78 | - Call routing between registered users 79 | - Transaction forwarding and response handling 80 | - Session management for active calls 81 | - Handling INVITE, BYE, REGISTER, and ACK methods 82 | 83 | ### 3. SIP User Agent Client 84 | 85 | A complete SIP client with registration, calling, and media support: 86 | 87 | ```bash 88 | # Local demo proxy 89 | cargo run --example client -- --port 25061 --sip-server 127.0.0.1:25060 90 | 91 | # Register with a SIP server 92 | cargo run --example client -- --sip-server sip.example.com --user alice --password secret 93 | ``` 94 | 95 | This example demonstrates: 96 | - SIP user registration with digest authentication 97 | - Making and receiving SIP calls (INVITE/BYE) 98 | - Dialog management for call sessions 99 | - RTP media streaming with file playback 100 | - STUN support for NAT traversal 101 | 102 | 103 | ## API Usage Guide 104 | 105 | ### 1. Simple SIP Connection 106 | 107 | ```rust 108 | use rsipstack::transport::{udp::UdpConnection, SipAddr}; 109 | 110 | // Create UDP connection 111 | let connection = UdpConnection::create_connection("127.0.0.1:5060".parse()?, None).await?; 112 | 113 | // Send raw SIP message 114 | let sip_message = "OPTIONS sip:test@example.com SIP/2.0\r\n..."; 115 | connection.send_raw(sip_message.as_bytes(), &target_addr).await?; 116 | ``` 117 | 118 | ### 2. Using Endpoint and Transactions 119 | 120 | ```rust 121 | use rsipstack::{EndpointBuilder, transport::TransportLayer}; 122 | use tokio_util::sync::CancellationToken; 123 | 124 | // Build endpoint with transport layer 125 | let cancel_token = CancellationToken::new(); 126 | let transport_layer = TransportLayer::new(cancel_token.clone()); 127 | let endpoint = EndpointBuilder::new() 128 | .with_transport_layer(transport_layer) 129 | .with_cancel_token(cancel_token) 130 | .build(); 131 | 132 | // Handle incoming transactions 133 | let mut incoming = endpoint.incoming_transactions(); 134 | while let Some(transaction) = incoming.recv().await { 135 | // Process transaction based on method 136 | match transaction.original.method { 137 | rsip::Method::Register => { 138 | transaction.reply(rsip::StatusCode::OK).await?; 139 | } 140 | rsip::Method::Options => { 141 | transaction.reply(rsip::StatusCode::OK).await?; 142 | } 143 | // ... handle other methods 144 | } 145 | } 146 | ``` 147 | 148 | ### 3. Creating a User Agent Client 149 | 150 | ```rust 151 | use rsipstack::dialog::{DialogLayer, registration::Registration}; 152 | use rsipstack::dialog::authenticate::Credential; 153 | 154 | // Create dialog layer 155 | let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone())); 156 | 157 | // Register with server 158 | let credential = Credential { 159 | username: "alice".to_string(), 160 | password: "secret".to_string(), 161 | realm: None, 162 | }; 163 | 164 | let registration = Registration::new( 165 | endpoint.inner.clone(), 166 | "sip:alice@example.com".parse()?, 167 | "sip:registrar.example.com".parse()?, 168 | credential, 169 | )?; 170 | 171 | // Make outgoing call 172 | let invite_option = InviteOption { 173 | callee: "sip:bob@example.com".parse()?, 174 | caller: "sip:alice@example.com".parse()?, 175 | content_type: None, 176 | offer: None, 177 | contact: "sip:alice@192.168.1.100:5060".parse()?, 178 | credential: Some(credential), 179 | headers: None, 180 | }; 181 | 182 | let invite_dialog = dialog_layer.create_invite_dialog(invite_option).await?; 183 | ``` 184 | 185 | ### 4. Implementing a Proxy 186 | 187 | ```rust 188 | use rsipstack::transaction::Transaction; 189 | use std::collections::HashMap; 190 | 191 | // Handle incoming requests 192 | while let Some(mut transaction) = incoming.recv().await { 193 | match transaction.original.method { 194 | rsip::Method::Register => { 195 | // Store user registration 196 | let user = User::try_from(&transaction.original)?; 197 | users.insert(user.username.clone(), user); 198 | transaction.reply(rsip::StatusCode::OK).await?; 199 | } 200 | rsip::Method::Invite => { 201 | // Route call to registered user 202 | let callee = extract_callee(&transaction.original)?; 203 | if let Some(target) = users.get(&callee) { 204 | // Forward request 205 | let mut forwarded_tx = transaction.create_client_transaction()?; 206 | forwarded_tx.destination = Some(target.destination.clone()); 207 | forwarded_tx.send().await?; 208 | } else { 209 | transaction.reply(rsip::StatusCode::NotFound).await?; 210 | } 211 | } 212 | // ... handle other methods 213 | } 214 | } 215 | ``` 216 | 217 | ## Running Tests 218 | 219 | ### Unit Tests 220 | ```bash 221 | cargo test 222 | ``` 223 | 224 | 225 | ### Benchmark Tests 226 | ```bash 227 | # Run server 228 | cargo run -r --bin bench_ua -- -m server -p 5060 229 | 230 | # Run client with 1000 calls 231 | cargo run -r --bin bench_ua -- -m client -p 5061 -s 127.0.0.1:5060 -c 1000 232 | ``` 233 | 234 | The test monitor: 235 | 236 | ```bash 237 | === SIP Benchmark UA Stats === 238 | Dialogs: 9992 239 | Active Calls: 9983 240 | Rejected Calls: 0 241 | Failed Calls: 0 242 | Total Calls: 250276 243 | Calls/Second: 1501 244 | ============================ 245 | ``` 246 | 247 | ## Documentation 248 | 249 | - [API Documentation](https://docs.rs/rsipstack) 250 | - [Examples](./examples/) 251 | 252 | ## Contributing 253 | 254 | We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. 255 | 256 | ## License 257 | 258 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /assets/example.pcmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/restsend/rsipstack/708235da762bcab527fe85ae944a0e8dbaaab51d/assets/example.pcmu -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /examples/client/play_file.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::time::Duration; 3 | 4 | use rsipstack::transport::udp::UdpConnection; 5 | use rsipstack::Result; 6 | use rsipstack::{transport::SipAddr, Error}; 7 | use rtp_rs::RtpPacketBuilder; 8 | use tokio::select; 9 | use tokio_util::sync::CancellationToken; 10 | use tracing::info; 11 | 12 | use crate::{stun, MediaSessionOption}; 13 | 14 | pub async fn build_rtp_conn( 15 | opt: &MediaSessionOption, 16 | ssrc: u32, 17 | ) -> Result<(UdpConnection, String)> { 18 | let addr = stun::get_first_non_loopback_interface()?; 19 | let mut conn = None; 20 | 21 | for p in 0..100 { 22 | let port = opt.rtp_start_port + p * 2; 23 | if let Ok(c) = 24 | UdpConnection::create_connection(format!("{:?}:{}", addr, port).parse()?, None).await 25 | { 26 | conn = Some(c); 27 | break; 28 | } else { 29 | info!("Failed to bind RTP socket on port: {}", port); 30 | } 31 | } 32 | 33 | if conn.is_none() { 34 | return Err(Error::Error("Failed to bind RTP socket".to_string())); 35 | } 36 | 37 | let mut conn = conn.unwrap(); 38 | if opt.external_ip.is_none() && opt.stun { 39 | if let Some(ref server) = opt.stun_server { 40 | match stun::external_by_stun(&mut conn, &server, Duration::from_secs(5)).await { 41 | Ok(socket) => info!("media external IP by stun: {:?}", socket), 42 | Err(e) => info!( 43 | "Failed to get media external IP, stunserver {} : {:?}", 44 | server, e 45 | ), 46 | } 47 | } 48 | } 49 | let codec = 0; 50 | let codec_name = "PCMU"; 51 | let socketaddr: SocketAddr = conn.get_addr().addr.to_owned().try_into()?; 52 | let sdp = format!( 53 | "v=0\r\n\ 54 | o=- 0 0 IN IP4 {}\r\n\ 55 | s=rsipstack example\r\n\ 56 | c=IN IP4 {}\r\n\ 57 | t=0 0\r\n\ 58 | m=audio {} RTP/AVP {codec}\r\n\ 59 | a=rtpmap:{codec} {codec_name}/8000\r\n\ 60 | a=ssrc:{ssrc}\r\n\ 61 | a=sendrecv\r\n", 62 | socketaddr.ip(), 63 | socketaddr.ip(), 64 | socketaddr.port(), 65 | ); 66 | info!("RTP socket: {:?} {}", conn.get_addr(), sdp); 67 | Ok((conn, sdp)) 68 | } 69 | 70 | pub async fn play_example_file( 71 | conn: UdpConnection, 72 | token: CancellationToken, 73 | ssrc: u32, 74 | peer_addr: String, 75 | ) -> Result<()> { 76 | select! { 77 | _ = token.cancelled() => { 78 | info!("RTP session cancelled"); 79 | } 80 | _ = async { 81 | let peer_addr = SipAddr{ 82 | addr: peer_addr.try_into().expect("peer_addr"), 83 | r#type: Some(rsip::transport::Transport::Udp), 84 | }; 85 | let mut ts = 0; 86 | let sample_size = 160; 87 | let mut seq = 1; 88 | let mut ticker = tokio::time::interval(Duration::from_millis(20)); 89 | 90 | let example_data = tokio::fs::read("./assets/example.pcmu").await.expect("read example.pcmu"); 91 | 92 | for chunk in example_data.chunks(sample_size) { 93 | let result = match RtpPacketBuilder::new() 94 | .payload_type(0) 95 | .ssrc(ssrc) 96 | .sequence(seq.into()) 97 | .timestamp(ts) 98 | .payload(&chunk) 99 | .build() { 100 | Ok(r) => r, 101 | Err(e) => { 102 | info!("Failed to build RTP packet: {:?}", e); 103 | break; 104 | } 105 | }; 106 | ts += chunk.len() as u32; 107 | seq += 1; 108 | match conn.send_raw(&result, &peer_addr).await { 109 | Ok(_) => {}, 110 | Err(e) => { 111 | info!("Failed to send RTP: {:?}", e); 112 | break; 113 | } 114 | } 115 | ticker.tick().await; 116 | } 117 | } => { 118 | info!("playback finished, hangup"); 119 | } 120 | }; 121 | Ok(()) 122 | } 123 | -------------------------------------------------------------------------------- /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 | get_if_addrs()? 17 | .iter() 18 | .find(|i| !i.is_loopback()) 19 | .map(|i| match i.addr { 20 | get_if_addrs::IfAddr::V4(ref addr) => Ok(std::net::IpAddr::V4(addr.ip)), 21 | _ => Err(Error::Error("No IPv4 address found".to_string())), 22 | }) 23 | .unwrap_or(Err(Error::Error("No interface found".to_string()))) 24 | } 25 | 26 | pub async fn external_by_stun( 27 | conn: &mut UdpConnection, 28 | stun_server: &str, 29 | expires: Duration, 30 | ) -> Result { 31 | info!("getting external IP by STUN server: {}", stun_server); 32 | let msg = StunMessageBuilder::new(BINDING, MessageClass::Request).build(); 33 | 34 | let encoder = MessageEncoderBuilder::default().build(); 35 | let mut buffer: [u8; 150] = [0x00; 150]; 36 | encoder 37 | .encode(&mut buffer, &msg) 38 | .map_err(|e| crate::Error::Error(e.to_string()))?; 39 | 40 | let mut addrs = lookup_host(stun_server).await?; 41 | let target = addrs 42 | .next() 43 | .ok_or_else(|| crate::Error::Error("STUN server address not found".to_string()))?; 44 | 45 | conn.send_raw( 46 | &buffer, 47 | &SipAddr { 48 | addr: target.into(), 49 | r#type: None, 50 | }, 51 | ) 52 | .await?; 53 | 54 | let mut buf = [0u8; 2048]; 55 | let (len, _) = select! { 56 | _ = sleep(expires) => { 57 | info!("stun timeout {}", stun_server); 58 | return Err(Error::Error("STUN server timeout".to_string())); 59 | } 60 | r = conn.recv_raw(&mut buf) => { 61 | r? 62 | } 63 | }; 64 | 65 | let decoder = MessageDecoderBuilder::default().build(); 66 | let (resp, _) = decoder 67 | .decode(&buf[..len]) 68 | .map_err(|e| crate::Error::Error(e.to_string()))?; 69 | 70 | let xor_addr = resp 71 | .get::() 72 | .ok_or(crate::Error::Error( 73 | "XorMappedAddress attribute not found".to_string(), 74 | ))? 75 | .as_xor_mapped_address() 76 | .map_err(|e| crate::Error::Error(e.to_string()))?; 77 | let socket: &SocketAddr = xor_addr.socket_address(); 78 | info!("external IP: {}", socket); 79 | conn.external = Some(SipAddr { 80 | r#type: Some(rsip::transport::Transport::Udp), 81 | addr: socket.to_owned().into(), 82 | }); 83 | Ok(socket.clone()) 84 | } 85 | 86 | #[tokio::test] 87 | async fn test_external_with_stun() -> Result<()> { 88 | let addrs = tokio::net::lookup_host("restsend.com:3478").await?; 89 | for addr in addrs { 90 | info!("stun server: {}", addr); 91 | } 92 | let mut peer_bob = UdpConnection::create_connection("0.0.0.0:0".parse()?, None).await?; 93 | let expires = Duration::from_secs(5); 94 | external_by_stun(&mut peer_bob, "restsend.com:3478", expires).await?; 95 | info!("external IP: {:?}", peer_bob.get_addr()); 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /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; 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 | /// headers: None, 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 proxy_header = rsip::header_opt!(resp.headers().iter(), Header::ProxyAuthenticate); 197 | let proxy_header = proxy_header.ok_or(crate::Error::DialogError( 198 | "missing proxy/www authenticate".to_string(), 199 | DialogId::try_from(&tx.original)?, 200 | ))?; 201 | Header::ProxyAuthenticate(proxy_header.clone()) 202 | } 203 | }; 204 | 205 | let mut new_req = tx.original.clone(); 206 | new_req.cseq_header_mut()?.mut_seq(new_seq)?; 207 | 208 | let auth_qop = AuthQop::Auth { 209 | cnonce: random_text(CNONCE_LEN), 210 | nc: 1, 211 | }; 212 | 213 | let challenge = match &header { 214 | Header::WwwAuthenticate(h) => h.typed()?, 215 | Header::ProxyAuthenticate(h) => h.typed()?.0, 216 | _ => unreachable!(), 217 | }; 218 | 219 | // Use MD5 as default algorithm if none specified (RFC 2617 compatibility) 220 | let algorithm = challenge.algorithm.unwrap_or(rsip::headers::auth::Algorithm::Md5); 221 | 222 | let response = DigestGenerator { 223 | username: cred.username.as_str(), 224 | password: cred.password.as_str(), 225 | algorithm, 226 | nonce: challenge.nonce.as_str(), 227 | method: &tx.original.method, 228 | qop: Some(&auth_qop), 229 | uri: &tx.original.uri, 230 | realm: challenge.realm.as_str(), 231 | } 232 | .compute(); 233 | 234 | let auth = Authorization { 235 | scheme: challenge.scheme, 236 | username: cred.username.clone(), 237 | realm: challenge.realm, 238 | nonce: challenge.nonce, 239 | uri: tx.original.uri.clone(), 240 | response, 241 | algorithm: Some(algorithm), 242 | opaque: challenge.opaque, 243 | qop: Some(auth_qop), 244 | }; 245 | 246 | let via_header = tx.original.via_header()?.clone(); 247 | 248 | // update new branch 249 | let mut params = via_header.params().clone()?; 250 | params.push(make_via_branch()); 251 | params.push(Param::Other("rport".into(), None)); 252 | new_req.headers_mut().unique_push(via_header.into()); 253 | 254 | new_req.headers_mut().retain(|h| { 255 | !matches!( 256 | h, 257 | Header::ProxyAuthenticate(_) 258 | | Header::Authorization(_) 259 | | Header::WwwAuthenticate(_) 260 | | Header::ProxyAuthorization(_) 261 | ) 262 | }); 263 | 264 | match header { 265 | Header::WwwAuthenticate(_) => { 266 | new_req.headers_mut().unique_push(auth.into()); 267 | } 268 | Header::ProxyAuthenticate(_) => { 269 | new_req 270 | .headers_mut() 271 | .unique_push(ProxyAuthorization(auth).into()); 272 | } 273 | _ => unreachable!(), 274 | } 275 | let key = TransactionKey::from_request(&new_req, TransactionRole::Client)?; 276 | let mut new_tx = Transaction::new_client( 277 | key, 278 | new_req, 279 | tx.endpoint_inner.clone(), 280 | tx.connection.clone(), 281 | ); 282 | new_tx.destination = tx.destination.clone(); 283 | Ok(new_tx) 284 | } 285 | -------------------------------------------------------------------------------- /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; 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::Request; 10 | use std::sync::atomic::{AtomicU32, Ordering}; 11 | use std::{ 12 | collections::HashMap, 13 | sync::{Arc, RwLock}, 14 | }; 15 | use tracing::info; 16 | 17 | /// Internal Dialog Layer State 18 | /// 19 | /// `DialogLayerInner` contains the core state for managing multiple SIP dialogs. 20 | /// It maintains a registry of active dialogs and tracks sequence numbers for 21 | /// dialog creation. 22 | /// 23 | /// # Fields 24 | /// 25 | /// * `last_seq` - Atomic counter for generating unique sequence numbers 26 | /// * `dialogs` - Thread-safe map of active dialogs indexed by DialogId 27 | /// 28 | /// # Thread Safety 29 | /// 30 | /// This structure is designed to be shared across multiple threads safely: 31 | /// * `last_seq` uses atomic operations for lock-free increments 32 | /// * `dialogs` uses RwLock for concurrent read access with exclusive writes 33 | pub struct DialogLayerInner { 34 | pub(super) last_seq: AtomicU32, 35 | pub(super) dialogs: RwLock>, 36 | } 37 | pub type DialogLayerInnerRef = Arc; 38 | 39 | /// SIP Dialog Layer 40 | /// 41 | /// `DialogLayer` provides high-level dialog management functionality for SIP 42 | /// applications. It handles dialog creation, lookup, and lifecycle management 43 | /// while coordinating with the transaction layer. 44 | /// 45 | /// # Key Responsibilities 46 | /// 47 | /// * Creating and managing SIP dialogs 48 | /// * Dialog identification and routing 49 | /// * Dialog state tracking and cleanup 50 | /// * Integration with transaction layer 51 | /// * Sequence number management 52 | /// 53 | /// # Usage Patterns 54 | /// 55 | /// ## Server-side Dialog Creation 56 | /// 57 | /// ```rust,no_run 58 | /// use rsipstack::dialog::dialog_layer::DialogLayer; 59 | /// use rsipstack::transaction::endpoint::EndpointInner; 60 | /// use std::sync::Arc; 61 | /// 62 | /// # fn example() -> rsipstack::Result<()> { 63 | /// # let endpoint: Arc = todo!(); 64 | /// # let transaction = todo!(); 65 | /// # let state_sender = todo!(); 66 | /// # let credential = None; 67 | /// # let contact_uri = None; 68 | /// // Create dialog layer 69 | /// let dialog_layer = DialogLayer::new(endpoint.clone()); 70 | /// 71 | /// // Handle incoming INVITE transaction 72 | /// let server_dialog = dialog_layer.get_or_create_server_invite( 73 | /// &transaction, 74 | /// state_sender, 75 | /// credential, 76 | /// contact_uri 77 | /// )?; 78 | /// 79 | /// // Accept the call 80 | /// server_dialog.accept(None, None)?; 81 | /// # Ok(()) 82 | /// # } 83 | /// ``` 84 | /// 85 | /// ## Dialog Lookup and Routing 86 | /// 87 | /// ```rust,no_run 88 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 89 | /// # async fn example() -> rsipstack::Result<()> { 90 | /// # let dialog_layer: DialogLayer = todo!(); 91 | /// # let request = todo!(); 92 | /// # let transaction = todo!(); 93 | /// // Find existing dialog for incoming request 94 | /// if let Some(mut dialog) = dialog_layer.match_dialog(&request) { 95 | /// // Route to existing dialog 96 | /// dialog.handle(transaction).await?; 97 | /// } else { 98 | /// // Create new dialog or reject 99 | /// } 100 | /// # Ok(()) 101 | /// # } 102 | /// ``` 103 | /// 104 | /// ## Dialog Cleanup 105 | /// 106 | /// ```rust,no_run 107 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 108 | /// # fn example() { 109 | /// # let dialog_layer: DialogLayer = todo!(); 110 | /// # let dialog_id = todo!(); 111 | /// // Remove completed dialog 112 | /// dialog_layer.remove_dialog(&dialog_id); 113 | /// # } 114 | /// ``` 115 | /// 116 | /// # Dialog Lifecycle 117 | /// 118 | /// 1. **Creation** - Dialog created from incoming INVITE or outgoing request 119 | /// 2. **Early State** - Dialog exists but not yet confirmed 120 | /// 3. **Confirmed** - Dialog established with 2xx response and ACK 121 | /// 4. **Active** - Dialog can exchange in-dialog requests 122 | /// 5. **Terminated** - Dialog ended with BYE or error 123 | /// 6. **Cleanup** - Dialog removed from layer 124 | /// 125 | /// # Thread Safety 126 | /// 127 | /// DialogLayer is thread-safe and can be shared across multiple tasks: 128 | /// * Dialog lookup operations are concurrent 129 | /// * Dialog creation is serialized when needed 130 | /// * Automatic cleanup prevents memory leaks 131 | pub struct DialogLayer { 132 | pub endpoint: EndpointInnerRef, 133 | pub inner: DialogLayerInnerRef, 134 | } 135 | 136 | impl DialogLayer { 137 | pub fn new(endpoint: EndpointInnerRef) -> Self { 138 | Self { 139 | endpoint, 140 | inner: Arc::new(DialogLayerInner { 141 | last_seq: AtomicU32::new(0), 142 | dialogs: RwLock::new(HashMap::new()), 143 | }), 144 | } 145 | } 146 | 147 | pub fn get_or_create_server_invite( 148 | &self, 149 | tx: &Transaction, 150 | state_sender: DialogStateSender, 151 | credential: Option, 152 | contact: Option, 153 | ) -> Result { 154 | let mut id = DialogId::try_from(&tx.original)?; 155 | if !id.to_tag.is_empty() { 156 | let dlg = self.inner.dialogs.read().unwrap().get(&id).cloned(); 157 | match dlg { 158 | Some(Dialog::ServerInvite(dlg)) => return Ok(dlg), 159 | _ => { 160 | return Err(crate::Error::DialogError( 161 | "the dialog not found".to_string(), 162 | id, 163 | )); 164 | } 165 | } 166 | } 167 | id.to_tag = make_tag().to_string(); // generate to tag 168 | 169 | let dlg_inner = DialogInner::new( 170 | TransactionRole::Server, 171 | id.clone(), 172 | tx.original.clone(), 173 | self.endpoint.clone(), 174 | state_sender, 175 | credential, 176 | contact, 177 | )?; 178 | 179 | let dialog = ServerInviteDialog { 180 | inner: Arc::new(dlg_inner), 181 | }; 182 | self.inner 183 | .dialogs 184 | .write() 185 | .unwrap() 186 | .insert(id.clone(), Dialog::ServerInvite(dialog.clone())); 187 | info!("server invite dialog created: {id}"); 188 | Ok(dialog) 189 | } 190 | 191 | pub fn increment_last_seq(&self) -> u32 { 192 | self.inner.last_seq.fetch_add(1, Ordering::Relaxed); 193 | self.inner.last_seq.load(Ordering::Relaxed) 194 | } 195 | 196 | pub fn len(&self) -> usize { 197 | self.inner.dialogs.read().unwrap().len() 198 | } 199 | 200 | pub fn get_dialog(&self, id: &DialogId) -> Option { 201 | let dialogs = self.inner.dialogs.read().unwrap(); 202 | match dialogs.get(id) { 203 | Some(dialog) => return Some(dialog.clone()), 204 | None => {} 205 | } 206 | let swap_id = DialogId { 207 | call_id: id.call_id.clone(), 208 | from_tag: id.to_tag.clone(), 209 | to_tag: id.from_tag.clone(), 210 | }; 211 | match dialogs.get(&swap_id) { 212 | Some(dialog) => Some(dialog.clone()), 213 | None => None, 214 | } 215 | } 216 | 217 | pub fn remove_dialog(&self, id: &DialogId) { 218 | info!("remove dialog: {id}"); 219 | self.inner 220 | .dialogs 221 | .write() 222 | .unwrap() 223 | .remove(id) 224 | .map(|d| d.on_remove()); 225 | } 226 | 227 | pub fn match_dialog(&self, req: &Request) -> Option { 228 | let id = DialogId::try_from(req).ok()?; 229 | self.get_dialog(&id) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/dialog/invitation.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | authenticate::Credential, 3 | client_dialog::ClientInviteDialog, 4 | dialog::{DialogInner, DialogStateSender}, 5 | dialog_layer::DialogLayer, 6 | }; 7 | use crate::{ 8 | dialog::{dialog::Dialog, DialogId}, 9 | transaction::{ 10 | key::{TransactionKey, TransactionRole}, 11 | make_tag, 12 | transaction::Transaction, 13 | }, 14 | Result, 15 | }; 16 | use rsip::{Request, Response}; 17 | use std::sync::Arc; 18 | use tracing::{debug, info}; 19 | 20 | /// INVITE Request Options 21 | /// 22 | /// `InviteOption` contains all the parameters needed to create and send 23 | /// an INVITE request to establish a SIP session. This structure provides 24 | /// a convenient way to specify all the necessary information for initiating 25 | /// a call or session. 26 | /// 27 | /// # Fields 28 | /// 29 | /// * `caller` - URI of the calling party (From header) 30 | /// * `callee` - URI of the called party (To header and Request-URI) 31 | /// * `content_type` - MIME type of the message body (default: "application/sdp") 32 | /// * `offer` - Optional message body (typically SDP offer) 33 | /// * `contact` - Contact URI for this user agent 34 | /// * `credential` - Optional authentication credentials 35 | /// * `headers` - Optional additional headers to include 36 | /// 37 | /// # Examples 38 | /// 39 | /// ## Basic Voice Call 40 | /// 41 | /// ```rust,no_run 42 | /// # use rsipstack::dialog::invitation::InviteOption; 43 | /// # fn example() -> rsipstack::Result<()> { 44 | /// # let sdp_offer_bytes = vec![]; 45 | /// let invite_option = InviteOption { 46 | /// caller: "sip:alice@example.com".try_into()?, 47 | /// callee: "sip:bob@example.com".try_into()?, 48 | /// content_type: Some("application/sdp".to_string()), 49 | /// offer: Some(sdp_offer_bytes), 50 | /// contact: "sip:alice@192.168.1.100:5060".try_into()?, 51 | /// credential: None, 52 | /// headers: None, 53 | /// }; 54 | /// # Ok(()) 55 | /// # } 56 | /// ``` 57 | /// 58 | /// ```rust,no_run 59 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 60 | /// # use rsipstack::dialog::invitation::InviteOption; 61 | /// # fn example() -> rsipstack::Result<()> { 62 | /// # let dialog_layer: DialogLayer = todo!(); 63 | /// # let invite_option: InviteOption = todo!(); 64 | /// let request = dialog_layer.make_invite_request(&invite_option)?; 65 | /// println!("Created INVITE to: {}", request.uri); 66 | /// # Ok(()) 67 | /// # } 68 | /// ``` 69 | /// 70 | /// ## Call with Custom Headers 71 | /// 72 | /// ```rust,no_run 73 | /// # use rsipstack::dialog::invitation::InviteOption; 74 | /// # fn example() -> rsipstack::Result<()> { 75 | /// # let sdp_bytes = vec![]; 76 | /// # let auth_credential = todo!(); 77 | /// let custom_headers = vec![ 78 | /// rsip::Header::UserAgent("MyApp/1.0".into()), 79 | /// rsip::Header::Subject("Important Call".into()), 80 | /// ]; 81 | /// 82 | /// let invite_option = InviteOption { 83 | /// caller: "sip:alice@example.com".try_into()?, 84 | /// callee: "sip:bob@example.com".try_into()?, 85 | /// content_type: Some("application/sdp".to_string()), 86 | /// offer: Some(sdp_bytes), 87 | /// contact: "sip:alice@192.168.1.100:5060".try_into()?, 88 | /// credential: Some(auth_credential), 89 | /// headers: Some(custom_headers), 90 | /// }; 91 | /// # Ok(()) 92 | /// # } 93 | /// ``` 94 | /// 95 | /// ## Call with Authentication 96 | /// 97 | /// ```rust,no_run 98 | /// # use rsipstack::dialog::invitation::InviteOption; 99 | /// # use rsipstack::dialog::authenticate::Credential; 100 | /// # fn example() -> rsipstack::Result<()> { 101 | /// # let sdp_bytes = vec![]; 102 | /// let credential = Credential { 103 | /// username: "alice".to_string(), 104 | /// password: "secret123".to_string(), 105 | /// realm: Some("example.com".to_string()), 106 | /// }; 107 | /// 108 | /// let invite_option = InviteOption { 109 | /// caller: "sip:alice@example.com".try_into()?, 110 | /// callee: "sip:bob@example.com".try_into()?, 111 | /// content_type: None, // Will default to "application/sdp" 112 | /// offer: Some(sdp_bytes), 113 | /// contact: "sip:alice@192.168.1.100:5060".try_into()?, 114 | /// credential: Some(credential), 115 | /// headers: None, 116 | /// }; 117 | /// # Ok(()) 118 | /// # } 119 | /// ``` 120 | pub struct InviteOption { 121 | pub caller: rsip::Uri, 122 | pub callee: rsip::Uri, 123 | pub content_type: Option, 124 | pub offer: Option>, 125 | pub contact: rsip::Uri, 126 | pub credential: Option, 127 | pub headers: Option>, 128 | } 129 | 130 | impl DialogLayer { 131 | /// Create an INVITE request from options 132 | /// 133 | /// Constructs a properly formatted SIP INVITE request based on the 134 | /// provided options. This method handles all the required headers 135 | /// and parameters according to RFC 3261. 136 | /// 137 | /// # Parameters 138 | /// 139 | /// * `opt` - INVITE options containing all necessary parameters 140 | /// 141 | /// # Returns 142 | /// 143 | /// * `Ok(Request)` - Properly formatted INVITE request 144 | /// * `Err(Error)` - Failed to create request 145 | /// 146 | /// # Generated Headers 147 | /// 148 | /// The method automatically generates: 149 | /// * Via header with branch parameter 150 | /// * From header with tag parameter 151 | /// * To header (without tag for initial request) 152 | /// * Contact header 153 | /// * Content-Type header 154 | /// * CSeq header with incremented sequence number 155 | /// * Call-ID header 156 | /// 157 | /// # Examples 158 | /// 159 | /// ```rust,no_run 160 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 161 | /// # use rsipstack::dialog::invitation::InviteOption; 162 | /// # fn example() -> rsipstack::Result<()> { 163 | /// # let dialog_layer: DialogLayer = todo!(); 164 | /// # let invite_option: InviteOption = todo!(); 165 | /// let request = dialog_layer.make_invite_request(&invite_option)?; 166 | /// println!("Created INVITE to: {}", request.uri); 167 | /// # Ok(()) 168 | /// # } 169 | /// ``` 170 | pub fn make_invite_request(&self, opt: &InviteOption) -> Result { 171 | let last_seq = self.increment_last_seq(); 172 | let to = rsip::typed::To { 173 | display_name: None, 174 | uri: opt.callee.clone(), 175 | params: vec![], 176 | }; 177 | let recipient = to.uri.clone(); 178 | 179 | let form = rsip::typed::From { 180 | display_name: None, 181 | uri: opt.caller.clone(), 182 | params: vec![], 183 | } 184 | .with_tag(make_tag()); 185 | 186 | let via = self.endpoint.get_via(None, None)?; 187 | let mut request = 188 | self.endpoint 189 | .make_request(rsip::Method::Invite, recipient, via, form, to, last_seq); 190 | 191 | let contact = rsip::typed::Contact { 192 | display_name: None, 193 | uri: opt.contact.clone(), 194 | params: vec![], 195 | }; 196 | 197 | request 198 | .headers 199 | .unique_push(rsip::Header::Contact(contact.into())); 200 | 201 | request.headers.unique_push(rsip::Header::ContentType( 202 | opt.content_type 203 | .clone() 204 | .unwrap_or("application/sdp".to_string()) 205 | .into(), 206 | )); 207 | // can override default headers 208 | if let Some(headers) = opt.headers.as_ref() { 209 | for header in headers { 210 | request.headers.unique_push(header.clone()); 211 | } 212 | } 213 | Ok(request) 214 | } 215 | 216 | /// Send an INVITE request and create a client dialog 217 | /// 218 | /// This is the main method for initiating outbound calls. It creates 219 | /// an INVITE request, sends it, and manages the resulting dialog. 220 | /// The method handles the complete INVITE transaction including 221 | /// authentication challenges and response processing. 222 | /// 223 | /// # Parameters 224 | /// 225 | /// * `opt` - INVITE options containing all call parameters 226 | /// * `state_sender` - Channel for receiving dialog state updates 227 | /// 228 | /// # Returns 229 | /// 230 | /// * `Ok((ClientInviteDialog, Option))` - Created dialog and final response 231 | /// * `Err(Error)` - Failed to send INVITE or process responses 232 | /// 233 | /// # Call Flow 234 | /// 235 | /// 1. Creates INVITE request from options 236 | /// 2. Creates client dialog and transaction 237 | /// 3. Sends INVITE request 238 | /// 4. Processes responses (1xx, 2xx, 3xx-6xx) 239 | /// 5. Handles authentication challenges if needed 240 | /// 6. Returns established dialog and final response 241 | /// 242 | /// # Examples 243 | /// 244 | /// ## Basic Call Setup 245 | /// 246 | /// ```rust,no_run 247 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 248 | /// # use rsipstack::dialog::invitation::InviteOption; 249 | /// # async fn example() -> rsipstack::Result<()> { 250 | /// # let dialog_layer: DialogLayer = todo!(); 251 | /// # let invite_option: InviteOption = todo!(); 252 | /// # let state_sender = todo!(); 253 | /// let (dialog, response) = dialog_layer.do_invite(invite_option, state_sender).await?; 254 | /// 255 | /// if let Some(resp) = response { 256 | /// match resp.status_code { 257 | /// rsip::StatusCode::OK => { 258 | /// println!("Call answered!"); 259 | /// // Process SDP answer in resp.body 260 | /// }, 261 | /// rsip::StatusCode::BusyHere => { 262 | /// println!("Called party is busy"); 263 | /// }, 264 | /// _ => { 265 | /// println!("Call failed: {}", resp.status_code); 266 | /// } 267 | /// } 268 | /// } 269 | /// # Ok(()) 270 | /// # } 271 | /// ``` 272 | /// 273 | /// ## Monitoring Dialog State 274 | /// 275 | /// ```rust,no_run 276 | /// # use rsipstack::dialog::dialog_layer::DialogLayer; 277 | /// # use rsipstack::dialog::invitation::InviteOption; 278 | /// # use rsipstack::dialog::dialog::DialogState; 279 | /// # async fn example() -> rsipstack::Result<()> { 280 | /// # let dialog_layer: DialogLayer = todo!(); 281 | /// # let invite_option: InviteOption = todo!(); 282 | /// let (state_tx, mut state_rx) = tokio::sync::mpsc::unbounded_channel(); 283 | /// let (dialog, response) = dialog_layer.do_invite(invite_option, state_tx).await?; 284 | /// 285 | /// // Monitor dialog state changes 286 | /// tokio::spawn(async move { 287 | /// while let Some(state) = state_rx.recv().await { 288 | /// match state { 289 | /// DialogState::Early(_, resp) => { 290 | /// println!("Ringing: {}", resp.status_code); 291 | /// }, 292 | /// DialogState::Confirmed(_) => { 293 | /// println!("Call established"); 294 | /// }, 295 | /// DialogState::Terminated(_, code) => { 296 | /// println!("Call ended: {:?}", code); 297 | /// break; 298 | /// }, 299 | /// _ => {} 300 | /// } 301 | /// } 302 | /// }); 303 | /// # Ok(()) 304 | /// # } 305 | /// ``` 306 | /// 307 | /// # Error Handling 308 | /// 309 | /// The method can fail for various reasons: 310 | /// * Network connectivity issues 311 | /// * Authentication failures 312 | /// * Invalid SIP URIs or headers 313 | /// * Transaction timeouts 314 | /// * Protocol violations 315 | /// 316 | /// # Authentication 317 | /// 318 | /// If credentials are provided in the options, the method will 319 | /// automatically handle 401/407 authentication challenges by 320 | /// resending the request with proper authentication headers. 321 | pub async fn do_invite( 322 | &self, 323 | opt: InviteOption, 324 | state_sender: DialogStateSender, 325 | ) -> Result<(ClientInviteDialog, Option)> { 326 | let mut request = self.make_invite_request(&opt)?; 327 | request.body = opt.offer.unwrap_or_default(); 328 | request.headers.unique_push(rsip::Header::ContentLength( 329 | (request.body.len() as u32).into(), 330 | )); 331 | 332 | let id = DialogId::try_from(&request)?; 333 | let dlg_inner = DialogInner::new( 334 | TransactionRole::Client, 335 | id.clone(), 336 | request.clone(), 337 | self.endpoint.clone(), 338 | state_sender, 339 | opt.credential, 340 | Some(opt.contact), 341 | )?; 342 | 343 | let key = 344 | TransactionKey::from_request(&dlg_inner.initial_request, TransactionRole::Client)?; 345 | let tx = Transaction::new_client(key, request.clone(), self.endpoint.clone(), None); 346 | 347 | let dialog = ClientInviteDialog { 348 | inner: Arc::new(dlg_inner), 349 | }; 350 | self.inner 351 | .dialogs 352 | .write() 353 | .unwrap() 354 | .insert(id.clone(), Dialog::ClientInvite(dialog.clone())); 355 | 356 | info!("client invite dialog created: {:?}", id); 357 | 358 | match dialog.process_invite(tx).await { 359 | Ok((new_dialog_id, resp)) => { 360 | debug!( 361 | "client invite dialog confirmed: {} => {}", 362 | id, new_dialog_id 363 | ); 364 | self.inner.dialogs.write().unwrap().remove(&id); 365 | // update with new dialog id 366 | self.inner 367 | .dialogs 368 | .write() 369 | .unwrap() 370 | .insert(new_dialog_id, Dialog::ClientInvite(dialog.clone())); 371 | return Ok((dialog, resp)); 372 | } 373 | Err(e) => { 374 | self.inner.dialogs.write().unwrap().remove(&id); 375 | return Err(e); 376 | } 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /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, PartialEq, Eq, Hash, Debug)] 49 | pub struct DialogId { 50 | pub call_id: String, 51 | pub from_tag: String, 52 | pub to_tag: String, 53 | } 54 | 55 | impl TryFrom<&Request> for DialogId { 56 | type Error = crate::Error; 57 | 58 | fn try_from(request: &Request) -> Result { 59 | let call_id = request.call_id_header()?.value().to_string(); 60 | 61 | let from_tag = match request.from_header()?.tag()? { 62 | Some(tag) => tag.value().to_string(), 63 | None => return Err(Error::Error("from tag not found".to_string())), 64 | }; 65 | 66 | let to_tag = match request.to_header()?.tag()? { 67 | Some(tag) => tag.value().to_string(), 68 | None => "".to_string(), 69 | }; 70 | 71 | Ok(DialogId { 72 | call_id, 73 | from_tag, 74 | to_tag, 75 | }) 76 | } 77 | } 78 | 79 | impl TryFrom<&Response> for DialogId { 80 | type Error = crate::Error; 81 | 82 | fn try_from(resp: &Response) -> Result { 83 | let call_id = resp.call_id_header()?.value().to_string(); 84 | 85 | let from_tag = match resp.from_header()?.tag()? { 86 | Some(tag) => tag.value().to_string(), 87 | None => return Err(Error::Error("from tag not found".to_string())), 88 | }; 89 | 90 | let to_tag = match resp.to_header()?.tag()? { 91 | Some(tag) => tag.value().to_string(), 92 | None => return Err(Error::Error("to tag not found".to_string())), 93 | }; 94 | 95 | Ok(DialogId { 96 | call_id, 97 | from_tag, 98 | to_tag, 99 | }) 100 | } 101 | } 102 | 103 | impl std::fmt::Display for DialogId { 104 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 105 | write!(f, "{}-{}-{}", self.call_id, self.from_tag, self.to_tag) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/dialog/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod test_client_dialog; 2 | mod test_dialog_layer; 3 | mod test_dialog_states; 4 | -------------------------------------------------------------------------------- /src/dialog/tests/test_client_dialog.rs: -------------------------------------------------------------------------------- 1 | //! Client dialog tests 2 | //! 3 | //! Tests for client-side dialog behavior and state management 4 | 5 | use crate::dialog::{ 6 | client_dialog::ClientInviteDialog, 7 | dialog::{DialogInner, DialogState, TerminatedReason}, 8 | DialogId, 9 | }; 10 | use crate::transaction::{endpoint::EndpointBuilder, key::TransactionRole}; 11 | use crate::transport::TransportLayer; 12 | use rsip::{headers::*, Request, Response, StatusCode, Uri}; 13 | use std::sync::Arc; 14 | use tokio::sync::mpsc::unbounded_channel; 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_invite_request(from_tag: &str, to_tag: &str, call_id: &str) -> Request { 28 | Request { 29 | method: rsip::Method::Invite, 30 | uri: Uri::try_from("sip:bob@example.com:5060").unwrap(), 31 | headers: vec![ 32 | Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(), 33 | CSeq::new("1 INVITE").into(), 34 | From::new(&format!("Alice ;tag={}", from_tag)).into(), 35 | To::new(&format!("Bob ;tag={}", to_tag)).into(), 36 | CallId::new(call_id).into(), 37 | Contact::new("").into(), 38 | MaxForwards::new("70").into(), 39 | ] 40 | .into(), 41 | version: rsip::Version::V2, 42 | body: b"v=0\r\no=alice 2890844526 2890844527 IN IP4 host.atlanta.com\r\n".to_vec(), 43 | } 44 | } 45 | 46 | #[tokio::test] 47 | async fn test_client_dialog_creation() -> crate::Result<()> { 48 | let endpoint = create_test_endpoint().await?; 49 | let (state_sender, _) = unbounded_channel(); 50 | 51 | let dialog_id = DialogId { 52 | call_id: "test-call-id".to_string(), 53 | from_tag: "alice-tag".to_string(), 54 | to_tag: "bob-tag".to_string(), 55 | }; 56 | 57 | let invite_req = create_invite_request("alice-tag", "", "test-call-id"); 58 | 59 | let dialog_inner = DialogInner::new( 60 | TransactionRole::Client, 61 | dialog_id.clone(), 62 | invite_req, 63 | endpoint.inner.clone(), 64 | state_sender, 65 | None, 66 | Some(Uri::try_from("sip:alice@alice.example.com:5060").unwrap()), 67 | )?; 68 | 69 | let client_dialog = ClientInviteDialog { 70 | inner: Arc::new(dialog_inner), 71 | }; 72 | 73 | // Test initial state 74 | assert_eq!(client_dialog.id(), dialog_id); 75 | assert!(!client_dialog.inner.is_confirmed()); 76 | 77 | Ok(()) 78 | } 79 | 80 | #[tokio::test] 81 | async fn test_client_dialog_sequence_handling() -> crate::Result<()> { 82 | let endpoint = create_test_endpoint().await?; 83 | let (state_sender, _) = unbounded_channel(); 84 | 85 | let dialog_id = DialogId { 86 | call_id: "test-call-seq".to_string(), 87 | from_tag: "alice-tag".to_string(), 88 | to_tag: "bob-tag".to_string(), 89 | }; 90 | 91 | let invite_req = create_invite_request("alice-tag", "bob-tag", "test-call-seq"); 92 | 93 | let dialog_inner = DialogInner::new( 94 | TransactionRole::Client, 95 | dialog_id.clone(), 96 | invite_req, 97 | endpoint.inner.clone(), 98 | state_sender, 99 | None, 100 | Some(Uri::try_from("sip:alice@alice.example.com:5060").unwrap()), 101 | )?; 102 | 103 | let client_dialog = ClientInviteDialog { 104 | inner: Arc::new(dialog_inner), 105 | }; 106 | 107 | // Test initial sequence 108 | let initial_seq = client_dialog.inner.get_local_seq(); 109 | assert_eq!(initial_seq, 1); 110 | 111 | // Test sequence increment 112 | let next_seq = client_dialog.inner.increment_local_seq(); 113 | assert_eq!(next_seq, 2); 114 | 115 | Ok(()) 116 | } 117 | 118 | #[tokio::test] 119 | async fn test_client_dialog_state_transitions() -> crate::Result<()> { 120 | let endpoint = create_test_endpoint().await?; 121 | let (state_sender, _) = unbounded_channel(); 122 | 123 | let dialog_id = DialogId { 124 | call_id: "test-call-flow".to_string(), 125 | from_tag: "alice-tag".to_string(), 126 | to_tag: "".to_string(), 127 | }; 128 | 129 | let invite_req = create_invite_request("alice-tag", "", "test-call-flow"); 130 | 131 | let dialog_inner = DialogInner::new( 132 | TransactionRole::Client, 133 | dialog_id.clone(), 134 | invite_req, 135 | endpoint.inner.clone(), 136 | state_sender, 137 | None, 138 | Some(Uri::try_from("sip:alice@alice.example.com:5060").unwrap()), 139 | )?; 140 | 141 | let client_dialog = ClientInviteDialog { 142 | inner: Arc::new(dialog_inner), 143 | }; 144 | 145 | // Test state transitions manually (simulating what happens during invite flow) 146 | 147 | // Initial state should be Calling 148 | let state = client_dialog.inner.state.lock().unwrap().clone(); 149 | assert!(matches!(state, DialogState::Calling(_))); 150 | 151 | // Transition to Trying (after sending INVITE) 152 | client_dialog 153 | .inner 154 | .transition(DialogState::Trying(dialog_id.clone()))?; 155 | let state = client_dialog.inner.state.lock().unwrap().clone(); 156 | assert!(matches!(state, DialogState::Trying(_))); 157 | 158 | // Transition to Early (after receiving 1xx) 159 | let ringing_resp = Response { 160 | status_code: StatusCode::Ringing, 161 | version: rsip::Version::V2, 162 | headers: vec![ 163 | Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(), 164 | CSeq::new("1 INVITE").into(), 165 | From::new("Alice ;tag=alice-tag").into(), 166 | To::new("Bob ;tag=bob-tag").into(), 167 | CallId::new("test-call-flow").into(), 168 | Contact::new("").into(), 169 | ] 170 | .into(), 171 | body: vec![], 172 | }; 173 | 174 | client_dialog 175 | .inner 176 | .transition(DialogState::Early(dialog_id.clone(), ringing_resp))?; 177 | let state = client_dialog.inner.state.lock().unwrap().clone(); 178 | assert!(matches!(state, DialogState::Early(_, _))); 179 | 180 | // Transition to Confirmed (after receiving 200 OK and sending ACK) 181 | client_dialog 182 | .inner 183 | .transition(DialogState::Confirmed(dialog_id.clone()))?; 184 | let state = client_dialog.inner.state.lock().unwrap().clone(); 185 | assert!(matches!(state, DialogState::Confirmed(_))); 186 | assert!(client_dialog.inner.is_confirmed()); 187 | 188 | Ok(()) 189 | } 190 | 191 | #[tokio::test] 192 | async fn test_client_dialog_termination_scenarios() -> crate::Result<()> { 193 | let endpoint = create_test_endpoint().await?; 194 | let (state_sender, _) = unbounded_channel(); 195 | 196 | // Test 1: Early termination (before confirmed) 197 | let dialog_id_1 = DialogId { 198 | call_id: "test-call-term-early".to_string(), 199 | from_tag: "alice-tag".to_string(), 200 | to_tag: "".to_string(), 201 | }; 202 | 203 | let invite_req_1 = create_invite_request("alice-tag", "", "test-call-term-early"); 204 | let dialog_inner_1 = DialogInner::new( 205 | TransactionRole::Client, 206 | dialog_id_1.clone(), 207 | invite_req_1, 208 | endpoint.inner.clone(), 209 | state_sender.clone(), 210 | None, 211 | Some(Uri::try_from("sip:alice@alice.example.com:5060").unwrap()), 212 | )?; 213 | 214 | let client_dialog_1 = ClientInviteDialog { 215 | inner: Arc::new(dialog_inner_1), 216 | }; 217 | 218 | // Terminate early with error 219 | client_dialog_1.inner.transition(DialogState::Terminated( 220 | dialog_id_1.clone(), 221 | TerminatedReason::UasBusy, 222 | ))?; 223 | 224 | let state = client_dialog_1.inner.state.lock().unwrap().clone(); 225 | assert!(matches!( 226 | state, 227 | DialogState::Terminated(_, TerminatedReason::UasBusy) 228 | )); 229 | 230 | // Test 2: Normal termination after confirmed 231 | let dialog_id_2 = DialogId { 232 | call_id: "test-call-term-normal".to_string(), 233 | from_tag: "alice-tag".to_string(), 234 | to_tag: "bob-tag".to_string(), 235 | }; 236 | 237 | let invite_req_2 = create_invite_request("alice-tag", "bob-tag", "test-call-term-normal"); 238 | let dialog_inner_2 = DialogInner::new( 239 | TransactionRole::Client, 240 | dialog_id_2.clone(), 241 | invite_req_2, 242 | endpoint.inner.clone(), 243 | state_sender, 244 | None, 245 | Some(Uri::try_from("sip:alice@alice.example.com:5060").unwrap()), 246 | )?; 247 | 248 | let client_dialog_2 = ClientInviteDialog { 249 | inner: Arc::new(dialog_inner_2), 250 | }; 251 | 252 | // Confirm dialog first 253 | client_dialog_2 254 | .inner 255 | .transition(DialogState::Confirmed(dialog_id_2.clone()))?; 256 | assert!(client_dialog_2.inner.is_confirmed()); 257 | 258 | // Then terminate normally 259 | client_dialog_2.inner.transition(DialogState::Terminated( 260 | dialog_id_2.clone(), 261 | TerminatedReason::UacBye, 262 | ))?; 263 | let state = client_dialog_2.inner.state.lock().unwrap().clone(); 264 | assert!(matches!( 265 | state, 266 | DialogState::Terminated(_, TerminatedReason::UacBye) 267 | )); 268 | 269 | Ok(()) 270 | } 271 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{dialog::DialogId, transaction::key::TransactionKey, transport::SipAddr}; 2 | use std::env::VarError; 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[derive(Debug, Eq, PartialEq, Clone)] 6 | pub enum Error { 7 | SipMessageError(String), 8 | DnsResolutionError(String), 9 | TransportLayerError(String, SipAddr), 10 | TransactionError(String, TransactionKey), 11 | EndpointError(String), 12 | DialogError(String, DialogId), 13 | Keepalive, 14 | Error(String), 15 | } 16 | 17 | impl std::fmt::Display for Error { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | Error::SipMessageError(e) => write!(f, "SIP message error: {}", e), 21 | Error::DnsResolutionError(e) => write!(f, "DNS resolution error: {}", e), 22 | Error::TransportLayerError(e, addr) => { 23 | write!(f, "Transport layer error: {}: {}", e, addr) 24 | } 25 | Error::TransactionError(e, key) => write!(f, "Transaction error: {}: {}", e, key), 26 | Error::EndpointError(e) => write!(f, "Endpoint error: {}", e), 27 | Error::DialogError(e, id) => write!(f, "Dialog error: {}: {}", e, id), 28 | Error::Keepalive => write!(f, "Keepalive message"), 29 | Error::Error(e) => write!(f, "Error: {}", e), 30 | } 31 | } 32 | } 33 | 34 | impl Into for Error { 35 | fn into(self) -> JsValue { 36 | match self { 37 | Error::DnsResolutionError(e) => e.into(), 38 | Error::SipMessageError(e) => e.into(), 39 | Error::TransportLayerError(e, _) => e.into(), 40 | Error::TransactionError(e, key) => format!("{}: {}", e, key.to_string()).into(), 41 | Error::EndpointError(e) => e.into(), 42 | Error::DialogError(e, id) => format!("{}: {}", e, id.to_string()).into(), 43 | Error::Keepalive => "Keepalive message".into(), 44 | Error::Error(e) => e.into(), 45 | } 46 | } 47 | } 48 | impl From for Error { 49 | fn from(e: rsip::Error) -> Self { 50 | Error::SipMessageError(e.to_string()) 51 | } 52 | } 53 | 54 | impl From> for Error { 55 | fn from(e: tokio::sync::mpsc::error::SendError) -> Self { 56 | Error::Error(e.to_string()) 57 | } 58 | } 59 | 60 | impl From for Error { 61 | fn from(e: tokio::sync::broadcast::error::RecvError) -> Self { 62 | Error::Error(e.to_string()) 63 | } 64 | } 65 | impl From for Error { 66 | fn from(e: std::io::Error) -> Self { 67 | Error::Error(e.to_string()) 68 | } 69 | } 70 | 71 | impl From for Error { 72 | fn from(e: std::fmt::Error) -> Self { 73 | Error::Error(e.to_string()) 74 | } 75 | } 76 | impl From for Error { 77 | fn from(e: VarError) -> Self { 78 | Error::Error(e.to_string()) 79 | } 80 | } 81 | 82 | impl From for Error { 83 | fn from(e: std::net::AddrParseError) -> Self { 84 | Error::Error(e.to_string()) 85 | } 86 | } 87 | 88 | impl From for Error { 89 | fn from(e: tokio_rustls::rustls::Error) -> Self { 90 | Error::Error(e.to_string()) 91 | } 92 | } 93 | 94 | impl From for Error { 95 | fn from(e: tokio_tungstenite::tungstenite::Error) -> Self { 96 | Error::Error(e.to_string()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /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(); 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 | //! credential: None, 93 | //! headers: None, 94 | //! }; 95 | //! 96 | //! let (dialog, response) = dialog_layer.do_invite(invite_option, state_sender).await?; 97 | //! # Ok(()) 98 | //! # } 99 | //! ``` 100 | //! 101 | //! ## Core Components 102 | //! 103 | //! ### Transport Layer 104 | //! 105 | //! The transport layer handles network communication across different protocols: 106 | //! 107 | //! * [`SipConnection`](transport::SipConnection) - Abstraction over transport protocols 108 | //! * [`SipAddr`](transport::SipAddr) - SIP addressing with transport information 109 | //! * [`TransportLayer`](transport::TransportLayer) - Transport management 110 | //! 111 | //! ### Transaction Layer 112 | //! 113 | //! The transaction layer provides reliable message delivery: 114 | //! 115 | //! * [`Transaction`](transaction::transaction::Transaction) - SIP transaction implementation 116 | //! * [`Endpoint`](transaction::Endpoint) - SIP endpoint for transaction management 117 | //! * [`TransactionState`](transaction::TransactionState) - Transaction state machine 118 | //! 119 | //! ### Dialog Layer 120 | //! 121 | //! The dialog layer manages SIP dialogs and sessions: 122 | //! 123 | //! * [`Dialog`](dialog::dialog::Dialog) - SIP dialog representation 124 | //! * [`DialogId`](dialog::DialogId) - Dialog identification 125 | //! * [`DialogState`](dialog::dialog::DialogState) - Dialog state management 126 | //! 127 | //! ## Error Handling 128 | //! 129 | //! The stack uses a comprehensive error type that covers all layers: 130 | //! 131 | //! ```rust 132 | //! use rsipstack::{Result, Error}; 133 | //! 134 | //! fn handle_sip_error(error: Error) { 135 | //! match error { 136 | //! Error::TransportLayerError(msg, addr) => { 137 | //! eprintln!("Transport error at {}: {}", addr, msg); 138 | //! }, 139 | //! Error::TransactionError(msg, key) => { 140 | //! eprintln!("Transaction error {}: {}", key, msg); 141 | //! }, 142 | //! Error::DialogError(msg, id) => { 143 | //! eprintln!("Dialog error {}: {}", id, msg); 144 | //! }, 145 | //! _ => eprintln!("Other error: {}", error), 146 | //! } 147 | //! } 148 | //! ``` 149 | //! 150 | //! ## Configuration 151 | //! 152 | //! The stack can be configured for different use cases: 153 | //! 154 | //! ### Basic UDP Server 155 | //! 156 | //! ```rust,no_run 157 | //! use rsipstack::EndpointBuilder; 158 | //! use rsipstack::transport::{TransportLayer, udp::UdpConnection}; 159 | //! use tokio_util::sync::CancellationToken; 160 | //! 161 | //! # async fn example() -> rsipstack::Result<()> { 162 | //! # let cancel_token = CancellationToken::new(); 163 | //! let transport_layer = TransportLayer::new(cancel_token.child_token()); 164 | //! let udp_conn = UdpConnection::create_connection("0.0.0.0:5060".parse()?, None).await?; 165 | //! transport_layer.add_transport(udp_conn.into()); 166 | //! 167 | //! let endpoint = EndpointBuilder::new() 168 | //! .with_transport_layer(transport_layer) 169 | //! .build(); 170 | //! # Ok(()) 171 | //! # } 172 | //! ``` 173 | //! 174 | //! ### Secure TLS Server 175 | //! 176 | //! ```rust,no_run 177 | //! #[cfg(feature = "rustls")] 178 | //! use rsipstack::transport::tls::{TlsConnection, TlsConfig}; 179 | //! use rsipstack::transport::TransportLayer; 180 | //! 181 | //! # async fn example() -> rsipstack::Result<()> { 182 | //! # let cert_pem = vec![]; 183 | //! # let key_pem = vec![]; 184 | //! # let transport_layer: TransportLayer = todo!(); 185 | //! // Configure TLS transport 186 | //! let tls_config = TlsConfig { 187 | //! cert: Some(cert_pem), 188 | //! key: Some(key_pem), 189 | //! ..Default::default() 190 | //! }; 191 | //! 192 | //! // TLS connections would be created using the TLS configuration 193 | //! // let tls_conn = TlsConnection::serve_listener(...).await?; 194 | //! # Ok(()) 195 | //! # } 196 | //! ``` 197 | //! 198 | //! ## Standards Compliance 199 | //! 200 | //! RSIPStack implements the following RFCs: 201 | //! 202 | //! * **RFC 3261** - SIP: Session Initiation Protocol (core specification) 203 | //! * **RFC 3581** - Symmetric Response Routing (rport) 204 | //! * **RFC 6026** - Correct Transaction Handling for 2xx Responses to INVITE 205 | //! 206 | //! ## Performance 207 | //! 208 | //! The stack is designed for high performance: 209 | //! 210 | //! * **Zero-copy parsing** where possible 211 | //! * **Async I/O** with Tokio for scalability 212 | //! * **Efficient timer management** for large numbers of transactions 213 | //! * **Memory-safe** with Rust's ownership system 214 | //! 215 | //! ## Testing 216 | //! 217 | //! Comprehensive test suite covering: 218 | //! 219 | //! * Unit tests for all components 220 | //! * Integration tests for protocol compliance 221 | //! * Performance benchmarks 222 | //! * Interoperability testing 223 | //! 224 | //! ## Examples 225 | //! 226 | //! See the `examples/` directory for complete working examples: 227 | //! 228 | //! * Simple SIP client 229 | //! * SIP proxy server 230 | //! * WebSocket SIP gateway 231 | //! * Load testing tools 232 | 233 | pub type Result = std::result::Result; 234 | pub use crate::error::Error; 235 | pub mod dialog; 236 | pub mod error; 237 | pub mod transaction; 238 | pub mod transport; 239 | pub use transaction::EndpointBuilder; 240 | pub mod rsip_ext; 241 | 242 | const USER_AGENT: &str = "rsipstack/0.1"; 243 | -------------------------------------------------------------------------------- /src/rsip_ext.rs: -------------------------------------------------------------------------------- 1 | use rsip::{message::HasHeaders, prelude::HeadersExt}; 2 | 3 | use crate::transport::SipConnection; 4 | pub trait RsipResponseExt { 5 | fn reason_phrase(&self) -> Option<&str>; 6 | fn via_received(&self) -> Option; 7 | } 8 | 9 | impl RsipResponseExt for rsip::Response { 10 | fn reason_phrase(&self) -> Option<&str> { 11 | let headers = self.headers(); 12 | for header in headers.iter() { 13 | if let rsip::Header::Other(name, value) = header { 14 | if name.eq_ignore_ascii_case("reason") { 15 | return Some(value); 16 | } 17 | } 18 | } 19 | None 20 | } 21 | /// Parse the received address from the Via header 22 | /// 23 | /// This function extracts the received address from the Via header 24 | /// and returns it as a HostWithPort struct. 25 | fn via_received(&self) -> Option { 26 | let via = self.via_header().ok()?; 27 | SipConnection::parse_target_from_via(via).ok() 28 | } 29 | } 30 | 31 | pub trait RsipHeadersExt { 32 | fn push_front(&mut self, header: rsip::Header); 33 | } 34 | 35 | impl RsipHeadersExt for rsip::Headers { 36 | fn push_front(&mut self, header: rsip::Header) { 37 | let mut headers = self.iter().cloned().collect::>(); 38 | headers.insert(0, header); 39 | *self = headers.into(); 40 | } 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! header_pop { 45 | ($iter:expr, $header:path) => { 46 | let mut first = true; 47 | $iter.retain(|h| { 48 | if first && matches!(h, $header(_)) { 49 | first = false; 50 | false 51 | } else { 52 | true 53 | } 54 | }); 55 | }; 56 | } 57 | 58 | pub fn extract_uri_from_contact(line: &str) -> crate::Result { 59 | match rsip::headers::Contact::try_from(line) { 60 | Ok(contact) => { 61 | match contact.uri() { 62 | Ok(mut uri) => { 63 | uri.params 64 | .retain(|p| matches!(p, rsip::Param::Transport(_))); 65 | return Ok(uri); 66 | } 67 | Err(_) => {} 68 | }; 69 | } 70 | Err(_) => {} 71 | }; 72 | 73 | match line.split('<').nth(1).and_then(|s| s.split('>').next()) { 74 | Some(uri) => rsip::Uri::try_from(uri).map_err(Into::into), 75 | None => Err(crate::Error::Error(format!("no uri found: {}", line))), 76 | } 77 | } 78 | 79 | #[test] 80 | fn test_rsip_headers_ext() { 81 | use rsip::{Header, Headers}; 82 | let mut headers: Headers = vec![ 83 | Header::Via("SIP/2.0/TCP".into()), 84 | Header::Via("SIP/2.0/UDP".into()), 85 | Header::Via("SIP/2.0/WSS".into()), 86 | ] 87 | .into(); 88 | let via = Header::Via("SIP/2.0/TLS".into()); 89 | headers.push_front(via); 90 | assert_eq!(headers.iter().count(), 4); 91 | 92 | header_pop!(headers, Header::Via); 93 | assert_eq!(headers.iter().count(), 3); 94 | 95 | assert_eq!( 96 | headers.iter().collect::>(), 97 | vec![ 98 | &Header::Via("SIP/2.0/TCP".into()), 99 | &Header::Via("SIP/2.0/UDP".into()), 100 | &Header::Via("SIP/2.0/WSS".into()) 101 | ] 102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /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_ack_or_cancel(req: &Request, role: TransactionRole) -> Result { 39 | let via = req.via_header()?.typed()?; 40 | let method = req.method().clone(); 41 | let from_tag = req 42 | .from_header()? 43 | .tag()? 44 | .ok_or(Error::Error("from tags missing".to_string()))?; 45 | let call_id = req.call_id_header()?.value(); 46 | let cseq = req.cseq_header()?.seq()?; 47 | Self::build_key(role, via, method, cseq, from_tag, call_id) 48 | } 49 | 50 | pub fn from_request(req: &Request, role: TransactionRole) -> Result { 51 | let via = req.via_header()?.typed()?; 52 | let mut method = req.method().clone(); 53 | 54 | if matches!(method, Method::Ack | Method::Cancel) { 55 | method = Method::Invite; 56 | } 57 | 58 | let from_tag = req 59 | .from_header()? 60 | .tag()? 61 | .ok_or(Error::Error("from tags missing".to_string()))?; 62 | let call_id = req.call_id_header()?.value(); 63 | let cseq = req.cseq_header()?.seq()?; 64 | Self::build_key(role, via, method, cseq, from_tag, call_id) 65 | } 66 | 67 | pub fn from_response(resp: &Response, role: TransactionRole) -> Result { 68 | let via = resp.via_header()?.typed()?; 69 | let cseq = resp.cseq_header()?; 70 | let method = cseq.method()?; 71 | let from_tag = resp 72 | .from_header()? 73 | .tag()? 74 | .ok_or(Error::Error("from tags missing".to_string()))?; 75 | let call_id = resp.call_id_header()?.value(); 76 | 77 | Self::build_key(role, via, method, cseq.seq()?, from_tag, call_id) 78 | } 79 | 80 | fn build_key( 81 | role: TransactionRole, 82 | via: Via, 83 | method: Method, 84 | cseq: u32, 85 | from_tag: Tag, 86 | call_id: &str, 87 | ) -> Result { 88 | let mut key = String::new(); 89 | match via.branch() { 90 | Some(branch) => { 91 | write!( 92 | &mut key, 93 | "{}.{}_{}_{}_{}_{}", 94 | role, method, cseq, call_id, from_tag, branch 95 | ) 96 | } 97 | None => { 98 | write!( 99 | &mut key, 100 | "{}.{}_{}_{}_{}_{}.2543", 101 | role, method, cseq, call_id, from_tag, via.uri.host_with_port 102 | ) 103 | } 104 | }?; 105 | Ok(TransactionKey(key)) 106 | } 107 | } 108 | 109 | #[test] 110 | fn test_transaction_key() -> Result<()> { 111 | use rsip::headers::*; 112 | let register_req = rsip::message::Request { 113 | method: rsip::method::Method::Register, 114 | uri: rsip::Uri { 115 | scheme: Some(rsip::Scheme::Sips), 116 | host_with_port: rsip::Domain::from("restsend.com").into(), 117 | ..Default::default() 118 | }, 119 | headers: vec![ 120 | Via::new("SIP/2.0/TLS sip.restsend.com:5061;branch=z9hG4bKnashd92").into(), 121 | CSeq::new("2 REGISTER").into(), 122 | From::new("Bob ;tag=ja743ks76zlflH").into(), 123 | CallId::new("1j9FpLxk3uxtm8tn@sip.restsend.com").into(), 124 | ] 125 | .into(), 126 | version: rsip::Version::V2, 127 | body: Default::default(), 128 | }; 129 | let key = TransactionKey::from_request(®ister_req, TransactionRole::Client)?; 130 | assert_eq!( 131 | key, 132 | TransactionKey( 133 | "c.REGISTER_2_1j9FpLxk3uxtm8tn@sip.restsend.com_ja743ks76zlflH_z9hG4bKnashd92" 134 | .to_string() 135 | ) 136 | ); 137 | let register_resp = rsip::message::Response { 138 | status_code: rsip::StatusCode::OK, 139 | version: rsip::Version::V2, 140 | headers: vec![ 141 | Via::new("SIP/2.0/TLS client.sip.restsend.com:5061;branch=z9hG4bKnashd92").into(), 142 | CSeq::new("2 REGISTER").into(), 143 | From::new("Bob ;tag=ja743ks76zlflH").into(), 144 | CallId::new("1j9FpLxk3uxtm8tn@sip.restsend.com").into(), 145 | ] 146 | .into(), 147 | body: Default::default(), 148 | }; 149 | let key = TransactionKey::from_response(®ister_resp, TransactionRole::Server)?; 150 | assert_eq!( 151 | key, 152 | TransactionKey( 153 | "s.REGISTER_2_1j9FpLxk3uxtm8tn@sip.restsend.com_ja743ks76zlflH_z9hG4bKnashd92" 154 | .to_string() 155 | ) 156 | ); 157 | 158 | let mut ack_req = register_req.clone(); 159 | ack_req.method = Method::Ack; 160 | ack_req.headers.unique_push(CSeq::new("2 ACK").into()); 161 | 162 | let key = TransactionKey::from_request(&ack_req, TransactionRole::Server)?; 163 | assert_eq!( 164 | key, 165 | TransactionKey( 166 | "s.INVITE_2_1j9FpLxk3uxtm8tn@sip.restsend.com_ja743ks76zlflH_z9hG4bKnashd92" 167 | .to_string() 168 | ) 169 | ); 170 | Ok(()) 171 | } 172 | -------------------------------------------------------------------------------- /src/transaction/message.rs: -------------------------------------------------------------------------------- 1 | use super::{endpoint::EndpointInner, make_call_id}; 2 | use rsip::{Header, Request, Response, StatusCode}; 3 | 4 | impl EndpointInner { 5 | /// Create a SIP request message 6 | /// 7 | /// Constructs a properly formatted SIP request with all required headers 8 | /// according to RFC 3261. This method is used internally by the endpoint 9 | /// to create outgoing SIP requests for various purposes. 10 | /// 11 | /// # Parameters 12 | /// 13 | /// * `method` - SIP method (INVITE, REGISTER, BYE, etc.) 14 | /// * `req_uri` - Request-URI indicating the target of the request 15 | /// * `via` - Via header for response routing 16 | /// * `from` - From header identifying the request originator 17 | /// * `to` - To header identifying the request target 18 | /// * `seq` - CSeq sequence number for the request 19 | /// 20 | /// # Returns 21 | /// 22 | /// A complete SIP request with all mandatory headers 23 | /// 24 | /// # Generated Headers 25 | /// 26 | /// The method automatically includes these mandatory headers: 27 | /// * **Via** - Response routing information 28 | /// * **Call-ID** - Unique identifier for the call/session 29 | /// * **From** - Request originator with tag parameter 30 | /// * **To** - Request target (tag added by recipient) 31 | /// * **CSeq** - Command sequence with method and number 32 | /// * **Max-Forwards** - Hop count limit (set to 70) 33 | /// * **User-Agent** - Endpoint identification 34 | /// 35 | /// # Examples 36 | /// 37 | /// ```rust,no_run 38 | /// # use rsipstack::transaction::endpoint::EndpointInner; 39 | /// # async fn example(endpoint: &EndpointInner) -> rsipstack::Result<()> { 40 | /// // Create an INVITE request 41 | /// let via = endpoint.get_via(None, None)?; 42 | /// let from = rsip::typed::From { 43 | /// display_name: None, 44 | /// uri: rsip::Uri::try_from("sip:alice@example.com")?, 45 | /// params: vec![rsip::Param::Tag("alice-tag".into())], 46 | /// }; 47 | /// let to = rsip::typed::To { 48 | /// display_name: None, 49 | /// uri: rsip::Uri::try_from("sip:bob@example.com")?, 50 | /// params: vec![], 51 | /// }; 52 | /// 53 | /// let request = endpoint.make_request( 54 | /// rsip::Method::Invite, 55 | /// rsip::Uri::try_from("sip:bob@example.com")?, 56 | /// via, 57 | /// from, 58 | /// to, 59 | /// 1 60 | /// ); 61 | /// # Ok(()) 62 | /// # } 63 | /// ``` 64 | /// 65 | /// # Usage Context 66 | /// 67 | /// This method is typically used by: 68 | /// * Dialog layer for creating in-dialog requests 69 | /// * Registration module for REGISTER requests 70 | /// * Transaction layer for creating client transactions 71 | /// * Application layer for custom request types 72 | /// 73 | /// # Header Ordering 74 | /// 75 | /// Headers are added in the order specified by RFC 3261 recommendations: 76 | /// 1. Via (topmost first) 77 | /// 2. Call-ID 78 | /// 3. From 79 | /// 4. To 80 | /// 5. CSeq 81 | /// 6. Max-Forwards 82 | /// 7. User-Agent 83 | /// 84 | /// Additional headers can be added after creation using the headers API. 85 | pub fn make_request( 86 | &self, 87 | method: rsip::Method, 88 | req_uri: rsip::Uri, 89 | via: rsip::typed::Via, 90 | from: rsip::typed::From, 91 | to: rsip::typed::To, 92 | seq: u32, 93 | ) -> rsip::Request { 94 | let headers = vec![ 95 | Header::Via(via.into()), 96 | Header::CallId(make_call_id(None)), 97 | Header::From(from.into()), 98 | Header::To(to.into()), 99 | Header::CSeq(rsip::typed::CSeq { seq, method }.into()), 100 | Header::MaxForwards(70.into()), 101 | Header::UserAgent(self.user_agent.clone().into()), 102 | ]; 103 | rsip::Request { 104 | method, 105 | uri: req_uri, 106 | headers: headers.into(), 107 | body: vec![], 108 | version: rsip::Version::V2, 109 | } 110 | } 111 | 112 | /// Create a SIP response message 113 | /// 114 | /// Constructs a properly formatted SIP response based on the received 115 | /// request. This method copies appropriate headers from the request 116 | /// and adds the response-specific information according to RFC 3261. 117 | /// 118 | /// # Parameters 119 | /// 120 | /// * `req` - Original request being responded to 121 | /// * `status_code` - SIP response status code (1xx-6xx) 122 | /// * `body` - Optional response body content 123 | /// 124 | /// # Returns 125 | /// 126 | /// A complete SIP response ready to be sent 127 | /// 128 | /// # Header Processing 129 | /// 130 | /// The method processes headers as follows: 131 | /// * **Copied from request**: Via, Call-ID, From, To, CSeq, Max-Forwards 132 | /// * **Added by endpoint**: User-Agent 133 | /// * **Filtered out**: All other headers from the request 134 | /// 135 | /// Additional response-specific headers should be added after creation. 136 | /// 137 | /// # Examples 138 | /// 139 | /// ## Success Response 140 | /// 141 | /// ```rust,no_run 142 | /// # use rsipstack::transaction::endpoint::EndpointInner; 143 | /// # fn example(endpoint: &EndpointInner, request: &rsip::Request, sdp_answer: String) { 144 | /// let response = endpoint.make_response( 145 | /// &request, 146 | /// rsip::StatusCode::OK, 147 | /// Some(sdp_answer.into_bytes()) 148 | /// ); 149 | /// # } 150 | /// ``` 151 | /// 152 | /// ## Error Response 153 | /// 154 | /// ```rust,no_run 155 | /// # use rsipstack::transaction::endpoint::EndpointInner; 156 | /// # fn example(endpoint: &EndpointInner, request: &rsip::Request) { 157 | /// let response = endpoint.make_response( 158 | /// &request, 159 | /// rsip::StatusCode::NotFound, 160 | /// None 161 | /// ); 162 | /// # } 163 | /// ``` 164 | /// 165 | /// ## Provisional Response 166 | /// 167 | /// ```rust,no_run 168 | /// # use rsipstack::transaction::endpoint::EndpointInner; 169 | /// # fn example(endpoint: &EndpointInner, request: &rsip::Request) { 170 | /// let response = endpoint.make_response( 171 | /// &request, 172 | /// rsip::StatusCode::Ringing, 173 | /// None 174 | /// ); 175 | /// # } 176 | /// ``` 177 | /// 178 | /// # Response Categories 179 | /// 180 | /// * **1xx Provisional** - Request received, processing continues 181 | /// * **2xx Success** - Request successfully received, understood, and accepted 182 | /// * **3xx Redirection** - Further action needed to complete request 183 | /// * **4xx Client Error** - Request contains bad syntax or cannot be fulfilled 184 | /// * **5xx Server Error** - Server failed to fulfill valid request 185 | /// * **6xx Global Failure** - Request cannot be fulfilled at any server 186 | /// 187 | /// # Usage Context 188 | /// 189 | /// This method is used by: 190 | /// * Server transactions to create responses 191 | /// * Dialog layer for dialog-specific responses 192 | /// * Application layer for handling incoming requests 193 | /// * Error handling for protocol violations 194 | /// 195 | /// # Header Compliance 196 | /// 197 | /// The response includes all headers required by RFC 3261: 198 | /// * Via headers are copied exactly (for response routing) 199 | /// * Call-ID is preserved (dialog/transaction identification) 200 | /// * From/To headers maintain dialog state 201 | /// * CSeq is copied for transaction matching 202 | /// * User-Agent identifies the responding endpoint 203 | /// 204 | /// # Content Handling 205 | /// 206 | /// * If body is provided, Content-Length should be added separately 207 | /// * Content-Type should be added for non-empty bodies 208 | /// * Body encoding is handled by the application layer 209 | pub fn make_response( 210 | &self, 211 | req: &Request, 212 | status_code: StatusCode, 213 | body: Option>, 214 | ) -> Response { 215 | let mut headers = req.headers.clone(); 216 | headers.retain(|h| { 217 | matches!( 218 | h, 219 | Header::Via(_) 220 | | Header::CallId(_) 221 | | Header::From(_) 222 | | Header::To(_) 223 | | Header::MaxForwards(_) 224 | | Header::CSeq(_) 225 | ) 226 | }); 227 | headers.unique_push(Header::UserAgent(self.user_agent.clone().into())); 228 | Response { 229 | status_code, 230 | version: req.version().clone(), 231 | headers, 232 | body: body.unwrap_or_default(), 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /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 | use uuid::Uuid; 7 | 8 | pub mod endpoint; 9 | pub mod key; 10 | pub mod message; 11 | mod timer; 12 | pub mod transaction; 13 | pub use endpoint::Endpoint; 14 | pub use endpoint::EndpointBuilder; 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | pub const TO_TAG_LEN: usize = 8; 19 | pub const BRANCH_LEN: usize = 12; 20 | pub const CNONCE_LEN: usize = 8; 21 | 22 | pub struct IncomingRequest { 23 | pub request: rsip::Request, 24 | pub connection: SipConnection, 25 | pub from: SipAddr, 26 | } 27 | 28 | pub type TransactionReceiver = UnboundedReceiver; 29 | pub type TransactionSender = UnboundedSender; 30 | 31 | /// SIP Transaction State 32 | /// 33 | /// `TransactionState` represents the various states a SIP transaction can be in 34 | /// during its lifecycle. These states implement the transaction state machines 35 | /// defined in RFC 3261 for both client and server transactions. 36 | /// 37 | /// # States 38 | /// 39 | /// * `Calling` - Initial state for client transactions when request is sent 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 | /// Calling → Trying → Proceeding → Completed → Terminated 51 | /// ``` 52 | /// 53 | /// ## Client INVITE Transaction 54 | /// ```text 55 | /// Calling → Trying → Proceeding → Completed → Terminated 56 | /// ↓ 57 | /// Confirmed → Terminated 58 | /// ``` 59 | /// 60 | /// ## Server Transactions 61 | /// ```text 62 | /// 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::Calling => println!("Transaction starting"), 75 | /// TransactionState::Trying => println!("Request sent/received"), 76 | /// TransactionState::Proceeding => println!("Provisional response"), 77 | /// TransactionState::Completed => println!("Final response"), 78 | /// TransactionState::Confirmed => println!("ACK received/sent"), 79 | /// TransactionState::Terminated => println!("Transaction complete"), 80 | /// } 81 | /// ``` 82 | #[derive(Debug, Clone, PartialEq)] 83 | pub enum TransactionState { 84 | Calling, 85 | Trying, 86 | Proceeding, 87 | Completed, 88 | Confirmed, 89 | Terminated, 90 | } 91 | 92 | /// SIP Transaction Type 93 | /// 94 | /// `TransactionType` distinguishes between the four types of SIP transactions 95 | /// as defined in RFC 3261. Each type has different behavior for retransmissions, 96 | /// timers, and state transitions. 97 | /// 98 | /// # Types 99 | /// 100 | /// * `ClientInvite` - Client-side INVITE transaction (UAC INVITE) 101 | /// * `ClientNonInvite` - Client-side non-INVITE transaction (UAC non-INVITE) 102 | /// * `ServerInvite` - Server-side INVITE transaction (UAS INVITE) 103 | /// * `ServerNonInvite` - Server-side non-INVITE transaction (UAS non-INVITE) 104 | /// 105 | /// # Characteristics 106 | /// 107 | /// ## Client INVITE 108 | /// * Longer timeouts due to human interaction 109 | /// * ACK handling for 2xx responses 110 | /// * CANCEL support for early termination 111 | /// 112 | /// ## Client Non-INVITE 113 | /// * Shorter timeouts for automated responses 114 | /// * No ACK required 115 | /// * Simpler state machine 116 | /// 117 | /// ## Server INVITE 118 | /// * Must handle ACK for final responses 119 | /// * Supports provisional responses 120 | /// * Complex retransmission rules 121 | /// 122 | /// ## Server Non-INVITE 123 | /// * Simple request/response pattern 124 | /// * No ACK handling 125 | /// * Faster completion 126 | /// 127 | /// # Examples 128 | /// 129 | /// ```rust 130 | /// use rsipstack::transaction::TransactionType; 131 | /// use rsip::Method; 132 | /// 133 | /// fn get_transaction_type(method: &Method, is_client: bool) -> TransactionType { 134 | /// match (method, is_client) { 135 | /// (Method::Invite, true) => TransactionType::ClientInvite, 136 | /// (Method::Invite, false) => TransactionType::ServerInvite, 137 | /// (_, true) => TransactionType::ClientNonInvite, 138 | /// (_, false) => TransactionType::ServerNonInvite, 139 | /// } 140 | /// } 141 | /// ``` 142 | #[derive(Debug, PartialEq)] 143 | pub enum TransactionType { 144 | ClientInvite, 145 | ClientNonInvite, 146 | ServerInvite, 147 | ServerNonInvite, 148 | } 149 | 150 | /// SIP Transaction Timers 151 | /// 152 | /// `TransactionTimer` represents the various timers used in SIP transactions 153 | /// as defined in RFC 3261. These timers ensure reliable message delivery 154 | /// and proper transaction cleanup. 155 | /// 156 | /// # Timer Types 157 | /// 158 | /// * `TimerA` - Retransmission timer for client transactions (unreliable transport) 159 | /// * `TimerB` - Transaction timeout timer for client transactions 160 | /// * `TimerD` - Wait timer for response retransmissions (client) 161 | /// * `TimerE` - Retransmission timer for non-INVITE server transactions 162 | /// * `TimerF` - Transaction timeout timer for non-INVITE server transactions 163 | /// * `TimerK` - Wait timer for ACK (server INVITE transactions) 164 | /// * `TimerG` - Retransmission timer for INVITE server transactions 165 | /// * `TimerCleanup` - Internal cleanup timer for transaction removal 166 | /// 167 | /// # Timer Values (RFC 3261) 168 | /// 169 | /// * T1 = 500ms (RTT estimate) 170 | /// * T2 = 4s (maximum retransmit interval) 171 | /// * T4 = 5s (maximum duration a message will remain in the network) 172 | /// 173 | /// ## Timer Calculations 174 | /// * Timer A: starts at T1, doubles each retransmission up to T2 175 | /// * Timer B: 64*T1 (32 seconds) 176 | /// * Timer D: 32 seconds for unreliable, 0 for reliable transports 177 | /// * Timer E: starts at T1, doubles up to T2 178 | /// * Timer F: 64*T1 (32 seconds) 179 | /// * Timer G: starts at T1, doubles up to T2 180 | /// * Timer K: T4 for unreliable, 0 for reliable transports 181 | /// 182 | /// # Examples 183 | /// 184 | /// ```rust 185 | /// use rsipstack::transaction::{TransactionTimer, key::{TransactionKey, TransactionRole}}; 186 | /// use std::time::Duration; 187 | /// 188 | /// # fn example() -> rsipstack::Result<()> { 189 | /// // Create a mock request to generate a transaction key 190 | /// let request = rsip::Request { 191 | /// method: rsip::Method::Register, 192 | /// uri: rsip::Uri::try_from("sip:example.com")?, 193 | /// headers: vec![ 194 | /// rsip::Header::Via("SIP/2.0/UDP example.com:5060;branch=z9hG4bKnashds".into()), 195 | /// rsip::Header::CSeq("1 REGISTER".into()), 196 | /// rsip::Header::From("Alice ;tag=1928301774".into()), 197 | /// rsip::Header::CallId("a84b4c76e66710@pc33.atlanta.com".into()), 198 | /// ].into(), 199 | /// version: rsip::Version::V2, 200 | /// body: Default::default(), 201 | /// }; 202 | /// let key = TransactionKey::from_request(&request, TransactionRole::Client)?; 203 | /// 204 | /// let timer = TransactionTimer::TimerA(key.clone(), Duration::from_millis(500)); 205 | /// match timer { 206 | /// TransactionTimer::TimerA(key, duration) => { 207 | /// println!("Timer A fired for transaction {}", key); 208 | /// }, 209 | /// TransactionTimer::TimerB(key) => { 210 | /// println!("Transaction {} timed out", key); 211 | /// }, 212 | /// _ => {} 213 | /// } 214 | /// # Ok(()) 215 | /// # } 216 | /// ``` 217 | /// 218 | /// # Usage 219 | /// 220 | /// Timers are automatically managed by the transaction layer: 221 | /// * Started when entering appropriate states 222 | /// * Cancelled when leaving states or receiving responses 223 | /// * Fire events that drive state machine transitions 224 | /// * Handle retransmissions and timeouts 225 | pub enum TransactionTimer { 226 | TimerA(TransactionKey, Duration), 227 | TimerB(TransactionKey), 228 | TimerD(TransactionKey), 229 | TimerE(TransactionKey), 230 | TimerF(TransactionKey), 231 | TimerK(TransactionKey), 232 | TimerG(TransactionKey, Duration), 233 | TimerCleanup(TransactionKey), 234 | } 235 | 236 | impl TransactionTimer { 237 | pub fn key(&self) -> &TransactionKey { 238 | match self { 239 | TransactionTimer::TimerA(key, _) => key, 240 | TransactionTimer::TimerB(key) => key, 241 | TransactionTimer::TimerD(key) => key, 242 | TransactionTimer::TimerE(key) => key, 243 | TransactionTimer::TimerF(key) => key, 244 | TransactionTimer::TimerG(key, _) => key, 245 | TransactionTimer::TimerK(key) => key, 246 | TransactionTimer::TimerCleanup(key) => key, 247 | } 248 | } 249 | } 250 | 251 | impl std::fmt::Display for TransactionTimer { 252 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 253 | match self { 254 | TransactionTimer::TimerA(key, duration) => { 255 | write!(f, "TimerA: {} {}", key, duration.as_millis()) 256 | } 257 | TransactionTimer::TimerB(key) => write!(f, "TimerB: {}", key), 258 | TransactionTimer::TimerD(key) => write!(f, "TimerD: {}", key), 259 | TransactionTimer::TimerE(key) => write!(f, "TimerE: {}", key), 260 | TransactionTimer::TimerF(key) => write!(f, "TimerF: {}", key), 261 | TransactionTimer::TimerG(key, duration) => { 262 | write!(f, "TimerG: {} {}", key, duration.as_millis()) 263 | } 264 | TransactionTimer::TimerK(key) => write!(f, "TimerK: {}", key), 265 | TransactionTimer::TimerCleanup(key) => write!(f, "TimerCleanup: {}", key), 266 | } 267 | } 268 | } 269 | 270 | pub fn make_via_branch() -> rsip::Param { 271 | rsip::Param::Branch(format!("z9hG4bK{}", random_text(BRANCH_LEN)).into()) 272 | } 273 | 274 | pub fn make_call_id(domain: Option<&str>) -> rsip::headers::CallId { 275 | format!("{}@{}", Uuid::new_v4(), domain.unwrap_or("restsend.com")).into() 276 | } 277 | 278 | pub fn make_tag() -> rsip::param::Tag { 279 | random_text(TO_TAG_LEN).into() 280 | } 281 | 282 | #[cfg(not(target_family = "wasm"))] 283 | pub fn random_text(count: usize) -> String { 284 | use rand::Rng; 285 | rand::rng() 286 | .sample_iter(rand::distr::Alphanumeric) 287 | .take(count) 288 | .map(char::from) 289 | .collect::() 290 | } 291 | 292 | #[cfg(target_family = "wasm")] 293 | pub fn random_text(count: usize) -> String { 294 | (0..count) 295 | .map(|_| { 296 | let r = js_sys::Math::random(); 297 | let c = (r * 16.0) as u8; 298 | if c < 10 { 299 | (c + 48) as char 300 | } else { 301 | (c + 87) as char 302 | } 303 | }) 304 | .collect() 305 | } 306 | -------------------------------------------------------------------------------- /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).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 = ";expires=3600;+org.linphone.specs=\"lime\""; 46 | let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact"); 47 | assert_eq!(contact_uri.to_string(), "sip:bob@localhost;transport=UDP"); 48 | 49 | let line = ";message-expires=2419200;+sip.instance=\"\""; 50 | let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact"); 51 | assert_eq!( 52 | contact_uri.to_string(), 53 | "sip:bob@restsend.com;transport=UDP" 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/transaction/tests/test_client.rs: -------------------------------------------------------------------------------- 1 | use crate::transaction::key::{TransactionKey, TransactionRole}; 2 | use crate::transaction::transaction::Transaction; 3 | use crate::transport::udp::UdpConnection; 4 | use crate::{transport::TransportEvent, Result}; 5 | use rsip::{headers::*, SipMessage}; 6 | use std::time::Duration; 7 | use tokio::{select, sync::mpsc::unbounded_channel, time::sleep}; 8 | use tracing::info; 9 | 10 | #[tokio::test] 11 | async fn test_client_transaction() -> Result<()> { 12 | let endpoint = super::create_test_endpoint(Some("127.0.0.1:0")).await?; 13 | let server_addr = endpoint 14 | .get_addrs() 15 | .get(0) 16 | .expect("must has connection") 17 | .to_owned(); 18 | info!("server addr: {}", server_addr); 19 | 20 | let peer_server = UdpConnection::create_connection("127.0.0.1:0".parse()?, None).await?; 21 | let peer_server_loop = async { 22 | let (sender, mut recevier) = unbounded_channel(); 23 | select! { 24 | _ = async { 25 | if let Some(event) = recevier.recv().await { 26 | match event { 27 | TransportEvent::Incoming(msg, connection, _) => { 28 | info!("recv request: {}", msg); 29 | assert!(msg.is_request()); 30 | match msg { 31 | SipMessage::Request(req) => { 32 | let headers = req.headers.clone(); 33 | let response = SipMessage::Response(rsip::message::Response { 34 | version: rsip::Version::V2, 35 | status_code:rsip::StatusCode::Trying, 36 | headers: headers.clone(), 37 | body: Default::default(), 38 | }); 39 | connection.send(response, None).await.expect("send trying"); 40 | sleep(Duration::from_millis(100)).await; 41 | 42 | let response = SipMessage::Response(rsip::message::Response { 43 | version: rsip::Version::V2, 44 | status_code:rsip::StatusCode::OK, 45 | headers, 46 | body: Default::default(), 47 | }); 48 | connection.send(response, None).await.expect("send Ok"); 49 | sleep(Duration::from_secs(1)).await; 50 | } 51 | _ => { 52 | assert!(false, "must not reach here"); 53 | } 54 | } 55 | } 56 | _ => {} 57 | } 58 | } else { 59 | assert!(false, "must not reach here"); 60 | } 61 | } => {} 62 | _ = peer_server.serve_loop(sender) => { 63 | assert!(false, "must not reach here"); 64 | } 65 | } 66 | }; 67 | 68 | let recv_loop = async { 69 | let register_req = rsip::message::Request { 70 | method: rsip::method::Method::Register, 71 | uri: rsip::Uri { 72 | scheme: Some(rsip::Scheme::Sip), 73 | host_with_port: peer_server.get_addr().addr.clone(), 74 | ..Default::default() 75 | }, 76 | headers: vec![ 77 | Via::new("SIP/2.0/TLS restsend.com:5061;branch=z9hG4bKnashd92").into(), 78 | CSeq::new("1 REGISTER").into(), 79 | From::new("Bob ;tag=ja743ks76zlflH").into(), 80 | CallId::new("1j9FpLxk3uxtm8tn@restsend.com").into(), 81 | ] 82 | .into(), 83 | version: rsip::Version::V2, 84 | body: Default::default(), 85 | }; 86 | 87 | let key = TransactionKey::from_request(®ister_req, TransactionRole::Client) 88 | .expect("client_transaction"); 89 | let mut tx = Transaction::new_client(key, register_req, endpoint.inner.clone(), None); 90 | tx.send().await.expect("send request"); 91 | 92 | while let Some(resp) = tx.receive().await { 93 | info!("Received response: {:?}", resp); 94 | } 95 | }; 96 | 97 | select! { 98 | _ = recv_loop => {} 99 | _ = peer_server_loop => { 100 | assert!(false, "must not reach here"); 101 | } 102 | _ = endpoint.serve() => { 103 | assert!(false, "must not reach here"); 104 | } 105 | _ = sleep(Duration::from_secs(1)) => { 106 | assert!(false, "timeout waiting"); 107 | } 108 | } 109 | Ok(()) 110 | } 111 | -------------------------------------------------------------------------------- /src/transaction/tests/test_endpoint.rs: -------------------------------------------------------------------------------- 1 | use rsip::headers::*; 2 | use std::time::Duration; 3 | use tokio::{select, time::sleep}; 4 | 5 | #[tokio::test] 6 | async fn test_endpoint_serve() { 7 | let endpoint = super::create_test_endpoint(None) 8 | .await 9 | .expect("create_test_endpoint"); 10 | select! { 11 | _ = async { 12 | sleep(Duration::from_millis(10)).await; 13 | endpoint.shutdown(); 14 | sleep(Duration::from_secs(1)).await; 15 | } => { 16 | assert!(false, "must not reach here"); 17 | } 18 | _ = endpoint.serve()=> {} 19 | } 20 | } 21 | 22 | #[tokio::test] 23 | async fn test_endpoint_recvrequests() { 24 | let addr = "127.0.0.1:0"; 25 | let endpoint = super::create_test_endpoint(Some(addr)) 26 | .await 27 | .expect("create_test_endpoint"); 28 | 29 | let addr = endpoint 30 | .get_addrs() 31 | .get(0) 32 | .expect("must has connection") 33 | .to_owned(); 34 | 35 | let send_loop = async { 36 | let test_conn = crate::transport::udp::UdpConnection::create_connection( 37 | "127.0.0.1:0".parse().unwrap(), 38 | None, 39 | ) 40 | .await 41 | .expect("create_connection"); 42 | let register_req = rsip::message::Request { 43 | method: rsip::method::Method::Register, 44 | uri: rsip::Uri { 45 | scheme: Some(rsip::Scheme::Sips), 46 | auth: Some(rsip::Auth { 47 | user: "bob".to_string(), 48 | password: None, 49 | }), 50 | host_with_port: rsip::HostWithPort::try_from("restsend.com") 51 | .expect("host_port parse") 52 | .into(), 53 | ..Default::default() 54 | }, 55 | headers: vec![ 56 | Via::new("SIP/2.0/TLS restsend.com:5061;branch=z9hG4bKnashd92").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 | let buf: String = register_req.try_into().expect("try_into"); 66 | test_conn 67 | .send_raw(&buf.as_bytes(), &addr) 68 | .await 69 | .expect("send_raw"); 70 | sleep(Duration::from_secs(1)).await; 71 | }; 72 | 73 | let incoming_loop = async { 74 | let mut incoming = endpoint.incoming_transactions(); 75 | incoming.recv().await.expect("incoming").original.clone() 76 | }; 77 | 78 | select! { 79 | _ = send_loop => { 80 | assert!(false, "must not reach here"); 81 | } 82 | _ = endpoint.serve()=> {} 83 | req = incoming_loop => { 84 | assert_eq!(req.method, rsip::method::Method::Register); 85 | assert_eq!(req.uri.to_string(), "sips:bob@restsend.com"); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/transaction/tests/test_server.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::SipConnection; 2 | use crate::{ 3 | transport::{channel::ChannelConnection, SipAddr, TransportEvent, TransportLayer}, 4 | EndpointBuilder, 5 | }; 6 | use rsip::headers::*; 7 | use std::time::Duration; 8 | use tokio::{select, sync::mpsc::unbounded_channel, 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 | let addr = SipAddr { 16 | r#type: Some(rsip::transport::Transport::Udp), 17 | addr: "127.0.0.1:2025".try_into().expect("parse addr"), 18 | }; 19 | let (incoming_tx, incoming_rx) = unbounded_channel(); 20 | let (outgoing_tx, mut outgoing_rx) = unbounded_channel(); 21 | 22 | let mock_conn: SipConnection = 23 | ChannelConnection::create_connection(incoming_rx, outgoing_tx, addr.clone()) 24 | .await 25 | .expect("create_connection") 26 | .into(); 27 | 28 | let tl = TransportLayer::new(token.child_token()); 29 | tl.add_transport(mock_conn.clone()); 30 | 31 | let endpoint = EndpointBuilder::new() 32 | .with_user_agent("rsipstack-test") 33 | .with_transport_layer(tl) 34 | .build(); 35 | 36 | let addr = endpoint 37 | .get_addrs() 38 | .get(0) 39 | .expect("must has connection") 40 | .to_owned(); 41 | 42 | let send_loop = async { 43 | let register_req = rsip::message::Request { 44 | method: rsip::method::Method::Register, 45 | uri: rsip::Uri { 46 | scheme: Some(rsip::Scheme::Sip), 47 | host_with_port: rsip::HostWithPort::try_from("127.0.0.1:2025") 48 | .expect("host_port parse") 49 | .into(), 50 | ..Default::default() 51 | }, 52 | headers: vec![ 53 | Via::new("SIP/2.0/TLS restsend.com:5061;branch=z9hG4bKnashd92").into(), 54 | CSeq::new("1 REGISTER").into(), 55 | From::new("Bob ;tag=ja743ks76zlflH").into(), 56 | CallId::new("1j9FpLxk3uxtm8tn@restsend.com").into(), 57 | ] 58 | .into(), 59 | version: rsip::Version::V2, 60 | body: Default::default(), 61 | }; 62 | incoming_tx 63 | .send(TransportEvent::Incoming( 64 | register_req.into(), 65 | mock_conn.clone(), 66 | addr.clone(), 67 | )) 68 | .expect("incoming_tx.send"); 69 | 70 | // wait 100 tring 71 | let resp_1xx = outgoing_rx.recv().await.expect("outgoing_rx"); 72 | match resp_1xx { 73 | TransportEvent::Incoming(msg, _, _) => match msg { 74 | rsip::SipMessage::Response(resp) => { 75 | info!("resp: {:?}", resp); 76 | assert_eq!(resp.status_code, rsip::StatusCode::Trying); 77 | } 78 | _ => { 79 | assert!(false, "unexpected message"); 80 | } 81 | }, 82 | _ => { 83 | assert!(false, "unexpected event"); 84 | } 85 | }; 86 | 87 | let must_200_resp = async { 88 | let resp_200 = outgoing_rx.recv().await.expect("outgoing_rx"); 89 | match resp_200 { 90 | TransportEvent::Incoming(msg, _, _) => match msg { 91 | rsip::SipMessage::Response(resp) => { 92 | assert_eq!(resp.status_code, rsip::StatusCode::OK); 93 | } 94 | _ => { 95 | assert!(false, "unexpected message"); 96 | } 97 | }, 98 | _ => { 99 | assert!(false, "unexpected event"); 100 | } 101 | } 102 | }; 103 | 104 | select! { 105 | _ = must_200_resp => {} 106 | _ = sleep(Duration::from_millis(500)) => { 107 | assert!(false, "timeout waiting"); 108 | } 109 | }; 110 | }; 111 | 112 | let incoming_loop = async { 113 | let mut incoming = endpoint.incoming_transactions(); 114 | let mut tx = incoming.recv().await.expect("incoming"); 115 | assert_eq!(tx.original.method, rsip::method::Method::Register); 116 | let headers = tx.original.headers.clone(); 117 | let done_response = rsip::Response { 118 | status_code: rsip::StatusCode::OK, 119 | version: rsip::Version::V2, 120 | headers, 121 | ..Default::default() 122 | }; 123 | tx.send_trying().await.expect("send_trying"); 124 | tx.respond(done_response).await.expect("respond 200"); 125 | 126 | assert!(tx 127 | .endpoint_inner 128 | .finished_transactions 129 | .lock() 130 | .unwrap() 131 | .contains_key(&tx.key)); 132 | sleep(Duration::from_secs(2)).await; 133 | }; 134 | 135 | select! { 136 | _ = send_loop => { 137 | } 138 | _ = endpoint.serve()=> {} 139 | _ = incoming_loop => { 140 | assert!(false, "must not reach here"); 141 | } 142 | _ = sleep(Duration::from_secs(1)) => { 143 | assert!(false, "timeout waiting"); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /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::Calling); 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::Calling); 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 Calling 97 | assert_eq!(tx.state, TransactionState::Calling); 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 Calling 119 | assert_eq!(tx.state, TransactionState::Calling); 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/transaction/timer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeMap, HashMap}, 3 | sync::{ 4 | atomic::{AtomicU64, Ordering}, 5 | RwLock, 6 | }, 7 | time::{Duration, Instant}, 8 | }; 9 | 10 | #[derive(Debug, PartialOrd, PartialEq, Eq, Clone)] 11 | struct TimerKey { 12 | task_id: u64, 13 | execute_at: Instant, 14 | } 15 | 16 | impl Ord for TimerKey { 17 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 18 | self.execute_at.cmp(&other.execute_at) 19 | } 20 | } 21 | 22 | pub struct Timer { 23 | tasks: RwLock>, 24 | id_to_tasks: RwLock>, 25 | last_task_id: AtomicU64, 26 | } 27 | 28 | impl Timer { 29 | pub fn new() -> Self { 30 | Timer { 31 | tasks: RwLock::new(BTreeMap::new()), 32 | id_to_tasks: RwLock::new(HashMap::new()), 33 | last_task_id: AtomicU64::new(1), 34 | } 35 | } 36 | 37 | pub fn len(&self) -> usize { 38 | self.tasks.read().unwrap().len() 39 | } 40 | 41 | pub fn timeout(&self, duration: Duration, value: T) -> u64 { 42 | self.timeout_at(Instant::now() + duration, value) 43 | } 44 | 45 | pub fn timeout_at(&self, execute_at: Instant, value: T) -> u64 { 46 | let task_id = self.last_task_id.fetch_add(1, Ordering::Relaxed); 47 | self.tasks.write().unwrap().insert( 48 | TimerKey { 49 | task_id, 50 | execute_at, 51 | }, 52 | value, 53 | ); 54 | 55 | self.id_to_tasks 56 | .write() 57 | .unwrap() 58 | .insert(task_id, execute_at); 59 | task_id 60 | } 61 | 62 | pub fn cancel(&self, task_id: u64) -> Option { 63 | let position = { self.id_to_tasks.write().unwrap().remove(&task_id) }; 64 | if let Some(execute_at) = position { 65 | self.tasks.write().unwrap().remove(&TimerKey { 66 | task_id, 67 | execute_at, 68 | }) 69 | } else { 70 | None 71 | } 72 | } 73 | 74 | pub fn poll(&self, now: Instant) -> Vec { 75 | let mut result = Vec::new(); 76 | let keys_to_remove = { 77 | let mut tasks = self.tasks.write().unwrap(); 78 | let keys_to_remove = tasks 79 | .range( 80 | ..=TimerKey { 81 | task_id: 0, 82 | execute_at: now, 83 | }, 84 | ) 85 | .map(|(key, _)| key.clone()) 86 | .collect::>(); 87 | 88 | if keys_to_remove.len() == 0 { 89 | return result; 90 | } 91 | result.reserve(keys_to_remove.len()); 92 | for key in keys_to_remove.iter() { 93 | tasks.remove(&key).map(|value| result.push(value)); 94 | } 95 | keys_to_remove 96 | }; 97 | { 98 | let mut id_to_tasks = self.id_to_tasks.write().unwrap(); 99 | for key in keys_to_remove { 100 | id_to_tasks.remove(&key.task_id); 101 | } 102 | } 103 | result 104 | } 105 | } 106 | 107 | #[test] 108 | fn test_timer() { 109 | use std::time::Duration; 110 | let timer = Timer::new(); 111 | let now = Instant::now(); 112 | let task_id = timer.timeout_at(now, "task1"); 113 | assert_eq!(task_id, 1); 114 | assert_eq!(timer.cancel(task_id), Some("task1")); 115 | assert_eq!(timer.cancel(task_id), None); 116 | 117 | timer.timeout_at(now, "task2"); 118 | let must_hass_task_2 = timer.poll(now + Duration::from_secs(1)); 119 | assert_eq!(must_hass_task_2.len(), 1); 120 | 121 | timer.timeout_at(now + Duration::from_millis(1001), "task3"); 122 | let non_tasks = timer.poll(now + Duration::from_secs(1)); 123 | assert_eq!(non_tasks.len(), 0); 124 | assert_eq!(timer.len(), 1); 125 | } 126 | -------------------------------------------------------------------------------- /src/transport/channel.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | connection::{TransportReceiver, TransportSender}, 3 | SipAddr, SipConnection, 4 | }; 5 | use crate::Result; 6 | use std::sync::{Arc, Mutex}; 7 | 8 | struct ChannelInner { 9 | incoming: Mutex>, 10 | outgoing: TransportSender, 11 | addr: SipAddr, 12 | } 13 | 14 | #[derive(Clone)] 15 | pub struct ChannelConnection { 16 | inner: Arc, 17 | } 18 | 19 | impl ChannelConnection { 20 | pub async fn create_connection( 21 | incoming: TransportReceiver, 22 | outgoing: TransportSender, 23 | addr: SipAddr, 24 | ) -> Result { 25 | let t = ChannelConnection { 26 | inner: Arc::new(ChannelInner { 27 | incoming: Mutex::new(Some(incoming)), 28 | outgoing, 29 | addr, 30 | }), 31 | }; 32 | Ok(t) 33 | } 34 | 35 | pub async fn send(&self, msg: rsip::SipMessage) -> crate::Result<()> { 36 | let transport = SipConnection::Channel(self.clone()); 37 | let source = self.get_addr().clone(); 38 | self.inner 39 | .outgoing 40 | .send(super::TransportEvent::Incoming(msg, transport, source)) 41 | .map_err(|e| e.into()) 42 | } 43 | 44 | pub fn get_addr(&self) -> &SipAddr { 45 | return &self.inner.addr; 46 | } 47 | 48 | pub async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 49 | let incoming = self.inner.clone().incoming.lock().unwrap().take(); 50 | if incoming.is_none() { 51 | return Err(crate::Error::Error( 52 | "ChannelTransport::serve_loop called twice".to_string(), 53 | )); 54 | } 55 | let mut incoming = incoming.unwrap(); 56 | while let Some(event) = incoming.recv().await { 57 | sender.send(event)?; 58 | } 59 | Ok(()) 60 | } 61 | } 62 | 63 | impl std::fmt::Display for ChannelConnection { 64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 | write!(f, "*:*") 66 | } 67 | } 68 | 69 | impl std::fmt::Debug for ChannelConnection { 70 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | write!(f, "*:*") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /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 tls; 7 | pub mod transport_layer; 8 | pub mod udp; 9 | pub mod websocket; 10 | 11 | pub use connection::SipConnection; 12 | pub use connection::TransportEvent; 13 | pub use sip_addr::SipAddr; 14 | pub use transport_layer::TransportLayer; 15 | 16 | #[cfg(test)] 17 | pub mod tests; 18 | -------------------------------------------------------------------------------- /src/transport/sip_addr.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use rsip::{host_with_port, HostWithPort}; 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)] 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 | impl From for SipAddr { 113 | fn from(addr: SocketAddr) -> Self { 114 | let host_with_port = HostWithPort { 115 | host: addr.ip().into(), 116 | port: Some(addr.port().into()), 117 | }; 118 | SipAddr { 119 | r#type: None, 120 | addr: host_with_port, 121 | } 122 | } 123 | } 124 | 125 | impl From for SipAddr { 126 | fn from(host_with_port: rsip::host_with_port::HostWithPort) -> Self { 127 | SipAddr { 128 | r#type: None, 129 | addr: host_with_port, 130 | } 131 | } 132 | } 133 | 134 | impl TryFrom<&rsip::Uri> for SipAddr { 135 | type Error = crate::Error; 136 | 137 | fn try_from(uri: &rsip::Uri) -> Result { 138 | let transport = uri.transport().cloned(); 139 | Ok(SipAddr { 140 | r#type: transport, 141 | addr: uri.host_with_port.clone(), 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /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 std::sync::Arc; 11 | use tokio::{ 12 | io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, 13 | sync::Mutex, 14 | }; 15 | use tokio_util::codec::{Decoder, Encoder}; 16 | use tracing::{debug, error, warn}; 17 | 18 | const MAX_SIP_MESSAGE_SIZE: usize = 65535; 19 | 20 | pub struct SipCodec { 21 | max_size: usize, 22 | } 23 | 24 | impl SipCodec { 25 | pub fn new() -> Self { 26 | Self { 27 | max_size: MAX_SIP_MESSAGE_SIZE, 28 | } 29 | } 30 | } 31 | 32 | impl Default for SipCodec { 33 | fn default() -> Self { 34 | Self::new() 35 | } 36 | } 37 | 38 | impl Decoder for SipCodec { 39 | type Item = SipMessage; 40 | type Error = crate::Error; 41 | 42 | fn decode(&mut self, src: &mut BytesMut) -> Result> { 43 | if src.len() >= 4 && &src[0..4] == KEEPALIVE_REQUEST { 44 | src.advance(4); 45 | return Err(crate::Error::Keepalive); 46 | } 47 | 48 | if src.len() >= 2 && &src[0..2] == KEEPALIVE_RESPONSE { 49 | src.advance(2); 50 | return Err(crate::Error::Keepalive); 51 | } 52 | 53 | let data = match std::str::from_utf8(&src[..]) { 54 | Ok(s) => s, 55 | Err(_) => { 56 | if src.len() > self.max_size { 57 | return Err(crate::Error::Error("SIP message too large".to_string())); 58 | } 59 | return Ok(None); 60 | } 61 | }; 62 | 63 | if !data.contains("\r\n\r\n") { 64 | if src.len() > self.max_size { 65 | return Err(crate::Error::Error("SIP message too large".to_string())); 66 | } 67 | return Ok(None); 68 | } 69 | 70 | match SipMessage::try_from(data) { 71 | Ok(msg) => { 72 | let msg_len = data.find("\r\n\r\n").unwrap() + 4; 73 | src.advance(msg_len); 74 | Ok(Some(msg)) 75 | } 76 | Err(e) => { 77 | if let Some(pos) = data[1..].find("\r\n\r\n") { 78 | src.advance(pos + 5); 79 | } else { 80 | src.clear(); 81 | } 82 | Err(crate::Error::Error(format!( 83 | "Failed to parse SIP message: {}", 84 | e 85 | ))) 86 | } 87 | } 88 | } 89 | } 90 | 91 | impl Encoder for SipCodec { 92 | type Error = crate::Error; 93 | 94 | fn encode(&mut self, item: SipMessage, dst: &mut BytesMut) -> Result<()> { 95 | let data = item.to_string(); 96 | dst.extend_from_slice(data.as_bytes()); 97 | Ok(()) 98 | } 99 | } 100 | 101 | #[async_trait::async_trait] 102 | pub trait StreamConnection: Send + Sync + 'static { 103 | fn get_addr(&self) -> &SipAddr; 104 | 105 | async fn send_message(&self, msg: SipMessage) -> Result<()>; 106 | 107 | async fn send_raw(&self, data: &[u8]) -> Result<()>; 108 | 109 | async fn serve_loop(&self, sender: TransportSender) -> Result<()>; 110 | 111 | async fn close(&self) -> Result<()>; 112 | } 113 | 114 | pub async fn handle_stream( 115 | stream: S, 116 | local_addr: SipAddr, 117 | remote_addr: SipAddr, 118 | connection: SipConnection, 119 | sender: TransportSender, 120 | ) -> Result<()> 121 | where 122 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 123 | { 124 | let (mut read_half, write_half) = tokio::io::split(stream); 125 | let write_half = Arc::new(Mutex::new(write_half)); 126 | 127 | let mut codec = SipCodec::new(); 128 | let mut buffer = BytesMut::with_capacity(4096); 129 | 130 | sender.send(TransportEvent::New(connection.clone()))?; 131 | 132 | let mut read_buf = [0u8; 4096]; 133 | 134 | loop { 135 | match read_half.read(&mut read_buf).await { 136 | Ok(0) => { 137 | debug!("Connection closed: {}", local_addr); 138 | break; 139 | } 140 | Ok(n) => { 141 | buffer.extend_from_slice(&read_buf[0..n]); 142 | 143 | loop { 144 | match codec.decode(&mut buffer) { 145 | Ok(Some(msg)) => { 146 | debug!("Received message from {}: {:?}", remote_addr, msg); 147 | 148 | sender.send(TransportEvent::Incoming( 149 | msg, 150 | connection.clone(), 151 | remote_addr.clone(), 152 | ))?; 153 | } 154 | Ok(None) => { 155 | break; 156 | } 157 | Err(crate::Error::Keepalive) => { 158 | let mut lock = write_half.lock().await; 159 | lock.write_all(KEEPALIVE_RESPONSE).await?; 160 | lock.flush().await?; 161 | } 162 | Err(e) => { 163 | warn!("Error decoding message from {}: {:?}", remote_addr, e); 164 | } 165 | } 166 | } 167 | } 168 | Err(e) => { 169 | error!("Error reading from stream: {}", e); 170 | break; 171 | } 172 | } 173 | } 174 | 175 | sender.send(TransportEvent::Closed(connection))?; 176 | 177 | Ok(()) 178 | } 179 | 180 | pub async fn send_to_stream(write_half: &Arc>, msg: SipMessage) -> Result<()> 181 | where 182 | W: AsyncWrite + Unpin + Send, 183 | { 184 | let data = msg.to_string(); 185 | let mut lock = write_half.lock().await; 186 | lock.write_all(data.as_bytes()).await?; 187 | lock.flush().await?; 188 | Ok(()) 189 | } 190 | 191 | pub async fn send_raw_to_stream(write_half: &Arc>, data: &[u8]) -> Result<()> 192 | where 193 | W: AsyncWrite + Unpin + Send, 194 | { 195 | let mut lock = write_half.lock().await; 196 | lock.write_all(data).await?; 197 | lock.flush().await?; 198 | Ok(()) 199 | } 200 | -------------------------------------------------------------------------------- /src/transport/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::TransportSender, 4 | sip_addr::SipAddr, 5 | stream::{send_raw_to_stream, send_to_stream, StreamConnection}, 6 | SipConnection, TransportEvent, 7 | }, 8 | Result, 9 | }; 10 | use rsip::SipMessage; 11 | use std::{fmt, sync::Arc}; 12 | use tokio::{ 13 | io::{AsyncReadExt, AsyncWriteExt}, 14 | net::{TcpListener, TcpStream}, 15 | sync::Mutex, 16 | }; 17 | use tracing::{debug, error, info}; 18 | pub struct TcpInner { 19 | pub local_addr: SipAddr, 20 | pub remote_addr: Option, 21 | pub read_half: Arc>>, 22 | pub write_half: Arc>>, 23 | } 24 | 25 | #[derive(Clone)] 26 | pub struct TcpConnection { 27 | pub inner: Arc, 28 | } 29 | 30 | impl TcpConnection { 31 | pub async fn connect(remote: &SipAddr) -> Result { 32 | let socket_addr = remote.get_socketaddr()?; 33 | let stream = TcpStream::connect(socket_addr).await?; 34 | 35 | let local_addr = SipAddr { 36 | r#type: Some(rsip::transport::Transport::Tcp), 37 | addr: remote.addr.clone(), 38 | }; 39 | 40 | let (read_half, write_half) = tokio::io::split(stream); 41 | 42 | let connection = TcpConnection { 43 | inner: Arc::new(TcpInner { 44 | local_addr, 45 | remote_addr: Some(remote.clone()), 46 | read_half: Arc::new(Mutex::new(read_half)), 47 | write_half: Arc::new(Mutex::new(write_half)), 48 | }), 49 | }; 50 | 51 | info!( 52 | "Created TCP client connection: {} -> {}", 53 | connection.get_addr(), 54 | remote 55 | ); 56 | 57 | Ok(connection) 58 | } 59 | 60 | pub async fn from_stream(stream: TcpStream, local_addr: SipAddr) -> Result { 61 | let remote_addr = stream.peer_addr()?; 62 | let remote_sip_addr = SipAddr { 63 | r#type: Some(rsip::transport::Transport::Tcp), 64 | addr: remote_addr.into(), 65 | }; 66 | 67 | let (read_half, write_half) = tokio::io::split(stream); 68 | 69 | let connection = TcpConnection { 70 | inner: Arc::new(TcpInner { 71 | local_addr, 72 | remote_addr: Some(remote_sip_addr), 73 | read_half: Arc::new(Mutex::new(read_half)), 74 | write_half: Arc::new(Mutex::new(write_half)), 75 | }), 76 | }; 77 | 78 | info!( 79 | "Created TCP server connection: {} <- {}", 80 | connection.get_addr(), 81 | remote_addr 82 | ); 83 | 84 | Ok(connection) 85 | } 86 | 87 | pub async fn create_listener(local: std::net::SocketAddr) -> Result<(TcpListener, SipAddr)> { 88 | let listener = TcpListener::bind(local).await?; 89 | let local_addr = listener.local_addr()?; 90 | 91 | let sip_addr = SipAddr { 92 | r#type: Some(rsip::transport::Transport::Tcp), 93 | addr: local_addr.into(), 94 | }; 95 | 96 | info!("Created TCP listener on {}", local_addr); 97 | 98 | Ok((listener, sip_addr)) 99 | } 100 | 101 | pub async fn serve_listener( 102 | listener: TcpListener, 103 | local_addr: SipAddr, 104 | sender: TransportSender, 105 | ) -> Result<()> { 106 | info!("Starting TCP listener on {}", local_addr); 107 | 108 | loop { 109 | match listener.accept().await { 110 | Ok((stream, remote_addr)) => { 111 | debug!("New TCP connection from {}", remote_addr); 112 | 113 | let tcp_connection = 114 | TcpConnection::from_stream(stream, local_addr.clone()).await?; 115 | let sip_connection = SipConnection::Tcp(tcp_connection.clone()); 116 | 117 | let sender_clone = sender.clone(); 118 | 119 | tokio::spawn(async move { 120 | if let Err(e) = tcp_connection.serve_loop(sender_clone).await { 121 | error!("Error handling TCP connection: {:?}", e); 122 | } 123 | }); 124 | 125 | if let Err(e) = sender.send(TransportEvent::New(sip_connection)) { 126 | error!("Error sending new connection event: {:?}", e); 127 | } 128 | } 129 | Err(e) => { 130 | error!("Error accepting TCP connection: {}", e); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | #[async_trait::async_trait] 138 | impl StreamConnection for TcpConnection { 139 | fn get_addr(&self) -> &SipAddr { 140 | &self.inner.local_addr 141 | } 142 | 143 | async fn send_message(&self, msg: SipMessage) -> Result<()> { 144 | info!("TcpConnection send:{}", msg); 145 | send_to_stream(&self.inner.write_half, msg).await 146 | } 147 | 148 | async fn send_raw(&self, data: &[u8]) -> Result<()> { 149 | send_raw_to_stream(&self.inner.write_half, data).await 150 | } 151 | 152 | async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 153 | let local_addr = self.inner.local_addr.clone(); 154 | let remote_addr = self.inner.remote_addr.clone().unwrap(); 155 | let sip_connection = SipConnection::Tcp(self.clone()); 156 | 157 | // We need to reconstruct the stream, but that's not easily possible 158 | // So let's keep using the manual approach but with SipCodec 159 | use crate::transport::stream::SipCodec; 160 | use bytes::BytesMut; 161 | use tokio_util::codec::Decoder; 162 | 163 | let mut codec = SipCodec::new(); 164 | let mut buffer = BytesMut::with_capacity(4096); 165 | let mut read_buf = [0u8; 4096]; 166 | let mut read_half = self.inner.read_half.lock().await; 167 | 168 | loop { 169 | match read_half.read(&mut read_buf).await { 170 | Ok(0) => { 171 | info!("TCP connection closed: {}", local_addr); 172 | break; 173 | } 174 | Ok(n) => { 175 | buffer.extend_from_slice(&read_buf[0..n]); 176 | 177 | loop { 178 | match codec.decode(&mut buffer) { 179 | Ok(Some(msg)) => { 180 | info!("TCP received message from {}: {}", remote_addr, msg); 181 | 182 | if let Err(e) = sender.send(TransportEvent::Incoming( 183 | msg, 184 | sip_connection.clone(), 185 | remote_addr.clone(), 186 | )) { 187 | error!("Error sending incoming message: {:?}", e); 188 | return Err(e.into()); 189 | } 190 | } 191 | Ok(None) => { 192 | // Need more data 193 | break; 194 | } 195 | Err(crate::Error::Keepalive) => { 196 | // Handle keepalive 197 | self.send_raw(crate::transport::connection::KEEPALIVE_RESPONSE) 198 | .await?; 199 | } 200 | Err(e) => { 201 | error!("Error decoding message from {}: {:?}", remote_addr, e); 202 | // Continue processing despite decode errors 203 | } 204 | } 205 | } 206 | } 207 | Err(e) => { 208 | error!("Error reading from TCP stream: {}", e); 209 | break; 210 | } 211 | } 212 | } 213 | Ok(()) 214 | } 215 | 216 | async fn close(&self) -> Result<()> { 217 | let mut write_half = self.inner.write_half.lock().await; 218 | write_half.shutdown().await?; 219 | Ok(()) 220 | } 221 | } 222 | 223 | impl fmt::Display for TcpConnection { 224 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 225 | match &self.inner.remote_addr { 226 | Some(remote) => write!(f, "TCP {} -> {}", self.inner.local_addr, remote), 227 | None => write!(f, "TCP {}", self.inner.local_addr), 228 | } 229 | } 230 | } 231 | 232 | impl fmt::Debug for TcpConnection { 233 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 234 | fmt::Display::fmt(self, f) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/transport/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod test_connection_management; 2 | mod test_sipaddr; 3 | mod test_stream_encoding; 4 | mod test_udp; 5 | mod test_via_received; 6 | mod transport_tests; 7 | -------------------------------------------------------------------------------- /src/transport/tests/test_connection_management.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::TransportEvent, stream::StreamConnection, tcp::TcpConnection, TransportLayer, 4 | }, 5 | Result, 6 | }; 7 | use rsip::{ 8 | prelude::{HeadersExt, UntypedHeader}, 9 | SipMessage, Transport, 10 | }; 11 | use std::time::Duration; 12 | use tokio::{ 13 | sync::mpsc::{self, UnboundedReceiver}, 14 | time::timeout, 15 | }; 16 | use tokio_util::sync::CancellationToken; 17 | use tracing::info; 18 | 19 | /// Test TCP server accept flow 20 | #[tokio::test] 21 | async fn test_tcp_server_accept_flow() -> Result<()> { 22 | let cancel_token = CancellationToken::new(); 23 | let transport_layer = TransportLayer::new(cancel_token.clone()); 24 | let (sender, mut receiver) = mpsc::unbounded_channel(); 25 | 26 | // Create TCP listener 27 | let server_addr = transport_layer 28 | .add_tcp_listener("127.0.0.1:0".parse()?, sender.clone()) 29 | .await?; 30 | info!("Created TCP server on {}", server_addr); 31 | 32 | // Start transport layer 33 | transport_layer.serve_listens(sender.clone()).await?; 34 | 35 | // Create multiple client connections 36 | let client1 = TcpConnection::connect(&server_addr).await?; 37 | let client2 = TcpConnection::connect(&server_addr).await?; 38 | let client3 = TcpConnection::connect(&server_addr).await?; 39 | 40 | info!("Created 3 client connections"); 41 | 42 | // Wait for new connection events 43 | let mut new_connections = 0; 44 | for _ in 0..3 { 45 | match wait_for_event(&mut receiver).await? { 46 | TransportEvent::New(_conn) => { 47 | new_connections += 1; 48 | info!("New connection received: {}", new_connections); 49 | } 50 | event => info!("Other event: {:?}", event), 51 | } 52 | } 53 | 54 | assert_eq!(new_connections, 3, "Should receive 3 new connection events"); 55 | 56 | // Close connections 57 | client1.close().await?; 58 | client2.close().await?; 59 | client3.close().await?; 60 | 61 | cancel_token.cancel(); 62 | Ok(()) 63 | } 64 | 65 | /// Test multiple clients sending messages simultaneously 66 | #[tokio::test] 67 | async fn test_multiple_client_messages() -> Result<()> { 68 | let cancel_token = CancellationToken::new(); 69 | let transport_layer = TransportLayer::new(cancel_token.clone()); 70 | let (sender, mut receiver) = mpsc::unbounded_channel(); 71 | 72 | // Create TCP listener 73 | let server_addr = transport_layer 74 | .add_tcp_listener("127.0.0.1:0".parse()?, sender.clone()) 75 | .await?; 76 | 77 | // Start transport layer 78 | transport_layer.serve_listens(sender.clone()).await?; 79 | 80 | // Create multiple client connections 81 | let client1 = TcpConnection::connect(&server_addr).await?; 82 | let client2 = TcpConnection::connect(&server_addr).await?; 83 | 84 | // Wait for new connection events 85 | wait_for_event(&mut receiver).await?; // client1 new 86 | wait_for_event(&mut receiver).await?; // client2 new 87 | 88 | // Send messages from both clients 89 | let message1 = create_test_message("client1"); 90 | let message2 = create_test_message("client2"); 91 | 92 | client1.send_message(message1.clone()).await?; 93 | client2.send_message(message2.clone()).await?; 94 | 95 | // Wait for incoming messages 96 | let mut received_messages = Vec::new(); 97 | for _ in 0..2 { 98 | match wait_for_event(&mut receiver).await? { 99 | TransportEvent::Incoming(msg, _conn, _addr) => { 100 | received_messages.push(msg); 101 | } 102 | event => info!("Other event: {:?}", event), 103 | } 104 | } 105 | 106 | assert_eq!(received_messages.len(), 2, "Should receive 2 messages"); 107 | 108 | // Verify messages (order might be different) 109 | let call_ids: Vec = received_messages 110 | .iter() 111 | .map(|msg| match msg { 112 | SipMessage::Request(req) => req.call_id_header().unwrap().value().to_string(), 113 | _ => panic!("Expected request"), 114 | }) 115 | .collect(); 116 | 117 | assert!(call_ids.contains(&"client1".to_string())); 118 | assert!(call_ids.contains(&"client2".to_string())); 119 | 120 | client1.close().await?; 121 | client2.close().await?; 122 | cancel_token.cancel(); 123 | Ok(()) 124 | } 125 | 126 | /// Test lookup mechanism with different protocols 127 | #[tokio::test] 128 | async fn test_transport_lookup() -> Result<()> { 129 | let cancel_token = CancellationToken::new(); 130 | let transport_layer = TransportLayer::new(cancel_token.clone()); 131 | let (sender, _receiver) = mpsc::unbounded_channel(); 132 | 133 | // Test UDP lookup 134 | let udp_addr = transport_layer 135 | .add_udp_listener("127.0.0.1:0".parse()?) 136 | .await?; 137 | 138 | let udp_uri: rsip::Uri = format!( 139 | "sip:test@{}:{};transport=udp", 140 | "127.0.0.1", 141 | udp_addr.addr.port.unwrap().value() 142 | ) 143 | .try_into()?; 144 | 145 | let (udp_connection, _) = transport_layer.lookup(&udp_uri, sender.clone()).await?; 146 | assert!(matches!( 147 | udp_connection.get_addr().r#type, 148 | Some(Transport::Udp) 149 | )); 150 | 151 | // Test TCP lookup (should create new connection) 152 | let tcp_uri: rsip::Uri = "sip:test@127.0.0.1:12345;transport=tcp".try_into()?; 153 | 154 | // This should fail because there's no TCP server on 12345 155 | let tcp_result = transport_layer.lookup(&tcp_uri, sender.clone()).await; 156 | assert!( 157 | tcp_result.is_err(), 158 | "Should fail to connect to non-existent TCP server" 159 | ); 160 | 161 | cancel_token.cancel(); 162 | Ok(()) 163 | } 164 | 165 | /// Test connection reuse for UDP 166 | #[tokio::test] 167 | async fn test_udp_connection_reuse() -> Result<()> { 168 | let cancel_token = CancellationToken::new(); 169 | let transport_layer = TransportLayer::new(cancel_token.clone()); 170 | let (sender, _receiver) = mpsc::unbounded_channel(); 171 | 172 | // Create UDP listener 173 | let udp_addr = transport_layer 174 | .add_udp_listener("127.0.0.1:0".parse()?) 175 | .await?; 176 | 177 | // Multiple lookups should return the same connection 178 | let uri: rsip::Uri = format!( 179 | "sip:test@{}:{};transport=udp", 180 | "127.0.0.1", 181 | udp_addr.addr.port.unwrap().value() 182 | ) 183 | .try_into()?; 184 | 185 | let (conn1, _) = transport_layer.lookup(&uri, sender.clone()).await?; 186 | let (conn2, _) = transport_layer.lookup(&uri, sender.clone()).await?; 187 | 188 | // For UDP, should reuse the same connection 189 | assert_eq!(conn1.get_addr(), conn2.get_addr()); 190 | 191 | cancel_token.cancel(); 192 | Ok(()) 193 | } 194 | 195 | /// Test connection error handling 196 | #[tokio::test] 197 | async fn test_connection_error_handling() -> Result<()> { 198 | let cancel_token = CancellationToken::new(); 199 | let transport_layer = TransportLayer::new(cancel_token.clone()); 200 | let (sender, mut receiver) = mpsc::unbounded_channel(); 201 | 202 | // Create TCP listener 203 | let server_addr = transport_layer 204 | .add_tcp_listener("127.0.0.1:0".parse()?, sender.clone()) 205 | .await?; 206 | 207 | transport_layer.serve_listens(sender.clone()).await?; 208 | 209 | // Create client connection 210 | let client = TcpConnection::connect(&server_addr).await?; 211 | 212 | // Wait for new connection event 213 | wait_for_event(&mut receiver).await?; 214 | 215 | // Send a message 216 | let message = create_test_message("test"); 217 | client.send_message(message).await?; 218 | 219 | // Wait for incoming message 220 | let _incoming = wait_for_event(&mut receiver).await?; 221 | 222 | // Force close the client connection 223 | client.close().await?; 224 | 225 | // The server should eventually detect the closed connection 226 | // This might generate a Closed event or cause the serve_loop to exit 227 | 228 | cancel_token.cancel(); 229 | Ok(()) 230 | } 231 | 232 | /// Test outbound connection configuration 233 | #[tokio::test] 234 | async fn test_outbound_connection() -> Result<()> { 235 | let cancel_token = CancellationToken::new(); 236 | let mut transport_layer = TransportLayer::new(cancel_token.clone()); 237 | let (sender, _receiver) = mpsc::unbounded_channel(); 238 | 239 | // Create UDP listener to use as outbound 240 | let udp_addr = transport_layer 241 | .add_udp_listener("127.0.0.1:0".parse()?) 242 | .await?; 243 | 244 | // Set outbound connection 245 | transport_layer.outbound = Some(udp_addr.clone()); 246 | 247 | // Any lookup should use the outbound connection 248 | let uri: rsip::Uri = "sip:test@example.com:5060".try_into()?; 249 | let (connection, _) = transport_layer.lookup(&uri, sender.clone()).await?; 250 | 251 | assert_eq!(connection.get_addr(), &udp_addr); 252 | 253 | cancel_token.cancel(); 254 | Ok(()) 255 | } 256 | 257 | async fn wait_for_event( 258 | receiver: &mut UnboundedReceiver, 259 | ) -> Result { 260 | match timeout(Duration::from_secs(5), receiver.recv()).await { 261 | Ok(Some(event)) => Ok(event), 262 | Ok(None) => Err(crate::Error::Error("Channel closed".to_string())), 263 | Err(_) => Err(crate::Error::Error("Timeout waiting for event".to_string())), 264 | } 265 | } 266 | 267 | fn create_test_message(call_id: &str) -> SipMessage { 268 | let test_message = format!( 269 | "REGISTER sip:example.com SIP/2.0\r\n\ 270 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 271 | From: ;tag=test\r\n\ 272 | To: \r\n\ 273 | Call-ID: {}\r\n\ 274 | CSeq: 1 REGISTER\r\n\ 275 | Contact: \r\n\ 276 | Max-Forwards: 70\r\n\ 277 | Content-Length: 0\r\n\r\n", 278 | call_id 279 | ); 280 | 281 | SipMessage::try_from(test_message.as_str()).expect("parse SIP message") 282 | } 283 | -------------------------------------------------------------------------------- /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/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 | 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 | 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 | 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 | 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); 169 | assert!( 170 | matches!(result, Err(crate::Error::Keepalive)), 171 | "Should handle keepalive request" 172 | ); 173 | assert_eq!(buffer.len(), 0, "Keepalive should be consumed from buffer"); 174 | 175 | // Test keepalive response 176 | buffer.extend_from_slice(KEEPALIVE_RESPONSE); 177 | let result = codec.decode(&mut buffer); 178 | assert!( 179 | matches!(result, Err(crate::Error::Keepalive)), 180 | "Should handle keepalive response" 181 | ); 182 | assert_eq!(buffer.len(), 0, "Keepalive should be consumed from buffer"); 183 | } 184 | 185 | /// Test SipCodec encoding 186 | #[test] 187 | fn test_sip_codec_encoding() { 188 | let mut codec = SipCodec::new(); 189 | let mut buffer = BytesMut::new(); 190 | 191 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 192 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 193 | From: ;tag=test\r\n\ 194 | To: \r\n\ 195 | Call-ID: test-call-id\r\n\ 196 | CSeq: 1 REGISTER\r\n\ 197 | Contact: \r\n\ 198 | Max-Forwards: 70\r\n\ 199 | Content-Length: 0\r\n\r\n"; 200 | 201 | let sip_message = SipMessage::try_from(test_message).expect("parse SIP message"); 202 | 203 | codec 204 | .encode(sip_message, &mut buffer) 205 | .expect("encode should succeed"); 206 | 207 | assert_eq!( 208 | String::from_utf8_lossy(&buffer), 209 | test_message, 210 | "Encoded message should match original" 211 | ); 212 | } 213 | 214 | /// Test error handling for malformed messages 215 | #[test] 216 | fn test_sip_codec_malformed_message() { 217 | let mut codec = SipCodec::new(); 218 | let mut buffer = BytesMut::new(); 219 | 220 | let malformed_message = "INVALID MESSAGE\r\n\r\n"; 221 | 222 | buffer.extend_from_slice(malformed_message.as_bytes()); 223 | 224 | let result = codec.decode(&mut buffer); 225 | assert!(result.is_err(), "Should error on malformed message"); 226 | 227 | // Buffer should be cleared or partially consumed 228 | // The exact behavior depends on implementation 229 | } 230 | 231 | /// Test message size limit 232 | #[test] 233 | fn test_sip_codec_size_limit() { 234 | let mut codec = SipCodec::new(); 235 | let mut buffer = BytesMut::new(); 236 | 237 | // Create a message larger than the limit without complete headers (no \r\n\r\n) 238 | let large_content = "A".repeat(70000); 239 | let incomplete_large_message = format!( 240 | "MESSAGE sip:example.com SIP/2.0\r\n\ 241 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 242 | From: ;tag=test\r\n\ 243 | To: \r\n\ 244 | Call-ID: test-call-id\r\n\ 245 | CSeq: 1 MESSAGE\r\n\ 246 | Content-Type: text/plain\r\n\ 247 | Content-Length: {}\r\n{}", 248 | large_content.len(), 249 | large_content // No \r\n\r\n ending to trigger size check 250 | ); 251 | 252 | buffer.extend_from_slice(incomplete_large_message.as_bytes()); 253 | 254 | let result = codec.decode(&mut buffer); 255 | assert!(result.is_err(), "Should error on oversized message"); 256 | } 257 | -------------------------------------------------------------------------------- /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).await?; 15 | let peer_alice = UdpConnection::create_connection("127.0.0.1:0".parse()?, 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).await?; 46 | let peer_alice = UdpConnection::create_connection("127.0.0.1:0".parse()?, 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/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/transport/tests/transport_tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::TransportEvent, stream::StreamConnection, tcp::TcpConnection, 4 | transport_layer::TransportConfig, TransportLayer, 5 | }, 6 | Result, 7 | }; 8 | use rsip::{SipMessage, Transport}; 9 | use std::time::Duration; 10 | use tokio::{ 11 | sync::mpsc::{self, UnboundedReceiver}, 12 | time::timeout, 13 | }; 14 | use tokio_util::sync::CancellationToken; 15 | use tracing::info; 16 | 17 | /// Test TCP client and server 18 | #[tokio::test] 19 | async fn test_tcp_client_server() -> Result<()> { 20 | // Create transport layer 21 | let cancel_token = CancellationToken::new(); 22 | let transport_layer = TransportLayer::new(cancel_token.clone()); 23 | 24 | // Create event channel 25 | let (sender, mut receiver) = mpsc::unbounded_channel(); 26 | 27 | // Create TCP listener 28 | let server_addr = transport_layer 29 | .add_tcp_listener("127.0.0.1:0".parse()?, sender.clone()) 30 | .await?; 31 | info!("Created TCP server on {}", server_addr); 32 | 33 | // Start transport layer 34 | transport_layer.serve_listens(sender.clone()).await?; 35 | 36 | // Create TCP client connection 37 | let client_connection = TcpConnection::connect(&server_addr).await?; 38 | info!("Created TCP client connection: {}", client_connection); 39 | 40 | // Send test message 41 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 42 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 43 | From: ;tag=test\r\n\ 44 | To: \r\n\ 45 | Call-ID: test-call-id\r\n\ 46 | CSeq: 1 REGISTER\r\n\ 47 | Contact: \r\n\ 48 | Max-Forwards: 70\r\n\ 49 | Content-Length: 0\r\n\r\n"; 50 | 51 | let sip_message = SipMessage::try_from(test_message)?; 52 | client_connection.send_message(sip_message.clone()).await?; 53 | 54 | // Wait for message 55 | let event = wait_for_event(&mut receiver).await?; 56 | info!("Received event: {:?}", event); 57 | match event { 58 | TransportEvent::Incoming(msg, _conn, addr) => { 59 | assert_eq!(msg.to_string(), sip_message.to_string()); 60 | assert_eq!(addr.r#type, Some(Transport::Tcp)); 61 | } 62 | TransportEvent::Closed(_conn) => { 63 | info!("Connection closed by the server"); 64 | } 65 | TransportEvent::New(_conn) => { 66 | info!("Connection created"); 67 | } 68 | } 69 | 70 | // Close connection 71 | client_connection.close().await?; 72 | cancel_token.cancel(); 73 | 74 | Ok(()) 75 | } 76 | 77 | /// Wait for event with timeout 78 | async fn wait_for_event( 79 | receiver: &mut UnboundedReceiver, 80 | ) -> Result { 81 | match timeout(Duration::from_secs(5), receiver.recv()).await { 82 | Ok(Some(event)) => Ok(event), 83 | Ok(None) => Err(crate::Error::Error("Channel closed".to_string())), 84 | Err(_) => Err(crate::Error::Error("Timeout waiting for event".to_string())), 85 | } 86 | } 87 | 88 | /// Test interoperability between TCP and UDP 89 | #[tokio::test] 90 | #[ignore] 91 | async fn test_tcp_udp_interop() -> Result<()> { 92 | // Create transport layer 93 | let cancel_token = CancellationToken::new(); 94 | let transport_layer = TransportLayer::new(cancel_token.clone()); 95 | 96 | // Create event channel 97 | let (sender, mut receiver) = mpsc::unbounded_channel(); 98 | 99 | // Create UDP listener 100 | let udp_addr = transport_layer 101 | .add_udp_listener("127.0.0.1:0".parse()?) 102 | .await?; 103 | info!("Created UDP server on {}", udp_addr); 104 | 105 | // Create TCP listener 106 | let tcp_addr = transport_layer 107 | .add_tcp_listener("127.0.0.1:0".parse()?, sender.clone()) 108 | .await?; 109 | info!("Created TCP server on {}", tcp_addr); 110 | 111 | // Start transport layer 112 | transport_layer.serve_listens(sender.clone()).await?; 113 | 114 | // Create test message 115 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 116 | Via: SIP/2.0/TCP 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 117 | From: ;tag=test\r\n\ 118 | To: \r\n\ 119 | Call-ID: test-call-id\r\n\ 120 | CSeq: 1 REGISTER\r\n\ 121 | Contact: \r\n\ 122 | Max-Forwards: 70\r\n\ 123 | Content-Length: 0\r\n\r\n"; 124 | 125 | let sip_message = SipMessage::try_from(test_message)?; 126 | 127 | // Use lookup to find connection 128 | let uri: rsip::Uri = "sip:example.com;transport=tcp".try_into()?; 129 | let (connection, _) = transport_layer.lookup(&uri, sender.clone()).await?; 130 | 131 | // Send message 132 | connection 133 | .send(sip_message.clone(), Some(&tcp_addr)) 134 | .await?; 135 | 136 | // Wait for message 137 | let event = wait_for_event(&mut receiver).await?; 138 | match event { 139 | TransportEvent::Incoming(msg, _, _) => { 140 | assert_eq!(msg.to_string(), sip_message.to_string()); 141 | } 142 | _ => panic!("Expected Incoming event"), 143 | } 144 | 145 | cancel_token.cancel(); 146 | 147 | Ok(()) 148 | } 149 | 150 | /// Test WebSocket functionality 151 | #[cfg(feature = "websocket")] 152 | #[tokio::test] 153 | //#[ignore] // Requires external WebSocket server to run 154 | async fn test_websocket() -> Result<()> { 155 | // Create transport layer 156 | let cancel_token = CancellationToken::new(); 157 | let mut config = TransportConfig::default(); 158 | config.enable_ws = true; 159 | 160 | let transport_layer = TransportLayer::with_config(cancel_token.clone(), config); 161 | 162 | // Create event channel 163 | let (sender, mut receiver) = mpsc::unbounded_channel(); 164 | 165 | // Create WebSocket listener 166 | let ws_addr = transport_layer 167 | .add_ws_listener("127.0.0.1:0".parse()?, sender.clone(), false) 168 | .await?; 169 | info!("Created WebSocket server on {}", ws_addr); 170 | 171 | // Start transport layer 172 | //transport_layer.serve_listens(sender.clone()).await?; 173 | 174 | // Create WebSocket client connection 175 | let uri: rsip::Uri = format!( 176 | "sip:127.0.0.1:{};transport=ws", 177 | ws_addr.addr.port.unwrap().value() 178 | ) 179 | .try_into()?; 180 | let (connection, _) = transport_layer.lookup(&uri, sender.clone()).await?; 181 | 182 | // Send test message 183 | let test_message = "REGISTER sip:example.com SIP/2.0\r\n\ 184 | Via: SIP/2.0/WS 127.0.0.1:5060;branch=z9hG4bK-test\r\n\ 185 | From: ;tag=test\r\n\ 186 | To: \r\n\ 187 | Call-ID: test-call-id\r\n\ 188 | CSeq: 1 REGISTER\r\n\ 189 | Contact: \r\n\ 190 | Max-Forwards: 70\r\n\ 191 | Content-Length: 0\r\n\r\n"; 192 | 193 | let sip_message = SipMessage::try_from(test_message)?; 194 | connection.send(sip_message.clone(), None).await?; 195 | 196 | // Wait for create 197 | { 198 | let event = wait_for_event(&mut receiver).await?; 199 | match event { 200 | TransportEvent::New(_sg) => {} 201 | _ => panic!("Expected New event"), 202 | } 203 | } 204 | 205 | // Wait for message 206 | { 207 | let event = wait_for_event(&mut receiver).await?; 208 | match event { 209 | TransportEvent::Incoming(msg, _, _) => { 210 | assert_eq!(msg.to_string(), sip_message.to_string()); 211 | } 212 | _ => panic!("Expected Incoming event"), 213 | } 214 | } 215 | 216 | cancel_token.cancel(); 217 | 218 | Ok(()) 219 | } 220 | -------------------------------------------------------------------------------- /src/transport/udp.rs: -------------------------------------------------------------------------------- 1 | use super::{connection::TransportSender, SipAddr, SipConnection}; 2 | use crate::{ 3 | transport::{ 4 | connection::{KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE}, 5 | TransportEvent, 6 | }, 7 | Result, 8 | }; 9 | use std::{net::SocketAddr, sync::Arc}; 10 | use tokio::net::UdpSocket; 11 | use tracing::{debug, error, info, instrument}; 12 | pub struct UdpInner { 13 | pub conn: UdpSocket, 14 | pub addr: SipAddr, 15 | } 16 | 17 | #[derive(Clone)] 18 | pub struct UdpConnection { 19 | pub external: Option, 20 | inner: Arc, 21 | } 22 | 23 | impl UdpConnection { 24 | pub async fn attach(inner: UdpInner, external: Option) -> Self { 25 | UdpConnection { 26 | external: external.map(|addr| SipAddr { 27 | r#type: Some(rsip::transport::Transport::Udp), 28 | addr: addr.into(), 29 | }), 30 | inner: Arc::new(inner), 31 | } 32 | } 33 | 34 | pub async fn create_connection( 35 | local: SocketAddr, 36 | external: Option, 37 | ) -> Result { 38 | let conn = UdpSocket::bind(local).await?; 39 | 40 | let addr = SipAddr { 41 | r#type: Some(rsip::transport::Transport::Udp), 42 | addr: conn.local_addr()?.into(), 43 | }; 44 | 45 | let t = UdpConnection { 46 | external: external.map(|addr| SipAddr { 47 | r#type: Some(rsip::transport::Transport::Udp), 48 | addr: addr.into(), 49 | }), 50 | inner: Arc::new(UdpInner { addr, conn }), 51 | }; 52 | info!("created UDP connection: {} external: {:?}", t, external); 53 | Ok(t) 54 | } 55 | 56 | pub async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 57 | let mut buf = vec![0u8; 2048]; 58 | loop { 59 | let (len, addr) = match self.inner.conn.recv_from(&mut buf).await { 60 | Ok((len, addr)) => (len, addr), 61 | Err(e) => { 62 | error!("error receiving UDP packet: {}", e); 63 | continue; 64 | } 65 | }; 66 | 67 | match &buf[..len] { 68 | KEEPALIVE_REQUEST => { 69 | self.inner.conn.send_to(KEEPALIVE_RESPONSE, addr).await.ok(); 70 | continue; 71 | } 72 | KEEPALIVE_RESPONSE => continue, 73 | _ => { 74 | if buf.iter().all(|&b| b.is_ascii_whitespace()) { 75 | continue; 76 | } 77 | } 78 | } 79 | 80 | let undecoded = match std::str::from_utf8(&buf[..len]) { 81 | Ok(s) => s, 82 | Err(e) => { 83 | info!( 84 | "decoding text from: {} error: {} buf: {:?}", 85 | addr, 86 | e, 87 | &buf[..len] 88 | ); 89 | continue; 90 | } 91 | }; 92 | 93 | let msg = match rsip::SipMessage::try_from(undecoded) { 94 | Ok(msg) => msg, 95 | Err(e) => { 96 | info!( 97 | "error parsing SIP message from: {} error: {} buf: {}", 98 | addr, e, undecoded 99 | ); 100 | continue; 101 | } 102 | }; 103 | 104 | let msg = match SipConnection::update_msg_received( 105 | msg, 106 | addr, 107 | rsip::transport::Transport::Udp, 108 | ) { 109 | Ok(msg) => msg, 110 | Err(e) => { 111 | info!( 112 | "error updating SIP via from: {} error: {:?} buf: {}", 113 | addr, e, undecoded 114 | ); 115 | continue; 116 | } 117 | }; 118 | 119 | debug!( 120 | "received {} {} -> {} {}", 121 | len, 122 | addr, 123 | self.get_addr(), 124 | undecoded 125 | ); 126 | 127 | sender.send(TransportEvent::Incoming( 128 | msg, 129 | SipConnection::Udp(self.clone()), 130 | SipAddr { 131 | r#type: Some(rsip::transport::Transport::Udp), 132 | addr: addr.into(), 133 | }, 134 | ))?; 135 | } 136 | } 137 | 138 | #[instrument(skip(self, msg), fields(addr = %self.get_addr()))] 139 | pub async fn send( 140 | &self, 141 | msg: rsip::SipMessage, 142 | destination: Option<&SipAddr>, 143 | ) -> crate::Result<()> { 144 | let destination = match destination { 145 | Some(addr) => addr.get_socketaddr(), 146 | None => SipConnection::get_destination(&msg), 147 | }?; 148 | let buf = msg.to_string(); 149 | debug!("send {} -> {} {}", buf.len(), destination, buf); 150 | 151 | self.inner 152 | .conn 153 | .send_to(buf.as_bytes(), destination) 154 | .await 155 | .map_err(|e| { 156 | crate::Error::TransportLayerError(e.to_string(), self.get_addr().to_owned()) 157 | }) 158 | .map(|_| ()) 159 | } 160 | 161 | #[instrument(skip(self, buf), fields(addr = %self.get_addr()))] 162 | pub async fn send_raw(&self, buf: &[u8], destination: &SipAddr) -> Result<()> { 163 | //trace!("send_raw {} -> {}", buf.len(), target); 164 | self.inner 165 | .conn 166 | .send_to(buf, destination.get_socketaddr()?) 167 | .await 168 | .map_err(|e| { 169 | crate::Error::TransportLayerError(e.to_string(), self.get_addr().to_owned()) 170 | }) 171 | .map(|_| ()) 172 | } 173 | 174 | #[instrument(skip(self, buf), fields(addr = %self.get_addr()))] 175 | pub async fn recv_raw(&self, buf: &mut [u8]) -> Result<(usize, SipAddr)> { 176 | let (len, addr) = self.inner.conn.recv_from(buf).await?; 177 | // trace!("received {} -> {}", len, addr); 178 | Ok(( 179 | len, 180 | SipAddr { 181 | r#type: Some(rsip::transport::Transport::Udp), 182 | addr: addr.into(), 183 | }, 184 | )) 185 | } 186 | 187 | pub fn get_addr(&self) -> &SipAddr { 188 | if let Some(external) = &self.external { 189 | external 190 | } else { 191 | &self.inner.addr 192 | } 193 | } 194 | } 195 | 196 | impl std::fmt::Display for UdpConnection { 197 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 198 | match self.inner.conn.local_addr() { 199 | Ok(addr) => write!(f, "{}", addr), 200 | Err(_) => write!(f, "*:*"), 201 | } 202 | } 203 | } 204 | 205 | impl std::fmt::Debug for UdpConnection { 206 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 207 | write!(f, "{}", self.inner.addr) 208 | } 209 | } 210 | 211 | impl Drop for UdpInner { 212 | fn drop(&mut self) { 213 | info!("dropping UDP transport: {}", self.addr); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/transport/websocket.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | connection::{TransportSender, KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE}, 4 | sip_addr::SipAddr, 5 | stream::StreamConnection, 6 | SipConnection, TransportEvent, 7 | }, 8 | Result, 9 | }; 10 | use futures_util::{SinkExt, StreamExt}; 11 | use rsip::SipMessage; 12 | use std::{fmt, sync::Arc}; 13 | use tokio::{net::TcpListener, sync::Mutex}; 14 | use tokio_tungstenite::{ 15 | connect_async, tungstenite::protocol::Message, MaybeTlsStream, WebSocketStream, 16 | }; 17 | use tracing::{debug, error, info, warn}; 18 | 19 | // Define a type alias for the WebSocket sink to make the code more readable 20 | type WsSink = futures_util::stream::SplitSink< 21 | WebSocketStream>, 22 | Message, 23 | >; 24 | type WsRead = 25 | futures_util::stream::SplitStream>>; 26 | 27 | pub struct WebSocketInner { 28 | pub local_addr: SipAddr, 29 | pub remote_addr: Option, 30 | pub ws_sink: Arc>, 31 | pub ws_read: Arc>, 32 | } 33 | 34 | #[derive(Clone)] 35 | pub struct WebSocketConnection { 36 | pub inner: Arc, 37 | } 38 | 39 | impl WebSocketConnection { 40 | pub async fn connect(remote: &SipAddr) -> Result { 41 | let scheme = match remote.r#type { 42 | Some(rsip::transport::Transport::Wss) => "wss", 43 | _ => "ws", 44 | }; 45 | 46 | let host = match &remote.addr.host { 47 | rsip::host_with_port::Host::Domain(domain) => domain.to_string(), 48 | rsip::host_with_port::Host::IpAddr(ip) => ip.to_string(), 49 | }; 50 | 51 | let port = remote.addr.port.as_ref().map_or(5060, |p| *p.value()); 52 | let url = format!("{}://{}:{}/sip", scheme, host, port); 53 | 54 | let (ws_stream, _) = connect_async(&url).await?; 55 | let (ws_sink, _ws_stream) = ws_stream.split(); 56 | 57 | let local_addr = SipAddr { 58 | r#type: Some(if scheme == "wss" { 59 | rsip::transport::Transport::Wss 60 | } else { 61 | rsip::transport::Transport::Ws 62 | }), 63 | addr: remote.addr.clone(), 64 | }; 65 | 66 | let connection = WebSocketConnection { 67 | inner: Arc::new(WebSocketInner { 68 | local_addr, 69 | remote_addr: Some(remote.clone()), 70 | ws_sink: Arc::new(Mutex::new(ws_sink)), 71 | ws_read: Arc::new(Mutex::new(_ws_stream)), 72 | }), 73 | }; 74 | 75 | info!( 76 | "Created WebSocket client connection: {} -> {}", 77 | connection.get_addr(), 78 | remote 79 | ); 80 | 81 | Ok(connection) 82 | } 83 | 84 | pub async fn serve_listener( 85 | tcp_listener: TcpListener, 86 | local_addr: SipAddr, 87 | sender: TransportSender, 88 | is_secure: bool, 89 | ) -> Result<()> { 90 | let transport_type = if is_secure { 91 | rsip::transport::Transport::Wss 92 | } else { 93 | rsip::transport::Transport::Ws 94 | }; 95 | 96 | info!("Starting WebSocket listener on {}", local_addr); 97 | 98 | loop { 99 | match tcp_listener.accept().await { 100 | Ok((stream, remote_addr)) => { 101 | debug!("New WebSocket connection from {}", remote_addr); 102 | 103 | let remote_sip_addr = SipAddr { 104 | r#type: Some(transport_type.clone()), 105 | addr: remote_addr.into(), 106 | }; 107 | 108 | let local_addr_clone = local_addr.clone(); 109 | let sender_clone = sender.clone(); 110 | 111 | tokio::spawn(async move { 112 | // Wrap the TCP stream in MaybeTlsStream 113 | let maybe_tls_stream = MaybeTlsStream::Plain(stream); 114 | 115 | // Accept the WebSocket connection 116 | let ws_stream = 117 | match tokio_tungstenite::accept_async(maybe_tls_stream).await { 118 | Ok(ws) => ws, 119 | Err(e) => { 120 | error!("Error upgrading to WebSocket: {}", e); 121 | return; 122 | } 123 | }; 124 | 125 | // Split the WebSocket stream 126 | let (ws_sink, ws_stream) = ws_stream.split(); 127 | 128 | // Create a new WebSocket connection 129 | let connection = WebSocketConnection { 130 | inner: Arc::new(WebSocketInner { 131 | local_addr: local_addr_clone.clone(), 132 | remote_addr: Some(remote_sip_addr.clone()), 133 | ws_sink: Arc::new(Mutex::new(ws_sink)), 134 | ws_read: Arc::new(Mutex::new(ws_stream)), 135 | }), 136 | }; 137 | let sip_connection = SipConnection::WebSocket(connection.clone()); 138 | 139 | if let Err(e) = 140 | sender_clone.send(TransportEvent::New(sip_connection.clone())) 141 | { 142 | error!("Error sending new connection event: {:?}", e); 143 | return; 144 | } 145 | match connection.serve_loop(sender_clone.clone()).await { 146 | Ok(_) => {} 147 | Err(e) => { 148 | error!("Error serving WebSocket connection: {:?}", e); 149 | } 150 | } 151 | }); 152 | } 153 | Err(e) => { 154 | error!("Error accepting TCP connection for WebSocket: {}", e); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | #[async_trait::async_trait] 162 | impl StreamConnection for WebSocketConnection { 163 | fn get_addr(&self) -> &SipAddr { 164 | &self.inner.local_addr 165 | } 166 | 167 | async fn send_message(&self, msg: SipMessage) -> Result<()> { 168 | let data = msg.to_string(); 169 | let mut sink = self.inner.ws_sink.lock().await; 170 | info!("WebSocket send:{}", data); 171 | sink.send(Message::Text(data.into())).await?; 172 | Ok(()) 173 | } 174 | 175 | async fn send_raw(&self, data: &[u8]) -> Result<()> { 176 | let mut sink = self.inner.ws_sink.lock().await; 177 | sink.send(Message::Binary(data.to_vec().into())).await?; 178 | Ok(()) 179 | } 180 | 181 | async fn serve_loop(&self, sender: TransportSender) -> Result<()> { 182 | let sip_connection = SipConnection::WebSocket(self.clone()); 183 | let remote_addr = self.inner.remote_addr.clone().unwrap().clone(); 184 | let mut ws_read = self.inner.ws_read.lock().await; 185 | while let Some(msg) = ws_read.next().await { 186 | match msg { 187 | Ok(Message::Text(text)) => match SipMessage::try_from(text.as_str()) { 188 | Ok(sip_msg) => { 189 | if let Err(e) = sender.send(TransportEvent::Incoming( 190 | sip_msg, 191 | sip_connection.clone(), 192 | remote_addr.clone(), 193 | )) { 194 | error!("Error sending incoming message: {:?}", e); 195 | break; 196 | } 197 | } 198 | Err(e) => { 199 | warn!("Error parsing SIP message: {}", e); 200 | } 201 | }, 202 | Ok(Message::Binary(bin)) => { 203 | if bin == *KEEPALIVE_REQUEST { 204 | if let Err(e) = self.send_raw(KEEPALIVE_RESPONSE).await { 205 | error!("Error sending keepalive response: {:?}", e); 206 | } 207 | continue; 208 | } 209 | 210 | match std::str::from_utf8(&bin) { 211 | Ok(text) => match SipMessage::try_from(text) { 212 | Ok(sip_msg) => { 213 | if let Err(e) = sender.send(TransportEvent::Incoming( 214 | sip_msg, 215 | sip_connection.clone(), 216 | remote_addr.clone(), 217 | )) { 218 | error!("Error sending incoming message: {:?}", e); 219 | break; 220 | } 221 | } 222 | Err(e) => { 223 | warn!("Error parsing SIP message: {}", e); 224 | } 225 | }, 226 | Err(e) => { 227 | warn!("Error decoding binary message: {}", e); 228 | } 229 | } 230 | } 231 | Ok(Message::Ping(data)) => { 232 | let mut sink = self.inner.ws_sink.lock().await; 233 | if let Err(e) = sink.send(Message::Pong(data)).await { 234 | error!("Error sending pong: {}", e); 235 | break; 236 | } 237 | } 238 | Ok(Message::Close(_)) => { 239 | debug!("WebSocket connection closed by peer"); 240 | break; 241 | } 242 | Err(e) => { 243 | error!("WebSocket error: {}", e); 244 | break; 245 | } 246 | _ => {} 247 | } 248 | } 249 | 250 | if let Err(e) = sender.send(TransportEvent::Closed(sip_connection.clone())) { 251 | error!("Error sending connection closed event: {:?}", e); 252 | } 253 | Ok(()) 254 | } 255 | 256 | async fn close(&self) -> Result<()> { 257 | let mut sink = self.inner.ws_sink.lock().await; 258 | sink.send(Message::Close(None)).await?; 259 | Ok(()) 260 | } 261 | } 262 | 263 | impl fmt::Display for WebSocketConnection { 264 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 265 | let transport = match self.inner.local_addr.r#type { 266 | Some(rsip::transport::Transport::Wss) => "WSS", 267 | _ => "WS", 268 | }; 269 | write!(f, "{} {}", transport, self.inner.local_addr) 270 | } 271 | } 272 | 273 | impl fmt::Debug for WebSocketConnection { 274 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 275 | fmt::Display::fmt(self, f) 276 | } 277 | } 278 | --------------------------------------------------------------------------------