Skip to main content

ezk_sip_types/
msg.rs

1//! Contains SIP message parts and parser
2
3use crate::Name;
4use crate::code::StatusCode;
5use crate::method::Method;
6use crate::parse::{Parse, token, whitespace};
7use crate::print::{AppendCtx, Print, PrintCtx};
8use crate::uri::SipUri;
9use anyhow::Result;
10use bytes::Bytes;
11use bytesstr::BytesStr;
12use internal::IResult;
13use internal::ws;
14use memchr::memchr2;
15use nom::AsChar;
16use nom::branch::alt;
17use nom::bytes::complete::{tag, take_while};
18use nom::character::complete::char;
19use nom::combinator::{map, map_res, opt};
20use nom::sequence::{preceded, separated_pair, terminated, tuple};
21use std::fmt;
22use std::str::FromStr;
23
24fn not_newline(c: char) -> bool {
25    !matches!(c, '\n' | '\r')
26}
27
28/// Represents a header `header-name: header-value` line inside a message
29///
30/// When using [`PullParser`] to extract lines from a SIP message this type should be used to
31/// parse the [`Name`] and remaining value from it.
32///
33/// # Example
34///
35/// ```rust
36/// use ezk_sip_types::msg::{PullParser, Line};
37/// use ezk_sip_types::Name;
38/// use bytes::Bytes;
39///
40/// let msg = Bytes::from_static( b"REGISTER sips:ss2.biloxi.example.com SIP/2.0
41/// Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7
42/// Max-Forwards: 70
43/// From: Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl
44/// To: Bob <sips:bob@biloxi.example.com>
45/// Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
46/// CSeq: 1 REGISTER
47/// Contact: <sips:bob@client.biloxi.example.com>
48/// Content-Length: 0
49///
50/// ");
51///
52/// let mut parser = PullParser::new(&msg, 0);
53///
54/// // skip the first line
55/// parser.next().unwrap();
56///
57/// let via_line = parser.next().unwrap().unwrap();
58///
59/// match Line::parse(&msg, std::str::from_utf8(via_line).unwrap()) {
60///     Ok((_, line)) => {
61///         assert_eq!(line.name, Name::VIA);
62///         assert_eq!(line.value, "SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7")
63///     }
64///     Err(e) => panic!("{:?}", e)
65/// }
66/// ```
67pub struct Line {
68    pub name: Name,
69    pub value: BytesStr,
70}
71
72impl Line {
73    pub fn parse<'i>(src: &Bytes, i: &'i str) -> IResult<&'i str, Self> {
74        map(
75            ws((take_while(token), char(':'), |i| Ok(("", i)))),
76            |(name, _, value)| Line {
77                name: BytesStr::from_parse(src, name).into(),
78                value: BytesStr::from_parse(src, value),
79            },
80        )(i)
81    }
82}
83
84/// The leading line of any SIP message
85#[derive(Debug, Clone)]
86pub enum MessageLine {
87    Request(RequestLine),
88    Response(StatusLine),
89}
90
91impl MessageLine {
92    pub fn is_request(&self) -> bool {
93        matches!(self, Self::Request(..))
94    }
95
96    pub fn request_method(&self) -> Option<&Method> {
97        match self {
98            MessageLine::Request(line) => Some(&line.method),
99            MessageLine::Response(_) => None,
100        }
101    }
102}
103
104impl Parse for MessageLine {
105    fn parse(src: &Bytes) -> impl Fn(&str) -> IResult<&str, Self> + '_ {
106        move |i| {
107            alt((
108                map(StatusLine::parse(src), MessageLine::Response),
109                map(RequestLine::parse(src), MessageLine::Request),
110            ))(i)
111        }
112    }
113}
114impl_from_str!(MessageLine);
115
116impl Print for MessageLine {
117    fn print(&self, f: &mut fmt::Formatter<'_>, ctx: PrintCtx<'_>) -> fmt::Result {
118        use std::fmt::Display;
119
120        match &self {
121            MessageLine::Request(l) => l.print(f, ctx),
122            MessageLine::Response(l) => l.fmt(f),
123        }
124    }
125}
126
127/// The leading line of a SIP request message
128#[derive(Debug, Clone)]
129pub struct RequestLine {
130    pub method: Method,
131    pub uri: SipUri,
132}
133
134impl Print for RequestLine {
135    fn print(&self, f: &mut fmt::Formatter<'_>, ctx: PrintCtx<'_>) -> fmt::Result {
136        write!(f, "{} {} SIP/2.0", self.method, self.uri.print_ctx(ctx))
137    }
138}
139
140impl Parse for RequestLine {
141    fn parse(src: &Bytes) -> impl Fn(&str) -> IResult<&str, Self> + '_ {
142        move |i| {
143            map(
144                separated_pair(
145                    Method::parse(src),
146                    take_while(whitespace),
147                    terminated(
148                        SipUri::parse(src),
149                        tuple((take_while(whitespace), tag("SIP/2.0"))),
150                    ),
151                ),
152                |(method, uri)| RequestLine { method, uri },
153            )(i)
154        }
155    }
156}
157impl_from_str!(RequestLine);
158
159/// The leading line of a SIP response message
160#[derive(Debug, Clone)]
161pub struct StatusLine {
162    pub code: StatusCode,
163    pub reason: Option<BytesStr>,
164}
165
166impl fmt::Display for StatusLine {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        write!(f, "SIP/2.0 {}", self.code.into_u16())?;
169
170        if let Some(reason) = &self.reason {
171            write!(f, " {reason}")?;
172        }
173
174        Ok(())
175    }
176}
177
178impl Parse for StatusLine {
179    fn parse(src: &Bytes) -> impl Fn(&str) -> IResult<&str, Self> + '_ {
180        move |i| {
181            map(
182                preceded(
183                    tuple((tag("SIP/2.0"), take_while(whitespace))),
184                    tuple((
185                        map_res(take_while(char::is_dec_digit), u16::from_str),
186                        take_while(whitespace),
187                        opt(take_while(not_newline)),
188                    )),
189                ),
190                move |(code, _, reason): (_, _, Option<&str>)| -> StatusLine {
191                    StatusLine {
192                        code: StatusCode::from(code),
193                        reason: reason.and_then(|reason| match reason.trim() {
194                            "" => None,
195                            s => Some(BytesStr::from_parse(src, s)),
196                        }),
197                    }
198                },
199            )(i)
200        }
201    }
202}
203impl_from_str!(StatusLine);
204
205/// Simple pull parser which returns all lines in a SIP message.
206///
207/// > __Note:__ Lines are terminated with either `\n` or `\r\n` followed by anything but a whitespace.
208///
209/// This is a SIP message feature allowing multi-line headers.
210///
211/// # Examples
212///
213/// ```
214/// use ezk_sip_types::msg::PullParser;
215///
216/// // message taken from torture message rfc
217/// let msg = b"OPTIONS sip:user;par=u%40example.net@example.com SIP/2.0
218/// To: sip:j_user@example.com
219/// From: sip:caller@example.org;tag=33242
220/// Max-Forwards: 3
221/// Call-ID: semiuri.0ha0isndaksdj
222/// CSeq: 8 OPTIONS
223/// Accept: application/sdp, application/pkcs7-mime,
224///         multipart/mixed, multipart/signed,
225///         message/sip, message/sipfrag
226/// Via: SIP/2.0/UDP 192.0.2.1;branch=z9hG4bKkdjuw
227/// l: 0
228///
229/// ";
230///
231/// let mut parser = PullParser::new(msg, 0);
232///
233/// assert_eq!(parser.next(), Some(Ok(&b"OPTIONS sip:user;par=u%40example.net@example.com SIP/2.0"[..])));
234/// assert_eq!(parser.next(), Some(Ok(&b"To: sip:j_user@example.com"[..])));
235/// assert_eq!(parser.next(), Some(Ok(&b"From: sip:caller@example.org;tag=33242"[..])));
236/// assert_eq!(parser.next(), Some(Ok(&b"Max-Forwards: 3"[..])));
237/// assert_eq!(parser.next(), Some(Ok(&b"Call-ID: semiuri.0ha0isndaksdj"[..])));
238/// assert_eq!(parser.next(), Some(Ok(&b"CSeq: 8 OPTIONS"[..])));
239/// assert_eq!(parser.next(), Some(Ok(&b"Accept: application/sdp, application/pkcs7-mime,\n        multipart/mixed, multipart/signed,\n        message/sip, message/sipfrag"[..])));
240/// assert_eq!(parser.next(), Some(Ok(&b"Via: SIP/2.0/UDP 192.0.2.1;branch=z9hG4bKkdjuw"[..])));
241/// assert_eq!(parser.next(), Some(Ok(&b"l: 0"[..])));
242/// assert_eq!(parser.next(), None);
243/// ```
244///
245/// The parser can also be used to detect incomplete messages. Note that this parser only detects
246/// if a SIP message __head__ is incomplete. To detect incomplete message bodies you need to parse
247/// the content-length header and go from there.
248///
249/// ```
250/// use ezk_sip_types::msg::PullParser;
251///
252/// // message taken from torture message rfc and randomly cut off
253/// let msg = b"OPTIONS sip:user@example.com SIP/2.0
254/// To: sip:user@example.com
255/// From: caller<si";
256///
257/// let mut parser = PullParser::new(msg, 0);
258///
259/// assert_eq!(parser.next(), Some(Ok(&b"OPTIONS sip:user@example.com SIP/2.0"[..])));
260/// assert_eq!(parser.next(), Some(Ok(&b"To: sip:user@example.com"[..])));
261/// // since the parser cannot find a new line and didnt detect a message-head end yet
262/// // it will return an error
263/// assert!(parser.next().unwrap().is_err());
264/// ```
265#[derive(Clone)]
266pub struct PullParser<'i> {
267    input: &'i [u8],
268    progress: usize,
269}
270
271/// semi-error type that just signals that the input is incomplete
272#[derive(Debug, PartialEq, Eq)]
273pub struct Incomplete(());
274
275impl<'i> PullParser<'i> {
276    /// Returns a new PullParser with input and progress
277    pub fn new(input: &'i [u8], progress: usize) -> Self {
278        Self { input, progress }
279    }
280
281    /// Returns the index of the last character of the message-head inside the slice
282    /// only valid after parser returned None
283    pub fn head_end(&self) -> usize {
284        match self.input[self.progress..] {
285            [b'\r', b'\n', b'\r', b'\n', ..] => self.progress + 4,
286            [b'\n', b'\n', ..] => self.progress + 2,
287            _ => self.progress,
288        }
289    }
290
291    /// Returns the current progress.
292    ///
293    /// Saving the parser progress when encountering a incomplete message inside a streaming
294    /// transport might be useful. It avoids having to parse the same lines multiple times.
295    pub fn progress(&self) -> usize {
296        self.progress
297    }
298
299    /// Perform a dry run of the parser to check if the input is incomplete
300    pub fn check_complete(&mut self) -> Result<(), Incomplete> {
301        for res in self {
302            let _ = res?;
303        }
304
305        Ok(())
306    }
307}
308
309impl<'i> Iterator for PullParser<'i> {
310    type Item = Result<&'i [u8], Incomplete>;
311
312    fn next(&mut self) -> Option<Self::Item> {
313        let line_begin = self.progress;
314
315        let mut skip = 0;
316
317        loop {
318            let progress = match memchr2(b'\n', b'\r', &self.input[line_begin + skip..]) {
319                None => return Some(Err(Incomplete(()))),
320                Some(progress) => progress,
321            };
322
323            let pos = progress + line_begin + skip;
324
325            match self.input[pos..] {
326                [b'\n', b' ' | b'\t', ..] | [b'\r', b'\n', b' ' | b'\t', ..] => {
327                    // whitespace after newline means its not a new line
328                    skip += progress + 1;
329                }
330                [b'\n', b, ..] => {
331                    let slice = &self.input[line_begin..pos];
332
333                    if slice.is_empty() {
334                        return None;
335                    }
336
337                    if b == b'\n' {
338                        self.progress = pos;
339                    } else {
340                        self.progress = pos + 1;
341                    }
342
343                    return Some(Ok(slice));
344                }
345                [b'\r', b'\n', b1, b2, ..] => {
346                    let slice = &self.input[line_begin..pos];
347
348                    if slice.is_empty() {
349                        return None;
350                    }
351
352                    if b1 == b'\r' && b2 == b'\n' {
353                        self.progress = pos;
354                    } else {
355                        self.progress = pos + 2;
356                    }
357
358                    return Some(Ok(slice));
359                }
360                _ => {
361                    // this means there is a missing char after newline,
362                    // since this is required the message is incomplete
363                    return Some(Err(Incomplete(())));
364                }
365            }
366        }
367    }
368}