freeswitch_sofia_trace_parser/sip/
fragment.rs1use std::borrow::Cow;
2
3use crate::finders::CRLF;
4use crate::frame::ParseError;
5use crate::sip::{parse_headers, split_headers_body, HasHeaders};
6use crate::startline::{is_header_line, parse_first_line};
7use crate::types::{MimePart, SipFragment};
8
9pub fn parse_sipfrag(data: &[u8]) -> Result<SipFragment, ParseError> {
16 if data.is_empty() {
17 return Err(ParseError::InvalidMessage("empty sipfrag".into()));
18 }
19
20 let first_line_end = CRLF.find(data).unwrap_or(data.len());
21 let mut first_line = &data[..first_line_end];
22 if let [rest @ .., b'\n'] = first_line {
25 first_line = rest;
26 }
27 if let [rest @ .., b'\r'] = first_line {
28 first_line = rest;
29 }
30
31 let (message_type, headers_start) = match parse_first_line(first_line) {
32 Ok(mt) => (Some(mt), (first_line_end + 2).min(data.len())),
33 Err(e) => {
34 if !is_header_line(first_line) {
35 return Err(e);
36 }
37 (None, 0)
38 }
39 };
40
41 let (header_bytes, body) = split_headers_body(data, headers_start);
42
43 Ok(SipFragment {
44 message_type,
45 headers: parse_headers(header_bytes),
46 body: body.to_vec(),
47 })
48}
49
50impl SipFragment {
51 pub fn media_type(&self) -> Option<Cow<'_, str>> {
55 HasHeaders::media_type(self)
56 }
57}
58
59impl MimePart {
60 pub fn parse_sipfrag(&self) -> Result<SipFragment, ParseError> {
65 parse_sipfrag(&self.body)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::sip::test_support::make_multipart_invite;
73 use crate::types::SipMessageType;
74
75 #[test]
76 fn sipfrag_status_line_with_crlf() {
77 let frag = parse_sipfrag(b"SIP/2.0 200 OK\r\n").unwrap();
78 assert_eq!(
79 frag.message_type,
80 Some(SipMessageType::Response {
81 code: 200,
82 reason: "OK".into()
83 })
84 );
85 assert!(frag.headers.is_empty());
86 assert!(frag.body.is_empty());
87 }
88
89 #[test]
90 fn sipfrag_status_line_without_trailing_crlf() {
91 let frag = parse_sipfrag(b"SIP/2.0 183 Session Progress").unwrap();
92 assert_eq!(
93 frag.message_type,
94 Some(SipMessageType::Response {
95 code: 183,
96 reason: "Session Progress".into()
97 })
98 );
99 }
100
101 #[test]
102 fn sipfrag_headers_only() {
103 let frag = parse_sipfrag(b"To: <sip:user@host>\r\nCSeq: 1 INVITE\r\n").unwrap();
104 assert_eq!(frag.message_type, None);
105 assert_eq!(frag.headers.len(), 2);
106 assert_eq!(frag.headers[0].0, "To");
107 assert_eq!(frag.headers[0].1, "<sip:user@host>");
108 assert_eq!(frag.headers[1].1, "1 INVITE");
109 }
110
111 #[test]
112 fn sipfrag_header_value_is_case_insensitive() {
113 let frag = parse_sipfrag(b"To: <sip:user@host>\r\nCSeq: 1 INVITE\r\n").unwrap();
114 assert_eq!(frag.header_value("cseq"), Some("1 INVITE"));
115 assert_eq!(frag.header_value("To"), Some("<sip:user@host>"));
116 assert_eq!(frag.header_value("Call-ID"), None);
117 }
118
119 #[test]
120 fn sipfrag_headers_only_without_trailing_crlf() {
121 let frag = parse_sipfrag(b"To: <sip:user@host>").unwrap();
122 assert_eq!(frag.message_type, None);
123 assert_eq!(frag.headers.len(), 1);
124 assert_eq!(frag.headers[0].1, "<sip:user@host>");
125 }
126
127 #[test]
128 fn sipfrag_start_line_headers_and_body() {
129 let data = b"SIP/2.0 200 OK\r\n\
130 Content-Type: application/sdp\r\n\
131 \r\n\
132 v=0\r\n";
133 let frag = parse_sipfrag(data).unwrap();
134 assert_eq!(
135 frag.message_type,
136 Some(SipMessageType::Response {
137 code: 200,
138 reason: "OK".into()
139 })
140 );
141 assert_eq!(frag.headers.len(), 1);
142 assert_eq!(frag.body, b"v=0\r\n");
143 }
144
145 #[test]
146 fn sipfrag_request_start_line() {
147 let frag = parse_sipfrag(b"INVITE sip:user@host SIP/2.0\r\nCSeq: 2 INVITE\r\n").unwrap();
148 assert_eq!(
149 frag.message_type,
150 Some(SipMessageType::Request {
151 method: "INVITE".into(),
152 uri: "sip:user@host".into()
153 })
154 );
155 assert_eq!(frag.headers.len(), 1);
156 }
157
158 #[test]
159 fn sipfrag_garbage_is_error() {
160 assert!(parse_sipfrag(b"just some text without a colon").is_err());
161 assert!(parse_sipfrag(b"").is_err());
162 }
163
164 #[test]
165 fn sipfrag_content_type_and_media_type() {
166 let frag = parse_sipfrag(
167 b"SIP/2.0 200 OK\r\nContent-Type: Application/SDP; charset=utf-8\r\n\r\nv=0",
168 )
169 .unwrap();
170 assert_eq!(frag.content_type(), Some("Application/SDP; charset=utf-8"));
171 assert_eq!(frag.media_type().as_deref(), Some("application/sdp"));
172
173 let compact = parse_sipfrag(b"SIP/2.0 200 OK\r\nc: text/plain\r\n\r\nhi").unwrap();
174 assert_eq!(compact.content_type(), Some("text/plain"));
175 }
176
177 #[test]
178 fn sipfrag_malformed_start_line_with_colon_is_error() {
179 assert!(parse_sipfrag(b"INVITE sip:host SIP/1.0\r\n").is_err());
180 }
181
182 #[test]
183 fn sipfrag_lf_only_status_line() {
184 let frag = parse_sipfrag(b"SIP/2.0 200 OK\n").unwrap();
185 assert!(matches!(
186 frag.message_type,
187 Some(SipMessageType::Response { code: 200, ref reason }) if reason == "OK"
188 ));
189 assert!(frag.headers.is_empty());
190 assert!(frag.body.is_empty());
191 }
192
193 #[test]
194 fn sipfrag_from_mime_part() {
195 let body = b"SIP/2.0 100 Trying\r\n";
196 let msg = make_multipart_invite("frag-boundary", &[("message/sipfrag", body)]);
197 let parsed = msg.parse().unwrap();
198 let parts = parsed.body_as_parts();
199 assert_eq!(parts[0].media_type().as_deref(), Some("message/sipfrag"));
200
201 let frag = parts[0].parse_sipfrag().unwrap();
202 assert_eq!(
203 frag.message_type,
204 Some(SipMessageType::Response {
205 code: 100,
206 reason: "Trying".into()
207 })
208 );
209 }
210}