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