use std::fmt::{self, Write as _};
use crate::call_id::SipCallId;
use crate::check::checked_token;
use crate::diagnostic::{Field, ParseWarning, Parsed, WarningCode};
use crate::error::{FaultCode, ParseError};
use crate::params::HeaderParams;
pub(crate) mod sealed {
pub trait Sealed {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum DialogFraming {
#[default]
Header,
UriHeader,
}
#[cfg(feature = "serde")]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(
remote = "DialogFraming",
rename = "DialogFraming",
rename_all = "kebab-case"
)]
enum DialogFramingDef {
Header,
UriHeader,
}
#[cfg(feature = "serde")]
impl crate::serde_parts::Expected for DialogFraming {
const TEXT: &'static str = "\"header\" or \"uri-header\"";
}
#[cfg(feature = "serde")]
impl serde::Serialize for DialogFraming {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
DialogFramingDef::serialize(self, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for DialogFraming {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
crate::serde_parts::leaf(
deserializer,
"dialog framing",
<DialogFraming as crate::serde_parts::Expected>::TEXT,
DialogFramingDef::deserialize,
)
}
}
pub trait DialogKind: sealed::Sealed {
const FIRST_TAG: &'static str;
const SECOND_TAG: &'static str;
const EARLY_ONLY: bool;
const RESERVED: &'static [&'static str];
}
params_located!(DialogId);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct DialogId {
call_id: String,
first_tag: String,
second_tag: String,
early_only: bool,
params: HeaderParams,
framing: DialogFraming,
}
impl DialogId {
pub(crate) fn new(
call_id: &str,
first_tag: &str,
second_tag: &str,
) -> Result<Self, ParseError> {
Ok(DialogId {
call_id: SipCallId::new(call_id)?.into(),
first_tag: checked_token(Field::Tag, first_tag)?,
second_tag: checked_token(Field::Tag, second_tag)?,
early_only: false,
params: HeaderParams::default(),
framing: DialogFraming::Header,
})
}
pub(crate) fn from_fields(fields: DialogFields, framing: DialogFraming) -> Self {
DialogId {
call_id: fields.call_id,
first_tag: fields.first_tag,
second_tag: fields.second_tag,
early_only: fields.early_only,
params: fields.params,
framing,
}
}
pub(crate) fn call_id(&self) -> &str {
&self.call_id
}
pub(crate) fn set_call_id(&mut self, call_id: &str) -> Result<(), ParseError> {
self.call_id = SipCallId::new(call_id)?.into();
self.params
.clear_spans();
Ok(())
}
pub(crate) fn host(&self) -> Option<&str> {
self.call_id
.split_once('@')
.map(|(_, host)| host)
}
pub(crate) fn first_tag(&self) -> &str {
&self.first_tag
}
pub(crate) fn second_tag(&self) -> &str {
&self.second_tag
}
pub(crate) fn early_only(&self) -> bool {
self.early_only
}
pub(crate) fn set_early_only(&mut self, early_only: bool) {
self.early_only = early_only;
self.params
.clear_spans();
}
pub(crate) fn params(&self) -> &HeaderParams {
&self.params
}
pub(crate) fn params_mut(&mut self) -> &mut HeaderParams {
&mut self.params
}
pub(crate) fn set_first_tag(&mut self, tag: &str) -> Result<(), ParseError> {
self.first_tag = checked_token(Field::Tag, tag)?;
self.params
.clear_spans();
Ok(())
}
pub(crate) fn set_second_tag(&mut self, tag: &str) -> Result<(), ParseError> {
self.second_tag = checked_token(Field::Tag, tag)?;
self.params
.clear_spans();
Ok(())
}
pub(crate) fn framing(&self) -> DialogFraming {
self.framing
}
pub(crate) fn set_framing(&mut self, framing: DialogFraming) {
self.framing = framing;
self.params
.clear_spans();
}
fn wire_form<K: DialogKind>(&self) -> Result<String, fmt::Error> {
let mut s = format!(
"{};{}={};{}={}",
self.call_id,
K::FIRST_TAG,
self.first_tag,
K::SECOND_TAG,
self.second_tag
);
if self.early_only {
s.push_str(";early-only");
}
write!(s, "{}", self.params)?;
Ok(s)
}
pub(crate) fn write<K: DialogKind>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let wire = self.wire_form::<K>()?;
match self.framing {
DialogFraming::Header => f.write_str(&wire),
DialogFraming::UriHeader => f.write_str(&sip_uri::encoding::encode_header(&wire)),
}
}
}
macro_rules! dialog_id_type {
($Type:ident, $first:ident, $set_first:ident => $first_name:literal, $second:ident, $set_second:ident => $second_name:literal, early_only: $early:literal) => {
impl $crate::dialog_id::sealed::Sealed for $Type {}
impl $crate::equivalence::sealed::Sealed for $Type {}
impl $crate::HeaderEquivalence for $Type {
fn equivalent(&self, other: &Self) -> bool {
$crate::equivalence::dialogs_equivalent(&self.0, &other.0)
}
}
impl $crate::dialog_id::DialogKind for $Type {
const FIRST_TAG: &'static str = $first_name;
const SECOND_TAG: &'static str = $second_name;
const EARLY_ONLY: bool = $early;
const RESERVED: &'static [&'static str] = if $early {
&[$first_name, $second_name, "early-only"]
} else {
&[$first_name, $second_name]
};
}
impl $Type {
#[doc = concat!("A header-framed value from its Call-ID, `", $first_name, "` and `", $second_name, "`.")]
pub fn new(
call_id: impl AsRef<str>,
$first: impl AsRef<str>,
$second: impl AsRef<str>,
) -> Result<Self, $crate::error::ParseError> {
$crate::dialog_id::DialogId::new(call_id.as_ref(), $first.as_ref(), $second.as_ref())
.map(Self)
}
pub fn params_mut(&mut self) -> $crate::params::ParamsMut<'_> {
$crate::params::ParamsMut::new(
self.0
.params_mut(),
$crate::params::ParamRule {
reserved: <Self as $crate::dialog_id::DialogKind>::RESERVED,
check: $crate::params::any_value,
},
$crate::params::Owner::Plain,
)
}
#[doc = concat!("Returns this value with a different `", $first_name, "`, a `token`.")]
pub fn $set_first(
mut self,
tag: impl AsRef<str>,
) -> Result<Self, $crate::error::ParseError> {
self.0
.set_first_tag(tag.as_ref())?;
Ok(self)
}
#[doc = concat!("Returns this value with a different `", $second_name, "`, a `token`.")]
pub fn $set_second(
mut self,
tag: impl AsRef<str>,
) -> Result<Self, $crate::error::ParseError> {
self.0
.set_second_tag(tag.as_ref())?;
Ok(self)
}
#[doc = concat!("use sip_header::{HeaderParse, ", stringify!($Type), "};")]
#[doc = concat!("let v = ", stringify!($Type), "::parse(\"abc@203.0.113.5;", $first_name, "=t1;", $second_name, "=f1\")?")]
#[doc = concat!("assert_eq!(v.to_string(), \"abc@example.com;", $first_name, "=t1;", $second_name, "=f1\");")]
pub fn with_call_id(
mut self,
call_id: impl AsRef<str>,
) -> Result<Self, $crate::error::ParseError> {
self.0
.set_call_id(call_id.as_ref())?;
Ok(self)
}
pub fn with_framing(mut self, framing: $crate::dialog_id::DialogFraming) -> Self {
self.0
.set_framing(framing);
self
}
pub fn framing(&self) -> $crate::dialog_id::DialogFraming {
self.0
.framing()
}
pub fn call_id(&self) -> &str {
self.0
.call_id()
}
pub fn host(&self) -> Option<&str> {
self.0
.host()
}
#[doc = concat!("The mandatory `", $first_name, "` value, case as sent.")]
pub fn $first(&self) -> &str {
self.0
.first_tag()
}
#[doc = concat!("The mandatory `", $second_name, "` value, case as sent.")]
pub fn $second(&self) -> &str {
self.0
.second_tag()
}
pub fn params(&self) -> &$crate::params::HeaderParams {
self.0
.params()
}
pub fn param(&self, key: &str) -> Option<Option<&str>> {
self.0
.params()
.get(key)
}
}
header_params!(@builders $Type);
impl std::fmt::Display for $Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0
.write::<Self>(f)
}
}
};
}
params_located!(DialogFields);
pub(crate) struct DialogFields {
pub(crate) call_id: String,
pub(crate) first_tag: String,
pub(crate) second_tag: String,
pub(crate) early_only: bool,
pub(crate) params: HeaderParams,
}
pub(crate) trait DialogBuild: DialogKind + Sized {
fn build(fields: DialogFields, framing: DialogFraming) -> Self;
#[cfg(feature = "serde")]
fn dialog(&self) -> &DialogId;
}
pub(crate) fn parse<T: DialogBuild>(raw: &str) -> Result<Parsed<T>, ParseError> {
crate::scrub::parse_scrubbed_located(raw, parse_framed::<T>)
.map(|p| p.map(|f| T::build(f, DialogFraming::Header)))
}
pub(crate) fn parse_uri_header<T: DialogBuild>(raw: &str) -> Result<Parsed<T>, ParseError> {
crate::scrub::parse_uri_header(raw, parse_framed::<T>)
.map(|p| p.map(|f| T::build(f, DialogFraming::UriHeader)))
}
#[cfg(feature = "serde")]
pub(crate) fn reads_back<T>(value: T) -> Result<T, ParseError>
where
T: DialogBuild + std::fmt::Display + PartialEq,
{
let framing = value
.dialog()
.framing;
crate::check::reads_back(value, |wire| {
match framing {
DialogFraming::Header => parse::<T>(wire),
DialogFraming::UriHeader => parse_uri_header::<T>(wire),
}
.map(|p| p.value)
})
}
fn parse_framed<K: DialogKind>(raw: &str) -> Result<Parsed<DialogFields>, ParseError> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return Err(ParseError::empty(Field::Value));
}
let (call_id, rest) = crate::split_at_params(trimmed);
let call_id = call_id.trim();
if call_id.is_empty() {
return Err(ParseError::malformed(
Field::CallId,
FaultCode::Missing,
Some(crate::offset_in(raw, trimmed)),
));
}
let mut warnings = Vec::new();
crate::call_id::report_breach(call_id, crate::offset_in(raw, call_id), &mut warnings);
let mut first_tag: Option<String> = None;
let mut second_tag: Option<String> = None;
let mut early_only = false;
let mut params = HeaderParams::default();
for param in crate::parse_params(rest) {
let key = param
.name()
.to_ascii_lowercase();
let Some(value) = param.value else {
let flag = K::EARLY_ONLY && key == "early-only";
if flag && !early_only {
param.report_name(raw, &mut warnings);
early_only = true;
} else {
if flag
&& params
.get(&key)
.is_none()
{
warnings.push(
ParseWarning::new(Field::Param, WarningCode::DuplicateParam)
.at(crate::offset_in(raw, param.key)),
);
}
params.push_raw(raw, ¶m, &mut warnings);
}
continue;
};
let slot = if key == K::FIRST_TAG {
&mut first_tag
} else if key == K::SECOND_TAG {
&mut second_tag
} else {
params.push_raw(raw, ¶m, &mut warnings);
continue;
};
param.report_name(raw, &mut warnings);
let tag = if !param.unterminated && value.starts_with('"') {
param.report_quoting(raw, &mut warnings);
value.to_string()
} else {
crate::token_field(raw, value, Field::Tag, &mut warnings).into_owned()
};
if tag.is_empty() {
return Err(ParseError::malformed(
Field::Tag,
FaultCode::Missing,
Some(crate::offset_in(raw, value)),
));
}
if slot
.replace(tag)
.is_some()
{
return Err(ParseError::malformed(
Field::Tag,
FaultCode::Duplicate,
Some(crate::offset_in(raw, param.key)),
));
}
}
let missing_tag = || ParseError::malformed(Field::Tag, FaultCode::Missing, None);
let fields = DialogFields {
call_id: call_id.to_string(),
first_tag: first_tag.ok_or_else(missing_tag)?,
second_tag: second_tag.ok_or_else(missing_tag)?,
early_only,
params,
};
Ok(Parsed::new(fields, warnings))
}
macro_rules! dialog_id_parse {
($Type:ident) => {
impl $crate::traits::sealed::Sealed for $Type {}
impl $crate::span::Located for $Type {
fn relocate_spans(&mut self, to: &$crate::span::Relocation<'_>) {
$crate::span::Located::relocate_spans(&mut self.0, to);
}
}
impl $crate::traits::HeaderParse for $Type {
fn parse_with_warnings(
raw: &str,
) -> Result<$crate::diagnostic::Parsed<Self>, $crate::error::ParseError> {
$crate::dialog_id::parse(raw)
}
}
impl $crate::traits::UriHeaderParse for $Type {
fn parse_uri_header_with_warnings(
raw: &str,
) -> Result<$crate::diagnostic::Parsed<Self>, $crate::error::ParseError> {
$crate::dialog_id::parse_uri_header(raw)
}
}
};
}