1 #[cfg(test)]
2 mod sdp_test;
3
4 use crate::api::media_engine::MediaEngine;
5 use crate::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint;
6 use crate::error::{Error, Result};
7 use crate::ice_transport::ice_candidate::RTCIceCandidate;
8 use crate::ice_transport::ice_gatherer::RTCIceGatherer;
9 use crate::ice_transport::ice_gathering_state::RTCIceGatheringState;
10 use crate::ice_transport::ice_parameters::RTCIceParameters;
11 use crate::rtp_transceiver::rtp_codec::{
12 RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType,
13 };
14 use crate::rtp_transceiver::rtp_transceiver_direction::RTCRtpTransceiverDirection;
15 use crate::rtp_transceiver::RTCRtpTransceiver;
16 use crate::rtp_transceiver::{PayloadType, RTCPFeedback, SSRC};
17
18 pub mod sdp_type;
19 pub mod session_description;
20
21 use crate::peer_connection::MEDIA_SECTION_APPLICATION;
22 use crate::SDP_ATTRIBUTE_RID;
23 use ice::candidate::candidate_base::unmarshal_candidate;
24 use ice::candidate::Candidate;
25 use sdp::description::common::{Address, ConnectionInformation};
26 use sdp::description::media::{MediaDescription, MediaName, RangedPort};
27 use sdp::description::session::*;
28 use sdp::extmap::ExtMap;
29 use sdp::util::ConnectionRole;
30 use std::collections::HashMap;
31 use std::convert::From;
32 use std::io::BufReader;
33 use std::sync::Arc;
34 use url::Url;
35
36 /// TrackDetails represents any media source that can be represented in a SDP
37 /// This isn't keyed by SSRC because it also needs to support rid based sources
38 #[derive(Default, Debug, Clone)]
39 pub(crate) struct TrackDetails {
40 pub(crate) mid: String,
41 pub(crate) kind: RTPCodecType,
42 pub(crate) stream_id: String,
43 pub(crate) id: String,
44 pub(crate) ssrcs: Vec<SSRC>,
45 pub(crate) repair_ssrc: SSRC,
46 pub(crate) rids: Vec<String>,
47 }
48
track_details_for_ssrc( track_details: &[TrackDetails], ssrc: SSRC, ) -> Option<&TrackDetails>49 pub(crate) fn track_details_for_ssrc(
50 track_details: &[TrackDetails],
51 ssrc: SSRC,
52 ) -> Option<&TrackDetails> {
53 track_details.iter().find(|x| x.ssrcs.contains(&ssrc))
54 }
55
track_details_for_rid( track_details: &[TrackDetails], rid: String, ) -> Option<&TrackDetails>56 pub(crate) fn track_details_for_rid(
57 track_details: &[TrackDetails],
58 rid: String,
59 ) -> Option<&TrackDetails> {
60 track_details.iter().find(|x| x.rids.contains(&rid))
61 }
62
filter_track_with_ssrc(incoming_tracks: &mut Vec<TrackDetails>, ssrc: SSRC)63 pub(crate) fn filter_track_with_ssrc(incoming_tracks: &mut Vec<TrackDetails>, ssrc: SSRC) {
64 incoming_tracks.retain(|x| !x.ssrcs.contains(&ssrc));
65 }
66
67 /// extract all TrackDetails from an SDP.
track_details_from_sdp( s: &SessionDescription, exclude_inactive: bool, ) -> Vec<TrackDetails>68 pub(crate) fn track_details_from_sdp(
69 s: &SessionDescription,
70 exclude_inactive: bool,
71 ) -> Vec<TrackDetails> {
72 let mut incoming_tracks = vec![];
73
74 for media in &s.media_descriptions {
75 let mut tracks_in_media_section = vec![];
76 let mut rtx_repair_flows = HashMap::new();
77
78 let mut stream_id = "";
79 let mut track_id = "";
80
81 // If media section is recvonly or inactive skip
82 if media.attribute(ATTR_KEY_RECV_ONLY).is_some()
83 || (exclude_inactive && media.attribute(ATTR_KEY_INACTIVE).is_some())
84 {
85 continue;
86 }
87
88 let mid_value = match get_mid_value(media) {
89 Some(mid_value) => mid_value,
90 None => continue,
91 };
92
93 let codec_type = RTPCodecType::from(media.media_name.media.as_str());
94 if codec_type == RTPCodecType::Unspecified {
95 continue;
96 }
97
98 for attr in &media.attributes {
99 match attr.key.as_str() {
100 ATTR_KEY_SSRCGROUP => {
101 if let Some(value) = &attr.value {
102 let split: Vec<&str> = value.split(' ').collect();
103 if split[0] == SEMANTIC_TOKEN_FLOW_IDENTIFICATION {
104 // Add rtx ssrcs to blacklist, to avoid adding them as tracks
105 // Essentially lines like `a=ssrc-group:FID 2231627014 632943048` are processed by this section
106 // as this declares that the second SSRC (632943048) is a rtx repair flow (RFC4588) for the first
107 // (2231627014) as specified in RFC5576
108 if split.len() == 3 {
109 let base_ssrc = match split[1].parse::<u32>() {
110 Ok(ssrc) => ssrc,
111 Err(err) => {
112 log::warn!("Failed to parse SSRC: {}", err);
113 continue;
114 }
115 };
116 let rtx_repair_flow = match split[2].parse::<u32>() {
117 Ok(n) => n,
118 Err(err) => {
119 log::warn!("Failed to parse SSRC: {}", err);
120 continue;
121 }
122 };
123 rtx_repair_flows.insert(rtx_repair_flow, base_ssrc);
124 // Remove if rtx was added as track before
125 filter_track_with_ssrc(
126 &mut tracks_in_media_section,
127 rtx_repair_flow as SSRC,
128 );
129 }
130 }
131 }
132 }
133
134 // Handle `a=msid:<stream_id> <track_label>` The first value is the same as MediaStream.id
135 // in the browser and can be used to figure out which tracks belong to the same stream. The browser should
136 // figure this out automatically when an ontrack event is emitted on RTCPeerConnection.
137 ATTR_KEY_MSID => {
138 if let Some(value) = &attr.value {
139 let mut split = value.split(' ');
140
141 if let (Some(sid), Some(tid), None) =
142 (split.next(), split.next(), split.next())
143 {
144 stream_id = sid;
145 track_id = tid;
146 }
147 }
148 }
149
150 ATTR_KEY_SSRC => {
151 if let Some(value) = &attr.value {
152 let split: Vec<&str> = value.split(' ').collect();
153 let ssrc = match split[0].parse::<u32>() {
154 Ok(ssrc) => ssrc,
155 Err(err) => {
156 log::warn!("Failed to parse SSRC: {}", err);
157 continue;
158 }
159 };
160
161 if rtx_repair_flows.contains_key(&ssrc) {
162 continue; // This ssrc is a RTX repair flow, ignore
163 }
164
165 if split.len() == 3 && split[1].starts_with("msid:") {
166 stream_id = &split[1]["msid:".len()..];
167 track_id = split[2];
168 }
169
170 let mut track_idx = tracks_in_media_section.len();
171
172 for (i, t) in tracks_in_media_section.iter().enumerate() {
173 if t.ssrcs.contains(&ssrc) {
174 track_idx = i;
175 //TODO: no break?
176 }
177 }
178
179 let mut repair_ssrc = 0;
180 for (repair, base) in &rtx_repair_flows {
181 if *base == ssrc {
182 repair_ssrc = *repair;
183 //TODO: no break?
184 }
185 }
186
187 if track_idx < tracks_in_media_section.len() {
188 tracks_in_media_section[track_idx].mid = mid_value.to_owned();
189 tracks_in_media_section[track_idx].kind = codec_type;
190 tracks_in_media_section[track_idx].stream_id = stream_id.to_owned();
191 tracks_in_media_section[track_idx].id = track_id.to_owned();
192 tracks_in_media_section[track_idx].ssrcs = vec![ssrc];
193 tracks_in_media_section[track_idx].repair_ssrc = repair_ssrc;
194 } else {
195 let track_details = TrackDetails {
196 mid: mid_value.to_owned(),
197 kind: codec_type,
198 stream_id: stream_id.to_owned(),
199 id: track_id.to_owned(),
200 ssrcs: vec![ssrc],
201 repair_ssrc,
202 ..Default::default()
203 };
204 tracks_in_media_section.push(track_details);
205 }
206 }
207 }
208 _ => {}
209 };
210 }
211
212 let rids = get_rids(media);
213 if !rids.is_empty() && !track_id.is_empty() && !stream_id.is_empty() {
214 let mut simulcast_track = TrackDetails {
215 mid: mid_value.to_owned(),
216 kind: codec_type,
217 stream_id: stream_id.to_owned(),
218 id: track_id.to_owned(),
219 rids: vec![],
220 ..Default::default()
221 };
222 for rid in rids.keys() {
223 simulcast_track.rids.push(rid.to_owned());
224 }
225 if simulcast_track.rids.len() == tracks_in_media_section.len() {
226 for track in &tracks_in_media_section {
227 simulcast_track.ssrcs.extend(&track.ssrcs)
228 }
229 }
230
231 tracks_in_media_section = vec![simulcast_track];
232 }
233
234 incoming_tracks.extend(tracks_in_media_section);
235 }
236
237 incoming_tracks
238 }
239
get_rids(media: &MediaDescription) -> HashMap<String, String>240 pub(crate) fn get_rids(media: &MediaDescription) -> HashMap<String, String> {
241 let mut rids = HashMap::new();
242 for attr in &media.attributes {
243 if attr.key.as_str() == SDP_ATTRIBUTE_RID {
244 if let Some(value) = &attr.value {
245 let split: Vec<&str> = value.split(' ').collect();
246 rids.insert(split[0].to_owned(), value.to_owned());
247 }
248 }
249 }
250 rids
251 }
252
add_candidates_to_media_descriptions( candidates: &[RTCIceCandidate], mut m: MediaDescription, ice_gathering_state: RTCIceGatheringState, ) -> Result<MediaDescription>253 pub(crate) async fn add_candidates_to_media_descriptions(
254 candidates: &[RTCIceCandidate],
255 mut m: MediaDescription,
256 ice_gathering_state: RTCIceGatheringState,
257 ) -> Result<MediaDescription> {
258 let append_candidate_if_new = |c: &dyn Candidate, m: MediaDescription| -> MediaDescription {
259 let marshaled = c.marshal();
260 for a in &m.attributes {
261 if let Some(value) = &a.value {
262 if &marshaled == value {
263 return m;
264 }
265 }
266 }
267
268 m.with_value_attribute("candidate".to_owned(), marshaled)
269 };
270
271 for c in candidates {
272 let candidate = c.to_ice()?;
273
274 candidate.set_component(1);
275 m = append_candidate_if_new(&candidate, m);
276
277 candidate.set_component(2);
278 m = append_candidate_if_new(&candidate, m);
279 }
280
281 if ice_gathering_state != RTCIceGatheringState::Complete {
282 return Ok(m);
283 }
284 for a in &m.attributes {
285 if &a.key == "end-of-candidates" {
286 return Ok(m);
287 }
288 }
289
290 Ok(m.with_property_attribute("end-of-candidates".to_owned()))
291 }
292
293 pub(crate) struct AddDataMediaSectionParams {
294 should_add_candidates: bool,
295 mid_value: String,
296 ice_params: RTCIceParameters,
297 dtls_role: ConnectionRole,
298 ice_gathering_state: RTCIceGatheringState,
299 }
300
add_data_media_section( d: SessionDescription, dtls_fingerprints: &[RTCDtlsFingerprint], candidates: &[RTCIceCandidate], params: AddDataMediaSectionParams, ) -> Result<SessionDescription>301 pub(crate) async fn add_data_media_section(
302 d: SessionDescription,
303 dtls_fingerprints: &[RTCDtlsFingerprint],
304 candidates: &[RTCIceCandidate],
305 params: AddDataMediaSectionParams,
306 ) -> Result<SessionDescription> {
307 let mut media = MediaDescription {
308 media_name: MediaName {
309 media: MEDIA_SECTION_APPLICATION.to_owned(),
310 port: RangedPort {
311 value: 9,
312 range: None,
313 },
314 protos: vec!["UDP".to_owned(), "DTLS".to_owned(), "SCTP".to_owned()],
315 formats: vec!["webrtc-datachannel".to_owned()],
316 },
317 media_title: None,
318 connection_information: Some(ConnectionInformation {
319 network_type: "IN".to_owned(),
320 address_type: "IP4".to_owned(),
321 address: Some(Address {
322 address: "0.0.0.0".to_owned(),
323 ttl: None,
324 range: None,
325 }),
326 }),
327 bandwidth: vec![],
328 encryption_key: None,
329 attributes: vec![],
330 }
331 .with_value_attribute(
332 ATTR_KEY_CONNECTION_SETUP.to_owned(),
333 params.dtls_role.to_string(),
334 )
335 .with_value_attribute(ATTR_KEY_MID.to_owned(), params.mid_value)
336 .with_property_attribute(RTCRtpTransceiverDirection::Sendrecv.to_string())
337 .with_property_attribute("sctp-port:5000".to_owned())
338 .with_ice_credentials(
339 params.ice_params.username_fragment,
340 params.ice_params.password,
341 );
342
343 for f in dtls_fingerprints {
344 media = media.with_fingerprint(f.algorithm.clone(), f.value.to_uppercase());
345 }
346
347 if params.should_add_candidates {
348 media = add_candidates_to_media_descriptions(candidates, media, params.ice_gathering_state)
349 .await?;
350 }
351
352 Ok(d.with_media(media))
353 }
354
populate_local_candidates( session_description: Option<&session_description::RTCSessionDescription>, ice_gatherer: Option<&Arc<RTCIceGatherer>>, ice_gathering_state: RTCIceGatheringState, ) -> Option<session_description::RTCSessionDescription>355 pub(crate) async fn populate_local_candidates(
356 session_description: Option<&session_description::RTCSessionDescription>,
357 ice_gatherer: Option<&Arc<RTCIceGatherer>>,
358 ice_gathering_state: RTCIceGatheringState,
359 ) -> Option<session_description::RTCSessionDescription> {
360 if session_description.is_none() || ice_gatherer.is_none() {
361 return session_description.cloned();
362 }
363
364 if let (Some(sd), Some(ice)) = (session_description, ice_gatherer) {
365 let candidates = match ice.get_local_candidates().await {
366 Ok(candidates) => candidates,
367 Err(_) => return Some(sd.clone()),
368 };
369
370 let mut parsed = match sd.unmarshal() {
371 Ok(parsed) => parsed,
372 Err(_) => return Some(sd.clone()),
373 };
374
375 if !parsed.media_descriptions.is_empty() {
376 let mut m = parsed.media_descriptions.remove(0);
377 m = match add_candidates_to_media_descriptions(&candidates, m, ice_gathering_state)
378 .await
379 {
380 Ok(m) => m,
381 Err(_) => return Some(sd.clone()),
382 };
383 parsed.media_descriptions.insert(0, m);
384 }
385
386 Some(session_description::RTCSessionDescription {
387 sdp_type: sd.sdp_type,
388 sdp: parsed.marshal(),
389 parsed: Some(parsed),
390 })
391 } else {
392 None
393 }
394 }
395
396 pub(crate) struct AddTransceiverSdpParams {
397 should_add_candidates: bool,
398 mid_value: String,
399 dtls_role: ConnectionRole,
400 ice_gathering_state: RTCIceGatheringState,
401 offered_direction: Option<RTCRtpTransceiverDirection>,
402 }
403
add_transceiver_sdp( mut d: SessionDescription, dtls_fingerprints: &[RTCDtlsFingerprint], media_engine: &Arc<MediaEngine>, ice_params: &RTCIceParameters, candidates: &[RTCIceCandidate], media_section: &MediaSection, params: AddTransceiverSdpParams, ) -> Result<(SessionDescription, bool)>404 pub(crate) async fn add_transceiver_sdp(
405 mut d: SessionDescription,
406 dtls_fingerprints: &[RTCDtlsFingerprint],
407 media_engine: &Arc<MediaEngine>,
408 ice_params: &RTCIceParameters,
409 candidates: &[RTCIceCandidate],
410 media_section: &MediaSection,
411 params: AddTransceiverSdpParams,
412 ) -> Result<(SessionDescription, bool)> {
413 if media_section.transceivers.is_empty() {
414 return Err(Error::ErrSDPZeroTransceivers);
415 }
416 let (should_add_candidates, mid_value, dtls_role, ice_gathering_state) = (
417 params.should_add_candidates,
418 params.mid_value,
419 params.dtls_role,
420 params.ice_gathering_state,
421 );
422
423 let transceivers = &media_section.transceivers;
424 // Use the first transceiver to generate the section attributes
425 let t = &transceivers[0];
426 let mut media = MediaDescription::new_jsep_media_description(t.kind.to_string(), vec![])
427 .with_value_attribute(ATTR_KEY_CONNECTION_SETUP.to_owned(), dtls_role.to_string())
428 .with_value_attribute(ATTR_KEY_MID.to_owned(), mid_value.clone())
429 .with_ice_credentials(
430 ice_params.username_fragment.clone(),
431 ice_params.password.clone(),
432 )
433 .with_property_attribute(ATTR_KEY_RTCPMUX.to_owned())
434 .with_property_attribute(ATTR_KEY_RTCPRSIZE.to_owned());
435
436 let codecs = t.get_codecs().await;
437 for codec in &codecs {
438 let name = codec
439 .capability
440 .mime_type
441 .trim_start_matches("audio/")
442 .trim_start_matches("video/")
443 .to_owned();
444 media = media.with_codec(
445 codec.payload_type,
446 name,
447 codec.capability.clock_rate,
448 codec.capability.channels,
449 codec.capability.sdp_fmtp_line.clone(),
450 );
451
452 for feedback in &codec.capability.rtcp_feedback {
453 media = media.with_value_attribute(
454 "rtcp-fb".to_owned(),
455 format!(
456 "{} {} {}",
457 codec.payload_type, feedback.typ, feedback.parameter
458 ),
459 );
460 }
461 }
462 if codecs.is_empty() {
463 // If we are sender and we have no codecs throw an error early
464 if t.sender().track().await.is_some() {
465 return Err(Error::ErrSenderWithNoCodecs);
466 }
467
468 // Explicitly reject track if we don't have the codec
469 d = d.with_media(MediaDescription {
470 media_name: sdp::description::media::MediaName {
471 media: t.kind.to_string(),
472 port: RangedPort {
473 value: 0,
474 range: None,
475 },
476 protos: vec![
477 "UDP".to_owned(),
478 "TLS".to_owned(),
479 "RTP".to_owned(),
480 "SAVPF".to_owned(),
481 ],
482 formats: vec!["0".to_owned()],
483 },
484 media_title: None,
485 // We need to include connection information even if we're rejecting a track, otherwise Firefox will fail to
486 // parse the SDP with an error like:
487 // SIPCC Failed to parse SDP: SDP Parse Error on line 50: c= connection line not specified for every media level, validation failed.
488 // In addition this makes our SDP compliant with RFC 4566 Section 5.7: https://datatracker.ietf.org/doc/html/rfc4566#section-5.7
489 connection_information: Some(ConnectionInformation {
490 network_type: "IN".to_owned(),
491 address_type: "IP4".to_owned(),
492 address: Some(Address {
493 address: "0.0.0.0".to_owned(),
494 ttl: None,
495 range: None,
496 }),
497 }),
498 bandwidth: vec![],
499 encryption_key: None,
500 attributes: vec![],
501 });
502 return Ok((d, false));
503 }
504
505 let parameters = media_engine.get_rtp_parameters_by_kind(t.kind, t.direction());
506 for rtp_extension in ¶meters.header_extensions {
507 let ext_url = Url::parse(rtp_extension.uri.as_str())?;
508 media = media.with_extmap(sdp::extmap::ExtMap {
509 value: rtp_extension.id,
510 uri: Some(ext_url),
511 ..Default::default()
512 });
513 }
514
515 if !media_section.rid_map.is_empty() {
516 let mut recv_rids: Vec<String> = vec![];
517
518 for rid in media_section.rid_map.keys() {
519 media =
520 media.with_value_attribute(SDP_ATTRIBUTE_RID.to_owned(), rid.to_owned() + " recv");
521 recv_rids.push(rid.to_owned());
522 }
523 // Simulcast
524 media = media.with_value_attribute(
525 "simulcast".to_owned(),
526 "recv ".to_owned() + recv_rids.join(";").as_str(),
527 );
528 }
529
530 for mt in transceivers {
531 let sender = mt.sender();
532 if let Some(track) = sender.track().await {
533 media = media.with_media_source(
534 sender.ssrc,
535 track.stream_id().to_owned(), /* cname */
536 track.stream_id().to_owned(), /* streamLabel */
537 track.id().to_owned(),
538 );
539
540 // Send msid based on the configured track if we haven't already
541 // sent on this sender. If we have sent we must keep the msid line consistent, this
542 // is handled below.
543 if sender.initial_track_id().is_none() {
544 for stream_id in sender.associated_media_stream_ids() {
545 media =
546 media.with_property_attribute(format!("msid:{} {}", stream_id, track.id()));
547 }
548
549 sender.set_initial_track_id(track.id().to_string())?;
550 break;
551 }
552 }
553
554 if let Some(track_id) = sender.initial_track_id() {
555 // After we have include an msid attribute in an offer it must stay the same for
556 // all subsequent offer even if the track or transceiver direction changes.
557 //
558 // [RFC 8829 Section 5.2.2](https://datatracker.ietf.org/doc/html/rfc8829#section-5.2.2)
559 //
560 // For RtpTransceivers that are not stopped, the "a=msid" line or
561 // lines MUST stay the same if they are present in the current
562 // description, regardless of changes to the transceiver's direction
563 // or track. If no "a=msid" line is present in the current
564 // description, "a=msid" line(s) MUST be generated according to the
565 // same rules as for an initial offer.
566 for stream_id in sender.associated_media_stream_ids() {
567 media = media.with_property_attribute(format!("msid:{stream_id} {track_id}"));
568 }
569
570 break;
571 }
572 }
573
574 let direction = match params.offered_direction {
575 Some(offered_direction) => {
576 use RTCRtpTransceiverDirection::*;
577 let transceiver_direction = t.direction();
578
579 match offered_direction {
580 Sendonly | Recvonly => {
581 // If a stream is offered as sendonly, the corresponding stream MUST be
582 // marked as recvonly or inactive in the answer.
583
584 // If a media stream is
585 // listed as recvonly in the offer, the answer MUST be marked as
586 // sendonly or inactive in the answer.
587 offered_direction.reverse().intersect(transceiver_direction)
588 }
589 // If an offered media stream is
590 // listed as sendrecv (or if there is no direction attribute at the
591 // media or session level, in which case the stream is sendrecv by
592 // default), the corresponding stream in the answer MAY be marked as
593 // sendonly, recvonly, sendrecv, or inactive
594 Sendrecv | Unspecified => t.direction(),
595 // If an offered media
596 // stream is listed as inactive, it MUST be marked as inactive in the
597 // answer.
598 Inactive => Inactive,
599 }
600 }
601 None => {
602 // If don't have an offered direction to intersect with just use the transceivers
603 // current direction.
604 //
605 // https://datatracker.ietf.org/doc/html/rfc8829#section-4.2.3
606 //
607 // When creating offers, the transceiver direction is directly reflected
608 // in the output, even for re-offers.
609 t.direction()
610 }
611 };
612 media = media.with_property_attribute(direction.to_string());
613
614 for fingerprint in dtls_fingerprints {
615 media = media.with_fingerprint(
616 fingerprint.algorithm.to_owned(),
617 fingerprint.value.to_uppercase(),
618 );
619 }
620
621 if should_add_candidates {
622 media =
623 add_candidates_to_media_descriptions(candidates, media, ice_gathering_state).await?;
624 }
625
626 Ok((d.with_media(media), true))
627 }
628
629 #[derive(Default)]
630 pub(crate) struct MediaSection {
631 pub(crate) id: String,
632 pub(crate) transceivers: Vec<Arc<RTCRtpTransceiver>>,
633 pub(crate) data: bool,
634 pub(crate) rid_map: HashMap<String, String>,
635 pub(crate) offered_direction: Option<RTCRtpTransceiverDirection>,
636 }
637
638 pub(crate) struct PopulateSdpParams {
639 pub(crate) media_description_fingerprint: bool,
640 pub(crate) is_icelite: bool,
641 pub(crate) connection_role: ConnectionRole,
642 pub(crate) ice_gathering_state: RTCIceGatheringState,
643 }
644
645 /// populate_sdp serializes a PeerConnections state into an SDP
populate_sdp( mut d: SessionDescription, dtls_fingerprints: &[RTCDtlsFingerprint], media_engine: &Arc<MediaEngine>, candidates: &[RTCIceCandidate], ice_params: &RTCIceParameters, media_sections: &[MediaSection], params: PopulateSdpParams, ) -> Result<SessionDescription>646 pub(crate) async fn populate_sdp(
647 mut d: SessionDescription,
648 dtls_fingerprints: &[RTCDtlsFingerprint],
649 media_engine: &Arc<MediaEngine>,
650 candidates: &[RTCIceCandidate],
651 ice_params: &RTCIceParameters,
652 media_sections: &[MediaSection],
653 params: PopulateSdpParams,
654 ) -> Result<SessionDescription> {
655 let media_dtls_fingerprints = if params.media_description_fingerprint {
656 dtls_fingerprints.to_vec()
657 } else {
658 vec![]
659 };
660
661 let mut bundle_value = "BUNDLE".to_owned();
662 let mut bundle_count = 0;
663 let append_bundle = |mid_value: &str, value: &mut String, count: &mut i32| {
664 *value = value.clone() + " " + mid_value;
665 *count += 1;
666 };
667
668 for (i, m) in media_sections.iter().enumerate() {
669 if m.data && !m.transceivers.is_empty() {
670 return Err(Error::ErrSDPMediaSectionMediaDataChanInvalid);
671 } else if m.transceivers.len() > 1 {
672 return Err(Error::ErrSDPMediaSectionMultipleTrackInvalid);
673 }
674
675 let should_add_candidates = i == 0;
676
677 let should_add_id = if m.data {
678 let params = AddDataMediaSectionParams {
679 should_add_candidates,
680 mid_value: m.id.clone(),
681 ice_params: ice_params.clone(),
682 dtls_role: params.connection_role,
683 ice_gathering_state: params.ice_gathering_state,
684 };
685 d = add_data_media_section(d, &media_dtls_fingerprints, candidates, params).await?;
686 true
687 } else {
688 let params = AddTransceiverSdpParams {
689 should_add_candidates,
690 mid_value: m.id.clone(),
691 dtls_role: params.connection_role,
692 ice_gathering_state: params.ice_gathering_state,
693 offered_direction: m.offered_direction,
694 };
695 let (d1, should_add_id) = add_transceiver_sdp(
696 d,
697 &media_dtls_fingerprints,
698 media_engine,
699 ice_params,
700 candidates,
701 m,
702 params,
703 )
704 .await?;
705 d = d1;
706 should_add_id
707 };
708
709 if should_add_id {
710 append_bundle(&m.id, &mut bundle_value, &mut bundle_count);
711 }
712 }
713
714 if !params.media_description_fingerprint {
715 for fingerprint in dtls_fingerprints {
716 d = d.with_fingerprint(
717 fingerprint.algorithm.clone(),
718 fingerprint.value.to_uppercase(),
719 );
720 }
721 }
722
723 if params.is_icelite {
724 // RFC 5245 S15.3
725 d = d.with_value_attribute(ATTR_KEY_ICELITE.to_owned(), ATTR_KEY_ICELITE.to_owned());
726 }
727
728 Ok(d.with_value_attribute(ATTR_KEY_GROUP.to_owned(), bundle_value))
729 }
730
get_mid_value(media: &MediaDescription) -> Option<&String>731 pub(crate) fn get_mid_value(media: &MediaDescription) -> Option<&String> {
732 for attr in &media.attributes {
733 if attr.key == "mid" {
734 return attr.value.as_ref();
735 }
736 }
737 None
738 }
739
get_peer_direction(media: &MediaDescription) -> RTCRtpTransceiverDirection740 pub(crate) fn get_peer_direction(media: &MediaDescription) -> RTCRtpTransceiverDirection {
741 for a in &media.attributes {
742 let direction = RTCRtpTransceiverDirection::from(a.key.as_str());
743 if direction != RTCRtpTransceiverDirection::Unspecified {
744 return direction;
745 }
746 }
747 RTCRtpTransceiverDirection::Unspecified
748 }
749
extract_fingerprint(desc: &SessionDescription) -> Result<(String, String)>750 pub(crate) fn extract_fingerprint(desc: &SessionDescription) -> Result<(String, String)> {
751 let mut fingerprints = vec![];
752
753 if let Some(fingerprint) = desc.attribute("fingerprint") {
754 fingerprints.push(fingerprint.clone());
755 }
756
757 for m in &desc.media_descriptions {
758 if let Some(fingerprint) = m.attribute("fingerprint").and_then(|o| o) {
759 fingerprints.push(fingerprint.to_owned());
760 }
761 }
762
763 if fingerprints.is_empty() {
764 return Err(Error::ErrSessionDescriptionNoFingerprint);
765 }
766
767 for m in 1..fingerprints.len() {
768 if fingerprints[m] != fingerprints[0] {
769 return Err(Error::ErrSessionDescriptionConflictingFingerprints);
770 }
771 }
772
773 let parts: Vec<&str> = fingerprints[0].split(' ').collect();
774 if parts.len() != 2 {
775 return Err(Error::ErrSessionDescriptionInvalidFingerprint);
776 }
777
778 Ok((parts[1].to_owned(), parts[0].to_owned()))
779 }
780
extract_ice_details( desc: &SessionDescription, ) -> Result<(String, String, Vec<RTCIceCandidate>)>781 pub(crate) async fn extract_ice_details(
782 desc: &SessionDescription,
783 ) -> Result<(String, String, Vec<RTCIceCandidate>)> {
784 let mut candidates = vec![];
785 let mut remote_pwds = vec![];
786 let mut remote_ufrags = vec![];
787
788 if let Some(ufrag) = desc.attribute("ice-ufrag") {
789 remote_ufrags.push(ufrag.clone());
790 }
791 if let Some(pwd) = desc.attribute("ice-pwd") {
792 remote_pwds.push(pwd.clone());
793 }
794
795 for m in &desc.media_descriptions {
796 if let Some(ufrag) = m.attribute("ice-ufrag").and_then(|o| o) {
797 remote_ufrags.push(ufrag.to_owned());
798 }
799 if let Some(pwd) = m.attribute("ice-pwd").and_then(|o| o) {
800 remote_pwds.push(pwd.to_owned());
801 }
802
803 for a in &m.attributes {
804 if a.is_ice_candidate() {
805 if let Some(value) = &a.value {
806 let c: Arc<dyn Candidate + Send + Sync> = Arc::new(unmarshal_candidate(value)?);
807 let candidate = RTCIceCandidate::from(&c);
808 candidates.push(candidate);
809 }
810 }
811 }
812 }
813
814 if remote_ufrags.is_empty() {
815 return Err(Error::ErrSessionDescriptionMissingIceUfrag);
816 } else if remote_pwds.is_empty() {
817 return Err(Error::ErrSessionDescriptionMissingIcePwd);
818 }
819
820 for m in 1..remote_ufrags.len() {
821 if remote_ufrags[m] != remote_ufrags[0] {
822 return Err(Error::ErrSessionDescriptionConflictingIceUfrag);
823 }
824 }
825
826 for m in 1..remote_pwds.len() {
827 if remote_pwds[m] != remote_pwds[0] {
828 return Err(Error::ErrSessionDescriptionConflictingIcePwd);
829 }
830 }
831
832 Ok((remote_ufrags[0].clone(), remote_pwds[0].clone(), candidates))
833 }
834
have_application_media_section(desc: &SessionDescription) -> bool835 pub(crate) fn have_application_media_section(desc: &SessionDescription) -> bool {
836 for m in &desc.media_descriptions {
837 if m.media_name.media == MEDIA_SECTION_APPLICATION {
838 return true;
839 }
840 }
841
842 false
843 }
844
get_by_mid<'a>( search_mid: &str, desc: &'a session_description::RTCSessionDescription, ) -> Option<&'a MediaDescription>845 pub(crate) fn get_by_mid<'a>(
846 search_mid: &str,
847 desc: &'a session_description::RTCSessionDescription,
848 ) -> Option<&'a MediaDescription> {
849 if let Some(parsed) = &desc.parsed {
850 for m in &parsed.media_descriptions {
851 if let Some(mid) = m.attribute(ATTR_KEY_MID).flatten() {
852 if mid == search_mid {
853 return Some(m);
854 }
855 }
856 }
857 }
858 None
859 }
860
861 /// have_data_channel return MediaDescription with MediaName equal application
have_data_channel( desc: &session_description::RTCSessionDescription, ) -> Option<&MediaDescription>862 pub(crate) fn have_data_channel(
863 desc: &session_description::RTCSessionDescription,
864 ) -> Option<&MediaDescription> {
865 if let Some(parsed) = &desc.parsed {
866 for d in &parsed.media_descriptions {
867 if d.media_name.media == MEDIA_SECTION_APPLICATION {
868 return Some(d);
869 }
870 }
871 }
872 None
873 }
874
codecs_from_media_description( m: &MediaDescription, ) -> Result<Vec<RTCRtpCodecParameters>>875 pub(crate) fn codecs_from_media_description(
876 m: &MediaDescription,
877 ) -> Result<Vec<RTCRtpCodecParameters>> {
878 let s = SessionDescription {
879 media_descriptions: vec![m.clone()],
880 ..Default::default()
881 };
882
883 let mut out = vec![];
884 for payload_str in &m.media_name.formats {
885 let payload_type: PayloadType = payload_str.parse::<u8>()?;
886 let codec = match s.get_codec_for_payload_type(payload_type) {
887 Ok(codec) => codec,
888 Err(err) => {
889 if payload_type == 0 {
890 continue;
891 }
892 return Err(err.into());
893 }
894 };
895
896 let channels = codec.encoding_parameters.parse::<u16>().unwrap_or(0);
897
898 let mut feedback = vec![];
899 for raw in &codec.rtcp_feedback {
900 let split: Vec<&str> = raw.split(' ').collect();
901
902 let entry = if split.len() == 2 {
903 RTCPFeedback {
904 typ: split[0].to_string(),
905 parameter: split[1].to_string(),
906 }
907 } else {
908 RTCPFeedback {
909 typ: split[0].to_string(),
910 parameter: String::new(),
911 }
912 };
913
914 feedback.push(entry);
915 }
916
917 out.push(RTCRtpCodecParameters {
918 capability: RTCRtpCodecCapability {
919 mime_type: m.media_name.media.clone() + "/" + codec.name.as_str(),
920 clock_rate: codec.clock_rate,
921 channels,
922 sdp_fmtp_line: codec.fmtp.clone(),
923 rtcp_feedback: feedback,
924 },
925 payload_type,
926 stats_id: String::new(),
927 })
928 }
929
930 Ok(out)
931 }
932
rtp_extensions_from_media_description( m: &MediaDescription, ) -> Result<HashMap<String, isize>>933 pub(crate) fn rtp_extensions_from_media_description(
934 m: &MediaDescription,
935 ) -> Result<HashMap<String, isize>> {
936 let mut out = HashMap::new();
937
938 for a in &m.attributes {
939 if a.key == ATTR_KEY_EXT_MAP {
940 let a_str = a.to_string();
941 let mut reader = BufReader::new(a_str.as_bytes());
942 let e = ExtMap::unmarshal(&mut reader)?;
943
944 if let Some(uri) = e.uri {
945 out.insert(uri.to_string(), e.value);
946 }
947 }
948 }
949
950 Ok(out)
951 }
952
953 /// update_sdp_origin saves sdp.Origin in PeerConnection when creating 1st local SDP;
954 /// for subsequent calling, it updates Origin for SessionDescription from saved one
955 /// and increments session version by one.
956 /// <https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-25#section-5.2.2>
update_sdp_origin(origin: &mut Origin, d: &mut SessionDescription)957 pub(crate) fn update_sdp_origin(origin: &mut Origin, d: &mut SessionDescription) {
958 //TODO: if atomic.CompareAndSwapUint64(&origin.SessionVersion, 0, d.Origin.SessionVersion)
959 if origin.session_version == 0 {
960 // store
961 origin.session_version = d.origin.session_version;
962 //atomic.StoreUint64(&origin.SessionID, d.Origin.SessionID)
963 origin.session_id = d.origin.session_id;
964 } else {
965 // load
966 /*for { // awaiting for saving session id
967 d.Origin.SessionID = atomic.LoadUint64(&origin.SessionID)
968 if d.Origin.SessionID != 0 {
969 break
970 }
971 }*/
972 d.origin.session_id = origin.session_id;
973
974 //d.Origin.SessionVersion = atomic.AddUint64(&origin.SessionVersion, 1)
975 origin.session_version += 1;
976 d.origin.session_version += 1;
977 }
978 }
979