pub(crate) mod sdp_type;
pub(crate) mod session_description;
pub use sdp_type::RTCSdpType;
pub use session_description::RTCSessionDescription;
use crate::media_stream::MediaStreamId;
use crate::media_stream::track::MediaStreamTrackId;
use crate::peer_connection::RTCPeerConnection;
use crate::peer_connection::configuration::media_engine::MediaEngine;
use crate::peer_connection::state::ice_gathering_state::RTCIceGatheringState;
use crate::peer_connection::transport::dtls::fingerprint::RTCDtlsFingerprint;
use crate::peer_connection::transport::ice::candidate::RTCIceCandidate;
use crate::peer_connection::transport::ice::parameters::RTCIceParameters;
use crate::rtp_transceiver::direction::RTCRtpTransceiverDirection;
use crate::rtp_transceiver::rtp_sender::rtp_codec::{RTCRtpCodec, RtpCodecKind};
use crate::rtp_transceiver::rtp_sender::rtp_codec_parameters::RTCRtpCodecParameters;
use crate::rtp_transceiver::{
PayloadType, RTCRtpTransceiver, RtpStreamId, SSRC, rtp_sender::rtcp_parameters::RTCPFeedback,
};
use ice::candidate::{Candidate, unmarshal_candidate};
use interceptor::Interceptor;
use sdp::description::common::{Address, ConnectionInformation};
use sdp::description::media::*;
use sdp::description::session::*;
use sdp::extmap::ExtMap;
use sdp::util::ConnectionRole;
use shared::error::{Error, Result};
use std::collections::HashMap;
use std::io::BufReader;
use std::str::FromStr;
use url::Url;
pub(crate) const SDP_ATTRIBUTE_RID: &str = "rid";
pub(crate) const SDP_ATTRIBUTE_SIMULCAST: &str = "simulcast";
pub(crate) const GENERATED_CERTIFICATE_ORIGIN: &str = "WebRTC";
pub(crate) const MEDIA_SECTION_APPLICATION: &str = "application";
#[derive(Default, Debug, Clone)]
pub(crate) struct TrackDetails {
pub(crate) mid: String,
pub(crate) kind: RtpCodecKind,
pub(crate) stream_id: MediaStreamId,
pub(crate) track_id: MediaStreamTrackId,
pub(crate) ssrc: Option<SSRC>,
pub(crate) rtx_ssrc: Option<SSRC>,
pub(crate) fec_ssrc: Option<SSRC>,
pub(crate) rids: Vec<String>,
}
pub(crate) fn track_details_for_ssrc(
track_details: &[TrackDetails],
ssrc: SSRC,
) -> Option<&TrackDetails> {
track_details.iter().find(|x| x.ssrc == Some(ssrc))
}
pub(crate) fn track_details_for_rid<'a>(
track_details: &'a [TrackDetails],
mid: &String,
rid: &String,
) -> Option<&'a TrackDetails> {
track_details
.iter()
.find(|x| &x.mid == mid && x.rids.contains(rid))
}
pub(crate) fn filter_track_with_ssrc(incoming_tracks: &mut Vec<TrackDetails>, ssrc: SSRC) {
incoming_tracks.retain(|x| x.ssrc != Some(ssrc));
}
pub(crate) fn track_details_from_sdp(s: &SessionDescription) -> Vec<TrackDetails> {
let mut incoming_tracks = vec![];
for media in &s.media_descriptions {
let mut tracks_in_media_section = vec![];
let mut rtx_repair_flows = HashMap::new();
let mut fec_repair_flows = HashMap::new();
let mut stream_id = "";
let mut track_id = "";
if media.attribute(ATTR_KEY_RECV_ONLY).is_some()
|| media.attribute(ATTR_KEY_INACTIVE).is_some()
{
continue;
}
let mid_value = match get_mid_value(media) {
Some(mid_value) if !mid_value.is_empty() => mid_value,
_ => continue,
};
let codec_type = RtpCodecKind::from(media.media_name.media.as_str());
if codec_type == RtpCodecKind::Unspecified {
continue;
}
for attr in &media.attributes {
match attr.key.as_str() {
ATTR_KEY_SSRC_GROUP => {
if let Some(value) = &attr.value {
let split: Vec<&str> = value.split(' ').collect();
if split[0] == SEMANTIC_TOKEN_FLOW_IDENTIFICATION {
if split.len() == 3 {
let base_ssrc = match split[1].parse::<u32>() {
Ok(ssrc) => ssrc,
Err(err) => {
log::warn!("Failed to parse SSRC: {err}");
continue;
}
};
let rtx_repair_flow = match split[2].parse::<u32>() {
Ok(n) => n,
Err(err) => {
log::warn!("Failed to parse SSRC: {err}");
continue;
}
};
rtx_repair_flows.insert(rtx_repair_flow, base_ssrc);
filter_track_with_ssrc(
&mut tracks_in_media_section,
rtx_repair_flow as SSRC,
);
tracks_in_media_section.iter_mut().for_each(|x| {
if x.ssrc.is_some_and(|ssrc| ssrc == base_ssrc) {
x.rtx_ssrc = Some(rtx_repair_flow);
}
});
}
} else if split[0] == SEMANTIC_TOKEN_FORWARD_ERROR_CORRECTION_FRAMEWORK {
if split.len() == 3 {
let base_ssrc = match split[1].parse::<u32>() {
Ok(ssrc) => ssrc,
Err(err) => {
log::warn!("Failed to parse SSRC: {err}");
continue;
}
};
let fec_repair_flow = match split[2].parse::<u32>() {
Ok(n) => n,
Err(err) => {
log::warn!("Failed to parse SSRC: {err}");
continue;
}
};
fec_repair_flows.insert(fec_repair_flow, base_ssrc);
filter_track_with_ssrc(
&mut tracks_in_media_section,
fec_repair_flow as SSRC,
);
tracks_in_media_section.iter_mut().for_each(|x| {
if x.ssrc.is_some_and(|ssrc| ssrc == base_ssrc) {
x.fec_ssrc = Some(fec_repair_flow);
}
});
}
}
}
}
ATTR_KEY_MSID => {
if let Some(value) = &attr.value {
let mut split = value.split(' ');
if let (Some(sid), Some(tid), None) =
(split.next(), split.next(), split.next())
{
stream_id = sid;
track_id = tid;
}
}
}
ATTR_KEY_SSRC => {
if let Some(value) = &attr.value {
let split: Vec<&str> = value.split(' ').collect();
let ssrc = match split[0].parse::<u32>() {
Ok(ssrc) => ssrc,
Err(err) => {
log::warn!("Failed to parse SSRC: {err}");
continue;
}
};
if rtx_repair_flows.contains_key(&ssrc) {
continue; }
if fec_repair_flows.contains_key(&ssrc) {
continue; }
if split.len() == 3 && split[1].starts_with("msid:") {
stream_id = &split[1]["msid:".len()..];
track_id = split[2];
}
let mut track_idx = tracks_in_media_section.len();
for (i, t) in tracks_in_media_section.iter().enumerate() {
if t.ssrc == Some(ssrc) {
track_idx = i;
break;
}
}
if track_idx < tracks_in_media_section.len() {
tracks_in_media_section[track_idx].mid = mid_value.to_string();
tracks_in_media_section[track_idx].kind = codec_type;
stream_id.clone_into(&mut tracks_in_media_section[track_idx].stream_id);
track_id.clone_into(&mut tracks_in_media_section[track_idx].track_id);
tracks_in_media_section[track_idx].ssrc = Some(ssrc);
} else {
let track_details = TrackDetails {
mid: mid_value.to_string(),
kind: codec_type,
stream_id: stream_id.to_owned(),
track_id: track_id.to_owned(),
ssrc: Some(ssrc),
..Default::default()
};
tracks_in_media_section.push(track_details);
}
}
}
_ => {}
};
}
for (repair_ssrc, base_ssrc) in rtx_repair_flows {
for track in &mut tracks_in_media_section {
if track.ssrc == Some(base_ssrc) {
track.rtx_ssrc = Some(repair_ssrc);
}
}
}
for (repair_ssrc, base_ssrc) in fec_repair_flows {
for track in &mut tracks_in_media_section {
if track.ssrc == Some(base_ssrc) {
track.fec_ssrc = Some(repair_ssrc);
}
}
}
if !track_id.is_empty() && !stream_id.is_empty() {
let rids = get_rids(media);
if !rids.is_empty() {
let simulcast_track = TrackDetails {
mid: mid_value.to_string(),
kind: codec_type,
stream_id: stream_id.to_owned(),
track_id: track_id.to_owned(),
rids: rids.iter().map(|r| r.id.clone()).collect(),
..Default::default()
};
tracks_in_media_section = vec![simulcast_track];
} else if tracks_in_media_section.is_empty() {
let implicit_track = TrackDetails {
mid: mid_value.to_string(),
kind: codec_type,
stream_id: stream_id.to_owned(),
track_id: track_id.to_owned(),
..Default::default()
};
tracks_in_media_section = vec![implicit_track];
}
}
incoming_tracks.extend(tracks_in_media_section);
}
incoming_tracks
}
pub(crate) fn get_rids(media: &MediaDescription) -> Vec<SimulcastRid> {
let mut rids = vec![];
let mut simulcast_attr: Option<String> = None;
for attr in &media.attributes {
if attr.key.as_str() == SDP_ATTRIBUTE_RID {
if let Err(err) = attr
.value
.as_ref()
.ok_or(Error::SimulcastRidParseErrorSyntaxIdDirSplit)
.and_then(SimulcastRid::try_from)
.map(|rid| rids.push(rid))
{
log::warn!("Failed to parse RID: {err}");
}
} else if attr.key.as_str() == SDP_ATTRIBUTE_SIMULCAST {
simulcast_attr.clone_from(&attr.value);
}
}
if let Some(attr) = simulcast_attr {
let mut split = attr.split(' ');
loop {
let _dir = split.next();
let sc_str_list = split.next();
if let Some(list) = sc_str_list {
let sc_list: Vec<&str> = list.split(';').flat_map(|alt| alt.split(',')).collect();
for sc_id in sc_list {
let (sc_id, paused) = if let Some(sc_id) = sc_id.strip_prefix('~') {
(sc_id, true)
} else {
(sc_id, false)
};
if let Some(rid) = rids.iter_mut().find(|f| f.id == sc_id) {
rid.paused = paused;
}
}
} else {
break;
}
}
}
rids
}
pub(crate) fn add_candidates_to_media_descriptions(
candidates: &[RTCIceCandidate],
mut m: MediaDescription,
ice_gathering_state: RTCIceGatheringState,
) -> Result<MediaDescription> {
let append_candidate_if_new = |c: &Candidate, m: MediaDescription| -> MediaDescription {
let marshaled = c.marshal();
for a in &m.attributes {
if let Some(value) = &a.value
&& &marshaled == value
{
return m;
}
}
m.with_value_attribute("candidate".to_owned(), marshaled)
};
for c in candidates {
let mut candidate = c.to_ice()?;
candidate.set_component(1);
m = append_candidate_if_new(&candidate, m);
candidate.set_component(2);
m = append_candidate_if_new(&candidate, m);
}
if ice_gathering_state != RTCIceGatheringState::Complete {
return Ok(m);
}
for a in &m.attributes {
if &a.key == "end-of-candidates" {
return Ok(m);
}
}
Ok(m.with_property_attribute("end-of-candidates".to_owned()))
}
pub(crate) struct AddDataMediaSectionParams {
should_add_candidates: bool,
mid_value: String,
ice_params: RTCIceParameters,
dtls_role: ConnectionRole,
ice_gathering_state: RTCIceGatheringState,
sctp_max_message_size: usize,
}
pub(crate) fn add_data_media_section(
d: SessionDescription,
dtls_fingerprints: &[RTCDtlsFingerprint],
candidates: &[RTCIceCandidate],
params: AddDataMediaSectionParams,
) -> Result<SessionDescription> {
let mut media = MediaDescription {
media_name: MediaName {
media: MEDIA_SECTION_APPLICATION.to_owned(),
port: RangedPort {
value: 9,
range: None,
},
protos: vec!["UDP".to_owned(), "DTLS".to_owned(), "SCTP".to_owned()],
formats: vec!["webrtc-datachannel".to_owned()],
},
media_title: None,
connection_information: Some(ConnectionInformation {
network_type: "IN".to_owned(),
address_type: "IP4".to_owned(),
address: Some(Address {
address: "0.0.0.0".to_owned(),
ttl: None,
range: None,
}),
}),
bandwidth: vec![],
encryption_key: None,
attributes: vec![],
}
.with_value_attribute(
ATTR_KEY_CONNECTION_SETUP.to_owned(),
params.dtls_role.to_string(),
)
.with_value_attribute(ATTR_KEY_MID.to_owned(), params.mid_value)
.with_property_attribute(RTCRtpTransceiverDirection::Sendrecv.to_string())
.with_property_attribute("sctp-port:5000".to_owned())
.with_value_attribute(
"max-message-size".to_owned(),
format!("{}", params.sctp_max_message_size),
)
.with_ice_credentials(
params.ice_params.username_fragment,
params.ice_params.password,
);
for f in dtls_fingerprints {
media = media.with_fingerprint(f.algorithm.clone(), f.value.to_uppercase());
}
if params.should_add_candidates {
media =
add_candidates_to_media_descriptions(candidates, media, params.ice_gathering_state)?;
}
Ok(d.with_media(media))
}
pub(crate) struct AddTransceiverSdpParams {
should_add_candidates: bool,
mid_value: String,
dtls_role: ConnectionRole,
ice_gathering_state: RTCIceGatheringState,
ignore_rid_pause_for_recv: bool,
write_ssrc_attributes_for_simulcast: bool,
}
impl<I> RTCPeerConnection<I>
where
I: Interceptor,
{
#[allow(clippy::too_many_arguments)]
pub(crate) fn add_transceiver_sdp(
mut d: SessionDescription,
dtls_fingerprints: &[RTCDtlsFingerprint],
media_engine: &MediaEngine,
transceivers: &mut [RTCRtpTransceiver<I>],
ice_params: &RTCIceParameters,
candidates: &[RTCIceCandidate],
media_section: &MediaSection,
params: AddTransceiverSdpParams,
) -> Result<(SessionDescription, bool)> {
let (
should_add_candidates,
mid_value,
dtls_role,
ice_gathering_state,
ignore_rid_pause_for_recv,
write_ssrc_attributes_for_simulcast,
) = (
params.should_add_candidates,
params.mid_value,
params.dtls_role,
params.ice_gathering_state,
params.ignore_rid_pause_for_recv,
params.write_ssrc_attributes_for_simulcast,
);
let transceiver = &mut transceivers[media_section.transceiver_index];
let mut media =
MediaDescription::new_jsep_media_description(transceiver.kind().to_string(), vec![])
.with_value_attribute(ATTR_KEY_CONNECTION_SETUP.to_owned(), dtls_role.to_string())
.with_value_attribute(ATTR_KEY_MID.to_owned(), mid_value.clone())
.with_ice_credentials(
ice_params.username_fragment.clone(),
ice_params.password.clone(),
)
.with_property_attribute(ATTR_KEY_RTCPMUX.to_owned())
.with_property_attribute(ATTR_KEY_RTCPRSIZE.to_owned());
let codecs = transceiver.get_codecs(media_engine);
for codec in &codecs {
let name = codec
.rtp_codec
.mime_type
.trim_start_matches("audio/")
.trim_start_matches("video/")
.to_owned();
media = media.with_codec(
codec.payload_type,
name,
codec.rtp_codec.clock_rate,
codec.rtp_codec.channels,
codec.rtp_codec.sdp_fmtp_line.clone(),
);
for feedback in &codec.rtp_codec.rtcp_feedback {
media = media.with_value_attribute(
"rtcp-fb".to_owned(),
if feedback.parameter.is_empty() {
format!("{} {}", codec.payload_type, feedback.typ)
} else {
format!(
"{} {} {}",
codec.payload_type, feedback.typ, feedback.parameter
)
},
);
}
}
if codecs.is_empty() {
if transceiver.sender().is_some() {
return Err(Error::ErrSenderWithNoCodecs);
}
d = d.with_media(
MediaDescription {
media_name: MediaName {
media: transceiver.kind().to_string(),
port: RangedPort {
value: 0,
range: None,
},
protos: vec![
"UDP".to_owned(),
"TLS".to_owned(),
"RTP".to_owned(),
"SAVPF".to_owned(),
],
formats: vec!["0".to_owned()],
},
media_title: None,
connection_information: Some(ConnectionInformation {
network_type: "IN".to_owned(),
address_type: "IP4".to_owned(),
address: Some(Address {
address: "0.0.0.0".to_owned(),
ttl: None,
range: None,
}),
}),
bandwidth: vec![],
encryption_key: None,
attributes: vec![],
}
.with_value_attribute(ATTR_KEY_MID.to_owned(), mid_value),
);
return Ok((d, false));
}
let parameters =
media_engine.get_rtp_parameters_by_kind(transceiver.kind(), transceiver.direction());
for rtp_extension in ¶meters.header_extensions {
if !media_section.match_extensions.is_empty()
&& !media_section
.match_extensions
.contains_key(&rtp_extension.uri)
{
continue;
}
let ext_url = Url::parse(rtp_extension.uri.as_str())?;
media = media.with_extmap(sdp::extmap::ExtMap {
value: rtp_extension.id,
uri: Some(ext_url),
..Default::default()
});
}
let mut recv_sc_list: Vec<String> = vec![];
let mut send_sc_list: Vec<String> = vec![];
if !media_section.rid_map.is_empty() {
for rid in &media_section.rid_map {
let rid_syntax = match rid.direction {
SimulcastDirection::Send => {
if rid.paused {
recv_sc_list.push(format!("~{}", rid.id));
} else {
recv_sc_list.push(rid.id.to_owned());
}
format!("{} recv", rid.id)
}
SimulcastDirection::Recv => {
if rid.paused && !ignore_rid_pause_for_recv {
send_sc_list.push(format!("~{}", rid.id));
} else {
send_sc_list.push(rid.id.to_owned());
}
format!("{} send", rid.id)
}
};
media = media.with_value_attribute(SDP_ATTRIBUTE_RID.to_owned(), rid_syntax);
}
}
media = {
let (media, send_rids) = RTCPeerConnection::add_sender_sdp(
media,
media_engine,
transceiver,
write_ssrc_attributes_for_simulcast,
);
let mut sc_attr = vec![];
if !recv_sc_list.is_empty() {
sc_attr.push(format!("recv {}", recv_sc_list.join(";")));
}
if !send_sc_list.is_empty() {
sc_attr.push(format!("send {}", send_sc_list.join(";")));
}
if !send_rids.is_empty() {
sc_attr.push(format!("send {}", send_rids.join(";")));
}
if !sc_attr.is_empty() {
media.with_value_attribute(SDP_ATTRIBUTE_SIMULCAST.to_owned(), sc_attr.join(" "))
} else {
media
}
};
media = media.with_property_attribute(transceiver.direction().to_string());
for fingerprint in dtls_fingerprints {
media = media.with_fingerprint(
fingerprint.algorithm.to_owned(),
fingerprint.value.to_uppercase(),
);
}
if should_add_candidates {
media = add_candidates_to_media_descriptions(candidates, media, ice_gathering_state)?;
}
Ok((d.with_media(media), true))
}
fn add_sender_sdp(
mut media: MediaDescription,
media_engine: &MediaEngine,
transceiver: &mut RTCRtpTransceiver<I>,
write_ssrc_attributes_for_simulcast: bool,
) -> (MediaDescription, Vec<RtpStreamId>) {
let mut send_rids = vec![];
if let Some(sender) = transceiver.sender_mut() {
let (encodings, track) = (
sender.get_parameters(media_engine).encodings.clone(),
sender.track(),
);
let is_simulcast = encodings.len() > 1;
media = media.with_property_attribute(format!(
"msid:{} {}",
track.stream_id(),
track.track_id()
));
if write_ssrc_attributes_for_simulcast || !is_simulcast {
for encoding in &encodings {
if let Some(&ssrc) = encoding.rtp_coding_parameters.ssrc.as_ref() {
if let Some(rtx) = encoding.rtp_coding_parameters.rtx.as_ref() {
media = media.with_value_attribute(
ATTR_KEY_SSRC_GROUP.to_owned(),
format!(
"{} {} {}",
SEMANTIC_TOKEN_FLOW_IDENTIFICATION, ssrc, rtx.ssrc
),
);
}
if let Some(fec) = encoding.rtp_coding_parameters.fec.as_ref() {
media = media.with_value_attribute(
ATTR_KEY_SSRC_GROUP.to_owned(),
format!(
"{} {} {}",
SEMANTIC_TOKEN_FORWARD_ERROR_CORRECTION, ssrc, fec.ssrc
),
);
}
media = media.with_media_source(
ssrc,
track.stream_id().clone(),
track.label().to_owned(),
track.track_id().to_owned(),
);
if let Some(rtx) = encoding.rtp_coding_parameters.rtx.as_ref() {
media = media.with_media_source(
rtx.ssrc,
track.stream_id().clone(),
track.label().to_owned(),
track.track_id().to_owned(),
);
}
if let Some(fec) = encoding.rtp_coding_parameters.fec.as_ref() {
media = media.with_media_source(
fec.ssrc,
track.stream_id().clone(),
track.label().to_owned(),
track.track_id().to_owned(),
);
}
}
}
}
if is_simulcast {
for encoding in &encodings {
media = media.with_value_attribute(
SDP_ATTRIBUTE_RID.to_owned(),
format!("{} send", encoding.rtp_coding_parameters.rid),
);
send_rids.push(encoding.rtp_coding_parameters.rid.to_string());
}
}
}
(media, send_rids)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum SimulcastDirection {
Send,
Recv,
}
impl TryFrom<&str> for SimulcastDirection {
type Error = Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"send" => Ok(SimulcastDirection::Send),
"recv" => Ok(SimulcastDirection::Recv),
_ => Err(Error::SimulcastRidParseErrorUnknownDirection),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct SimulcastRid {
pub(crate) id: String,
pub(crate) direction: SimulcastDirection,
pub(crate) params: String,
pub(crate) paused: bool,
}
impl TryFrom<&String> for SimulcastRid {
type Error = Error;
fn try_from(value: &String) -> std::result::Result<Self, Self::Error> {
let mut split = value.split(' ');
let id = split
.next()
.ok_or(Error::SimulcastRidParseErrorSyntaxIdDirSplit)?
.to_owned();
let direction = SimulcastDirection::try_from(
split
.next()
.ok_or(Error::SimulcastRidParseErrorSyntaxIdDirSplit)?,
)?;
let params = split.collect();
Ok(Self {
id,
direction,
params,
paused: false,
})
}
}
fn bundle_match(bundle: Option<&String>, id: &str) -> bool {
match bundle {
None => true,
Some(b) => b.split_whitespace().any(|s| s == id),
}
}
#[derive(Default)]
pub(crate) struct MediaSection {
pub(crate) mid: String,
pub(crate) transceiver_index: usize,
pub(crate) data: bool,
pub(crate) match_extensions: HashMap<String, u16>,
pub(crate) rid_map: Vec<SimulcastRid>,
}
pub(crate) struct PopulateSdpParams {
pub(crate) media_description_fingerprint: bool,
pub(crate) is_ice_lite: bool,
pub(crate) is_extmap_allow_mixed: bool,
pub(crate) connection_role: ConnectionRole,
pub(crate) ice_gathering_state: RTCIceGatheringState,
pub(crate) match_bundle_group: Option<String>,
pub(crate) sctp_max_message_size: usize,
pub(crate) ignore_rid_pause_for_recv: bool,
pub(crate) write_ssrc_attributes_for_simulcast: bool,
}
impl<I> RTCPeerConnection<I>
where
I: Interceptor,
{
#[allow(clippy::too_many_arguments)]
pub(crate) fn populate_sdp(
mut d: SessionDescription,
dtls_fingerprints: &[RTCDtlsFingerprint],
media_engine: &MediaEngine,
transceivers: &mut [RTCRtpTransceiver<I>],
candidates: &[RTCIceCandidate],
ice_params: &RTCIceParameters,
media_sections: &[MediaSection],
params: PopulateSdpParams,
) -> Result<SessionDescription> {
let media_dtls_fingerprints = if params.media_description_fingerprint {
dtls_fingerprints.to_vec()
} else {
vec![]
};
let mut bundle_value = "BUNDLE".to_owned();
let mut bundle_count = 0;
let append_bundle = |mid_value: &str, value: &mut String, count: &mut i32| {
*value = value.clone() + " " + mid_value;
*count += 1;
};
for (i, m) in media_sections.iter().enumerate() {
if m.data && m.transceiver_index != usize::MAX {
return Err(Error::ErrSDPMediaSectionMediaDataChanInvalid);
} else if !m.data && m.transceiver_index >= transceivers.len() {
return Err(Error::ErrSDPMediaSectionTrackInvalid);
}
let should_add_candidates = i == 0;
let should_add_id = if m.data {
let params = AddDataMediaSectionParams {
should_add_candidates,
mid_value: m.mid.clone(),
ice_params: ice_params.clone(),
dtls_role: params.connection_role,
ice_gathering_state: params.ice_gathering_state,
sctp_max_message_size: params.sctp_max_message_size,
};
d = add_data_media_section(d, &media_dtls_fingerprints, candidates, params)?;
true
} else {
let params = AddTransceiverSdpParams {
should_add_candidates,
mid_value: m.mid.clone(),
dtls_role: params.connection_role,
ice_gathering_state: params.ice_gathering_state,
ignore_rid_pause_for_recv: params.ignore_rid_pause_for_recv,
write_ssrc_attributes_for_simulcast: params.write_ssrc_attributes_for_simulcast,
};
let (d1, should_add_id) = RTCPeerConnection::add_transceiver_sdp(
d,
&media_dtls_fingerprints,
media_engine,
transceivers,
ice_params,
candidates,
m,
params,
)?;
d = d1;
should_add_id
};
if should_add_id {
if bundle_match(params.match_bundle_group.as_ref(), &m.mid) {
append_bundle(&m.mid, &mut bundle_value, &mut bundle_count);
} else if let Some(desc) = d.media_descriptions.last_mut() {
desc.media_name.port = RangedPort {
value: 0,
range: None,
}
}
}
}
if !params.media_description_fingerprint {
for fingerprint in dtls_fingerprints {
d = d.with_fingerprint(
fingerprint.algorithm.clone(),
fingerprint.value.to_uppercase(),
);
}
}
if params.is_ice_lite {
d = d.with_value_attribute(ATTR_KEY_ICELITE.to_owned(), ATTR_KEY_ICELITE.to_owned());
}
if params.is_extmap_allow_mixed {
d = d.with_property_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED.to_owned());
}
if bundle_count > 0 {
d = d.with_value_attribute(ATTR_KEY_GROUP.to_owned(), bundle_value);
}
Ok(d)
}
}
pub(crate) fn get_mid_value(media: &MediaDescription) -> Option<&String> {
for attr in &media.attributes {
if attr.key == "mid" {
return attr.value.as_ref();
}
}
None
}
pub(crate) fn get_peer_direction(media: &MediaDescription) -> RTCRtpTransceiverDirection {
for a in &media.attributes {
let direction = RTCRtpTransceiverDirection::from(a.key.as_str());
if direction != RTCRtpTransceiverDirection::Unspecified {
return direction;
}
}
RTCRtpTransceiverDirection::Unspecified
}
pub(crate) fn extract_fingerprint(desc: &SessionDescription) -> Result<(String, String)> {
let mut fingerprints = vec![];
if let Some(fingerprint) = desc.attribute("fingerprint") {
fingerprints.push(fingerprint.clone());
}
for m in &desc.media_descriptions {
if let Some(fingerprint) = m.attribute("fingerprint").and_then(|o| o) {
fingerprints.push(fingerprint.to_owned());
}
}
if fingerprints.is_empty() {
return Err(Error::ErrSessionDescriptionNoFingerprint);
}
for m in 1..fingerprints.len() {
if fingerprints[m] != fingerprints[0] {
return Err(Error::ErrSessionDescriptionConflictingFingerprints);
}
}
let parts: Vec<&str> = fingerprints[0].split(' ').collect();
if parts.len() != 2 {
return Err(Error::ErrSessionDescriptionInvalidFingerprint);
}
Ok((parts[1].to_owned(), parts[0].to_owned()))
}
pub(crate) fn extract_ice_details(
desc: &SessionDescription,
) -> Result<(String, String, Vec<Candidate>)> {
let mut candidates = vec![];
let mut backup_ufrag = None;
let mut backup_pwd = None;
let mut remote_ufrag = desc.attribute("ice-ufrag").map(|s| s.as_str());
let mut remote_pwd = desc.attribute("ice-pwd").map(|s| s.as_str());
for m in &desc.media_descriptions {
let ufrag = m.attribute("ice-ufrag").and_then(|o| o);
let pwd = m.attribute("ice-pwd").and_then(|o| o);
if m.attribute(ATTR_KEY_INACTIVE).is_some() {
if backup_ufrag.is_none() {
backup_ufrag = ufrag;
}
if backup_pwd.is_none() {
backup_pwd = pwd;
}
continue;
}
if remote_ufrag.is_none() {
remote_ufrag = ufrag;
}
if remote_pwd.is_none() {
remote_pwd = pwd;
}
if ufrag.is_some() && ufrag != remote_ufrag {
return Err(Error::ErrSessionDescriptionConflictingIceUfrag);
}
if pwd.is_some() && pwd != remote_pwd {
return Err(Error::ErrSessionDescriptionConflictingIcePwd);
}
for a in &m.attributes {
if a.is_ice_candidate()
&& let Some(value) = &a.value
{
let c: Candidate = unmarshal_candidate(value)?;
candidates.push(c);
}
}
}
let remote_ufrag = remote_ufrag
.or(backup_ufrag)
.ok_or(Error::ErrSessionDescriptionMissingIceUfrag)?;
let remote_pwd = remote_pwd
.or(backup_pwd)
.ok_or(Error::ErrSessionDescriptionMissingIcePwd)?;
Ok((remote_ufrag.to_owned(), remote_pwd.to_owned(), candidates))
}
pub(crate) fn is_lite_set(desc: &SessionDescription) -> bool {
for a in &desc.attributes {
if a.key.trim() == ATTR_KEY_ICELITE {
return true;
}
}
false
}
pub(crate) fn get_application_media_section_sctp_port(desc: &SessionDescription) -> Option<u16> {
for m in &desc.media_descriptions {
if m.media_name.media == MEDIA_SECTION_APPLICATION {
return if let Some(sctp_port_attr) =
m.attributes.iter().find(|attr| attr.key == "sctp-port")
{
let sctp_port_value = sctp_port_attr.value.as_ref();
sctp_port_value.and_then(|attr| attr.parse::<u16>().ok())
} else if let Some(sctp_port_attr) =
m.attributes.iter().find(|attr| attr.key == "sctpmap")
{
if let Some(sctp_port_attr_value) = sctp_port_attr.value.as_ref() {
sctp_port_attr_value
.split(" ")
.next()
.and_then(|port| u16::from_str(port).ok())
} else {
None
}
} else {
None
};
}
}
None
}
pub(crate) fn get_application_media_section_max_message_size(
desc: &SessionDescription,
) -> Option<u32> {
get_application_media(desc)?
.attribute(ATTR_KEY_MAX_MESSAGE_SIZE)??
.parse()
.ok()
}
pub(crate) fn get_by_mid<'a>(
search_mid: &str,
desc: &'a session_description::RTCSessionDescription,
) -> Option<&'a MediaDescription> {
if let Some(parsed) = &desc.parsed {
for m in &parsed.media_descriptions {
if let Some(mid) = m.attribute(ATTR_KEY_MID).flatten()
&& mid == search_mid
{
return Some(m);
}
}
}
None
}
pub(crate) fn get_application_media(desc: &SessionDescription) -> Option<&MediaDescription> {
desc.media_descriptions
.iter()
.find(|media_description| media_description.media_name.media == MEDIA_SECTION_APPLICATION)
}
pub(crate) fn have_data_channel(
desc: &session_description::RTCSessionDescription,
) -> Option<&MediaDescription> {
get_application_media(desc.parsed.as_ref()?)
}
pub(crate) fn codecs_from_media_description(
m: &MediaDescription,
) -> Result<Vec<RTCRtpCodecParameters>> {
let s = SessionDescription {
media_descriptions: vec![m.clone()],
..Default::default()
};
let mut out = vec![];
for payload_str in &m.media_name.formats {
let payload_type: PayloadType = payload_str.parse::<u8>()?;
let codec = match s.get_codec_for_payload_type(payload_type) {
Ok(codec) => codec,
Err(err) => {
if payload_type == 0 {
continue;
}
return Err(err);
}
};
let channels = codec.encoding_parameters.parse::<u16>().unwrap_or(0);
let mut feedback = vec![];
for raw in &codec.rtcp_feedback {
let split: Vec<&str> = raw.split(' ').collect();
let entry = if split.len() == 2 {
RTCPFeedback {
typ: split[0].to_string(),
parameter: split[1].to_string(),
}
} else {
RTCPFeedback {
typ: split[0].to_string(),
parameter: String::new(),
}
};
feedback.push(entry);
}
out.push(RTCRtpCodecParameters {
rtp_codec: RTCRtpCodec {
mime_type: m.media_name.media.clone() + "/" + codec.name.as_str(),
clock_rate: codec.clock_rate,
channels,
sdp_fmtp_line: codec.fmtp.clone(),
rtcp_feedback: feedback,
},
payload_type,
})
}
Ok(out)
}
pub(crate) fn rtp_extensions_from_media_description(
m: &MediaDescription,
) -> Result<HashMap<String, u16>> {
let mut out = HashMap::new();
for a in &m.attributes {
if a.key == ATTR_KEY_EXT_MAP {
let a_str = a.to_string();
let mut reader = BufReader::new(a_str.as_bytes());
let e = ExtMap::unmarshal(&mut reader)?;
if let Some(uri) = e.uri {
out.insert(uri.to_string(), e.value);
}
}
}
Ok(out)
}
pub(crate) fn update_sdp_origin(origin: &mut Origin, d: &mut SessionDescription) {
if origin.session_version == 0 {
origin.session_version = d.origin.session_version;
origin.session_id = d.origin.session_id;
} else {
d.origin.session_id = origin.session_id;
origin.session_version += 1;
d.origin.session_version += 1;
}
}
pub(crate) fn is_ext_map_allow_mixed_set(
remote_description: Option<&RTCSessionDescription>,
) -> bool {
remote_description.is_some_and(|r| {
r.parsed
.as_ref()
.is_some_and(|parsed| parsed.has_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED))
})
}
pub(crate) fn has_ice_trickle_option(desc: &SessionDescription) -> bool {
if let Some(value) = desc.attribute("ice-options")
&& value.split_whitespace().any(|opt| opt == "trickle")
{
return true;
}
for media in &desc.media_descriptions {
if let Some(Some(value)) = media.attribute("ice-options")
&& value.split_whitespace().any(|opt| opt == "trickle")
{
return true;
}
}
false
}