/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "signaling/src/sdp/SdpHelper.h" #include "signaling/src/sdp/Sdp.h" #include "signaling/src/sdp/SdpMediaSection.h" #include "logging.h" #include "nsDebug.h" #include "nsError.h" #include "prprf.h" #include #include namespace mozilla { MOZ_MTLOG_MODULE("sdp") #define SDP_SET_ERROR(error) \ do { \ std::ostringstream os; \ os << error; \ mLastError = os.str(); \ MOZ_MTLOG(ML_ERROR, mLastError); \ } while (0); nsresult SdpHelper::CopyTransportParams(size_t numComponents, const SdpMediaSection& oldLocal, SdpMediaSection* newLocal) { // Copy over m-section details newLocal->SetPort(oldLocal.GetPort()); newLocal->GetConnection() = oldLocal.GetConnection(); const SdpAttributeList& oldLocalAttrs = oldLocal.GetAttributeList(); SdpAttributeList& newLocalAttrs = newLocal->GetAttributeList(); // Now we copy over attributes that won't be added by the usual logic if (oldLocalAttrs.HasAttribute(SdpAttribute::kCandidateAttribute) && numComponents) { UniquePtr candidateAttrs( new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute)); for (const std::string& candidate : oldLocalAttrs.GetCandidate()) { size_t component; nsresult rv = GetComponent(candidate, &component); NS_ENSURE_SUCCESS(rv, rv); if (numComponents >= component) { candidateAttrs->mValues.push_back(candidate); } } if (candidateAttrs->mValues.size()) { newLocalAttrs.SetAttribute(candidateAttrs.release()); } } if (numComponents == 2 && oldLocalAttrs.HasAttribute(SdpAttribute::kRtcpAttribute)) { // copy rtcp attribute if we had one that we are using newLocalAttrs.SetAttribute(new SdpRtcpAttribute(oldLocalAttrs.GetRtcp())); } return NS_OK; } bool SdpHelper::AreOldTransportParamsValid(const Sdp& oldAnswer, const Sdp& offerersPreviousSdp, const Sdp& newOffer, size_t level) { if (MsectionIsDisabled(oldAnswer.GetMediaSection(level)) || MsectionIsDisabled(newOffer.GetMediaSection(level))) { // Obvious return false; } if (IsBundleSlave(oldAnswer, level)) { // The transport attributes on this m-section were thrown away, because it // was bundled. return false; } if (newOffer.GetMediaSection(level).GetAttributeList().HasAttribute( SdpAttribute::kBundleOnlyAttribute) && IsBundleSlave(newOffer, level)) { // It never makes sense to put transport attributes in a bundle-only // m-section return false; } if (IceCredentialsDiffer(newOffer.GetMediaSection(level), offerersPreviousSdp.GetMediaSection(level))) { return false; } return true; } bool SdpHelper::IceCredentialsDiffer(const SdpMediaSection& msection1, const SdpMediaSection& msection2) { const SdpAttributeList& attrs1(msection1.GetAttributeList()); const SdpAttributeList& attrs2(msection2.GetAttributeList()); if ((attrs1.GetIceUfrag() != attrs2.GetIceUfrag()) || (attrs1.GetIcePwd() != attrs2.GetIcePwd())) { return true; } return false; } nsresult SdpHelper::GetComponent(const std::string& candidate, size_t* component) { unsigned int temp; int32_t result = PR_sscanf(candidate.c_str(), "%*s %u", &temp); if (result == 1) { *component = temp; return NS_OK; } SDP_SET_ERROR("Malformed ICE candidate: " << candidate); return NS_ERROR_INVALID_ARG; } bool SdpHelper::MsectionIsDisabled(const SdpMediaSection& msection) const { return !msection.GetPort() && !msection.GetAttributeList().HasAttribute( SdpAttribute::kBundleOnlyAttribute); } void SdpHelper::DisableMsection(Sdp* sdp, SdpMediaSection* msection) { // Make sure to remove the mid from any group attributes if (msection->GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) { std::string mid = msection->GetAttributeList().GetMid(); if (sdp->GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) { UniquePtr newGroupAttr(new SdpGroupAttributeList( sdp->GetAttributeList().GetGroup())); newGroupAttr->RemoveMid(mid); sdp->GetAttributeList().SetAttribute(newGroupAttr.release()); } } // Clear out attributes. msection->GetAttributeList().Clear(); auto* direction = new SdpDirectionAttribute(SdpDirectionAttribute::kInactive); msection->GetAttributeList().SetAttribute(direction); msection->SetPort(0); msection->ClearCodecs(); auto mediaType = msection->GetMediaType(); switch (mediaType) { case SdpMediaSection::kAudio: msection->AddCodec("0", "PCMU", 8000, 1); break; case SdpMediaSection::kVideo: msection->AddCodec("120", "VP8", 90000, 1); break; case SdpMediaSection::kApplication: msection->AddDataChannel("5000", "rejected", 0); break; default: // We need to have something here to fit the grammar, this seems safe // and 19 is a reserved payload type which should not be used by anyone. msection->AddCodec("19", "reserved", 8000, 1); } } void SdpHelper::GetBundleGroups( const Sdp& sdp, std::vector* bundleGroups) const { if (sdp.GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) { for (auto& group : sdp.GetAttributeList().GetGroup().mGroups) { if (group.semantics == SdpGroupAttributeList::kBundle) { bundleGroups->push_back(group); } } } } nsresult SdpHelper::GetBundledMids(const Sdp& sdp, BundledMids* bundledMids) { std::vector bundleGroups; GetBundleGroups(sdp, &bundleGroups); for (SdpGroupAttributeList::Group& group : bundleGroups) { if (group.tags.empty()) { SDP_SET_ERROR("Empty BUNDLE group"); return NS_ERROR_INVALID_ARG; } const SdpMediaSection* masterBundleMsection( FindMsectionByMid(sdp, group.tags[0])); if (!masterBundleMsection) { SDP_SET_ERROR("mid specified for bundle transport in group attribute" " does not exist in the SDP. (mid=" << group.tags[0] << ")"); return NS_ERROR_INVALID_ARG; } if (MsectionIsDisabled(*masterBundleMsection)) { SDP_SET_ERROR("mid specified for bundle transport in group attribute" " points at a disabled m-section. (mid=" << group.tags[0] << ")"); return NS_ERROR_INVALID_ARG; } for (const std::string& mid : group.tags) { if (bundledMids->count(mid)) { SDP_SET_ERROR("mid \'" << mid << "\' appears more than once in a " "BUNDLE group"); return NS_ERROR_INVALID_ARG; } (*bundledMids)[mid] = masterBundleMsection; } } return NS_OK; } bool SdpHelper::IsBundleSlave(const Sdp& sdp, uint16_t level) { auto& msection = sdp.GetMediaSection(level); if (!msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) { // No mid, definitely no bundle for this m-section return false; } std::string mid(msection.GetAttributeList().GetMid()); BundledMids bundledMids; nsresult rv = GetBundledMids(sdp, &bundledMids); if (NS_FAILED(rv)) { // Should have been caught sooner. MOZ_ASSERT(false); return false; } if (bundledMids.count(mid) && level != bundledMids[mid]->GetLevel()) { // mid is bundled, and isn't the bundle m-section return true; } return false; } nsresult SdpHelper::GetMidFromLevel(const Sdp& sdp, uint16_t level, std::string* mid) { if (level >= sdp.GetMediaSectionCount()) { SDP_SET_ERROR("Index " << level << " out of range"); return NS_ERROR_INVALID_ARG; } const SdpMediaSection& msection = sdp.GetMediaSection(level); const SdpAttributeList& attrList = msection.GetAttributeList(); // grab the mid and set the outparam if (attrList.HasAttribute(SdpAttribute::kMidAttribute)) { *mid = attrList.GetMid(); } return NS_OK; } nsresult SdpHelper::AddCandidateToSdp(Sdp* sdp, const std::string& candidateUntrimmed, const std::string& mid, uint16_t level) { if (level >= sdp->GetMediaSectionCount()) { SDP_SET_ERROR("Index " << level << " out of range"); return NS_ERROR_INVALID_ARG; } // Trim off '[a=]candidate:' size_t begin = candidateUntrimmed.find(':'); if (begin == std::string::npos) { SDP_SET_ERROR("Invalid candidate, no ':' (" << candidateUntrimmed << ")"); return NS_ERROR_INVALID_ARG; } ++begin; std::string candidate = candidateUntrimmed.substr(begin); // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-11#section-3.4.2.1 // Implementations receiving an ICE Candidate object MUST use the MID if // present, or the m= line index, if not (as it could have come from a // non-JSEP endpoint). (bug 1095793) SdpMediaSection* msection = 0; if (!mid.empty()) { // FindMsectionByMid could return nullptr msection = FindMsectionByMid(*sdp, mid); // Check to make sure mid matches what we'd get by // looking up the m= line using the level. (mjf) std::string checkMid; nsresult rv = GetMidFromLevel(*sdp, level, &checkMid); if (NS_FAILED(rv)) { return rv; } if (mid != checkMid) { SDP_SET_ERROR("Mismatch between mid and level - \"" << mid << "\" is not the mid for level " << level << "; \"" << checkMid << "\" is"); return NS_ERROR_INVALID_ARG; } } if (!msection) { msection = &(sdp->GetMediaSection(level)); } SdpAttributeList& attrList = msection->GetAttributeList(); UniquePtr candidates; if (!attrList.HasAttribute(SdpAttribute::kCandidateAttribute)) { // Create new candidates.reset( new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute)); } else { // Copy existing candidates.reset(new SdpMultiStringAttribute( *static_cast( attrList.GetAttribute(SdpAttribute::kCandidateAttribute)))); } candidates->PushEntry(candidate); attrList.SetAttribute(candidates.release()); return NS_OK; } void SdpHelper::SetIceGatheringComplete(Sdp* sdp, uint16_t level, BundledMids bundledMids) { SdpMediaSection& msection = sdp->GetMediaSection(level); if (kSlaveBundle == GetMsectionBundleType(*sdp, level, bundledMids, nullptr)) { return; // Slave bundle m-section. Skip. } SdpAttributeList& attrs = msection.GetAttributeList(); attrs.SetAttribute( new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute)); // Remove trickle-ice option attrs.RemoveAttribute(SdpAttribute::kIceOptionsAttribute); } void SdpHelper::SetDefaultAddresses(const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort, const std::string& defaultRtcpCandidateAddr, uint16_t defaultRtcpCandidatePort, Sdp* sdp, uint16_t level, BundledMids bundledMids) { SdpMediaSection& msection = sdp->GetMediaSection(level); std::string masterMid; MsectionBundleType bundleType = GetMsectionBundleType(*sdp, level, bundledMids, &masterMid); if (kSlaveBundle == bundleType) { return; // Slave bundle m-section. Skip. } if (kMasterBundle == bundleType) { // Master bundle m-section. Set defaultCandidateAddr and // defaultCandidatePort on all bundled m-sections. const SdpMediaSection* masterBundleMsection(bundledMids[masterMid]); for (auto i = bundledMids.begin(); i != bundledMids.end(); ++i) { if (i->second != masterBundleMsection) { continue; } SdpMediaSection* bundledMsection = FindMsectionByMid(*sdp, i->first); if (!bundledMsection) { MOZ_ASSERT(false); continue; } SetDefaultAddresses(defaultCandidateAddr, defaultCandidatePort, defaultRtcpCandidateAddr, defaultRtcpCandidatePort, bundledMsection); } } SetDefaultAddresses(defaultCandidateAddr, defaultCandidatePort, defaultRtcpCandidateAddr, defaultRtcpCandidatePort, &msection); } void SdpHelper::SetDefaultAddresses(const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort, const std::string& defaultRtcpCandidateAddr, uint16_t defaultRtcpCandidatePort, SdpMediaSection* msection) { msection->GetConnection().SetAddress(defaultCandidateAddr); msection->SetPort(defaultCandidatePort); if (!defaultRtcpCandidateAddr.empty()) { sdp::AddrType ipVersion = sdp::kIPv4; if (defaultRtcpCandidateAddr.find(':') != std::string::npos) { ipVersion = sdp::kIPv6; } msection->GetAttributeList().SetAttribute(new SdpRtcpAttribute( defaultRtcpCandidatePort, sdp::kInternet, ipVersion, defaultRtcpCandidateAddr)); } } nsresult SdpHelper::GetIdsFromMsid(const Sdp& sdp, const SdpMediaSection& msection, std::string* streamId, std::string* trackId) { if (!sdp.GetAttributeList().HasAttribute( SdpAttribute::kMsidSemanticAttribute)) { return NS_ERROR_NOT_AVAILABLE; } auto& msidSemantics = sdp.GetAttributeList().GetMsidSemantic().mMsidSemantics; std::vector allMsids; nsresult rv = GetMsids(msection, &allMsids); NS_ENSURE_SUCCESS(rv, rv); bool allMsidsAreWebrtc = false; std::set webrtcMsids; for (auto i = msidSemantics.begin(); i != msidSemantics.end(); ++i) { if (i->semantic == "WMS") { for (auto j = i->msids.begin(); j != i->msids.end(); ++j) { if (*j == "*") { allMsidsAreWebrtc = true; } else { webrtcMsids.insert(*j); } } break; } } bool found = false; for (auto i = allMsids.begin(); i != allMsids.end(); ++i) { if (allMsidsAreWebrtc || webrtcMsids.count(i->identifier)) { if (i->appdata.empty()) { SDP_SET_ERROR("Invalid webrtc msid at level " << msection.GetLevel() << ": Missing track id."); return NS_ERROR_INVALID_ARG; } if (!found) { *streamId = i->identifier; *trackId = i->appdata; found = true; } else if ((*streamId != i->identifier) || (*trackId != i->appdata)) { SDP_SET_ERROR("Found multiple different webrtc msids in m-section " << msection.GetLevel() << ". The behavior here is " "undefined."); return NS_ERROR_INVALID_ARG; } } } if (!found) { return NS_ERROR_NOT_AVAILABLE; } return NS_OK; } nsresult SdpHelper::GetMsids(const SdpMediaSection& msection, std::vector* msids) { if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) { *msids = msection.GetAttributeList().GetMsid().mMsids; } // Can we find some additional msids in ssrc attributes? // (Chrome does not put plain-old msid attributes in its SDP) if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs; for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) { if (i->attribute.find("msid:") == 0) { std::string streamId; std::string trackId; nsresult rv = ParseMsid(i->attribute, &streamId, &trackId); NS_ENSURE_SUCCESS(rv, rv); msids->push_back({streamId, trackId}); } } } return NS_OK; } nsresult SdpHelper::ParseMsid(const std::string& msidAttribute, std::string* streamId, std::string* trackId) { // Would be nice if SdpSsrcAttributeList could parse out the contained // attribute, but at least the parse here is simple. // We are being very forgiving here wrt whitespace; tabs are not actually // allowed, nor is leading/trailing whitespace. size_t streamIdStart = msidAttribute.find_first_not_of(" \t", 5); // We do not assume the appdata token is here, since this is not // necessarily a webrtc msid if (streamIdStart == std::string::npos) { SDP_SET_ERROR("Malformed source-level msid attribute: " << msidAttribute); return NS_ERROR_INVALID_ARG; } size_t streamIdEnd = msidAttribute.find_first_of(" \t", streamIdStart); if (streamIdEnd == std::string::npos) { streamIdEnd = msidAttribute.size(); } size_t trackIdStart = msidAttribute.find_first_not_of(" \t", streamIdEnd); if (trackIdStart == std::string::npos) { trackIdStart = msidAttribute.size(); } size_t trackIdEnd = msidAttribute.find_first_of(" \t", trackIdStart); if (trackIdEnd == std::string::npos) { trackIdEnd = msidAttribute.size(); } size_t streamIdSize = streamIdEnd - streamIdStart; size_t trackIdSize = trackIdEnd - trackIdStart; *streamId = msidAttribute.substr(streamIdStart, streamIdSize); *trackId = msidAttribute.substr(trackIdStart, trackIdSize); return NS_OK; } void SdpHelper::SetupMsidSemantic(const std::vector& msids, Sdp* sdp) const { if (!msids.empty()) { UniquePtr msidSemantics( new SdpMsidSemanticAttributeList); msidSemantics->PushEntry("WMS", msids); sdp->GetAttributeList().SetAttribute(msidSemantics.release()); } } std::string SdpHelper::GetCNAME(const SdpMediaSection& msection) const { if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs; for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) { if (i->attribute.find("cname:") == 0) { return i->attribute.substr(6); } } } return ""; } const SdpMediaSection* SdpHelper::FindMsectionByMid(const Sdp& sdp, const std::string& mid) const { for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) { auto& attrs = sdp.GetMediaSection(i).GetAttributeList(); if (attrs.HasAttribute(SdpAttribute::kMidAttribute) && attrs.GetMid() == mid) { return &sdp.GetMediaSection(i); } } return nullptr; } SdpMediaSection* SdpHelper::FindMsectionByMid(Sdp& sdp, const std::string& mid) const { for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) { auto& attrs = sdp.GetMediaSection(i).GetAttributeList(); if (attrs.HasAttribute(SdpAttribute::kMidAttribute) && attrs.GetMid() == mid) { return &sdp.GetMediaSection(i); } } return nullptr; } nsresult SdpHelper::CopyStickyParams(const SdpMediaSection& source, SdpMediaSection* dest) { auto& sourceAttrs = source.GetAttributeList(); auto& destAttrs = dest->GetAttributeList(); // There's no reason to renegotiate rtcp-mux if (sourceAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) { destAttrs.SetAttribute( new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute)); } // mid should stay the same if (sourceAttrs.HasAttribute(SdpAttribute::kMidAttribute)) { destAttrs.SetAttribute( new SdpStringAttribute(SdpAttribute::kMidAttribute, sourceAttrs.GetMid())); } return NS_OK; } bool SdpHelper::HasRtcp(SdpMediaSection::Protocol proto) const { switch (proto) { case SdpMediaSection::kRtpAvpf: case SdpMediaSection::kDccpRtpAvpf: case SdpMediaSection::kDccpRtpSavpf: case SdpMediaSection::kRtpSavpf: case SdpMediaSection::kUdpTlsRtpSavpf: case SdpMediaSection::kTcpTlsRtpSavpf: case SdpMediaSection::kDccpTlsRtpSavpf: return true; case SdpMediaSection::kRtpAvp: case SdpMediaSection::kUdp: case SdpMediaSection::kVat: case SdpMediaSection::kRtp: case SdpMediaSection::kUdptl: case SdpMediaSection::kTcp: case SdpMediaSection::kTcpRtpAvp: case SdpMediaSection::kRtpSavp: case SdpMediaSection::kTcpBfcp: case SdpMediaSection::kTcpTlsBfcp: case SdpMediaSection::kTcpTls: case SdpMediaSection::kFluteUdp: case SdpMediaSection::kTcpMsrp: case SdpMediaSection::kTcpTlsMsrp: case SdpMediaSection::kDccp: case SdpMediaSection::kDccpRtpAvp: case SdpMediaSection::kDccpRtpSavp: case SdpMediaSection::kUdpTlsRtpSavp: case SdpMediaSection::kTcpTlsRtpSavp: case SdpMediaSection::kDccpTlsRtpSavp: case SdpMediaSection::kUdpMbmsFecRtpAvp: case SdpMediaSection::kUdpMbmsFecRtpSavp: case SdpMediaSection::kUdpMbmsRepair: case SdpMediaSection::kFecUdp: case SdpMediaSection::kUdpFec: case SdpMediaSection::kTcpMrcpv2: case SdpMediaSection::kTcpTlsMrcpv2: case SdpMediaSection::kPstn: case SdpMediaSection::kUdpTlsUdptl: case SdpMediaSection::kSctp: case SdpMediaSection::kSctpDtls: case SdpMediaSection::kDtlsSctp: return false; } MOZ_CRASH("Unknown protocol, probably corruption."); } SdpMediaSection::Protocol SdpHelper::GetProtocolForMediaType(SdpMediaSection::MediaType type) { if (type == SdpMediaSection::kApplication) { return SdpMediaSection::kDtlsSctp; } return SdpMediaSection::kUdpTlsRtpSavpf; } void SdpHelper::appendSdpParseErrors( const std::vector >& aErrors, std::string* aErrorString) { std::ostringstream os; for (auto i = aErrors.begin(); i != aErrors.end(); ++i) { os << "SDP Parse Error on line " << i->first << ": " + i->second << std::endl; } *aErrorString += os.str(); } /* static */ bool SdpHelper::GetPtAsInt(const std::string& ptString, uint16_t* ptOutparam) { char* end; unsigned long pt = strtoul(ptString.c_str(), &end, 10); size_t length = static_cast(end - ptString.c_str()); if ((pt > UINT16_MAX) || (length != ptString.size())) { return false; } *ptOutparam = pt; return true; } void SdpHelper::AddCommonExtmaps( const SdpMediaSection& remoteMsection, const std::vector& localExtensions, SdpMediaSection* localMsection) { if (!remoteMsection.GetAttributeList().HasAttribute( SdpAttribute::kExtmapAttribute)) { return; } UniquePtr localExtmap(new SdpExtmapAttributeList); auto& theirExtmap = remoteMsection.GetAttributeList().GetExtmap().mExtmaps; for (auto i = theirExtmap.begin(); i != theirExtmap.end(); ++i) { for (auto j = localExtensions.begin(); j != localExtensions.end(); ++j) { // verify we have a valid combination of directions. For kInactive // we'll just not add the response if (i->extensionname == j->extensionname && (((i->direction == SdpDirectionAttribute::Direction::kSendrecv || i->direction == SdpDirectionAttribute::Direction::kSendonly) && (j->direction == SdpDirectionAttribute::Direction::kSendrecv || j->direction == SdpDirectionAttribute::Direction::kRecvonly)) || ((i->direction == SdpDirectionAttribute::Direction::kSendrecv || i->direction == SdpDirectionAttribute::Direction::kRecvonly) && (j->direction == SdpDirectionAttribute::Direction::kSendrecv || j->direction == SdpDirectionAttribute::Direction::kSendonly)))) { auto k = *i; // we need to modify it if (j->direction == SdpDirectionAttribute::Direction::kSendonly) { k.direction = SdpDirectionAttribute::Direction::kRecvonly; } else if (j->direction == SdpDirectionAttribute::Direction::kRecvonly) { k.direction = SdpDirectionAttribute::Direction::kSendonly; } localExtmap->mExtmaps.push_back(k); // RFC 5285 says that ids >= 4096 can be used by the offerer to // force the answerer to pick, otherwise the value in the offer is // used. if (localExtmap->mExtmaps.back().entry >= 4096) { localExtmap->mExtmaps.back().entry = j->entry; } } } } if (!localExtmap->mExtmaps.empty()) { localMsection->GetAttributeList().SetAttribute(localExtmap.release()); } } SdpHelper::MsectionBundleType SdpHelper::GetMsectionBundleType(const Sdp& sdp, uint16_t level, BundledMids& bundledMids, std::string* masterMid) const { const SdpMediaSection& msection = sdp.GetMediaSection(level); if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) { std::string mid(msection.GetAttributeList().GetMid()); if (bundledMids.count(mid)) { const SdpMediaSection* masterBundleMsection(bundledMids[mid]); if (msection.GetLevel() != masterBundleMsection->GetLevel()) { return kSlaveBundle; } // allow the caller not to care about the masterMid if (masterMid) { *masterMid = mid; } return kMasterBundle; } } return kNoBundle; } } // namespace mozilla