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}