xref: /webrtc/sdp/src/description/media.rs (revision 5d8fe953)
1 use std::collections::HashMap;
2 use std::fmt;
3 use url::Url;
4 
5 use crate::description::common::*;
6 use crate::extmap::*;
7 
8 /// Constants for extmap key
9 pub const EXT_MAP_VALUE_TRANSPORT_CC_KEY: isize = 3;
10 pub const EXT_MAP_VALUE_TRANSPORT_CC_URI: &str =
11     "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
12 
ext_map_uri() -> HashMap<isize, &'static str>13 fn ext_map_uri() -> HashMap<isize, &'static str> {
14     let mut m = HashMap::new();
15     m.insert(
16         EXT_MAP_VALUE_TRANSPORT_CC_KEY,
17         EXT_MAP_VALUE_TRANSPORT_CC_URI,
18     );
19     m
20 }
21 
22 /// MediaDescription represents a media type.
23 /// <https://tools.ietf.org/html/rfc4566#section-5.14>
24 #[derive(Debug, Default, Clone)]
25 pub struct MediaDescription {
26     /// `m=<media> <port>/<number of ports> <proto> <fmt> ...`
27     ///
28     /// <https://tools.ietf.org/html/rfc4566#section-5.14>
29     pub media_name: MediaName,
30 
31     /// `i=<session description>`
32     ///
33     /// <https://tools.ietf.org/html/rfc4566#section-5.4>
34     pub media_title: Option<Information>,
35 
36     /// `c=<nettype> <addrtype> <connection-address>`
37     ///
38     /// <https://tools.ietf.org/html/rfc4566#section-5.7>
39     pub connection_information: Option<ConnectionInformation>,
40 
41     /// `b=<bwtype>:<bandwidth>`
42     ///
43     /// <https://tools.ietf.org/html/rfc4566#section-5.8>
44     pub bandwidth: Vec<Bandwidth>,
45 
46     /// `k=<method>`
47     ///
48     /// `k=<method>:<encryption key>`
49     ///
50     /// <https://tools.ietf.org/html/rfc4566#section-5.12>
51     pub encryption_key: Option<EncryptionKey>,
52 
53     /// Attributes are the primary means for extending SDP.  Attributes may
54     /// be defined to be used as "session-level" attributes, "media-level"
55     /// attributes, or both.
56     ///
57     /// <https://tools.ietf.org/html/rfc4566#section-5.12>
58     pub attributes: Vec<Attribute>,
59 }
60 
61 impl MediaDescription {
62     /// attribute returns the value of an attribute and if it exists
attribute(&self, key: &str) -> Option<Option<&str>>63     pub fn attribute(&self, key: &str) -> Option<Option<&str>> {
64         for a in &self.attributes {
65             if a.key == key {
66                 return Some(a.value.as_ref().map(|s| s.as_ref()));
67             }
68         }
69         None
70     }
71 
72     /// new_jsep_media_description creates a new MediaName with
73     /// some settings that are required by the JSEP spec.
new_jsep_media_description(codec_type: String, _codec_prefs: Vec<&str>) -> Self74     pub fn new_jsep_media_description(codec_type: String, _codec_prefs: Vec<&str>) -> Self {
75         MediaDescription {
76             media_name: MediaName {
77                 media: codec_type,
78                 port: RangedPort {
79                     value: 9,
80                     range: None,
81                 },
82                 protos: vec![
83                     "UDP".to_string(),
84                     "TLS".to_string(),
85                     "RTP".to_string(),
86                     "SAVPF".to_string(),
87                 ],
88                 formats: vec![],
89             },
90             media_title: None,
91             connection_information: Some(ConnectionInformation {
92                 network_type: "IN".to_string(),
93                 address_type: "IP4".to_string(),
94                 address: Some(Address {
95                     address: "0.0.0.0".to_string(),
96                     ttl: None,
97                     range: None,
98                 }),
99             }),
100             bandwidth: vec![],
101             encryption_key: None,
102             attributes: vec![],
103         }
104     }
105 
106     /// with_property_attribute adds a property attribute 'a=key' to the media description
with_property_attribute(mut self, key: String) -> Self107     pub fn with_property_attribute(mut self, key: String) -> Self {
108         self.attributes.push(Attribute::new(key, None));
109         self
110     }
111 
112     /// with_value_attribute adds a value attribute 'a=key:value' to the media description
with_value_attribute(mut self, key: String, value: String) -> Self113     pub fn with_value_attribute(mut self, key: String, value: String) -> Self {
114         self.attributes.push(Attribute::new(key, Some(value)));
115         self
116     }
117 
118     /// with_fingerprint adds a fingerprint to the media description
with_fingerprint(self, algorithm: String, value: String) -> Self119     pub fn with_fingerprint(self, algorithm: String, value: String) -> Self {
120         self.with_value_attribute("fingerprint".to_owned(), algorithm + " " + &value)
121     }
122 
123     /// with_ice_credentials adds ICE credentials to the media description
with_ice_credentials(self, username: String, password: String) -> Self124     pub fn with_ice_credentials(self, username: String, password: String) -> Self {
125         self.with_value_attribute("ice-ufrag".to_string(), username)
126             .with_value_attribute("ice-pwd".to_string(), password)
127     }
128 
129     /// with_codec adds codec information to the media description
with_codec( mut self, payload_type: u8, name: String, clockrate: u32, channels: u16, fmtp: String, ) -> Self130     pub fn with_codec(
131         mut self,
132         payload_type: u8,
133         name: String,
134         clockrate: u32,
135         channels: u16,
136         fmtp: String,
137     ) -> Self {
138         self.media_name.formats.push(payload_type.to_string());
139         let mut rtpmap = format!("{payload_type} {name}/{clockrate}");
140         if channels > 0 {
141             rtpmap += format!("/{channels}").as_str();
142         }
143 
144         if !fmtp.is_empty() {
145             self.with_value_attribute("rtpmap".to_string(), rtpmap)
146                 .with_value_attribute("fmtp".to_string(), format!("{payload_type} {fmtp}"))
147         } else {
148             self.with_value_attribute("rtpmap".to_string(), rtpmap)
149         }
150     }
151 
152     /// with_media_source adds media source information to the media description
with_media_source( self, ssrc: u32, cname: String, stream_label: String, label: String, ) -> Self153     pub fn with_media_source(
154         self,
155         ssrc: u32,
156         cname: String,
157         stream_label: String,
158         label: String,
159     ) -> Self {
160         self.
161             with_value_attribute("ssrc".to_string(), format!("{ssrc} cname:{cname}")). // Deprecated but not phased out?
162             with_value_attribute("ssrc".to_string(), format!("{ssrc} msid:{stream_label} {label}")).
163             with_value_attribute("ssrc".to_string(), format!("{ssrc} mslabel:{stream_label}")). // Deprecated but not phased out?
164             with_value_attribute("ssrc".to_string(), format!("{ssrc} label:{label}"))
165         // Deprecated but not phased out?
166     }
167 
168     /// with_candidate adds an ICE candidate to the media description
169     /// Deprecated: use WithICECandidate instead
with_candidate(self, value: String) -> Self170     pub fn with_candidate(self, value: String) -> Self {
171         self.with_value_attribute("candidate".to_string(), value)
172     }
173 
with_extmap(self, e: ExtMap) -> Self174     pub fn with_extmap(self, e: ExtMap) -> Self {
175         self.with_property_attribute(e.marshal())
176     }
177 
178     /// with_transport_cc_extmap adds an extmap to the media description
with_transport_cc_extmap(self) -> Self179     pub fn with_transport_cc_extmap(self) -> Self {
180         let uri = {
181             let m = ext_map_uri();
182             if let Some(uri_str) = m.get(&EXT_MAP_VALUE_TRANSPORT_CC_KEY) {
183                 match Url::parse(uri_str) {
184                     Ok(uri) => Some(uri),
185                     Err(_) => None,
186                 }
187             } else {
188                 None
189             }
190         };
191 
192         let e = ExtMap {
193             value: EXT_MAP_VALUE_TRANSPORT_CC_KEY,
194             uri,
195             ..Default::default()
196         };
197 
198         self.with_extmap(e)
199     }
200 }
201 
202 /// RangedPort supports special format for the media field "m=" port value. If
203 /// it may be necessary to specify multiple transport ports, the protocol allows
204 /// to write it as: <port>/<number of ports> where number of ports is a an
205 /// offsetting range.
206 #[derive(Debug, Default, Clone)]
207 pub struct RangedPort {
208     pub value: isize,
209     pub range: Option<isize>,
210 }
211 
212 impl fmt::Display for RangedPort {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result213     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214         if let Some(range) = self.range {
215             write!(f, "{}/{}", self.value, range)
216         } else {
217             write!(f, "{}", self.value)
218         }
219     }
220 }
221 
222 /// MediaName describes the "m=" field storage structure.
223 #[derive(Debug, Default, Clone)]
224 pub struct MediaName {
225     pub media: String,
226     pub port: RangedPort,
227     pub protos: Vec<String>,
228     pub formats: Vec<String>,
229 }
230 
231 impl fmt::Display for MediaName {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result232     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233         let s = vec![
234             self.media.clone(),
235             self.port.to_string(),
236             self.protos.join("/"),
237             self.formats.join(" "),
238         ];
239         write!(f, "{}", s.join(" "))
240     }
241 }
242 
243 #[cfg(test)]
244 mod tests {
245     use super::MediaDescription;
246 
247     #[test]
test_attribute_missing()248     fn test_attribute_missing() {
249         let media_description = MediaDescription::default();
250 
251         assert_eq!(media_description.attribute("recvonly"), None);
252     }
253 
254     #[test]
test_attribute_present_with_no_value()255     fn test_attribute_present_with_no_value() {
256         let media_description =
257             MediaDescription::default().with_property_attribute("recvonly".to_owned());
258 
259         assert_eq!(media_description.attribute("recvonly"), Some(None));
260     }
261 
262     #[test]
test_attribute_present_with_value()263     fn test_attribute_present_with_value() {
264         let media_description =
265             MediaDescription::default().with_value_attribute("ptime".to_owned(), "1".to_owned());
266 
267         assert_eq!(media_description.attribute("ptime"), Some(Some("1")));
268     }
269 }
270