• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 23374838686

21 Mar 2026 07:29AM UTC coverage: 59.341% (-20.0%) from 79.315%
23374838686

Pull #15075

github

web-flow
Merge 90b4e834f into 6587e363a
Pull Request #15075: Stack 8001 v16.4

38 of 70 new or added lines in 10 files covered. (54.29%)

34165 existing lines in 563 files now uncovered.

119621 of 201584 relevant lines covered (59.34%)

650666.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.62
/rust/src/sdp/parser.rs
1
/* Copyright (C) 2024 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
// written by Giuseppe Longo <giuseppe@glongo.it>
19

20
use nom8::{
21
    branch::alt,
22
    bytes::complete::{tag, take_till, take_while, take_while_m_n},
23
    character::complete::{char as char_parser, digit1, line_ending, space1, u8 as take_u8},
24
    combinator::map_res,
25
    combinator::{opt, peek, verify},
26
    error::{make_error, ErrorKind},
27
    multi::{many0, many1},
28
    number::complete::be_u8,
29
    sequence::preceded,
30
    {AsChar, Err, IResult, Parser},
31
};
32

33
use std::net::IpAddr;
34
use std::str::FromStr;
35

36
#[derive(Debug)]
37
pub struct SdpMessage {
38
    pub version: u32,
39
    pub origin: String,
40
    pub session_name: String,
41
    pub session_info: Option<String>,
42
    pub uri: Option<String>,
43
    pub email: Option<String>,
44
    pub phone_number: Option<String>,
45
    pub connection_data: Option<String>,
46
    pub bandwidths: Option<Vec<String>>,
47
    pub time_description: Vec<TimeDescription>,
48
    pub time_zone: Option<String>,
49
    pub encryption_key: Option<String>,
50
    pub attributes: Option<Vec<String>>,
51
    pub media_description: Option<Vec<MediaDescription>>,
52
}
53

54
#[derive(Debug)]
55
pub struct MediaDescription {
56
    pub media: String,
57
    pub session_info: Option<String>,
58
    pub connection_data: Option<String>,
59
    pub bandwidths: Option<Vec<String>>,
60
    pub encryption_key: Option<String>,
61
    pub attributes: Option<Vec<String>>,
62
}
63

64
#[derive(Debug)]
65
pub struct TimeDescription {
66
    pub time: String,
67
    pub repeat_time: Option<String>,
68
}
69

70
// token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
71
#[inline]
72
fn is_token_char(b: u8) -> bool {
5,626✔
73
    matches!(b, 0x21 | 0x2A | 0x2B | 0x2D | 0x2E)
5,626✔
74
        || (0x23..=0x27).contains(&b)
5,159✔
75
        || (0x30..=0x39).contains(&b)
5,158✔
76
        || (0x41..=0x5a).contains(&b)
5,079✔
77
        || (0x5e..=0x7e).contains(&b)
2,337✔
78
}
5,626✔
79

80
#[inline]
81
fn is_request_uri_char(b: u8) -> bool {
4,270✔
82
    b.is_alphanum() || is_token_char(b) || b"~#@:;=?+&$,/".contains(&b)
4,270✔
83
}
4,270✔
84

85
#[inline]
86
fn is_line_ending(b: u8) -> bool {
39,087✔
87
    b == b'\r' || b == b'\n'
39,087✔
88
}
39,087✔
89

90
#[inline]
91
fn is_ipaddr_char(b: u8) -> bool {
5,870✔
92
    b.is_ascii_hexdigit() || b".:".contains(&b)
5,870✔
93
}
5,870✔
94

95
#[inline]
96
fn is_session_name_char(b: u8) -> bool {
10,601✔
97
    b.is_alphanum() || b.is_space()
10,601✔
98
}
10,601✔
99

100
#[inline]
101
fn is_time_char(b: u8) -> bool {
3,906✔
102
    b.is_dec_digit() || b"dhms-".contains(&b)
3,906✔
103
}
3,906✔
104

UNCOV
105
fn parse_num(i: &[u8]) -> IResult<&[u8], u8> {
×
UNCOV
106
    let (i, num) = preceded(verify(peek(be_u8), |d| *d != 0x30), take_u8).parse(i)?;
×
UNCOV
107
    Ok((i, num))
×
UNCOV
108
}
×
109

110
// SDP Message format (fields marked with * are optional):
111
// https://www.rfc-editor.org/rfc/rfc4566#page-9
112
//
113
//       Session description
114
//         v=  (protocol version)
115
//         o=  (originator and session identifier)
116
//         s=  (session name)
117
//         i=* (session information)
118
//         u=* (URI of description)
119
//         e=* (email address)
120
//         p=* (phone number)
121
//         c=* (connection information -- not required if included in
122
//              all media)
123
//         b=* (zero or more bandwidth information lines)
124
//         One or more time descriptions ("t=" and "r=" lines; see below)
125
//         z=* (time zone adjustments)
126
//         k=* (encryption key)
127
//         a=* (zero or more session attribute lines)
128
//         Zero or more media descriptions
129
//
130
//      Time description
131
//         t=  (time the session is active)
132
//         r=* (zero or more repeat times)
133
//
134
//      Media description, if present
135
//         m=  (media name and transport address)
136
//         i=* (media title)
137
//         c=* (connection information -- optional if included at
138
//              session level)
139
//         b=* (zero or more bandwidth information lines)
140
//         k=* (encryption key)
141
//         a=* (zero or more media attribute lines)
142

143
pub fn sdp_parse_message(i: &[u8]) -> IResult<&[u8], SdpMessage> {
3,081✔
144
    let (i, version) = parse_version_line(i)?;
3,081✔
145
    let (i, origin) = parse_origin_line(i)?;
635✔
146
    let (i, session_name) = parse_session_name(i)?;
560✔
147
    let (i, session_info) = opt(parse_session_info).parse(i)?;
530✔
148
    let (i, uri) = opt(parse_uri).parse(i)?;
530✔
149
    let (i, email) = opt(parse_email).parse(i)?;
530✔
150
    let (i, phone_number) = opt(parse_phone_number).parse(i)?;
530✔
151
    let (i, connection_data) = opt(parse_connection_data).parse(i)?;
530✔
152
    let (i, bandwidths) = opt(parse_bandwidth).parse(i)?;
530✔
153
    let (i, time_description) = many1(parse_time_description).parse(i)?;
530✔
154
    let (i, time_zone) = opt(parse_time_zone).parse(i)?;
441✔
155
    let (i, encryption_key) = opt(parse_encryption_key).parse(i)?;
441✔
156
    let (i, attributes) = opt(parse_attributes).parse(i)?;
441✔
157
    let (i, media_description) = opt(many0(parse_media_description)).parse(i)?;
441✔
158
    Ok((
441✔
159
        i,
441✔
160
        SdpMessage {
441✔
161
            version,
441✔
162
            origin,
441✔
163
            session_name,
441✔
164
            session_info,
441✔
165
            uri,
441✔
166
            email,
441✔
167
            phone_number,
441✔
168
            connection_data,
441✔
169
            bandwidths,
441✔
170
            time_description,
441✔
171
            time_zone,
441✔
172
            encryption_key,
441✔
173
            attributes,
441✔
174
            media_description,
441✔
175
        },
441✔
176
    ))
441✔
177
}
3,081✔
178

179
fn parse_version_line(i: &[u8]) -> IResult<&[u8], u32> {
3,081✔
180
    let (i, _) = tag("v=").parse(i)?;
3,081✔
181
    let (i, _v) = tag("0").parse(i)?;
643✔
182
    let (i, _) = line_ending.parse(i)?;
640✔
183

184
    Ok((i, 0))
635✔
185
}
3,081✔
186

187
fn parse_origin_line(i: &[u8]) -> IResult<&[u8], String> {
635✔
188
    let (i, _) = tag("o=").parse(i)?;
635✔
189
    let (i, username) = map_res(take_while(is_token_char), std::str::from_utf8).parse(i)?;
628✔
190
    let (i, _) = space1.parse(i)?;
628✔
191
    let (i, sess_id) = map_res(take_while(|c: u8| c.is_dec_digit()), std::str::from_utf8).parse(i)?;
5,567✔
192
    let (i, _) = space1.parse(i)?;
609✔
193
    let (i, sess_version) = map_res(take_while(|c: u8| c.is_dec_digit()), std::str::from_utf8).parse(i)?;
5,456✔
194
    let (i, _) = space1.parse(i)?;
590✔
195
    let (i, nettype) = map_res(take_while(|c: u8| c.is_alpha()), std::str::from_utf8).parse(i)?;
2,173✔
196
    let (i, _) = space1.parse(i)?;
576✔
197
    let (i, addrtype) = map_res(take_while(|c: u8| c.is_alphanum()), std::str::from_utf8).parse(i)?;
2,316✔
198
    let (i, _) = space1.parse(i)?;
571✔
199
    let (i, unicast_address) = map_res(take_till(is_line_ending), std::str::from_utf8).parse(i)?;
565✔
200
    let (i, _) = line_ending.parse(i)?;
564✔
201

202
    let origin_line = format!(
560✔
203
        "{} {} {} {} {} {}",
560✔
204
        username, sess_id, sess_version, nettype, addrtype, unicast_address
560✔
205
    );
560✔
206

560✔
207
    Ok((i, origin_line))
560✔
208
}
635✔
209

210
fn parse_session_name(i: &[u8]) -> IResult<&[u8], String> {
560✔
211
    let (i, _) = tag("s=").parse(i)?;
560✔
212
    let (i, name) = map_res(take_while(is_session_name_char), std::str::from_utf8).parse(i)?;
554✔
213
    let (i, _) = line_ending.parse(i)?;
554✔
214
    Ok((i, name.to_string()))
530✔
215
}
560✔
216

217
fn parse_session_info(i: &[u8]) -> IResult<&[u8], String> {
880✔
218
    let (i, _) = tag("i=").parse(i)?;
880✔
219
    let (i, info) = map_res(take_while(is_session_name_char), std::str::from_utf8).parse(i)?;
185✔
220
    let (i, _) = line_ending.parse(i)?;
185✔
221
    Ok((i, info.to_string()))
181✔
222
}
880✔
223

224
fn parse_uri(i: &[u8]) -> IResult<&[u8], String> {
530✔
225
    let (i, _) = tag("u=").parse(i)?;
530✔
226
    let (i, uri) = map_res(take_while(is_request_uri_char), std::str::from_utf8).parse(i)?;
162✔
227
    let (i, _) = line_ending.parse(i)?;
162✔
228
    Ok((i, uri.to_string()))
143✔
229
}
530✔
230

231
fn parse_connection_data(i: &[u8]) -> IResult<&[u8], String> {
880✔
232
    let (i, _) = tag("c=").parse(i)?;
880✔
233
    let (i, nettype) = map_res(take_while(|c: u8| c.is_alpha()), std::str::from_utf8).parse(i)?;
1,495✔
234
    let (i, _) = space1.parse(i)?;
497✔
235
    let (i, addrtype) = map_res(take_while(|c: u8| c.is_alphanum()), std::str::from_utf8).parse(i)?;
1,983✔
236
    let (i, _) = space1.parse(i)?;
494✔
237
    let (i, connection_address) = map_res(
489✔
238
        map_res(take_while(is_ipaddr_char), std::str::from_utf8),
489✔
239
        IpAddr::from_str,
489✔
240
    ).parse(i)?;
489✔
241
    let (i, first_num) = opt(preceded(char_parser('/'), parse_num)).parse(i)?;
480✔
242
    let (i, second_num) = opt(preceded(char_parser('/'), parse_num)).parse(i)?;
480✔
243
    let (i, _) = line_ending.parse(i)?;
480✔
244

245
    let (ttl, number_of_addresses) = match connection_address {
479✔
246
        _ if connection_address.is_ipv6() => (None, first_num),
479✔
247
        _ if connection_address.is_ipv4() && connection_address.is_multicast() => {
479✔
248
            match (first_num, second_num) {
6✔
249
                (None, _) => return Err(Err::Error(make_error(i, ErrorKind::HexDigit))),
6✔
UNCOV
250
                _ => (first_num, second_num),
×
251
            }
252
        }
253
        _ if connection_address.is_ipv4() => match (first_num, second_num) {
473✔
254
            (Some(_), None) => (None, first_num),
×
255
            _ => (first_num, second_num),
473✔
256
        },
257
        _ => (None, None),
×
258
    };
259

260
    let mut connection_data = format!(
473✔
261
        "{} {} {}",
473✔
262
        &nettype,
473✔
263
        &addrtype,
473✔
264
        &connection_address.to_string()
473✔
265
    );
473✔
266
    if let Some(ttl) = ttl {
473✔
UNCOV
267
        connection_data = format!("{}/{}", connection_data, ttl);
×
268
    }
473✔
269
    if let Some(num_addrs) = number_of_addresses {
473✔
UNCOV
270
        connection_data = format!("{}/{}", connection_data, num_addrs);
×
271
    }
473✔
272

273
    Ok((i, connection_data))
473✔
274
}
880✔
275

276
fn parse_email(i: &[u8]) -> IResult<&[u8], String> {
530✔
277
    let (i, email) = preceded(
530✔
278
        tag("e="),
530✔
279
        map_res(take_till(is_line_ending), std::str::from_utf8),
530✔
280
    ).parse(i)?;
530✔
281
    let (i, _) = line_ending.parse(i)?;
138✔
282
    Ok((i, email.to_string()))
136✔
283
}
530✔
284

285
fn parse_phone_number(i: &[u8]) -> IResult<&[u8], String> {
530✔
286
    let (i, phone_number) = preceded(
530✔
287
        tag("p="),
530✔
288
        map_res(take_till(is_line_ending), std::str::from_utf8),
530✔
289
    ).parse(i)?;
530✔
290
    let (i, _) = line_ending.parse(i)?;
128✔
291
    Ok((i, phone_number.to_string()))
125✔
292
}
530✔
293

294
fn parse_bandwidth(i: &[u8]) -> IResult<&[u8], Vec<String>> {
880✔
295
    let (i, bws) = many0(preceded(
880✔
296
        tag("b="),
880✔
297
        (
880✔
298
            map_res(
880✔
299
                alt((tag("CT"), tag("AS"), tag("TIAS"))),
880✔
300
                std::str::from_utf8,
880✔
301
            ),
880✔
302
            char_parser(':'),
880✔
303
            map_res(digit1, std::str::from_utf8),
880✔
304
            line_ending,
880✔
305
        ),
880✔
306
    )).parse(i)?;
880✔
307
    let vec = bws.iter().map(|bw| format!("{}:{}", bw.0, bw.2)).collect();
880✔
308
    Ok((i, vec))
880✔
309
}
880✔
310

311
fn parse_time_description(i: &[u8]) -> IResult<&[u8], TimeDescription> {
971✔
312
    let (i, time) = parse_time(i)?;
971✔
313
    let (i, repeat_time) = opt(parse_repeat_times).parse(i)?;
441✔
314
    Ok((i, TimeDescription { time, repeat_time }))
441✔
315
}
971✔
316

317
fn parse_time(i: &[u8]) -> IResult<&[u8], String> {
971✔
318
    let (i, (start_time, _, stop_time)) = preceded(
971✔
319
        tag("t="),
971✔
320
        (
971✔
321
            map_res(digit1, std::str::from_utf8),
971✔
322
            space1,
971✔
323
            map_res(digit1, std::str::from_utf8),
971✔
324
        ),
971✔
325
    ).parse(i)?;
971✔
326
    let (i, _) = line_ending.parse(i)?;
443✔
327
    let time = format!("{} {}", start_time, stop_time);
441✔
328
    Ok((i, time))
441✔
329
}
971✔
330

331
fn parse_repeat_times(i: &[u8]) -> IResult<&[u8], String> {
441✔
332
    let (i, (d, _, h, _, m, _, s)) = preceded(
441✔
333
        tag("r="),
441✔
334
        (
441✔
335
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
336
            space1,
441✔
337
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
338
            space1,
441✔
339
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
340
            space1,
441✔
341
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
342
        ),
441✔
343
    ).parse(i)?;
441✔
344
    let (i, _) = line_ending.parse(i)?;
85✔
345
    let val = format!("{} {} {} {}", d, h, m, s);
83✔
346
    Ok((i, val.to_string()))
83✔
347
}
441✔
348

349
fn parse_time_zone(i: &[u8]) -> IResult<&[u8], String> {
441✔
350
    let (i, (z1, _, z2, _, z3, _, z4)) = preceded(
441✔
351
        tag("z="),
441✔
352
        (
441✔
353
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
354
            space1,
441✔
355
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
356
            space1,
441✔
357
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
358
            space1,
441✔
359
            map_res(take_while(is_time_char), std::str::from_utf8),
441✔
360
        ),
441✔
361
    ).parse(i)?;
441✔
362
    let (i, _) = line_ending.parse(i)?;
68✔
363
    let tz = format!("{} {} {} {}", z1, z2, z3, z4);
64✔
364
    Ok((i, tz.to_string()))
64✔
365
}
441✔
366

367
fn parse_encryption_key(i: &[u8]) -> IResult<&[u8], String> {
791✔
368
    let (i, key) = preceded(
791✔
369
        tag("k="),
791✔
370
        map_res(take_till(is_line_ending), std::str::from_utf8),
791✔
371
    ).parse(i)?;
791✔
372
    let (i, _) = line_ending.parse(i)?;
80✔
373
    Ok((i, key.to_string()))
79✔
374
}
791✔
375

376
fn parse_attributes(i: &[u8]) -> IResult<&[u8], Vec<String>> {
791✔
377
    let (i, attrs) = many0(preceded(
791✔
378
        tag("a="),
791✔
379
        (
791✔
380
            map_res(take_while(|c: u8| c.is_alpha()), std::str::from_utf8),
14,834✔
381
            opt(preceded(
791✔
382
                char_parser(':'),
791✔
383
                map_res(take_till(is_line_ending), std::str::from_utf8),
791✔
384
            )),
791✔
385
            line_ending,
791✔
386
        ),
791✔
387
    )).parse(i)?;
791✔
388
    let vec = attrs
791✔
389
        .iter()
791✔
390
        .map(|a| {
2,062✔
391
            if let Some(val) = a.1 {
2,062✔
392
                format!("{}:{}", a.0, val)
1,727✔
393
            } else {
394
                a.0.to_string()
335✔
395
            }
396
        })
2,062✔
397
        .collect();
791✔
398
    Ok((i, vec))
791✔
399
}
791✔
400

401
fn parse_media_description(i: &[u8]) -> IResult<&[u8], MediaDescription> {
790✔
402
    let (i, _) = tag("m=").parse(i)?;
790✔
403
    let (i, media) = map_res(
399✔
404
        alt((
399✔
405
            tag("audio"),
399✔
406
            tag("video"),
399✔
407
            tag("text"),
399✔
408
            tag("application"),
399✔
409
            tag("message"),
399✔
410
        )),
399✔
411
        |bytes: &[u8]| String::from_utf8(bytes.to_vec()),
399✔
412
    ).parse(i)?;
399✔
413
    let (i, _) = space1.parse(i)?;
387✔
414

415
    let (i, port) = map_res(
384✔
416
        take_while_m_n(1, 5, |b: u8| b.is_ascii_digit()),
1,898✔
417
        std::str::from_utf8,
384✔
418
    ).parse(i)?;
384✔
419
    let (i, number_of_ports) = opt(preceded(
381✔
420
        char_parser('/'),
381✔
421
        map_res(
381✔
422
            take_while_m_n(1, 5, |b: u8| b.is_ascii_digit()),
381✔
423
            std::str::from_utf8,
381✔
424
        ),
381✔
425
    )).parse(i)?;
381✔
426
    let (i, _) = space1.parse(i)?;
381✔
427

428
    let (i, proto) = map_res(
376✔
429
        alt((tag("udp"), tag("RTP/AVP"), tag("RTP/SAVP"))),
376✔
430
        |bytes: &[u8]| String::from_utf8(bytes.to_vec()),
376✔
431
    ).parse(i)?;
376✔
432

433
    let (i, fmt) = many1(preceded(
366✔
434
        space1,
366✔
435
        map_res(
366✔
436
            take_while_m_n(1, 255, |b: u8| b.is_ascii_alphanumeric()),
3,809✔
437
            std::str::from_utf8,
366✔
438
        ),
366✔
439
    )).parse(i)?;
366✔
440
    let (i, _) = line_ending.parse(i)?;
363✔
441

442
    let (i, session_info) = opt(parse_session_info).parse(i)?;
350✔
443
    let (i, connection_data) = opt(parse_connection_data).parse(i)?;
350✔
444
    let (i, bandwidths) = opt(parse_bandwidth).parse(i)?;
350✔
445
    let (i, encryption_key) = opt(parse_encryption_key).parse(i)?;
350✔
446
    let (i, attributes) = opt(parse_attributes).parse(i)?;
350✔
447

448
    let port: u16 = match port.parse::<u16>() {
350✔
449
        Ok(p) => p,
349✔
450
        Err(_) => return Err(Err::Error(make_error(i, ErrorKind::HexDigit))),
1✔
451
    };
452
    let number_of_ports: Option<u16> = match number_of_ports {
349✔
453
        Some(num_str) => num_str.parse().ok(),
×
454
        None => None,
349✔
455
    };
456

457
    let port = if let Some(num_ports) = number_of_ports {
349✔
458
        format!("{}/{}", port, num_ports)
×
459
    } else {
460
        format!("{}", port)
349✔
461
    };
462
    let mut media_str = format!("{} {} {}", &media, &port, &proto);
349✔
463
    if !fmt.is_empty() {
349✔
464
        let fmt: Vec<String> = fmt.into_iter().map(String::from).collect();
349✔
465
        media_str = format!("{} {}", media_str, fmt.join(" "));
349✔
466
    }
349✔
467
    Ok((
349✔
468
        i,
349✔
469
        MediaDescription {
349✔
470
            media: media_str,
349✔
471
            session_info,
349✔
472
            connection_data,
349✔
473
            bandwidths,
349✔
474
            encryption_key,
349✔
475
            attributes,
349✔
476
        },
349✔
477
    ))
349✔
478
}
790✔
479

480
#[cfg(test)]
481
mod tests {
482
    use crate::sdp::parser::*;
483

484
    #[test]
485
    fn test_version_line() {
486
        let buf: &[u8] = "v=0\n\r".as_bytes();
487
        let (_, v) = parse_version_line(buf).expect("parsing failed");
488
        assert_eq!(v, 0);
489
    }
490

491
    #[test]
492
    fn test_origin_line() {
493
        let buf: &[u8] = "o=Clarent 120386 120387 IN IP4 200.57.7.196\r\n".as_bytes();
494

495
        let (_, o) = parse_origin_line(buf).expect("parsing failed");
496
        assert_eq!(o, "Clarent 120386 120387 IN IP4 200.57.7.196");
497
    }
498

499
    #[test]
500
    fn test_session_name_line() {
501
        let buf: &[u8] = "s=Clarent C5CM\r\n".as_bytes();
502

503
        let (_, s) = parse_session_name(buf).expect("parsing failed");
504
        assert_eq!(s, "Clarent C5CM");
505
    }
506

507
    #[test]
508
    fn test_session_info_line() {
509
        let buf: &[u8] = "i=Session Description Protocol\r\n".as_bytes();
510

511
        let (_, s) = parse_session_info(buf).expect("parsing failed");
512
        assert_eq!(s, "Session Description Protocol");
513
    }
514

515
    #[test]
516
    fn test_uri_line() {
517
        let buf: &[u8] = "u=https://www.sdp.proto\r\n".as_bytes();
518

519
        let (_, u) = parse_uri(buf).expect("parsing failed");
520
        assert_eq!(u, "https://www.sdp.proto");
521
    }
522

523
    #[test]
524
    fn test_connection_line_1() {
525
        let buf: &[u8] = "c=IN IP4 224.2.36.42/127\r\n".as_bytes();
526

527
        let (_, c) = parse_connection_data(buf).expect("parsing failed");
528
        assert_eq!(c, "IN IP4 224.2.36.42/127");
529
    }
530

531
    #[test]
532
    fn test_connection_line_2() {
533
        let buf: &[u8] = "c=IN IP6 FF15::101/3\r\n".as_bytes();
534

535
        let (_, c) = parse_connection_data(buf).expect("parsing failed");
536
        assert_eq!(c, "IN IP6 ff15::101/3");
537
    }
538

539
    #[test]
540
    fn test_connection_line_3() {
541
        let buf: &[u8] = "c=IN IP4 224.2.36.42/127/2\r\n".as_bytes();
542

543
        let (_, c) = parse_connection_data(buf).expect("parsing failed");
544
        assert_eq!(c, "IN IP4 224.2.36.42/127/2");
545
    }
546

547
    #[test]
548
    fn test_connection_line_4() {
549
        let buf: &[u8] = "c=IN IP4 224.2.36.42\r\n".as_bytes();
550

551
        let result = parse_connection_data(buf);
552
        assert!(result.is_err());
553
    }
554

555
    #[test]
556
    fn test_connection_line_5() {
557
        let buf: &[u8] = "c=IN IP4 8.8.8.8\r\n".as_bytes();
558

559
        let (_, c) = parse_connection_data(buf).expect("parsing failed");
560
        assert_eq!(c, "IN IP4 8.8.8.8");
561
    }
562

563
    #[test]
564
    fn test_connection_line_6() {
565
        let buf: &[u8] = "c=IN IP6 FF15::101\r\n".as_bytes();
566

567
        let (_, c) = parse_connection_data(buf).expect("parsing failed");
568
        assert_eq!(c, "IN IP6 ff15::101");
569
    }
570

571
    #[test]
572
    fn test_email_line() {
573
        let buf: &[u8] = "e=j.doe@example.com (Jane Doe)\r\n".as_bytes();
574

575
        let (_, e) = parse_email(buf).expect("parsing failed");
576
        assert_eq!(e, "j.doe@example.com (Jane Doe)");
577
    }
578

579
    #[test]
580
    fn test_phone_line() {
581
        let buf: &[u8] = "p=+1 617 555-6011 (Jane Doe)\r\n".as_bytes();
582

583
        let (_, p) = parse_phone_number(buf).expect("parsing failed");
584
        assert_eq!(p, "+1 617 555-6011 (Jane Doe)");
585
    }
586

587
    #[test]
588
    fn test_bandwidth_line() {
589
        let buf: &[u8] = "b=AS:64\r\n".as_bytes();
590
        let (_, b) = parse_bandwidth(buf).expect("parsing failed");
591
        assert_eq!(b.first().unwrap(), "AS:64");
592
    }
593

594
    #[test]
595
    fn test_time_line() {
596
        let buf: &[u8] = "t=3034423619 3042462419\r\n".as_bytes();
597
        let (_, t) = parse_time(buf).expect("parsing failed");
598
        assert_eq!(t, "3034423619 3042462419");
599
    }
600

601
    #[test]
602
    fn test_repeat_time_line_1() {
603
        let buf: &[u8] = "r=604800 3600 0 90000\r\n".as_bytes();
604
        let (_, t) = parse_repeat_times(buf).expect("parsing failed");
605
        assert_eq!(t, "604800 3600 0 90000");
606
    }
607

608
    #[test]
609
    fn test_repeat_time_line_2() {
610
        let buf: &[u8] = "r=7d 1h 0 25h\r\n".as_bytes();
611
        let (_, t) = parse_repeat_times(buf).expect("parsing failed");
612
        assert_eq!(t, "7d 1h 0 25h");
613
    }
614

615
    #[test]
616
    fn test_time_zone_line() {
617
        let buf: &[u8] = "z=2882844526 -1h 2898848070 0\r\n".as_bytes();
618
        let (_, t) = parse_time_zone(buf).expect("parsing failed");
619
        assert_eq!(t, "2882844526 -1h 2898848070 0");
620
    }
621

622
    #[test]
623
    fn test_encryption_key_line() {
624
        let buf: &[u8] = "k=prompt\r\n".as_bytes();
625
        let (_, k) = parse_encryption_key(buf).expect("parsing failed");
626
        assert_eq!(k, "prompt");
627
    }
628

629
    #[test]
630
    fn test_attribute_line() {
631
        let buf: &[u8] = "a=sendrecv\r\na=rtpmap:8 PCMA/8000/1\r\n".as_bytes();
632
        let (_, a) = parse_attributes(buf).expect("parsing failed");
633
        assert_eq!(a.first().unwrap(), "sendrecv");
634
        assert_eq!(a.get(1).unwrap(), "rtpmap:8 PCMA/8000/1");
635
    }
636

637
    #[test]
638
    fn test_media_line() {
639
        let buf: &[u8] = "m=audio 40392 RTP/AVP 8 0\r\n".as_bytes();
640
        let (_, m) = parse_media_description(buf).expect("parsing failed");
641
        assert_eq!(m.media, "audio 40392 RTP/AVP 8 0");
642
    }
643

644
    #[test]
645
    fn test_media_line_2() {
646
        let buf: &[u8] = "m=audio 70000 RTP/AVP 8 0\r\n".as_bytes();
647
        let result = parse_media_description(buf);
648
        assert!(result.is_err());
649
    }
650
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc