Add ufrag to the ICE candidate signaling.
On the receiving side, if a candidate arrives with an old ufrag, it will be dropped. If it contains a new frag that has never seen before, it will hold the ufrag and create connections, although those connections are not pingable until the ICE credentials are received. This could avoid a bunch of ICE generation issues. BUG=webrtc:5138,webrt:5292 Review URL: https://codereview.webrtc.org/1498993002 Cr-Commit-Position: refs/heads/master@{#11060}
This commit is contained in:
parent
3514cbe554
commit
a54a080112
@ -140,8 +140,8 @@ static const char kAttributeCandidate[] = "candidate";
|
||||
static const char kAttributeCandidateTyp[] = "typ";
|
||||
static const char kAttributeCandidateRaddr[] = "raddr";
|
||||
static const char kAttributeCandidateRport[] = "rport";
|
||||
static const char kAttributeCandidateUsername[] = "username";
|
||||
static const char kAttributeCandidatePassword[] = "password";
|
||||
static const char kAttributeCandidateUfrag[] = "ufrag";
|
||||
static const char kAttributeCandidatePwd[] = "pwd";
|
||||
static const char kAttributeCandidateGeneration[] = "generation";
|
||||
static const char kAttributeFingerprint[] = "fingerprint";
|
||||
static const char kAttributeSetup[] = "setup";
|
||||
@ -262,6 +262,7 @@ static void BuildRtpMap(const MediaContentDescription* media_desc,
|
||||
const MediaType media_type,
|
||||
std::string* message);
|
||||
static void BuildCandidate(const std::vector<Candidate>& candidates,
|
||||
bool include_ufrag,
|
||||
std::string* message);
|
||||
static void BuildIceOptions(const std::vector<std::string>& transport_options,
|
||||
std::string* message);
|
||||
@ -878,7 +879,7 @@ std::string SdpSerializeCandidate(
|
||||
std::string message;
|
||||
std::vector<cricket::Candidate> candidates;
|
||||
candidates.push_back(candidate.candidate());
|
||||
BuildCandidate(candidates, &message);
|
||||
BuildCandidate(candidates, true, &message);
|
||||
// From WebRTC draft section 4.8.1.1 candidate-attribute will be
|
||||
// just candidate:<candidate> not a=candidate:<blah>CRLF
|
||||
ASSERT(message.find("a=") == 0);
|
||||
@ -1072,10 +1073,9 @@ bool ParseCandidate(const std::string& message, Candidate* candidate,
|
||||
}
|
||||
|
||||
// Extension
|
||||
// Empty string as the candidate username and password.
|
||||
// Will be updated later with the ice-ufrag and ice-pwd.
|
||||
// TODO: Remove the username/password extension, which is currently
|
||||
// kept for backwards compatibility.
|
||||
// Though non-standard, we support the ICE ufrag and pwd being signaled on
|
||||
// the candidate to avoid issues with confusing which generation a candidate
|
||||
// belongs to when trickling multiple generations at the same time.
|
||||
std::string username;
|
||||
std::string password;
|
||||
uint32_t generation = 0;
|
||||
@ -1086,9 +1086,9 @@ bool ParseCandidate(const std::string& message, Candidate* candidate,
|
||||
if (!GetValueFromString(first_line, fields[++i], &generation, error)) {
|
||||
return false;
|
||||
}
|
||||
} else if (fields[i] == kAttributeCandidateUsername) {
|
||||
} else if (fields[i] == kAttributeCandidateUfrag) {
|
||||
username = fields[++i];
|
||||
} else if (fields[i] == kAttributeCandidatePassword) {
|
||||
} else if (fields[i] == kAttributeCandidatePwd) {
|
||||
password = fields[++i];
|
||||
} else {
|
||||
// Skip the unknown extension.
|
||||
@ -1285,8 +1285,9 @@ void BuildMediaDescription(const ContentInfo* content_info,
|
||||
}
|
||||
}
|
||||
|
||||
// Build the a=candidate lines.
|
||||
BuildCandidate(candidates, message);
|
||||
// Build the a=candidate lines. We don't include ufrag and pwd in the
|
||||
// candidates in the SDP to avoid redundancy.
|
||||
BuildCandidate(candidates, false, message);
|
||||
|
||||
// Use the transport_info to build the media level ice-ufrag and ice-pwd.
|
||||
if (transport_info) {
|
||||
@ -1717,6 +1718,7 @@ void BuildRtpMap(const MediaContentDescription* media_desc,
|
||||
}
|
||||
|
||||
void BuildCandidate(const std::vector<Candidate>& candidates,
|
||||
bool include_ufrag,
|
||||
std::string* message) {
|
||||
std::ostringstream os;
|
||||
|
||||
@ -1766,6 +1768,9 @@ void BuildCandidate(const std::vector<Candidate>& candidates,
|
||||
|
||||
// Extensions
|
||||
os << kAttributeCandidateGeneration << " " << it->generation();
|
||||
if (include_ufrag && !it->username().empty()) {
|
||||
os << " " << kAttributeCandidateUfrag << " " << it->username();
|
||||
}
|
||||
|
||||
AddLine(os.str(), message);
|
||||
}
|
||||
@ -2677,7 +2682,8 @@ bool ParseContent(const std::string& message,
|
||||
// Update the candidates with the media level "ice-pwd" and "ice-ufrag".
|
||||
for (Candidates::iterator it = candidates_orig.begin();
|
||||
it != candidates_orig.end(); ++it) {
|
||||
ASSERT((*it).username().empty());
|
||||
ASSERT((*it).username().empty() ||
|
||||
(*it).username() == transport->ice_ufrag);
|
||||
(*it).set_username(transport->ice_ufrag);
|
||||
ASSERT((*it).password().empty());
|
||||
(*it).set_password(transport->ice_pwd);
|
||||
|
||||
@ -396,9 +396,9 @@ static const char kRawIPV6Candidate[] =
|
||||
"abcd::abcd::abcd::abcd::abcd::abcd::abcd::abcd 1234 typ host generation 2";
|
||||
|
||||
// One candidate reference string.
|
||||
static const char kSdpOneCandidateOldFormat[] =
|
||||
static const char kSdpOneCandidateWithUfragPwd[] =
|
||||
"a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host network_name"
|
||||
" eth0 username user_rtp password password_rtp generation 2\r\n";
|
||||
" eth0 ufrag user_rtp pwd password_rtp generation 2\r\n";
|
||||
|
||||
// Session id and version
|
||||
static const char kSessionId[] = "18446744069414584320";
|
||||
@ -1727,6 +1727,13 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmap) {
|
||||
TEST_F(WebRtcSdpTest, SerializeCandidates) {
|
||||
std::string message = webrtc::SdpSerializeCandidate(*jcandidate_);
|
||||
EXPECT_EQ(std::string(kRawCandidate), message);
|
||||
|
||||
Candidate candidate_with_ufrag(candidates_.front());
|
||||
candidate_with_ufrag.set_username("ABC");
|
||||
jcandidate_.reset(new JsepIceCandidate(std::string("audio_content_name"), 0,
|
||||
candidate_with_ufrag));
|
||||
message = webrtc::SdpSerializeCandidate(*jcandidate_);
|
||||
EXPECT_EQ(std::string(kRawCandidate) + " ufrag ABC", message);
|
||||
}
|
||||
|
||||
// TODO(mallinath) : Enable this test once WebRTCSdp capable of parsing
|
||||
@ -2323,9 +2330,10 @@ TEST_F(WebRtcSdpTest, DeserializeCandidateWithDifferentTransport) {
|
||||
EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
|
||||
}
|
||||
|
||||
TEST_F(WebRtcSdpTest, DeserializeCandidateOldFormat) {
|
||||
TEST_F(WebRtcSdpTest, DeserializeCandidateWithUfragPwd) {
|
||||
JsepIceCandidate jcandidate(kDummyMid, kDummyIndex);
|
||||
EXPECT_TRUE(SdpDeserializeCandidate(kSdpOneCandidateOldFormat,&jcandidate));
|
||||
EXPECT_TRUE(
|
||||
SdpDeserializeCandidate(kSdpOneCandidateWithUfragPwd, &jcandidate));
|
||||
EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
|
||||
EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
|
||||
Candidate ref_candidate = jcandidate_->candidate();
|
||||
|
||||
@ -218,7 +218,6 @@ P2PTransportChannel::P2PTransportChannel(const std::string& transport_name,
|
||||
remote_ice_mode_(ICEMODE_FULL),
|
||||
ice_role_(ICEROLE_UNKNOWN),
|
||||
tiebreaker_(0),
|
||||
remote_candidate_generation_(0),
|
||||
gathering_state_(kIceGatheringNew),
|
||||
check_receiving_delay_(MIN_CHECK_RECEIVING_DELAY * 5),
|
||||
receiving_timeout_(MIN_CHECK_RECEIVING_DELAY * 50),
|
||||
@ -347,26 +346,19 @@ void P2PTransportChannel::SetIceCredentials(const std::string& ice_ufrag,
|
||||
void P2PTransportChannel::SetRemoteIceCredentials(const std::string& ice_ufrag,
|
||||
const std::string& ice_pwd) {
|
||||
ASSERT(worker_thread_ == rtc::Thread::Current());
|
||||
bool ice_restart = false;
|
||||
if (!remote_ice_ufrag_.empty() && !remote_ice_pwd_.empty()) {
|
||||
ice_restart = (remote_ice_ufrag_ != ice_ufrag) ||
|
||||
(remote_ice_pwd_!= ice_pwd);
|
||||
IceParameters* current_ice = remote_ice();
|
||||
IceParameters new_ice(ice_ufrag, ice_pwd);
|
||||
if (!current_ice || *current_ice != new_ice) {
|
||||
// Keep the ICE credentials so that newer connections
|
||||
// are prioritized over the older ones.
|
||||
remote_ice_parameters_.push_back(new_ice);
|
||||
}
|
||||
|
||||
remote_ice_ufrag_ = ice_ufrag;
|
||||
remote_ice_pwd_ = ice_pwd;
|
||||
|
||||
// We need to update the credentials for any peer reflexive candidates.
|
||||
std::vector<Connection*>::iterator it = connections_.begin();
|
||||
for (; it != connections_.end(); ++it) {
|
||||
(*it)->MaybeSetRemoteIceCredentials(ice_ufrag, ice_pwd);
|
||||
}
|
||||
|
||||
if (ice_restart) {
|
||||
// We need to keep track of the remote ice restart so newer
|
||||
// connections are prioritized over the older.
|
||||
++remote_candidate_generation_;
|
||||
}
|
||||
}
|
||||
|
||||
void P2PTransportChannel::SetRemoteIceMode(IceMode mode) {
|
||||
@ -536,8 +528,11 @@ void P2PTransportChannel::OnUnknownAddress(
|
||||
// The STUN binding request may arrive after setRemoteDescription and before
|
||||
// adding remote candidate, so we need to set the password to the shared
|
||||
// password if the user name matches.
|
||||
if (remote_password.empty() && remote_username == remote_ice_ufrag_) {
|
||||
remote_password = remote_ice_pwd_;
|
||||
if (remote_password.empty()) {
|
||||
IceParameters* current_ice = remote_ice();
|
||||
if (current_ice && remote_username == current_ice->ufrag) {
|
||||
remote_password = current_ice->pwd;
|
||||
}
|
||||
}
|
||||
|
||||
Candidate remote_candidate;
|
||||
@ -655,19 +650,39 @@ void P2PTransportChannel::OnNominated(Connection* conn) {
|
||||
void P2PTransportChannel::AddRemoteCandidate(const Candidate& candidate) {
|
||||
ASSERT(worker_thread_ == rtc::Thread::Current());
|
||||
|
||||
uint32_t generation = candidate.generation();
|
||||
// Network may not guarantee the order of the candidate delivery. If a
|
||||
// remote candidate with an older generation arrives, drop it.
|
||||
if (generation != 0 && generation < remote_candidate_generation_) {
|
||||
LOG(LS_WARNING) << "Dropping a remote candidate because its generation "
|
||||
<< generation
|
||||
<< " is lower than the current remote generation "
|
||||
<< remote_candidate_generation_;
|
||||
uint32_t generation = GetRemoteCandidateGeneration(candidate);
|
||||
// If a remote candidate with a previous generation arrives, drop it.
|
||||
if (generation < remote_ice_generation()) {
|
||||
LOG(LS_WARNING) << "Dropping a remote candidate because its ufrag "
|
||||
<< candidate.username()
|
||||
<< " indicates it was for a previous generation.";
|
||||
return;
|
||||
}
|
||||
|
||||
Candidate new_remote_candidate(candidate);
|
||||
new_remote_candidate.set_generation(generation);
|
||||
// ICE candidates don't need to have username and password set, but
|
||||
// the code below this (specifically, ConnectionRequest::Prepare in
|
||||
// port.cc) uses the remote candidates's username. So, we set it
|
||||
// here.
|
||||
if (remote_ice()) {
|
||||
if (candidate.username().empty()) {
|
||||
new_remote_candidate.set_username(remote_ice()->ufrag);
|
||||
}
|
||||
if (new_remote_candidate.username() == remote_ice()->ufrag) {
|
||||
if (candidate.password().empty()) {
|
||||
new_remote_candidate.set_password(remote_ice()->pwd);
|
||||
}
|
||||
} else {
|
||||
// The candidate belongs to the next generation. Its pwd will be set
|
||||
// when the new remote ICE credentials arrive.
|
||||
LOG(LS_WARNING) << "A remote candidate arrives with an unknown ufrag: "
|
||||
<< candidate.username();
|
||||
}
|
||||
}
|
||||
|
||||
// Create connections to this remote candidate.
|
||||
CreateConnections(candidate, NULL);
|
||||
CreateConnections(new_remote_candidate, NULL);
|
||||
|
||||
// Resort the connections list, which may have new elements.
|
||||
SortConnections();
|
||||
@ -680,20 +695,6 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate,
|
||||
PortInterface* origin_port) {
|
||||
ASSERT(worker_thread_ == rtc::Thread::Current());
|
||||
|
||||
Candidate new_remote_candidate(remote_candidate);
|
||||
new_remote_candidate.set_generation(
|
||||
GetRemoteCandidateGeneration(remote_candidate));
|
||||
// ICE candidates don't need to have username and password set, but
|
||||
// the code below this (specifically, ConnectionRequest::Prepare in
|
||||
// port.cc) uses the remote candidates's username. So, we set it
|
||||
// here.
|
||||
if (remote_candidate.username().empty()) {
|
||||
new_remote_candidate.set_username(remote_ice_ufrag_);
|
||||
}
|
||||
if (remote_candidate.password().empty()) {
|
||||
new_remote_candidate.set_password(remote_ice_pwd_);
|
||||
}
|
||||
|
||||
// If we've already seen the new remote candidate (in the current candidate
|
||||
// generation), then we shouldn't try creating connections for it.
|
||||
// We either already have a connection for it, or we previously created one
|
||||
@ -702,7 +703,7 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate,
|
||||
// immediately be re-pruned, churning the network for no purpose.
|
||||
// This only applies to candidates received over signaling (i.e. origin_port
|
||||
// is NULL).
|
||||
if (!origin_port && IsDuplicateRemoteCandidate(new_remote_candidate)) {
|
||||
if (!origin_port && IsDuplicateRemoteCandidate(remote_candidate)) {
|
||||
// return true to indicate success, without creating any new connections.
|
||||
return true;
|
||||
}
|
||||
@ -715,7 +716,7 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate,
|
||||
bool created = false;
|
||||
std::vector<PortInterface *>::reverse_iterator it;
|
||||
for (it = ports_.rbegin(); it != ports_.rend(); ++it) {
|
||||
if (CreateConnection(*it, new_remote_candidate, origin_port)) {
|
||||
if (CreateConnection(*it, remote_candidate, origin_port)) {
|
||||
if (*it == origin_port)
|
||||
created = true;
|
||||
}
|
||||
@ -723,12 +724,12 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate,
|
||||
|
||||
if ((origin_port != NULL) &&
|
||||
std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) {
|
||||
if (CreateConnection(origin_port, new_remote_candidate, origin_port))
|
||||
if (CreateConnection(origin_port, remote_candidate, origin_port))
|
||||
created = true;
|
||||
}
|
||||
|
||||
// Remember this remote candidate so that we can add it to future ports.
|
||||
RememberRemoteCandidate(new_remote_candidate, origin_port);
|
||||
RememberRemoteCandidate(remote_candidate, origin_port);
|
||||
|
||||
return created;
|
||||
}
|
||||
@ -789,9 +790,27 @@ uint32_t P2PTransportChannel::GetRemoteCandidateGeneration(
|
||||
const Candidate& candidate) {
|
||||
// We need to keep track of the remote ice restart so newer
|
||||
// connections are prioritized over the older.
|
||||
ASSERT(candidate.generation() == 0 ||
|
||||
candidate.generation() == remote_candidate_generation_);
|
||||
return remote_candidate_generation_;
|
||||
const auto& params = remote_ice_parameters_;
|
||||
if (!candidate.username().empty()) {
|
||||
// If remote side sets the ufrag, we use that to determine the candidate
|
||||
// generation.
|
||||
// Search backward as it is more likely to find it near the end.
|
||||
auto it = std::find_if(params.rbegin(), params.rend(),
|
||||
[candidate](const IceParameters& param) {
|
||||
return param.ufrag == candidate.username();
|
||||
});
|
||||
if (it == params.rend()) {
|
||||
// If not found, assume it is the next (future) generation.
|
||||
return static_cast<uint32_t>(remote_ice_parameters_.size());
|
||||
}
|
||||
return params.rend() - it - 1;
|
||||
}
|
||||
// If candidate generation is set, use that.
|
||||
if (candidate.generation() > 0) {
|
||||
return candidate.generation();
|
||||
}
|
||||
// Otherwise, assume the generation from remote ice parameters.
|
||||
return remote_ice_generation();
|
||||
}
|
||||
|
||||
// Check if remote candidate is already cached.
|
||||
|
||||
@ -36,6 +36,18 @@ namespace cricket {
|
||||
|
||||
extern const uint32_t WEAK_PING_DELAY;
|
||||
|
||||
struct IceParameters {
|
||||
std::string ufrag;
|
||||
std::string pwd;
|
||||
IceParameters(const std::string& ice_ufrag, const std::string& ice_pwd)
|
||||
: ufrag(ice_ufrag), pwd(ice_pwd) {}
|
||||
|
||||
bool operator==(const IceParameters& other) {
|
||||
return ufrag == other.ufrag && pwd == other.pwd;
|
||||
}
|
||||
bool operator!=(const IceParameters& other) { return !(*this == other); }
|
||||
};
|
||||
|
||||
// Adds the port on which the candidate originated.
|
||||
class RemoteCandidate : public Candidate {
|
||||
public:
|
||||
@ -229,6 +241,20 @@ class P2PTransportChannel : public TransportChannelImpl,
|
||||
Connection* best_nominated_connection() const;
|
||||
bool IsBackupConnection(Connection* conn) const;
|
||||
|
||||
// Returns the latest remote ICE parameters or nullptr if there are no remote
|
||||
// ICE parameters yet.
|
||||
IceParameters* remote_ice() {
|
||||
return remote_ice_parameters_.empty() ? nullptr
|
||||
: &remote_ice_parameters_.back();
|
||||
}
|
||||
// Returns the index of the latest remote ICE parameters, or 0 if no remote
|
||||
// ICE parameters have been received.
|
||||
uint32_t remote_ice_generation() {
|
||||
return remote_ice_parameters_.empty()
|
||||
? 0
|
||||
: static_cast<uint32_t>(remote_ice_parameters_.size() - 1);
|
||||
}
|
||||
|
||||
P2PTransport* transport_;
|
||||
PortAllocator* allocator_;
|
||||
rtc::Thread* worker_thread_;
|
||||
@ -248,12 +274,10 @@ class P2PTransportChannel : public TransportChannelImpl,
|
||||
OptionMap options_;
|
||||
std::string ice_ufrag_;
|
||||
std::string ice_pwd_;
|
||||
std::string remote_ice_ufrag_;
|
||||
std::string remote_ice_pwd_;
|
||||
std::vector<IceParameters> remote_ice_parameters_;
|
||||
IceMode remote_ice_mode_;
|
||||
IceRole ice_role_;
|
||||
uint64_t tiebreaker_;
|
||||
uint32_t remote_candidate_generation_;
|
||||
IceGatheringState gathering_state_;
|
||||
|
||||
int check_receiving_delay_;
|
||||
|
||||
@ -1767,12 +1767,14 @@ class P2PTransportChannelPingTest : public testing::Test,
|
||||
|
||||
cricket::Candidate CreateCandidate(const std::string& ip,
|
||||
int port,
|
||||
int priority) {
|
||||
int priority,
|
||||
const std::string& ufrag = "") {
|
||||
cricket::Candidate c;
|
||||
c.set_address(rtc::SocketAddress(ip, port));
|
||||
c.set_component(1);
|
||||
c.set_protocol(cricket::UDP_PROTOCOL_NAME);
|
||||
c.set_priority(priority);
|
||||
c.set_username(ufrag);
|
||||
return c;
|
||||
}
|
||||
|
||||
@ -1856,6 +1858,51 @@ TEST_F(P2PTransportChannelPingTest, TestNoTriggeredChecksWhenWritable) {
|
||||
EXPECT_EQ(conn2, ch.FindNextPingableConnection());
|
||||
}
|
||||
|
||||
// Test adding remote candidates with different ufrags. If a remote candidate
|
||||
// is added with an old ufrag, it will be discarded. If it is added with a
|
||||
// ufrag that was not seen before, it will be used to create connections
|
||||
// although the ICE pwd in the remote candidate will be set when the ICE
|
||||
// credentials arrive. If a remote candidate is added with the current ICE
|
||||
// ufrag, its pwd and generation will be set properly.
|
||||
TEST_F(P2PTransportChannelPingTest, TestAddRemoteCandidateWithVariousUfrags) {
|
||||
cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
||||
cricket::P2PTransportChannel ch("add candidate", 1, nullptr, &pa);
|
||||
PrepareChannel(&ch);
|
||||
ch.Connect();
|
||||
ch.MaybeStartGathering();
|
||||
// Add a candidate with a future ufrag.
|
||||
ch.AddRemoteCandidate(CreateCandidate("1.1.1.1", 1, 1, kIceUfrag[2]));
|
||||
cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
|
||||
ASSERT_TRUE(conn1 != nullptr);
|
||||
const cricket::Candidate& candidate = conn1->remote_candidate();
|
||||
EXPECT_EQ(kIceUfrag[2], candidate.username());
|
||||
EXPECT_TRUE(candidate.password().empty());
|
||||
EXPECT_TRUE(ch.FindNextPingableConnection() == nullptr);
|
||||
|
||||
// Set the remote credentials with the "future" ufrag.
|
||||
// This should set the ICE pwd in the remote candidate of |conn1|, making
|
||||
// it pingable.
|
||||
ch.SetRemoteIceCredentials(kIceUfrag[2], kIcePwd[2]);
|
||||
EXPECT_EQ(kIceUfrag[2], candidate.username());
|
||||
EXPECT_EQ(kIcePwd[2], candidate.password());
|
||||
EXPECT_EQ(conn1, ch.FindNextPingableConnection());
|
||||
|
||||
// Add a candidate with an old ufrag. No connection will be created.
|
||||
ch.AddRemoteCandidate(CreateCandidate("2.2.2.2", 2, 2, kIceUfrag[1]));
|
||||
rtc::Thread::Current()->ProcessMessages(500);
|
||||
EXPECT_TRUE(GetConnectionTo(&ch, "2.2.2.2", 2) == nullptr);
|
||||
|
||||
// Add a candidate with the current ufrag, its pwd and generation will be
|
||||
// assigned, even if the generation is not set.
|
||||
ch.AddRemoteCandidate(CreateCandidate("3.3.3.3", 3, 0, kIceUfrag[2]));
|
||||
cricket::Connection* conn3 = nullptr;
|
||||
ASSERT_TRUE_WAIT((conn3 = GetConnectionTo(&ch, "3.3.3.3", 3)) != nullptr,
|
||||
3000);
|
||||
const cricket::Candidate& new_candidate = conn3->remote_candidate();
|
||||
EXPECT_EQ(kIcePwd[2], new_candidate.password());
|
||||
EXPECT_EQ(1U, new_candidate.generation());
|
||||
}
|
||||
|
||||
TEST_F(P2PTransportChannelPingTest, ConnectionResurrection) {
|
||||
cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
|
||||
cricket::P2PTransportChannel ch("connection resurrection", 1, nullptr, &pa);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user