375 lines
13 KiB
C++
375 lines
13 KiB
C++
/*
|
|
* Copyright 2021 The WebRTC Project Authors. All rights reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "pc/jsep_transport_collection.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <set>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "p2p/base/p2p_constants.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
namespace webrtc {
|
|
|
|
void BundleManager::Update(const cricket::SessionDescription* description,
|
|
SdpType type) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
// Rollbacks should call Rollback, not Update.
|
|
RTC_DCHECK(type != SdpType::kRollback);
|
|
bool bundle_groups_changed = false;
|
|
// TODO(bugs.webrtc.org/3349): Do this for kPrAnswer as well. To make this
|
|
// work, we also need to make sure PRANSWERs don't call
|
|
// MaybeDestroyJsepTransport, because the final answer may need the destroyed
|
|
// transport if it changes the BUNDLE group.
|
|
if (bundle_policy_ == PeerConnectionInterface::kBundlePolicyMaxBundle ||
|
|
type == SdpType::kAnswer) {
|
|
// If our policy is "max-bundle" or this is an answer, update all bundle
|
|
// groups.
|
|
bundle_groups_changed = true;
|
|
bundle_groups_.clear();
|
|
for (const cricket::ContentGroup* new_bundle_group :
|
|
description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
|
|
bundle_groups_.push_back(
|
|
std::make_unique<cricket::ContentGroup>(*new_bundle_group));
|
|
RTC_DLOG(LS_VERBOSE) << "Establishing bundle group "
|
|
<< new_bundle_group->ToString();
|
|
}
|
|
} else if (type == SdpType::kOffer) {
|
|
// If this is an offer, update existing bundle groups.
|
|
// We do this because as per RFC 8843, section 7.3.2, the answerer cannot
|
|
// remove an m= section from an existing BUNDLE group without rejecting it.
|
|
// Thus any m= sections added to a BUNDLE group in this offer can
|
|
// preemptively start using the bundled transport, as there is no possible
|
|
// non-bundled fallback.
|
|
for (const cricket::ContentGroup* new_bundle_group :
|
|
description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
|
|
// Attempt to find a matching existing group.
|
|
for (const std::string& mid : new_bundle_group->content_names()) {
|
|
auto it = established_bundle_groups_by_mid_.find(mid);
|
|
if (it != established_bundle_groups_by_mid_.end()) {
|
|
*it->second = *new_bundle_group;
|
|
bundle_groups_changed = true;
|
|
RTC_DLOG(LS_VERBOSE)
|
|
<< "Establishing bundle group " << new_bundle_group->ToString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bundle_groups_changed) {
|
|
RefreshEstablishedBundleGroupsByMid();
|
|
}
|
|
}
|
|
|
|
const cricket::ContentGroup* BundleManager::LookupGroupByMid(
|
|
const std::string& mid) const {
|
|
auto it = established_bundle_groups_by_mid_.find(mid);
|
|
return it != established_bundle_groups_by_mid_.end() ? it->second : nullptr;
|
|
}
|
|
bool BundleManager::IsFirstMidInGroup(const std::string& mid) const {
|
|
auto group = LookupGroupByMid(mid);
|
|
if (!group) {
|
|
return true; // Unbundled MIDs are considered group leaders
|
|
}
|
|
return mid == *(group->FirstContentName());
|
|
}
|
|
|
|
cricket::ContentGroup* BundleManager::LookupGroupByMid(const std::string& mid) {
|
|
auto it = established_bundle_groups_by_mid_.find(mid);
|
|
return it != established_bundle_groups_by_mid_.end() ? it->second : nullptr;
|
|
}
|
|
|
|
void BundleManager::DeleteMid(const cricket::ContentGroup* bundle_group,
|
|
const std::string& mid) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
RTC_LOG(LS_VERBOSE) << "Deleting mid " << mid << " from bundle group "
|
|
<< bundle_group->ToString();
|
|
// Remove the rejected content from the `bundle_group`.
|
|
// The const pointer arg is used to identify the group, we verify
|
|
// it before we use it to make a modification.
|
|
auto bundle_group_it = std::find_if(
|
|
bundle_groups_.begin(), bundle_groups_.end(),
|
|
[bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
|
|
return bundle_group == group.get();
|
|
});
|
|
RTC_DCHECK(bundle_group_it != bundle_groups_.end());
|
|
(*bundle_group_it)->RemoveContentName(mid);
|
|
established_bundle_groups_by_mid_.erase(
|
|
established_bundle_groups_by_mid_.find(mid));
|
|
}
|
|
|
|
void BundleManager::DeleteGroup(const cricket::ContentGroup* bundle_group) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
RTC_DLOG(LS_VERBOSE) << "Deleting bundle group " << bundle_group->ToString();
|
|
|
|
auto bundle_group_it = std::find_if(
|
|
bundle_groups_.begin(), bundle_groups_.end(),
|
|
[bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
|
|
return bundle_group == group.get();
|
|
});
|
|
RTC_DCHECK(bundle_group_it != bundle_groups_.end());
|
|
auto mid_list = (*bundle_group_it)->content_names();
|
|
for (const auto& content_name : mid_list) {
|
|
DeleteMid(bundle_group, content_name);
|
|
}
|
|
bundle_groups_.erase(bundle_group_it);
|
|
}
|
|
|
|
void BundleManager::Rollback() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
bundle_groups_.clear();
|
|
for (const auto& bundle_group : stable_bundle_groups_) {
|
|
bundle_groups_.push_back(
|
|
std::make_unique<cricket::ContentGroup>(*bundle_group));
|
|
}
|
|
RefreshEstablishedBundleGroupsByMid();
|
|
}
|
|
|
|
void BundleManager::Commit() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
stable_bundle_groups_.clear();
|
|
for (const auto& bundle_group : bundle_groups_) {
|
|
stable_bundle_groups_.push_back(
|
|
std::make_unique<cricket::ContentGroup>(*bundle_group));
|
|
}
|
|
}
|
|
|
|
void BundleManager::RefreshEstablishedBundleGroupsByMid() {
|
|
established_bundle_groups_by_mid_.clear();
|
|
for (const auto& bundle_group : bundle_groups_) {
|
|
for (const std::string& content_name : bundle_group->content_names()) {
|
|
established_bundle_groups_by_mid_[content_name] = bundle_group.get();
|
|
}
|
|
}
|
|
}
|
|
|
|
void JsepTransportCollection::RegisterTransport(
|
|
const std::string& mid,
|
|
std::unique_ptr<cricket::JsepTransport> transport) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
SetTransportForMid(mid, transport.get());
|
|
jsep_transports_by_name_[mid] = std::move(transport);
|
|
RTC_DCHECK(IsConsistent());
|
|
}
|
|
|
|
std::vector<cricket::JsepTransport*> JsepTransportCollection::Transports() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
std::vector<cricket::JsepTransport*> result;
|
|
for (auto& kv : jsep_transports_by_name_) {
|
|
result.push_back(kv.second.get());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<cricket::JsepTransport*>
|
|
JsepTransportCollection::ActiveTransports() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
std::set<cricket::JsepTransport*> transports;
|
|
for (const auto& kv : mid_to_transport_) {
|
|
transports.insert(kv.second);
|
|
}
|
|
return std::vector<cricket::JsepTransport*>(transports.begin(),
|
|
transports.end());
|
|
}
|
|
|
|
void JsepTransportCollection::DestroyAllTransports() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
for (const auto& jsep_transport : jsep_transports_by_name_) {
|
|
map_change_callback_(jsep_transport.first, nullptr);
|
|
}
|
|
jsep_transports_by_name_.clear();
|
|
RTC_DCHECK(IsConsistent());
|
|
}
|
|
|
|
const cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
|
|
const std::string& transport_name) const {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
auto it = jsep_transports_by_name_.find(transport_name);
|
|
return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
|
|
}
|
|
|
|
cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
|
|
const std::string& transport_name) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
auto it = jsep_transports_by_name_.find(transport_name);
|
|
return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
|
|
}
|
|
|
|
cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
|
|
const std::string& mid) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
auto it = mid_to_transport_.find(mid);
|
|
return it == mid_to_transport_.end() ? nullptr : it->second;
|
|
}
|
|
|
|
const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
|
|
const std::string& mid) const {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
auto it = mid_to_transport_.find(mid);
|
|
return it == mid_to_transport_.end() ? nullptr : it->second;
|
|
}
|
|
|
|
bool JsepTransportCollection::SetTransportForMid(
|
|
const std::string& mid,
|
|
cricket::JsepTransport* jsep_transport) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
RTC_DCHECK(jsep_transport);
|
|
|
|
auto it = mid_to_transport_.find(mid);
|
|
if (it != mid_to_transport_.end() && it->second == jsep_transport)
|
|
return true;
|
|
|
|
// The map_change_callback must be called before destroying the
|
|
// transport, because it removes references to the transport
|
|
// in the RTP demuxer.
|
|
bool result = map_change_callback_(mid, jsep_transport);
|
|
|
|
if (it == mid_to_transport_.end()) {
|
|
mid_to_transport_.insert(std::make_pair(mid, jsep_transport));
|
|
} else {
|
|
auto old_transport = it->second;
|
|
it->second = jsep_transport;
|
|
MaybeDestroyJsepTransport(old_transport);
|
|
}
|
|
RTC_DCHECK(IsConsistent());
|
|
return result;
|
|
}
|
|
|
|
void JsepTransportCollection::RemoveTransportForMid(const std::string& mid) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
RTC_DCHECK(IsConsistent());
|
|
bool ret = map_change_callback_(mid, nullptr);
|
|
// Calling OnTransportChanged with nullptr should always succeed, since it is
|
|
// only expected to fail when adding media to a transport (not removing).
|
|
RTC_DCHECK(ret);
|
|
|
|
auto old_transport = GetTransportForMid(mid);
|
|
if (old_transport) {
|
|
mid_to_transport_.erase(mid);
|
|
MaybeDestroyJsepTransport(old_transport);
|
|
}
|
|
RTC_DCHECK(IsConsistent());
|
|
}
|
|
|
|
bool JsepTransportCollection::RollbackTransports() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
bool ret = true;
|
|
// First, remove any new mid->transport mappings.
|
|
for (const auto& kv : mid_to_transport_) {
|
|
if (stable_mid_to_transport_.count(kv.first) == 0) {
|
|
ret = ret && map_change_callback_(kv.first, nullptr);
|
|
}
|
|
}
|
|
// Next, restore old mappings.
|
|
for (const auto& kv : stable_mid_to_transport_) {
|
|
auto it = mid_to_transport_.find(kv.first);
|
|
if (it == mid_to_transport_.end() || it->second != kv.second) {
|
|
ret = ret && map_change_callback_(kv.first, kv.second);
|
|
}
|
|
}
|
|
mid_to_transport_ = stable_mid_to_transport_;
|
|
// Moving a transport back to mid_to_transport_ means it's now included in
|
|
// the aggregate state if it wasn't previously.
|
|
state_change_callback_();
|
|
DestroyUnusedTransports();
|
|
RTC_DCHECK(IsConsistent());
|
|
return ret;
|
|
}
|
|
|
|
void JsepTransportCollection::CommitTransports() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
stable_mid_to_transport_ = mid_to_transport_;
|
|
DestroyUnusedTransports();
|
|
RTC_DCHECK(IsConsistent());
|
|
}
|
|
|
|
bool JsepTransportCollection::TransportInUse(
|
|
cricket::JsepTransport* jsep_transport) const {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
for (const auto& kv : mid_to_transport_) {
|
|
if (kv.second == jsep_transport) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JsepTransportCollection::TransportNeededForRollback(
|
|
cricket::JsepTransport* jsep_transport) const {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
for (const auto& kv : stable_mid_to_transport_) {
|
|
if (kv.second == jsep_transport) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void JsepTransportCollection::MaybeDestroyJsepTransport(
|
|
cricket::JsepTransport* transport) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
// Don't destroy the JsepTransport if there are still media sections referring
|
|
// to it, or if it will be needed in case of rollback.
|
|
if (TransportInUse(transport)) {
|
|
return;
|
|
}
|
|
// If this transport is needed for rollback, don't destroy it yet, but make
|
|
// sure the aggregate state is updated since this transport is no longer
|
|
// included in it.
|
|
if (TransportNeededForRollback(transport)) {
|
|
state_change_callback_();
|
|
return;
|
|
}
|
|
for (const auto& it : jsep_transports_by_name_) {
|
|
if (it.second.get() == transport) {
|
|
jsep_transports_by_name_.erase(it.first);
|
|
state_change_callback_();
|
|
break;
|
|
}
|
|
}
|
|
RTC_DCHECK(IsConsistent());
|
|
}
|
|
|
|
void JsepTransportCollection::DestroyUnusedTransports() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
bool need_state_change_callback = false;
|
|
auto it = jsep_transports_by_name_.begin();
|
|
while (it != jsep_transports_by_name_.end()) {
|
|
if (TransportInUse(it->second.get()) ||
|
|
TransportNeededForRollback(it->second.get())) {
|
|
++it;
|
|
} else {
|
|
it = jsep_transports_by_name_.erase(it);
|
|
need_state_change_callback = true;
|
|
}
|
|
}
|
|
if (need_state_change_callback) {
|
|
state_change_callback_();
|
|
}
|
|
}
|
|
|
|
bool JsepTransportCollection::IsConsistent() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
for (const auto& it : jsep_transports_by_name_) {
|
|
if (!TransportInUse(it.second.get()) &&
|
|
!TransportNeededForRollback(it.second.get())) {
|
|
RTC_LOG(LS_ERROR) << "Transport registered with mid " << it.first
|
|
<< " is not in use, transport " << it.second.get();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace webrtc
|