use crate::headers::{CSeq, Via};
use crate::message::{Headers, Method, Request, Response};
use crate::name::HeaderName;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TransactionKey {
Rfc3261 {
branch: Vec<u8>,
sent_by: Vec<u8>,
method: Vec<u8>,
},
Legacy {
request_uri: Vec<u8>,
top_via: Vec<u8>,
from_tag: Vec<u8>,
to_tag: Vec<u8>,
call_id: Vec<u8>,
cseq: u32,
method: Vec<u8>,
},
}
fn match_method(method: &Method) -> Vec<u8> {
match method {
Method::Ack => Method::Invite.as_bytes().to_vec(),
other => other.as_bytes().to_vec(),
}
}
fn sent_by(via: &Via) -> Vec<u8> {
let mut out = via.host.to_bytes().to_ascii_lowercase();
if let Some(port) = via.port {
out.push(b':');
out.extend_from_slice(port.to_string().as_bytes());
}
out
}
fn top_via(headers: &Headers) -> Vec<u8> {
headers
.value(&HeaderName::Via)
.map(|v| v.to_vec())
.unwrap_or_default()
}
fn from_tag(headers: &Headers) -> Vec<u8> {
headers
.typed::<crate::headers::From>()
.and_then(Result::ok)
.and_then(|f| f.tag().map(<[u8]>::to_vec))
.unwrap_or_default()
}
fn to_tag(headers: &Headers) -> Vec<u8> {
headers
.typed::<crate::headers::To>()
.and_then(Result::ok)
.and_then(|t| t.tag().map(<[u8]>::to_vec))
.unwrap_or_default()
}
fn call_id(headers: &Headers) -> Vec<u8> {
headers
.value(&HeaderName::CallId)
.map(|v| v.to_vec())
.unwrap_or_default()
}
impl TransactionKey {
#[must_use]
pub fn from_request(request: &Request) -> Option<Self> {
let via = request.headers.typed::<Via>()?.ok()?;
let method = match_method(&request.method);
if via.has_rfc3261_branch() {
return Some(Self::Rfc3261 {
branch: via.branch()?.to_vec(),
sent_by: sent_by(&via),
method,
});
}
let cseq = request.headers.typed::<CSeq>()?.ok()?;
Some(Self::Legacy {
request_uri: request.uri.to_bytes().to_vec(),
top_via: top_via(&request.headers),
from_tag: from_tag(&request.headers),
to_tag: to_tag(&request.headers),
call_id: call_id(&request.headers),
cseq: cseq.sequence,
method,
})
}
#[must_use]
pub fn from_sent_request(request: &Request) -> Option<Self> {
let via = request.headers.typed::<Via>()?.ok()?;
let method = match_method(&request.method);
if via.has_rfc3261_branch() {
return Some(Self::Rfc3261 {
branch: via.branch()?.to_vec(),
sent_by: sent_by(&via),
method,
});
}
let cseq = request.headers.typed::<CSeq>()?.ok()?;
Some(Self::legacy_client(&request.headers, cseq.sequence, method))
}
fn legacy_client(headers: &Headers, cseq: u32, method: Vec<u8>) -> Self {
Self::Legacy {
request_uri: Vec::new(),
top_via: top_via(headers),
from_tag: from_tag(headers),
to_tag: Vec::new(),
call_id: call_id(headers),
cseq,
method,
}
}
#[must_use]
pub fn from_response(response: &Response) -> Option<Self> {
let via = response.headers.typed::<Via>()?.ok()?;
let cseq = response.headers.typed::<CSeq>()?.ok()?;
let method = match_method(&cseq.method);
if via.has_rfc3261_branch() {
return Some(Self::Rfc3261 {
branch: via.branch()?.to_vec(),
sent_by: sent_by(&via),
method,
});
}
Some(Self::legacy_client(
&response.headers,
cseq.sequence,
method,
))
}
#[must_use]
pub fn for_cancelled_invite(request: &Request) -> Option<Self> {
if request.method != Method::Cancel {
return None;
}
let key = Self::from_request(request)?;
Some(match key {
Self::Rfc3261 {
branch, sent_by, ..
} => Self::Rfc3261 {
branch,
sent_by,
method: Method::Invite.as_bytes().to_vec(),
},
Self::Legacy {
request_uri,
top_via,
from_tag,
to_tag,
call_id,
cseq,
..
} => Self::Legacy {
request_uri,
top_via,
from_tag,
to_tag,
call_id,
cseq,
method: Method::Invite.as_bytes().to_vec(),
},
})
}
#[must_use]
pub fn is_legacy(&self) -> bool {
matches!(self, Self::Legacy { .. })
}
}