1 /*
2 * Copyright 2021 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "pc/jsep_transport_collection.h"
12
13 #include <algorithm>
14 #include <map>
15 #include <set>
16 #include <type_traits>
17 #include <utility>
18
19 #include "p2p/base/p2p_constants.h"
20 #include "rtc_base/logging.h"
21
22 namespace webrtc {
23
Update(const cricket::SessionDescription * description,SdpType type)24 void BundleManager::Update(const cricket::SessionDescription* description,
25 SdpType type) {
26 RTC_DCHECK_RUN_ON(&sequence_checker_);
27 // Rollbacks should call Rollback, not Update.
28 RTC_DCHECK(type != SdpType::kRollback);
29 bool bundle_groups_changed = false;
30 // TODO(bugs.webrtc.org/3349): Do this for kPrAnswer as well. To make this
31 // work, we also need to make sure PRANSWERs don't call
32 // MaybeDestroyJsepTransport, because the final answer may need the destroyed
33 // transport if it changes the BUNDLE group.
34 if (bundle_policy_ == PeerConnectionInterface::kBundlePolicyMaxBundle ||
35 type == SdpType::kAnswer) {
36 // If our policy is "max-bundle" or this is an answer, update all bundle
37 // groups.
38 bundle_groups_changed = true;
39 bundle_groups_.clear();
40 for (const cricket::ContentGroup* new_bundle_group :
41 description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
42 bundle_groups_.push_back(
43 std::make_unique<cricket::ContentGroup>(*new_bundle_group));
44 RTC_DLOG(LS_VERBOSE) << "Establishing bundle group "
45 << new_bundle_group->ToString();
46 }
47 } else if (type == SdpType::kOffer) {
48 // If this is an offer, update existing bundle groups.
49 // We do this because as per RFC 8843, section 7.3.2, the answerer cannot
50 // remove an m= section from an existing BUNDLE group without rejecting it.
51 // Thus any m= sections added to a BUNDLE group in this offer can
52 // preemptively start using the bundled transport, as there is no possible
53 // non-bundled fallback.
54 for (const cricket::ContentGroup* new_bundle_group :
55 description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
56 // Attempt to find a matching existing group.
57 for (const std::string& mid : new_bundle_group->content_names()) {
58 auto it = established_bundle_groups_by_mid_.find(mid);
59 if (it != established_bundle_groups_by_mid_.end()) {
60 *it->second = *new_bundle_group;
61 bundle_groups_changed = true;
62 RTC_DLOG(LS_VERBOSE)
63 << "Establishing bundle group " << new_bundle_group->ToString();
64 break;
65 }
66 }
67 }
68 }
69 if (bundle_groups_changed) {
70 RefreshEstablishedBundleGroupsByMid();
71 }
72 }
73
LookupGroupByMid(const std::string & mid) const74 const cricket::ContentGroup* BundleManager::LookupGroupByMid(
75 const std::string& mid) const {
76 auto it = established_bundle_groups_by_mid_.find(mid);
77 return it != established_bundle_groups_by_mid_.end() ? it->second : nullptr;
78 }
IsFirstMidInGroup(const std::string & mid) const79 bool BundleManager::IsFirstMidInGroup(const std::string& mid) const {
80 auto group = LookupGroupByMid(mid);
81 if (!group) {
82 return true; // Unbundled MIDs are considered group leaders
83 }
84 return mid == *(group->FirstContentName());
85 }
86
LookupGroupByMid(const std::string & mid)87 cricket::ContentGroup* BundleManager::LookupGroupByMid(const std::string& mid) {
88 auto it = established_bundle_groups_by_mid_.find(mid);
89 return it != established_bundle_groups_by_mid_.end() ? it->second : nullptr;
90 }
91
DeleteMid(const cricket::ContentGroup * bundle_group,const std::string & mid)92 void BundleManager::DeleteMid(const cricket::ContentGroup* bundle_group,
93 const std::string& mid) {
94 RTC_DCHECK_RUN_ON(&sequence_checker_);
95 RTC_LOG(LS_VERBOSE) << "Deleting mid " << mid << " from bundle group "
96 << bundle_group->ToString();
97 // Remove the rejected content from the `bundle_group`.
98 // The const pointer arg is used to identify the group, we verify
99 // it before we use it to make a modification.
100 auto bundle_group_it = std::find_if(
101 bundle_groups_.begin(), bundle_groups_.end(),
102 [bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
103 return bundle_group == group.get();
104 });
105 RTC_DCHECK(bundle_group_it != bundle_groups_.end());
106 (*bundle_group_it)->RemoveContentName(mid);
107 established_bundle_groups_by_mid_.erase(
108 established_bundle_groups_by_mid_.find(mid));
109 }
110
DeleteGroup(const cricket::ContentGroup * bundle_group)111 void BundleManager::DeleteGroup(const cricket::ContentGroup* bundle_group) {
112 RTC_DCHECK_RUN_ON(&sequence_checker_);
113 RTC_DLOG(LS_VERBOSE) << "Deleting bundle group " << bundle_group->ToString();
114
115 auto bundle_group_it = std::find_if(
116 bundle_groups_.begin(), bundle_groups_.end(),
117 [bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
118 return bundle_group == group.get();
119 });
120 RTC_DCHECK(bundle_group_it != bundle_groups_.end());
121 auto mid_list = (*bundle_group_it)->content_names();
122 for (const auto& content_name : mid_list) {
123 DeleteMid(bundle_group, content_name);
124 }
125 bundle_groups_.erase(bundle_group_it);
126 }
127
Rollback()128 void BundleManager::Rollback() {
129 RTC_DCHECK_RUN_ON(&sequence_checker_);
130 bundle_groups_.clear();
131 for (const auto& bundle_group : stable_bundle_groups_) {
132 bundle_groups_.push_back(
133 std::make_unique<cricket::ContentGroup>(*bundle_group));
134 }
135 RefreshEstablishedBundleGroupsByMid();
136 }
137
Commit()138 void BundleManager::Commit() {
139 RTC_DCHECK_RUN_ON(&sequence_checker_);
140 stable_bundle_groups_.clear();
141 for (const auto& bundle_group : bundle_groups_) {
142 stable_bundle_groups_.push_back(
143 std::make_unique<cricket::ContentGroup>(*bundle_group));
144 }
145 }
146
RefreshEstablishedBundleGroupsByMid()147 void BundleManager::RefreshEstablishedBundleGroupsByMid() {
148 established_bundle_groups_by_mid_.clear();
149 for (const auto& bundle_group : bundle_groups_) {
150 for (const std::string& content_name : bundle_group->content_names()) {
151 established_bundle_groups_by_mid_[content_name] = bundle_group.get();
152 }
153 }
154 }
155
RegisterTransport(const std::string & mid,std::unique_ptr<cricket::JsepTransport> transport)156 void JsepTransportCollection::RegisterTransport(
157 const std::string& mid,
158 std::unique_ptr<cricket::JsepTransport> transport) {
159 RTC_DCHECK_RUN_ON(&sequence_checker_);
160 SetTransportForMid(mid, transport.get());
161 jsep_transports_by_name_[mid] = std::move(transport);
162 RTC_DCHECK(IsConsistent());
163 }
164
Transports()165 std::vector<cricket::JsepTransport*> JsepTransportCollection::Transports() {
166 RTC_DCHECK_RUN_ON(&sequence_checker_);
167 std::vector<cricket::JsepTransport*> result;
168 for (auto& kv : jsep_transports_by_name_) {
169 result.push_back(kv.second.get());
170 }
171 return result;
172 }
173
174 std::vector<cricket::JsepTransport*>
ActiveTransports()175 JsepTransportCollection::ActiveTransports() {
176 RTC_DCHECK_RUN_ON(&sequence_checker_);
177 std::set<cricket::JsepTransport*> transports;
178 for (const auto& kv : mid_to_transport_) {
179 transports.insert(kv.second);
180 }
181 return std::vector<cricket::JsepTransport*>(transports.begin(),
182 transports.end());
183 }
184
DestroyAllTransports()185 void JsepTransportCollection::DestroyAllTransports() {
186 RTC_DCHECK_RUN_ON(&sequence_checker_);
187 for (const auto& jsep_transport : jsep_transports_by_name_) {
188 map_change_callback_(jsep_transport.first, nullptr);
189 }
190 jsep_transports_by_name_.clear();
191 RTC_DCHECK(IsConsistent());
192 }
193
GetTransportByName(const std::string & transport_name) const194 const cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
195 const std::string& transport_name) const {
196 RTC_DCHECK_RUN_ON(&sequence_checker_);
197 auto it = jsep_transports_by_name_.find(transport_name);
198 return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
199 }
200
GetTransportByName(const std::string & transport_name)201 cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
202 const std::string& transport_name) {
203 RTC_DCHECK_RUN_ON(&sequence_checker_);
204 auto it = jsep_transports_by_name_.find(transport_name);
205 return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
206 }
207
GetTransportForMid(const std::string & mid)208 cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
209 const std::string& mid) {
210 RTC_DCHECK_RUN_ON(&sequence_checker_);
211 auto it = mid_to_transport_.find(mid);
212 return it == mid_to_transport_.end() ? nullptr : it->second;
213 }
214
GetTransportForMid(const std::string & mid) const215 const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
216 const std::string& mid) const {
217 RTC_DCHECK_RUN_ON(&sequence_checker_);
218 auto it = mid_to_transport_.find(mid);
219 return it == mid_to_transport_.end() ? nullptr : it->second;
220 }
221
GetTransportForMid(absl::string_view mid)222 cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
223 absl::string_view mid) {
224 RTC_DCHECK_RUN_ON(&sequence_checker_);
225 // TODO(hta): should be a better way.
226 auto it = mid_to_transport_.find(std::string(mid));
227 return it == mid_to_transport_.end() ? nullptr : it->second;
228 }
229
GetTransportForMid(absl::string_view mid) const230 const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
231 absl::string_view mid) const {
232 RTC_DCHECK_RUN_ON(&sequence_checker_);
233 // TODO(hta): Should be a better way
234 auto it = mid_to_transport_.find(std::string(mid));
235 return it == mid_to_transport_.end() ? nullptr : it->second;
236 }
237
SetTransportForMid(const std::string & mid,cricket::JsepTransport * jsep_transport)238 bool JsepTransportCollection::SetTransportForMid(
239 const std::string& mid,
240 cricket::JsepTransport* jsep_transport) {
241 RTC_DCHECK_RUN_ON(&sequence_checker_);
242 RTC_DCHECK(jsep_transport);
243
244 auto it = mid_to_transport_.find(mid);
245 if (it != mid_to_transport_.end() && it->second == jsep_transport)
246 return true;
247
248 // The map_change_callback must be called before destroying the
249 // transport, because it removes references to the transport
250 // in the RTP demuxer.
251 bool result = map_change_callback_(mid, jsep_transport);
252
253 if (it == mid_to_transport_.end()) {
254 mid_to_transport_.insert(std::make_pair(mid, jsep_transport));
255 } else {
256 auto old_transport = it->second;
257 it->second = jsep_transport;
258 MaybeDestroyJsepTransport(old_transport);
259 }
260 RTC_DCHECK(IsConsistent());
261 return result;
262 }
263
RemoveTransportForMid(const std::string & mid)264 void JsepTransportCollection::RemoveTransportForMid(const std::string& mid) {
265 RTC_DCHECK_RUN_ON(&sequence_checker_);
266 RTC_DCHECK(IsConsistent());
267 bool ret = map_change_callback_(mid, nullptr);
268 // Calling OnTransportChanged with nullptr should always succeed, since it is
269 // only expected to fail when adding media to a transport (not removing).
270 RTC_DCHECK(ret);
271
272 auto old_transport = GetTransportForMid(mid);
273 if (old_transport) {
274 mid_to_transport_.erase(mid);
275 MaybeDestroyJsepTransport(old_transport);
276 }
277 RTC_DCHECK(IsConsistent());
278 }
279
RollbackTransports()280 bool JsepTransportCollection::RollbackTransports() {
281 RTC_DCHECK_RUN_ON(&sequence_checker_);
282 bool ret = true;
283 // First, remove any new mid->transport mappings.
284 for (const auto& kv : mid_to_transport_) {
285 if (stable_mid_to_transport_.count(kv.first) == 0) {
286 ret = ret && map_change_callback_(kv.first, nullptr);
287 }
288 }
289 // Next, restore old mappings.
290 for (const auto& kv : stable_mid_to_transport_) {
291 auto it = mid_to_transport_.find(kv.first);
292 if (it == mid_to_transport_.end() || it->second != kv.second) {
293 ret = ret && map_change_callback_(kv.first, kv.second);
294 }
295 }
296 mid_to_transport_ = stable_mid_to_transport_;
297 // Moving a transport back to mid_to_transport_ means it's now included in
298 // the aggregate state if it wasn't previously.
299 state_change_callback_();
300 DestroyUnusedTransports();
301 RTC_DCHECK(IsConsistent());
302 return ret;
303 }
304
CommitTransports()305 void JsepTransportCollection::CommitTransports() {
306 RTC_DCHECK_RUN_ON(&sequence_checker_);
307 stable_mid_to_transport_ = mid_to_transport_;
308 DestroyUnusedTransports();
309 RTC_DCHECK(IsConsistent());
310 }
311
TransportInUse(cricket::JsepTransport * jsep_transport) const312 bool JsepTransportCollection::TransportInUse(
313 cricket::JsepTransport* jsep_transport) const {
314 RTC_DCHECK_RUN_ON(&sequence_checker_);
315 for (const auto& kv : mid_to_transport_) {
316 if (kv.second == jsep_transport) {
317 return true;
318 }
319 }
320 return false;
321 }
322
TransportNeededForRollback(cricket::JsepTransport * jsep_transport) const323 bool JsepTransportCollection::TransportNeededForRollback(
324 cricket::JsepTransport* jsep_transport) const {
325 RTC_DCHECK_RUN_ON(&sequence_checker_);
326 for (const auto& kv : stable_mid_to_transport_) {
327 if (kv.second == jsep_transport) {
328 return true;
329 }
330 }
331 return false;
332 }
333
MaybeDestroyJsepTransport(cricket::JsepTransport * transport)334 void JsepTransportCollection::MaybeDestroyJsepTransport(
335 cricket::JsepTransport* transport) {
336 RTC_DCHECK_RUN_ON(&sequence_checker_);
337 // Don't destroy the JsepTransport if there are still media sections referring
338 // to it, or if it will be needed in case of rollback.
339 if (TransportInUse(transport)) {
340 return;
341 }
342 // If this transport is needed for rollback, don't destroy it yet, but make
343 // sure the aggregate state is updated since this transport is no longer
344 // included in it.
345 if (TransportNeededForRollback(transport)) {
346 state_change_callback_();
347 return;
348 }
349 for (const auto& it : jsep_transports_by_name_) {
350 if (it.second.get() == transport) {
351 jsep_transports_by_name_.erase(it.first);
352 state_change_callback_();
353 break;
354 }
355 }
356 RTC_DCHECK(IsConsistent());
357 }
358
DestroyUnusedTransports()359 void JsepTransportCollection::DestroyUnusedTransports() {
360 RTC_DCHECK_RUN_ON(&sequence_checker_);
361 bool need_state_change_callback = false;
362 auto it = jsep_transports_by_name_.begin();
363 while (it != jsep_transports_by_name_.end()) {
364 if (TransportInUse(it->second.get()) ||
365 TransportNeededForRollback(it->second.get())) {
366 ++it;
367 } else {
368 it = jsep_transports_by_name_.erase(it);
369 need_state_change_callback = true;
370 }
371 }
372 if (need_state_change_callback) {
373 state_change_callback_();
374 }
375 }
376
IsConsistent()377 bool JsepTransportCollection::IsConsistent() {
378 RTC_DCHECK_RUN_ON(&sequence_checker_);
379 for (const auto& it : jsep_transports_by_name_) {
380 if (!TransportInUse(it.second.get()) &&
381 !TransportNeededForRollback(it.second.get())) {
382 RTC_LOG(LS_ERROR) << "Transport registered with mid " << it.first
383 << " is not in use, transport " << it.second.get();
384 return false;
385 }
386 }
387 return true;
388 }
389
390 } // namespace webrtc
391