| /* | 
 |  *  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; | 
 | } | 
 |  | 
 | cricket::JsepTransport* JsepTransportCollection::GetTransportForMid( | 
 |     absl::string_view mid) { | 
 |   RTC_DCHECK_RUN_ON(&sequence_checker_); | 
 |   // TODO(hta): should be a better way. | 
 |   auto it = mid_to_transport_.find(std::string(mid)); | 
 |   return it == mid_to_transport_.end() ? nullptr : it->second; | 
 | } | 
 |  | 
 | const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid( | 
 |     absl::string_view mid) const { | 
 |   RTC_DCHECK_RUN_ON(&sequence_checker_); | 
 |   // TODO(hta): Should be a better way | 
 |   auto it = mid_to_transport_.find(std::string(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(); | 
 |   for (auto& transport : jsep_transports_by_name_) { | 
 |     transport.second->CommitPayloadTypes(); | 
 |   } | 
 |   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 |