1use bstr::*;
47use fallible_iterator::FallibleIterator;
48
49mod parser;
50mod writer;
51
52pub use parser::ParserError;
53
54#[derive(Debug, PartialEq, Eq, Clone)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct Origin {
60 pub username: Option<String>,
62 pub sess_id: String,
66 pub sess_version: u64,
68 pub nettype: String,
70 pub addrtype: String,
72 pub unicast_address: String,
74}
75
76#[derive(Debug, PartialEq, Eq, Clone)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct Connection {
82 pub nettype: String,
84 pub addrtype: String,
86 pub connection_address: String,
88}
89
90#[derive(Debug, PartialEq, Eq, Clone)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct Bandwidth {
96 pub bwtype: String,
98 pub bandwidth: u64,
100}
101
102#[derive(Debug, PartialEq, Eq, Clone)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107pub struct Time {
108 pub start_time: u64,
110 pub stop_time: u64,
112 pub repeats: Vec<Repeat>,
114}
115
116#[derive(Debug, PartialEq, Eq, Clone)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct Repeat {
122 pub repeat_interval: u64,
124 pub active_duration: u64,
126 pub offsets: Vec<u64>,
128}
129
130#[derive(Debug, PartialEq, Eq, Clone)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
135pub struct TimeZone {
136 pub adjustment_time: u64,
138 pub offset: i64,
140}
141
142#[derive(Debug, PartialEq, Eq, Clone)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub struct Key {
148 pub method: String,
150 pub encryption_key: Option<String>,
152}
153
154#[derive(Debug, PartialEq, Eq, Clone)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159pub struct Attribute {
160 pub attribute: String,
162 pub value: Option<String>,
164}
165
166#[derive(Debug, PartialEq, Eq, Clone)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
171pub struct Media {
172 pub media: String,
174 pub port: u16,
176 pub num_ports: Option<u16>,
178 pub proto: String,
180 pub fmt: String,
182 pub media_title: Option<String>,
184 pub connections: Vec<Connection>,
186 pub bandwidths: Vec<Bandwidth>,
188 pub key: Option<Key>,
190 pub attributes: Vec<Attribute>,
192}
193
194#[derive(Debug, PartialEq, Eq, Clone)]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199pub struct Session {
200 pub origin: Origin,
202 pub session_name: String,
204 pub session_description: Option<String>,
206 pub uri: Option<String>,
208 pub emails: Vec<String>,
210 pub phones: Vec<String>,
212 pub connection: Option<Connection>,
214 pub bandwidths: Vec<Bandwidth>,
216 pub times: Vec<Time>,
218 pub time_zones: Vec<TimeZone>,
220 pub key: Option<Key>,
222 pub attributes: Vec<Attribute>,
224 pub medias: Vec<Media>,
226}
227
228#[derive(Debug, PartialEq, Eq)]
230pub struct AttributeNotFoundError;
231
232impl std::error::Error for AttributeNotFoundError {}
233
234impl std::fmt::Display for AttributeNotFoundError {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
236 write!(f, "Attribute not found")
237 }
238}
239
240impl Media {
241 pub fn has_attribute(&self, name: &str) -> bool {
243 self.attributes.iter().any(|a| a.attribute == name)
244 }
245
246 pub fn get_first_attribute_value(
248 &self,
249 name: &str,
250 ) -> Result<Option<&str>, AttributeNotFoundError> {
251 self.attributes
252 .iter()
253 .find(|a| a.attribute == name)
254 .ok_or(AttributeNotFoundError)
255 .map(|a| a.value.as_deref())
256 }
257
258 pub fn get_attribute_values<'a>(
260 &'a self,
261 name: &'a str,
262 ) -> Result<impl Iterator<Item = Option<&'a str>> + 'a, AttributeNotFoundError> {
263 let mut iter = self
264 .attributes
265 .iter()
266 .filter(move |a| a.attribute == name)
267 .map(|a| a.value.as_deref())
268 .peekable();
269 if iter.peek().is_some() {
270 Ok(iter)
271 } else {
272 Err(AttributeNotFoundError)
273 }
274 }
275}
276
277impl Session {
278 pub fn has_attribute(&self, name: &str) -> bool {
280 self.attributes.iter().any(|a| a.attribute == name)
281 }
282
283 pub fn get_first_attribute_value(
285 &self,
286 name: &str,
287 ) -> Result<Option<&str>, AttributeNotFoundError> {
288 self.attributes
289 .iter()
290 .find(|a| a.attribute == name)
291 .ok_or(AttributeNotFoundError)
292 .map(|a| a.value.as_deref())
293 }
294
295 pub fn get_attribute_values<'a>(
297 &'a self,
298 name: &'a str,
299 ) -> Result<impl Iterator<Item = Option<&'a str>> + 'a, AttributeNotFoundError> {
300 let mut iter = self
301 .attributes
302 .iter()
303 .filter(move |a| a.attribute == name)
304 .map(|a| a.value.as_deref())
305 .peekable();
306 if iter.peek().is_some() {
307 Ok(iter)
308 } else {
309 Err(AttributeNotFoundError)
310 }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn parse_write() {
320 let sdp = "v=0\r
321o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
322s=SDP Seminar\r
323i=A Seminar on the session description protocol\r
324u=http://www.example.com/seminars/sdp.pdf\r
325e=j.doe@example.com (Jane Doe)\r
326p=+1 617 555-6011\r
327c=IN IP4 224.2.17.12/127\r
328b=AS:128\r
329t=2873397496 2873404696\r
330r=7d 1h 0 25h\r
331z=2882844526 -1h 2898848070 0\r
332k=clear:1234\r
333a=recvonly\r
334m=audio 49170 RTP/AVP 0\r
335m=video 51372/2 RTP/AVP 99\r
336a=rtpmap:99 h263-1998/90000\r
337a=fingerprint:sha-256 3A:96:6D:57:B2:C2:C7:61:A0:46:3E:1C:97:39:D3:F7:0A:88:A0:B1:EC:03:FB:10:A5:5D:3A:37:AB:DD:02:AA\r
338";
339 let parsed = Session::parse(sdp.as_bytes()).unwrap();
340 let mut written = vec![];
341 parsed.write(&mut written).unwrap();
342 assert_eq!(String::from_utf8_lossy(&written), sdp);
343 }
344
345 #[test]
346 fn parse_media_attributes() {
347 let media = Media {
348 media: "video".into(),
349 port: 51372,
350 num_ports: Some(2),
351 proto: "RTP/AVP".into(),
352 fmt: "99 100".into(),
353 media_title: None,
354 connections: vec![],
355 bandwidths: vec![],
356 key: None,
357 attributes: vec![
358 Attribute {
359 attribute: "rtpmap".into(),
360 value: Some("99 h263-1998/90000".into()),
361 },
362 Attribute {
363 attribute: "rtpmap".into(),
364 value: Some("100 h264/90000".into()),
365 },
366 Attribute {
367 attribute: "rtcp".into(),
368 value: None,
369 },
370 ],
371 };
372
373 assert!(media.has_attribute("rtpmap"));
374 assert!(media.has_attribute("rtcp"));
375 assert!(!media.has_attribute("foo"));
376
377 assert_eq!(
378 media.get_first_attribute_value("rtpmap"),
379 Ok(Some("99 h263-1998/90000"))
380 );
381 assert_eq!(media.get_first_attribute_value("rtcp"), Ok(None));
382 assert_eq!(
383 media.get_first_attribute_value("foo"),
384 Err(AttributeNotFoundError)
385 );
386
387 assert_eq!(
388 media
389 .get_attribute_values("rtpmap")
390 .unwrap()
391 .collect::<Vec<_>>(),
392 &[Some("99 h263-1998/90000"), Some("100 h264/90000")]
393 );
394 assert_eq!(
395 media
396 .get_attribute_values("rtcp")
397 .unwrap()
398 .collect::<Vec<_>>(),
399 &[None]
400 );
401 assert!(media.get_attribute_values("foo").is_err());
402 }
403
404 #[test]
405 fn parse_session_attributes() {
406 let session = Session {
407 origin: Origin {
408 username: Some("jdoe".into()),
409 sess_id: "2890844526".into(),
410 sess_version: 2890842807,
411 nettype: "IN".into(),
412 addrtype: "IP4".into(),
413 unicast_address: "10.47.16.5".into(),
414 },
415 session_name: "SDP Seminar".into(),
416 session_description: None,
417 uri: None,
418 emails: vec![],
419 phones: vec![],
420 connection: None,
421 bandwidths: vec![],
422 times: vec![Time {
423 start_time: 0,
424 stop_time: 0,
425 repeats: vec![],
426 }],
427 time_zones: vec![],
428 key: None,
429 attributes: vec![
430 Attribute {
431 attribute: "rtpmap".into(),
432 value: Some("99 h263-1998/90000".into()),
433 },
434 Attribute {
435 attribute: "rtpmap".into(),
436 value: Some("100 h264/90000".into()),
437 },
438 Attribute {
439 attribute: "rtcp".into(),
440 value: None,
441 },
442 ],
443 medias: vec![],
444 };
445
446 assert!(session.has_attribute("rtpmap"));
447 assert!(session.has_attribute("rtcp"));
448 assert!(!session.has_attribute("foo"));
449
450 assert_eq!(
451 session.get_first_attribute_value("rtpmap"),
452 Ok(Some("99 h263-1998/90000"))
453 );
454 assert_eq!(session.get_first_attribute_value("rtcp"), Ok(None));
455 assert_eq!(
456 session.get_first_attribute_value("foo"),
457 Err(AttributeNotFoundError)
458 );
459
460 assert_eq!(
461 session
462 .get_attribute_values("rtpmap")
463 .unwrap()
464 .collect::<Vec<_>>(),
465 &[Some("99 h263-1998/90000"), Some("100 h264/90000")]
466 );
467 assert_eq!(
468 session
469 .get_attribute_values("rtcp")
470 .unwrap()
471 .collect::<Vec<_>>(),
472 &[None]
473 );
474 assert!(session.get_attribute_values("foo").is_err());
475 }
476}