diff options
author | metamuffin <metamuffin@disroot.org> | 2024-07-06 15:43:45 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-07-06 15:43:45 +0200 |
commit | 7177367ae41a5e2d6ed401f60ee1455812dd8ffb (patch) | |
tree | 75c89835d03e1a1ccd4e8c930c95310f757b2b3a | |
parent | 5dd0fafce20ed37fdc97dc96539391ebdebffaff (diff) | |
download | sip-rs-7177367ae41a5e2d6ed401f60ee1455812dd8ffb.tar sip-rs-7177367ae41a5e2d6ed401f60ee1455812dd8ffb.tar.bz2 sip-rs-7177367ae41a5e2d6ed401f60ee1455812dd8ffb.tar.zst |
phone is ringing
-rw-r--r-- | examples/server.rs | 76 | ||||
-rw-r--r-- | src/encoding/headermap.rs | 3 | ||||
-rw-r--r-- | src/encoding/headers.rs | 36 | ||||
-rw-r--r-- | src/transaction/auth.rs | 51 | ||||
-rw-r--r-- | src/transaction/mod.rs | 23 | ||||
-rw-r--r-- | src/transport/udp.rs | 2 |
6 files changed, 177 insertions, 14 deletions
diff --git a/examples/server.rs b/examples/server.rs index 5e294b0..60a2b33 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,20 +1,78 @@ +use std::net::SocketAddr; + use anyhow::Result; -use log::info; -use sip::{transaction::TransactionUser, transport::tcp::TcpTransport}; -use tokio::net::TcpListener; +use log::{info, warn}; +use sip::{ + encoding::{ + headermap::HeaderMap, + headers::{Contact, ContentLength, From, To, UserAgent}, + method::Method, + response::Response, + status::Status, + }, + transaction::TransactionUser, + transport::tcp::TcpTransport, +}; +use tokio::{ + net::{TcpListener, TcpStream}, + spawn, +}; #[tokio::main] async fn main() -> Result<()> { + env_logger::init_from_env("LOG"); let listener = TcpListener::bind("0.0.0.0:5060").await?; - + info!("tcp listener bound to {}", listener.local_addr().unwrap()); loop { let (stream, addr) = listener.accept().await?; - info!("connection from {addr}"); - let transport = TcpTransport::new(stream).await?; - let tu = TransactionUser::new(transport); + info!("connect {addr}"); - let req = tu.process_incoming().await?; + spawn(async move { + if let Err(e) = handle_client(stream, addr).await { + warn!("client error: {e}") + } + info!("disconnect {addr}") + }); } +} + +async fn handle_client(stream: TcpStream, addr: SocketAddr) -> Result<()> { + let transport = TcpTransport::new(stream).await?; + let tu = TransactionUser::new(transport); + loop { + let req = tu.process_incoming().await?; + + if req.method == Method::Register { + let from: From = req.headers.get_res()?; + let to: To = req.headers.get_res()?; - Ok(()) + tu.respond( + &req, + Response { + status: Status::Ok, + headers: HeaderMap::new() + .add(Contact(format!("<sip:{username}@{addr}>;expires=600"))) + .add(From(to.0)) + .add(To(from.0)) + .add(UserAgent("siptest v0.1.0".to_string())) + .add(ContentLength(0)), + }, + ) + .await?; + } + } } + +/* +[2024-07-05T22:22:40Z DEBUG sip::transport::udp] SIP/2.0 200 OK + Via: SIP/2.0/UDP 198.18.1.135:52125;branch=Uz7r7ysvrS91q9j9;rport=52125 + Contact: <sip:metatest-0x5e11f89f7900@198.18.1.135:40708>;expires=379;+sip.instance="<urn:uuid:9b2421ef-43ed-3b3b-9181-607ba5c13fb9>" + Contact: <sip:metatest-0x5e11f89f7900@198.18.1.135:47412>;expires=411 + Contact: <sip:metatest-0x5e11f89f7900@198.18.1.135:52125>;expires=600 + To: <sip:metatest@198.18.0.220>;tag=81756f4e + From: <sip:metatest@198.18.0.220>;tag=-AK4OkphTFVZv50h + Call-ID: U3Fyb6vT1BhWxiKG + CSeq: 1 REGISTER + User-Agent: AGFEO SIP V3.00.15 n (MAC=00094070069C) + Content-Length: 0 +*/ diff --git a/src/encoding/headermap.rs b/src/encoding/headermap.rs index 5d1fa0a..01e1962 100644 --- a/src/encoding/headermap.rs +++ b/src/encoding/headermap.rs @@ -25,6 +25,9 @@ impl HeaderMap { pub fn get<H: Header>(&self) -> Option<Result<H>> { self.get_raw(H::NAME).map(H::from_str) } + pub fn get_res<H: Header>(&self) -> Result<H> { + self.get().ok_or(anyhow!("{} header missing", H::NAME))? + } pub fn insert_raw(&mut self, key: String, value: String) { self.0.push((key, value)) } diff --git a/src/encoding/headers.rs b/src/encoding/headers.rs index 9e785f0..afcfef1 100644 --- a/src/encoding/headers.rs +++ b/src/encoding/headers.rs @@ -1,4 +1,4 @@ -use super::{headermap::HeaderMap, method::Method}; +use super::{headermap::HeaderMap, method::Method, uri::Uri}; use anyhow::{anyhow, bail, Result}; use std::{fmt::Display, str::FromStr}; @@ -31,7 +31,6 @@ header!("Content-Length", struct ContentLength(usize)); header!("Content-Type", struct ContentType(String)); header!("Call-ID", struct CallID(String)); header!("Via", struct Via(String)); -header!("Contact", struct Contact(String)); header!("Max-Forwards", struct MaxForwards(usize)); header!("From", struct From(String)); header!("To", struct To(String)); @@ -100,7 +99,7 @@ impl FromStr for WWWAuthenticate { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Authorization { pub username: String, pub realm: String, @@ -140,3 +139,34 @@ pub fn unquote(v: &str) -> Result<String> { .ok_or(anyhow!("end quote missing"))? .to_string()) } + +#[derive(Debug)] +pub struct Contact { + pub display_name: Option<String>, + pub uri: Uri, + pub params: String, +} + +impl Header for Contact { + const NAME: &'static str = "Contact"; +} +impl Display for Contact { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + display_name, + uri, + params, + } = self; + if let Some(display_name) = display_name { + write!(f, "{display_name} <{uri}>{params}") + } else { + write!(f, "<{uri}>{params}") + } + } +} +impl FromStr for Contact { + type Err = anyhow::Error; + fn from_str(_s: &str) -> Result<Self, Self::Err> { + todo!() + } +} diff --git a/src/transaction/auth.rs b/src/transaction/auth.rs new file mode 100644 index 0000000..f64bb4b --- /dev/null +++ b/src/transaction/auth.rs @@ -0,0 +1,51 @@ +use crate::encoding::{ + headers::{Authorization, WWWAuthenticate}, + method::Method, + request::Request, + response::Response, + uri::Uri, +}; +use anyhow::Result; + +impl Authorization { + pub fn construct( + request: &Request, + failed_response: &Response, + username: &str, + password: &str, + ) -> Result<Authorization> { + let challenge = failed_response.headers.get::<WWWAuthenticate>().unwrap()?; + + Ok(Authorization { + response: response_digest( + username.to_string(), + challenge.realm.clone(), + password.to_string(), + request.method, + challenge.nonce.clone(), + request.uri.clone(), + ), + nonce: challenge.nonce, + realm: challenge.realm, + uri: request.uri.content.clone(), + username: username.to_string(), + }) + } +} + +fn response_digest( + username: String, + realm: String, + password: String, + method: Method, + nonce: String, + uri: Uri, +) -> String { + let h = |s: String| hex::encode(md5::compute(s.as_bytes()).0); + let kd = |secret, data| h(format!("{secret}:{data}")); + + let a1 = format!("{username}:{realm}:{password}"); + let a2 = format!("{method}:{uri}"); + let response_digest = kd(h(a1), format!("{nonce}:{}", h(a2))); + return response_digest; +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 3be9544..601b134 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -1,5 +1,12 @@ +pub mod auth; + use crate::{ - encoding::{headers::CSeq, request::Request, response::Response, Message}, + encoding::{ + headers::{CSeq, CallID}, + request::Request, + response::Response, + Message, + }, transport::Transport, }; use anyhow::{anyhow, Result}; @@ -48,6 +55,20 @@ impl<T: Transport> TransactionUser<T> { } } } + pub async fn respond(&self, req: &Request, mut resp: Response) -> Result<()> { + resp.headers.insert( + req.headers + .get::<CSeq>() + .ok_or(anyhow!("cseq is mandatory"))??, + ); + resp.headers.insert( + req.headers + .get::<CallID>() + .ok_or(anyhow!("call-id is mandatory"))??, + ); + self.transport.send(Message::Response(resp)).await?; + Ok(()) + } pub async fn transact(&self, mut request: Request) -> Result<mpsc::Receiver<Response>> { let seq = self.sequence.fetch_add(1, Ordering::Relaxed); diff --git a/src/transport/udp.rs b/src/transport/udp.rs index 391e2b3..78ca4f2 100644 --- a/src/transport/udp.rs +++ b/src/transport/udp.rs @@ -19,7 +19,7 @@ impl Transport for UdpTransport { let mut buf = [0; 1024]; let size = self.sock.recv(&mut buf).await?; let message = String::from_utf8(buf[..size].to_vec())?; - debug!("{message}"); + debug!("<- {message}"); Message::from_str(message.trim_end()) } async fn send(&self, request: Message) -> Result<()> { |