Chromium Code Reviews| Index: webrtc/p2p/quic/quictransportchannel_unittest.cc |
| diff --git a/webrtc/p2p/quic/quictransportchannel_unittest.cc b/webrtc/p2p/quic/quictransportchannel_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7614d60c82c9b367249dbc5838b15b7c53d0edd1 |
| --- /dev/null |
| +++ b/webrtc/p2p/quic/quictransportchannel_unittest.cc |
| @@ -0,0 +1,508 @@ |
| +/* |
| + * Copyright 2016 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 "webrtc/p2p/quic/quictransportchannel.h" |
| + |
| +#include <set> |
| +#include <string> |
| +#include <vector> |
| + |
| +#include "webrtc/base/common.h" |
| +#include "webrtc/base/gunit.h" |
| +#include "webrtc/base/scoped_ptr.h" |
| +#include "webrtc/base/sslidentity.h" |
| +#include "webrtc/p2p/base/faketransportcontroller.h" |
| + |
| +using cricket::ConnectionRole; |
| +using cricket::IceRole; |
| +using cricket::QuicTransportChannel; |
| +using cricket::TransportChannel; |
| +using cricket::TransportDescription; |
| + |
| +// Timeout in milliseconds for asynchronous operations in unit tests. |
| +const int kTimeoutMs = 1000; |
| + |
| +// Export keying material parameters. |
| +const char kExporterLabel[] = "label"; |
| +const uint8_t kExporterContext[] = "context"; |
| +const size_t kExporterContextLength = sizeof(kExporterContext); |
| +const size_t kOutputKeyLength = 20; |
| + |
| +// Packet size for SRTP. |
| +const size_t kPacketSize = 100; |
| + |
| +// Indicates channel has no write error. |
| +const int kNoWriteError = 0; |
| + |
| +// ICE parameters. |
| +const char kIceUfrag[] = "TESTICEUFRAG0001"; |
| +const char kIcePwd[] = "TESTICEPWD00000000000001"; |
| + |
| +// QUIC packet parameters. |
| +const net::IPAddressNumber kIpAddress(net::kIPv4AddressSize, 0); |
| +const net::IPEndPoint kIpEndpoint(kIpAddress, 0); |
| + |
| +// Detects incoming RTP packets. |
| +bool IsRtpLeadByte(uint8_t b) { |
| + return (b & 0xC0) == 0x80; |
| +} |
| +// Detects incoming QUIC packets. |
| +bool IsQuicLeadByte(uint8_t b) { |
| + return (b & 0x80) == 0; |
| +} |
| + |
| +// Maps SSL role to ICE connection role. The peer with a client role is assumed |
| +// to be the one who initiates the connection. |
| +ConnectionRole SslRoleToConnectionRole(rtc::SSLRole ssl_role) { |
| + return (ssl_role == rtc::SSL_CLIENT) ? cricket::CONNECTIONROLE_ACTIVE |
| + : cricket::CONNECTIONROLE_PASSIVE; |
| +} |
| + |
| +// Allows cricket::FakeTransportChannel to simulate write blocked |
| +// and write error states. |
| +// TODO(mikescarlett): Add this functionality to cricket::FakeTransportChannel. |
| +class FakeTransportChannel : public cricket::FakeTransportChannel { |
|
pthatcher1
2016/03/01 22:46:52
Can you give it a different name to avoid confusti
mikescarlett
2016/03/02 02:34:19
Done.
|
| + public: |
| + FakeTransportChannel(const std::string& name, int component) |
| + : cricket::FakeTransportChannel(name, component), error_(kNoWriteError) {} |
| + int GetError() override { return error_; } |
| + void SetError(int error) { error_ = error; } |
| + int SendPacket(const char* data, |
| + size_t len, |
| + const rtc::PacketOptions& options, |
| + int flags) override { |
| + if (error_ == kNoWriteError) { |
| + return cricket::FakeTransportChannel::SendPacket(data, len, options, |
| + flags); |
| + } |
| + return -1; |
| + } |
| + |
| + private: |
| + int error_; |
| +}; |
| + |
| +// Peer who establishes a handshake using a QuicTransportChannel, which wraps |
| +// a FakeTransportChannel to simulate network connectivity and ICE negotiation. |
| +class QuicTestPeer : public sigslot::has_slots<> { |
| + public: |
| + explicit QuicTestPeer(const std::string& name) |
| + : name_(name), |
| + bytes_sent_(0), |
| + fake_channel_(name_, 0), |
| + quic_channel_(&fake_channel_) { |
| + quic_channel_.SignalReadPacket.connect( |
| + this, &QuicTestPeer::OnTransportChannelReadPacket); |
| + fake_channel_.SetAsync(true); |
| + SetLocalCertificate(); |
| + } |
| + |
| + // Connects |fake_channel_| to that of the other peer. |
| + void Connect(QuicTestPeer* peer) { |
| + fake_channel_.Connect(); |
| + peer->fake_channel_.Connect(); |
| + fake_channel_.SetDestination(&peer->fake_channel_); |
| + } |
| + |
| + // Disconnects |fake_channel_|. |
| + void Disconnect() { fake_channel_.SetDestination(nullptr); } |
| + |
| + // Simulates the fingerprint exchange and ICE parameter negotiation |
| + // which is done before QUIC handshake is started. Peer certificates must be |
| + // set first. |
| + void NegotiateBeforeQuic(QuicTestPeer* peer, |
|
pthatcher1
2016/03/01 22:46:52
It might be more clear to call it "remote_peer"
mikescarlett
2016/03/02 02:34:19
Done.
|
| + IceRole ice_role, |
|
pthatcher1
2016/03/01 22:46:52
Might as well call it local_ice_role to be clear.
mikescarlett
2016/03/02 02:34:19
Done.
|
| + rtc::SSLRole local_ssl_role, |
| + rtc::SSLRole remote_ssl_role) { |
| + rtc::scoped_refptr<rtc::RTCCertificate> local_cert = |
| + quic_channel()->GetLocalCertificate(); |
| + ASSERT_TRUE(local_cert); |
| + rtc::scoped_refptr<rtc::RTCCertificate> remote_cert = |
| + peer->quic_channel()->GetLocalCertificate(); |
| + ASSERT_TRUE(remote_cert); |
| + // Create fingerprints from certificates. |
| + rtc::scoped_ptr<rtc::SSLFingerprint> local_fingerprint; |
| + rtc::scoped_ptr<rtc::SSLFingerprint> remote_fingerprint; |
| + local_fingerprint.reset(CreateFingerprint(local_cert.get())); |
| + ASSERT_NE(local_fingerprint, nullptr); |
| + remote_fingerprint.reset(CreateFingerprint(remote_cert.get())); |
| + ASSERT_NE(remote_fingerprint, nullptr); |
| + // Pass the ICE credentials to the underlying channel. |
| + SetIceCredentials(ice_role, local_fingerprint.get(), |
| + remote_fingerprint.get(), local_ssl_role, |
| + remote_ssl_role); |
| + // Set SSL and fingerprint parameters for QUIC channel. |
| + quic_channel_.SetSslRole(local_ssl_role); |
| + quic_channel_.SetRemoteFingerprint( |
| + remote_fingerprint->algorithm, |
| + reinterpret_cast<const uint8_t*>(remote_fingerprint->digest.data()), |
| + remote_fingerprint->digest.size()); |
| + } |
| + |
| + // Generates ICE credentials and passes them to |quic_channel_|. |
| + void SetIceCredentials(IceRole ice_role, |
| + rtc::SSLFingerprint* local_fingerprint, |
| + rtc::SSLFingerprint* remote_fingerprint, |
| + rtc::SSLRole local_ssl_role, |
| + rtc::SSLRole remote_ssl_role) { |
| + quic_channel_.SetIceRole(ice_role); |
| + quic_channel_.SetIceTiebreaker( |
| + (ice_role == cricket::ICEROLE_CONTROLLING) ? 1 : 2); |
| + |
| + ConnectionRole local_connection_role = |
| + SslRoleToConnectionRole(local_ssl_role); |
| + ConnectionRole remote_connection_role = |
| + SslRoleToConnectionRole(remote_ssl_role); |
| + |
| + TransportDescription local_desc(std::vector<std::string>(), kIceUfrag, |
| + kIcePwd, cricket::ICEMODE_FULL, |
| + local_connection_role, local_fingerprint); |
| + TransportDescription remote_desc( |
| + std::vector<std::string>(), kIceUfrag, kIcePwd, cricket::ICEMODE_FULL, |
| + remote_connection_role, remote_fingerprint); |
| + |
| + quic_channel_.SetIceCredentials(local_desc.ice_ufrag, local_desc.ice_pwd); |
| + quic_channel_.SetRemoteIceCredentials(remote_desc.ice_ufrag, |
| + remote_desc.ice_pwd); |
| + } |
| + |
| + // Sets certificate for |quic_channel_|. |
| + void SetLocalCertificate() { |
| + quic_channel_.SetLocalCertificate( |
| + rtc::RTCCertificate::Create(rtc::scoped_ptr<rtc::SSLIdentity>( |
| + rtc::SSLIdentity::Generate(name_, rtc::KT_DEFAULT)))); |
| + } |
| + |
| + // Creates fingerprint from certificate. |
| + rtc::SSLFingerprint* CreateFingerprint(rtc::RTCCertificate* cert) { |
| + std::string digest_algorithm; |
| + bool get_digest_algorithm = |
| + cert->ssl_certificate().GetSignatureDigestAlgorithm(&digest_algorithm); |
| + if (!get_digest_algorithm || digest_algorithm.empty()) { |
| + return nullptr; |
| + } |
| + scoped_ptr<rtc::SSLFingerprint> fingerprint( |
| + rtc::SSLFingerprint::Create(digest_algorithm, cert->identity())); |
| + if (digest_algorithm != rtc::DIGEST_SHA_256) { |
| + return nullptr; |
| + } |
| + return fingerprint.release(); |
| + } |
| + |
| + // Send packets to the other peer via |quic_channel_|. |
| + void SendPackets(size_t count, bool srtp, bool expect_success) { |
| + char packet[kPacketSize]; |
| + for (size_t sent = 0; sent < count; ++sent) { |
| + // If |srtp| is true, make this packet look like SRTP. |
| + packet[0] = (srtp) ? 0x80 : 0x00; |
| + // Set the bypass flag if we're sending SRTP. |
| + int flags = srtp ? cricket::PF_SRTP_BYPASS : 0; |
| + rtc::PacketOptions packet_options; |
| + int rv = quic_channel_.SendPacket(&packet[0], kPacketSize, packet_options, |
| + flags); |
| + if (rv > 0) { |
| + ASSERT_TRUE(expect_success); |
| + ASSERT_EQ(kPacketSize, static_cast<size_t>(rv)); |
| + bytes_sent_ += rv; |
| + } else { |
| + ASSERT_FALSE(expect_success); |
| + ASSERT_EQ(-1, rv); |
| + } |
| + } |
| + } |
| + |
| + // Sends a non-SRTP packet with the PF_SRTP_BYPASS flag. |
| + int SendInvalidSrtpPacket() { |
| + char packet[kPacketSize]; |
| + // Fill the packet with 0 to form an invalid SRTP packet. |
| + memset(packet, 0, kPacketSize); |
| + rtc::PacketOptions packet_options; |
| + return quic_channel_.SendPacket(&packet[0], kPacketSize, packet_options, |
| + cricket::PF_SRTP_BYPASS); |
| + } |
| + |
| + net::WriteResult WriteQuicPacket(std::string packet) { |
| + return quic_channel_.WritePacket(packet.data(), packet.size(), kIpAddress, |
| + kIpEndpoint); |
| + } |
| + |
| + bool handshake_confirmed() const { |
| + return quic_channel_.quic_state() == cricket::QUIC_TRANSPORT_CONNECTED; |
| + } |
| + |
| + void ClearBytesSent() { bytes_sent_ = 0; } |
| + |
| + void ClearBytesReceived() { bytes_received_ = 0; } |
| + |
| + void SetWriteError(int error) { fake_channel_.SetError(error); } |
| + |
| + size_t bytes_received() const { return bytes_received_; } |
| + |
| + size_t bytes_sent() const { return bytes_sent_; } |
| + |
| + FakeTransportChannel* fake_channel() { return &fake_channel_; } |
| + |
| + QuicTransportChannel* quic_channel() { return &quic_channel_; } |
| + |
| + private: |
| + // QUIC channel callback. |
| + void OnTransportChannelReadPacket(TransportChannel* channel, |
| + const char* data, |
| + size_t size, |
| + const rtc::PacketTime& packet_time, |
| + int flags) { |
| + bytes_received_ += size; |
| + // Only SRTP packets should have the bypass flag set. |
| + int expected_flags = IsRtpLeadByte(data[0]) ? cricket::PF_SRTP_BYPASS : 0; |
| + ASSERT_EQ(expected_flags, flags); |
| + } |
| + |
| + std::string name_; // Channel name. |
| + size_t bytes_sent_; // Bytes sent by QUIC channel. |
| + size_t bytes_received_; // Bytes received by fake channel. |
| + FakeTransportChannel fake_channel_; |
| + QuicTransportChannel quic_channel_; |
| +}; |
| + |
| +class QuicTransportChannelTest : public testing::Test { |
| + public: |
| + QuicTransportChannelTest() : peer1_("P1"), peer2_("P2") {} |
| + |
| + // Performs negotiation before QUIC handshake, then connects the fake |
| + // transport channels of each peer. As a side effect, the QUIC channels |
| + // start sending handshake messages. |
| + void Connect(rtc::SSLRole peer1_ssl_role, rtc::SSLRole peer2_ssl_role) { |
| + ASSERT_NE(peer1_ssl_role, peer2_ssl_role); |
| + NegotiateBeforeQuic(peer1_ssl_role, peer2_ssl_role); |
| + peer1_.Connect(&peer2_); |
| + } |
| + |
| + // By default, |peer1_| has client role and |peer2_| has server role in the |
| + // QUIC handshake. |
| + void Connect() { Connect(rtc::SSL_CLIENT, rtc::SSL_SERVER); } |
| + |
| + // Disconnects the fake transport channels. |
| + void Disconnect() { |
| + peer1_.Disconnect(); |
| + peer2_.Disconnect(); |
| + } |
| + |
| + // Sets up ICE parameters and exchanges fingerprints before QUIC handshake. |
| + void NegotiateBeforeQuic(rtc::SSLRole peer1_ssl_role, |
| + rtc::SSLRole peer2_ssl_role) { |
| + peer1_.NegotiateBeforeQuic(&peer2_, cricket::ICEROLE_CONTROLLED, |
| + peer1_ssl_role, peer2_ssl_role); |
| + peer2_.NegotiateBeforeQuic(&peer1_, cricket::ICEROLE_CONTROLLING, |
| + peer2_ssl_role, peer1_ssl_role); |
| + } |
| + |
| + // Check whether |peer2_| receives packets sent by |peer1_|. |
| + void TestTransfer(size_t count, bool srtp, bool expect_success) { |
| + LOG(INFO) << "Expect packets, srtp=" << srtp |
| + << ", success=" << expect_success; |
| + peer1_.ClearBytesSent(); |
| + peer2_.ClearBytesReceived(); |
| + peer1_.SendPackets(count, srtp, expect_success); |
| + |
| + if (expect_success) { |
| + size_t total_bytes = kPacketSize * count; |
| + EXPECT_EQ_WAIT(total_bytes, peer2_.bytes_received(), kTimeoutMs); |
| + EXPECT_EQ(total_bytes, peer1_.bytes_sent()); |
| + } else { |
| + EXPECT_EQ(0u, peer1_.bytes_sent()); |
| + } |
| + } |
| + |
| + // Check that non-SRTP is not sent with the SRTP_BYPASS flag. |
| + void TestTransferInvalidSrtp() { |
| + peer1_.ClearBytesSent(); |
| + peer2_.ClearBytesReceived(); |
| + EXPECT_EQ(-1, peer1_.SendInvalidSrtpPacket()); |
| + EXPECT_EQ(0u, peer2_.bytes_received()); |
| + } |
| + |
| + // Check that peers export identical keying material after the QUIC handshake. |
| + void TestExportKeyingMaterial() { |
| + uint8_t key1[kOutputKeyLength]; |
| + uint8_t key2[kOutputKeyLength]; |
| + |
| + bool from_success = peer1_.quic_channel()->ExportKeyingMaterial( |
| + kExporterLabel, kExporterContext, kExporterContextLength, true, key1, |
| + kOutputKeyLength); |
| + ASSERT_TRUE(from_success); |
| + bool to_success = peer2_.quic_channel()->ExportKeyingMaterial( |
| + kExporterLabel, kExporterContext, kExporterContextLength, true, key2, |
| + kOutputKeyLength); |
| + ASSERT_TRUE(to_success); |
| + |
| + EXPECT_EQ(0, memcmp(key1, key2, sizeof(key1))); |
| + } |
| + |
| + // Checks if QUIC handshake is done. |
| + bool handshake_confirmed() const { |
| + return peer1_.handshake_confirmed() && peer2_.handshake_confirmed(); |
| + } |
| + |
| + // Checks if QUIC channels are writable. |
| + bool quic_writable() { |
| + return peer1_.quic_channel()->writable() && |
| + peer2_.quic_channel()->writable(); |
| + } |
| + |
| + protected: |
| + // QUIC peer with a client role, who initiates the QUIC handshake. |
| + QuicTestPeer peer1_; |
| + // QUIC peer with a server role, who responds to the client peer. |
| + QuicTestPeer peer2_; |
| +}; |
| + |
| +// Test that the QUIC channel passes down ICE parameters to the underlying ICE |
| +// channel. |
| +TEST_F(QuicTransportChannelTest, ChannelSetupIce) { |
| + NegotiateBeforeQuic(rtc::SSL_CLIENT, rtc::SSL_SERVER); |
| + FakeTransportChannel* channel1 = peer1_.fake_channel(); |
| + FakeTransportChannel* channel2 = peer2_.fake_channel(); |
| + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel1->GetIceRole()); |
| + EXPECT_EQ(2u, channel1->IceTiebreaker()); |
| + EXPECT_EQ(kIceUfrag, channel1->ice_ufrag()); |
| + EXPECT_EQ(kIcePwd, channel1->ice_pwd()); |
| + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel2->GetIceRole()); |
| + EXPECT_EQ(1u, channel2->IceTiebreaker()); |
| +} |
| + |
| +// Test export keying material after QUIC handshake. |
| +TEST_F(QuicTransportChannelTest, ExportKeyingMaterial) { |
| + Connect(); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| + TestExportKeyingMaterial(); |
| +} |
| + |
| +// Test that QUIC channel is not writable before the QUIC handshake. |
| +TEST_F(QuicTransportChannelTest, NotWritableBeforeHandshake) { |
| + Connect(); |
| + EXPECT_FALSE(quic_writable()); |
| + Disconnect(); |
| + EXPECT_FALSE(quic_writable()); |
| + Connect(); |
| + EXPECT_FALSE(quic_writable()); |
| +} |
| + |
| +// Test that once handshake begins, QUIC is not writable until its completion. |
| +TEST_F(QuicTransportChannelTest, QuicHandshake) { |
| + Connect(); |
| + EXPECT_FALSE(quic_writable()); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| + EXPECT_TRUE(quic_writable()); |
| +} |
| + |
| +// Non-SRTP data should not be sent using SendPacket(), regardless of QUIC |
| +// channel state. |
| +TEST_F(QuicTransportChannelTest, TransferNonSrtp) { |
| + TestTransfer(1, false, false); |
| + TestTransfer(1, true, false); |
| + Connect(); |
| + TestTransfer(1, false, false); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| + TestTransfer(1, false, false); |
| +} |
| + |
| +// SRTP data should always be sent, regardless of QUIC channel state. |
| +TEST_F(QuicTransportChannelTest, TransferSrtp) { |
| + Connect(); |
| + TestTransfer(10, true, true); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| + TestTransfer(10, true, true); |
| +} |
| + |
| +// Test that invalid SRTP (non-SRTP data with |
| +// PF_SRTP_BYPASS flag) fails to send with return value -1. |
| +TEST_F(QuicTransportChannelTest, TransferInvalidSrtp) { |
| + TestTransferInvalidSrtp(); |
| + Connect(); |
| + TestTransferInvalidSrtp(); |
| +} |
| + |
| +// Test QuicTransportChannel::WritePacket return values under various states |
| +// of the wrapped channel. |
| +TEST_F(QuicTransportChannelTest, QuicWritePacket) { |
| + peer1_.fake_channel()->Connect(); |
| + peer2_.fake_channel()->Connect(); |
| + peer1_.fake_channel()->SetDestination(peer2_.fake_channel()); |
| + |
| + // Write blocked error. |
| + peer1_.SetWriteError(EWOULDBLOCK); |
| + std::string packet = "FAKEQUICPACKET"; |
| + net::WriteResult write_blocked_result = peer1_.WriteQuicPacket(packet); |
| + EXPECT_EQ(net::WRITE_STATUS_BLOCKED, write_blocked_result.status); |
| + EXPECT_EQ(EWOULDBLOCK, write_blocked_result.error_code); |
| + |
| + // Errors other than write blocked. |
| + peer1_.SetWriteError(ETIMEDOUT); |
| + net::WriteResult write_error_result = peer1_.WriteQuicPacket(packet); |
| + EXPECT_EQ(net::WRITE_STATUS_BLOCKED, write_error_result.status); |
| + EXPECT_EQ(ETIMEDOUT, write_error_result.error_code); |
| + |
| + // No error. |
| + peer1_.SetWriteError(kNoWriteError); |
| + net::WriteResult no_error_result = peer1_.WriteQuicPacket(packet); |
| + EXPECT_EQ(net::WRITE_STATUS_OK, no_error_result.status); |
| + EXPECT_EQ(static_cast<int>(packet.size()), no_error_result.bytes_written); |
| +} |
| + |
| +// Test that SSL roles can be reversed before QUIC handshake. |
| +TEST_F(QuicTransportChannelTest, QuicRoleReversalBeforeQuic) { |
| + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); |
| + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_CLIENT)); |
| + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); |
| +} |
| + |
| +// Test that SSL roles cannot be reversed after QUIC handshake. SetSslRole |
| +// returns true if the current SSL role equals the proposed SSL role. |
| +TEST_F(QuicTransportChannelTest, QuicRoleReversalAfterQuic) { |
| + Connect(); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| + EXPECT_FALSE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); |
| + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_CLIENT)); |
| + EXPECT_FALSE(peer2_.quic_channel()->SetSslRole(rtc::SSL_CLIENT)); |
| + EXPECT_TRUE(peer2_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); |
| +} |
| + |
| +// Set SSL role, then check that GetSslRole returns the same value. |
| +TEST_F(QuicTransportChannelTest, SetGetSslRole) { |
| + ASSERT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); |
| + rtc::scoped_ptr<rtc::SSLRole> role(new rtc::SSLRole()); |
| + ASSERT_TRUE(peer1_.quic_channel()->GetSslRole(role.get())); |
| + EXPECT_EQ(rtc::SSL_SERVER, *role); |
| +} |
| + |
| +// Test that after QUIC handshake is complete, QUIC handshake remains confirmed |
| +// even if underlying channel reconnects. |
| +TEST_F(QuicTransportChannelTest, HandshakeConfirmedAfterReconnect) { |
| + Connect(); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| + Disconnect(); |
| + EXPECT_TRUE(handshake_confirmed()); |
| + Connect(); |
| + EXPECT_TRUE(handshake_confirmed()); |
| +} |
| + |
| +// Test that QUIC is able to resume a handshake if the channel becomes write |
| +// blocked then writable again. |
| +TEST_F(QuicTransportChannelTest, TestQuicCryptoStreamWriteBlocked) { |
| + peer1_.SetWriteError(EWOULDBLOCK); |
| + ASSERT_FALSE(peer1_.quic_channel()->HasDataToWrite()); |
| + Connect(); |
| + ASSERT_TRUE(peer1_.quic_channel()->HasDataToWrite()); |
| + ASSERT_FALSE(handshake_confirmed()); |
| + peer1_.SetWriteError(kNoWriteError); |
| + peer1_.quic_channel()->OnCanWrite(); |
| + ASSERT_FALSE(peer1_.quic_channel()->HasDataToWrite()); |
| + ASSERT_TRUE_WAIT(handshake_confirmed(), kTimeoutMs); |
| +} |