OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright 2015 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 "webrtc/p2p/base/transportcontroller.h" |
| 12 |
| 13 #include "webrtc/base/bind.h" |
| 14 #include "webrtc/base/thread.h" |
| 15 #include "webrtc/p2p/base/dtlstransport.h" |
| 16 #include "webrtc/p2p/base/p2ptransport.h" |
| 17 |
| 18 namespace cricket { |
| 19 |
| 20 enum { |
| 21 MSG_CONNECTIONSTATE, |
| 22 MSG_RECEIVING, |
| 23 MSG_CANDIDATESALLOCATIONSTARTED, |
| 24 MSG_CANDIDATESALLOCATIONDONE, |
| 25 MSG_CANDIDATESREADY, |
| 26 }; |
| 27 |
| 28 struct CandidatesData : public rtc::MessageData { |
| 29 CandidatesData(const std::string& transport_name, |
| 30 const Candidates& candidates) |
| 31 : transport_name(transport_name), |
| 32 candidates(candidates) { |
| 33 } |
| 34 |
| 35 std::string transport_name; |
| 36 Candidates candidates; |
| 37 }; |
| 38 |
| 39 TransportController::TransportController(rtc::Thread* signaling_thread, |
| 40 rtc::Thread* worker_thread, |
| 41 PortAllocator* port_allocator) |
| 42 : signaling_thread_(signaling_thread), |
| 43 worker_thread_(worker_thread), |
| 44 port_allocator_(port_allocator), |
| 45 ice_receiving_timeout_ms_(-1), |
| 46 ice_role_(ICEROLE_CONTROLLING), |
| 47 ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10), |
| 48 ice_role_switch_(false), |
| 49 ice_tiebreaker_(rtc::CreateRandomId64()), |
| 50 connection_state_(kConnecting), |
| 51 receiving_(false), |
| 52 candidate_gathering_state_(Transport::kGatheringNew) { |
| 53 ASSERT(signaling_thread_->IsCurrent()); |
| 54 } |
| 55 |
| 56 TransportController::~TransportController() { |
| 57 ASSERT(signaling_thread_->IsCurrent()); |
| 58 worker_thread_->Invoke<void>( |
| 59 rtc::Bind(&TransportController::DestroyAllTransports_w, this)); |
| 60 signaling_thread_->Clear(this); |
| 61 } |
| 62 |
| 63 void TransportController::SetIceConnectionReceivingTimeout(int timeout_ms) { |
| 64 ASSERT(signaling_thread_->IsCurrent()); |
| 65 worker_thread_->Invoke<void>( |
| 66 rtc::Bind(&TransportController::SetIceConnectionReceivingTimeout_w, this, |
| 67 timeout_ms)); |
| 68 } |
| 69 |
| 70 void TransportController::SetIceRole(IceRole ice_role) { |
| 71 ASSERT(signaling_thread_->IsCurrent()); |
| 72 worker_thread_->Invoke<void>( |
| 73 rtc::Bind(&TransportController::SetIceRole_w, this, ice_role)); |
| 74 } |
| 75 |
| 76 bool TransportController::SetIdentity(rtc::SSLIdentity* identity) { |
| 77 ASSERT(signaling_thread_->IsCurrent()); |
| 78 return worker_thread_->Invoke<bool>( |
| 79 rtc::Bind(&TransportController::SetIdentity_w, this, identity)); |
| 80 } |
| 81 |
| 82 bool TransportController::GetIdentity(const std::string& transport_name, |
| 83 rtc::SSLIdentity** identity) { |
| 84 ASSERT(signaling_thread_->IsCurrent()); |
| 85 return worker_thread_->Invoke<bool>( |
| 86 rtc::Bind(&TransportController::GetIdentity_w, this, transport_name, |
| 87 identity)); |
| 88 } |
| 89 |
| 90 bool TransportController::GetRemoteCertificate( |
| 91 const std::string& transport_name, |
| 92 rtc::SSLCertificate** cert) { |
| 93 ASSERT(signaling_thread_->IsCurrent()); |
| 94 return worker_thread_->Invoke<bool>( |
| 95 rtc::Bind(&TransportController::GetRemoteCertificate_w, this, |
| 96 transport_name, cert)); |
| 97 } |
| 98 |
| 99 bool TransportController::SetSslMaxProtocolVersion( |
| 100 rtc::SSLProtocolVersion version) { |
| 101 ASSERT(signaling_thread_->IsCurrent()); |
| 102 return worker_thread_->Invoke<bool>( |
| 103 rtc::Bind(&TransportController::SetSslMaxProtocolVersion_w, this, |
| 104 version)); |
| 105 } |
| 106 |
| 107 bool TransportController::SetLocalTransportDescription( |
| 108 const std::string& transport_name, |
| 109 const TransportDescription& tdesc, |
| 110 ContentAction action, |
| 111 std::string* err) { |
| 112 ASSERT(signaling_thread_->IsCurrent()); |
| 113 return worker_thread_->Invoke<bool>( |
| 114 rtc::Bind(&TransportController::SetLocalTransportDescription_w, this, |
| 115 transport_name, tdesc, action, err)); |
| 116 } |
| 117 |
| 118 bool TransportController::SetRemoteTransportDescription( |
| 119 const std::string& transport_name, |
| 120 const TransportDescription& tdesc, |
| 121 ContentAction action, |
| 122 std::string* err) { |
| 123 ASSERT(signaling_thread_->IsCurrent()); |
| 124 return worker_thread_->Invoke<bool>( |
| 125 rtc::Bind(&TransportController::SetRemoteTransportDescription_w, this, |
| 126 transport_name, tdesc, action, err)); |
| 127 } |
| 128 |
| 129 bool TransportController::AddRemoteCandidates( |
| 130 const std::string& transport_name, |
| 131 const Candidates& candidates, |
| 132 std::string* err) { |
| 133 ASSERT(signaling_thread_->IsCurrent()); |
| 134 return worker_thread_->Invoke<bool>( |
| 135 rtc::Bind(&TransportController::AddRemoteCandidates_w, this, |
| 136 transport_name, candidates, err)); |
| 137 } |
| 138 |
| 139 bool TransportController::ReadyForRemoteCandidates( |
| 140 const std::string& transport_name) { |
| 141 ASSERT(signaling_thread_->IsCurrent()); |
| 142 return worker_thread_->Invoke<bool>( |
| 143 rtc::Bind(&TransportController::ReadyForRemoteCandidates_w, this, |
| 144 transport_name)); |
| 145 } |
| 146 |
| 147 bool TransportController::GetSslRole(rtc::SSLRole* role) { |
| 148 ASSERT(signaling_thread_->IsCurrent()); |
| 149 return worker_thread_->Invoke<bool>( |
| 150 rtc::Bind(&TransportController::GetSslRole_w, this, role)); |
| 151 } |
| 152 |
| 153 bool TransportController::GetStats( |
| 154 const std::string& transport_name, |
| 155 TransportStats* stats) { |
| 156 ASSERT(signaling_thread_->IsCurrent()); |
| 157 return worker_thread_->Invoke<bool>( |
| 158 rtc::Bind(&TransportController::GetStats_w, this, |
| 159 transport_name, stats)); |
| 160 } |
| 161 |
| 162 TransportChannel* TransportController::CreateTransportChannel_w( |
| 163 const std::string& transport_name, int component) { |
| 164 ASSERT(worker_thread_->IsCurrent()); |
| 165 |
| 166 Transport* transport = GetOrCreateTransport_w(transport_name); |
| 167 return transport->CreateChannel(component); |
| 168 } |
| 169 |
| 170 void TransportController::DestroyTransportChannel_w( |
| 171 const std::string& transport_name, int component) { |
| 172 ASSERT(worker_thread_->IsCurrent()); |
| 173 |
| 174 Transport* transport = GetTransport_w(transport_name); |
| 175 ASSERT(transport != nullptr); |
| 176 transport->DestroyChannel(component); |
| 177 |
| 178 // Just as we create a Transport when its first channel is created, |
| 179 // we delete it when its last channel is deleted. |
| 180 if (!transport->HasChannels()) { |
| 181 DestroyTransport_w(transport_name); |
| 182 UpdateState_w(); |
| 183 CheckIfCandidatesAllocationDone_w(); |
| 184 } |
| 185 } |
| 186 |
| 187 void TransportController::OnMessage(rtc::Message* pmsg) { |
| 188 ASSERT(signaling_thread_->IsCurrent()); |
| 189 |
| 190 switch (pmsg->message_id) { |
| 191 case MSG_CONNECTIONSTATE: { |
| 192 rtc::TypedMessageData<ConnectionState>* data = |
| 193 static_cast<rtc::TypedMessageData<ConnectionState>*>(pmsg->pdata); |
| 194 SignalConnectionStateChanged(data->data()); |
| 195 delete data; |
| 196 break; |
| 197 } |
| 198 case MSG_RECEIVING: { |
| 199 rtc::TypedMessageData<bool>* data = |
| 200 static_cast<rtc::TypedMessageData<bool>*>(pmsg->pdata); |
| 201 SignalReceiving(data->data()); |
| 202 delete data; |
| 203 break; |
| 204 } |
| 205 case MSG_CANDIDATESALLOCATIONSTARTED: |
| 206 SignalCandidatesAllocationStarted(); |
| 207 break; |
| 208 case MSG_CANDIDATESALLOCATIONDONE: |
| 209 SignalCandidatesAllocationDone(); |
| 210 break; |
| 211 case MSG_CANDIDATESREADY: { |
| 212 CandidatesData* data = static_cast<CandidatesData*>(pmsg->pdata); |
| 213 SignalCandidatesReady(data->transport_name, data->candidates); |
| 214 delete data; |
| 215 break; |
| 216 } |
| 217 default: |
| 218 ASSERT(false); |
| 219 } |
| 220 } |
| 221 |
| 222 Transport* TransportController::CreateTransport_w( |
| 223 const std::string& transport_name) { |
| 224 ASSERT(worker_thread_->IsCurrent()); |
| 225 |
| 226 Transport* transport = new DtlsTransport<P2PTransport>( |
| 227 transport_name, port_allocator(), identity_.get()); |
| 228 return transport; |
| 229 } |
| 230 |
| 231 Transport* TransportController::GetOrCreateTransport_w( |
| 232 const std::string& transport_name) { |
| 233 ASSERT(worker_thread_->IsCurrent()); |
| 234 |
| 235 Transport* transport = GetTransport_w(transport_name); |
| 236 if (transport) |
| 237 return transport; |
| 238 |
| 239 transport = CreateTransport_w(transport_name); |
| 240 transport->SetChannelReceivingTimeout(ice_receiving_timeout_ms_); |
| 241 transport->SetIceRole(ice_role_); |
| 242 transport->SetIceTiebreaker(ice_tiebreaker_); |
| 243 transport->SetSslMaxProtocolVersion(ssl_max_version_); |
| 244 transport->SignalConnecting.connect( |
| 245 this, &TransportController::OnTransportConnecting_w); |
| 246 transport->SignalWritableState.connect( |
| 247 this, &TransportController::OnTransportWritableState_w); |
| 248 transport->SignalReceivingState.connect( |
| 249 this, &TransportController::OnTransportReceivingState_w); |
| 250 transport->SignalCandidatesAllocationStarted.connect( |
| 251 this, &TransportController::OnTransportCandidatesAllocationStarted_w); |
| 252 transport->SignalRouteChange.connect( |
| 253 this, &TransportController::OnTransportRouteChange_w); |
| 254 transport->SignalCandidatesAllocationDone.connect( |
| 255 this, &TransportController::OnTransportCandidatesAllocationDone_w); |
| 256 transport->SignalRoleConflict.connect( |
| 257 this, &TransportController::OnTransportRoleConflict_w); |
| 258 transport->SignalCompleted.connect( |
| 259 this, &TransportController::OnTransportCompleted_w); |
| 260 transport->SignalFailed.connect( |
| 261 this, &TransportController::OnTransportFailed_w); |
| 262 transport->SignalCandidatesReady.connect( |
| 263 this, &TransportController::OnTransportCandidatesReady_w); |
| 264 if (identity_) |
| 265 transport->SetIdentity(identity_.get()); |
| 266 transports_[transport_name] = transport; |
| 267 |
| 268 return transport; |
| 269 } |
| 270 |
| 271 Transport* TransportController::GetTransport_w( |
| 272 const std::string& transport_name) { |
| 273 ASSERT(worker_thread_->IsCurrent()); |
| 274 |
| 275 auto iter = transports_.find(transport_name); |
| 276 return (iter != transports_.end()) ? iter->second : nullptr; |
| 277 } |
| 278 |
| 279 void TransportController::DestroyTransport_w( |
| 280 const std::string& transport_name) { |
| 281 ASSERT(worker_thread_->IsCurrent()); |
| 282 |
| 283 auto iter = transports_.find(transport_name); |
| 284 if (iter != transports_.end()) { |
| 285 delete iter->second; |
| 286 transports_.erase(transport_name); |
| 287 } |
| 288 } |
| 289 |
| 290 void TransportController::DestroyAllTransports_w() { |
| 291 ASSERT(worker_thread_->IsCurrent()); |
| 292 |
| 293 for (const auto& kv : transports_) { |
| 294 delete kv.second; |
| 295 } |
| 296 transports_.clear(); |
| 297 } |
| 298 |
| 299 void TransportController::SetIceConnectionReceivingTimeout_w(int timeout_ms) { |
| 300 ASSERT(worker_thread_->IsCurrent()); |
| 301 ice_receiving_timeout_ms_ = timeout_ms; |
| 302 for (const auto& kv : transports_) { |
| 303 kv.second->SetChannelReceivingTimeout(timeout_ms); |
| 304 } |
| 305 } |
| 306 |
| 307 void TransportController::SetIceRole_w(IceRole ice_role) { |
| 308 ASSERT(worker_thread_->IsCurrent()); |
| 309 ice_role_ = ice_role; |
| 310 for (const auto& kv : transports_) { |
| 311 kv.second->SetIceRole(ice_role_); |
| 312 } |
| 313 } |
| 314 |
| 315 bool TransportController::SetIdentity_w(rtc::SSLIdentity* identity) { |
| 316 ASSERT(worker_thread_->IsCurrent()); |
| 317 |
| 318 if (identity_) |
| 319 return false; |
| 320 identity_.reset(identity); |
| 321 |
| 322 for (const auto& kv : transports_) { |
| 323 kv.second->SetIdentity(identity_.get()); |
| 324 } |
| 325 return true; |
| 326 } |
| 327 |
| 328 bool TransportController::GetIdentity_w(const std::string& transport_name, |
| 329 rtc::SSLIdentity** identity) { |
| 330 ASSERT(worker_thread_->IsCurrent()); |
| 331 |
| 332 Transport* t = GetTransport_w(transport_name); |
| 333 if (!t) { |
| 334 return false; |
| 335 } |
| 336 |
| 337 return t->GetIdentity(identity); |
| 338 } |
| 339 |
| 340 bool TransportController::GetRemoteCertificate_w( |
| 341 const std::string& transport_name, |
| 342 rtc::SSLCertificate** cert) { |
| 343 ASSERT(worker_thread_->IsCurrent()); |
| 344 |
| 345 Transport* t = GetTransport_w(transport_name); |
| 346 if (!t) { |
| 347 return false; |
| 348 } |
| 349 |
| 350 return t->GetRemoteCertificate(cert); |
| 351 } |
| 352 |
| 353 bool TransportController::SetSslMaxProtocolVersion_w( |
| 354 rtc::SSLProtocolVersion version) { |
| 355 ASSERT(worker_thread_->IsCurrent()); |
| 356 |
| 357 // Max SSL version can only be set before transports are created |
| 358 if (!transports_.empty()) { |
| 359 return false; |
| 360 } |
| 361 |
| 362 ssl_max_version_ = version; |
| 363 return true; |
| 364 } |
| 365 |
| 366 bool TransportController::SetLocalTransportDescription_w( |
| 367 const std::string& transport_name, |
| 368 const TransportDescription& tdesc, |
| 369 ContentAction action, |
| 370 std::string* err) { |
| 371 ASSERT(worker_thread()->IsCurrent()); |
| 372 |
| 373 Transport* transport = GetTransport_w(transport_name); |
| 374 if (!transport) { |
| 375 // If we didn't find a transport, that's not an error; |
| 376 // it could have been deleted as a result of bundling. |
| 377 return true; |
| 378 } |
| 379 |
| 380 if (!transport->SetLocalTransportDescription(tdesc, action, err)) { |
| 381 return false; |
| 382 } |
| 383 transport->ConnectChannels(); |
| 384 return true; |
| 385 } |
| 386 |
| 387 bool TransportController::SetRemoteTransportDescription_w( |
| 388 const std::string& transport_name, |
| 389 const TransportDescription& tdesc, |
| 390 ContentAction action, |
| 391 std::string* err) { |
| 392 ASSERT(worker_thread()->IsCurrent()); |
| 393 |
| 394 Transport* transport = GetTransport_w(transport_name); |
| 395 if (!transport) { |
| 396 // If we didn't find a transport, that's not an error; |
| 397 // it could have been deleted as a result of bundling. |
| 398 return true; |
| 399 } |
| 400 |
| 401 return transport->SetRemoteTransportDescription(tdesc, action, err); |
| 402 } |
| 403 |
| 404 bool TransportController::AddRemoteCandidates_w( |
| 405 const std::string& transport_name, |
| 406 const Candidates& candidates, |
| 407 std::string* err) { |
| 408 ASSERT(worker_thread()->IsCurrent()); |
| 409 |
| 410 Transport* transport = GetTransport_w(transport_name); |
| 411 if (!transport) { |
| 412 // If we didn't find a transport, that's not an error; |
| 413 // it could have been deleted as a result of bundling. |
| 414 return true; |
| 415 } |
| 416 |
| 417 return transport->AddRemoteCandidates(candidates, err); |
| 418 } |
| 419 |
| 420 bool TransportController::ReadyForRemoteCandidates_w( |
| 421 const std::string& transport_name) { |
| 422 ASSERT(worker_thread()->IsCurrent()); |
| 423 |
| 424 Transport* transport = GetTransport_w(transport_name); |
| 425 if (!transport) { |
| 426 return false; |
| 427 } |
| 428 return transport->local_description_set() && |
| 429 transport->remote_description_set(); |
| 430 } |
| 431 |
| 432 bool TransportController::GetSslRole_w(rtc::SSLRole* role) { |
| 433 ASSERT(worker_thread()->IsCurrent()); |
| 434 |
| 435 // TODO(mallinath) - Return role of each transport, as role may differ from |
| 436 // one another. |
| 437 // In current implementaion we just return the role of first transport in the |
| 438 // transport map. |
| 439 for (const auto& kv : transports_) { |
| 440 return kv.second->GetSslRole(role); |
| 441 } |
| 442 return false; |
| 443 } |
| 444 |
| 445 bool TransportController::GetStats_w( |
| 446 const std::string& transport_name, |
| 447 TransportStats* stats) { |
| 448 ASSERT(worker_thread()->IsCurrent()); |
| 449 |
| 450 Transport* transport = GetTransport_w(transport_name); |
| 451 if (!transport) { |
| 452 return false; |
| 453 } |
| 454 return transport->GetStats(stats); |
| 455 } |
| 456 |
| 457 void TransportController::OnTransportConnecting_w(Transport* transport) { |
| 458 ASSERT(worker_thread_->IsCurrent()); |
| 459 UpdateState_w(); |
| 460 } |
| 461 |
| 462 void TransportController::OnTransportWritableState_w(Transport* transport) { |
| 463 ASSERT(worker_thread_->IsCurrent()); |
| 464 UpdateState_w(); |
| 465 } |
| 466 |
| 467 void TransportController::OnTransportReceivingState_w(Transport* transport) { |
| 468 ASSERT(worker_thread_->IsCurrent()); |
| 469 UpdateState_w(); |
| 470 } |
| 471 |
| 472 void TransportController::OnTransportCandidatesAllocationStarted_w( |
| 473 Transport* transport) { |
| 474 ASSERT(worker_thread_->IsCurrent()); |
| 475 if (candidate_gathering_state_ != Transport::kGatheringGathering) { |
| 476 candidate_gathering_state_ = Transport::kGatheringGathering; |
| 477 signaling_thread_->Post(this, MSG_CANDIDATESALLOCATIONSTARTED); |
| 478 } |
| 479 } |
| 480 |
| 481 void TransportController::OnTransportRouteChange_w(Transport* transport, |
| 482 int component, |
| 483 const Candidate& candidate) { |
| 484 ASSERT(worker_thread_->IsCurrent()); |
| 485 // This signal is not currently used for anything |
| 486 } |
| 487 |
| 488 void TransportController::OnTransportCandidatesAllocationDone_w( |
| 489 Transport* transport) { |
| 490 ASSERT(worker_thread_->IsCurrent()); |
| 491 CheckIfCandidatesAllocationDone_w(); |
| 492 } |
| 493 |
| 494 void TransportController::OnTransportRoleConflict_w() { |
| 495 ASSERT(worker_thread_->IsCurrent()); |
| 496 |
| 497 if (ice_role_switch_) { |
| 498 LOG(LS_WARNING) << "Repeat of role conflic signal from Transport."; |
| 499 return; |
| 500 } |
| 501 |
| 502 ice_role_switch_ = true; |
| 503 IceRole reversed_role = (ice_role_ == ICEROLE_CONTROLLING) |
| 504 ? ICEROLE_CONTROLLED |
| 505 : ICEROLE_CONTROLLING; |
| 506 for (const auto& kv : transports_) { |
| 507 kv.second->SetIceRole(reversed_role); |
| 508 } |
| 509 } |
| 510 |
| 511 void TransportController::OnTransportCompleted_w(Transport* transport) { |
| 512 ASSERT(worker_thread_->IsCurrent()); |
| 513 UpdateState_w(); |
| 514 } |
| 515 |
| 516 void TransportController::OnTransportFailed_w(Transport* transport) { |
| 517 ASSERT(worker_thread_->IsCurrent()); |
| 518 UpdateState_w(); |
| 519 } |
| 520 |
| 521 void TransportController::OnTransportCandidatesReady_w(Transport* transport, |
| 522 const std::vector<Candidate>& candidates) { |
| 523 ASSERT(worker_thread_->IsCurrent()); |
| 524 CandidatesData* data = new CandidatesData(transport->content_name(), |
| 525 candidates); |
| 526 signaling_thread_->Post(this, MSG_CANDIDATESREADY, data); |
| 527 } |
| 528 |
| 529 void TransportController::UpdateState_w() { |
| 530 ASSERT(worker_thread_->IsCurrent()); |
| 531 |
| 532 ConnectionState new_state = kConnecting; |
| 533 bool receiving = false; |
| 534 // If we don't have ANY Transports, we shouldn't signal that we're completed |
| 535 if (!transports_.empty()) { |
| 536 bool failed = false; |
| 537 bool connected = true; |
| 538 bool completed = true; |
| 539 for (const auto& kv : transports_) { |
| 540 if (!kv.second->Completed()) { |
| 541 completed = false; |
| 542 } |
| 543 if (!kv.second->all_channels_writable()) { |
| 544 connected = false; |
| 545 } |
| 546 if (kv.second->any_channel_receiving()) { |
| 547 // The connection is considered receiving if at least one transport is |
| 548 // receiving on any channel. |
| 549 receiving = true; |
| 550 } |
| 551 } |
| 552 |
| 553 if (failed) { |
| 554 new_state = kFailed; |
| 555 } else if (completed) { |
| 556 new_state = kCompleted; |
| 557 } else if (connected) { |
| 558 new_state = kConnected; |
| 559 } |
| 560 } |
| 561 |
| 562 if (connection_state_ != new_state) { |
| 563 connection_state_ = new_state; |
| 564 signaling_thread_->Post( |
| 565 this, MSG_CONNECTIONSTATE, |
| 566 new rtc::TypedMessageData<ConnectionState>(new_state)); |
| 567 } |
| 568 |
| 569 if (receiving_ != receiving) { |
| 570 receiving_ = receiving; |
| 571 signaling_thread_->Post(this, MSG_RECEIVING, |
| 572 new rtc::TypedMessageData<bool>(receiving)); |
| 573 } |
| 574 } |
| 575 |
| 576 void TransportController::CheckIfCandidatesAllocationDone_w() { |
| 577 ASSERT(worker_thread_->IsCurrent()); |
| 578 |
| 579 // If already allocated, no point in checking. |
| 580 if (candidate_gathering_state_ == Transport::kGatheringDone) { |
| 581 return; |
| 582 } |
| 583 |
| 584 if (transports_.empty()) { |
| 585 return; |
| 586 } |
| 587 |
| 588 for (const auto& kv : transports_) { |
| 589 if (kv.second->candidate_gathering_state() != Transport::kGatheringDone) { |
| 590 return; |
| 591 } |
| 592 } |
| 593 |
| 594 candidate_gathering_state_ = Transport::kGatheringDone; |
| 595 signaling_thread_->Post(this, MSG_CANDIDATESALLOCATIONDONE); |
| 596 } |
| 597 |
| 598 } // namespace cricket |
OLD | NEW |