Skip to main content

rsipstack/sip/
message.rs

1use crate::sip::{
2    headers::untyped::*,
3    headers::{Header, Headers},
4    uri::Branch,
5    Error, Method, StatusCode, Uri, Version,
6};
7
8pub trait HasHeaders {
9    fn headers(&self) -> &Headers;
10    fn headers_mut(&mut self) -> &mut Headers;
11}
12
13macro_rules! header_get {
14    ($iter:expr, $variant:path, $err:expr) => {
15        $iter
16            .find_map(|h| {
17                if let $variant(inner) = h {
18                    Some(inner)
19                } else {
20                    None
21                }
22            })
23            .ok_or_else(|| $err)
24    };
25}
26
27macro_rules! header_get_mut {
28    ($iter:expr, $variant:path, $err:expr) => {
29        $iter
30            .find_map(|h| {
31                if let $variant(inner) = h {
32                    Some(inner)
33                } else {
34                    None
35                }
36            })
37            .ok_or_else(|| $err)
38    };
39}
40
41macro_rules! header_opt {
42    ($iter:expr, $variant:path) => {
43        $iter.find_map(|h| {
44            if let $variant(inner) = h {
45                Some(inner)
46            } else {
47                None
48            }
49        })
50    };
51}
52
53macro_rules! all_headers {
54    ($iter:expr, $variant:path) => {
55        $iter
56            .filter_map(|h| {
57                if let $variant(inner) = h {
58                    Some(inner)
59                } else {
60                    None
61                }
62            })
63            .collect()
64    };
65}
66
67pub trait HeadersExt: HasHeaders {
68    fn to_header(&self) -> Result<&To, Error> {
69        header_get!(
70            self.headers().iter(),
71            Header::To,
72            Error::MissingHeader("To".into())
73        )
74    }
75    fn to_header_mut(&mut self) -> Result<&mut To, Error> {
76        header_get_mut!(
77            self.headers_mut().iter_mut(),
78            Header::To,
79            Error::MissingHeader("To".into())
80        )
81    }
82    fn from_header(&self) -> Result<&From, Error> {
83        header_get!(
84            self.headers().iter(),
85            Header::From,
86            Error::MissingHeader("From".into())
87        )
88    }
89    fn from_header_mut(&mut self) -> Result<&mut From, Error> {
90        header_get_mut!(
91            self.headers_mut().iter_mut(),
92            Header::From,
93            Error::MissingHeader("From".into())
94        )
95    }
96    fn via_header(&self) -> Result<&Via, Error> {
97        header_get!(
98            self.headers().iter(),
99            Header::Via,
100            Error::MissingHeader("Via".into())
101        )
102    }
103    fn top_via_header(&self) -> Result<Via, Error> {
104        self.via_header()?.first_value()
105    }
106    fn via_header_mut(&mut self) -> Result<&mut Via, Error> {
107        header_get_mut!(
108            self.headers_mut().iter_mut(),
109            Header::Via,
110            Error::MissingHeader("Via".into())
111        )
112    }
113    fn call_id_header(&self) -> Result<&CallId, Error> {
114        header_get!(
115            self.headers().iter(),
116            Header::CallId,
117            Error::MissingHeader("Call-ID".into())
118        )
119    }
120    fn call_id_header_mut(&mut self) -> Result<&mut CallId, Error> {
121        header_get_mut!(
122            self.headers_mut().iter_mut(),
123            Header::CallId,
124            Error::MissingHeader("Call-ID".into())
125        )
126    }
127    fn cseq_header(&self) -> Result<&CSeq, Error> {
128        header_get!(
129            self.headers().iter(),
130            Header::CSeq,
131            Error::MissingHeader("CSeq".into())
132        )
133    }
134    fn cseq_header_mut(&mut self) -> Result<&mut CSeq, Error> {
135        header_get_mut!(
136            self.headers_mut().iter_mut(),
137            Header::CSeq,
138            Error::MissingHeader("CSeq".into())
139        )
140    }
141    fn max_forwards_header(&self) -> Result<&MaxForwards, Error> {
142        header_get!(
143            self.headers().iter(),
144            Header::MaxForwards,
145            Error::MissingHeader("Max-Forwards".into())
146        )
147    }
148    fn max_forwards_header_mut(&mut self) -> Result<&mut MaxForwards, Error> {
149        header_get_mut!(
150            self.headers_mut().iter_mut(),
151            Header::MaxForwards,
152            Error::MissingHeader("Max-Forwards".into())
153        )
154    }
155    fn contact_header(&self) -> Result<&Contact, Error> {
156        header_get!(
157            self.headers().iter(),
158            Header::Contact,
159            Error::MissingHeader("Contact".into())
160        )
161    }
162    fn contact_header_mut(&mut self) -> Result<&mut Contact, Error> {
163        header_get_mut!(
164            self.headers_mut().iter_mut(),
165            Header::Contact,
166            Error::MissingHeader("Contact".into())
167        )
168    }
169    fn contact_headers(&self) -> Vec<&Contact> {
170        all_headers!(self.headers().iter(), Header::Contact)
171    }
172    fn typed_contact_headers(&self) -> Result<Vec<crate::sip::typed::Contact>, Error> {
173        let mut contacts = Vec::new();
174        for contact in self.contact_headers() {
175            contacts.extend(crate::sip::typed::Contact::parse_header_list(
176                contact.value(),
177            )?);
178        }
179        Ok(contacts)
180    }
181    fn record_route_headers(&self) -> Vec<&RecordRoute> {
182        all_headers!(self.headers().iter(), Header::RecordRoute)
183    }
184    fn record_route_header(&self) -> Option<&RecordRoute> {
185        header_opt!(self.headers().iter(), Header::RecordRoute)
186    }
187    fn typed_record_route_headers(&self) -> Result<Vec<crate::sip::typed::RecordRoute>, Error> {
188        let mut rrs = Vec::new();
189        for rr in self.record_route_headers() {
190            rrs.extend(crate::sip::typed::RecordRoute::parse_header_list(
191                rr.value(),
192            )?);
193        }
194        Ok(rrs)
195    }
196    fn route_headers(&self) -> Vec<&Route> {
197        all_headers!(self.headers().iter(), Header::Route)
198    }
199    fn route_header(&self) -> Option<&Route> {
200        header_opt!(self.headers().iter(), Header::Route)
201    }
202    fn typed_route_headers(&self) -> Result<Vec<crate::sip::typed::Route>, Error> {
203        let mut routes = Vec::new();
204        for r in self.route_headers() {
205            routes.extend(crate::sip::typed::Route::parse_header_list(r.value())?);
206        }
207        Ok(routes)
208    }
209    fn user_agent_header(&self) -> Option<&UserAgent> {
210        header_opt!(self.headers().iter(), Header::UserAgent)
211    }
212    fn authorization_header(&self) -> Option<&Authorization> {
213        header_opt!(self.headers().iter(), Header::Authorization)
214    }
215    fn www_authenticate_header(&self) -> Option<&WwwAuthenticate> {
216        header_opt!(self.headers().iter(), Header::WwwAuthenticate)
217    }
218    fn expires_header(&self) -> Option<&Expires> {
219        header_opt!(self.headers().iter(), Header::Expires)
220    }
221    fn min_expires_header(&self) -> Option<&MinExpires> {
222        header_opt!(self.headers().iter(), Header::MinExpires)
223    }
224    fn reason_header(&self) -> Option<&crate::sip::headers::untyped::Reason> {
225        header_opt!(self.headers().iter(), Header::Reason)
226    }
227    fn refer_to_header(&self) -> Option<&crate::sip::headers::untyped::ReferTo> {
228        header_opt!(self.headers().iter(), Header::ReferTo)
229    }
230    fn referred_by_header(&self) -> Option<&crate::sip::headers::untyped::ReferredBy> {
231        header_opt!(self.headers().iter(), Header::ReferredBy)
232    }
233    fn session_expires_header(&self) -> Option<&crate::sip::headers::untyped::SessionExpires> {
234        header_opt!(self.headers().iter(), Header::SessionExpires)
235    }
236    fn p_asserted_identity_header(
237        &self,
238    ) -> Option<&crate::sip::headers::untyped::PAssertedIdentity> {
239        header_opt!(self.headers().iter(), Header::PAssertedIdentity)
240    }
241    fn call_info_header(&self) -> Option<&CallInfo> {
242        header_opt!(self.headers().iter(), Header::CallInfo)
243    }
244    fn call_info_headers(&self) -> Vec<&CallInfo> {
245        all_headers!(self.headers().iter(), Header::CallInfo)
246    }
247    fn user_to_user_header(&self) -> Option<&UserToUser> {
248        header_opt!(self.headers().iter(), Header::UserToUser)
249    }
250    fn user_to_user_headers(&self) -> Vec<&UserToUser> {
251        all_headers!(self.headers().iter(), Header::UserToUser)
252    }
253    fn replaces_header(&self) -> Option<&crate::sip::headers::untyped::Replaces> {
254        header_opt!(self.headers().iter(), Header::Replaces)
255    }
256    fn privacy_header(&self) -> Option<&crate::sip::headers::untyped::Privacy> {
257        header_opt!(self.headers().iter(), Header::Privacy)
258    }
259    fn path_headers(&self) -> Vec<&crate::sip::headers::untyped::Path> {
260        all_headers!(self.headers().iter(), Header::Path)
261    }
262    fn rseq_value(&self) -> Option<u32> {
263        self.headers().iter().find_map(|h| {
264            if let Header::RSeq(r) = h {
265                r.value().trim().parse().ok()
266            } else {
267                None
268            }
269        })
270    }
271    fn rack_value(&self) -> Option<(u32, u32, Method)> {
272        self.headers().iter().find_map(|h| {
273            if let Header::RAck(r) = h {
274                let v = r.value();
275                let mut parts = v.split_whitespace();
276                let rseq = parts.next()?.parse::<u32>().ok()?;
277                let cseq = parts.next()?.parse::<u32>().ok()?;
278                let method = parts.next()?.parse::<Method>().ok()?;
279                Some((rseq, cseq, method))
280            } else {
281                None
282            }
283        })
284    }
285    fn header_value(&self, name: &str) -> Option<String> {
286        self.headers().iter().find_map(|h| {
287            if h.name().eq_ignore_ascii_case(name) {
288                Some(h.value().trim().to_string())
289            } else {
290                None
291            }
292        })
293    }
294    fn header_contains_token(&self, name: &str, token: &str) -> bool {
295        self.headers().iter().any(|header| match header {
296            Header::Supported(value) if name.eq_ignore_ascii_case("Supported") => value
297                .value()
298                .split(',')
299                .any(|item| item.trim().eq_ignore_ascii_case(token)),
300            Header::Require(value) if name.eq_ignore_ascii_case("Require") => value
301                .value()
302                .split(',')
303                .any(|item| item.trim().eq_ignore_ascii_case(token)),
304            _ => false,
305        })
306    }
307    fn transaction_id(&self) -> Result<Option<Branch>, Error> {
308        use crate::sip::headers::untyped::ToTypedHeader;
309        Ok(self.top_via_header()?.typed()?.branch().cloned())
310    }
311}
312
313#[derive(Debug, PartialEq, Eq, Clone)]
314pub struct Request {
315    pub method: Method,
316    pub uri: Uri,
317    pub version: Version,
318    pub headers: Headers,
319    pub body: Vec<u8>,
320}
321
322impl Request {
323    pub fn method(&self) -> &Method {
324        &self.method
325    }
326    pub fn uri(&self) -> &Uri {
327        &self.uri
328    }
329    pub fn destination(&self) -> Uri {
330        for route in self.route_headers() {
331            if let Ok(mut routes) = crate::sip::typed::Route::parse_header_list(route.value()) {
332                if let Some(route) = routes.drain(..).next() {
333                    return route.uri;
334                }
335            }
336        }
337        self.uri.clone()
338    }
339    pub fn version(&self) -> &Version {
340        &self.version
341    }
342    pub fn body(&self) -> &Vec<u8> {
343        &self.body
344    }
345    pub fn body_mut(&mut self) -> &mut Vec<u8> {
346        &mut self.body
347    }
348}
349
350impl HasHeaders for Request {
351    fn headers(&self) -> &Headers {
352        &self.headers
353    }
354    fn headers_mut(&mut self) -> &mut Headers {
355        &mut self.headers
356    }
357}
358
359impl HeadersExt for Request {}
360
361impl std::fmt::Display for Request {
362    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        write!(
364            f,
365            "{} {} {}\r\n{}\r\n{}",
366            self.method,
367            self.uri,
368            self.version,
369            self.headers,
370            String::from_utf8_lossy(&self.body)
371        )
372    }
373}
374
375impl Request {
376    /// Serialize the request to its on-the-wire byte representation.
377    ///
378    /// The request line and headers are ASCII; the body is appended verbatim.
379    /// A SIP message body is an opaque octet sequence (RFC 3261 ยง7.4) and may
380    /// contain arbitrary bytes โ€” e.g. a binary ISUP part or an eCall MSD โ€” so
381    /// it must not be forced through UTF-8.
382    ///
383    /// Prefer this over [`Display`](std::fmt::Display) / `to_string()` when
384    /// putting a message on the wire: `Display` renders the body with
385    /// [`String::from_utf8_lossy`], which is fine for logging but corrupts
386    /// non-UTF-8 bytes.
387    pub fn to_bytes(&self) -> Vec<u8> {
388        let head = format!(
389            "{} {} {}\r\n{}\r\n",
390            self.method, self.uri, self.version, self.headers
391        );
392        let mut buf = head.into_bytes();
393        buf.extend_from_slice(&self.body);
394        buf
395    }
396}
397
398impl std::convert::TryFrom<Vec<u8>> for Request {
399    type Error = Error;
400    fn try_from(bytes: Vec<u8>) -> Result<Self, Error> {
401        match crate::sip::parser::parse_message(&bytes)? {
402            SipMessage::Request(r) => Ok(r),
403            SipMessage::Response(_) => {
404                Err(Error::Unexpected("expected Request, got Response".into()))
405            }
406        }
407    }
408}
409
410impl std::convert::TryFrom<&[u8]> for Request {
411    type Error = Error;
412    fn try_from(bytes: &[u8]) -> Result<Self, Error> {
413        match crate::sip::parser::parse_message(bytes)? {
414            SipMessage::Request(r) => Ok(r),
415            SipMessage::Response(_) => {
416                Err(Error::Unexpected("expected Request, got Response".into()))
417            }
418        }
419    }
420}
421
422impl std::convert::TryFrom<&str> for Request {
423    type Error = Error;
424    fn try_from(s: &str) -> Result<Self, Error> {
425        Self::try_from(s.as_bytes())
426    }
427}
428
429impl std::convert::TryFrom<String> for Request {
430    type Error = Error;
431    fn try_from(s: String) -> Result<Self, Error> {
432        Self::try_from(s.as_bytes())
433    }
434}
435
436impl std::convert::From<Request> for String {
437    fn from(r: Request) -> String {
438        r.to_string()
439    }
440}
441
442impl std::convert::From<Request> for Vec<u8> {
443    fn from(r: Request) -> Vec<u8> {
444        r.to_bytes()
445    }
446}
447
448#[derive(Debug, PartialEq, Eq, Clone)]
449pub struct Response {
450    pub status_code: StatusCode,
451    pub version: Version,
452    pub headers: Headers,
453    pub body: Vec<u8>,
454}
455
456impl Response {
457    pub fn status_code(&self) -> &StatusCode {
458        &self.status_code
459    }
460    pub fn version(&self) -> &Version {
461        &self.version
462    }
463    pub fn body(&self) -> &Vec<u8> {
464        &self.body
465    }
466    pub fn body_mut(&mut self) -> &mut Vec<u8> {
467        &mut self.body
468    }
469    pub fn reason_phrase(&self) -> Option<&str> {
470        self.headers.iter().find_map(|h| {
471            if let Header::Reason(r) = h {
472                Some(r.value())
473            } else {
474                None
475            }
476        })
477    }
478
479    /// Get the received address from the Via header (including rport if present)
480    pub fn via_received(&self) -> Option<crate::sip::uri::HostWithPort> {
481        use crate::sip::HeadersExt;
482        self.top_via_header().ok().and_then(|via| {
483            via.typed()
484                .ok()
485                .and_then(|typed_via: crate::sip::typed::Via| {
486                    let received = typed_via.params.iter().find_map(|p| {
487                        if let crate::sip::uri::Param::Received(r) = p {
488                            r.parse().ok().map(crate::sip::uri::Host::IpAddr)
489                        } else {
490                            None
491                        }
492                    });
493                    let rport = typed_via.params.iter().find_map(|p| {
494                        if let crate::sip::uri::Param::Rport(rport) = p {
495                            *rport
496                        } else {
497                            None
498                        }
499                    });
500                    received.map(|host| crate::sip::uri::HostWithPort {
501                        host,
502                        port: rport.map(crate::sip::transport::Port),
503                    })
504                })
505        })
506    }
507}
508
509impl HasHeaders for Response {
510    fn headers(&self) -> &Headers {
511        &self.headers
512    }
513    fn headers_mut(&mut self) -> &mut Headers {
514        &mut self.headers
515    }
516}
517
518impl HeadersExt for Response {}
519
520impl Default for Response {
521    fn default() -> Self {
522        Response {
523            status_code: StatusCode::OK,
524            version: Version::V2,
525            headers: Headers::default(),
526            body: Vec::new(),
527        }
528    }
529}
530
531impl std::fmt::Display for Response {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        write!(
534            f,
535            "{} {} {}\r\n{}\r\n{}",
536            self.version,
537            self.status_code.code(),
538            self.status_code.text(),
539            self.headers,
540            String::from_utf8_lossy(&self.body)
541        )
542    }
543}
544
545impl Response {
546    /// Serialize the response to its on-the-wire byte representation.
547    ///
548    /// See [`Request::to_bytes`]: the body is written verbatim because a SIP
549    /// body is an opaque octet sequence (RFC 3261 ยง7.4), not necessarily UTF-8.
550    pub fn to_bytes(&self) -> Vec<u8> {
551        let head = format!(
552            "{} {} {}\r\n{}\r\n",
553            self.version,
554            self.status_code.code(),
555            self.status_code.text(),
556            self.headers
557        );
558        let mut buf = head.into_bytes();
559        buf.extend_from_slice(&self.body);
560        buf
561    }
562}
563
564impl std::convert::TryFrom<Vec<u8>> for Response {
565    type Error = Error;
566    fn try_from(bytes: Vec<u8>) -> Result<Self, Error> {
567        match crate::sip::parser::parse_message(&bytes)? {
568            SipMessage::Response(r) => Ok(r),
569            SipMessage::Request(_) => {
570                Err(Error::Unexpected("expected Response, got Request".into()))
571            }
572        }
573    }
574}
575
576impl std::convert::TryFrom<&[u8]> for Response {
577    type Error = Error;
578    fn try_from(bytes: &[u8]) -> Result<Self, Error> {
579        match crate::sip::parser::parse_message(bytes)? {
580            SipMessage::Response(r) => Ok(r),
581            SipMessage::Request(_) => {
582                Err(Error::Unexpected("expected Response, got Request".into()))
583            }
584        }
585    }
586}
587
588impl std::convert::TryFrom<&str> for Response {
589    type Error = Error;
590    fn try_from(s: &str) -> Result<Self, Error> {
591        Self::try_from(s.as_bytes())
592    }
593}
594
595impl std::convert::TryFrom<String> for Response {
596    type Error = Error;
597    fn try_from(s: String) -> Result<Self, Error> {
598        Self::try_from(s.as_bytes())
599    }
600}
601
602impl std::convert::From<Response> for String {
603    fn from(r: Response) -> String {
604        r.to_string()
605    }
606}
607
608impl std::convert::From<Response> for Vec<u8> {
609    fn from(r: Response) -> Vec<u8> {
610        r.to_bytes()
611    }
612}
613
614impl std::convert::TryFrom<bytes::Bytes> for SipMessage {
615    type Error = Error;
616    fn try_from(bytes: bytes::Bytes) -> Result<Self, Error> {
617        crate::sip::parser::parse_message(&bytes)
618    }
619}
620
621#[derive(Debug, PartialEq, Eq, Clone)]
622pub enum SipMessage {
623    Request(Request),
624    Response(Response),
625}
626
627impl SipMessage {
628    pub fn is_request(&self) -> bool {
629        matches!(self, SipMessage::Request(_))
630    }
631    pub fn is_response(&self) -> bool {
632        matches!(self, SipMessage::Response(_))
633    }
634
635    /// The start line (request/status line) of the message โ€” the first line of
636    /// its on-wire representation, e.g. `CANCEL sip:2001@localhost SIP/2.0` or
637    /// `SIP/2.0 200 OK`. Useful for single-line transport logging without
638    /// spilling the multi-line headers/body into the log.
639    pub fn start_line(&self) -> String {
640        match self {
641            SipMessage::Request(r) => format!("{} {} {}", r.method, r.uri, r.version),
642            SipMessage::Response(r) => format!(
643                "{} {} {}",
644                r.version,
645                r.status_code.code(),
646                r.status_code.text()
647            ),
648        }
649    }
650}
651
652impl HasHeaders for SipMessage {
653    fn headers(&self) -> &Headers {
654        match self {
655            SipMessage::Request(r) => r.headers(),
656            SipMessage::Response(r) => r.headers(),
657        }
658    }
659    fn headers_mut(&mut self) -> &mut Headers {
660        match self {
661            SipMessage::Request(r) => r.headers_mut(),
662            SipMessage::Response(r) => r.headers_mut(),
663        }
664    }
665}
666
667impl HeadersExt for SipMessage {}
668
669impl std::fmt::Display for SipMessage {
670    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671        match self {
672            SipMessage::Request(r) => write!(f, "{}", r),
673            SipMessage::Response(r) => write!(f, "{}", r),
674        }
675    }
676}
677
678impl SipMessage {
679    /// Serialize the message to its on-the-wire byte representation.
680    ///
681    /// See [`Request::to_bytes`]. Transports should use this rather than
682    /// `to_string()` so binary bodies are preserved byte-for-byte.
683    pub fn to_bytes(&self) -> Vec<u8> {
684        match self {
685            SipMessage::Request(r) => r.to_bytes(),
686            SipMessage::Response(r) => r.to_bytes(),
687        }
688    }
689}
690
691impl std::convert::TryFrom<Vec<u8>> for SipMessage {
692    type Error = Error;
693    fn try_from(bytes: Vec<u8>) -> Result<Self, Error> {
694        crate::sip::parser::parse_message(&bytes)
695    }
696}
697
698impl std::convert::TryFrom<&[u8]> for SipMessage {
699    type Error = Error;
700    fn try_from(bytes: &[u8]) -> Result<Self, Error> {
701        crate::sip::parser::parse_message(bytes)
702    }
703}
704
705impl std::convert::TryFrom<&str> for SipMessage {
706    type Error = Error;
707    fn try_from(s: &str) -> Result<Self, Error> {
708        Self::try_from(s.as_bytes())
709    }
710}
711
712impl std::convert::TryFrom<String> for SipMessage {
713    type Error = Error;
714    fn try_from(s: String) -> Result<Self, Error> {
715        Self::try_from(s.as_bytes())
716    }
717}
718
719impl std::convert::From<Request> for SipMessage {
720    fn from(r: Request) -> SipMessage {
721        SipMessage::Request(r)
722    }
723}
724
725impl std::convert::From<Response> for SipMessage {
726    fn from(r: Response) -> SipMessage {
727        SipMessage::Response(r)
728    }
729}
730
731impl std::convert::TryFrom<SipMessage> for Request {
732    type Error = Error;
733    fn try_from(m: SipMessage) -> Result<Self, Error> {
734        match m {
735            SipMessage::Request(r) => Ok(r),
736            SipMessage::Response(_) => Err(Error::Unexpected("expected Request".into())),
737        }
738    }
739}
740
741impl std::convert::TryFrom<SipMessage> for Response {
742    type Error = Error;
743    fn try_from(m: SipMessage) -> Result<Self, Error> {
744        match m {
745            SipMessage::Response(r) => Ok(r),
746            SipMessage::Request(_) => Err(Error::Unexpected("expected Response".into())),
747        }
748    }
749}
750
751impl std::convert::From<SipMessage> for String {
752    fn from(m: SipMessage) -> String {
753        m.to_string()
754    }
755}
756
757impl std::convert::From<SipMessage> for Vec<u8> {
758    fn from(m: SipMessage) -> Vec<u8> {
759        m.to_bytes()
760    }
761}
762
763#[cfg(test)]
764mod tests {
765    use super::{HasHeaders, HeadersExt, Request, Response, SipMessage};
766    use crate::sip::{Header, Method};
767
768    fn invite_request() -> &'static str {
769        concat!(
770            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
771            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bK776asdhds\r\n",
772            "Max-Forwards: 70\r\n",
773            "To: Bob <sip:bob@restsend.com>\r\n",
774            "From: Alice <sip:alice@restsend.com>;tag=1928301774\r\n",
775            "Call-ID: a84b4c76e66710@ua.restsend.com\r\n",
776            "CSeq: 314159 INVITE\r\n",
777            "Contact: <sip:alice@ua.restsend.com>\r\n",
778            "Content-Type: application/sdp\r\n",
779            "Content-Length: 0\r\n",
780            "\r\n"
781        )
782    }
783
784    fn register_request() -> &'static str {
785        concat!(
786            "REGISTER sip:registrar.restsend.com SIP/2.0\r\n",
787            "Via: SIP/2.0/UDP ua.restsend.com:5060;branch=z9hG4bKnashds8\r\n",
788            "Max-Forwards: 70\r\n",
789            "To: Bob <sip:bob@restsend.com>\r\n",
790            "From: Bob <sip:bob@restsend.com>;tag=456248\r\n",
791            "Call-ID: 843817637684230@998sdasdh09\r\n",
792            "CSeq: 1826 REGISTER\r\n",
793            "Contact: <sip:bob@192.0.2.4>\r\n",
794            "Expires: 7200\r\n",
795            "Content-Length: 0\r\n",
796            "\r\n"
797        )
798    }
799
800    fn ok_response() -> &'static str {
801        concat!(
802            "SIP/2.0 200 OK\r\n",
803            "Via: SIP/2.0/UDP proxy1.restsend.com;branch=z9hG4bK4b43c2ff8.1;received=192.0.2.3\r\n",
804            "Via: SIP/2.0/UDP proxy2.restsend.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n",
805            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bK776asdhds;received=192.0.2.1\r\n",
806            "To: Bob <sip:bob@restsend.com>;tag=a6c85cf\r\n",
807            "From: Alice <sip:alice@restsend.com>;tag=1928301774\r\n",
808            "Call-ID: a84b4c76e66710@ua.restsend.com\r\n",
809            "CSeq: 314159 INVITE\r\n",
810            "Contact: <sip:bob@192.0.2.4>\r\n",
811            "Content-Type: application/sdp\r\n",
812            "Content-Length: 131\r\n",
813            "\r\n",
814            "v=0\r\no=bob 2890844527 2890844527 IN IP4 192.0.2.4\r\ns=-\r\nc=IN IP4 192.0.2.4\r\nt=0 0\r\nm=audio 3456 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000"
815        )
816    }
817
818    #[test]
819    fn parse_invite_request() {
820        let req: Request = invite_request().try_into().unwrap();
821        assert_eq!(req.method, Method::Invite);
822        assert_eq!(req.uri.to_string(), "sip:bob@restsend.com");
823        assert_eq!(
824            req.from_header().unwrap().value(),
825            "Alice <sip:alice@restsend.com>;tag=1928301774"
826        );
827        assert_eq!(
828            req.to_header().unwrap().value(),
829            "Bob <sip:bob@restsend.com>"
830        );
831        assert_eq!(
832            req.call_id_header().unwrap().value(),
833            "a84b4c76e66710@ua.restsend.com"
834        );
835    }
836
837    #[test]
838    fn parse_register_request() {
839        let req: Request = register_request().try_into().unwrap();
840        assert_eq!(req.method, Method::Register);
841        let contacts = req.typed_contact_headers().unwrap();
842        assert_eq!(contacts.len(), 1);
843        assert_eq!(contacts[0].uri.to_string(), "sip:bob@192.0.2.4");
844    }
845
846    #[test]
847    fn parse_ok_response() {
848        let resp: Response = ok_response().try_into().unwrap();
849        assert_eq!(resp.status_code.code(), 200);
850        assert_eq!(resp.body, b"v=0\r\no=bob 2890844527 2890844527 IN IP4 192.0.2.4\r\ns=-\r\nc=IN IP4 192.0.2.4\r\nt=0 0\r\nm=audio 3456 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000");
851    }
852
853    #[test]
854    fn request_binary_body_survives_serialization() {
855        // A SIP message body is an opaque octet payload (RFC 3261 ยง7.4), not
856        // necessarily UTF-8 text. e.g. an NG-eCall INVITE carries a binary
857        // ASN.1 PER-encoded MSD. Serialization to the wire must preserve every
858        // byte, including bytes that are invalid as UTF-8.
859        let mut req: Request = invite_request().try_into().unwrap();
860        let binary_body: Vec<u8> = vec![0x00, 0x01, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x02, 0x7F, 0x90];
861        req.body = binary_body.clone();
862
863        let wire: Vec<u8> = req.into();
864
865        assert!(
866            wire.ends_with(&binary_body),
867            "binary body corrupted during serialization: got tail {:?}",
868            &wire[wire.len().saturating_sub(binary_body.len() + 4)..]
869        );
870    }
871
872    #[test]
873    fn response_binary_body_survives_serialization() {
874        let mut resp: Response = ok_response().try_into().unwrap();
875        let binary_body: Vec<u8> = vec![0x00, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x02];
876        resp.body = binary_body.clone();
877
878        let wire: Vec<u8> = resp.into();
879
880        assert!(
881            wire.ends_with(&binary_body),
882            "binary body corrupted during serialization"
883        );
884    }
885
886    #[test]
887    fn response_has_multiple_via_headers() {
888        let resp: Response = ok_response().try_into().unwrap();
889        let vias: Vec<_> = resp
890            .headers()
891            .iter()
892            .filter(|h| matches!(h, Header::Via(_)))
893            .collect();
894        assert_eq!(vias.len(), 3);
895    }
896
897    #[test]
898    fn top_via_header_returns_first_value_from_combined_header() {
899        let resp: Response = concat!(
900            "SIP/2.0 401 Unauthorized\r\n",
901            "Via: SIP/2.0/UDP 172.22.22.80:5062;received=172.22.22.80;rport=5062;branch=z9hG4bKfirst,SIP/2.0/TCP 10.0.13.70:5060;branch=z9hG4bKsecond\r\n",
902            "From: <sip:001010000000001@ims.example.com>;tag=e80c1d8c\r\n",
903            "To: <sip:001010000000001@ims.example.com>;tag=2e518\r\n",
904            "Call-ID: 9e353fc94f78064f@10.0.13.70\r\n",
905            "CSeq: 1 REGISTER\r\n",
906            "Content-Length: 0\r\n",
907            "\r\n",
908        )
909        .try_into()
910        .unwrap();
911
912        assert_eq!(
913            resp.via_header().unwrap().value(),
914            "SIP/2.0/UDP 172.22.22.80:5062;received=172.22.22.80;rport=5062;branch=z9hG4bKfirst,SIP/2.0/TCP 10.0.13.70:5060;branch=z9hG4bKsecond"
915        );
916        assert_eq!(
917            resp.top_via_header().unwrap().value(),
918            "SIP/2.0/UDP 172.22.22.80:5062;received=172.22.22.80;rport=5062;branch=z9hG4bKfirst"
919        );
920    }
921
922    #[test]
923    fn headers_push_front() {
924        let mut req: Request = invite_request().try_into().unwrap();
925        let orig_first = req.headers.iter().next().unwrap().name().to_string();
926        req.headers
927            .push_front(Header::MaxForwards(crate::sip::MaxForwards::new("10")));
928        assert_eq!(req.headers.iter().next().unwrap().name(), "Max-Forwards");
929        assert_eq!(req.headers.iter().nth(1).unwrap().name(), orig_first);
930    }
931
932    #[test]
933    fn new_headers_reason_roundtrip() {
934        let msg: SipMessage = concat!(
935            "BYE sip:alice@restsend.com SIP/2.0\r\n",
936            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bK776asdhds\r\n",
937            "From: <sip:bob@restsend.com>;tag=a6c85cf\r\n",
938            "To: <sip:alice@restsend.com>;tag=1928301774\r\n",
939            "Call-ID: a84b4c76e66710@ua.restsend.com\r\n",
940            "CSeq: 231 BYE\r\n",
941            "Reason: SIP ;cause=200 ;text=\"Call completed elsewhere\"\r\n",
942            "Content-Length: 0\r\n",
943            "\r\n"
944        )
945        .try_into()
946        .unwrap();
947        let reason = msg.reason_header().unwrap();
948        assert!(reason.value().contains("200"));
949        assert!(reason.value().contains("Call completed elsewhere"));
950    }
951
952    #[test]
953    fn new_headers_refer_to_roundtrip() {
954        let msg: SipMessage = concat!(
955            "REFER sip:bob@restsend.com SIP/2.0\r\n",
956            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKkjshdyff\r\n",
957            "To: <sip:bob@restsend.com>\r\n",
958            "From: <sip:alice@restsend.com>;tag=xyz\r\n",
959            "Call-ID: 12345600@ua.restsend.com\r\n",
960            "CSeq: 1 REFER\r\n",
961            "Refer-To: <sip:carol@restsend.com>\r\n",
962            "Content-Length: 0\r\n",
963            "\r\n"
964        )
965        .try_into()
966        .unwrap();
967        assert!(msg.refer_to_header().is_some());
968        assert_eq!(
969            msg.refer_to_header().unwrap().value(),
970            "<sip:carol@restsend.com>"
971        );
972    }
973
974    #[test]
975    fn new_headers_session_expires_roundtrip() {
976        let msg: SipMessage = concat!(
977            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
978            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
979            "From: <sip:alice@restsend.com>;tag=abc\r\n",
980            "To: <sip:bob@restsend.com>\r\n",
981            "Call-ID: test@ua.restsend.com\r\n",
982            "CSeq: 1 INVITE\r\n",
983            "Session-Expires: 1800;refresher=uac\r\n",
984            "Min-SE: 90\r\n",
985            "Content-Length: 0\r\n",
986            "\r\n"
987        )
988        .try_into()
989        .unwrap();
990        let se = msg.session_expires_header().unwrap();
991        assert!(se.value().contains("1800"));
992        let min_se = msg.headers().iter().find_map(|h| {
993            if let Header::MinSE(m) = h {
994                Some(m)
995            } else {
996                None
997            }
998        });
999        assert!(min_se.is_some());
1000        assert_eq!(min_se.unwrap().value(), "90");
1001    }
1002
1003    #[test]
1004    fn new_headers_call_info() {
1005        let msg: SipMessage = concat!(
1006            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1007            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1008            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1009            "To: <sip:bob@restsend.com>\r\n",
1010            "Call-ID: call-info-test@ua.restsend.com\r\n",
1011            "CSeq: 1 INVITE\r\n",
1012            "Call-Info: <http://www.example.com/alice/photo.jpg>;purpose=icon\r\n",
1013            "Call-Info: <http://www.example.com/alice/>;purpose=info\r\n",
1014            "Content-Length: 0\r\n",
1015            "\r\n"
1016        )
1017        .try_into()
1018        .unwrap();
1019        let first = msg.call_info_header().unwrap();
1020        assert!(first.value().contains("photo.jpg"));
1021        assert!(first.value().contains("purpose=icon"));
1022        let all = msg.call_info_headers();
1023        assert_eq!(all.len(), 2);
1024        assert!(all[1].value().contains("purpose=info"));
1025    }
1026
1027    #[test]
1028    fn new_headers_user_to_user() {
1029        let msg: SipMessage = concat!(
1030            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1031            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1032            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1033            "To: <sip:bob@restsend.com>\r\n",
1034            "Call-ID: uui-test@ua.restsend.com\r\n",
1035            "CSeq: 1 INVITE\r\n",
1036            "User-to-User: 56a390f3d2b7310023a;encoding=hex;purpose=isdn-uui;content=isdn-uui\r\n",
1037            "Content-Length: 0\r\n",
1038            "\r\n"
1039        )
1040        .try_into()
1041        .unwrap();
1042        let uui = msg.user_to_user_header().unwrap();
1043        assert!(uui.value().starts_with("56a390f3d2b7310023a"));
1044        assert!(uui.value().contains("encoding=hex"));
1045        assert!(uui.value().contains("purpose=isdn-uui"));
1046        assert_eq!(msg.user_to_user_headers().len(), 1);
1047    }
1048
1049    #[test]
1050    fn new_headers_p_asserted_identity() {
1051        let msg: SipMessage = concat!(
1052            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1053            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1054            "From: <sip:anonymous@anonymous.invalid>;tag=abc\r\n",
1055            "To: <sip:bob@restsend.com>\r\n",
1056            "Call-ID: pai-test@ua.restsend.com\r\n",
1057            "CSeq: 1 INVITE\r\n",
1058            "P-Asserted-Identity: <sip:alice@restsend.com>\r\n",
1059            "Privacy: id\r\n",
1060            "Content-Length: 0\r\n",
1061            "\r\n"
1062        )
1063        .try_into()
1064        .unwrap();
1065        assert!(msg.p_asserted_identity_header().is_some());
1066        assert_eq!(
1067            msg.p_asserted_identity_header().unwrap().value(),
1068            "<sip:alice@restsend.com>"
1069        );
1070        assert!(msg.privacy_header().is_some());
1071    }
1072
1073    #[test]
1074    fn new_headers_replaces() {
1075        let msg: SipMessage = concat!(
1076            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1077            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1078            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1079            "To: <sip:bob@restsend.com>\r\n",
1080            "Call-ID: transfer-test@ua.restsend.com\r\n",
1081            "CSeq: 1 INVITE\r\n",
1082            "Replaces: original-call-id@ua.restsend.com;to-tag=orig-to;from-tag=orig-from\r\n",
1083            "Content-Length: 0\r\n",
1084            "\r\n"
1085        )
1086        .try_into()
1087        .unwrap();
1088        let replaces = msg.replaces_header().unwrap();
1089        assert!(replaces.value().contains("original-call-id"));
1090    }
1091
1092    #[test]
1093    fn new_headers_rseq_rack() {
1094        let prack: SipMessage = concat!(
1095            "PRACK sip:bob@restsend.com SIP/2.0\r\n",
1096            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1097            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1098            "To: <sip:bob@restsend.com>;tag=xyz\r\n",
1099            "Call-ID: prack-test@ua.restsend.com\r\n",
1100            "CSeq: 2 PRACK\r\n",
1101            "RAck: 776656 1 INVITE\r\n",
1102            "Content-Length: 0\r\n",
1103            "\r\n"
1104        )
1105        .try_into()
1106        .unwrap();
1107
1108        let rack = prack.rack_value().unwrap();
1109        assert_eq!(rack.0, 776656);
1110        assert_eq!(rack.1, 1);
1111        assert_eq!(rack.2, Method::Invite);
1112
1113        let provisional: SipMessage = concat!(
1114            "SIP/2.0 183 Session Progress\r\n",
1115            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1116            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1117            "To: <sip:bob@restsend.com>;tag=xyz\r\n",
1118            "Call-ID: prack-test@ua.restsend.com\r\n",
1119            "CSeq: 1 INVITE\r\n",
1120            "RSeq: 776656\r\n",
1121            "Content-Length: 0\r\n",
1122            "\r\n"
1123        )
1124        .try_into()
1125        .unwrap();
1126
1127        assert_eq!(provisional.rseq_value(), Some(776656));
1128    }
1129
1130    #[test]
1131    fn new_headers_path_header() {
1132        let msg: SipMessage = concat!(
1133            "REGISTER sip:registrar.restsend.com SIP/2.0\r\n",
1134            "Via: SIP/2.0/TCP edge.restsend.com;branch=z9hG4bKtest\r\n",
1135            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1136            "To: <sip:alice@restsend.com>\r\n",
1137            "Call-ID: path-test@edge.restsend.com\r\n",
1138            "CSeq: 1 REGISTER\r\n",
1139            "Path: <sip:edge.restsend.com;lr>\r\n",
1140            "Contact: <sip:alice@192.0.2.5:5060>\r\n",
1141            "Content-Length: 0\r\n",
1142            "\r\n"
1143        )
1144        .try_into()
1145        .unwrap();
1146        let paths = msg.path_headers();
1147        assert_eq!(paths.len(), 1);
1148        assert!(paths[0].value().contains("edge.restsend.com"));
1149    }
1150
1151    #[test]
1152    fn header_value_helper_case_insensitive() {
1153        let req: Request = invite_request().try_into().unwrap();
1154        let val = req.header_value("content-type");
1155        assert!(val.is_some());
1156        assert!(val.unwrap().contains("application/sdp"));
1157    }
1158
1159    #[test]
1160    fn header_contains_token_helper() {
1161        let msg: SipMessage = concat!(
1162            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1163            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1164            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1165            "To: <sip:bob@restsend.com>\r\n",
1166            "Call-ID: test@pc33\r\n",
1167            "CSeq: 1 INVITE\r\n",
1168            "Supported: timer, 100rel, replaces\r\n",
1169            "Content-Length: 0\r\n",
1170            "\r\n"
1171        )
1172        .try_into()
1173        .unwrap();
1174        assert!(msg.header_contains_token("supported", "timer"));
1175        assert!(msg.header_contains_token("supported", "100rel"));
1176        assert!(!msg.header_contains_token("supported", "gruu"));
1177        assert!(!msg.header_contains_token("content-type", "application/sdp"));
1178    }
1179
1180    #[test]
1181    fn destination_from_request_without_route() {
1182        let req: Request = invite_request().try_into().unwrap();
1183        let dest = req.destination();
1184        assert_eq!(dest.to_string(), "sip:bob@restsend.com");
1185    }
1186
1187    #[test]
1188    fn destination_from_request_with_route() {
1189        let req: Request = concat!(
1190            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1191            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1192            "Route: <sip:proxy.restsend.com;lr>\r\n",
1193            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1194            "To: <sip:bob@restsend.com>\r\n",
1195            "Call-ID: routed@pc33\r\n",
1196            "CSeq: 1 INVITE\r\n",
1197            "Content-Length: 0\r\n",
1198            "\r\n"
1199        )
1200        .try_into()
1201        .unwrap();
1202        let dest = req.destination();
1203        assert_eq!(dest.to_string(), "sip:proxy.restsend.com;lr");
1204    }
1205
1206    #[test]
1207    fn compact_form_headers_are_parsed() {
1208        let message: SipMessage = concat!(
1209            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1210            "v: SIP/2.0/UDP restsend.com:5060;branch=z9hG4bK-1\r\n",
1211            "f: <sip:alice@restsend.com>;tag=123\r\n",
1212            "t: <sip:bob@restsend.com>\r\n",
1213            "i: call-id-1\r\n",
1214            "m: <sip:alice@restsend.com>\r\n",
1215            "e: gzip\r\n",
1216            "l: 0\r\n",
1217            "c: application/sdp\r\n",
1218            "s: hello\r\n",
1219            "k: timer\r\n",
1220            "\r\n"
1221        )
1222        .try_into()
1223        .unwrap();
1224
1225        assert!(message.via_header().is_ok());
1226        assert!(message.from_header().is_ok());
1227        assert!(message.to_header().is_ok());
1228        assert!(message.call_id_header().is_ok());
1229        assert!(message.contact_header().is_ok());
1230        assert!(message
1231            .headers()
1232            .iter()
1233            .any(|header| matches!(header, Header::ContentEncoding(_))));
1234        assert!(message
1235            .headers()
1236            .iter()
1237            .any(|header| matches!(header, Header::ContentLength(_))));
1238        assert!(message
1239            .headers()
1240            .iter()
1241            .any(|header| matches!(header, Header::ContentType(_))));
1242        assert!(message
1243            .headers()
1244            .iter()
1245            .any(|header| matches!(header, Header::Subject(_))));
1246        assert!(message
1247            .headers()
1248            .iter()
1249            .any(|header| matches!(header, Header::Supported(_))));
1250    }
1251
1252    #[test]
1253    fn compact_form_refer_to_alias_r() {
1254        let msg: SipMessage = concat!(
1255            "REFER sip:bob@restsend.com SIP/2.0\r\n",
1256            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1257            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1258            "To: <sip:bob@restsend.com>\r\n",
1259            "Call-ID: refer-compact@host\r\n",
1260            "CSeq: 1 REFER\r\n",
1261            "r: <sip:carol@restsend.com>\r\n",
1262            "Content-Length: 0\r\n",
1263            "\r\n"
1264        )
1265        .try_into()
1266        .unwrap();
1267        assert!(msg.refer_to_header().is_some());
1268    }
1269
1270    #[test]
1271    fn compact_form_session_expires_alias_x() {
1272        let msg: SipMessage = concat!(
1273            "INVITE sip:bob@restsend.com SIP/2.0\r\n",
1274            "Via: SIP/2.0/UDP ua.restsend.com;branch=z9hG4bKtest\r\n",
1275            "From: <sip:alice@restsend.com>;tag=abc\r\n",
1276            "To: <sip:bob@restsend.com>\r\n",
1277            "Call-ID: se-compact@host\r\n",
1278            "CSeq: 1 INVITE\r\n",
1279            "x: 1800\r\n",
1280            "Content-Length: 0\r\n",
1281            "\r\n"
1282        )
1283        .try_into()
1284        .unwrap();
1285        assert!(msg.session_expires_header().is_some());
1286        assert_eq!(msg.session_expires_header().unwrap().value(), "1800");
1287    }
1288}