aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/server.rs76
-rw-r--r--src/encoding/headermap.rs3
-rw-r--r--src/encoding/headers.rs36
-rw-r--r--src/transaction/auth.rs51
-rw-r--r--src/transaction/mod.rs23
-rw-r--r--src/transport/udp.rs2
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<()> {