1use crate::{lines, Error};
2use std::convert::{TryFrom, TryInto};
3use vec1::Vec1;
4
5pub use crate::tokenizers::session_description::Tokenizer;
8
9#[derive(Debug, PartialEq, PartialOrd, Clone)]
11pub struct SessionDescription {
12 pub version: lines::Version,
13 pub origin: lines::Origin,
14 pub session_name: lines::SessionName,
15 pub session_info: Option<lines::SessionInformation>,
16 pub uri: Option<lines::Uri>,
17 pub emails: Vec<lines::Email>,
18 pub phones: Vec<lines::Phone>,
19 pub connection: Option<lines::Connection>,
20 pub bandwidths: Vec<lines::Bandwidth>,
21 pub times: Vec1<crate::Time>,
22 pub key: Option<lines::Key>,
23 pub attributes: Vec<lines::Attribute>,
24 pub media_descriptions: Vec<crate::MediaDescription>,
25}
26
27impl TryFrom<String> for SessionDescription {
28 type Error = Error;
29
30 fn try_from(from: String) -> Result<Self, Self::Error> {
31 let tokenizer = Tokenizer::tokenize(&from)?;
32 Self::try_from(tokenizer.1)
33 }
34}
35
36impl TryFrom<&str> for SessionDescription {
37 type Error = Error;
38
39 fn try_from(from: &str) -> Result<Self, Self::Error> {
40 let tokenizer = Tokenizer::tokenize(from)?;
41 Self::try_from(tokenizer.1)
42 }
43}
44
45impl std::str::FromStr for SessionDescription {
46 type Err = Error;
47 fn from_str(str: &str) -> Result<Self, Self::Err> {
48 Self::try_from(str)
49 }
50}
51
52impl<'a> TryFrom<Tokenizer<'a>> for SessionDescription {
53 type Error = Error;
54
55 fn try_from(tokenizer: Tokenizer<'a>) -> Result<Self, Self::Error> {
56 Ok(Self {
57 version: tokenizer.version.try_into()?,
58 origin: tokenizer.origin.try_into()?,
59 session_name: tokenizer.session_name.into(),
60 session_info: tokenizer.session_info.map(Into::into),
61 uri: tokenizer.uri.map(Into::into),
62 emails: tokenizer
63 .emails
64 .into_iter()
65 .map(Into::into)
66 .collect::<Vec<_>>(),
67 phones: tokenizer
68 .phones
69 .into_iter()
70 .map(Into::into)
71 .collect::<Vec<_>>(),
72 connection: tokenizer.connection.map(TryInto::try_into).transpose()?,
73 bandwidths: tokenizer
74 .bandwidths
75 .into_iter()
76 .map(TryInto::try_into)
77 .collect::<Result<Vec<_>, _>>()?,
78 times: tokenizer
79 .times
80 .into_iter()
81 .map(TryInto::try_into)
82 .collect::<Result<Vec<_>, _>>()?
83 .try_into()
84 .map_err(|_| Error::parser("times", "missing time(s) line(s)"))?,
85 key: tokenizer.key.map(Into::into),
86 attributes: tokenizer
87 .attributes
88 .into_iter()
89 .map(TryInto::try_into)
90 .collect::<Result<Vec<_>, _>>()?,
91 media_descriptions: tokenizer
92 .media_descriptions
93 .into_iter()
94 .map(TryInto::try_into)
95 .collect::<Result<Vec<_>, _>>()?,
96 })
97 }
98}
99
100impl std::fmt::Display for SessionDescription {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 write!(f, "{}\r\n", self.version)?;
103 write!(f, "{}\r\n", self.origin)?;
104 write!(f, "{}\r\n", self.session_name)?;
105 if let Some(info) = &self.session_info {
106 write!(f, "{}\r\n", info)?;
107 }
108 if let Some(uri) = &self.uri {
109 write!(f, "{}\r\n", uri)?;
110 }
111 for email in self.emails.iter() {
112 write!(f, "{}\r\n", email)?;
113 }
114 for phone in self.phones.iter() {
115 write!(f, "{}\r\n", phone)?;
116 }
117 if let Some(connection) = &self.connection {
118 write!(f, "{}\r\n", connection)?;
119 }
120 for bandwidth in self.bandwidths.iter() {
121 write!(f, "{}\r\n", bandwidth)?;
122 }
123 for time in self.times.iter() {
124 write!(f, "{}\r\n", time)?;
125 }
126 if let Some(key) = &self.key {
127 write!(f, "{}\r\n", key)?;
128 }
129 for attribute in self.attributes.iter() {
130 write!(f, "{}\r\n", attribute)?;
131 }
132 for media_description in self.media_descriptions.iter() {
133 write!(f, "{}\r\n", media_description)?;
134 }
135
136 Ok(())
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use chrono::Duration;
144 use std::net::{IpAddr, Ipv4Addr};
145 use vec1::vec1;
146
147 #[test]
148 fn from_tokenizer1() {
149 let tokenizer = Tokenizer {
150 version: "0".into(),
151 origin: crate::tokenizers::origin::Tokenizer {
152 username: "Alice",
153 sess_id: "2890844526",
154 sess_version: "2890842807",
155 nettype: "IN",
156 addrtype: "IP4",
157 unicast_address: "10.47.16.5",
158 },
159 session_name: "-".into(),
160 session_info: Some("A Seminar on the session description protocol".into()),
161 uri: Some("http://www.example.com/seminars/sdp.pdf".into()),
162 emails: vec!["alice@example.com (Alice Smith)".into()],
163 phones: vec!["+1 911-345-1160".into()],
164 connection: Some(crate::tokenizers::connection::Tokenizer {
165 nettype: "IN",
166 addrtype: "IP4",
167 connection_address: ("10.47.16.5").into(),
168 }),
169 bandwidths: vec![("CT", "1024").into()],
170 times: vec1![crate::time::Tokenizer {
171 active: ("2854678930", "2854679000").into(),
172 repeat: vec![("604800", "3600", vec!["0", "90000"]).into(),],
173 zone: Some(vec![("2882844526", "-1h"), ("2898848070", "0h")].into())
174 }],
175 key: Some(("clear", "password").into()),
176 attributes: vec!["recvonly".into()],
177 media_descriptions: vec![crate::tokenizers::media_description::Tokenizer {
178 media: crate::tokenizers::media::Tokenizer {
179 media: "audio",
180 port: "49170".into(),
181 proto: "RTP/AVP",
182 fmt: "0",
183 },
184 info: Some("audio media".into()),
185 connections: vec![
186 crate::tokenizers::connection::Tokenizer {
187 nettype: "IN",
188 addrtype: "IP4",
189 connection_address: "10.47.16.5".into(),
190 },
191 crate::tokenizers::connection::Tokenizer {
192 nettype: "IN",
193 addrtype: "IP4",
194 connection_address: "10.47.16.6".into(),
195 },
196 ],
197 bandwidths: vec![("CT", "1000").into(), ("AS", "551").into()],
198 key: Some("prompt".into()),
199 attributes: vec![
200 ("rtpmap", "99 h232-199/90000").into(),
201 ("rtpmap", "90 h263-1998/90000").into(),
202 ],
203 }],
204 };
205
206 let expected_times = vec1![crate::Time {
207 active: lines::Active {
208 start: 2854678930,
209 stop: 2854679000,
210 },
211 repeat: vec![lines::Repeat {
212 interval: lines::common::TypedTime::None(Duration::seconds(604800)),
213 duration: lines::common::TypedTime::None(Duration::seconds(3600)),
214 offsets: vec![
215 lines::common::TypedTime::None(Duration::seconds(0)),
216 lines::common::TypedTime::None(Duration::seconds(90000))
217 ],
218 },],
219 zone: Some(lines::Zone {
220 parts: vec![
221 lines::zone::ZonePart {
222 adjustment_time: 2882844526,
223 offset: lines::common::TypedTime::Hours(Duration::hours(-1)),
224 },
225 lines::zone::ZonePart {
226 adjustment_time: 2898848070,
227 offset: lines::common::TypedTime::Hours(Duration::hours(0)),
228 },
229 ],
230 })
231 }];
232
233 let expected_media_description = crate::MediaDescription {
234 media: lines::Media {
235 media: lines::media::MediaType::Audio,
236 port: 49170,
237 num_of_ports: None,
238 proto: lines::media::ProtoType::RtpAvp,
239 fmt: "0".into(),
240 },
241 info: Some(lines::SessionInformation::new("audio media".into())),
242 connections: vec![
243 lines::Connection {
244 nettype: lines::common::Nettype::In,
245 addrtype: lines::common::Addrtype::Ip4,
246 connection_address: "10.47.16.5".parse::<IpAddr>().unwrap().into(),
247 },
248 lines::Connection {
249 nettype: lines::common::Nettype::In,
250 addrtype: lines::common::Addrtype::Ip4,
251 connection_address: "10.47.16.6".parse::<IpAddr>().unwrap().into(),
252 },
253 ],
254 bandwidths: vec![
255 lines::Bandwidth {
256 bwtype: lines::bandwidth::Bwtype::Ct,
257 bandwidth: 1000,
258 },
259 lines::Bandwidth {
260 bwtype: lines::bandwidth::Bwtype::As,
261 bandwidth: 551,
262 },
263 ],
264 key: Some(lines::Key {
265 method: lines::key::KeyMethod::Prompt,
266 encryption_key: Default::default(),
267 }),
268 attributes: vec![
269 lines::Attribute::Rtpmap(lines::attribute::Rtpmap {
270 payload_type: 99,
271 encoding_name: "h232-199".into(),
272 clock_rate: 90000,
273 encoding_params: None,
274 }),
275 lines::Attribute::Rtpmap(lines::attribute::Rtpmap {
276 payload_type: 90,
277 encoding_name: "h263-1998".into(),
278 clock_rate: 90000,
279 encoding_params: None,
280 }),
281 ],
282 };
283
284 assert_eq!(
285 SessionDescription::try_from(tokenizer),
286 Ok(SessionDescription {
287 version: lines::Version::V0,
288 origin: lines::Origin {
289 username: "Alice".into(),
290 sess_id: "2890844526".into(),
291 sess_version: "2890842807".into(),
292 nettype: lines::common::Nettype::In,
293 addrtype: lines::common::Addrtype::Ip4,
294 unicast_address: IpAddr::V4(Ipv4Addr::new(10, 47, 16, 5)),
295 },
296 session_name: lines::SessionName::new("-".into()),
297 session_info: Some(lines::SessionInformation::new(
298 "A Seminar on the session description protocol".into()
299 )),
300 uri: Some(lines::Uri::new(
301 "http://www.example.com/seminars/sdp.pdf".into()
302 )),
303 emails: vec![lines::Email::new("alice@example.com (Alice Smith)".into())],
304 phones: vec![lines::Phone::new("+1 911-345-1160".into())],
305 connection: Some(lines::Connection {
306 nettype: lines::common::Nettype::In,
307 addrtype: lines::common::Addrtype::Ip4,
308 connection_address: lines::connection::ConnectionAddress {
309 base: "10.47.16.5".parse().unwrap(),
310 ttl: None,
311 numaddr: None
312 }
313 }),
314 bandwidths: vec![lines::Bandwidth {
315 bwtype: lines::bandwidth::Bwtype::Ct,
316 bandwidth: 1024,
317 }],
318 times: expected_times,
319 key: Some(lines::Key {
320 method: lines::key::KeyMethod::Clear,
321 encryption_key: "password".into()
322 }),
323 attributes: vec![lines::Attribute::Recvonly],
324 media_descriptions: vec![expected_media_description]
325 })
326 );
327 }
328
329 #[test]
330 fn from_tokenizer2() {
331 let tokenizer = Tokenizer {
332 version: "0".into(),
333 origin: crate::tokenizers::origin::Tokenizer {
334 username: "Alice",
335 sess_id: "2890844526",
336 sess_version: "2890842807",
337 nettype: "IN",
338 addrtype: "IP4",
339 unicast_address: "10.47.16.5",
340 },
341 session_name: "-".into(),
342 session_info: None,
343 uri: None,
344 emails: vec![],
345 phones: vec![],
346 connection: None,
347 bandwidths: vec![],
348 times: vec1![crate::tokenizers::time::Tokenizer {
349 active: ("2854678930", "2854679000").into(),
350 repeat: vec![],
351 zone: None
352 }],
353 key: None,
354 attributes: vec![],
355 media_descriptions: vec![crate::media_description::Tokenizer {
356 media: crate::tokenizers::media::Tokenizer {
357 media: "audio",
358 port: "49170".into(),
359 proto: "RTP/AVP",
360 fmt: "0",
361 },
362 info: None,
363 connections: vec![crate::tokenizers::connection::Tokenizer {
364 nettype: "IN",
365 addrtype: "IP4",
366 connection_address: "10.47.16.6".into(),
367 }],
368 bandwidths: vec![("AS", "551").into()],
369 key: None,
370 attributes: vec![],
371 }],
372 };
373
374 let expected_times = vec1![crate::Time {
375 active: lines::Active {
376 start: 2854678930,
377 stop: 2854679000,
378 },
379 repeat: vec![],
380 zone: None
381 }];
382
383 let expected_media_description = crate::MediaDescription {
384 media: lines::Media {
385 media: lines::media::MediaType::Audio,
386 port: 49170,
387 num_of_ports: None,
388 proto: lines::media::ProtoType::RtpAvp,
389 fmt: "0".into(),
390 },
391 info: None,
392 connections: vec![lines::Connection {
393 nettype: lines::common::Nettype::In,
394 addrtype: lines::common::Addrtype::Ip4,
395 connection_address: "10.47.16.6".parse::<IpAddr>().unwrap().into(),
396 }],
397 bandwidths: vec![lines::Bandwidth {
398 bwtype: lines::bandwidth::Bwtype::As,
399 bandwidth: 551,
400 }],
401 key: None,
402 attributes: vec![],
403 };
404
405 assert_eq!(
406 SessionDescription::try_from(tokenizer),
407 Ok(SessionDescription {
408 version: lines::Version::V0,
409 origin: lines::Origin {
410 username: "Alice".into(),
411 sess_id: "2890844526".into(),
412 sess_version: "2890842807".into(),
413 nettype: lines::common::Nettype::In,
414 addrtype: lines::common::Addrtype::Ip4,
415 unicast_address: IpAddr::V4(Ipv4Addr::new(10, 47, 16, 5)),
416 },
417 session_name: lines::SessionName::new("-".into()),
418 session_info: None,
419 uri: None,
420 emails: vec![],
421 phones: vec![],
422 connection: None,
423 bandwidths: vec![],
424 times: expected_times,
425 key: None,
426 attributes: vec![],
427 media_descriptions: vec![expected_media_description]
428 })
429 );
430 }
431
432 #[test]
433 fn from_tokenizer3() {
434 let tokenizer = Tokenizer {
435 version: "0".into(),
436 origin: crate::tokenizers::origin::Tokenizer {
437 username: "Alice",
438 sess_id: "2890844526",
439 sess_version: "2890842807",
440 nettype: "IN",
441 addrtype: "IP4",
442 unicast_address: "10.47.16.5",
443 },
444 session_name: "-".into(),
445 session_info: None,
446 uri: None,
447 emails: vec![],
448 phones: vec![],
449 connection: None,
450 bandwidths: vec![],
451 times: vec1![crate::tokenizers::time::Tokenizer {
452 active: ("2854678930", "2854679000").into(),
453 repeat: vec![],
454 zone: None
455 }],
456 key: None,
457 attributes: vec![],
458 media_descriptions: vec![],
459 };
460
461 let expected_times = vec1![crate::Time {
462 active: lines::Active {
463 start: 2854678930,
464 stop: 2854679000,
465 },
466 repeat: vec![],
467 zone: None
468 }];
469
470 assert_eq!(
471 SessionDescription::try_from(tokenizer),
472 Ok(SessionDescription {
473 version: lines::Version::V0,
474 origin: lines::Origin {
475 username: "Alice".into(),
476 sess_id: "2890844526".into(),
477 sess_version: "2890842807".into(),
478 nettype: lines::common::Nettype::In,
479 addrtype: lines::common::Addrtype::Ip4,
480 unicast_address: IpAddr::V4(Ipv4Addr::new(10, 47, 16, 5)),
481 },
482 session_name: lines::SessionName::new("-".into()),
483 session_info: None,
484 uri: None,
485 emails: vec![],
486 phones: vec![],
487 connection: None,
488 bandwidths: vec![],
489 times: expected_times,
490 key: None,
491 attributes: vec![],
492 media_descriptions: vec![]
493 })
494 );
495 }
496
497 #[test]
498 fn display1() {
499 let sdp = concat!(
500 "v=0\r\n",
501 "o=Alice 2890844526 2890842807 IN IP4 10.47.16.5\r\n",
502 "s=-\r\n",
503 "i=A Seminar on the session description protocol\r\n",
504 "u=http://www.example.com/seminars/sdp.pdf\r\n",
505 "e=alice@example.com (Alice Smith)\r\n",
506 "p=+1 911-345-1160\r\n",
507 "c=IN IP4 10.47.16.5\r\n",
508 "b=CT:1024\r\n",
509 "t=2854678930 2854679000\r\n",
510 "r=604800 3600 0 90000\r\n",
511 "z=2882844526 -1h 2898848070 0h\r\n",
512 "k=clear:password\r\n",
513 "a=recvonly\r\n",
514 "m=audio 49170 RTP/AVP 0\r\n",
515 "i=audio media\r\n",
516 "c=IN IP4 10.47.16.5\r\n",
517 "c=IN IP4 10.47.16.6\r\n",
518 "b=CT:1000\r\n",
519 "b=AS:551\r\n",
520 "k=prompt\r\n",
521 "a=rtpmap:99 h232-199/90000\r\n",
522 "a=rtpmap:90 h263-1998/90000\r\n"
523 );
524
525 let parsed_sdp = SessionDescription::try_from(Tokenizer::tokenize(sdp).unwrap().1).unwrap();
526 assert_eq!(parsed_sdp.to_string(), sdp);
527 }
528
529 #[test]
530 fn display2() {
531 let sdp = concat!(
532 "v=0\r\n",
533 "o=Alice 2890844526 2890842807 IN IP4 10.47.16.5\r\n",
534 "s=-\r\n",
535 "t=2854678930 2854679000\r\n",
536 "m=audio 49170 RTP/AVP 0\r\n",
537 "c=IN IP4 10.47.16.6\r\n",
538 "b=AS:551\r\n",
539 );
540
541 let parsed_sdp = SessionDescription::try_from(Tokenizer::tokenize(sdp).unwrap().1).unwrap();
542 assert_eq!(parsed_sdp.to_string(), sdp);
543 }
544
545 #[test]
546 fn display3() {
547 let sdp = concat!(
548 "v=0\r\n",
549 "o=Alice 2890844526 2890842807 IN IP4 10.47.16.5\r\n",
550 "s=-\r\n",
551 "t=2854678930 2854679000\r\n",
552 );
553
554 let parsed_sdp = SessionDescription::try_from(Tokenizer::tokenize(sdp).unwrap().1).unwrap();
555 assert_eq!(parsed_sdp.to_string(), sdp);
556 }
557
558 #[test]
559 fn errors1() {
560 let sdp = concat!(
561 "v=0\r\n",
562 "o=Alice 2890844526 2890842807 IN IP4 10.47.16.5\r\n",
563 "s=-\r\n",
564 "i=A Seminar on the session description protocol\r\n",
565 "u=http://www.example.com/seminars/sdp.pdf\r\n",
566 "e=alice@example.com (Alice Smith)\r\n",
567 "p=+1 911-345-1160\r\n",
568 "c=IN IP4 10.47.16\r\n",
569 "b=CT:1024\r\n",
570 "t=2854678930 2854679000\r\n",
571 "r=604800 3600 0 90000\r\n",
572 "z=2882844526 -1h 2898848070 0h\r\n",
573 "k=clear:password\r\n",
574 "a=recvonly\r\n",
575 "m=audio 49170 RTP/AVP 0\r\n",
576 "i=audio media\r\n",
577 "c=IN IP4 10.47.16.5\r\n",
578 "c=IN IP4 10.47.16.6\r\n",
579 "b=CT:1000\r\n",
580 "b=AS:551\r\n",
581 "k=prompt\r\n",
582 "a=rtpmap:99 h232-199/90000\r\n",
583 "a=rtpmap:99 h263-1998/90000\r\n"
584 );
585
586 let parsed_sdp = SessionDescription::try_from(sdp);
587 assert!(parsed_sdp.is_err());
588 }
589}