sdp_types/
lib.rs

1// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
2//
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5//! Crate for handling SDP ([RFC 4566](https://tools.ietf.org/html/rfc4566))
6//! session descriptions, including a parser and serializer.
7//!
8//! ## Serializing an SDP
9//!
10//! ```rust,ignore
11//! // Create SDP session description
12//! let sdp = sdp_types::Session {
13//!     ...
14//! };
15//!
16//! // And write it to an `Vec<u8>`
17//! let mut output = Vec::new();
18//! sdp.write(&mut output).unwrap();
19//! ```
20//!
21//! ## Parsing an SDP
22//!
23//! ```rust,no_run
24//! # let data = [0u8];
25//! // Parse SDP session description from a byte slice
26//! let sdp = sdp_types::Session::parse(&data).unwrap();
27//!
28//! // Access the 'tool' attribute
29//! match sdp.get_first_attribute_value("tool") {
30//!     Ok(Some(tool)) => println!("tool: {}", tool),
31//!     Ok(None) => println!("tool: empty"),
32//!     Err(_) => println!("no tool attribute"),
33//! }
34//! ```
35//!
36//! ## Limitations
37//!
38//!  * SDP session descriptions are by default in UTF-8 but an optional `charset`
39//!    attribute can change this for various SDP fields, including various other
40//!    attributes. This is currently not supported, only UTF-8 is supported.
41//!
42//!  * Network addresses, Phone numbers, E-Mail addresses and various other fields
43//!    are currently parsed as a plain string and not according to the SDP
44//!    grammar.
45
46use bstr::*;
47use fallible_iterator::FallibleIterator;
48
49mod parser;
50mod writer;
51
52pub use parser::ParserError;
53
54/// Originator of the session.
55///
56/// See [RFC 4566 Section 5.2](https://tools.ietf.org/html/rfc4566#section-5.2) for more details.
57#[derive(Debug, PartialEq, Eq, Clone)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct Origin {
60    /// User's login on the originating host.
61    pub username: Option<String>,
62    /// Session ID to make the whole `Origin` unique.
63    ///
64    /// Must be a numeric string but this is *not* checked.
65    pub sess_id: String,
66    /// Session version number.
67    pub sess_version: u64,
68    /// Type of network for this session.
69    pub nettype: String,
70    /// Type of the `unicast_address`.
71    pub addrtype: String,
72    /// Address where the session was created.
73    pub unicast_address: String,
74}
75
76/// Connection data for the session or media.
77///
78/// See [RFC 4566 Section 5.7](https://tools.ietf.org/html/rfc4566#section-5.7) for more details.
79#[derive(Debug, PartialEq, Eq, Clone)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct Connection {
82    /// Type of network for this connection.
83    pub nettype: String,
84    /// Type of the `connection_address`.
85    pub addrtype: String,
86    /// Connection address.
87    pub connection_address: String,
88}
89
90/// Bandwidth information for the session or media.
91///
92/// See [RFC 4566 Section 5.8](https://tools.ietf.org/html/rfc4566#section-5.8) for more details.
93#[derive(Debug, PartialEq, Eq, Clone)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct Bandwidth {
96    /// Bandwidth type, usually "CT" or "AS".
97    pub bwtype: String,
98    /// Bandwidth.
99    pub bandwidth: u64,
100}
101
102/// Timing information of the session.
103///
104/// See [RFC 4566 Section 5.9](https://tools.ietf.org/html/rfc4566#section-5.9) for more details.
105#[derive(Debug, PartialEq, Eq, Clone)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107pub struct Time {
108    /// Start time of the session in seconds since 1900.
109    pub start_time: u64,
110    /// Stop time of the session in seconds since 1900.
111    pub stop_time: u64,
112    /// Repeat times.
113    pub repeats: Vec<Repeat>,
114}
115
116/// Repeat times for timing information.
117///
118/// See [RFC 4566 Section 5.10](https://tools.ietf.org/html/rfc4566#section-5.10) for more details.
119#[derive(Debug, PartialEq, Eq, Clone)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct Repeat {
122    /// Repeat interval in seconds.
123    pub repeat_interval: u64,
124    /// Duration of one repeat.
125    pub active_duration: u64,
126    /// Offsets for the repeats from the `start_time`.
127    pub offsets: Vec<u64>,
128}
129
130/// Time zone information for the session.
131///
132/// See [RFC 4566 Section 5.11](https://tools.ietf.org/html/rfc4566#section-5.11) for more details.
133#[derive(Debug, PartialEq, Eq, Clone)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
135pub struct TimeZone {
136    /// Time in seconds since 1900 when the adjustment happens.
137    pub adjustment_time: u64,
138    /// Amount of the adjustment in seconds.
139    pub offset: i64,
140}
141
142/// Encryption key for the session or media.
143///
144/// See [RFC 4566 Section 5.12](https://tools.ietf.org/html/rfc4566#section-5.12) for more details.
145#[derive(Debug, PartialEq, Eq, Clone)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub struct Key {
148    /// Encryption method that is used.
149    pub method: String,
150    /// Encryption key or information to obtain the encryption key.
151    pub encryption_key: Option<String>,
152}
153
154/// Attributes for the session or media.
155///
156/// See [RFC 4566 Section 5.13](https://tools.ietf.org/html/rfc4566#section-5.13) for more details.
157#[derive(Debug, PartialEq, Eq, Clone)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159pub struct Attribute {
160    /// Attribute name.
161    pub attribute: String,
162    /// Attribute value.
163    pub value: Option<String>,
164}
165
166/// Media description.
167///
168/// See [RFC 4566 Section 5.14](https://tools.ietf.org/html/rfc4566#section-5.14) for more details.
169#[derive(Debug, PartialEq, Eq, Clone)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
171pub struct Media {
172    /// Media type, e.g. "audio", "video", "text", "application" or "message".
173    pub media: String,
174    /// Transport port to which the media is sent.
175    pub port: u16,
176    /// Number of ports starting at `port` used for the media.
177    pub num_ports: Option<u16>,
178    /// Transport protocol.
179    pub proto: String,
180    /// Media format description.
181    pub fmt: String,
182    /// Media title.
183    pub media_title: Option<String>,
184    /// Connection data for the media.
185    pub connections: Vec<Connection>,
186    /// Bandwidth information for the media.
187    pub bandwidths: Vec<Bandwidth>,
188    /// Encryption key for the media.
189    pub key: Option<Key>,
190    /// Attributes of the media.
191    pub attributes: Vec<Attribute>,
192}
193
194/// SDP session description.
195///
196/// See [RFC 4566 Section 5](https://tools.ietf.org/html/rfc4566#section-5) for more details.
197#[derive(Debug, PartialEq, Eq, Clone)]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199pub struct Session {
200    /// Originator of the session.
201    pub origin: Origin,
202    /// Name of the session.
203    pub session_name: String,
204    /// Session description.
205    pub session_description: Option<String>,
206    /// URI to additional information about the session.
207    pub uri: Option<String>,
208    /// E-Mail contacts for the session.
209    pub emails: Vec<String>,
210    /// Phone contacts for the session.
211    pub phones: Vec<String>,
212    /// Connection data for the session.
213    pub connection: Option<Connection>,
214    /// Bandwidth information for the session.
215    pub bandwidths: Vec<Bandwidth>,
216    /// Timing information for the session.
217    pub times: Vec<Time>,
218    /// Time zone information for the session.
219    pub time_zones: Vec<TimeZone>,
220    /// Encryption key for the session.
221    pub key: Option<Key>,
222    /// Attributes of the session.
223    pub attributes: Vec<Attribute>,
224    /// Media descriptions for this session.
225    pub medias: Vec<Media>,
226}
227
228/// Error returned when an attribute is not found.
229#[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    /// Checks if the given attribute exists.
242    pub fn has_attribute(&self, name: &str) -> bool {
243        self.attributes.iter().any(|a| a.attribute == name)
244    }
245
246    /// Gets the first value of the given attribute, if existing.
247    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    /// Gets an iterator over all attribute values of the given name, if existing.
259    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    /// Checks if the given attribute exists.
279    pub fn has_attribute(&self, name: &str) -> bool {
280        self.attributes.iter().any(|a| a.attribute == name)
281    }
282
283    /// Gets the first value of the given attribute, if existing.
284    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    /// Gets an iterator over all attribute values of the given name, if existing.
296    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}