diff options
author | rch <rch@chromium.org> | 2015-11-03 21:25:28 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-04 05:26:09 +0000 |
commit | 99b644cbd649fe031f0fdf882177c89484a65e73 (patch) | |
tree | 1889ce6fbfe77f6090e707a7e58eb2701a2b02ee | |
parent | a4ca64d03fc86e257a06c62da05725623881d8a0 (diff) | |
download | chromium_src-99b644cbd649fe031f0fdf882177c89484a65e73.zip chromium_src-99b644cbd649fe031f0fdf882177c89484a65e73.tar.gz chromium_src-99b644cbd649fe031f0fdf882177c89484a65e73.tar.bz2 |
Landing Recent QUIC changes until: Fri Oct 30 22:23:58 2015 +0000
Change QuicAckFrame's revived_packets to be a single QuicPacketNumber. QUIC now ignores all but the last packet revived by FEC.
Consumes 24 bytes less and is faster and simpler.
Merge internal change: 106729701
https://codereview.chromium.org/1431693002/
Fix a comment in quic_dispatcher.cc to mention the correct method.
Merge internal change: 106724344
https://codereview.chromium.org/1418213011/
Add a new method to QuicSpdySession
Add a public method OnHeadersHeadOfLineBlocking() which does
nothing. This method is used to replace EXPECT_CALL(Log()) in quic
headers stream test.
Merge internal change: 106716057
https://codereview.chromium.org/1410243007/
Simplify QUIC stateless rejects by latching the value of FLAGS_enable_quic_stateless_reject_support directly in the QuicCryptoServerStream constructor instead of requiring that it be passed in via a setter.
Merge internal change: 106714904
https://codereview.chromium.org/1411223011/
Add support of serving SCT on the server side in QUIC, gated by QUIC_VERSION_30.
Add support of serving SCT in QUIC. Flag-protected.
The protocol works like below:
1. The client sends CSCT tag associated with an empty value in client
hello to indicate that it supports cert transparency.
2. If so, the server responds with the signed certificate
timestamp (SCT) using the same tag.
Merge internal change: 106709176
https://codereview.chromium.org/1416583005/
Put a space between available window value and ">=" in log output.
Merge internal change: 106687764
https://codereview.chromium.org/1424313006/
Remove FLAGS_quic_session_map_threshold_for_stateless_rejects, and instead always use stateless rejects when the peer supports it and when FLAGS_gfe2_reloadable_flag_enable_quic_stateless_reject_support is true.
Merge internal change: 106656452
https://codereview.chromium.org/1417033008/
Create a new data structure StreamSequencerBuffer for QuicStreamSequencer. Currently QuicStreamSequencer is using std::list to store frames, which could buffer a series of tiny stream frames if the frames arrive so. And they are read out independently as very small strings never coalesced.
StreamSequencerBuffer coalesce these small strings and read them out as
a whole. It is a circular stream buffer with random write and
in-sequence read. It consists of an array of pointers pointing to memory
block de/allocated using new/delete and a list of GapRange objects to
indicate the missing data between the data already been written into the
buffer. And it keeps tracking the total bytes having been read out.
Merge internal change: 106593539
https://codereview.chromium.org/1409053006/
Remove unused method OnPacketSent from QuicPacketDroppingTestWriter::Delegate.
Merge internal change: 106492030
https://codereview.chromium.org/1422563005/
Remove unused member variables. No functional change.
Remove unused QuicClock from TcpCubicSender and TcpCubicBytesSender.
Merge internal change: 106458891
https://codereview.chromium.org/1406023017/
Remove unused input argument. No functional change expected.
Merge internal change: 106451721
https://codereview.chromium.org/1410013010/
Change QuicWallTime to have microsecond granularity.
Merge internal change: 106446616
https://codereview.chromium.org/1421873007/
On client address changes, only migrate QUIC connections when client address changes are considered to be caused by NATs.
Add an enum PeerAddressChangeType. On each peer address change, the
change type is determined before the connection migration.
Merge internal change: 106444524
https://codereview.chromium.org/1420623004/
Make QUIC stateless rejects work in the face of packet loss by storing the SREJ packet(s) in the QuicTimeWaitListManager so they can be retransmitted.
Store QUIC SREJ packet(s) in the QuicTimeWaitListManager.
Merge internal change: 106433678
https://codereview.chromium.org/1424653008/
Remove QuicPacketCreator::fec_group_number_; replace it with QuicFecGroupInterface::FecGroupNumber(). No behavior change expected.
Merge internal change: 106432163
https://codereview.chromium.org/1409813006/
Remove unused QuicErrorCode QUIC_PACKET_FOR_NONEXISTENT_STREAM. No behavior change.
Merge internal change: 106425553
https://codereview.chromium.org/1408963003/
Merge GetIncomingDynamicStream and GetDynamicStream into a single GetOrCreateDynamicStream method in QuicSession. No behavior change.
Merge internal change: 106421704
https://codereview.chromium.org/1422533006/
Rename MockHelper to the more descriptive (and more consistent with other Mock classes) MockConnectionHelper.
Merge internal change: 106419223
https://codereview.chromium.org/1433433003/
Remove the QuicPacketGenerator from the PacketTooLarge EndToEndTest and no longer allow too large packets to be created by the QuicPacketCreator.
Merge internal change: 106414015
https://codereview.chromium.org/1411953005/
fix a comment in quic_session.h
Merge internal change: 106400931
https://codereview.chromium.org/1408183008/
Cleanup: Call OnWriteError instead of directly closing the connection when failing to send a QUIC version negotiation packet. No behavior change.
Merge internal change: 106397442
https://codereview.chromium.org/1424043003/
Removing quic_allow_oversized_packets_for_test by generating and writing packets without a QuicConnection. Test-only change.
Merge internal change: 106394664
https://codereview.chromium.org/1415743010/
Change QuicFramer::EncryptPayload to return the length of the encrypted payload rather than a new EncryptedPacket.
Merge internal change: 106389203
Fix QUIC end to end test
Merge internal change: 106366330
https://codereview.chromium.org/1421703005/
Remove the kMaxSegmentSize constant from QUIC's TcpCubicSender and TcpCubicBytesSender and replace with the identical kDefaultTCPMSS. Also fixes an error where the bandwidth resumption check was incorrectly using kMaxPacketSize. Changes bandwidth resumption CWNDs by less than 1%.
Merge internal change: 106356338
https://codereview.chromium.org/1413193009/
Correct (enlarge) the situations when QuicPacketGenerator::MaybeSendFecPacketAndCloseGroup calls packet_creator_.StopFecProtectingPackets.
Merge internal change: 106333315
https://codereview.chromium.org/1417533011/
Factor QuicCryptoClientStream APIs into QuicCryptoClientStreamBase.
Merge internal change: 106327944
https://codereview.chromium.org/1425363002/
Add new version QUIC_VERSION_29 in which streams honor RST_STREAM + NO_ERROR.
Override QuicDataStream OnStreamReset method to honor
QUIC_STREAM_NO_ERROR on QUIC_VERSION_29 and later versions: close write
side only and leave read side open to read response data from server.
Merge internal change: 106313860
https://codereview.chromium.org/1413143004/
Review URL: https://codereview.chromium.org/1421853006
Cr-Commit-Position: refs/heads/master@{#357761}
113 files changed, 2714 insertions, 810 deletions
diff --git a/components/cronet/android/test/quic_test_server.cc b/components/cronet/android/test/quic_test_server.cc index dcb70dc..20a4514 100644 --- a/components/cronet/android/test/quic_test_server.cc +++ b/components/cronet/android/test/quic_test_server.cc @@ -50,7 +50,8 @@ void StartOnServerThread(const base::FilePath& test_files_root) { net::ProofSourceChromium* proof_source = new net::ProofSourceChromium(); CHECK(proof_source->Initialize( directory.Append("quic_test.example.com.crt"), - directory.Append("quic_test.example.com.key.pkcs8"))); + directory.Append("quic_test.example.com.key.pkcs8"), + directory.Append("quic_test.example.com.key.sct"))); g_quic_server = new net::tools::QuicSimpleServer( proof_source, config, net::QuicSupportedVersions()); diff --git a/net/data/ssl/certificates/quic_test.example.com.key.sct b/net/data/ssl/certificates/quic_test.example.com.key.sct new file mode 100644 index 0000000..55b0b42 --- /dev/null +++ b/net/data/ssl/certificates/quic_test.example.com.key.sct @@ -0,0 +1 @@ +fake sct for test.example.com diff --git a/net/net.gypi b/net/net.gypi index e24cd6a..dd96f5e 100644 --- a/net/net.gypi +++ b/net/net.gypi @@ -380,6 +380,8 @@ 'quic/quic_write_blocked_list.h', 'quic/reliable_quic_stream.cc', 'quic/reliable_quic_stream.h', + 'quic/stream_sequencer_buffer.cc', + 'quic/stream_sequencer_buffer.h', ], 'net_non_nacl_sources': [ 'android/android_private_key.cc', @@ -1559,6 +1561,7 @@ 'quic/quic_utils_test.cc', 'quic/quic_write_blocked_list_test.cc', 'quic/reliable_quic_stream_test.cc', + 'quic/stream_sequencer_buffer_test.cc', 'quic/test_tools/crypto_test_utils.cc', 'quic/test_tools/crypto_test_utils.h', 'quic/test_tools/crypto_test_utils_chromium.cc', diff --git a/net/quic/congestion_control/tcp_cubic_bytes_sender.cc b/net/quic/congestion_control/tcp_cubic_bytes_sender.cc index 06288db..c34d448 100644 --- a/net/quic/congestion_control/tcp_cubic_bytes_sender.cc +++ b/net/quic/congestion_control/tcp_cubic_bytes_sender.cc @@ -22,8 +22,7 @@ namespace { // The minimum cwnd based on RFC 3782 (TCP NewReno) for cwnd reductions on a // fast retransmission. const QuicByteCount kDefaultMinimumCongestionWindow = 2 * kDefaultTCPMSS; -const QuicByteCount kMaxSegmentSize = kDefaultTCPMSS; -const QuicByteCount kMaxBurstBytes = 3 * kMaxSegmentSize; +const QuicByteCount kMaxBurstBytes = 3 * kDefaultTCPMSS; const float kRenoBeta = 0.7f; // Reno backoff factor. const uint32 kDefaultNumConnections = 2; // N-connection emulation. } // namespace @@ -44,13 +43,12 @@ TcpCubicBytesSender::TcpCubicBytesSender( largest_sent_packet_number_(0), largest_acked_packet_number_(0), largest_sent_at_last_cutback_(0), - congestion_window_(initial_tcp_congestion_window * kMaxSegmentSize), + congestion_window_(initial_tcp_congestion_window * kDefaultTCPMSS), min_congestion_window_(kDefaultMinimumCongestionWindow), min4_mode_(false), - max_congestion_window_(max_congestion_window * kMaxSegmentSize), - slowstart_threshold_(max_congestion_window * kMaxSegmentSize), - last_cutback_exited_slowstart_(false), - clock_(clock) {} + max_congestion_window_(max_congestion_window * kDefaultTCPMSS), + slowstart_threshold_(max_congestion_window * kDefaultTCPMSS), + last_cutback_exited_slowstart_(false) {} TcpCubicBytesSender::~TcpCubicBytesSender() { } @@ -61,18 +59,18 @@ void TcpCubicBytesSender::SetFromConfig(const QuicConfig& config, if (config.HasReceivedConnectionOptions() && ContainsQuicTag(config.ReceivedConnectionOptions(), kIW10)) { // Initial window experiment. - congestion_window_ = 10 * kMaxSegmentSize; + congestion_window_ = 10 * kDefaultTCPMSS; } if (config.HasReceivedConnectionOptions() && ContainsQuicTag(config.ReceivedConnectionOptions(), kMIN1)) { // Min CWND experiment. - min_congestion_window_ = kMaxSegmentSize; + min_congestion_window_ = kDefaultTCPMSS; } if (config.HasReceivedConnectionOptions() && ContainsQuicTag(config.ReceivedConnectionOptions(), kMIN4)) { // Min CWND of 4 experiment. min4_mode_ = true; - min_congestion_window_ = kMaxSegmentSize; + min_congestion_window_ = kDefaultTCPMSS; } } } @@ -90,8 +88,8 @@ void TcpCubicBytesSender::ResumeConnectionState( // Make sure CWND is in appropriate range (in case of bad data). QuicByteCount new_congestion_window = bandwidth.ToBytesPerPeriod(rtt_ms); congestion_window_ = - max(min(new_congestion_window, kMaxCongestionWindow * kMaxSegmentSize), - kMinCongestionWindowForBandwidthResumption * kMaxSegmentSize); + max(min(new_congestion_window, kMaxCongestionWindow * kDefaultTCPMSS), + kMinCongestionWindowForBandwidthResumption * kDefaultTCPMSS); } void TcpCubicBytesSender::SetNumEmulatedConnections(int num_connections) { @@ -120,7 +118,7 @@ void TcpCubicBytesSender::OnCongestionEvent( if (rtt_updated && InSlowStart() && hybrid_slow_start_.ShouldExitSlowStart( rtt_stats_->latest_rtt(), rtt_stats_->min_rtt(), - congestion_window_ / kMaxSegmentSize)) { + congestion_window_ / kDefaultTCPMSS)) { slowstart_threshold_ = congestion_window_; } for (CongestionVector::const_iterator it = lost_packets.begin(); @@ -227,7 +225,7 @@ QuicTime::Delta TcpCubicBytesSender::TimeUntilSend( if (GetCongestionWindow() > bytes_in_flight) { return QuicTime::Delta::Zero(); } - if (min4_mode_ && bytes_in_flight < 4 * kMaxSegmentSize) { + if (min4_mode_ && bytes_in_flight < 4 * kDefaultTCPMSS) { return QuicTime::Delta::Zero(); } return QuicTime::Delta::Infinite(); @@ -310,7 +308,7 @@ void TcpCubicBytesSender::MaybeIncreaseCwnd( } if (InSlowStart()) { // TCP slow start, exponential growth, increase by one for each ACK. - congestion_window_ += kMaxSegmentSize; + congestion_window_ += kDefaultTCPMSS; DVLOG(1) << "Slow start; congestion window: " << congestion_window_ << " slowstart threshold: " << slowstart_threshold_; return; @@ -322,8 +320,8 @@ void TcpCubicBytesSender::MaybeIncreaseCwnd( // Divide by num_connections to smoothly increase the CWND at a faster rate // than conventional Reno. if (num_acked_packets_ * num_connections_ >= - congestion_window_ / kMaxSegmentSize) { - congestion_window_ += kMaxSegmentSize; + congestion_window_ / kDefaultTCPMSS) { + congestion_window_ += kDefaultTCPMSS; num_acked_packets_ = 0; } diff --git a/net/quic/congestion_control/tcp_cubic_bytes_sender.h b/net/quic/congestion_control/tcp_cubic_bytes_sender.h index 7a345f6..9040fb6 100644 --- a/net/quic/congestion_control/tcp_cubic_bytes_sender.h +++ b/net/quic/congestion_control/tcp_cubic_bytes_sender.h @@ -129,8 +129,6 @@ class NET_EXPORT_PRIVATE TcpCubicBytesSender : public SendAlgorithmInterface { // collection of slowstart_packets_lost. bool last_cutback_exited_slowstart_; - const QuicClock* clock_; - DISALLOW_COPY_AND_ASSIGN(TcpCubicBytesSender); }; diff --git a/net/quic/congestion_control/tcp_cubic_sender.cc b/net/quic/congestion_control/tcp_cubic_sender.cc index 457dd2e..81da996 100644 --- a/net/quic/congestion_control/tcp_cubic_sender.cc +++ b/net/quic/congestion_control/tcp_cubic_sender.cc @@ -23,8 +23,7 @@ namespace { // The minimum cwnd based on RFC 3782 (TCP NewReno) for cwnd reductions on a // fast retransmission. The cwnd after a timeout is still 1. const QuicPacketCount kDefaultMinimumCongestionWindow = 2; -const QuicByteCount kMaxSegmentSize = kDefaultTCPMSS; -const QuicByteCount kMaxBurstBytes = 3 * kMaxSegmentSize; +const QuicByteCount kMaxBurstBytes = 3 * kDefaultTCPMSS; const float kRenoBeta = 0.7f; // Reno backoff factor. const uint32 kDefaultNumConnections = 2; // N-connection emulation. } // namespace @@ -49,8 +48,7 @@ TcpCubicSender::TcpCubicSender(const QuicClock* clock, min4_mode_(false), slowstart_threshold_(max_tcp_congestion_window), last_cutback_exited_slowstart_(false), - max_tcp_congestion_window_(max_tcp_congestion_window), - clock_(clock) {} + max_tcp_congestion_window_(max_tcp_congestion_window) {} TcpCubicSender::~TcpCubicSender() { UMA_HISTOGRAM_COUNTS("Net.QuicSession.FinalTcpCwnd", congestion_window_); @@ -105,7 +103,7 @@ void TcpCubicSender::ResumeConnectionState( // Make sure CWND is in appropriate range (in case of bad data). QuicPacketCount new_congestion_window = - bandwidth.ToBytesPerPeriod(rtt_ms) / kMaxPacketSize; + bandwidth.ToBytesPerPeriod(rtt_ms) / kDefaultTCPMSS; congestion_window_ = max(min(new_congestion_window, kMaxCongestionWindow), kMinCongestionWindowForBandwidthResumption); } @@ -117,7 +115,7 @@ void TcpCubicSender::SetNumEmulatedConnections(int num_connections) { void TcpCubicSender::SetMaxCongestionWindow( QuicByteCount max_congestion_window) { - max_tcp_congestion_window_ = max_congestion_window / kMaxPacketSize; + max_tcp_congestion_window_ = max_congestion_window / kDefaultTCPMSS; } float TcpCubicSender::RenoBeta() const { @@ -237,12 +235,12 @@ QuicTime::Delta TcpCubicSender::TimeUntilSend( if (InRecovery()) { // PRR is used when in recovery. return prr_.TimeUntilSend(GetCongestionWindow(), bytes_in_flight, - slowstart_threshold_ * kMaxSegmentSize); + slowstart_threshold_ * kDefaultTCPMSS); } if (GetCongestionWindow() > bytes_in_flight) { return QuicTime::Delta::Zero(); } - if (min4_mode_ && bytes_in_flight < 4 * kMaxSegmentSize) { + if (min4_mode_ && bytes_in_flight < 4 * kDefaultTCPMSS) { return QuicTime::Delta::Zero(); } return QuicTime::Delta::Infinite(); @@ -279,7 +277,7 @@ QuicTime::Delta TcpCubicSender::RetransmissionDelay() const { } QuicByteCount TcpCubicSender::GetCongestionWindow() const { - return congestion_window_ * kMaxSegmentSize; + return congestion_window_ * kDefaultTCPMSS; } bool TcpCubicSender::InSlowStart() const { @@ -287,7 +285,7 @@ bool TcpCubicSender::InSlowStart() const { } QuicByteCount TcpCubicSender::GetSlowStartThreshold() const { - return slowstart_threshold_ * kMaxSegmentSize; + return slowstart_threshold_ * kDefaultTCPMSS; } bool TcpCubicSender::IsCwndLimited(QuicByteCount bytes_in_flight) const { diff --git a/net/quic/congestion_control/tcp_cubic_sender.h b/net/quic/congestion_control/tcp_cubic_sender.h index 1e82401..e4eb908 100644 --- a/net/quic/congestion_control/tcp_cubic_sender.h +++ b/net/quic/congestion_control/tcp_cubic_sender.h @@ -130,8 +130,6 @@ class NET_EXPORT_PRIVATE TcpCubicSender : public SendAlgorithmInterface { // Maximum number of outstanding packets for tcp. QuicPacketCount max_tcp_congestion_window_; - const QuicClock* clock_; - DISALLOW_COPY_AND_ASSIGN(TcpCubicSender); }; diff --git a/net/quic/congestion_control/tcp_cubic_sender_test.cc b/net/quic/congestion_control/tcp_cubic_sender_test.cc index ad19af8..3d13996 100644 --- a/net/quic/congestion_control/tcp_cubic_sender_test.cc +++ b/net/quic/congestion_control/tcp_cubic_sender_test.cc @@ -748,7 +748,7 @@ TEST_F(TcpCubicSenderTest, BandwidthResumption) { CachedNetworkParameters cached_network_params; const QuicPacketCount kNumberOfPackets = 123; const int kBandwidthEstimateBytesPerSecond = - kNumberOfPackets * kMaxPacketSize; + kNumberOfPackets * kDefaultTCPMSS; cached_network_params.set_bandwidth_estimate_bytes_per_second( kBandwidthEstimateBytesPerSecond); cached_network_params.set_min_rtt_ms(1000); @@ -761,12 +761,12 @@ TEST_F(TcpCubicSenderTest, BandwidthResumption) { // Resumed CWND is limited to be in a sensible range. cached_network_params.set_bandwidth_estimate_bytes_per_second( - (kMaxCongestionWindow + 1) * kMaxPacketSize); + (kMaxCongestionWindow + 1) * kDefaultTCPMSS); sender_->ResumeConnectionState(cached_network_params, false); EXPECT_EQ(kMaxCongestionWindow, sender_->congestion_window()); cached_network_params.set_bandwidth_estimate_bytes_per_second( - (kMinCongestionWindowForBandwidthResumption - 1) * kMaxPacketSize); + (kMinCongestionWindowForBandwidthResumption - 1) * kDefaultTCPMSS); sender_->ResumeConnectionState(cached_network_params, false); EXPECT_EQ(kMinCongestionWindowForBandwidthResumption, sender_->congestion_window()); diff --git a/net/quic/crypto/crypto_handshake.cc b/net/quic/crypto/crypto_handshake.cc index c790814..7676c8c 100644 --- a/net/quic/crypto/crypto_handshake.cc +++ b/net/quic/crypto/crypto_handshake.cc @@ -15,7 +15,8 @@ QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters() : key_exchange(0), aead(0), x509_ecdsa_supported(false), - x509_supported(false) {} + x509_supported(false), + sct_supported_by_client(false) {} QuicCryptoNegotiatedParameters::~QuicCryptoNegotiatedParameters() {} diff --git a/net/quic/crypto/crypto_handshake.h b/net/quic/crypto/crypto_handshake.h index 74671cae..ef4fe22 100644 --- a/net/quic/crypto/crypto_handshake.h +++ b/net/quic/crypto/crypto_handshake.h @@ -137,6 +137,10 @@ struct NET_EXPORT_PRIVATE QuicCryptoNegotiatedParameters { // Used to generate cert chain when sending server config updates. std::string client_common_set_hashes; std::string client_cached_cert_hashes; + + // Default to false; set to true if the client indicates that it supports sct + // by sending CSCT tag with an empty value in client hello. + bool sct_supported_by_client; }; struct NET_EXPORT_PRIVATE QuicCryptoProof { @@ -146,6 +150,7 @@ struct NET_EXPORT_PRIVATE QuicCryptoProof { std::string signature; // QuicCryptoProof does not take ownership of |certs|. const std::vector<std::string>* certs; + std::string cert_sct; }; // QuicCryptoConfig contains common configuration between clients and servers. diff --git a/net/quic/crypto/crypto_protocol.h b/net/quic/crypto/crypto_protocol.h index 6d56198..18dbc0c 100644 --- a/net/quic/crypto/crypto_protocol.h +++ b/net/quic/crypto/crypto_protocol.h @@ -188,6 +188,8 @@ const QuicTag kSourceAddressTokenTag = TAG('S', 'T', 'K', 0); // Source-address token const QuicTag kCertificateTag = TAG('C', 'R', 'T', 255); // Certificate chain +const QuicTag kCertificateSCTTag = + TAG('C', 'S', 'C', 'T'); // Signed cert timestamp (RFC6962) of leaf cert. #undef TAG diff --git a/net/quic/crypto/crypto_server_test.cc b/net/quic/crypto/crypto_server_test.cc index 52b4baa..427c1cf 100644 --- a/net/quic/crypto/crypto_server_test.cc +++ b/net/quic/crypto/crypto_server_test.cc @@ -115,6 +115,8 @@ class CryptoServerTest : public ::testing::TestWithParam<TestParams> { CryptoTestUtils::FakeProofSourceForTesting()) { #endif supported_versions_ = GetParam().supported_versions; + config_.set_enable_serving_sct(true); + client_version_ = supported_versions_.front(); client_version_string_ = QuicUtils::TagToString(QuicVersionToQuicTag(client_version_)); @@ -149,6 +151,7 @@ class CryptoServerTest : public ::testing::TestWithParam<TestParams> { "KEXS", "C255", "PUBS", pub_hex_.c_str(), "NONC", nonce_hex_.c_str(), + "CSCT", "", "VER\0", client_version_string_.c_str(), "$padding", static_cast<int>(kClientHelloMinimumSize), nullptr); @@ -348,6 +351,7 @@ class CryptoServerTest : public ::testing::TestWithParam<TestParams> { const vector<string>* certs; IPAddressNumber server_ip; string sig; + string cert_sct; #if defined(USE_OPENSSL) scoped_ptr<ProofSource> proof_source( CryptoTestUtils::ProofSourceForTesting()); @@ -355,7 +359,8 @@ class CryptoServerTest : public ::testing::TestWithParam<TestParams> { scoped_ptr<ProofSource> proof_source( CryptoTestUtils::FakeProofSourceForTesting()); #endif - if (!proof_source->GetProof(server_ip, "", "", false, &certs, &sig) || + if (!proof_source->GetProof(server_ip, "", "", false, &certs, &sig, + &cert_sct) || certs->empty()) { return "#0100000000000000"; } @@ -440,14 +445,17 @@ TEST_P(CryptoServerTest, DefaultCert) { // clang-format on ShouldSucceed(msg); - StringPiece cert, proof; + StringPiece cert, proof, cert_sct; EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert)); EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof)); + EXPECT_EQ(client_version_ > QUIC_VERSION_29, + out_.GetStringPiece(kCertificateSCTTag, &cert_sct)); EXPECT_NE(0u, cert.size()); EXPECT_NE(0u, proof.size()); const HandshakeFailureReason kRejectReasons[] = { SERVER_CONFIG_INCHOATE_HELLO_FAILURE}; CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons)); + EXPECT_EQ(client_version_ > QUIC_VERSION_29, cert_sct.size() > 0); } TEST_P(CryptoServerTest, TooSmall) { diff --git a/net/quic/crypto/crypto_utils.cc b/net/quic/crypto/crypto_utils.cc index 6021dc6..555a2b9 100644 --- a/net/quic/crypto/crypto_utils.cc +++ b/net/quic/crypto/crypto_utils.cc @@ -165,4 +165,40 @@ uint64 CryptoUtils::ComputeLeafCertHash(const std::string& cert) { return QuicUtils::FNV1a_64_Hash(cert.data(), cert.size()); } +QuicErrorCode CryptoUtils::ValidateServerHello( + const CryptoHandshakeMessage& server_hello, + const QuicVersionVector& negotiated_versions, + string* error_details) { + DCHECK(error_details != nullptr); + + if (server_hello.tag() != kSHLO) { + *error_details = "Bad tag"; + return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; + } + + const QuicTag* supported_version_tags; + size_t num_supported_versions; + + if (server_hello.GetTaglist(kVER, &supported_version_tags, + &num_supported_versions) != QUIC_NO_ERROR) { + *error_details = "server hello missing version list"; + return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; + } + if (!negotiated_versions.empty()) { + bool mismatch = num_supported_versions != negotiated_versions.size(); + for (size_t i = 0; i < num_supported_versions && !mismatch; ++i) { + mismatch = QuicTagToQuicVersion(supported_version_tags[i]) != + negotiated_versions[i]; + } + // The server sent a list of supported versions, and the connection + // reports that there was a version negotiation during the handshake. + // Ensure that these two lists are identical. + if (mismatch) { + *error_details = "Downgrade attack detected"; + return QUIC_VERSION_NEGOTIATION_MISMATCH; + } + } + return QUIC_NO_ERROR; +} + } // namespace net diff --git a/net/quic/crypto/crypto_utils.h b/net/quic/crypto/crypto_utils.h index 436f178..ff9c951 100644 --- a/net/quic/crypto/crypto_utils.h +++ b/net/quic/crypto/crypto_utils.h @@ -12,6 +12,7 @@ #include "base/strings/string_piece.h" #include "net/base/net_export.h" #include "net/quic/crypto/crypto_handshake.h" +#include "net/quic/crypto/crypto_handshake_message.h" #include "net/quic/crypto/crypto_protocol.h" #include "net/quic/quic_protocol.h" #include "net/quic/quic_time.h" @@ -74,6 +75,16 @@ class NET_EXPORT_PRIVATE CryptoUtils { // XLCT tag. static uint64 ComputeLeafCertHash(const std::string& cert); + // Validates that |server_hello| is actually an SHLO message and that it is + // not part of a downgrade attack. + // + // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error + // code and sets |error_details|. + static QuicErrorCode ValidateServerHello( + const CryptoHandshakeMessage& server_hello, + const QuicVersionVector& negotiated_versions, + std::string* error_details); + private: DISALLOW_COPY_AND_ASSIGN(CryptoUtils); }; diff --git a/net/quic/crypto/proof_source.h b/net/quic/crypto/proof_source.h index 744a8ed..235b972 100644 --- a/net/quic/crypto/proof_source.h +++ b/net/quic/crypto/proof_source.h @@ -45,13 +45,16 @@ class NET_EXPORT_PRIVATE ProofSource { // |hostname| may be empty to signify that a default certificate should be // used. // + // |out_leaf_cert_sct| points to the signed timestamp (RFC6962) of the leaf + // cert. // This function may be called concurrently. virtual bool GetProof(const IPAddressNumber& server_ip, const std::string& hostname, const std::string& server_config, bool ecdsa_ok, const std::vector<std::string>** out_certs, - std::string* out_signature) = 0; + std::string* out_signature, + std::string* out_leaf_cert_sct) = 0; }; } // namespace net diff --git a/net/quic/crypto/proof_source_chromium.h b/net/quic/crypto/proof_source_chromium.h index ef5e676..fae666a 100644 --- a/net/quic/crypto/proof_source_chromium.h +++ b/net/quic/crypto/proof_source_chromium.h @@ -26,9 +26,11 @@ class NET_EXPORT_PRIVATE ProofSourceChromium : public ProofSource { ~ProofSourceChromium() override; // Initializes this object based on the certificate chain in |cert_path|, - // and the PKCS#8 RSA private key in |key_path|. + // and the PKCS#8 RSA private key in |key_path|. Signed certificate + // timestamp may be loaded from |sct_path| if it is non-empty. bool Initialize(const base::FilePath& cert_path, - const base::FilePath& key_path); + const base::FilePath& key_path, + const base::FilePath& sct_path); // ProofSource interface bool GetProof(const IPAddressNumber& server_ip, @@ -36,11 +38,13 @@ class NET_EXPORT_PRIVATE ProofSourceChromium : public ProofSource { const std::string& server_config, bool ecdsa_ok, const std::vector<std::string>** out_certs, - std::string* out_signature) override; + std::string* out_signature, + std::string* out_leaf_cert_sct) override; private: scoped_ptr<crypto::RSAPrivateKey> private_key_; std::vector<std::string> certificates_; + std::string signed_certificate_timestamp_; DISALLOW_COPY_AND_ASSIGN(ProofSourceChromium); }; diff --git a/net/quic/crypto/proof_source_chromium_nss.cc b/net/quic/crypto/proof_source_chromium_nss.cc index f73a6ac..407089a 100644 --- a/net/quic/crypto/proof_source_chromium_nss.cc +++ b/net/quic/crypto/proof_source_chromium_nss.cc @@ -14,7 +14,8 @@ ProofSourceChromium::ProofSourceChromium() {} ProofSourceChromium::~ProofSourceChromium() {} bool ProofSourceChromium::Initialize(const base::FilePath& cert_path, - const base::FilePath& key_path) { + const base::FilePath& key_path, + const base::FilePath& sct_path) { return false; } @@ -23,7 +24,8 @@ bool ProofSourceChromium::GetProof(const IPAddressNumber& server_ip, const string& server_config, bool ecdsa_ok, const vector<string>** out_certs, - string* out_signature) { + string* out_signature, + string* out_leaf_cert_sct) { return false; } diff --git a/net/quic/crypto/proof_source_chromium_openssl.cc b/net/quic/crypto/proof_source_chromium_openssl.cc index c89f2d9..72017d2 100644 --- a/net/quic/crypto/proof_source_chromium_openssl.cc +++ b/net/quic/crypto/proof_source_chromium_openssl.cc @@ -24,7 +24,8 @@ ProofSourceChromium::ProofSourceChromium() {} ProofSourceChromium::~ProofSourceChromium() {} bool ProofSourceChromium::Initialize(const base::FilePath& cert_path, - const base::FilePath& key_path) { + const base::FilePath& key_path, + const base::FilePath& sct_path) { crypto::EnsureOpenSSLInit(); std::string cert_data; @@ -64,6 +65,16 @@ bool ProofSourceChromium::Initialize(const base::FilePath& cert_path, DLOG(FATAL) << "Unable to create private key."; return false; } + + // Loading of the signed certificate timestamp is optional. + if (sct_path.empty()) + return true; + + if (!base::ReadFileToString(sct_path, &signed_certificate_timestamp_)) { + DLOG(FATAL) << "Unable to read signed certificate timestamp."; + return false; + } + return true; } @@ -72,7 +83,8 @@ bool ProofSourceChromium::GetProof(const IPAddressNumber& server_ip, const string& server_config, bool ecdsa_ok, const vector<string>** out_certs, - string* out_signature) { + string* out_signature, + string* out_leaf_cert_sct) { DCHECK(private_key_.get()) << " this: " << this; crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); @@ -108,6 +120,7 @@ bool ProofSourceChromium::GetProof(const IPAddressNumber& server_ip, *out_certs = &certificates_; VLOG(1) << "signature: " << base::HexEncode(out_signature->data(), out_signature->size()); + *out_leaf_cert_sct = signed_certificate_timestamp_; return true; } diff --git a/net/quic/crypto/proof_test.cc b/net/quic/crypto/proof_test.cc index b493f22..67055be 100644 --- a/net/quic/crypto/proof_test.cc +++ b/net/quic/crypto/proof_test.cc @@ -118,18 +118,20 @@ TEST(ProofTest, DISABLED_Verify) { const string hostname = "test.example.com"; const vector<string>* certs; const vector<string>* first_certs; - string error_details, signature, first_signature; + string error_details, signature, first_signature, first_cert_sct, cert_sct; IPAddressNumber server_ip; ASSERT_TRUE(source->GetProof(server_ip, hostname, server_config, false /* no ECDSA */, &first_certs, - &first_signature)); + &first_signature, &first_cert_sct)); ASSERT_TRUE(source->GetProof(server_ip, hostname, server_config, - false /* no ECDSA */, &certs, &signature)); + false /* no ECDSA */, &certs, &signature, + &cert_sct)); // Check that the proof source is caching correctly: ASSERT_EQ(first_certs, certs); ASSERT_EQ(signature, first_signature); + ASSERT_EQ(first_cert_sct, cert_sct); RunVerification( verifier.get(), hostname, server_config, *certs, signature, true); diff --git a/net/quic/crypto/quic_crypto_client_config.cc b/net/quic/crypto/quic_crypto_client_config.cc index 8d46239..b9b0bcd 100644 --- a/net/quic/crypto/quic_crypto_client_config.cc +++ b/net/quic/crypto/quic_crypto_client_config.cc @@ -773,32 +773,10 @@ QuicErrorCode QuicCryptoClientConfig::ProcessServerHello( string* error_details) { DCHECK(error_details != nullptr); - if (server_hello.tag() != kSHLO) { - *error_details = "Bad tag"; - return QUIC_INVALID_CRYPTO_MESSAGE_TYPE; - } - - const QuicTag* supported_version_tags; - size_t num_supported_versions; - - if (server_hello.GetTaglist(kVER, &supported_version_tags, - &num_supported_versions) != QUIC_NO_ERROR) { - *error_details = "server hello missing version list"; - return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; - } - if (!negotiated_versions.empty()) { - bool mismatch = num_supported_versions != negotiated_versions.size(); - for (size_t i = 0; i < num_supported_versions && !mismatch; ++i) { - mismatch = QuicTagToQuicVersion(supported_version_tags[i]) != - negotiated_versions[i]; - } - // The server sent a list of supported versions, and the connection - // reports that there was a version negotiation during the handshake. - // Ensure that these two lists are identical. - if (mismatch) { - *error_details = "Downgrade attack detected"; - return QUIC_VERSION_NEGOTIATION_MISMATCH; - } + QuicErrorCode valid = CryptoUtils::ValidateServerHello( + server_hello, negotiated_versions, error_details); + if (valid != QUIC_NO_ERROR) { + return valid; } // Learn about updated source address tokens. diff --git a/net/quic/crypto/quic_crypto_server_config.cc b/net/quic/crypto/quic_crypto_server_config.cc index 173e9808..c523dd4 100644 --- a/net/quic/crypto/quic_crypto_server_config.cc +++ b/net/quic/crypto/quic_crypto_server_config.cc @@ -223,7 +223,8 @@ QuicCryptoServerConfig::QuicCryptoServerConfig( source_address_token_future_secs_(3600), source_address_token_lifetime_secs_(86400), server_nonce_strike_register_max_entries_(1 << 10), - server_nonce_strike_register_window_secs_(120) { + server_nonce_strike_register_window_secs_(120), + enable_serving_sct_(false) { DCHECK(proof_source_.get()); default_source_address_token_boxer_.SetKey( DeriveSourceAddressTokenKey(source_address_token_secret)); @@ -611,13 +612,21 @@ QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( if (!crypto_proof->certs && !proof_source_->GetProof(server_ip, info.sni.as_string(), primary_config->serialized, x509_ecdsa_supported, - &crypto_proof->certs, - &crypto_proof->signature)) { + &crypto_proof->certs, &crypto_proof->signature, + &crypto_proof->cert_sct)) { return QUIC_HANDSHAKE_FAILED; } + if (version > QUIC_VERSION_29) { + StringPiece cert_sct; + if (client_hello.GetStringPiece(kCertificateSCTTag, &cert_sct) && + cert_sct.empty()) { + params->sct_supported_by_client = true; + } + } + if (!info.reject_reasons.empty() || !requested_config.get()) { - BuildRejection(*primary_config, client_hello, info, + BuildRejection(version, *primary_config, client_hello, info, validate_chlo_result.cached_network_params, use_stateless_rejects, server_designated_connection_id, rand, params, *crypto_proof, out); @@ -1014,10 +1023,10 @@ void QuicCryptoServerConfig::EvaluateClientHello( bool x509_supported = false; bool x509_ecdsa_supported = false; ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported); - if (!proof_source_->GetProof(server_ip, info->sni.as_string(), - requested_config->serialized, - x509_ecdsa_supported, &crypto_proof->certs, - &crypto_proof->signature)) { + if (!proof_source_->GetProof( + server_ip, info->sni.as_string(), requested_config->serialized, + x509_ecdsa_supported, &crypto_proof->certs, + &crypto_proof->signature, &crypto_proof->cert_sct)) { found_error = true; info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE); } @@ -1115,6 +1124,7 @@ void QuicCryptoServerConfig::EvaluateClientHello( } bool QuicCryptoServerConfig::BuildServerConfigUpdateMessage( + QuicVersion version, const SourceAddressTokens& previous_source_address_tokens, const IPAddressNumber& server_ip, const IPAddressNumber& client_ip, @@ -1134,9 +1144,10 @@ bool QuicCryptoServerConfig::BuildServerConfigUpdateMessage( const vector<string>* certs; string signature; + string cert_sct; if (!proof_source_->GetProof( server_ip, params.sni, primary_config_->serialized, - params.x509_ecdsa_supported, &certs, &signature)) { + params.x509_ecdsa_supported, &certs, &signature, &cert_sct)) { DVLOG(1) << "Server: failed to get proof."; return false; } @@ -1147,10 +1158,19 @@ bool QuicCryptoServerConfig::BuildServerConfigUpdateMessage( out->SetStringPiece(kCertificateTag, compressed); out->SetStringPiece(kPROF, signature); + if (params.sct_supported_by_client && version > QUIC_VERSION_29 && + enable_serving_sct_) { + if (cert_sct.empty()) { + DLOG(WARNING) << "SCT is expected but it is empty."; + } else { + out->SetStringPiece(kCertificateSCTTag, cert_sct); + } + } return true; } void QuicCryptoServerConfig::BuildRejection( + QuicVersion version, const Config& config, const CryptoHandshakeMessage& client_hello, const ClientHelloInfo& info, @@ -1215,19 +1235,30 @@ void QuicCryptoServerConfig::BuildRejection( const size_t kREJOverheadBytes = 166; // kMultiplier is the multiple of the CHLO message size that a REJ message // must stay under when the client doesn't present a valid source-address - // token. + // token. This is used to protect QUIC from amplification attacks. const size_t kMultiplier = 2; - // max_unverified_size is the number of bytes that the certificate chain - // and signature can consume before we will demand a valid source-address - // token. + // max_unverified_size is the number of bytes that the certificate chain, + // signature, and (optionally) signed certificate timestamp can consume before + // we will demand a valid source-address token. const size_t max_unverified_size = client_hello.size() * kMultiplier - kREJOverheadBytes; static_assert(kClientHelloMinimumSize * kMultiplier >= kREJOverheadBytes, - "overhead calculation may overflow"); + "overhead calculation may underflow"); + bool should_return_sct = params->sct_supported_by_client && + version > QUIC_VERSION_29 && enable_serving_sct_; + const size_t sct_size = should_return_sct ? crypto_proof.cert_sct.size() : 0; if (info.valid_source_address_token || - crypto_proof.signature.size() + compressed.size() < max_unverified_size) { + crypto_proof.signature.size() + compressed.size() + sct_size < + max_unverified_size) { out->SetStringPiece(kCertificateTag, compressed); out->SetStringPiece(kPROF, crypto_proof.signature); + if (should_return_sct) { + if (crypto_proof.cert_sct.empty()) { + DLOG(WARNING) << "SCT is expected but it is empty."; + } else { + out->SetStringPiece(kCertificateSCTTag, crypto_proof.cert_sct); + } + } } } @@ -1452,6 +1483,10 @@ void QuicCryptoServerConfig::set_server_nonce_strike_register_window_secs( server_nonce_strike_register_window_secs_ = window_secs; } +void QuicCryptoServerConfig::set_enable_serving_sct(bool enable_serving_sct) { + enable_serving_sct_ = enable_serving_sct; +} + void QuicCryptoServerConfig::AcquirePrimaryConfigChangedCb( PrimaryConfigChangedCallback* cb) { base::AutoLock locked(configs_lock_); diff --git a/net/quic/crypto/quic_crypto_server_config.h b/net/quic/crypto/quic_crypto_server_config.h index 02d0523..3bf6d7c 100644 --- a/net/quic/crypto/quic_crypto_server_config.h +++ b/net/quic/crypto/quic_crypto_server_config.h @@ -271,6 +271,7 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { // // |cached_network_params| is optional, and can be nullptr. bool BuildServerConfigUpdateMessage( + QuicVersion version, const SourceAddressTokens& previous_source_address_tokens, const IPAddressNumber& server_ip, const IPAddressNumber& client_ip, @@ -338,6 +339,10 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { // uniqueness. void set_server_nonce_strike_register_window_secs(uint32 window_secs); + // set_enable_serving_sct enables or disables serving signed cert timestamp + // (RFC6962) in server hello. + void set_enable_serving_sct(bool enable_serving_sct); + // Set and take ownership of the callback to invoke on primary config changes. void AcquirePrimaryConfigChangedCb(PrimaryConfigChangedCallback* cb); @@ -438,7 +443,8 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { ValidateClientHelloResultCallback* done_cb) const; // BuildRejection sets |out| to be a REJ message in reply to |client_hello|. - void BuildRejection(const Config& config, + void BuildRejection(QuicVersion version, + const Config& config, const CryptoHandshakeMessage& client_hello, const ClientHelloInfo& info, const CachedNetworkParameters& cached_network_params, @@ -593,6 +599,9 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { uint32 server_nonce_strike_register_max_entries_; uint32 server_nonce_strike_register_window_secs_; + // Enable serving SCT or not. + bool enable_serving_sct_; + DISALLOW_COPY_AND_ASSIGN(QuicCryptoServerConfig); }; diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc index 733154f3..41da420 100644 --- a/net/quic/quic_chromium_client_session.cc +++ b/net/quic/quic_chromium_client_session.cc @@ -337,6 +337,13 @@ QuicChromiumClientSession::~QuicChromiumClientSession() { static_cast<base::HistogramBase::Sample>(stats.max_sequence_reordering)); } +void QuicChromiumClientSession::OnHeadersHeadOfLineBlocking( + QuicTime::Delta delta) { + UMA_HISTOGRAM_TIMES( + "Net.QuicSession.HeadersHOLBlockedTime", + base::TimeDelta::FromMicroseconds(delta.ToMicroseconds())); +} + void QuicChromiumClientSession::OnStreamFrame(const QuicStreamFrame& frame) { // Record total number of stream frames. UMA_HISTOGRAM_COUNTS("Net.QuicNumStreamFramesInPacket", 1); diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h index b269684..9cae22c 100644 --- a/net/quic/quic_chromium_client_session.h +++ b/net/quic/quic_chromium_client_session.h @@ -144,6 +144,9 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession // Cancels the pending stream creation request. void CancelRequest(StreamRequest* request); + // QuicSpdySession methods: + void OnHeadersHeadOfLineBlocking(QuicTime::Delta delta) override; + // QuicSession methods: void OnStreamFrame(const QuicStreamFrame& frame) override; QuicReliableClientStream* CreateOutgoingDynamicStream() override; diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc index faf6dbc..e21d265 100644 --- a/net/quic/quic_chromium_client_session_test.cc +++ b/net/quic/quic_chromium_client_session_test.cc @@ -94,7 +94,7 @@ class QuicChromiumClientSessionTest ASSERT_EQ(OK, callback_.WaitForResult()); } - MockHelper helper_; + MockConnectionHelper helper_; QuicCryptoClientConfig crypto_config_; PacketSavingConnection* connection_; TestNetLog net_log_; diff --git a/net/quic/quic_clock.cc b/net/quic/quic_clock.cc index 9d5ac6d..b20b9c4 100644 --- a/net/quic/quic_clock.cc +++ b/net/quic/quic_clock.cc @@ -24,7 +24,8 @@ QuicTime QuicClock::Now() const { } QuicWallTime QuicClock::WallNow() const { - return QuicWallTime::FromUNIXSeconds(base::Time::Now().ToTimeT()); + return QuicWallTime::FromUNIXMicroseconds(base::Time::Now().ToJavaTime() * + 1000); } } // namespace net diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc index 180e196..e29130a 100644 --- a/net/quic/quic_connection.cc +++ b/net/quic/quic_connection.cc @@ -278,6 +278,7 @@ QuicConnection::QuicConnection(QuicConnectionId connection_id, largest_seen_packet_with_stop_waiting_(0), max_undecryptable_packets_(0), pending_version_negotiation_packet_(false), + save_crypto_packets_as_termination_packets_(false), silent_close_enabled_(false), received_packet_manager_(&stats_), ack_queued_(false), @@ -341,6 +342,9 @@ QuicConnection::~QuicConnection() { delete writer_; } STLDeleteElements(&undecryptable_packets_); + if (termination_packets_.get() != nullptr) { + STLDeleteElements(termination_packets_.get()); + } STLDeleteValues(&group_map_); ClearQueuedPackets(); } @@ -843,12 +847,12 @@ bool QuicConnection::ValidateAckFrame(const QuicAckFrame& incoming_ack) { return false; } - for (QuicPacketNumber revived_packet : incoming_ack.revived_packets) { - if (!incoming_ack.missing_packets.Contains(revived_packet)) { - DLOG(ERROR) << ENDPOINT - << "Peer specified revived packet which was not missing."; - return false; - } + if (incoming_ack.latest_revived_packet != 0 && + !incoming_ack.missing_packets.Contains( + incoming_ack.latest_revived_packet)) { + DLOG(ERROR) << ENDPOINT + << "Peer specified revived packet which was not missing."; + return false; } return true; } @@ -1103,8 +1107,7 @@ void QuicConnection::SendVersionNegotiationPacket() { self_address().address(), peer_address()); if (result.status == WRITE_STATUS_ERROR) { - // We can't send an error as the socket is presumably borked. - CloseConnection(QUIC_PACKET_WRITE_ERROR, false); + OnWriteError(result.error_code); return; } if (result.status == WRITE_STATUS_BLOCKED) { @@ -1340,6 +1343,15 @@ bool QuicConnection::ProcessValidatedPacket() { } if (peer_ip_changed_ || peer_port_changed_) { + PeerAddressChangeType type = DeterminePeerAddressChangeType(); + if (type != NO_CHANGE && type != UNKNOWN && + (FLAGS_quic_disable_non_nat_address_migration && + type != NAT_PORT_REBINDING && type != IPV4_SUBNET_REBINDING)) { + SendConnectionCloseWithDetails(QUIC_ERROR_MIGRATING_ADDRESS, + "Invalid peer address migration."); + return false; + } + IPEndPoint old_peer_address = peer_address_; peer_address_ = IPEndPoint( peer_ip_changed_ ? migrating_peer_ip_ : peer_address_.address(), @@ -1498,9 +1510,9 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) { ++stats_.packets_discarded; return true; } - // Connection close packets are encrypted and saved, so don't exit early. - const bool is_connection_close = IsConnectionClose(*packet); - if (writer_->IsWriteBlocked() && !is_connection_close) { + // Termination packets are encrypted and saved, so don't exit early. + const bool is_termination_packet = IsTerminationPacket(*packet); + if (writer_->IsWriteBlocked() && !is_termination_packet) { return false; } @@ -1509,12 +1521,14 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) { packet_number_of_last_sent_packet_ = packet_number; QuicEncryptedPacket* encrypted = packet->serialized_packet.packet; - // Connection close packets are eventually owned by TimeWaitListManager. + // Termination packets are eventually owned by TimeWaitListManager. // Others are deleted at the end of this call. - if (is_connection_close) { - DCHECK(connection_close_packet_.get() == nullptr); + if (is_termination_packet) { + if (termination_packets_.get() == nullptr) { + termination_packets_.reset(new std::vector<QuicEncryptedPacket*>); + } // Clone the packet so it's owned in the future. - connection_close_packet_.reset(encrypted->Clone()); + termination_packets_->push_back(encrypted->Clone()); // This assures we won't try to write *forced* packets when blocked. // Return true to stop processing. if (writer_->IsWriteBlocked()) { @@ -1523,9 +1537,7 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) { } } - if (!FLAGS_quic_allow_oversized_packets_for_test) { - DCHECK_LE(encrypted->length(), kMaxPacketSize); - } + DCHECK_LE(encrypted->length(), kMaxPacketSize); DCHECK_LE(encrypted->length(), packet_generator_.GetMaxPacketLength()); DVLOG(1) << ENDPOINT << "Sending packet " << packet_number << " : " << (packet->serialized_packet.is_fec_packet @@ -1738,6 +1750,10 @@ void QuicConnection::SendOrQueuePacket(QueuedPacket packet) { } } +PeerAddressChangeType QuicConnection::DeterminePeerAddressChangeType() { + return UNKNOWN; +} + void QuicConnection::SendPing() { if (retransmission_alarm_->IsSet()) { return; @@ -2027,6 +2043,10 @@ bool QuicConnection::HasQueuedData() const { !queued_packets_.empty() || packet_generator_.HasQueuedFrames(); } +void QuicConnection::EnableSavingCryptoPackets() { + save_crypto_packets_as_termination_packets_ = true; +} + bool QuicConnection::CanWriteStreamData() { // Don't write stream data if there are negotiation or queued data packets // to send. Otherwise, continue and bundle as many frames as possible. @@ -2231,7 +2251,7 @@ HasRetransmittableData QuicConnection::IsRetransmittable( } } -bool QuicConnection::IsConnectionClose(const QueuedPacket& packet) { +bool QuicConnection::IsTerminationPacket(const QueuedPacket& packet) { const RetransmittableFrames* retransmittable_frames = packet.serialized_packet.retransmittable_frames; if (retransmittable_frames == nullptr) { @@ -2241,6 +2261,11 @@ bool QuicConnection::IsConnectionClose(const QueuedPacket& packet) { if (frame.type == CONNECTION_CLOSE_FRAME) { return true; } + if (save_crypto_packets_as_termination_packets_ && + frame.type == STREAM_FRAME && + frame.stream_frame->stream_id == kCryptoStreamId) { + return true; + } } return false; } @@ -2251,10 +2276,6 @@ void QuicConnection::SetMtuDiscoveryTarget(QuicByteCount target) { QuicByteCount QuicConnection::LimitMaxPacketSize( QuicByteCount suggested_max_packet_size) { - if (FLAGS_quic_allow_oversized_packets_for_test) { - return suggested_max_packet_size; - } - if (peer_address_.address().empty()) { LOG(DFATAL) << "Attempted to use a connection without a valid peer address"; return suggested_max_packet_size; diff --git a/net/quic/quic_connection.h b/net/quic/quic_connection.h index 8399747..674c5ff 100644 --- a/net/quic/quic_connection.h +++ b/net/quic/quic_connection.h @@ -479,9 +479,9 @@ class NET_EXPORT_PRIVATE QuicConnection // Testing only. size_t NumQueuedPackets() const { return queued_packets_.size(); } - QuicEncryptedPacket* ReleaseConnectionClosePacket() { - return connection_close_packet_.release(); - } + // Once called, any sent crypto packets to be saved as the + // termination packet, for use with stateless rejections. + void EnableSavingCryptoPackets(); // Returns true if the underlying UDP socket is writable, there is // no queued data and the connection is not congestion-control @@ -622,6 +622,10 @@ class NET_EXPORT_PRIVATE QuicConnection // Return the id of the cipher of the primary decrypter of the framer. uint32 cipher_id() const { return framer_.decrypter()->cipher_id(); } + std::vector<QuicEncryptedPacket*>* termination_packets() { + return termination_packets_.get(); + } + protected: // Packets which have not been written to the wire. // Owns the QuicPacket* packet. @@ -652,6 +656,9 @@ class NET_EXPORT_PRIVATE QuicConnection QuicConnectionHelperInterface* helper() { return helper_; } + // On peer address changes, determine and return the change type. + virtual PeerAddressChangeType DeterminePeerAddressChangeType(); + // Selects and updates the version of the protocol being used by selecting a // version from |available_versions| which is also supported. Returns true if // such a version exists, false otherwise. @@ -661,6 +668,10 @@ class NET_EXPORT_PRIVATE QuicConnection bool peer_port_changed() const { return peer_port_changed_; } + const IPAddressNumber& migrating_peer_ip() const { + return migrating_peer_ip_; + } + private: friend class test::QuicConnectionPeer; friend class test::PacketSavingConnection; @@ -765,7 +776,7 @@ class NET_EXPORT_PRIVATE QuicConnection const IPEndPoint& peer_address); HasRetransmittableData IsRetransmittable(const QueuedPacket& packet); - bool IsConnectionClose(const QueuedPacket& packet); + bool IsTerminationPacket(const QueuedPacket& packet); // Set the size of the packet we are targeting while doing path MTU discovery. void SetMtuDiscoveryTarget(QuicByteCount target); @@ -835,8 +846,11 @@ class NET_EXPORT_PRIVATE QuicConnection // unacked_packets_ if they are to be retransmitted. QueuedPacketList queued_packets_; - // Contains the connection close packet if the connection has been closed. - scoped_ptr<QuicEncryptedPacket> connection_close_packet_; + // If true, then crypto packets will be saved as termination packets. + bool save_crypto_packets_as_termination_packets_; + + // Contains the connection close packets if the connection has been closed. + scoped_ptr<std::vector<QuicEncryptedPacket*>> termination_packets_; // When true, the connection does not send a close packet on timeout. bool silent_close_enabled_; diff --git a/net/quic/quic_connection_logger.cc b/net/quic/quic_connection_logger.cc index c24bb6e..9288ed0 100644 --- a/net/quic/quic_connection_logger.cc +++ b/net/quic/quic_connection_logger.cc @@ -128,13 +128,8 @@ scoped_ptr<base::Value> NetLogQuicAckFrameCallback( for (QuicPacketNumber packet : frame->missing_packets) missing->AppendString(base::Uint64ToString(packet)); - base::ListValue* revived = new base::ListValue(); - dict->Set("revived_packets", revived); - const PacketNumberSet& revived_packets = frame->revived_packets; - for (PacketNumberSet::const_iterator it = revived_packets.begin(); - it != revived_packets.end(); ++it) { - revived->AppendString(base::Uint64ToString(*it)); - } + dict->SetString("latest_revived_packet", + base::Int64ToString(frame->latest_revived_packet)); base::ListValue* received = new base::ListValue(); dict->Set("received_packet_times", received); diff --git a/net/quic/quic_connection_logger_unittest.cc b/net/quic/quic_connection_logger_unittest.cc index 2348329..ebaa12d 100644 --- a/net/quic/quic_connection_logger_unittest.cc +++ b/net/quic/quic_connection_logger_unittest.cc @@ -39,7 +39,7 @@ class QuicConnectionLoggerTest : public ::testing::Test { net_log_) {} BoundNetLog net_log_; - MockHelper helper_; + MockConnectionHelper helper_; MockQuicSpdySession session_; QuicConnectionLogger logger_; }; diff --git a/net/quic/quic_connection_test.cc b/net/quic/quic_connection_test.cc index 2ce6c83..765b569 100644 --- a/net/quic/quic_connection_test.cc +++ b/net/quic/quic_connection_test.cc @@ -455,13 +455,14 @@ class TestConnection : public QuicConnection { ? new RetransmittableFrames(ENCRYPTION_NONE) : nullptr; char buffer[kMaxPacketSize]; - QuicEncryptedPacket* encrypted = + size_t encrypted_length = QuicConnectionPeer::GetFramer(this)->EncryptPayload( ENCRYPTION_NONE, packet_number, *packet, buffer, kMaxPacketSize); delete packet; - OnSerializedPacket(SerializedPacket( - packet_number, PACKET_6BYTE_PACKET_NUMBER, encrypted, entropy_hash, - retransmittable_frames, has_ack, has_pending_frames)); + OnSerializedPacket( + SerializedPacket(packet_number, PACKET_6BYTE_PACKET_NUMBER, buffer, + encrypted_length, false, entropy_hash, + retransmittable_frames, has_ack, has_pending_frames)); } QuicConsumedData SendStreamDataWithString( @@ -787,19 +788,23 @@ class QuicConnectionTest : public ::testing::TestWithParam<TestParams> { scoped_ptr<QuicPacket> packet(ConstructDataPacket(number, fec_group, entropy_flag)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted( - framer_.EncryptPayload(level, number, *packet, buffer, kMaxPacketSize)); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); - return encrypted->length(); + size_t encrypted_length = + framer_.EncryptPayload(level, number, *packet, buffer, kMaxPacketSize); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); + return encrypted_length; } void ProcessClosePacket(QuicPacketNumber number, QuicFecGroupNumber fec_group) { scoped_ptr<QuicPacket> packet(ConstructClosePacket(number, fec_group)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, number, *packet, buffer, kMaxPacketSize)); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + size_t encrypted_length = framer_.EncryptPayload( + ENCRYPTION_NONE, number, *packet, buffer, kMaxPacketSize); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); } size_t ProcessFecProtectedPacket(QuicPacketNumber number, @@ -859,11 +864,13 @@ class QuicConnectionTest : public ::testing::TestWithParam<TestParams> { scoped_ptr<QuicPacket> fec_packet( framer_.BuildFecPacket(header, data_packet->FecProtectedData())); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, number, *fec_packet, buffer, kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload( + ENCRYPTION_NONE, number, *fec_packet, buffer, kMaxPacketSize); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); - return encrypted->length(); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); + return encrypted_length; } QuicByteCount SendStreamDataToPeer(QuicStreamId id, @@ -1087,13 +1094,15 @@ TEST_P(QuicConnectionTest, IncreaseServerMaxPacketSize) { frames.push_back(QuicFrame(padding)); scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, 12, *packet, buffer, kMaxPacketSize)); - EXPECT_EQ(kMaxPacketSize, encrypted->length()); + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, + buffer, kMaxPacketSize); + EXPECT_EQ(kMaxPacketSize, encrypted_length); framer_.set_version(version()); EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); EXPECT_EQ(kMaxPacketSize, connection_.max_packet_length()); } @@ -1118,13 +1127,15 @@ TEST_P(QuicConnectionTest, IncreaseServerMaxPacketSizeWhileWriterLimited) { frames.push_back(QuicFrame(padding)); scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, 12, *packet, buffer, kMaxPacketSize)); - EXPECT_EQ(kMaxPacketSize, encrypted->length()); + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, + buffer, kMaxPacketSize); + EXPECT_EQ(kMaxPacketSize, encrypted_length); framer_.set_version(version()); EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); // Here, the limit imposed by the writer is lower than the size of the packet // received, so the writer max packet size is used. @@ -4141,12 +4152,14 @@ TEST_P(QuicConnectionTest, ServerSendsVersionNegotiationPacket) { frames.push_back(QuicFrame(&frame1_)); scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, 12, *packet, buffer, kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, + buffer, kMaxPacketSize); framer_.set_version(version()); connection_.set_perspective(Perspective::IS_SERVER); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); EXPECT_TRUE(writer_->version_negotiation_packet() != nullptr); size_t num_versions = arraysize(kSupportedQuicVersions); @@ -4174,13 +4187,15 @@ TEST_P(QuicConnectionTest, ServerSendsVersionNegotiationPacketSocketBlocked) { frames.push_back(QuicFrame(&frame1_)); scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, 12, *packet, buffer, kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, + buffer, kMaxPacketSize); framer_.set_version(version()); connection_.set_perspective(Perspective::IS_SERVER); BlockOnNextWrite(); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); EXPECT_EQ(0u, writer_->last_packet_size()); EXPECT_TRUE(connection_.HasQueuedData()); @@ -4214,14 +4229,16 @@ TEST_P(QuicConnectionTest, frames.push_back(QuicFrame(&frame1_)); scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, 12, *packet, buffer, kMaxPacketSize)); + size_t encryped_length = framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, + buffer, kMaxPacketSize); framer_.set_version(version()); connection_.set_perspective(Perspective::IS_SERVER); BlockOnNextWrite(); writer_->set_is_write_blocked_data_buffered(true); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encryped_length, false)); EXPECT_EQ(0u, writer_->last_packet_size()); EXPECT_FALSE(connection_.HasQueuedData()); } @@ -4254,11 +4271,14 @@ TEST_P(QuicConnectionTest, ClientHandlesVersionNegotiation) { frames.push_back(QuicFrame(&frame1_)); scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); char buffer[kMaxPacketSize]; - encrypted.reset(framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, buffer, - kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, 12, *packet, + buffer, kMaxPacketSize); + ASSERT_NE(0u, encrypted_length); EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); ASSERT_FALSE(QuicPacketCreatorPeer::SendVersionInPacket(creator_)); } @@ -4389,14 +4409,16 @@ TEST_P(QuicConnectionTest, ProcessFramesIfPacketClosedConnection) { scoped_ptr<QuicPacket> packet(ConstructPacket(header, frames)); EXPECT_TRUE(nullptr != packet.get()); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, 1, *packet, buffer, kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, 1, *packet, + buffer, kMaxPacketSize); EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_PEER_GOING_AWAY, true)); EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1); EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); - connection_.ProcessUdpPacket(kSelfAddress, kPeerAddress, *encrypted); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicEncryptedPacket(buffer, encrypted_length, false)); } TEST_P(QuicConnectionTest, SelectMutualVersion) { @@ -4630,7 +4652,7 @@ TEST_P(QuicConnectionTest, AckNotifierFECTriggerCallback) { EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _)); QuicAckFrame frame = InitAckFrame(2); NackPacket(1, &frame); - frame.revived_packets.insert(1); + frame.latest_revived_packet = 1; ProcessAckPacket(&frame); // If the ack is processed again, the notifier should not be called again. ProcessAckPacket(&frame); diff --git a/net/quic/quic_crypto_client_stream.cc b/net/quic/quic_crypto_client_stream.cc index 8884373..98668e2 100644 --- a/net/quic/quic_crypto_client_stream.cc +++ b/net/quic/quic_crypto_client_stream.cc @@ -17,6 +17,10 @@ using std::string; namespace net { +QuicCryptoClientStreamBase::QuicCryptoClientStreamBase( + QuicClientSessionBase* session) + : QuicCryptoStream(session) {} + QuicCryptoClientStream::ChannelIDSourceCallbackImpl:: ChannelIDSourceCallbackImpl(QuicCryptoClientStream* stream) : stream_(stream) {} @@ -77,7 +81,7 @@ QuicCryptoClientStream::QuicCryptoClientStream( QuicClientSessionBase* session, ProofVerifyContext* verify_context, QuicCryptoClientConfig* crypto_config) - : QuicCryptoStream(session), + : QuicCryptoClientStreamBase(session), next_state_(STATE_IDLE), num_client_hellos_(0), crypto_config_(crypto_config), @@ -103,7 +107,7 @@ QuicCryptoClientStream::~QuicCryptoClientStream() { void QuicCryptoClientStream::OnHandshakeMessage( const CryptoHandshakeMessage& message) { - QuicCryptoStream::OnHandshakeMessage(message); + QuicCryptoClientStreamBase::OnHandshakeMessage(message); if (message.tag() == kSCUP) { if (!handshake_confirmed()) { @@ -187,7 +191,7 @@ void QuicCryptoClientStream::DoHandshakeLoop( DoInitialize(cached); break; case STATE_SEND_CHLO: - DoSendCHLO(in, cached); + DoSendCHLO(cached); return; // return waiting to hear from server. case STATE_RECV_REJ: DoReceiveREJ(in, cached); @@ -237,7 +241,6 @@ void QuicCryptoClientStream::DoInitialize( } void QuicCryptoClientStream::DoSendCHLO( - const CryptoHandshakeMessage* in, QuicCryptoClientConfig::CachedState* cached) { if (stateless_reject_received_) { // If we've gotten to this point, we've sent at least one hello diff --git a/net/quic/quic_crypto_client_stream.h b/net/quic/quic_crypto_client_stream.h index edfc1f5..634b281 100644 --- a/net/quic/quic_crypto_client_stream.h +++ b/net/quic/quic_crypto_client_stream.h @@ -23,7 +23,23 @@ class CryptoTestUtils; class QuicChromiumClientSessionPeer; } // namespace test -class NET_EXPORT_PRIVATE QuicCryptoClientStream : public QuicCryptoStream { +class NET_EXPORT_PRIVATE QuicCryptoClientStreamBase : public QuicCryptoStream { + public: + explicit QuicCryptoClientStreamBase(QuicClientSessionBase* session); + + ~QuicCryptoClientStreamBase() override{}; + + // Performs a crypto handshake with the server. + virtual void CryptoConnect() = 0; + + // num_sent_client_hellos returns the number of client hello messages that + // have been sent. If the handshake has completed then this is one greater + // than the number of round-trips needed for the handshake. + virtual int num_sent_client_hellos() const = 0; +}; + +class NET_EXPORT_PRIVATE QuicCryptoClientStream + : public QuicCryptoClientStreamBase { public: // kMaxClientHellos is the maximum number of times that we'll send a client // hello. The value 3 accounts for: @@ -32,24 +48,20 @@ class NET_EXPORT_PRIVATE QuicCryptoClientStream : public QuicCryptoStream { // the server being unwilling to send it without a valid source-address // token. static const int kMaxClientHellos = 3; - QuicCryptoClientStream(const QuicServerId& server_id, QuicClientSessionBase* session, ProofVerifyContext* verify_context, QuicCryptoClientConfig* crypto_config); + ~QuicCryptoClientStream() override; + // From QuicCryptoClientStreamBase + void CryptoConnect() override; + int num_sent_client_hellos() const override; + // CryptoFramerVisitorInterface implementation void OnHandshakeMessage(const CryptoHandshakeMessage& message) override; - // Performs a crypto handshake with the server. - virtual void CryptoConnect(); - - // num_sent_client_hellos returns the number of client hello messages that - // have been sent. If the handshake has completed then this is one greater - // than the number of round-trips needed for the handshake. - int num_sent_client_hellos() const; - // Returns true if a channel ID was sent on this connection. bool WasChannelIDSent() const; @@ -128,8 +140,7 @@ class NET_EXPORT_PRIVATE QuicCryptoClientStream : public QuicCryptoStream { void DoInitialize(QuicCryptoClientConfig::CachedState* cached); // Send either InchoateClientHello or ClientHello message to the server. - void DoSendCHLO(const CryptoHandshakeMessage* in, - QuicCryptoClientConfig::CachedState* cached); + void DoSendCHLO(QuicCryptoClientConfig::CachedState* cached); // Process REJ message from the server. void DoReceiveREJ(const CryptoHandshakeMessage* in, diff --git a/net/quic/quic_crypto_client_stream_test.cc b/net/quic/quic_crypto_client_stream_test.cc index 0754f480..7d98f47 100644 --- a/net/quic/quic_crypto_client_stream_test.cc +++ b/net/quic/quic_crypto_client_stream_test.cc @@ -58,7 +58,7 @@ class QuicCryptoClientStreamTest : public ::testing::Test { QuicCryptoClientStream* stream() { return session_->GetCryptoStream(); } - MockHelper helper_; + MockConnectionHelper helper_; PacketSavingConnection* connection_; scoped_ptr<TestQuicSpdyClientSession> session_; QuicServerId server_id_; @@ -237,10 +237,10 @@ class QuicCryptoClientStreamStatelessTest : public ::testing::Test { CryptoTestUtils::SetupCryptoServerConfigForTest( server_connection_->clock(), server_connection_->random_generator(), server_session_->config(), &server_crypto_config_); - server_stream()->set_use_stateless_rejects_if_peer_supported(true); + FLAGS_enable_quic_stateless_reject_support = true; } - MockHelper helper_; + MockConnectionHelper helper_; // Client crypto stream state PacketSavingConnection* client_connection_; diff --git a/net/quic/quic_crypto_server_stream.cc b/net/quic/quic_crypto_server_stream.cc index d475833..30ba158 100644 --- a/net/quic/quic_crypto_server_stream.cc +++ b/net/quic/quic_crypto_server_stream.cc @@ -38,7 +38,8 @@ QuicCryptoServerStream::QuicCryptoServerStream( num_handshake_messages_(0), num_handshake_messages_with_server_nonces_(0), num_server_config_update_messages_sent_(0), - use_stateless_rejects_if_peer_supported_(false), + use_stateless_rejects_if_peer_supported_( + FLAGS_enable_quic_stateless_reject_support), peer_supports_stateless_rejects_(false) { DCHECK_EQ(Perspective::IS_SERVER, session->connection()->perspective()); } @@ -93,7 +94,7 @@ void QuicCryptoServerStream::FinishProcessingHandshakeMessage( DCHECK(validate_client_hello_cb_ != nullptr); validate_client_hello_cb_ = nullptr; - if (FLAGS_enable_quic_stateless_reject_support) { + if (use_stateless_rejects_if_peer_supported_) { peer_supports_stateless_rejects_ = DoesPeerSupportStatelessRejects(message); } @@ -108,9 +109,17 @@ void QuicCryptoServerStream::FinishProcessingHandshakeMessage( } if (reply.tag() != kSHLO) { + if (reply.tag() == kSREJ) { + DCHECK(use_stateless_rejects_if_peer_supported()); + DCHECK(peer_supports_stateless_rejects()); + // Before sending the SREJ, cause the connection to save crypto packets + // so that they can be added to the time wait list manager and + // retransmitted. + session()->connection()->EnableSavingCryptoPackets(); + } SendHandshakeMessage(reply); - if (FLAGS_enable_quic_stateless_reject_support && reply.tag() == kSREJ) { + if (reply.tag() == kSREJ) { DCHECK(use_stateless_rejects_if_peer_supported()); DCHECK(peer_supports_stateless_rejects()); DCHECK(!handshake_confirmed()); @@ -180,7 +189,7 @@ void QuicCryptoServerStream::SendServerConfigUpdate( CryptoHandshakeMessage server_config_update_message; if (!crypto_config_->BuildServerConfigUpdateMessage( - previous_source_address_tokens_, + session()->connection()->version(), previous_source_address_tokens_, session()->connection()->self_address().address(), session()->connection()->peer_address().address(), session()->connection()->clock(), @@ -255,7 +264,6 @@ QuicErrorCode QuicCryptoServerStream::ProcessClientHello( previous_source_address_tokens_ = result.info.source_address_tokens; const bool use_stateless_rejects_in_crypto_config = - FLAGS_enable_quic_stateless_reject_support && use_stateless_rejects_if_peer_supported_ && peer_supports_stateless_rejects_; QuicConnection* connection = session()->connection(); diff --git a/net/quic/quic_crypto_server_stream.h b/net/quic/quic_crypto_server_stream.h index dfd8c95..f3d9c92 100644 --- a/net/quic/quic_crypto_server_stream.h +++ b/net/quic/quic_crypto_server_stream.h @@ -92,15 +92,6 @@ class NET_EXPORT_PRIVATE QuicCryptoServerStream : public QuicCryptoStream { return use_stateless_rejects_if_peer_supported_; } - // Used by the quic dispatcher to indicate that this crypto server - // stream should use stateless rejects, so long as stateless rejects - // are supported by the client. - void set_use_stateless_rejects_if_peer_supported( - bool use_stateless_rejects_if_peer_supported) { - use_stateless_rejects_if_peer_supported_ = - use_stateless_rejects_if_peer_supported; - } - bool peer_supports_stateless_rejects() const { return peer_supports_stateless_rejects_; } diff --git a/net/quic/quic_crypto_server_stream_test.cc b/net/quic/quic_crypto_server_stream_test.cc index af3bcb3..01a2db8 100644 --- a/net/quic/quic_crypto_server_stream_test.cc +++ b/net/quic/quic_crypto_server_stream_test.cc @@ -74,6 +74,7 @@ class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> { CryptoTestUtils::ProofSourceForTesting()), server_id_(kServerHostname, kServerPort, PRIVACY_MODE_DISABLED), client_crypto_config_(CryptoTestUtils::ProofVerifierForTesting()) { + FLAGS_enable_quic_stateless_reject_support = false; server_crypto_config_.set_strike_register_no_startup_period(); InitializeServer(); @@ -99,7 +100,8 @@ class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> { // called multiple times. void InitializeServer() { TestQuicSpdyServerSession* server_session = nullptr; - helpers_.push_back(new MockHelper); + helpers_.push_back(new MockConnectionHelper); + CreateServerSessionForTest(server_id_, QuicTime::Delta::FromSeconds(100000), helpers_.back(), &server_crypto_config_, &server_connection_, &server_session); @@ -122,7 +124,7 @@ class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> { // testing. May be called multiple times. void InitializeFakeClient(bool supports_stateless_rejects) { TestQuicSpdyClientSession* client_session = nullptr; - helpers_.push_back(new MockHelper); + helpers_.push_back(new MockConnectionHelper); CreateClientSessionForTest(server_id_, supports_stateless_rejects, QuicTime::Delta::FromSeconds(100000), helpers_.back(), &client_crypto_config_, @@ -164,9 +166,10 @@ class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> { } protected: - // Every connection gets its own MockHelper, tracked separately from the - // server and client state so their lifetimes persist through the whole test. - std::vector<MockHelper*> helpers_; + // Every connection gets its own MockConnectionHelper, tracked separately + // from the server and client state so their lifetimes persist through the + // whole test. + std::vector<MockConnectionHelper*> helpers_; // Server state PacketSavingConnection* server_connection_; @@ -210,7 +213,7 @@ TEST_P(QuicCryptoServerStreamTest, ConnectedAfterCHLO) { TEST_P(QuicCryptoServerStreamTest, StatelessRejectAfterCHLO) { ValueRestore<bool> old_flag(&FLAGS_enable_quic_stateless_reject_support, true); - server_stream()->set_use_stateless_rejects_if_peer_supported(true); + InitializeServer(); InitializeFakeClient(/* supports_stateless_rejects= */ true); AdvanceHandshakeWithFakeClient(); @@ -242,7 +245,7 @@ TEST_P(QuicCryptoServerStreamTest, StatelessRejectAfterCHLO) { TEST_P(QuicCryptoServerStreamTest, ConnectedAfterStatelessHandshake) { ValueRestore<bool> old_flag(&FLAGS_enable_quic_stateless_reject_support, true); - server_stream()->set_use_stateless_rejects_if_peer_supported(true); + InitializeServer(); InitializeFakeClient(/* supports_stateless_rejects= */ true); AdvanceHandshakeWithFakeClient(); @@ -269,7 +272,6 @@ TEST_P(QuicCryptoServerStreamTest, ConnectedAfterStatelessHandshake) { // Now create new client and server streams with the existing config // and try the handshake again (0-RTT handshake). InitializeServer(); - server_stream()->set_use_stateless_rejects_if_peer_supported(true); InitializeFakeClient(/* supports_stateless_rejects= */ true); @@ -290,7 +292,7 @@ TEST_P(QuicCryptoServerStreamTest, ConnectedAfterStatelessHandshake) { TEST_P(QuicCryptoServerStreamTest, NoStatelessRejectIfNoClientSupport) { ValueRestore<bool> old_flag(&FLAGS_enable_quic_stateless_reject_support, true); - server_stream()->set_use_stateless_rejects_if_peer_supported(true); + InitializeServer(); // The server is configured to use stateless rejects, but the client does not // support it. diff --git a/net/quic/quic_crypto_stream_test.cc b/net/quic/quic_crypto_stream_test.cc index 7225030..a6ffdbe 100644 --- a/net/quic/quic_crypto_stream_test.cc +++ b/net/quic/quic_crypto_stream_test.cc @@ -61,7 +61,7 @@ class QuicCryptoStreamTest : public ::testing::Test { } protected: - MockHelper helper_; + MockConnectionHelper helper_; MockConnection* connection_; MockQuicSpdySession session_; MockQuicCryptoStream stream_; diff --git a/net/quic/quic_fec_group.cc b/net/quic/quic_fec_group.cc index 35251d9..37c3e5a 100644 --- a/net/quic/quic_fec_group.cc +++ b/net/quic/quic_fec_group.cc @@ -190,4 +190,8 @@ EncryptionLevel QuicFecGroup::EffectiveEncryptionLevel() const { return effective_encryption_level_; } +QuicFecGroupNumber QuicFecGroup::FecGroupNumber() const { + return min_protected_packet_; +} + } // namespace net diff --git a/net/quic/quic_fec_group.h b/net/quic/quic_fec_group.h index 732e703..c35835c 100644 --- a/net/quic/quic_fec_group.h +++ b/net/quic/quic_fec_group.h @@ -38,6 +38,7 @@ class NET_EXPORT_PRIVATE QuicFecGroup : public QuicFecGroupInterface { const base::StringPiece PayloadParity() const override; QuicPacketCount NumReceivedPackets() const override; EncryptionLevel EffectiveEncryptionLevel() const override; + QuicFecGroupNumber FecGroupNumber() const override; private: bool UpdateParity(base::StringPiece payload); diff --git a/net/quic/quic_fec_group_interface.h b/net/quic/quic_fec_group_interface.h index 159662a..87914f7 100644 --- a/net/quic/quic_fec_group_interface.h +++ b/net/quic/quic_fec_group_interface.h @@ -63,6 +63,9 @@ class NET_EXPORT_PRIVATE QuicFecGroupInterface { // Returns the effective encryption level of the FEC group. virtual EncryptionLevel EffectiveEncryptionLevel() const = 0; + // Return the FEC group number of this group. + virtual QuicFecGroupNumber FecGroupNumber() const = 0; + // An optimized version of running |output| ^= |input|, where ^ is // byte-by-byte XOR and both |output| and |input| are of size |size_in_bytes|. static void XorBuffers(const char* input, size_t size_in_bytes, char* output); diff --git a/net/quic/quic_flags.cc b/net/quic/quic_flags.cc index fe97495..767c1db 100644 --- a/net/quic/quic_flags.cc +++ b/net/quic/quic_flags.cc @@ -111,3 +111,7 @@ bool FLAGS_quic_count_unfinished_as_open_streams = true; // If true, use the unrolled prefetch path in QuicPacketCreator::CopyToBuffer. bool FLAGS_quic_packet_creator_prefetch = false; + +// If true, only migrate QUIC connections when client address changes are +// considered to be caused by NATs. +bool FLAGS_quic_disable_non_nat_address_migration = true; diff --git a/net/quic/quic_flags.h b/net/quic/quic_flags.h index 7b7b0f4..a0e242a 100644 --- a/net/quic/quic_flags.h +++ b/net/quic/quic_flags.h @@ -35,5 +35,6 @@ NET_EXPORT_PRIVATE extern bool FLAGS_quic_implement_stop_reading; NET_EXPORT_PRIVATE extern bool FLAGS_quic_no_ack_notifier; NET_EXPORT_PRIVATE extern bool FLAGS_quic_count_unfinished_as_open_streams; NET_EXPORT_PRIVATE extern bool FLAGS_quic_packet_creator_prefetch; +NET_EXPORT_PRIVATE extern bool FLAGS_quic_disable_non_nat_address_migration; #endif // NET_QUIC_QUIC_FLAGS_H_ diff --git a/net/quic/quic_flow_controller.cc b/net/quic/quic_flow_controller.cc index 72f4c7f..e2eaf3e 100644 --- a/net/quic/quic_flow_controller.cc +++ b/net/quic/quic_flow_controller.cc @@ -176,7 +176,7 @@ void QuicFlowController::MaybeSendWindowUpdate() { if (available_window >= threshold) { DVLOG(1) << ENDPOINT << "Not sending WindowUpdate for stream " << id_ << ", available window: " << available_window - << ">= threshold: " << threshold; + << " >= threshold: " << threshold; return; } diff --git a/net/quic/quic_flow_controller_test.cc b/net/quic/quic_flow_controller_test.cc index 9a7e452..64705765 100644 --- a/net/quic/quic_flow_controller_test.cc +++ b/net/quic/quic_flow_controller_test.cc @@ -40,7 +40,7 @@ class QuicFlowControllerTest : public ::testing::Test { QuicByteCount send_window_; QuicByteCount receive_window_; scoped_ptr<QuicFlowController> flow_controller_; - MockHelper helper_; + MockConnectionHelper helper_; MockConnection connection_; }; diff --git a/net/quic/quic_framer.cc b/net/quic/quic_framer.cc index 56aaaad..1d062fd 100644 --- a/net/quic/quic_framer.cc +++ b/net/quic/quic_framer.cc @@ -304,11 +304,7 @@ size_t QuicFramer::GetSerializedFrameLength( DVLOG(1) << "Truncating large frame, free bytes: " << free_bytes; return free_bytes; } - if (!FLAGS_quic_allow_oversized_packets_for_test) { - return 0; - } - LOG(DFATAL) << "Packet size too small to fit frame."; - return frame_len; + return 0; } QuicFramer::AckFrameInfo::AckFrameInfo() : max_delta(0) {} @@ -1363,6 +1359,7 @@ bool QuicFramer::ProcessAckFrame(QuicDataReader* reader, } // Parse the revived packets list. + // TODO(ianswett): Change the ack frame so it only expresses one revived. uint8 num_revived_packets; if (!reader->ReadBytes(&num_revived_packets, 1)) { set_detailed_error("Unable to read num revived packets."); @@ -1376,8 +1373,7 @@ bool QuicFramer::ProcessAckFrame(QuicDataReader* reader, set_detailed_error("Unable to read revived packet."); return false; } - - ack_frame->revived_packets.insert(revived_packet); + ack_frame->latest_revived_packet = revived_packet; } return true; @@ -1614,44 +1610,28 @@ void QuicFramer::SetEncrypter(EncryptionLevel level, encrypter_[level].reset(encrypter); } -QuicEncryptedPacket* QuicFramer::EncryptPayload(EncryptionLevel level, - QuicPacketNumber packet_number, - const QuicPacket& packet, - char* buffer, - size_t buffer_len) { +size_t QuicFramer::EncryptPayload(EncryptionLevel level, + QuicPacketNumber packet_number, + const QuicPacket& packet, + char* buffer, + size_t buffer_len) { DCHECK(encrypter_[level].get() != nullptr); - const size_t encrypted_len = - encrypter_[level]->GetCiphertextSize(packet.Plaintext().length()); StringPiece header_data = packet.BeforePlaintext(); - const size_t total_len = header_data.length() + encrypted_len; - - char* encryption_buffer = buffer; - // Allocate a large enough buffer for the header and the encrypted data. - const bool is_new_buffer = total_len > buffer_len; - if (is_new_buffer) { - if (!FLAGS_quic_allow_oversized_packets_for_test) { - LOG(DFATAL) << "Buffer of length:" << buffer_len - << " is not large enough to encrypt length " << total_len; - return nullptr; - } - encryption_buffer = new char[total_len]; - } // Copy in the header, because the encrypter only populates the encrypted // plaintext content. - memcpy(encryption_buffer, header_data.data(), header_data.length()); + const size_t header_len = header_data.length(); + memcpy(buffer, header_data.data(), header_len); // Encrypt the plaintext into the buffer. size_t output_length = 0; if (!encrypter_[level]->EncryptPacket( packet_number, packet.AssociatedData(), packet.Plaintext(), - encryption_buffer + header_data.length(), &output_length, - encrypted_len)) { + buffer + header_len, &output_length, buffer_len - header_len)) { RaiseError(QUIC_ENCRYPTION_FAILURE); - return nullptr; + return 0; } - return new QuicEncryptedPacket( - encryption_buffer, header_data.length() + output_length, is_new_buffer); + return header_len + output_length; } size_t QuicFramer::GetMaxPlaintextSize(size_t ciphertext_size) { @@ -1733,8 +1713,9 @@ size_t QuicFramer::GetAckFrameSize( ack_size += kNumberOfNackRangesSize + kNumberOfRevivedPacketsSize; ack_size += min(ack_info.nack_ranges.size(), kMaxNackRanges) * (missing_packet_number_length + PACKET_1BYTE_PACKET_NUMBER); - ack_size += min(ack.revived_packets.size(), - kMaxRevivedPackets) * largest_observed_length; + if (ack.latest_revived_packet != 0) { + ack_size += largest_observed_length; + } } // In version 23, if the ack will be truncated due to too many nack ranges, @@ -2026,20 +2007,20 @@ bool QuicFramer::AppendAckFrameAndTypeByte( // Append revived packets. // If not all the revived packets fit, only mention the ones that do. - uint8 num_revived_packets = - static_cast<uint8>(min(frame.revived_packets.size(), kMaxRevivedPackets)); - num_revived_packets = static_cast<uint8>(min( - static_cast<size_t>(num_revived_packets), - (writer->capacity() - writer->length()) / largest_observed_length)); + uint8 num_revived_packets = frame.latest_revived_packet == 0 ? 0 : 1; + if (((writer->capacity() - writer->length()) / largest_observed_length) == + 0) { + num_revived_packets = 0; + } if (!writer->WriteBytes(&num_revived_packets, 1)) { return false; } - PacketNumberSet::const_iterator iter = frame.revived_packets.begin(); - for (int i = 0; i < num_revived_packets; ++i, ++iter) { - LOG_IF(DFATAL, !frame.missing_packets.Contains(*iter)); + if (num_revived_packets > 0) { + LOG_IF(DFATAL, + !frame.missing_packets.Contains(frame.latest_revived_packet)); if (!AppendPacketSequenceNumber(largest_observed_length, - *iter, writer)) { + frame.latest_revived_packet, writer)) { return false; } } diff --git a/net/quic/quic_framer.h b/net/quic/quic_framer.h index 26e6995..0bc3299 100644 --- a/net/quic/quic_framer.h +++ b/net/quic/quic_framer.h @@ -332,14 +332,13 @@ class NET_EXPORT_PRIVATE QuicFramer { // takes ownership of |encrypter|. void SetEncrypter(EncryptionLevel level, QuicEncrypter* encrypter); - // Returns a new encrypted packet, owned by the caller. - // Encrypts into |buffer| if |buffer_len| is long enough, and otherwise - // constructs a new buffer owned by the EncryptedPacket. - QuicEncryptedPacket* EncryptPayload(EncryptionLevel level, - QuicPacketNumber packet_number, - const QuicPacket& packet, - char* buffer, - size_t buffer_len); + // Returns the length of the data encrypted into |buffer| if |buffer_len| is + // long enough, and otherwise 0. + size_t EncryptPayload(EncryptionLevel level, + QuicPacketNumber packet_number, + const QuicPacket& packet, + char* buffer, + size_t buffer_len); // Returns the maximum length of plaintext that can be encrypted // to ciphertext no larger than |ciphertext_size|. diff --git a/net/quic/quic_framer_test.cc b/net/quic/quic_framer_test.cc index 51430b3..4a3d3be 100644 --- a/net/quic/quic_framer_test.cc +++ b/net/quic/quic_framer_test.cc @@ -2186,7 +2186,7 @@ TEST_P(QuicFramerTest, AckFrame500Nacks) { QuicAckFrame* frame = visitor_.ack_frames_[0]; EXPECT_EQ(0xBA, frame->entropy_hash); EXPECT_EQ(UINT64_C(0x0123456789ABF), frame->largest_observed); - EXPECT_EQ(0u, frame->revived_packets.size()); + EXPECT_EQ(0u, frame->latest_revived_packet); ASSERT_EQ(500u, frame->missing_packets.NumPacketsSlow()); EXPECT_EQ(UINT64_C(0x0123456789ABE) - 499, frame->missing_packets.Min()); EXPECT_EQ(UINT64_C(0x0123456789ABE), frame->missing_packets.Max()); @@ -4023,10 +4023,10 @@ TEST_P(QuicFramerTest, EncryptPacket) { AsChars(packet), arraysize(packet), false, PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion, PACKET_6BYTE_PACKET_NUMBER)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, packet_number, *raw, buffer, kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload( + ENCRYPTION_NONE, packet_number, *raw, buffer, kMaxPacketSize); - ASSERT_TRUE(encrypted.get() != nullptr); + ASSERT_NE(0u, encrypted_length); EXPECT_TRUE(CheckEncryption(packet_number, raw.get())); } @@ -4061,10 +4061,10 @@ TEST_P(QuicFramerTest, EncryptPacketWithVersionFlag) { AsChars(packet), arraysize(packet), false, PACKET_8BYTE_CONNECTION_ID, kIncludeVersion, PACKET_6BYTE_PACKET_NUMBER)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, packet_number, *raw, buffer, kMaxPacketSize)); + size_t encrypted_length = framer_.EncryptPayload( + ENCRYPTION_NONE, packet_number, *raw, buffer, kMaxPacketSize); - ASSERT_TRUE(encrypted.get() != nullptr); + ASSERT_NE(0u, encrypted_length); EXPECT_TRUE(CheckEncryption(packet_number, raw.get())); } @@ -4090,11 +4090,13 @@ TEST_P(QuicFramerTest, AckTruncationLargePacket) { scoped_ptr<QuicPacket> raw_ack_packet(BuildDataPacket(header, frames)); ASSERT_TRUE(raw_ack_packet != nullptr); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> ack_packet( + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, header.packet_number, - *raw_ack_packet, buffer, kMaxPacketSize)); + *raw_ack_packet, buffer, kMaxPacketSize); + ASSERT_NE(0u, encrypted_length); // Now make sure we can turn our ack packet back into an ack frame. - ASSERT_TRUE(framer_.ProcessPacket(*ack_packet)); + ASSERT_TRUE(framer_.ProcessPacket( + QuicEncryptedPacket(buffer, encrypted_length, false))); ASSERT_EQ(1u, visitor_.ack_frames_.size()); QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0]; EXPECT_TRUE(processed_ack_frame.is_truncated); @@ -4126,11 +4128,13 @@ TEST_P(QuicFramerTest, AckTruncationSmallPacket) { scoped_ptr<QuicPacket> raw_ack_packet(BuildDataPacket(header, frames, 500)); ASSERT_TRUE(raw_ack_packet != nullptr); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> ack_packet( + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, header.packet_number, - *raw_ack_packet, buffer, kMaxPacketSize)); + *raw_ack_packet, buffer, kMaxPacketSize); + ASSERT_NE(0u, encrypted_length); // Now make sure we can turn our ack packet back into an ack frame. - ASSERT_TRUE(framer_.ProcessPacket(*ack_packet)); + ASSERT_TRUE(framer_.ProcessPacket( + QuicEncryptedPacket(buffer, encrypted_length, false))); ASSERT_EQ(1u, visitor_.ack_frames_.size()); QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0]; EXPECT_TRUE(processed_ack_frame.is_truncated); @@ -4165,12 +4169,14 @@ TEST_P(QuicFramerTest, CleanTruncation) { ASSERT_TRUE(raw_ack_packet != nullptr); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> ack_packet( + size_t encrypted_length = framer_.EncryptPayload(ENCRYPTION_NONE, header.packet_number, - *raw_ack_packet, buffer, kMaxPacketSize)); + *raw_ack_packet, buffer, kMaxPacketSize); + ASSERT_NE(0u, encrypted_length); // Now make sure we can turn our ack packet back into an ack frame. - ASSERT_TRUE(framer_.ProcessPacket(*ack_packet)); + ASSERT_TRUE(framer_.ProcessPacket( + QuicEncryptedPacket(buffer, encrypted_length, false))); // Test for clean truncation of the ack by comparing the length of the // original packets to the re-serialized packets. diff --git a/net/quic/quic_headers_stream.cc b/net/quic/quic_headers_stream.cc index 5b1f942..c47f14f 100644 --- a/net/quic/quic_headers_stream.cc +++ b/net/quic/quic_headers_stream.cc @@ -299,9 +299,7 @@ void QuicHeadersStream::OnControlFrameHeaderData(SpdyStreamId stream_id, DVLOG(1) << "stream " << stream_id << ": Net.QuicSession.HeadersHOLBlockedTime " << delta.ToMilliseconds(); - UMA_HISTOGRAM_TIMES( - "Net.QuicSession.HeadersHOLBlockedTime", - base::TimeDelta::FromMicroseconds(delta.ToMicroseconds())); + spdy_session_->OnHeadersHeadOfLineBlocking(delta); } prev_max_timestamp_ = std::max(prev_max_timestamp_, cur_max_timestamp_); cur_max_timestamp_ = QuicTime::Zero(); diff --git a/net/quic/quic_headers_stream_test.cc b/net/quic/quic_headers_stream_test.cc index 640ad21..b5ca189 100644 --- a/net/quic/quic_headers_stream_test.cc +++ b/net/quic/quic_headers_stream_test.cc @@ -4,7 +4,6 @@ #include "net/quic/quic_headers_stream.h" -#include "base/test/histogram_tester.h" #include "net/quic/quic_utils.h" #include "net/quic/spdy_utils.h" #include "net/quic/test_tools/quic_connection_peer.h" @@ -15,8 +14,6 @@ #include "net/spdy/spdy_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" -using base::Bucket; -using base::HistogramTester; using base::StringPiece; using std::ostream; using std::string; @@ -229,7 +226,7 @@ class QuicHeadersStreamTest : public ::testing::TestWithParam<TestParams> { static const bool kFrameComplete = true; static const bool kHasPriority = true; - MockHelper helper_; + MockConnectionHelper helper_; StrictMock<MockConnection>* connection_; StrictMock<MockQuicSpdySession> session_; QuicHeadersStream* headers_stream_; @@ -306,17 +303,8 @@ TEST_P(QuicHeadersStreamTest, ProcessRawData) { } TEST_P(QuicHeadersStreamTest, EmptyHeaderHOLBlockedTime) { -// In the absence of surfacing HOL measurements externally, via UMA -// or tcp connection stats, log messages are the only indication. -// This test verifies that false positives are not generated when -// headers arrive in order. -#if 0 - ScopedMockLog log(kDoNotCaptureLogsYet); - EXPECT_CALL(log, Log(_, _, _)).Times(0); - log.StartCapturingLogs(); -#endif - InSequence seq; - HistogramTester histogram_tester; + EXPECT_CALL(session_, OnHeadersHeadOfLineBlocking(_)).Times(0); + testing::InSequence seq; bool fin = true; for (int stream_num = 0; stream_num < 10; stream_num++) { QuicStreamId stream_id = QuicClientDataStreamId(stream_num); @@ -343,18 +331,9 @@ TEST_P(QuicHeadersStreamTest, EmptyHeaderHOLBlockedTime) { connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); stream_frame_.offset += frame->size(); } - histogram_tester.ExpectTotalCount("Net.QuicSession.HeadersHOLBlockedTime", 0); } TEST_P(QuicHeadersStreamTest, NonEmptyHeaderHOLBlockedTime) { -// In the absence of surfacing HOL measurements externally, via UMA -// or tcp connection stats, log messages are the only indication. -// This test verifies that HOL blocking log messages are correct -// when there are out of order arrivals. -#if 0 - ScopedMockLog log(kDoNotCaptureLogsYet); -#endif - HistogramTester histogram_tester; QuicStreamId stream_id; bool fin = true; QuicStreamFrame stream_frames[10]; @@ -388,36 +367,16 @@ TEST_P(QuicHeadersStreamTest, NonEmptyHeaderHOLBlockedTime) { .Times(1); } } -#if 0 - // Actually writing the frames in reverse order will trigger log messages. - { - InSequence seq; - for (int stream_num = 0; stream_num < 10; ++stream_num) { - stream_id = QuicClientDataStreamId(stream_num); - if (stream_num > 0) { - string expected_msg = StringPrintf( - "stream %d: Net.QuicSession.HeadersHOLBlockedTime %d", - stream_id, stream_num); -#ifndef NDEBUG - EXPECT_CALL(log, Log(INFO, _, expected_msg)); -#endif - } - } - } - log.StartCapturingLogs(); -#endif + + // Actually writing the frames in reverse order will cause HOL blocking. + EXPECT_CALL(session_, OnHeadersHeadOfLineBlocking(_)).Times(9); + for (int stream_num = 9; stream_num >= 0; --stream_num) { DVLOG(1) << "OnStreamFrame for stream " << stream_num << " offset " << stream_frames[stream_num].offset; headers_stream_->OnStreamFrame(stream_frames[stream_num]); connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); } - // We expect 1 sample each for delays from 1 to 9 ms (8 and 9 go - // into the same bucket). - EXPECT_THAT( - histogram_tester.GetAllSamples("Net.QuicSession.HeadersHOLBlockedTime"), - ElementsAre(Bucket(1, 1), Bucket(2, 1), Bucket(3, 1), Bucket(4, 1), - Bucket(5, 1), Bucket(6, 1), Bucket(7, 1), Bucket(8, 2))); } TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) { diff --git a/net/quic/quic_packet_creator.cc b/net/quic/quic_packet_creator.cc index 3679d19..ccbf7e9 100644 --- a/net/quic/quic_packet_creator.cc +++ b/net/quic/quic_packet_creator.cc @@ -76,7 +76,6 @@ QuicPacketCreator::QuicPacketCreator(QuicConnectionId connection_id, random_bool_source_(new QuicRandomBoolSource(random_generator)), packet_number_(0), should_fec_protect_(false), - fec_group_number_(0), send_version_in_packet_(framer->perspective() == Perspective::IS_CLIENT), max_packet_length_(0), max_packets_per_fec_group_(kDefaultMaxPacketsPerFecGroup), @@ -130,6 +129,10 @@ void QuicPacketCreator::set_max_packets_per_fec_group( DCHECK_LT(0u, max_packets_per_fec_group_); } +QuicFecGroupNumber QuicPacketCreator::fec_group_number() { + return fec_group_ != nullptr ? fec_group_->FecGroupNumber() : 0; +} + bool QuicPacketCreator::ShouldSendFec(bool force_close) const { DCHECK(!HasPendingFrames()); return fec_group_.get() != nullptr && fec_group_->NumReceivedPackets() > 0 && @@ -175,7 +178,6 @@ void QuicPacketCreator::StopFecProtectingPackets() { } DCHECK(should_fec_protect_); should_fec_protect_ = false; - fec_group_number_ = 0; } bool QuicPacketCreator::IsFecProtected() const { @@ -205,8 +207,7 @@ InFecGroup QuicPacketCreator::MaybeUpdateLengthsAndStartFec() { } // Start a new FEC group since protection is on. Set the fec group number to // the packet number of the next packet. - fec_group_number_ = packet_number() + 1; - fec_group_.reset(new QuicFecGroup(fec_group_number_)); + fec_group_.reset(new QuicFecGroup(packet_number_ + 1)); return IN_FEC_GROUP; } @@ -479,9 +480,12 @@ SerializedPacket QuicPacketCreator::SerializePacket( DCHECK_LT(0u, encrypted_buffer_len); LOG_IF(DFATAL, queued_frames_.empty()) << "Attempt to serialize empty packet"; - DCHECK_GE(packet_number_ + 1, fec_group_number_); + if (fec_group_.get() != nullptr) { + DCHECK_GE(packet_number_ + 1, fec_group_->FecGroupNumber()); + } QuicPacketHeader header; - FillPacketHeader(should_fec_protect_ ? fec_group_number_ : 0, false, &header); + // FillPacketHeader increments packet_number_. + FillPacketHeader(fec_group_number(), false, &header); MaybeAddPadding(); @@ -500,17 +504,8 @@ SerializedPacket QuicPacketCreator::SerializePacket( ALIGNAS(64) char buffer[kMaxPacketSize]; // Use the packet_size_ instead of the buffer size to ensure smaller // packet sizes are properly used. - scoped_ptr<char[]> large_buffer; - size_t length = 0; - const bool use_stack_buffer = packet_size_ <= kMaxPacketSize; - if (use_stack_buffer) { - length = - framer_->BuildDataPacket(header, queued_frames_, buffer, packet_size_); - } else { - large_buffer.reset(new char[packet_size_]); - length = framer_->BuildDataPacket(header, queued_frames_, - large_buffer.get(), packet_size_); - } + size_t length = + framer_->BuildDataPacket(header, queued_frames_, buffer, packet_size_); if (length == 0) { LOG(DFATAL) << "Failed to serialize " << queued_frames_.size() << " frames."; @@ -519,7 +514,7 @@ SerializedPacket QuicPacketCreator::SerializePacket( // TODO(ianswett) Consider replacing QuicPacket with something else, // since it's only used to provide convenience methods to FEC and encryption. - QuicPacket packet(use_stack_buffer ? buffer : large_buffer.get(), length, + QuicPacket packet(buffer, length, /* owns_buffer */ false, header.public_header.connection_id_length, header.public_header.version_flag, @@ -533,10 +528,10 @@ SerializedPacket QuicPacketCreator::SerializePacket( } // Immediately encrypt the packet, to ensure we don't encrypt the same packet // packet number multiple times. - QuicEncryptedPacket* encrypted = + size_t encrypted_length = framer_->EncryptPayload(encryption_level_, packet_number_, packet, encrypted_buffer, encrypted_buffer_len); - if (encrypted == nullptr) { + if (encrypted_length == 0) { LOG(DFATAL) << "Failed to encrypt packet number " << packet_number_; return NoPacket(); } @@ -561,7 +556,8 @@ SerializedPacket QuicPacketCreator::SerializePacket( needs_padding_ = false; return SerializedPacket( header.packet_number, header.public_header.packet_number_length, - encrypted, QuicFramer::GetPacketEntropyHash(header), + encrypted_buffer, encrypted_length, /* owns_buffer*/ false, + QuicFramer::GetPacketEntropyHash(header), queued_retransmittable_frames_.release(), has_ack, has_stop_waiting); } @@ -575,26 +571,27 @@ SerializedPacket QuicPacketCreator::SerializeFec(char* buffer, } DCHECK_EQ(0u, queued_frames_.size()); QuicPacketHeader header; - FillPacketHeader(fec_group_number_, true, &header); + FillPacketHeader(fec_group_->FecGroupNumber(), true, &header); scoped_ptr<QuicPacket> packet( framer_->BuildFecPacket(header, fec_group_->PayloadParity())); fec_group_.reset(nullptr); packet_size_ = 0; LOG_IF(DFATAL, packet == nullptr) - << "Failed to serialize fec packet for group:" << fec_group_number_; + << "Failed to serialize fec packet for group:" + << fec_group_->FecGroupNumber(); DCHECK_GE(max_packet_length_, packet->length()); // Immediately encrypt the packet, to ensure we don't encrypt the same packet // packet number multiple times. - QuicEncryptedPacket* encrypted = framer_->EncryptPayload( + size_t encrypted_length = framer_->EncryptPayload( encryption_level_, packet_number_, *packet, buffer, buffer_len); - if (encrypted == nullptr) { + if (encrypted_length == 0) { LOG(DFATAL) << "Failed to encrypt packet number " << packet_number_; return NoPacket(); } SerializedPacket serialized( - header.packet_number, header.public_header.packet_number_length, - encrypted, QuicFramer::GetPacketEntropyHash(header), nullptr, false, - false); + header.packet_number, header.public_header.packet_number_length, buffer, + encrypted_length, /* owns_buffer */ false, + QuicFramer::GetPacketEntropyHash(header), nullptr, false, false); serialized.is_fec_packet = true; return serialized; } diff --git a/net/quic/quic_packet_creator.h b/net/quic/quic_packet_creator.h index da66785..31646ec 100644 --- a/net/quic/quic_packet_creator.h +++ b/net/quic/quic_packet_creator.h @@ -226,10 +226,9 @@ class NET_EXPORT_PRIVATE QuicPacketCreator { // To turn off FEC protection, use StopFecProtectingPackets(). void set_max_packets_per_fec_group(size_t max_packets_per_fec_group); - // Returns the currently open FEC group's number. If there isn't an open FEC - // group, returns the last closed FEC group number. Returns 0 when FEC is - // disabled or no FEC group has been created yet. - QuicFecGroupNumber fec_group_number() { return fec_group_number_; } + // Returns the currently open FEC group's number. Returns 0 when FEC is + // disabled or no FEC group is open. + QuicFecGroupNumber fec_group_number(); private: friend class test::QuicPacketCreatorPeer; @@ -276,7 +275,6 @@ class NET_EXPORT_PRIVATE QuicPacketCreator { QuicPacketNumber packet_number_; // If true, any created packets will be FEC protected. bool should_fec_protect_; - QuicFecGroupNumber fec_group_number_; scoped_ptr<QuicFecGroup> fec_group_; // Controls whether protocol version should be included while serializing the // packet. diff --git a/net/quic/quic_packet_generator.cc b/net/quic/quic_packet_generator.cc index a5e707e..12ebcfb 100644 --- a/net/quic/quic_packet_generator.cc +++ b/net/quic/quic_packet_generator.cc @@ -288,28 +288,28 @@ void QuicPacketGenerator::MaybeStartFecProtection() { void QuicPacketGenerator::MaybeSendFecPacketAndCloseGroup(bool force, bool is_fec_timeout) { - if (!ShouldSendFecPacket(force)) { - return; + if (ShouldSendFecPacket(force)) { + // If we want to send FEC packet only when FEC alaram goes off and if it is + // not a FEC timeout then close the group and dont send FEC packet. + if (fec_send_policy_ == FEC_ALARM_TRIGGER && !is_fec_timeout) { + ResetFecGroup(); + } else { + // TODO(jri): SerializeFec can return a NULL packet, and this should cause + // an early return, with a call to delegate_->OnPacketGenerationError. + char buffer[kMaxPacketSize]; + SerializedPacket serialized_fec = + packet_creator_.SerializeFec(buffer, kMaxPacketSize); + DCHECK(serialized_fec.packet); + delegate_->OnSerializedPacket(serialized_fec); + } } - // If we want to send FEC packet only when FEC alaram goes off and if it is - // not a FEC timeout then close the group and dont send FEC packet. - if (fec_send_policy_ == FEC_ALARM_TRIGGER && !is_fec_timeout) { - ResetFecGroup(); - } else { - // TODO(jri): SerializeFec can return a NULL packet, and this should - // cause an early return, with a call to delegate_->OnPacketGenerationError. - char buffer[kMaxPacketSize]; - SerializedPacket serialized_fec = - packet_creator_.SerializeFec(buffer, kMaxPacketSize); - DCHECK(serialized_fec.packet); - delegate_->OnSerializedPacket(serialized_fec); - } // Turn FEC protection off if creator's protection is on and the creator // does not have an open FEC group. // Note: We only wait until the frames queued in the creator are flushed; // pending frames in the generator will not keep us from turning FEC off. - if (!should_fec_protect_ && !packet_creator_.IsFecGroupOpen()) { + if (!should_fec_protect_ && packet_creator_.IsFecProtected() && + !packet_creator_.IsFecGroupOpen()) { packet_creator_.StopFecProtectingPackets(); DCHECK(!packet_creator_.IsFecProtected()); } diff --git a/net/quic/quic_packet_generator_test.cc b/net/quic/quic_packet_generator_test.cc index 044dfa4..0dcf229 100644 --- a/net/quic/quic_packet_generator_test.cc +++ b/net/quic/quic_packet_generator_test.cc @@ -1217,7 +1217,35 @@ TEST_P(QuicPacketGeneratorTest, ResetFecGroupNoTimeout) { // FEC_ANY_TRIGGER. CheckPacketIsFec(8, 7); } - EXPECT_TRUE(creator_->IsFecProtected()); + EXPECT_FALSE(creator_->IsFecProtected()); + + // Do the another send (with MAY_FEC_PROTECT) on a different stream id, which + // should not produce an FEC packet because the last FEC group has been + // closed. + { + InSequence dummy; + EXPECT_CALL(delegate_, OnSerializedPacket(_)) + .WillOnce(Invoke(this, &QuicPacketGeneratorTest::SavePacket)); + EXPECT_CALL(delegate_, OnSerializedPacket(_)) + .WillOnce(Invoke(this, &QuicPacketGeneratorTest::SavePacket)); + EXPECT_CALL(delegate_, OnSerializedPacket(_)) + .WillOnce(Invoke(this, &QuicPacketGeneratorTest::SavePacket)); + } + consumed = generator_.ConsumeData(9, CreateData(data_len), 0, true, + MAY_FEC_PROTECT, nullptr); + EXPECT_EQ(data_len, consumed.bytes_consumed); + EXPECT_TRUE(consumed.fin_consumed); + EXPECT_FALSE(generator_.HasQueuedFrames()); + if (generator_.fec_send_policy() == FEC_ALARM_TRIGGER) { + CheckPacketHasSingleStreamFrame(6); + CheckPacketHasSingleStreamFrame(7); + CheckPacketHasSingleStreamFrame(8); + } else { + CheckPacketHasSingleStreamFrame(9); + CheckPacketHasSingleStreamFrame(10); + CheckPacketHasSingleStreamFrame(11); + } + EXPECT_FALSE(creator_->IsFecProtected()); } // 1. Create and send one packet with MUST_FEC_PROTECT. diff --git a/net/quic/quic_protocol.cc b/net/quic/quic_protocol.cc index c241f4e..762523a 100644 --- a/net/quic/quic_protocol.cc +++ b/net/quic/quic_protocol.cc @@ -142,6 +142,10 @@ QuicTag QuicVersionToQuicTag(const QuicVersion version) { return MakeQuicTag('Q', '0', '2', '7'); case QUIC_VERSION_28: return MakeQuicTag('Q', '0', '2', '8'); + case QUIC_VERSION_29: + return MakeQuicTag('Q', '0', '2', '9'); + case QUIC_VERSION_30: + return MakeQuicTag('Q', '0', '3', '0'); default: // This shold be an ERROR because we should never attempt to convert an // invalid QuicVersion to be written to the wire. @@ -172,6 +176,8 @@ string QuicVersionToString(const QuicVersion version) { RETURN_STRING_LITERAL(QUIC_VERSION_26); RETURN_STRING_LITERAL(QUIC_VERSION_27); RETURN_STRING_LITERAL(QUIC_VERSION_28); + RETURN_STRING_LITERAL(QUIC_VERSION_29); + RETURN_STRING_LITERAL(QUIC_VERSION_30); default: return "QUIC_VERSION_UNSUPPORTED"; } @@ -235,7 +241,8 @@ QuicAckFrame::QuicAckFrame() : entropy_hash(0), is_truncated(false), largest_observed(0), - delta_time_largest_observed(QuicTime::Delta::Infinite()) {} + delta_time_largest_observed(QuicTime::Delta::Infinite()), + latest_revived_packet(0) {} QuicAckFrame::~QuicAckFrame() {} @@ -468,13 +475,9 @@ ostream& operator<<(ostream& os, const QuicAckFrame& ack_frame) { << " delta_time_largest_observed: " << ack_frame.delta_time_largest_observed.ToMicroseconds() << " missing_packets: [ " << ack_frame.missing_packets - << " ] is_truncated: " << ack_frame.is_truncated; - os << " revived_packets: [ "; - for (PacketNumberSet::const_iterator it = ack_frame.revived_packets.begin(); - it != ack_frame.revived_packets.end(); ++it) { - os << *it << " "; - } - os << " ] received_packets: [ "; + << " ] is_truncated: " << ack_frame.is_truncated + << " revived_packet: " << ack_frame.latest_revived_packet + << " received_packets: [ "; for (const std::pair<QuicPacketNumber, QuicTime>& p : ack_frame.received_packet_times) { os << p.first << " at " << p.second.ToDebuggingValue() << " "; @@ -775,6 +778,26 @@ SerializedPacket::SerializedPacket( has_ack(has_ack), has_stop_waiting(has_stop_waiting) {} +SerializedPacket::SerializedPacket( + QuicPacketNumber packet_number, + QuicPacketNumberLength packet_number_length, + char* encrypted_buffer, + size_t encrypted_length, + bool owns_buffer, + QuicPacketEntropyHash entropy_hash, + RetransmittableFrames* retransmittable_frames, + bool has_ack, + bool has_stop_waiting) + : SerializedPacket(packet_number, + packet_number_length, + new QuicEncryptedPacket(encrypted_buffer, + encrypted_length, + owns_buffer), + entropy_hash, + retransmittable_frames, + has_ack, + has_stop_waiting) {} + SerializedPacket::~SerializedPacket() {} QuicEncryptedPacket* QuicEncryptedPacket::Clone() const { diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h index aa008c4..1286b9a 100644 --- a/net/quic/quic_protocol.h +++ b/net/quic/quic_protocol.h @@ -346,6 +346,8 @@ enum QuicVersion { QUIC_VERSION_26 = 26, // In CHLO, send XLCT tag containing hash of leaf cert QUIC_VERSION_27 = 27, // Sends a nonce in the SHLO. QUIC_VERSION_28 = 28, // Receiver can refuse to create a requested stream. + QUIC_VERSION_29 = 29, // Server and client honor QUIC_STREAM_NO_ERROR. + QUIC_VERSION_30 = 30, // Add server side support of cert transparency. }; // This vector contains QUIC versions which we currently support. @@ -356,7 +358,8 @@ enum QuicVersion { // IMPORTANT: if you are adding to this list, follow the instructions at // http://sites/quic/adding-and-removing-versions static const QuicVersion kSupportedQuicVersions[] = { - QUIC_VERSION_28, QUIC_VERSION_27, QUIC_VERSION_26, QUIC_VERSION_25}; + QUIC_VERSION_30, QUIC_VERSION_29, QUIC_VERSION_28, + QUIC_VERSION_27, QUIC_VERSION_26, QUIC_VERSION_25}; typedef std::vector<QuicVersion> QuicVersionVector; @@ -418,6 +421,8 @@ GetStartOfEncryptedData(QuicConnectionIdLength connection_id_length, QuicPacketNumberLength packet_number_length); enum QuicRstStreamErrorCode { + // Complete response has been sent, sending a RST to ask the other endpoint + // to stop sending request data without discarding the response. QUIC_STREAM_NO_ERROR = 0, // There was some error which halted stream processing. @@ -490,8 +495,6 @@ enum QuicErrorCode { // ACK frame data is malformed. QUIC_INVALID_ACK_DATA = 9, - // deprecated: QUIC_INVALID_CONGESTION_FEEDBACK_DATA = 47, - // Version negotiation packet is malformed. QUIC_INVALID_VERSION_NEGOTIATION_PACKET = 10, // Public RST packet is malformed. @@ -502,8 +505,6 @@ enum QuicErrorCode { QUIC_ENCRYPTION_FAILURE = 13, // The packet exceeded kMaxPacketSize. QUIC_PACKET_TOO_LARGE = 14, - // Data was sent for a stream which did not exist. - QUIC_PACKET_FOR_NONEXISTENT_STREAM = 15, // The peer is going away. May be a client or server. QUIC_PEER_GOING_AWAY = 16, // A stream ID was invalid. @@ -521,8 +522,6 @@ enum QuicErrorCode { // Invalid protocol version. QUIC_INVALID_VERSION = 20, - // deprecated: QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED = 21 - // The Header ID for a stream was too far from the previous. QUIC_INVALID_HEADER_ID = 22, // Negotiable parameter received during handshake had invalid value. @@ -868,9 +867,9 @@ struct NET_EXPORT_PRIVATE QuicAckFrame { // The set of packets which we're expecting and have not received. PacketNumberQueue missing_packets; - // Packets which have been revived via FEC. - // All of these must also be in missing_packets. - PacketNumberSet revived_packets; + // Packet most recently revived via FEC, 0 if no packet was revived by FEC. + // If non-zero, must be present in missing_packets. + QuicPacketNumber latest_revived_packet; }; // True if the packet number is greater than largest_observed or is listed @@ -988,6 +987,23 @@ enum EncryptionLevel { NUM_ENCRYPTION_LEVELS, }; +enum PeerAddressChangeType { + NO_CHANGE, + // Peer address changes which are considered to be cause by NATs. Currently, + // IPv4 address change with /24 does not change is considered to be cause by + // NATs. + NAT_PORT_REBINDING, + IPV4_SUBNET_REBINDING, + // IPv6 related address changes. + IPV4_TO_IPV6, + IPV6_TO_IPV4, + IPV6_TO_IPV6, + // This type is used when we always allow peer address changes. + UNKNOWN, + // All other peer address change types. + UNSPECIFIED, +}; + struct NET_EXPORT_PRIVATE QuicFrame { QuicFrame(); explicit QuicFrame(QuicPaddingFrame padding_frame); @@ -1154,6 +1170,15 @@ struct NET_EXPORT_PRIVATE SerializedPacket { RetransmittableFrames* retransmittable_frames, bool has_ack, bool has_stop_waiting); + SerializedPacket(QuicPacketNumber packet_number, + QuicPacketNumberLength packet_number_length, + char* encrypted_buffer, + size_t encrypted_length, + bool owns_buffer, + QuicPacketEntropyHash entropy_hash, + RetransmittableFrames* retransmittable_frames, + bool has_ack, + bool has_stop_waiting); ~SerializedPacket(); QuicEncryptedPacket* packet; diff --git a/net/quic/quic_received_packet_manager.cc b/net/quic/quic_received_packet_manager.cc index f49391c..98cc86b 100644 --- a/net/quic/quic_received_packet_manager.cc +++ b/net/quic/quic_received_packet_manager.cc @@ -179,13 +179,15 @@ void QuicReceivedPacketManager::RecordPacketReceived( ack_frame_.received_packet_times.push_back( std::make_pair(packet_number, receipt_time)); - ack_frame_.revived_packets.erase(packet_number); + if (ack_frame_.latest_revived_packet == packet_number) { + ack_frame_.latest_revived_packet = 0; + } } void QuicReceivedPacketManager::RecordPacketRevived( QuicPacketNumber packet_number) { LOG_IF(DFATAL, !IsAwaitingPacket(packet_number)); - ack_frame_.revived_packets.insert(packet_number); + ack_frame_.latest_revived_packet = packet_number; } bool QuicReceivedPacketManager::IsMissing(QuicPacketNumber packet_number) { @@ -253,9 +255,9 @@ QuicPacketEntropyHash QuicReceivedPacketManager::EntropyHash( bool QuicReceivedPacketManager::DontWaitForPacketsBefore( QuicPacketNumber least_unacked) { - ack_frame_.revived_packets.erase( - ack_frame_.revived_packets.begin(), - ack_frame_.revived_packets.lower_bound(least_unacked)); + if (ack_frame_.latest_revived_packet < least_unacked) { + ack_frame_.latest_revived_packet = 0; + } return ack_frame_.missing_packets.RemoveUpTo(least_unacked); } diff --git a/net/quic/quic_received_packet_manager_test.cc b/net/quic/quic_received_packet_manager_test.cc index 31ff8a0..b5a3542 100644 --- a/net/quic/quic_received_packet_manager_test.cc +++ b/net/quic/quic_received_packet_manager_test.cc @@ -355,8 +355,7 @@ TEST_F(QuicReceivedPacketManagerTest, RevivedPacket) { received_manager_.UpdateReceivedPacketInfo(&ack, QuicTime::Zero()); EXPECT_EQ(1u, ack.missing_packets.NumPacketsSlow()); EXPECT_EQ(2u, ack.missing_packets.Min()); - EXPECT_EQ(1u, ack.revived_packets.size()); - EXPECT_EQ(2u, *ack.revived_packets.begin()); + EXPECT_EQ(2u, ack.latest_revived_packet); } TEST_F(QuicReceivedPacketManagerTest, PacketRevivedThenReceived) { @@ -368,7 +367,7 @@ TEST_F(QuicReceivedPacketManagerTest, PacketRevivedThenReceived) { QuicAckFrame ack; received_manager_.UpdateReceivedPacketInfo(&ack, QuicTime::Zero()); EXPECT_TRUE(ack.missing_packets.Empty()); - EXPECT_TRUE(ack.revived_packets.empty()); + EXPECT_EQ(0u, ack.latest_revived_packet); } diff --git a/net/quic/quic_reliable_client_stream_test.cc b/net/quic/quic_reliable_client_stream_test.cc index db5331f..9162ba9 100644 --- a/net/quic/quic_reliable_client_stream_test.cc +++ b/net/quic/quic_reliable_client_stream_test.cc @@ -98,7 +98,7 @@ class QuicReliableClientStreamTest QuicCryptoClientConfig crypto_config_; testing::StrictMock<MockDelegate> delegate_; - MockHelper helper_; + MockConnectionHelper helper_; MockQuicSpdySession session_; QuicReliableClientStream* stream_; SpdyHeaderBlock headers_; diff --git a/net/quic/quic_sent_packet_manager.cc b/net/quic/quic_sent_packet_manager.cc index 3d4fb9b..586d27a 100644 --- a/net/quic/quic_sent_packet_manager.cc +++ b/net/quic/quic_sent_packet_manager.cc @@ -326,10 +326,8 @@ void QuicSentPacketManager::HandleAckForSentPackets( } // Discard any retransmittable frames associated with revived packets. - for (PacketNumberSet::const_iterator revived_it = - ack_frame.revived_packets.begin(); - revived_it != ack_frame.revived_packets.end(); ++revived_it) { - MarkPacketRevived(*revived_it, delta_largest_observed); + if (ack_frame.latest_revived_packet != 0) { + MarkPacketRevived(ack_frame.latest_revived_packet, delta_largest_observed); } } diff --git a/net/quic/quic_sent_packet_manager_test.cc b/net/quic/quic_sent_packet_manager_test.cc index 6f12f78..deb076e 100644 --- a/net/quic/quic_sent_packet_manager_test.cc +++ b/net/quic/quic_sent_packet_manager_test.cc @@ -501,7 +501,7 @@ TEST_F(QuicSentPacketManagerTest, LoseButDontRetransmitRevivedPacket) { QuicAckFrame ack_frame; ack_frame.largest_observed = 3; ack_frame.missing_packets.Add(1); - ack_frame.revived_packets.insert(1); + ack_frame.latest_revived_packet = 1; QuicPacketNumber acked[] = {2, 3}; ExpectAcksAndLosses(true, acked, arraysize(acked), nullptr, 0); manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow()); @@ -547,7 +547,7 @@ TEST_F(QuicSentPacketManagerTest, MarkLostThenReviveAndDontRetransmitPacket) { // Ack 5th packet (FEC) and revive 1st packet. 1st packet should now be // removed from pending retransmissions map. ack_frame.largest_observed = 5; - ack_frame.revived_packets.insert(1); + ack_frame.latest_revived_packet = 1; ExpectAck(5); manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow()); diff --git a/net/quic/quic_session.cc b/net/quic/quic_session.cc index 3925924..2a325d6 100644 --- a/net/quic/quic_session.cc +++ b/net/quic/quic_session.cc @@ -157,7 +157,7 @@ void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) { return; } - ReliableQuicStream* stream = GetDynamicStream(frame.stream_id); + ReliableQuicStream* stream = GetOrCreateDynamicStream(frame.stream_id); if (!stream) { // The RST frame contains the final byte offset for the stream: we can now // update the connection level flow controller if needed. @@ -570,7 +570,7 @@ ReliableQuicStream* QuicSession::GetStream(const QuicStreamId stream_id) { if (it != static_stream_map_.end()) { return it->second; } - return GetDynamicStream(stream_id); + return GetOrCreateDynamicStream(stream_id); } void QuicSession::StreamDraining(QuicStreamId stream_id) { @@ -586,10 +586,11 @@ void QuicSession::CloseConnection(QuicErrorCode error) { } } -ReliableQuicStream* QuicSession::GetDynamicStream( +ReliableQuicStream* QuicSession::GetOrCreateDynamicStream( const QuicStreamId stream_id) { - if (static_stream_map_.find(stream_id) != static_stream_map_.end()) { - DLOG(FATAL) << "Attempt to call GetDynamicStream for a static stream"; + if (ContainsKey(static_stream_map_, stream_id)) { + DLOG(FATAL) + << "Attempt to call GetOrCreateDynamicStream for a static stream"; return nullptr; } @@ -603,31 +604,14 @@ ReliableQuicStream* QuicSession::GetDynamicStream( } if (stream_id % 2 == next_outgoing_stream_id_ % 2) { - // We've received a frame for a locally-created stream that is not - // currently active. This is an error. - CloseConnection(QUIC_PACKET_FOR_NONEXISTENT_STREAM); + // Received a frame for a locally-created stream that is not currently + // active. This is an error. + CloseConnection(QUIC_INVALID_STREAM_ID); return nullptr; } - return GetIncomingDynamicStream(stream_id); -} - -ReliableQuicStream* QuicSession::GetIncomingDynamicStream( - QuicStreamId stream_id) { - if (IsClosedStream(stream_id)) { - return nullptr; - } available_streams_.erase(stream_id); - // Legitimate streams created by the peer are alternately-numbered. - if (FLAGS_allow_many_available_streams && - stream_id % 2 != largest_peer_created_stream_id_ % 2) { - // Close the connection. - DVLOG(1) << "Invalid incoming stream_id " << stream_id; - connection()->SendConnectionClose(QUIC_INVALID_STREAM_ID); - return nullptr; - } - if (stream_id > largest_peer_created_stream_id_) { if (FLAGS_allow_many_available_streams) { // Check if the new number of available streams would cause the number of diff --git a/net/quic/quic_session.h b/net/quic/quic_session.h index 8c3f079..cbdfb05 100644 --- a/net/quic/quic_session.h +++ b/net/quic/quic_session.h @@ -53,6 +53,7 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface { HANDSHAKE_CONFIRMED, }; + // Takes ownership of |connection|. QuicSession(QuicConnection* connection, const QuicConfig& config); ~QuicSession() override; @@ -225,9 +226,11 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface { // underlying counter. QuicStreamId GetNextOutgoingStreamId(); - ReliableQuicStream* GetIncomingDynamicStream(QuicStreamId stream_id); - - ReliableQuicStream* GetDynamicStream(const QuicStreamId stream_id); + // Returns existing stream with id = |stream_id|. If no such stream exists, + // and |stream_id| is a peer-created id, then a new stream is created and + // returned. However if |stream_id| is a locally-created id and no such stream + // exists, the connection is closed. + ReliableQuicStream* GetOrCreateDynamicStream(QuicStreamId stream_id); // This is called after every call other than OnConnectionClose from the // QuicConnectionVisitor to allow post-processing once the work has been done. diff --git a/net/quic/quic_session_test.cc b/net/quic/quic_session_test.cc index 244743e..6d29f23 100644 --- a/net/quic/quic_session_test.cc +++ b/net/quic/quic_session_test.cc @@ -147,8 +147,8 @@ class TestSession : public QuicSpdySession { return QuicSession::IsClosedStream(id); } - ReliableQuicStream* GetIncomingDynamicStream(QuicStreamId stream_id) { - return QuicSpdySession::GetIncomingDynamicStream(stream_id); + ReliableQuicStream* GetOrCreateDynamicStream(QuicStreamId stream_id) { + return QuicSpdySession::GetOrCreateDynamicStream(stream_id); } QuicConsumedData WritevData( @@ -245,7 +245,7 @@ class QuicSessionTestBase : public ::testing::TestWithParam<QuicVersion> { QuicVersion version() const { return connection_->version(); } - MockHelper helper_; + MockConnectionHelper helper_; StrictMock<MockConnection>* connection_; TestSession session_; set<QuicStreamId> closed_streams_; @@ -280,12 +280,12 @@ TEST_P(QuicSessionTestServer, IsClosedStreamDefault) { } TEST_P(QuicSessionTestServer, AvailableStreams) { - ASSERT_TRUE(session_.GetIncomingDynamicStream(9) != nullptr); + ASSERT_TRUE(session_.GetOrCreateDynamicStream(9) != nullptr); // Both 5 and 7 should be available. EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(&session_, 5)); EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(&session_, 7)); - ASSERT_TRUE(session_.GetIncomingDynamicStream(7) != nullptr); - ASSERT_TRUE(session_.GetIncomingDynamicStream(5) != nullptr); + ASSERT_TRUE(session_.GetOrCreateDynamicStream(7) != nullptr); + ASSERT_TRUE(session_.GetOrCreateDynamicStream(5) != nullptr); } TEST_P(QuicSessionTestServer, IsClosedStreamLocallyCreated) { @@ -304,8 +304,8 @@ TEST_P(QuicSessionTestServer, IsClosedStreamLocallyCreated) { TEST_P(QuicSessionTestServer, IsClosedStreamPeerCreated) { QuicStreamId stream_id1 = kClientDataStreamId1; QuicStreamId stream_id2 = kClientDataStreamId2; - session_.GetIncomingDynamicStream(stream_id1); - session_.GetIncomingDynamicStream(stream_id2); + session_.GetOrCreateDynamicStream(stream_id1); + session_.GetOrCreateDynamicStream(stream_id2); CheckClosedStreams(); CloseStream(stream_id1); @@ -313,7 +313,7 @@ TEST_P(QuicSessionTestServer, IsClosedStreamPeerCreated) { CloseStream(stream_id2); // Create a stream, and make another available. ReliableQuicStream* stream3 = - session_.GetIncomingDynamicStream(stream_id2 + 4); + session_.GetOrCreateDynamicStream(stream_id2 + 4); CheckClosedStreams(); // Close one, but make sure the other is still not closed CloseStream(stream3->id()); @@ -322,17 +322,17 @@ TEST_P(QuicSessionTestServer, IsClosedStreamPeerCreated) { TEST_P(QuicSessionTestServer, MaximumAvailableOpenedStreams) { QuicStreamId stream_id = kClientDataStreamId1; - session_.GetIncomingDynamicStream(stream_id); + session_.GetOrCreateDynamicStream(stream_id); EXPECT_CALL(*connection_, SendConnectionClose(_)).Times(0); EXPECT_NE(nullptr, - session_.GetIncomingDynamicStream( + session_.GetOrCreateDynamicStream( stream_id + 2 * (session_.get_max_open_streams() - 1))); } TEST_P(QuicSessionTestServer, TooManyAvailableStreams) { QuicStreamId stream_id1 = kClientDataStreamId1; QuicStreamId stream_id2; - EXPECT_NE(nullptr, session_.GetIncomingDynamicStream(stream_id1)); + EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id1)); // A stream ID which is too large to create. if (FLAGS_allow_many_available_streams) { stream_id2 = stream_id1 + 2 * session_.get_max_available_streams() + 4; @@ -342,7 +342,7 @@ TEST_P(QuicSessionTestServer, TooManyAvailableStreams) { stream_id2 = stream_id1 + 2 * session_.get_max_open_streams(); EXPECT_CALL(*connection_, SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS)); } - EXPECT_EQ(nullptr, session_.GetIncomingDynamicStream(stream_id2)); + EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(stream_id2)); } TEST_P(QuicSessionTestServer, ManyAvailableStreams) { @@ -351,10 +351,10 @@ TEST_P(QuicSessionTestServer, ManyAvailableStreams) { QuicSessionPeer::SetMaxOpenStreams(&session_, 200); QuicStreamId stream_id = kClientDataStreamId1; // Create one stream. - session_.GetIncomingDynamicStream(stream_id); + session_.GetOrCreateDynamicStream(stream_id); EXPECT_CALL(*connection_, SendConnectionClose(_)).Times(0); // Create the largest stream ID of a threatened total of 200 streams. - session_.GetIncomingDynamicStream(stream_id + 2 * (200 - 1)); + session_.GetOrCreateDynamicStream(stream_id + 2 * (200 - 1)); } TEST_P(QuicSessionTestServer, DebugDFatalIfMarkingClosedStreamWriteBlocked) { @@ -593,7 +593,7 @@ TEST_P(QuicSessionTestServer, SendGoAway) { EXPECT_CALL(*connection_, SendRstStream(kTestStreamId, QUIC_STREAM_PEER_GOING_AWAY, 0)) .Times(0); - EXPECT_TRUE(session_.GetIncomingDynamicStream(kTestStreamId)); + EXPECT_TRUE(session_.GetOrCreateDynamicStream(kTestStreamId)); } TEST_P(QuicSessionTestServer, IncreasedTimeoutAfterCryptoHandshake) { @@ -612,7 +612,8 @@ TEST_P(QuicSessionTestServer, RstStreamBeforeHeadersDecompressed) { EXPECT_EQ(1u, session_.GetNumOpenStreams()); EXPECT_CALL(*connection_, SendRstStream(kClientDataStreamId1, _, _)); - QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); + QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_ERROR_PROCESSING_STREAM, + 0); session_.OnRstStream(rst1); EXPECT_EQ(0u, session_.GetNumOpenStreams()); // Connection should remain alive. @@ -1081,12 +1082,12 @@ INSTANTIATE_TEST_CASE_P(Tests, ::testing::ValuesIn(QuicSupportedVersions())); TEST_P(QuicSessionTestClient, AvailableStreamsClient) { - ASSERT_TRUE(session_.GetIncomingDynamicStream(6) != nullptr); + ASSERT_TRUE(session_.GetOrCreateDynamicStream(6) != nullptr); // Both 2 and 4 should be available. EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(&session_, 2)); EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(&session_, 4)); - ASSERT_TRUE(session_.GetIncomingDynamicStream(2) != nullptr); - ASSERT_TRUE(session_.GetIncomingDynamicStream(4) != nullptr); + ASSERT_TRUE(session_.GetOrCreateDynamicStream(2) != nullptr); + ASSERT_TRUE(session_.GetOrCreateDynamicStream(4) != nullptr); // And 5 should be not available. EXPECT_FALSE(QuicSessionPeer::IsStreamAvailable(&session_, 5)); } diff --git a/net/quic/quic_spdy_session.cc b/net/quic/quic_spdy_session.cc index 459a37e..ca0e67f 100644 --- a/net/quic/quic_spdy_session.cc +++ b/net/quic/quic_spdy_session.cc @@ -72,9 +72,13 @@ size_t QuicSpdySession::WriteHeaders( ack_notifier_delegate); } +void QuicSpdySession::OnHeadersHeadOfLineBlocking(QuicTime::Delta delta) { + // Implemented in Chromium for stats tracking. +} + QuicSpdyStream* QuicSpdySession::GetSpdyDataStream( const QuicStreamId stream_id) { - return static_cast<QuicSpdyStream*>(GetDynamicStream(stream_id)); + return static_cast<QuicSpdyStream*>(GetOrCreateDynamicStream(stream_id)); } } // namespace net diff --git a/net/quic/quic_spdy_session.h b/net/quic/quic_spdy_session.h index 77ebe2e..6f7b4a0 100644 --- a/net/quic/quic_spdy_session.h +++ b/net/quic/quic_spdy_session.h @@ -51,6 +51,10 @@ class NET_EXPORT_PRIVATE QuicSpdySession : public QuicSession { QuicHeadersStream* headers_stream() { return headers_stream_.get(); } + // Called when Head of Line Blocking happens in the headers stream. + // |delta| indicates how long that piece of data has been blocked. + virtual void OnHeadersHeadOfLineBlocking(QuicTime::Delta delta); + protected: // Override CreateIncomingDynamicStream() and CreateOutgoingDynamicStream() // with QuicSpdyStream return type to make sure that all data streams are diff --git a/net/quic/quic_spdy_stream.cc b/net/quic/quic_spdy_stream.cc index 374405bc..746e96a 100644 --- a/net/quic/quic_spdy_stream.cc +++ b/net/quic/quic_spdy_stream.cc @@ -113,6 +113,19 @@ void QuicSpdyStream::OnStreamHeadersComplete(bool fin, size_t frame_len) { } } +void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) { + if (frame.error_code != QUIC_STREAM_NO_ERROR || + version() <= QUIC_VERSION_28) { + ReliableQuicStream::OnStreamReset(frame); + return; + } + DVLOG(1) << "Received QUIC_STREAM_NO_ERROR, not discarding response"; + set_rst_received(true); + MaybeIncreaseHighestReceivedOffset(frame.byte_offset); + set_stream_error(frame.error_code); + CloseWriteSide(); +} + void QuicSpdyStream::OnClose() { ReliableQuicStream::OnClose(); diff --git a/net/quic/quic_spdy_stream.h b/net/quic/quic_spdy_stream.h index 781b005..48e74b1 100644 --- a/net/quic/quic_spdy_stream.h +++ b/net/quic/quic_spdy_stream.h @@ -78,6 +78,10 @@ class NET_EXPORT_PRIVATE QuicSpdyStream : public ReliableQuicStream { // should be closed; no more data will be sent by the peer. virtual void OnStreamHeadersComplete(bool fin, size_t frame_len); + // Override the base class to not discard response when receiving + // QUIC_STREAM_NO_ERROR on QUIC_VERSION_29 and later versions. + void OnStreamReset(const QuicRstStreamFrame& frame) override; + // Writes the headers contained in |header_block| to the dedicated // headers stream. virtual size_t WriteHeaders(const SpdyHeaderBlock& header_block, diff --git a/net/quic/quic_spdy_stream_test.cc b/net/quic/quic_spdy_stream_test.cc index da94274..75c5238 100644 --- a/net/quic/quic_spdy_stream_test.cc +++ b/net/quic/quic_spdy_stream_test.cc @@ -58,7 +58,7 @@ class TestStream : public QuicSpdyStream { string data_; }; -class QuicSpdyStreamTest : public ::testing::Test { +class QuicSpdyStreamTest : public ::testing::TestWithParam<QuicVersion> { public: QuicSpdyStreamTest() { headers_[":host"] = "www.google.com"; @@ -92,7 +92,7 @@ class QuicSpdyStreamTest : public ::testing::Test { void Initialize(bool stream_should_process_data) { connection_ = new testing::StrictMock<MockConnection>( - &helper_, Perspective::IS_SERVER); + &helper_, Perspective::IS_SERVER, SupportedVersions(GetParam())); session_.reset(new testing::StrictMock<MockQuicSpdySession>(connection_)); stream_.reset(new TestStream(kClientDataStreamId1, session_.get(), stream_should_process_data)); @@ -103,7 +103,7 @@ class QuicSpdyStreamTest : public ::testing::Test { } protected: - MockHelper helper_; + MockConnectionHelper helper_; MockConnection* connection_; scoped_ptr<MockQuicSpdySession> session_; scoped_ptr<TestStream> stream_; @@ -112,7 +112,11 @@ class QuicSpdyStreamTest : public ::testing::Test { QuicWriteBlockedList* write_blocked_list_; }; -TEST_F(QuicSpdyStreamTest, ProcessHeaders) { +INSTANTIATE_TEST_CASE_P(Tests, + QuicSpdyStreamTest, + ::testing::ValuesIn(QuicSupportedVersions())); + +TEST_P(QuicSpdyStreamTest, ProcessHeaders) { Initialize(kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -127,7 +131,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeaders) { EXPECT_FALSE(stream_->IsDoneReading()); } -TEST_F(QuicSpdyStreamTest, ProcessHeadersWithFin) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersWithFin) { Initialize(kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -143,7 +147,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersWithFin) { EXPECT_TRUE(stream_->HasFinalReceivedByteOffset()); } -TEST_F(QuicSpdyStreamTest, MarkHeadersConsumed) { +TEST_P(QuicSpdyStreamTest, MarkHeadersConsumed) { Initialize(kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -161,7 +165,7 @@ TEST_F(QuicSpdyStreamTest, MarkHeadersConsumed) { EXPECT_EQ("", stream_->decompressed_headers()); } -TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBody) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBody) { Initialize(kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -179,7 +183,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBody) { EXPECT_EQ(body, stream_->data()); } -TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyFragments) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyFragments) { string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); string body = "this is the body"; @@ -207,7 +211,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyFragments) { } } -TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyFragmentsSplit) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyFragmentsSplit) { string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); string body = "this is the body"; @@ -238,7 +242,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyFragmentsSplit) { } } -TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyReadv) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyReadv) { Initialize(!kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -261,7 +265,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyReadv) { EXPECT_EQ(body, string(buffer, bytes_read)); } -TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyMarkConsumed) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyMarkConsumed) { Initialize(!kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -283,7 +287,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyMarkConsumed) { EXPECT_EQ(body.length(), stream_->flow_controller()->bytes_consumed()); } -TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyIncrementalReadv) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyIncrementalReadv) { Initialize(!kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -306,7 +310,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersAndBodyIncrementalReadv) { } } -TEST_F(QuicSpdyStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) { +TEST_P(QuicSpdyStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) { Initialize(!kShouldProcessData); string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -333,7 +337,7 @@ TEST_F(QuicSpdyStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) { } } -TEST_F(QuicSpdyStreamTest, StreamFlowControlBlocked) { +TEST_P(QuicSpdyStreamTest, StreamFlowControlBlocked) { // Tests that we send a BLOCKED frame to the peer when we attempt to write, // but are flow control blocked. Initialize(kShouldProcessData); @@ -364,7 +368,7 @@ TEST_F(QuicSpdyStreamTest, StreamFlowControlBlocked) { EXPECT_EQ(kOverflow, ReliableQuicStreamPeer::SizeOfQueuedData(stream_.get())); } -TEST_F(QuicSpdyStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) { +TEST_P(QuicSpdyStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) { // The flow control receive window decreases whenever we add new bytes to the // sequencer, whether they are consumed immediately or buffered. However we // only send WINDOW_UPDATE frames based on increasing number of bytes @@ -408,7 +412,7 @@ TEST_F(QuicSpdyStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) { QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller())); } -TEST_F(QuicSpdyStreamTest, StreamFlowControlWindowUpdate) { +TEST_P(QuicSpdyStreamTest, StreamFlowControlWindowUpdate) { // Tests that on receipt of data, the stream updates its receive window offset // appropriately, and sends WINDOW_UPDATE frames when its receive window drops // too low. @@ -452,7 +456,7 @@ TEST_F(QuicSpdyStreamTest, StreamFlowControlWindowUpdate) { stream_->flow_controller())); } -TEST_F(QuicSpdyStreamTest, ConnectionFlowControlWindowUpdate) { +TEST_P(QuicSpdyStreamTest, ConnectionFlowControlWindowUpdate) { // Tests that on receipt of data, the connection updates its receive window // offset appropriately, and sends WINDOW_UPDATE frames when its receive // window drops too low. @@ -505,7 +509,7 @@ TEST_F(QuicSpdyStreamTest, ConnectionFlowControlWindowUpdate) { stream_->OnStreamFrame(frame3); } -TEST_F(QuicSpdyStreamTest, StreamFlowControlViolation) { +TEST_P(QuicSpdyStreamTest, StreamFlowControlViolation) { // Tests that on if the peer sends too much data (i.e. violates the flow // control protocol), then we terminate the connection. @@ -531,7 +535,22 @@ TEST_F(QuicSpdyStreamTest, StreamFlowControlViolation) { stream_->OnStreamFrame(frame); } -TEST_F(QuicSpdyStreamTest, ConnectionFlowControlViolation) { +TEST_P(QuicSpdyStreamTest, TestHandlingQuicRstStreamNoError) { + Initialize(kShouldProcessData); + string headers = SpdyUtils::SerializeUncompressedHeaders(headers_); + stream_->OnStreamHeaders(headers); + stream_->OnStreamHeadersComplete(false, headers.size()); + stream_->OnStreamReset( + QuicRstStreamFrame(stream_->id(), QUIC_STREAM_NO_ERROR, 0)); + EXPECT_TRUE(stream_->write_side_closed()); + if (GetParam() > QUIC_VERSION_28) { + EXPECT_FALSE(stream_->reading_stopped()); + } else { + EXPECT_TRUE(stream_->reading_stopped()); + } +} + +TEST_P(QuicSpdyStreamTest, ConnectionFlowControlViolation) { // Tests that on if the peer sends too much data (i.e. violates the flow // control protocol), at the connection level (rather than the stream level) // then we terminate the connection. @@ -563,7 +582,7 @@ TEST_F(QuicSpdyStreamTest, ConnectionFlowControlViolation) { stream_->OnStreamFrame(frame); } -TEST_F(QuicSpdyStreamTest, StreamFlowControlFinNotBlocked) { +TEST_P(QuicSpdyStreamTest, StreamFlowControlFinNotBlocked) { // An attempt to write a FIN with no data should not be flow control blocked, // even if the send window is 0. diff --git a/net/quic/quic_stream_sequencer_buffer_interface.h b/net/quic/quic_stream_sequencer_buffer_interface.h index e03ea79..22b4ed8 100644 --- a/net/quic/quic_stream_sequencer_buffer_interface.h +++ b/net/quic/quic_stream_sequencer_buffer_interface.h @@ -5,7 +5,6 @@ #ifndef NET_QUIC_QUIC_STREAM_SEQUENCER_BUFFER_INTERFACE_H_ #define NET_QUIC_QUIC_STREAM_SEQUENCER_BUFFER_INTERFACE_H_ -#include "net/quic/quic_frame_list.h" #include "net/quic/quic_protocol.h" using base::StringPiece; diff --git a/net/quic/quic_stream_sequencer_test.cc b/net/quic/quic_stream_sequencer_test.cc index 6c2910e..aaf1d26 100644 --- a/net/quic/quic_stream_sequencer_test.cc +++ b/net/quic/quic_stream_sequencer_test.cc @@ -147,7 +147,7 @@ class QuicStreamSequencerTest : public ::testing::Test { return QuicStreamSequencerPeer::GetNumBufferedBytes(sequencer_.get()); } - MockHelper helper_; + MockConnectionHelper helper_; MockConnection* connection_; MockClock clock_; MockQuicSpdySession session_; diff --git a/net/quic/quic_time.cc b/net/quic/quic_time.cc index cd59953..8321718 100644 --- a/net/quic/quic_time.cc +++ b/net/quic/quic_time.cc @@ -11,51 +11,55 @@ namespace net { uint64 QuicWallTime::ToUNIXSeconds() const { - return seconds_; + return microseconds_ / 1000000; +} + +uint64 QuicWallTime::ToUNIXMicroseconds() const { + return microseconds_; } bool QuicWallTime::IsAfter(QuicWallTime other) const { - return seconds_ > other.seconds_; + return microseconds_ > other.microseconds_; } bool QuicWallTime::IsBefore(QuicWallTime other) const { - return seconds_ < other.seconds_; + return microseconds_ < other.microseconds_; } bool QuicWallTime::IsZero() const { - return seconds_ == 0; + return microseconds_ == 0; } QuicTime::Delta QuicWallTime::AbsoluteDifference(QuicWallTime other) const { uint64 d; - if (seconds_ > other.seconds_) { - d = seconds_ - other.seconds_; + if (microseconds_ > other.microseconds_) { + d = microseconds_ - other.microseconds_; } else { - d = other.seconds_ - seconds_; + d = other.microseconds_ - microseconds_; } if (d > static_cast<uint64>(kint64max)) { d = kint64max; } - return QuicTime::Delta::FromSeconds(d); + return QuicTime::Delta::FromMicroseconds(d); } QuicWallTime QuicWallTime::Add(QuicTime::Delta delta) const { - uint64 seconds = seconds_ + delta.ToSeconds(); - if (seconds < seconds_) { - seconds = kuint64max; + uint64 microseconds = microseconds_ + delta.ToMicroseconds(); + if (microseconds < microseconds_) { + microseconds = kuint64max; } - return QuicWallTime(seconds); + return QuicWallTime(microseconds); } // TODO(ianswett) Test this. QuicWallTime QuicWallTime::Subtract(QuicTime::Delta delta) const { - uint64 seconds = seconds_ - delta.ToSeconds(); - if (seconds > seconds_) { - seconds = 0; + uint64 microseconds = microseconds_ - delta.ToMicroseconds(); + if (microseconds > microseconds_) { + microseconds = 0; } - return QuicWallTime(seconds); + return QuicWallTime(microseconds); } } // namespace net diff --git a/net/quic/quic_time.h b/net/quic/quic_time.h index 99b3afd5..5637837 100644 --- a/net/quic/quic_time.h +++ b/net/quic/quic_time.h @@ -159,24 +159,30 @@ class NET_EXPORT_PRIVATE QuicTime { int64 time_; }; -// A QuicWallTime represents an absolute time that is globally consistent. It -// provides, at most, one second granularity and, in practice, clock-skew means -// that you shouldn't even depend on that. +// A QuicWallTime represents an absolute time that is globally consistent. In +// practice, clock-skew means that comparing values from different machines +// requires some flexibility in interpretation. class NET_EXPORT_PRIVATE QuicWallTime { public: // FromUNIXSeconds constructs a QuicWallTime from a count of the seconds // since the UNIX epoch. static QUICTIME_CONSTEXPR QuicWallTime FromUNIXSeconds(uint64 seconds) { - return QuicWallTime(seconds); + return QuicWallTime(seconds * 1000000); + } + + static QUICTIME_CONSTEXPR QuicWallTime + FromUNIXMicroseconds(uint64 microseconds) { + return QuicWallTime(microseconds); } // Zero returns a QuicWallTime set to zero. IsZero will return true for this // value. static QUICTIME_CONSTEXPR QuicWallTime Zero() { return QuicWallTime(0); } - // ToUNIXSeconds converts a QuicWallTime into a count of seconds since the - // UNIX epoch. + // Returns the number of seconds since the UNIX epoch. uint64 ToUNIXSeconds() const; + // Returns the number of microseconds since the UNIX epoch. + uint64 ToUNIXMicroseconds() const; bool IsAfter(QuicWallTime other) const; bool IsBefore(QuicWallTime other) const; @@ -197,10 +203,10 @@ class NET_EXPORT_PRIVATE QuicWallTime { QuicWallTime Subtract(QuicTime::Delta delta) const WARN_UNUSED_RESULT; private: - explicit QUICTIME_CONSTEXPR QuicWallTime(uint64 seconds) - : seconds_(seconds) {} + explicit QUICTIME_CONSTEXPR QuicWallTime(uint64 microseconds) + : microseconds_(microseconds) {} - uint64 seconds_; + uint64 microseconds_; }; // Non-member relational operators for QuicTime::Delta. diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc index b336202..b821633 100644 --- a/net/quic/quic_utils.cc +++ b/net/quic/quic_utils.cc @@ -179,7 +179,6 @@ const char* QuicUtils::ErrorToString(QuicErrorCode error) { RETURN_STRING_LITERAL(QUIC_DECRYPTION_FAILURE); RETURN_STRING_LITERAL(QUIC_ENCRYPTION_FAILURE); RETURN_STRING_LITERAL(QUIC_PACKET_TOO_LARGE); - RETURN_STRING_LITERAL(QUIC_PACKET_FOR_NONEXISTENT_STREAM); RETURN_STRING_LITERAL(QUIC_PEER_GOING_AWAY); RETURN_STRING_LITERAL(QUIC_HANDSHAKE_FAILED); RETURN_STRING_LITERAL(QUIC_CRYPTO_TAGS_OUT_OF_ORDER); diff --git a/net/quic/reliable_quic_stream.h b/net/quic/reliable_quic_stream.h index 1c696af..a608c32 100644 --- a/net/quic/reliable_quic_stream.h +++ b/net/quic/reliable_quic_stream.h @@ -114,6 +114,9 @@ class NET_EXPORT_PRIVATE ReliableQuicStream { void set_fec_policy(FecPolicy fec_policy) { fec_policy_ = fec_policy; } FecPolicy fec_policy() const { return fec_policy_; } + void set_rst_received(bool rst_received) { rst_received_ = rst_received; } + void set_stream_error(QuicRstStreamErrorCode error) { stream_error_ = error; } + // Adjust the flow control window according to new offset in |frame|. virtual void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame); diff --git a/net/quic/reliable_quic_stream_test.cc b/net/quic/reliable_quic_stream_test.cc index 8fe2be6..46a3fff 100644 --- a/net/quic/reliable_quic_stream_test.cc +++ b/net/quic/reliable_quic_stream_test.cc @@ -141,7 +141,7 @@ class ReliableQuicStreamTest : public ::testing::TestWithParam<bool> { } protected: - MockHelper helper_; + MockConnectionHelper helper_; MockConnection* connection_; scoped_ptr<MockQuicSpdySession> session_; TestStream* stream_; diff --git a/net/quic/stream_sequencer_buffer.cc b/net/quic/stream_sequencer_buffer.cc new file mode 100644 index 0000000..9e723f9 --- /dev/null +++ b/net/quic/stream_sequencer_buffer.cc @@ -0,0 +1,453 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/quic/stream_sequencer_buffer.h" + +#include "base/basictypes.h" +#include "base/logging.h" + +using std::min; + +namespace net { + +StreamSequencerBuffer::Gap::Gap(QuicStreamOffset begin_offset, + QuicStreamOffset end_offset) + : begin_offset(begin_offset), end_offset(end_offset) {} + +StreamSequencerBuffer::FrameInfo::FrameInfo() + : length(1), timestamp(QuicTime::Zero()) {} + +StreamSequencerBuffer::FrameInfo::FrameInfo(size_t length, QuicTime timestamp) + : length(length), timestamp(timestamp) {} + +StreamSequencerBuffer::StreamSequencerBuffer(size_t max_capacity_bytes) + : max_buffer_capacity_bytes_(max_capacity_bytes), + blocks_count_( + ceil(static_cast<double>(max_capacity_bytes) / kBlockSizeBytes)), + total_bytes_read_(0), + blocks_(blocks_count_) { + Clear(); +} + +StreamSequencerBuffer::~StreamSequencerBuffer() { + Clear(); +} + +void StreamSequencerBuffer::Clear() { + for (size_t i = 0; i < blocks_count_; ++i) { + if (blocks_[i] != nullptr) { + RetireBlock(i); + } + } + num_bytes_buffered_ = 0; + // Reset gaps_ so that buffer is in a state as if all data before + // total_bytes_read_ has been consumed, and those after total_bytes_read_ + // has never arrived. + gaps_ = std::list<Gap>( + 1, Gap(total_bytes_read_, std::numeric_limits<QuicStreamOffset>::max())), + frame_arrival_time_map_.clear(); +} + +void StreamSequencerBuffer::RetireBlock(size_t idx) { + DCHECK(blocks_[idx] != nullptr); + delete blocks_[idx]; + blocks_[idx] = nullptr; + DVLOG(1) << "Retired block" << idx; +} + +QuicErrorCode StreamSequencerBuffer::OnStreamData( + QuicStreamOffset starting_offset, + base::StringPiece data, + QuicTime timestamp, + size_t* const bytes_buffered) { + *bytes_buffered = 0; + QuicStreamOffset offset = starting_offset; + size_t size = data.size(); + if (size == 0) { + LOG(DFATAL) << "Attempted to write 0 bytes of data."; + return QUIC_INVALID_STREAM_FRAME; + } + + // Find the first gap not ending before |offset|. This gap maybe the gap to + // fill if the arriving frame doesn't overlaps with previous ones. + std::list<Gap>::iterator current_gap = gaps_.begin(); + while (current_gap != gaps_.end() && current_gap->end_offset <= offset) { + ++current_gap; + } + + DCHECK(current_gap != gaps_.end()); + + // "duplication": might duplicate with data alread filled,but also might + // overlap across different base::StringPiece objects already written. + // In both cases, don't write the data, + // and allow the caller of this method to handle the result. + if (offset < current_gap->begin_offset && + offset + size <= current_gap->begin_offset) { + DVLOG(1) << "duplicated data at offset:" << offset << " len: " << size; + return QUIC_NO_ERROR; + } + if (offset < current_gap->begin_offset && + offset + size > current_gap->begin_offset) { + // Beginning of new data overlaps data before current gap. + return QUIC_INVALID_STREAM_DATA; + } + if (offset + size > current_gap->end_offset) { + // End of new data overlaps with data after current gap. + return QUIC_INVALID_STREAM_DATA; + } + + // Write beyond the current range this buffer is covering. + if (offset + size > total_bytes_read_ + max_buffer_capacity_bytes_) { + return QUIC_INTERNAL_ERROR; + } + + size_t total_written = 0; + size_t source_remaining = size; + const char* source = data.data(); + // Write data block by block. If corresponding block has not created yet, + // create it first. + // Stop when all data are written or reaches the logical end of the buffer. + while (source_remaining > 0) { + const size_t write_block_num = GetBlockIndex(offset); + const size_t write_block_offset = GetInBlockOffset(offset); + DCHECK_GT(blocks_count_, write_block_num); + + size_t block_capacity = GetBlockCapacity(write_block_num); + size_t bytes_avail = block_capacity - write_block_offset; + + // If this write meets the upper boundary of the buffer, + // reduce the available free bytes. + if (offset + bytes_avail > total_bytes_read_ + max_buffer_capacity_bytes_) { + bytes_avail = total_bytes_read_ + max_buffer_capacity_bytes_ - offset; + } + + if (blocks_[write_block_num] == nullptr) { + // TODO(danzh): Investigate if using a freelist would improve performance. + // Same as RetireBlock(). + blocks_[write_block_num] = new BufferBlock(); + } + + const size_t bytes_to_copy = min<size_t>(bytes_avail, source_remaining); + char* dest = blocks_[write_block_num]->buffer + write_block_offset; + DVLOG(1) << "write at offset: " << offset << " len: " << bytes_to_copy; + memcpy(dest, source, bytes_to_copy); + source += bytes_to_copy; + source_remaining -= bytes_to_copy; + offset += bytes_to_copy; + total_written += bytes_to_copy; + } + + DCHECK_GT(total_written, 0u); + *bytes_buffered = total_written; + UpdateGapList(current_gap, starting_offset, total_written); + + frame_arrival_time_map_.insert( + std::make_pair(starting_offset, FrameInfo(size, timestamp))); + num_bytes_buffered_ += total_written; + return QUIC_NO_ERROR; +} + +inline void StreamSequencerBuffer::UpdateGapList( + std::list<Gap>::iterator gap_with_new_data_written, + QuicStreamOffset start_offset, + size_t bytes_written) { + if (gap_with_new_data_written->begin_offset == start_offset && + gap_with_new_data_written->end_offset > start_offset + bytes_written) { + // New data has been written into the left part of the buffer. + gap_with_new_data_written->begin_offset = start_offset + bytes_written; + } else if (gap_with_new_data_written->begin_offset < start_offset && + gap_with_new_data_written->end_offset == + start_offset + bytes_written) { + // New data has been written into the right part of the buffer. + gap_with_new_data_written->end_offset = start_offset; + } else if (gap_with_new_data_written->begin_offset < start_offset && + gap_with_new_data_written->end_offset > + start_offset + bytes_written) { + // New data has been written into the middle of the buffer. + auto current = gap_with_new_data_written++; + size_t current_end = current->end_offset; + current->end_offset = start_offset; + gaps_.insert(gap_with_new_data_written, + Gap(start_offset + bytes_written, current_end)); + } else if (gap_with_new_data_written->begin_offset == start_offset && + gap_with_new_data_written->end_offset == + start_offset + bytes_written) { + // This gap has been filled with new data. So it's no longer a gap. + gaps_.erase(gap_with_new_data_written); + } +} + +size_t StreamSequencerBuffer::Readv(const iovec* dest_iov, size_t dest_count) { + size_t bytes_read = 0; + for (size_t i = 0; i < dest_count && ReadableBytes() > 0; ++i) { + char* dest = reinterpret_cast<char*>(dest_iov[i].iov_base); + size_t dest_remaining = dest_iov[i].iov_len; + while (dest_remaining > 0 && ReadableBytes() > 0) { + size_t block_idx = NextBlockToRead(); + size_t start_offset_in_block = ReadOffset(); + size_t block_capacity = GetBlockCapacity(block_idx); + size_t bytes_available_in_block = + min<size_t>(ReadableBytes(), block_capacity - start_offset_in_block); + size_t bytes_to_copy = + min<size_t>(bytes_available_in_block, dest_remaining); + DCHECK_GT(bytes_to_copy, 0u); + DCHECK_NE(static_cast<BufferBlock*>(nullptr), blocks_[block_idx]); + memcpy(dest, blocks_[block_idx]->buffer + start_offset_in_block, + bytes_to_copy); + dest += bytes_to_copy; + dest_remaining -= bytes_to_copy; + num_bytes_buffered_ -= bytes_to_copy; + total_bytes_read_ += bytes_to_copy; + bytes_read += bytes_to_copy; + + // Retire the block if all the data is read out + // and no other data is stored in this block. + if (bytes_to_copy == bytes_available_in_block) { + RetireBlockIfEmpty(block_idx); + } + } + } + + if (bytes_read > 0) { + UpdateFrameArrivalMap(total_bytes_read_); + } + return bytes_read; +} + +int StreamSequencerBuffer::GetReadableRegions(struct iovec* iov, + int iov_count) const { + DCHECK(iov != nullptr); + DCHECK_GT(iov_count, 0); + + if (ReadableBytes() == 0) { + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; + return 0; + } + + size_t start_block_idx = NextBlockToRead(); + QuicStreamOffset readable_offset_end = gaps_.front().begin_offset - 1; + DCHECK_GE(readable_offset_end + 1, total_bytes_read_); + size_t end_block_offset = GetInBlockOffset(readable_offset_end); + size_t end_block_idx = GetBlockIndex(readable_offset_end); + + // If readable region is within one block, deal with it seperately. + if (start_block_idx == end_block_idx && ReadOffset() <= end_block_offset) { + iov[0].iov_base = blocks_[start_block_idx]->buffer + ReadOffset(); + iov[0].iov_len = ReadableBytes(); + DVLOG(1) << "get only block" << start_block_idx; + return 1; + } + + // Get first block + iov[0].iov_base = blocks_[start_block_idx]->buffer + ReadOffset(); + iov[0].iov_len = GetBlockCapacity(start_block_idx) - ReadOffset(); + DVLOG(1) << "get first block" << start_block_idx << " with len " + << iov[0].iov_len; + DCHECK_GT(readable_offset_end + 1, total_bytes_read_ + iov[0].iov_len) + << "there should be more available data"; + + // Get readable regions of the rest blocks till either 2nd to last block + // before gap is met or |iov| is filled. For these blocks, one whole block is + // a region. + int iov_used = 1; + size_t block_idx = (start_block_idx + iov_used) % blocks_count_; + while (block_idx != end_block_idx && iov_used < iov_count) { + DCHECK_NE(static_cast<BufferBlock*>(nullptr), blocks_[block_idx]); + iov[iov_used].iov_base = blocks_[block_idx]->buffer; + iov[iov_used].iov_len = GetBlockCapacity(block_idx); + DVLOG(1) << "get block" << block_idx; + ++iov_used; + block_idx = (start_block_idx + iov_used) % blocks_count_; + } + + // Deal with last block if |iov| can hold more. + if (iov_used < iov_count) { + DCHECK_NE(static_cast<BufferBlock*>(nullptr), blocks_[block_idx]); + iov[iov_used].iov_base = blocks_[end_block_idx]->buffer; + iov[iov_used].iov_len = end_block_offset + 1; + DVLOG(1) << "get last block " << end_block_idx; + ++iov_used; + } + return iov_used; +} + +bool StreamSequencerBuffer::GetReadableRegion(iovec* iov, + QuicTime* timestamp) const { + if (ReadableBytes() == 0) { + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; + return false; + } + + size_t start_block_idx = NextBlockToRead(); + iov->iov_base = blocks_[start_block_idx]->buffer + ReadOffset(); + size_t readable_bytes_in_block = min<size_t>( + GetBlockCapacity(start_block_idx) - ReadOffset(), ReadableBytes()); + size_t region_len = 0; + auto iter = frame_arrival_time_map_.begin(); + *timestamp = iter->second.timestamp; + DVLOG(1) << "readable bytes in block: " << readable_bytes_in_block; + for (; iter != frame_arrival_time_map_.end() && + region_len + iter->second.length <= readable_bytes_in_block; + ++iter) { + if (iter->second.timestamp != *timestamp) { + // If reaches a frame arrive at another timestamp, stop expanding current + // region. + DVLOG(1) << "Meet frame with different timestamp."; + break; + } + region_len += iter->second.length; + DVLOG(1) << "Add bytes to region: " << iter->second.length; + } + if (iter == frame_arrival_time_map_.end() || + iter->second.timestamp == *timestamp) { + // If encountered the end of readable bytes before reaching a different + // timestamp. + DVLOG(1) << "Get all readable bytes in first block."; + region_len = readable_bytes_in_block; + } + iov->iov_len = region_len; + return true; +} + +bool StreamSequencerBuffer::MarkConsumed(size_t bytes_used) { + if (bytes_used > ReadableBytes()) { + return false; + } + size_t bytes_to_consume = bytes_used; + while (bytes_to_consume > 0) { + size_t block_idx = NextBlockToRead(); + size_t offset_in_block = ReadOffset(); + size_t bytes_available = min<size_t>( + ReadableBytes(), GetBlockCapacity(block_idx) - offset_in_block); + size_t bytes_read = min<size_t>(bytes_to_consume, bytes_available); + total_bytes_read_ += bytes_read; + num_bytes_buffered_ -= bytes_read; + bytes_to_consume -= bytes_read; + // If advanced to the end of current block and end of buffer hasn't wrapped + // to this block yet. + if (bytes_available == bytes_read) { + RetireBlockIfEmpty(block_idx); + } + } + if (bytes_used > 0) { + UpdateFrameArrivalMap(total_bytes_read_); + } + return true; +} + +size_t StreamSequencerBuffer::FlushBufferedFrames() { + size_t prev_total_bytes_read = total_bytes_read_; + total_bytes_read_ = gaps_.back().begin_offset; + Clear(); + return total_bytes_read_ - prev_total_bytes_read; +} + +size_t StreamSequencerBuffer::ReadableBytes() const { + return gaps_.front().begin_offset - total_bytes_read_; +} + +bool StreamSequencerBuffer::HasBytesToRead() const { + return ReadableBytes() > 0; +} + +QuicStreamOffset StreamSequencerBuffer::BytesConsumed() const { + return total_bytes_read_; +} + +size_t StreamSequencerBuffer::BytesBuffered() const { + return num_bytes_buffered_; +} + +size_t StreamSequencerBuffer::GetBlockIndex(QuicStreamOffset offset) const { + return (offset % max_buffer_capacity_bytes_) / kBlockSizeBytes; +} + +size_t StreamSequencerBuffer::GetInBlockOffset(QuicStreamOffset offset) const { + return (offset % max_buffer_capacity_bytes_) % kBlockSizeBytes; +} + +size_t StreamSequencerBuffer::ReadOffset() const { + return GetInBlockOffset(total_bytes_read_); +} + +size_t StreamSequencerBuffer::NextBlockToRead() const { + return GetBlockIndex(total_bytes_read_); +} + +void StreamSequencerBuffer::RetireBlockIfEmpty(size_t block_index) { + DCHECK(ReadableBytes() == 0 || GetInBlockOffset(total_bytes_read_) == 0) + << "RetireBlockIfEmpty() should only be called when advancing to next " + "block" + " or a gap has been reached."; + // If the whole buffer becomes empty, the last piece of data has been read. + if (Empty()) { + RetireBlock(block_index); + return; + } + + // Check where the logical end of this buffer is. + // Not empty if the end of circular buffer has been wrapped to this block. + if (GetBlockIndex(gaps_.back().begin_offset - 1) == block_index) { + return; + } + + // Read index remains in this block, which means a gap has been reached. + if (NextBlockToRead() == block_index) { + Gap first_gap = gaps_.front(); + DCHECK(first_gap.begin_offset == total_bytes_read_); + // Check where the next piece data is. + // Not empty if next piece of data is still in this chunk. + bool gap_extends_to_infinity = + (first_gap.end_offset != std::numeric_limits<QuicStreamOffset>::max()); + bool gap_ends_in_this_block = + (GetBlockIndex(first_gap.end_offset) == block_index); + if (gap_extends_to_infinity || gap_ends_in_this_block) { + return; + } + } + RetireBlock(block_index); +} + +bool StreamSequencerBuffer::Empty() const { + return gaps_.size() == 1 && gaps_.front().begin_offset == total_bytes_read_; +} + +size_t StreamSequencerBuffer::GetBlockCapacity(size_t block_index) const { + if ((block_index + 1) == blocks_count_) { + size_t result = max_buffer_capacity_bytes_ % kBlockSizeBytes; + if (result == 0) { // whole block + result = kBlockSizeBytes; + } + return result; + } else { + return kBlockSizeBytes; + } +} + +void StreamSequencerBuffer::UpdateFrameArrivalMap(QuicStreamOffset offset) { + // Get the frame before which all frames should be removed. + auto next_frame = frame_arrival_time_map_.upper_bound(offset); + DCHECK(next_frame != frame_arrival_time_map_.begin()); + auto iter = frame_arrival_time_map_.begin(); + while (iter != next_frame) { + auto erased = *iter; + iter = frame_arrival_time_map_.erase(iter); + DVLOG(1) << "remove FrameInfo with offsest: " << erased.first + << " len: " << erased.second.length; + if (erased.first + erased.second.length > offset) { + // If last frame is partially read out, update this FrameInfo and insert + // it back. + auto updated = std::make_pair( + offset, FrameInfo(erased.first + erased.second.length - offset, + erased.second.timestamp)); + DVLOG(1) << "insert back FrameInfo with offset: " << updated.first + << " len: " << updated.second.length; + frame_arrival_time_map_.insert(updated); + } + } +} + +} // namespace net diff --git a/net/quic/stream_sequencer_buffer.h b/net/quic/stream_sequencer_buffer.h new file mode 100644 index 0000000..07df630 --- /dev/null +++ b/net/quic/stream_sequencer_buffer.h @@ -0,0 +1,201 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_QUIC_STREAM_SEQUENCER_BUFFER_H_ +#define NET_QUIC_STREAM_SEQUENCER_BUFFER_H_ + +// StreamSequencerBuffer implements QuicStreamSequencerBufferInterface. +// It is a circular stream buffer with random write and +// in-sequence read. It consists of an std::vector of pointers pointing +// to memory blocks created as needed and a std::list of Gaps to indicate +// the missing data between the data already written into the buffer. +// - Data are written in with offset indicating where it should be in the +// stream, and the buffer grown as needed (up to the maximum buffer capacity), +// without expensive copying (extra blocks are allocated). +// - Data can be read from the buffer if there is no gap before it, +// and the buffer shrinks as the data are consumed. +// - An upper limit on the number of blocks in the buffer provides an upper +// bound on memory use. +// +// This class is thread-unsafe. +// +// StreamSequencerBuffer maintains a concept of the readable region, which +// contains all written data that has not been read. +// It promises stability of the underlying memory addresses in the readable +// region, so pointers into it can be maintained, and the offset of a pointer +// from the start of the read region can be calculated. +// +// Expected Use: +// StreamSequencerBuffer buffer(2.5 * 8 * 1024); +// std::string source(1024, 'a'); +// base::StringPiece std::string_piece(source.data(), source.size()); +// size_t written = 0; +// buffer.OnStreamData(800, std::string_piece, GetEpollClockNow(), &written); +// source = std::string{800, 'b'}; +// base::StringPiece std::string_piece1(source.data(), 800); +// // Try to write to [1, 801), but should fail due to overlapping, +// // res should be QUIC_INVALID_STREAM_DATA +// auto res = buffer.OnStreamData(1, std::string_piece1, &written)); +// // write to [0, 800), res should be QUIC_NO_ERROR +// auto res = buffer.OnStreamData(0, std::string_piece1, GetEpollClockNow(), +// &written); +// +// // Read into a iovec array with total capacity of 120 bytes. +// char dest[120]; +// iovec iovecs[3]{iovec{dest, 40}, iovec{dest + 40, 40}, +// iovec{dest + 80, 40}}; +// size_t read = buffer.Readv(iovecs, 3); +// +// // Get single readable region with timestamp. +// QuicTime t; +// iovec iov; +// buffer.GetReadableRegion(iov, &t); +// +// // Get readable regions from [256, 1024) and consume some of it. +// iovec iovs[2]; +// int iov_count = buffer.GetReadableRegions(iovs, 2); +// // Consume some bytes in iovs, returning number of bytes having been +// consumed. +// size_t consumed = consume_iovs(iovs, iov_count); +// buffer.MarkConsumed(consumed); + +#include <functional> +#include <list> +#include <memory> + +#include "base/macros.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/quic_stream_sequencer_buffer_interface.h" + +namespace net { + +namespace test { +class StreamSequencerBufferPeer; +} // namespace test + +class NET_EXPORT_PRIVATE StreamSequencerBuffer + : public QuicStreamSequencerBufferInterface { + public: + // A Gap indicates a missing chunk of bytes between + // [begin_offset, end_offset) in the stream + struct Gap { + Gap(QuicStreamOffset begin_offset, QuicStreamOffset end_offset); + QuicStreamOffset begin_offset; + QuicStreamOffset end_offset; + }; + + // A FrameInfo stores the length of a frame and the time it arrived. + struct NET_EXPORT_PRIVATE FrameInfo { + FrameInfo(); + FrameInfo(size_t length, QuicTime timestamp); + + size_t length; + QuicTime timestamp; + }; + + // Size of blocks used by this buffer. + // Choose 8K to make block large enough to hold multiple frames, each of + // which could be up to 1.5 KB. + static const size_t kBlockSizeBytes = 8 * 1024; // 8KB + + // The basic storage block used by this buffer. + struct BufferBlock { + char buffer[kBlockSizeBytes]; + }; + + explicit StreamSequencerBuffer(size_t max_capacity_bytes); + + ~StreamSequencerBuffer() override; + + // QuicStreamSequencerBufferInterface implementation. + void Clear() override; + bool Empty() const override; + QuicErrorCode OnStreamData(QuicStreamOffset offset, + base::StringPiece data, + QuicTime timestamp, + size_t* bytes_buffered) override; + size_t Readv(const struct iovec* dest_iov, size_t dest_count) override; + int GetReadableRegions(struct iovec* iov, int iov_len) const override; + bool GetReadableRegion(iovec* iov, QuicTime* timestamp) const override; + bool MarkConsumed(size_t bytes_buffered) override; + size_t FlushBufferedFrames() override; + bool HasBytesToRead() const override; + QuicStreamOffset BytesConsumed() const override; + size_t BytesBuffered() const override; + + private: + friend class test::StreamSequencerBufferPeer; + + // Dispose the given buffer block. + // After calling this method, blocks_[index] is set to nullptr + // in order to indicate that no memory set is allocated for that block. + void RetireBlock(size_t index); + + // Should only be called after the indexed block is read till the end of the + // block or a gap has been reached. + // If the block at |block_index| contains no buffered data, then the block is + // retired. + void RetireBlockIfEmpty(size_t block_index); + + // Called within OnStreamData() to update the gap OnStreamData() writes into + // (remove, split or change begin/end offset). + void UpdateGapList(std::list<Gap>::iterator gap_with_new_data_written, + QuicStreamOffset start_offset, + size_t bytes_written); + + // Calculate the capacity of block at specified index. + // Return value should be either kBlockSizeBytes for non-trailing blocks and + // max_buffer_capacity % kBlockSizeBytes for trailing block. + size_t GetBlockCapacity(size_t index) const; + + // Does not check if offset is within reasonable range. + size_t GetBlockIndex(QuicStreamOffset offset) const; + + // Given an offset in the stream, return the offset from the beginning of the + // block which contains this data. + size_t GetInBlockOffset(QuicStreamOffset offset) const; + + // Get offset relative to index 0 in logical 1st block to start next read. + size_t ReadOffset() const; + + // Get the index of the logical 1st block to start next read. + size_t NextBlockToRead() const; + + // Returns number of bytes available to be read out. + size_t ReadableBytes() const; + + // Called after Readv() and MarkConsumed() to keep frame_arrival_time_map_ + // up to date. + // |offset| is the byte next read should start from. All frames before it + // should be removed from the map. + void UpdateFrameArrivalMap(QuicStreamOffset offset); + + // The maximum total capacity of this buffer in byte, as constructed. + const size_t max_buffer_capacity_bytes_; + + // How many blocks this buffer would need when it reaches full capacity. + const size_t blocks_count_; + + // Number of bytes read out of buffer. + QuicStreamOffset total_bytes_read_; + + // Contains Gaps which represents currently missing data. + std::list<Gap> gaps_; + + // An ordered, variable-length std::list of blocks, with the length limited + // such that the number of blocks never exceeds blocks_count_. + // Each std::list entry can hold up to kBlockSizeBytes bytes. + std::vector<BufferBlock*> blocks_; + + // Number of bytes in buffer. + size_t num_bytes_buffered_; + + // Stores all the buffered frames' start offset, length and arrival time. + std::map<QuicStreamOffset, FrameInfo> frame_arrival_time_map_; + + DISALLOW_COPY_AND_ASSIGN(StreamSequencerBuffer); +}; +} // namespace net + +#endif // NET_QUIC_STREAM_SEQUENCER_BUFFER_H_ diff --git a/net/quic/stream_sequencer_buffer_test.cc b/net/quic/stream_sequencer_buffer_test.cc new file mode 100644 index 0000000..c85de86 --- /dev/null +++ b/net/quic/stream_sequencer_buffer_test.cc @@ -0,0 +1,981 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/quic/stream_sequencer_buffer.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/rand_util.h" +#include "net/quic/test_tools/mock_clock.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gmock_mutant.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::min; + +namespace net { + +namespace test { + +char GetCharFromIOVecs(size_t offset, iovec iov[], size_t count) { + size_t start_offset = 0; + for (size_t i = 0; i < count; i++) { + if (iov[i].iov_len == 0) { + continue; + } + size_t end_offset = start_offset + iov[i].iov_len - 1; + if (offset >= start_offset && offset <= end_offset) { + const char* buf = reinterpret_cast<const char*>(iov[i].iov_base); + return buf[offset - start_offset]; + } + start_offset += iov[i].iov_len; + } + LOG(ERROR) << "Could not locate char at offset " << offset << " in " << count + << " iovecs"; + for (size_t i = 0; i < count; ++i) { + LOG(ERROR) << " iov[" << i << "].iov_len = " << iov[i].iov_len; + } + return '\0'; +} + +static const size_t kBlockSizeBytes = StreamSequencerBuffer::kBlockSizeBytes; +typedef StreamSequencerBuffer::BufferBlock BufferBlock; +typedef StreamSequencerBuffer::Gap Gap; +typedef StreamSequencerBuffer::FrameInfo FrameInfo; + +class StreamSequencerBufferPeer { + public: + explicit StreamSequencerBufferPeer(StreamSequencerBuffer* buffer) + : buffer_(buffer) {} + + // Read from this buffer_->into the given destination buffer_-> up to the + // size of the destination. Returns the number of bytes read. Reading from + // an empty buffer_->returns 0. + size_t Read(char* dest_buffer, size_t size) { + iovec dest; + dest.iov_base = dest_buffer, dest.iov_len = size; + return buffer_->Readv(&dest, 1); + } + + // If buffer is empty, the blocks_ array must be empty, which means all + // blocks are deallocated. + bool CheckEmptyInvariants() { + return !buffer_->Empty() || IsBlockArrayEmpty(); + } + + bool IsBlockArrayEmpty() { + size_t count = buffer_->blocks_count_; + for (size_t i = 0; i < count; i++) { + if (buffer_->blocks_[i] != nullptr) { + return false; + } + } + return true; + } + + bool CheckInitialState() { + EXPECT_TRUE(buffer_->Empty() && buffer_->total_bytes_read_ == 0 && + buffer_->num_bytes_buffered_ == 0); + return CheckBufferInvariants(); + } + + bool CheckBufferInvariants() { + QuicStreamOffset data_span = + buffer_->gaps_.back().begin_offset - buffer_->total_bytes_read_; + bool capacity_sane = data_span <= buffer_->max_buffer_capacity_bytes_ && + data_span >= buffer_->num_bytes_buffered_; + if (!capacity_sane) { + LOG(ERROR) << "data span is larger than capacity."; + LOG(ERROR) << "total read: " << buffer_->total_bytes_read_ + << " last byte: " << buffer_->gaps_.back().begin_offset; + } + bool total_read_sane = + buffer_->gaps_.front().begin_offset >= buffer_->total_bytes_read_; + if (!total_read_sane) { + LOG(ERROR) << "read across 1st gap."; + } + bool read_offset_sane = buffer_->ReadOffset() < kBlockSizeBytes; + if (!capacity_sane) { + LOG(ERROR) << "read offset go beyond 1st block"; + } + bool block_match_capacity = + (buffer_->max_buffer_capacity_bytes_ <= + buffer_->blocks_count_ * kBlockSizeBytes) && + (buffer_->max_buffer_capacity_bytes_ > + (buffer_->blocks_count_ - 1) * kBlockSizeBytes); + if (!capacity_sane) { + LOG(ERROR) << "block number not match capcaity."; + } + bool block_retired_when_empty = CheckEmptyInvariants(); + if (!block_retired_when_empty) { + LOG(ERROR) << "block is not retired after use."; + } + return capacity_sane && total_read_sane && read_offset_sane && + block_match_capacity && block_retired_when_empty; + } + + size_t GetInBlockOffset(QuicStreamOffset offset) { + return buffer_->GetInBlockOffset(offset); + } + + BufferBlock* GetBlock(size_t index) { return buffer_->blocks_[index]; } + + int GapSize() { return buffer_->gaps_.size(); } + + std::list<Gap> GetGaps() { return buffer_->gaps_; } + + size_t max_buffer_capacity() { return buffer_->max_buffer_capacity_bytes_; } + + size_t ReadableBytes() { return buffer_->ReadableBytes(); } + + std::map<QuicStreamOffset, FrameInfo>* frame_arrival_time_map() { + return &(buffer_->frame_arrival_time_map_); + } + + private: + StreamSequencerBuffer* buffer_; +}; + +namespace { + +class StreamSequencerBufferTest : public testing::Test { + public: + void SetUp() override { Initialize(); } + + void ResetMaxCapacityBytes(size_t max_capacity_bytes) { + max_capacity_bytes_ = max_capacity_bytes; + Initialize(); + } + + protected: + void Initialize() { + buffer_.reset(new StreamSequencerBuffer(max_capacity_bytes_)); + helper_.reset(new StreamSequencerBufferPeer(buffer_.get())); + } + + // Use 2.5 here to make sure the buffer has more than one block and its end + // doesn't align with the end of a block in order to test all the offset + // calculation. + size_t max_capacity_bytes_ = 2.5 * kBlockSizeBytes; + + MockClock clock_; + std::unique_ptr<StreamSequencerBuffer> buffer_; + std::unique_ptr<StreamSequencerBufferPeer> helper_; +}; + +TEST_F(StreamSequencerBufferTest, InitializationWithDifferentSizes) { + const size_t kCapacity = 2 * StreamSequencerBuffer::kBlockSizeBytes; + ResetMaxCapacityBytes(kCapacity); + EXPECT_EQ(max_capacity_bytes_, helper_->max_buffer_capacity()); + EXPECT_TRUE(helper_->CheckInitialState()); + + const size_t kCapacity1 = 8 * StreamSequencerBuffer::kBlockSizeBytes; + ResetMaxCapacityBytes(kCapacity1); + EXPECT_EQ(kCapacity1, helper_->max_buffer_capacity()); + EXPECT_TRUE(helper_->CheckInitialState()); +} + +TEST_F(StreamSequencerBufferTest, ClearOnEmpty) { + buffer_->Clear(); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamData0length) { + std::string source; + size_t written; + EXPECT_DFATAL( + buffer_->OnStreamData(800, source, clock_.ApproximateNow(), &written), + "Attempted to write 0 bytes of data."); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataWithinBlock) { + std::string source(1024, 'a'); + size_t written; + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t = clock_.ApproximateNow(); + EXPECT_EQ(QUIC_NO_ERROR, buffer_->OnStreamData(800, source, t, &written)); + BufferBlock* block_ptr = helper_->GetBlock(0); + for (size_t i = 0; i < source.size(); ++i) { + ASSERT_EQ('a', block_ptr->buffer[helper_->GetInBlockOffset(800) + i]); + } + EXPECT_EQ(2, helper_->GapSize()); + std::list<Gap> gaps = helper_->GetGaps(); + EXPECT_EQ(800u, gaps.front().end_offset); + EXPECT_EQ(1824u, gaps.back().begin_offset); + auto frame_map = helper_->frame_arrival_time_map(); + EXPECT_EQ(1u, frame_map->size()); + EXPECT_EQ(800u, frame_map->begin()->first); + EXPECT_EQ(t, (*frame_map)[800].timestamp); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataWithOverlap) { + std::string source(1024, 'a'); + // Write something into [800, 1824) + size_t written; + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t1 = clock_.ApproximateNow(); + EXPECT_EQ(QUIC_NO_ERROR, buffer_->OnStreamData(800, source, t1, &written)); + // Try to write to [0, 1024) and [1024, 2048). + // But no byte will be written since overlap. + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t2 = clock_.ApproximateNow(); + EXPECT_EQ(QUIC_INVALID_STREAM_DATA, + buffer_->OnStreamData(0, source, t2, &written)); + EXPECT_EQ(QUIC_INVALID_STREAM_DATA, + buffer_->OnStreamData(1024, source, t2, &written)); + auto frame_map = helper_->frame_arrival_time_map(); + EXPECT_EQ(1u, frame_map->size()); + EXPECT_EQ(t1, (*frame_map)[800].timestamp); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataOverlapAndDuplicateCornerCases) { + std::string source(1024, 'a'); + // Write something into [800, 1824) + size_t written; + buffer_->OnStreamData(800, source, clock_.ApproximateNow(), &written); + source = std::string(800, 'b'); + // Try to write to [1, 801), but should fail due to overlapping + EXPECT_EQ( + QUIC_INVALID_STREAM_DATA, + buffer_->OnStreamData(1, source, clock_.ApproximateNow(), &written)); + // write to [0, 800) + EXPECT_EQ(QUIC_NO_ERROR, buffer_->OnStreamData( + 0, source, clock_.ApproximateNow(), &written)); + // Try to write one byte to [1823, 1824), but should count as duplicate + std::string one_byte = "c"; + EXPECT_EQ( + QUIC_NO_ERROR, + buffer_->OnStreamData(1823, one_byte, clock_.ApproximateNow(), &written)); + EXPECT_EQ(0u, written); + // write one byte to [1824, 1825) + EXPECT_EQ( + QUIC_NO_ERROR, + buffer_->OnStreamData(1824, one_byte, clock_.ApproximateNow(), &written)); + auto frame_map = helper_->frame_arrival_time_map(); + EXPECT_EQ(3u, frame_map->size()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataWithoutOverlap) { + std::string source(1024, 'a'); + // Write something into [800, 1824). + size_t written; + EXPECT_EQ(QUIC_NO_ERROR, buffer_->OnStreamData( + 800, source, clock_.ApproximateNow(), &written)); + source = std::string(100, 'b'); + // Write something into [kBlockSizeBytes * 2 - 20, kBlockSizeBytes * 2 + 80). + EXPECT_EQ(QUIC_NO_ERROR, + buffer_->OnStreamData(kBlockSizeBytes * 2 - 20, source, + clock_.ApproximateNow(), &written)); + EXPECT_EQ(3, helper_->GapSize()); + EXPECT_EQ(1024u + 100u, buffer_->BytesBuffered()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataTillEnd) { + // Write 50 bytes to the end. + const size_t kBytesToWrite = 50; + std::string source(kBytesToWrite, 'a'); + size_t written; + EXPECT_EQ(QUIC_NO_ERROR, + buffer_->OnStreamData(max_capacity_bytes_ - kBytesToWrite, source, + clock_.ApproximateNow(), &written)); + EXPECT_EQ(50u, buffer_->BytesBuffered()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataTillEndCorner) { + // Write 1 byte to the end. + const size_t kBytesToWrite = 1; + std::string source(kBytesToWrite, 'a'); + size_t written; + EXPECT_EQ(QUIC_NO_ERROR, + buffer_->OnStreamData(max_capacity_bytes_ - kBytesToWrite, source, + clock_.ApproximateNow(), &written)); + EXPECT_EQ(1u, buffer_->BytesBuffered()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataBeyondCapacity) { + std::string source(60, 'a'); + size_t written; + EXPECT_EQ(QUIC_INTERNAL_ERROR, + buffer_->OnStreamData(max_capacity_bytes_ - 50, source, + clock_.ApproximateNow(), &written)); + EXPECT_TRUE(helper_->CheckBufferInvariants()); + + source = "b"; + EXPECT_EQ(QUIC_INTERNAL_ERROR, + buffer_->OnStreamData(max_capacity_bytes_, source, + clock_.ApproximateNow(), &written)); + EXPECT_TRUE(helper_->CheckBufferInvariants()); + + EXPECT_EQ(QUIC_INTERNAL_ERROR, + buffer_->OnStreamData(max_capacity_bytes_ * 1000, source, + clock_.ApproximateNow(), &written)); + EXPECT_TRUE(helper_->CheckBufferInvariants()); + EXPECT_EQ(0u, buffer_->BytesBuffered()); +} + +TEST_F(StreamSequencerBufferTest, Readv100Bytes) { + std::string source(1024, 'a'); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t1 = clock_.ApproximateNow(); + // Write something into [kBlockSizeBytes, kBlockSizeBytes + 1024). + size_t written; + buffer_->OnStreamData(kBlockSizeBytes, source, t1, &written); + EXPECT_FALSE(buffer_->HasBytesToRead()); + source = std::string(100, 'b'); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t2 = clock_.ApproximateNow(); + // Write something into [0, 100). + buffer_->OnStreamData(0, source, t2, &written); + EXPECT_TRUE(buffer_->HasBytesToRead()); + EXPECT_EQ(2u, helper_->frame_arrival_time_map()->size()); + // Read into a iovec array with total capacity of 120 bytes. + char dest[120]; + iovec iovecs[3]{iovec{dest, 40}, iovec{dest + 40, 40}, iovec{dest + 80, 40}}; + size_t read = buffer_->Readv(iovecs, 3); + EXPECT_EQ(100u, read); + EXPECT_EQ(100u, buffer_->BytesConsumed()); + EXPECT_EQ(source, std::string(dest, read)); + EXPECT_EQ(1u, helper_->frame_arrival_time_map()->size()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, ReadvAcrossBlocks) { + std::string source(kBlockSizeBytes + 50, 'a'); + // Write 1st block to full and extand 50 bytes to next block. + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + EXPECT_EQ(source.size(), helper_->ReadableBytes()); + // Iteratively read 512 bytes from buffer_-> Overwrite dest[] each time. + char dest[512]; + while (helper_->ReadableBytes()) { + std::fill(dest, dest + 512, 0); + iovec iovecs[2]{iovec{dest, 256}, iovec{dest + 256, 256}}; + buffer_->Readv(iovecs, 2); + } + // The last read only reads the rest 50 bytes in 2nd block. + EXPECT_EQ(std::string(50, 'a'), std::string(dest, 50)); + EXPECT_EQ(0, dest[50]) << "Dest[50] shouln't be filled."; + EXPECT_EQ(source.size(), buffer_->BytesConsumed()); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, ClearAfterRead) { + std::string source(kBlockSizeBytes + 50, 'a'); + // Write 1st block to full with 'a'. + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + // Read first 512 bytes from buffer to make space at the beginning. + char dest[512]{0}; + const iovec iov{dest, 512}; + buffer_->Readv(&iov, 1); + // Clear() should make buffer empty while preserving BytesConsumed() + buffer_->Clear(); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, OnStreamDataAcrossLastBlockAndFillCapacity) { + std::string source(kBlockSizeBytes + 50, 'a'); + // Write 1st block to full with 'a'. + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + // Read first 512 bytes from buffer to make space at the beginning. + char dest[512]{0}; + const iovec iov{dest, 512}; + buffer_->Readv(&iov, 1); + EXPECT_EQ(source.size(), written); + + // Write more than half block size of bytes in the last block with 'b', which + // will wrap to the beginning and reaches the full capacity. + source = std::string(0.5 * kBlockSizeBytes + 512, 'b'); + EXPECT_EQ(QUIC_NO_ERROR, + buffer_->OnStreamData(2 * kBlockSizeBytes, source, + clock_.ApproximateNow(), &written)); + EXPECT_EQ(source.size(), written); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, + OnStreamDataAcrossLastBlockAndExceedCapacity) { + std::string source(kBlockSizeBytes + 50, 'a'); + // Write 1st block to full. + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + // Read first 512 bytes from buffer to make space at the beginning. + char dest[512]{0}; + const iovec iov{dest, 512}; + buffer_->Readv(&iov, 1); + + // Try to write from [max_capacity_bytes_ - 0.5 * kBlockSizeBytes, + // max_capacity_bytes_ + 512 + 1). But last bytes exceeds current capacity. + source = std::string(0.5 * kBlockSizeBytes + 512 + 1, 'b'); + EXPECT_EQ(QUIC_INTERNAL_ERROR, + buffer_->OnStreamData(2 * kBlockSizeBytes, source, + clock_.ApproximateNow(), &written)); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, ReadvAcrossLastBlock) { + // Write to full capacity and read out 512 bytes at beginning and continue + // appending 256 bytes. + std::string source(max_capacity_bytes_, 'a'); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t = clock_.ApproximateNow(); + size_t written; + buffer_->OnStreamData(0, source, t, &written); + char dest[512]{0}; + const iovec iov{dest, 512}; + buffer_->Readv(&iov, 1); + source = std::string(256, 'b'); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t2 = clock_.ApproximateNow(); + buffer_->OnStreamData(max_capacity_bytes_, source, t2, &written); + EXPECT_TRUE(helper_->CheckBufferInvariants()); + EXPECT_EQ(2u, helper_->frame_arrival_time_map()->size()); + + // Read all data out. + std::unique_ptr<char[]> dest1{new char[max_capacity_bytes_]{0}}; + const iovec iov1{dest1.get(), max_capacity_bytes_}; + EXPECT_EQ(max_capacity_bytes_ - 512 + 256, buffer_->Readv(&iov1, 1)); + EXPECT_EQ(max_capacity_bytes_ + 256, buffer_->BytesConsumed()); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); + EXPECT_EQ(0u, helper_->frame_arrival_time_map()->size()); +} + +TEST_F(StreamSequencerBufferTest, ReadvEmpty) { + char dest[512]{0}; + iovec iov{dest, 512}; + size_t read = buffer_->Readv(&iov, 1); + EXPECT_EQ(0u, read); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionsEmpty) { + iovec iovs[2]; + int iov_count = buffer_->GetReadableRegions(iovs, 2); + EXPECT_EQ(0, iov_count); + EXPECT_EQ(nullptr, iovs[iov_count].iov_base); + EXPECT_EQ(0u, iovs[iov_count].iov_len); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionsBlockedByGap) { + // Write into [1, 1024). + std::string source(1023, 'a'); + size_t written; + buffer_->OnStreamData(1, source, clock_.ApproximateNow(), &written); + // Try to get readable regions, but none is there. + iovec iovs[2]; + int iov_count = buffer_->GetReadableRegions(iovs, 2); + EXPECT_EQ(0, iov_count); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionsTillEndOfBlock) { + // Write first block to full with [0, 256) 'a' and the rest 'b' then read out + // [0, 256) + std::string source(kBlockSizeBytes, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[256]; + helper_->Read(dest, 256); + // Get readable region from [256, 1024) + iovec iovs[2]; + int iov_count = buffer_->GetReadableRegions(iovs, 2); + EXPECT_EQ(1, iov_count); + EXPECT_EQ(std::string(kBlockSizeBytes - 256, 'a'), + std::string(reinterpret_cast<const char*>(iovs[0].iov_base), + iovs[0].iov_len)); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionsWithinOneBlock) { + // Write into [0, 1024) and then read out [0, 256) + std::string source(1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[256]; + helper_->Read(dest, 256); + // Get readable region from [256, 1024) + iovec iovs[2]; + int iov_count = buffer_->GetReadableRegions(iovs, 2); + EXPECT_EQ(1, iov_count); + EXPECT_EQ(std::string(1024 - 256, 'a'), + std::string(reinterpret_cast<const char*>(iovs[0].iov_base), + iovs[0].iov_len)); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionsAcrossBlockWithLongIOV) { + // Write into [0, 2 * kBlockSizeBytes + 1024) and then read out [0, 1024) + std::string source(2 * kBlockSizeBytes + 1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[1024]; + helper_->Read(dest, 1024); + + iovec iovs[4]; + int iov_count = buffer_->GetReadableRegions(iovs, 4); + EXPECT_EQ(3, iov_count); + EXPECT_EQ(kBlockSizeBytes - 1024, iovs[0].iov_len); + EXPECT_EQ(kBlockSizeBytes, iovs[1].iov_len); + EXPECT_EQ(1024u, iovs[2].iov_len); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionsWithMultipleIOVsAcrossEnd) { + // Write into [0, 2 * kBlockSizeBytes + 1024) and then read out [0, 1024) + // and then append 1024 + 512 bytes. + std::string source(2.5 * kBlockSizeBytes - 1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[1024]; + helper_->Read(dest, 1024); + // Write across the end. + source = std::string(1024 + 512, 'b'); + buffer_->OnStreamData(2.5 * kBlockSizeBytes - 1024, source, + clock_.ApproximateNow(), &written); + // Use short iovec's. + iovec iovs[2]; + int iov_count = buffer_->GetReadableRegions(iovs, 2); + EXPECT_EQ(2, iov_count); + EXPECT_EQ(kBlockSizeBytes - 1024, iovs[0].iov_len); + EXPECT_EQ(kBlockSizeBytes, iovs[1].iov_len); + // Use long iovec's and wrap the end of buffer. + iovec iovs1[5]; + EXPECT_EQ(4, buffer_->GetReadableRegions(iovs1, 5)); + EXPECT_EQ(0.5 * kBlockSizeBytes, iovs1[2].iov_len); + EXPECT_EQ(512u, iovs1[3].iov_len); + EXPECT_EQ(std::string(512, 'b'), + std::string(reinterpret_cast<const char*>(iovs1[3].iov_base), + iovs1[3].iov_len)); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionEmpty) { + iovec iov; + QuicTime t = QuicTime::Zero(); + EXPECT_FALSE(buffer_->GetReadableRegion(&iov, &t)); + EXPECT_EQ(nullptr, iov.iov_base); + EXPECT_EQ(0u, iov.iov_len); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionBeforeGap) { + // Write into [1, 1024). + std::string source(1023, 'a'); + size_t written; + buffer_->OnStreamData(1, source, clock_.ApproximateNow(), &written); + // GetReadableRegion should return false because range [0,1) hasn't been + // filled yet. + iovec iov; + QuicTime t = QuicTime::Zero(); + EXPECT_FALSE(buffer_->GetReadableRegion(&iov, &t)); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionTillEndOfBlock) { + // Write into [0, kBlockSizeBytes + 1) and then read out [0, 256) + std::string source(kBlockSizeBytes + 1, 'a'); + size_t written; + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t = clock_.ApproximateNow(); + buffer_->OnStreamData(0, source, t, &written); + char dest[256]; + helper_->Read(dest, 256); + // Get readable region from [256, 1024) + iovec iov; + QuicTime t2 = QuicTime::Zero(); + EXPECT_TRUE(buffer_->GetReadableRegion(&iov, &t2)); + EXPECT_EQ(t, t2); + EXPECT_EQ( + std::string(kBlockSizeBytes - 256, 'a'), + std::string(reinterpret_cast<const char*>(iov.iov_base), iov.iov_len)); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionTillGap) { + // Write into [0, kBlockSizeBytes - 1) and then read out [0, 256) + std::string source(kBlockSizeBytes - 1, 'a'); + size_t written; + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t = clock_.ApproximateNow(); + buffer_->OnStreamData(0, source, t, &written); + char dest[256]; + helper_->Read(dest, 256); + // Get readable region from [256, 1023) + iovec iov; + QuicTime t2 = QuicTime::Zero(); + EXPECT_TRUE(buffer_->GetReadableRegion(&iov, &t2)); + EXPECT_EQ(t, t2); + EXPECT_EQ( + std::string(kBlockSizeBytes - 1 - 256, 'a'), + std::string(reinterpret_cast<const char*>(iov.iov_base), iov.iov_len)); +} + +TEST_F(StreamSequencerBufferTest, GetReadableRegionByArrivalTime) { + // Write into [0, kBlockSizeBytes - 100) and then read out [0, 256) + std::string source(kBlockSizeBytes - 100, 'a'); + size_t written; + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t = clock_.ApproximateNow(); + buffer_->OnStreamData(0, source, t, &written); + char dest[256]; + helper_->Read(dest, 256); + // Write into [kBlockSizeBytes - 100, kBlockSizeBytes - 50)] in same time + std::string source2(50, 'b'); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + buffer_->OnStreamData(kBlockSizeBytes - 100, source2, t, &written); + + // Write into [kBlockSizeBytes - 50, kBlockSizeBytes)] in another time + std::string source3(50, 'c'); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + QuicTime t3 = clock_.ApproximateNow(); + buffer_->OnStreamData(kBlockSizeBytes - 50, source3, t3, &written); + + // Get readable region from [256, 1024 - 50) + iovec iov; + QuicTime t4 = QuicTime::Zero(); + EXPECT_TRUE(buffer_->GetReadableRegion(&iov, &t4)); + EXPECT_EQ(t, t4); + EXPECT_EQ( + std::string(kBlockSizeBytes - 100 - 256, 'a') + source2, + std::string(reinterpret_cast<const char*>(iov.iov_base), iov.iov_len)); +} + +TEST_F(StreamSequencerBufferTest, MarkConsumedInOneBlock) { + // Write into [0, 1024) and then read out [0, 256) + std::string source(1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[256]; + helper_->Read(dest, 256); + + EXPECT_TRUE(buffer_->MarkConsumed(512)); + EXPECT_EQ(256u + 512u, buffer_->BytesConsumed()); + EXPECT_EQ(256u, helper_->ReadableBytes()); + EXPECT_EQ(1u, helper_->frame_arrival_time_map()->size()); + buffer_->MarkConsumed(256); + EXPECT_EQ(0u, helper_->frame_arrival_time_map()->size()); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, MarkConsumedNotEnoughBytes) { + // Write into [0, 1024) and then read out [0, 256) + std::string source(1024, 'a'); + size_t written; + QuicTime t = clock_.ApproximateNow(); + buffer_->OnStreamData(0, source, t, &written); + char dest[256]; + helper_->Read(dest, 256); + + // Consume 1st 512 bytes + EXPECT_TRUE(buffer_->MarkConsumed(512)); + EXPECT_EQ(256u + 512u, buffer_->BytesConsumed()); + EXPECT_EQ(256u, helper_->ReadableBytes()); + // Try to consume one bytes more than available. Should return false. + EXPECT_FALSE(buffer_->MarkConsumed(257)); + EXPECT_EQ(256u + 512u, buffer_->BytesConsumed()); + QuicTime t2 = QuicTime::Zero(); + iovec iov; + EXPECT_TRUE(buffer_->GetReadableRegion(&iov, &t2)); + EXPECT_EQ(t, t2); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, MarkConsumedAcrossBlock) { + // Write into [0, 2 * kBlockSizeBytes + 1024) and then read out [0, 1024) + std::string source(2 * kBlockSizeBytes + 1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[1024]; + helper_->Read(dest, 1024); + + buffer_->MarkConsumed(2 * kBlockSizeBytes); + EXPECT_EQ(source.size(), buffer_->BytesConsumed()); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, MarkConsumedAcrossEnd) { + // Write into [0, 2.5 * kBlockSizeBytes - 1024) and then read out [0, 1024) + // and then append 1024 + 512 bytes. + std::string source(2.5 * kBlockSizeBytes - 1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[1024]; + helper_->Read(dest, 1024); + source = std::string(1024 + 512, 'b'); + buffer_->OnStreamData(2.5 * kBlockSizeBytes - 1024, source, + clock_.ApproximateNow(), &written); + EXPECT_EQ(1024u, buffer_->BytesConsumed()); + + // Consume to the end of 2nd block. + buffer_->MarkConsumed(2 * kBlockSizeBytes - 1024); + EXPECT_EQ(2 * kBlockSizeBytes, buffer_->BytesConsumed()); + // Consume across the physical end of buffer + buffer_->MarkConsumed(0.5 * kBlockSizeBytes + 500); + EXPECT_EQ(max_capacity_bytes_ + 500, buffer_->BytesConsumed()); + EXPECT_EQ(12u, helper_->ReadableBytes()); + // Consume to the logical end of buffer + buffer_->MarkConsumed(12); + EXPECT_EQ(max_capacity_bytes_ + 512, buffer_->BytesConsumed()); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +TEST_F(StreamSequencerBufferTest, FlushBufferedFrames) { + // Write into [0, 2.5 * kBlockSizeBytes - 1024) and then read out [0, 1024). + std::string source(max_capacity_bytes_ - 1024, 'a'); + size_t written; + buffer_->OnStreamData(0, source, clock_.ApproximateNow(), &written); + char dest[1024]; + helper_->Read(dest, 1024); + EXPECT_EQ(1024u, buffer_->BytesConsumed()); + // Write [1024, 512) to the physical beginning. + source = std::string(512, 'b'); + buffer_->OnStreamData(max_capacity_bytes_, source, clock_.ApproximateNow(), + &written); + EXPECT_EQ(512u, written); + EXPECT_EQ(max_capacity_bytes_ - 1024 + 512, buffer_->FlushBufferedFrames()); + EXPECT_EQ(max_capacity_bytes_ + 512, buffer_->BytesConsumed()); + EXPECT_TRUE(buffer_->Empty()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); + // Clear buffer at this point should still preserve BytesConsumed(). + buffer_->Clear(); + EXPECT_EQ(max_capacity_bytes_ + 512, buffer_->BytesConsumed()); + EXPECT_TRUE(helper_->CheckBufferInvariants()); +} + +class StreamSequencerBufferRandomIOTest : public StreamSequencerBufferTest { + public: + typedef std::pair<QuicStreamOffset, size_t> OffsetSizePair; + + void SetUp() override { + // Test against a larger capacity then above tests. Also make sure the last + // block is partially available to use. + max_capacity_bytes_ = 6.25 * kBlockSizeBytes; + // Stream to be buffered should be larger than the capacity to test wrap + // around. + bytes_to_buffer_ = 2 * max_capacity_bytes_; + Initialize(); + + uint32 seed = base::RandInt(0, std::numeric_limits<int32>::max()); + LOG(INFO) << "RandomWriteAndProcessInPlace test seed is " << seed; + rng_.set_seed(seed); + } + + // Create an out-of-order source stream with given size to populate + // shuffled_buf_. + void CreateSourceAndShuffle(size_t max_chunk_size_bytes) { + max_chunk_size_bytes_ = max_chunk_size_bytes; + std::unique_ptr<OffsetSizePair[]> chopped_stream( + new OffsetSizePair[bytes_to_buffer_]); + + // Split stream into small chunks with random length. chopped_stream will be + // populated with segmented stream chunks. + size_t start_chopping_offset = 0; + size_t iterations = 0; + while (start_chopping_offset < bytes_to_buffer_) { + size_t max_chunk = min<size_t>(max_chunk_size_bytes_, + bytes_to_buffer_ - start_chopping_offset); + size_t chunk_size = rng_.RandUint64() % max_chunk + 1; + chopped_stream[iterations] = + OffsetSizePair(start_chopping_offset, chunk_size); + start_chopping_offset += chunk_size; + ++iterations; + } + DCHECK(start_chopping_offset == bytes_to_buffer_); + size_t chunk_num = iterations; + + // Randomly change the sequence of in-ordered OffsetSizePairs to make a + // out-of-order array of OffsetSizePairs. + for (int i = chunk_num - 1; i >= 0; --i) { + size_t random_idx = rng_.RandUint64() % (i + 1); + DVLOG(1) << "chunk offset " << chopped_stream[random_idx].first + << " size " << chopped_stream[random_idx].second; + shuffled_buf_.push_front(chopped_stream[random_idx]); + chopped_stream[random_idx] = chopped_stream[i]; + } + } + + // Write the currently first chunk of data in the out-of-order stream into + // StreamSequencerBuffer. If current chuck cannot be written into buffer + // because it goes beyond current capacity, move it to the end of + // shuffled_buf_ and write it later. + void WriteNextChunkToBuffer() { + OffsetSizePair& chunk = shuffled_buf_.front(); + QuicStreamOffset offset = chunk.first; + const size_t num_to_write = chunk.second; + std::unique_ptr<char[]> write_buf{new char[max_chunk_size_bytes_]}; + for (size_t i = 0; i < num_to_write; ++i) { + write_buf[i] = (offset + i) % 256; + } + base::StringPiece string_piece_w(write_buf.get(), num_to_write); + size_t written; + auto result = buffer_->OnStreamData(offset, string_piece_w, + clock_.ApproximateNow(), &written); + if (result == QUIC_NO_ERROR) { + shuffled_buf_.pop_front(); + total_bytes_written_ += num_to_write; + } else { + // This chunk offset exceeds window size. + shuffled_buf_.push_back(chunk); + shuffled_buf_.pop_front(); + } + DVLOG(1) << " write at offset: " << offset + << " len to write: " << num_to_write << " write result: " << result + << " left over: " << shuffled_buf_.size(); + } + + protected: + std::list<OffsetSizePair> shuffled_buf_; + size_t max_chunk_size_bytes_; + QuicStreamOffset bytes_to_buffer_; + size_t total_bytes_written_ = 0; + size_t total_bytes_read_ = 0; + SimpleRandom rng_; +}; + +TEST_F(StreamSequencerBufferRandomIOTest, RandomWriteAndReadv) { + // Set kMaxReadSize larger than kBlockSizeBytes to test both small and large + // read. + const size_t kMaxReadSize = kBlockSizeBytes * 2; + // kNumReads is larger than 1 to test how multiple read destinations work. + const size_t kNumReads = 2; + // Since write and read operation have equal possibility to be called. Bytes + // to be written into and read out of should roughly the same. + const size_t kMaxWriteSize = kNumReads * kMaxReadSize; + size_t iterations = 0; + + CreateSourceAndShuffle(kMaxWriteSize); + + while ((!shuffled_buf_.empty() || total_bytes_read_ < bytes_to_buffer_) && + iterations <= 2 * bytes_to_buffer_) { + uint8 next_action = + shuffled_buf_.empty() ? uint8{1} : rng_.RandUint64() % 2; + DVLOG(1) << "iteration: " << iterations; + switch (next_action) { + case 0: { // write + WriteNextChunkToBuffer(); + ASSERT_TRUE(helper_->CheckBufferInvariants()); + break; + } + case 1: { // readv + std::unique_ptr<char[][kMaxReadSize]> read_buf{ + new char[kNumReads][kMaxReadSize]}; + iovec dest_iov[kNumReads]; + size_t num_to_read = 0; + for (size_t i = 0; i < kNumReads; ++i) { + dest_iov[i].iov_base = + reinterpret_cast<void*>(const_cast<char*>(read_buf[i])); + dest_iov[i].iov_len = rng_.RandUint64() % kMaxReadSize; + num_to_read += dest_iov[i].iov_len; + } + size_t actually_read = buffer_->Readv(dest_iov, kNumReads); + ASSERT_LE(actually_read, num_to_read); + DVLOG(1) << " read from offset: " << total_bytes_read_ + << " size: " << num_to_read + << " actual read: " << actually_read; + for (size_t i = 0; i < actually_read; ++i) { + char ch = (i + total_bytes_read_) % 256; + ASSERT_EQ(ch, GetCharFromIOVecs(i, dest_iov, kNumReads)) + << " at iteration " << iterations; + } + total_bytes_read_ += actually_read; + ASSERT_EQ(total_bytes_read_, buffer_->BytesConsumed()); + ASSERT_TRUE(helper_->CheckBufferInvariants()); + break; + } + } + ++iterations; + ASSERT_LE(total_bytes_read_, total_bytes_written_); + } + EXPECT_LT(iterations, bytes_to_buffer_) << "runaway test"; + EXPECT_LE(bytes_to_buffer_, total_bytes_read_) << "iterations: " + << iterations; + EXPECT_LE(bytes_to_buffer_, total_bytes_written_); +} + +TEST_F(StreamSequencerBufferRandomIOTest, RandomWriteAndConsumeInPlace) { + // The value 4 is chosen such that the max write size is no larger than the + // maximum buffer capacity. + const size_t kMaxNumReads = 4; + // Adjust write amount be roughly equal to that GetReadableRegions() can get. + const size_t kMaxWriteSize = kMaxNumReads * kBlockSizeBytes; + ASSERT_LE(kMaxWriteSize, max_capacity_bytes_); + size_t iterations = 0; + + CreateSourceAndShuffle(kMaxWriteSize); + + while ((!shuffled_buf_.empty() || total_bytes_read_ < bytes_to_buffer_) && + iterations <= 2 * bytes_to_buffer_) { + uint8 next_action = + shuffled_buf_.empty() ? uint8{1} : rng_.RandUint64() % 2; + DVLOG(1) << "iteration: " << iterations; + switch (next_action) { + case 0: { // write + WriteNextChunkToBuffer(); + ASSERT_TRUE(helper_->CheckBufferInvariants()); + break; + } + case 1: { // GetReadableRegions and then MarkConsumed + size_t num_read = rng_.RandUint64() % kMaxNumReads + 1; + iovec dest_iov[kMaxNumReads]; + ASSERT_TRUE(helper_->CheckBufferInvariants()); + size_t actually_num_read = + buffer_->GetReadableRegions(dest_iov, num_read); + ASSERT_LE(actually_num_read, num_read); + size_t avail_bytes = 0; + for (size_t i = 0; i < actually_num_read; ++i) { + avail_bytes += dest_iov[i].iov_len; + } + // process random number of bytes (check the value of each byte). + size_t bytes_to_process = rng_.RandUint64() % (avail_bytes + 1); + size_t bytes_processed = 0; + for (size_t i = 0; i < actually_num_read; ++i) { + size_t bytes_in_block = min<size_t>( + bytes_to_process - bytes_processed, dest_iov[i].iov_len); + if (bytes_in_block == 0) { + break; + } + for (size_t j = 0; j < bytes_in_block; ++j) { + ASSERT_LE(bytes_processed, bytes_to_process); + char char_expected = + (buffer_->BytesConsumed() + bytes_processed) % 256; + ASSERT_EQ(char_expected, + reinterpret_cast<const char*>(dest_iov[i].iov_base)[j]) + << " at iteration " << iterations; + ++bytes_processed; + } + } + + buffer_->MarkConsumed(bytes_processed); + + DVLOG(1) << "iteration " << iterations << ": try to get " << num_read + << " readable regions, actually get " << actually_num_read + << " from offset: " << total_bytes_read_ + << "\nprocesse bytes: " << bytes_processed; + total_bytes_read_ += bytes_processed; + ASSERT_EQ(total_bytes_read_, buffer_->BytesConsumed()); + ASSERT_TRUE(helper_->CheckBufferInvariants()); + break; + } + } + ++iterations; + ASSERT_LE(total_bytes_read_, total_bytes_written_); + } + EXPECT_LT(iterations, bytes_to_buffer_) << "runaway test"; + EXPECT_LE(bytes_to_buffer_, total_bytes_read_) << "iterations: " + << iterations; + EXPECT_LE(bytes_to_buffer_, total_bytes_written_); +} + +} // anonymous namespace + +} // namespace test + +} // namespace net diff --git a/net/quic/test_tools/crypto_test_utils.cc b/net/quic/test_tools/crypto_test_utils.cc index 84c1c9f..6d8dae9 100644 --- a/net/quic/test_tools/crypto_test_utils.cc +++ b/net/quic/test_tools/crypto_test_utils.cc @@ -171,7 +171,7 @@ CryptoTestUtils::FakeClientOptions::FakeClientOptions() // static int CryptoTestUtils::HandshakeWithFakeServer( - MockHelper* helper, + MockConnectionHelper* helper, PacketSavingConnection* client_conn, QuicCryptoClientStream* client) { PacketSavingConnection* server_conn = new PacketSavingConnection( @@ -199,7 +199,7 @@ int CryptoTestUtils::HandshakeWithFakeServer( // static int CryptoTestUtils::HandshakeWithFakeClient( - MockHelper* helper, + MockConnectionHelper* helper, PacketSavingConnection* server_conn, QuicCryptoServerStream* server, const QuicServerId& server_id, diff --git a/net/quic/test_tools/crypto_test_utils.h b/net/quic/test_tools/crypto_test_utils.h index c0a7d9e..3104bca 100644 --- a/net/quic/test_tools/crypto_test_utils.h +++ b/net/quic/test_tools/crypto_test_utils.h @@ -69,12 +69,12 @@ class CryptoTestUtils { }; // returns: the number of client hellos that the client sent. - static int HandshakeWithFakeServer(MockHelper* helper, + static int HandshakeWithFakeServer(MockConnectionHelper* helper, PacketSavingConnection* client_conn, QuicCryptoClientStream* client); // returns: the number of client hellos that the client sent. - static int HandshakeWithFakeClient(MockHelper* helper, + static int HandshakeWithFakeClient(MockConnectionHelper* helper, PacketSavingConnection* server_conn, QuicCryptoServerStream* server, const QuicServerId& server_id, diff --git a/net/quic/test_tools/crypto_test_utils_chromium.cc b/net/quic/test_tools/crypto_test_utils_chromium.cc index ffa1788..8ff3c97 100644 --- a/net/quic/test_tools/crypto_test_utils_chromium.cc +++ b/net/quic/test_tools/crypto_test_utils_chromium.cc @@ -50,6 +50,7 @@ class TestProofVerifierChromium : public ProofVerifierChromium { const char kLeafCert[] = "leaf"; const char kIntermediateCert[] = "intermediate"; const char kSignature[] = "signature"; +const char kSCT[] = "CryptoServerTests"; class FakeProofSource : public ProofSource { public: @@ -65,9 +66,11 @@ class FakeProofSource : public ProofSource { const std::string& server_config, bool ecdsa_ok, const std::vector<std::string>** out_certs, - std::string* out_signature) override { + std::string* out_signature, + std::string* out_leaf_cert_sct) override { *out_certs = &certs_; *out_signature = kSignature; + *out_leaf_cert_sct = kSCT; return true; } @@ -117,7 +120,8 @@ ProofSource* CryptoTestUtils::ProofSourceForTesting() { base::FilePath certs_dir = GetTestCertsDirectory(); CHECK(source->Initialize( certs_dir.AppendASCII("quic_test.example.com.crt"), - certs_dir.AppendASCII("quic_test.example.com.key.pkcs8"))); + certs_dir.AppendASCII("quic_test.example.com.key.pkcs8"), + certs_dir.AppendASCII("quic_test.example.com.key.sct"))); return source; } diff --git a/net/quic/test_tools/quic_connection_peer.cc b/net/quic/test_tools/quic_connection_peer.cc index 5bd6370..15653ce 100644 --- a/net/quic/test_tools/quic_connection_peer.cc +++ b/net/quic/test_tools/quic_connection_peer.cc @@ -73,6 +73,12 @@ QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout( } // static +QuicSentEntropyManager* QuicConnectionPeer::GetSentEntropyManager( + QuicConnection* connection) { + return &connection->sent_entropy_manager_; +} + +// static // TODO(ianswett): Create a GetSentEntropyHash which accepts an AckFrame. QuicPacketEntropyHash QuicConnectionPeer::GetSentEntropyHash( QuicConnection* connection, @@ -213,7 +219,11 @@ void QuicConnectionPeer::CloseConnection(QuicConnection* connection) { // static QuicEncryptedPacket* QuicConnectionPeer::GetConnectionClosePacket( QuicConnection* connection) { - return connection->connection_close_packet_.get(); + if (connection->termination_packets_ == nullptr || + connection->termination_packets_->empty()) { + return nullptr; + } + return (*connection->termination_packets_)[0]; } // static diff --git a/net/quic/test_tools/quic_connection_peer.h b/net/quic/test_tools/quic_connection_peer.h index d7bfe44..ff29cf1 100644 --- a/net/quic/test_tools/quic_connection_peer.h +++ b/net/quic/test_tools/quic_connection_peer.h @@ -25,6 +25,7 @@ class QuicPacketCreator; class QuicPacketGenerator; class QuicPacketWriter; class QuicReceivedPacketManager; +class QuicSentEntropyManager; class QuicSentPacketManager; class SendAlgorithmInterface; @@ -55,6 +56,9 @@ class QuicConnectionPeer { static QuicTime::Delta GetNetworkTimeout(QuicConnection* connection); + static QuicSentEntropyManager* GetSentEntropyManager( + QuicConnection* connection); + static QuicPacketEntropyHash GetSentEntropyHash( QuicConnection* connection, QuicPacketNumber packet_number); diff --git a/net/quic/test_tools/quic_packet_creator_peer.cc b/net/quic/test_tools/quic_packet_creator_peer.cc index b5c9414..0b9a101 100644 --- a/net/quic/test_tools/quic_packet_creator_peer.cc +++ b/net/quic/test_tools/quic_packet_creator_peer.cc @@ -52,5 +52,13 @@ void QuicPacketCreatorPeer::SetPacketNumber(QuicPacketCreator* creator, creator->packet_number_ = s; } +// static +void QuicPacketCreatorPeer::FillPacketHeader(QuicPacketCreator* creator, + QuicFecGroupNumber fec_group, + bool fec_flag, + QuicPacketHeader* header) { + creator->FillPacketHeader(fec_group, fec_flag, header); +} + } // namespace test } // namespace net diff --git a/net/quic/test_tools/quic_packet_creator_peer.h b/net/quic/test_tools/quic_packet_creator_peer.h index 83efcb2..305a20c 100644 --- a/net/quic/test_tools/quic_packet_creator_peer.h +++ b/net/quic/test_tools/quic_packet_creator_peer.h @@ -29,6 +29,10 @@ class QuicPacketCreatorPeer { static QuicPacketNumberLength NextPacketNumberLength( QuicPacketCreator* creator); static void SetPacketNumber(QuicPacketCreator* creator, QuicPacketNumber s); + static void FillPacketHeader(QuicPacketCreator* creator, + QuicFecGroupNumber fec_group, + bool fec_flag, + QuicPacketHeader* header); private: DISALLOW_COPY_AND_ASSIGN(QuicPacketCreatorPeer); diff --git a/net/quic/test_tools/quic_session_peer.cc b/net/quic/test_tools/quic_session_peer.cc index f757121..cbf0ab4 100644 --- a/net/quic/test_tools/quic_session_peer.cc +++ b/net/quic/test_tools/quic_session_peer.cc @@ -42,10 +42,10 @@ QuicWriteBlockedList* QuicSessionPeer::GetWriteBlockedStreams( } // static -ReliableQuicStream* QuicSessionPeer::GetIncomingDynamicStream( +ReliableQuicStream* QuicSessionPeer::GetOrCreateDynamicStream( QuicSession* session, QuicStreamId stream_id) { - return session->GetIncomingDynamicStream(stream_id); + return session->GetOrCreateDynamicStream(stream_id); } // static diff --git a/net/quic/test_tools/quic_session_peer.h b/net/quic/test_tools/quic_session_peer.h index b1c0cd4..5ff9da9 100644 --- a/net/quic/test_tools/quic_session_peer.h +++ b/net/quic/test_tools/quic_session_peer.h @@ -29,7 +29,7 @@ class QuicSessionPeer { static void SetMaxOpenStreams(QuicSession* session, uint32 max_streams); static QuicCryptoStream* GetCryptoStream(QuicSession* session); static QuicWriteBlockedList* GetWriteBlockedStreams(QuicSession* session); - static ReliableQuicStream* GetIncomingDynamicStream(QuicSession* session, + static ReliableQuicStream* GetOrCreateDynamicStream(QuicSession* session, QuicStreamId stream_id); static std::map<QuicStreamId, QuicStreamOffset>& GetLocallyClosedStreamsHighestOffset(QuicSession* session); diff --git a/net/quic/test_tools/quic_test_packet_maker.cc b/net/quic/test_tools/quic_test_packet_maker.cc index e3f30fd..adecf9c 100644 --- a/net/quic/test_tools/quic_test_packet_maker.cc +++ b/net/quic/test_tools/quic_test_packet_maker.cc @@ -92,10 +92,11 @@ scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeAckAndRstPacket( scoped_ptr<QuicPacket> packet( BuildUnsizedDataPacket(&framer, header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer.EncryptPayload( - ENCRYPTION_NONE, header.packet_number, *packet, buffer, kMaxPacketSize)); - EXPECT_TRUE(encrypted != nullptr); - return scoped_ptr<QuicEncryptedPacket>(encrypted->Clone()); + size_t encrypted_size = framer.EncryptPayload( + ENCRYPTION_NONE, header.packet_number, *packet, buffer, kMaxPacketSize); + EXPECT_NE(0u, encrypted_size); + QuicEncryptedPacket encrypted(buffer, encrypted_size, false); + return scoped_ptr<QuicEncryptedPacket>(encrypted.Clone()); } scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeConnectionClosePacket( @@ -149,10 +150,11 @@ scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeAckPacket( scoped_ptr<QuicPacket> packet( BuildUnsizedDataPacket(&framer, header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer.EncryptPayload( - ENCRYPTION_NONE, header.packet_number, *packet, buffer, kMaxPacketSize)); - EXPECT_TRUE(encrypted != nullptr); - return scoped_ptr<QuicEncryptedPacket>(encrypted->Clone()); + size_t encrypted_size = framer.EncryptPayload( + ENCRYPTION_NONE, header.packet_number, *packet, buffer, kMaxPacketSize); + EXPECT_NE(0u, encrypted_size); + QuicEncryptedPacket encrypted(buffer, encrypted_size, false); + return scoped_ptr<QuicEncryptedPacket>(encrypted.Clone()); } // Returns a newly created packet to send kData on stream 1. @@ -282,10 +284,11 @@ scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakePacket( scoped_ptr<QuicPacket> packet( BuildUnsizedDataPacket(&framer, header, frames)); char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer.EncryptPayload( - ENCRYPTION_NONE, header.packet_number, *packet, buffer, kMaxPacketSize)); - EXPECT_TRUE(encrypted != nullptr); - return scoped_ptr<QuicEncryptedPacket>(encrypted->Clone()); + size_t encrypted_size = framer.EncryptPayload( + ENCRYPTION_NONE, header.packet_number, *packet, buffer, kMaxPacketSize); + EXPECT_NE(0u, encrypted_size); + QuicEncryptedPacket encrypted(buffer, encrypted_size, false); + return scoped_ptr<QuicEncryptedPacket>(encrypted.Clone()); } void QuicTestPacketMaker::InitializeHeader(QuicPacketNumber packet_number, diff --git a/net/quic/test_tools/quic_test_utils.cc b/net/quic/test_tools/quic_test_utils.cc index 44e2c83b..380a487a 100644 --- a/net/quic/test_tools/quic_test_utils.cc +++ b/net/quic/test_tools/quic_test_utils.cc @@ -33,7 +33,7 @@ namespace net { namespace test { namespace { -// No-op alarm implementation used by MockHelper. +// No-op alarm implementation used by MockConnectionHelper. class TestAlarm : public QuicAlarm { public: explicit TestAlarm(QuicAlarm::Delegate* delegate) @@ -204,23 +204,23 @@ MockConnectionVisitor::MockConnectionVisitor() {} MockConnectionVisitor::~MockConnectionVisitor() {} -MockHelper::MockHelper() {} +MockConnectionHelper::MockConnectionHelper() {} -MockHelper::~MockHelper() {} +MockConnectionHelper::~MockConnectionHelper() {} -const QuicClock* MockHelper::GetClock() const { +const QuicClock* MockConnectionHelper::GetClock() const { return &clock_; } -QuicRandom* MockHelper::GetRandomGenerator() { +QuicRandom* MockConnectionHelper::GetRandomGenerator() { return &random_generator_; } -QuicAlarm* MockHelper::CreateAlarm(QuicAlarm::Delegate* delegate) { +QuicAlarm* MockConnectionHelper::CreateAlarm(QuicAlarm::Delegate* delegate) { return new TestAlarm(delegate); } -void MockHelper::AdvanceTime(QuicTime::Delta delta) { +void MockConnectionHelper::AdvanceTime(QuicTime::Delta delta) { clock_.AdvanceTime(delta); } @@ -229,7 +229,8 @@ QuicPacketWriter* NiceMockPacketWriterFactory::Create( return new testing::NiceMock<MockPacketWriter>(); } -MockConnection::MockConnection(MockHelper* helper, Perspective perspective) +MockConnection::MockConnection(MockConnectionHelper* helper, + Perspective perspective) : MockConnection(kTestConnectionId, IPEndPoint(TestPeerIPAddress(), kTestPort), helper, @@ -237,7 +238,7 @@ MockConnection::MockConnection(MockHelper* helper, Perspective perspective) QuicSupportedVersions()) {} MockConnection::MockConnection(IPEndPoint address, - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective) : MockConnection(kTestConnectionId, address, @@ -246,7 +247,7 @@ MockConnection::MockConnection(IPEndPoint address, QuicSupportedVersions()) {} MockConnection::MockConnection(QuicConnectionId connection_id, - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective) : MockConnection(connection_id, IPEndPoint(TestPeerIPAddress(), kTestPort), @@ -254,7 +255,7 @@ MockConnection::MockConnection(QuicConnectionId connection_id, perspective, QuicSupportedVersions()) {} -MockConnection::MockConnection(MockHelper* helper, +MockConnection::MockConnection(MockConnectionHelper* helper, Perspective perspective, const QuicVersionVector& supported_versions) : MockConnection(kTestConnectionId, @@ -265,7 +266,7 @@ MockConnection::MockConnection(MockHelper* helper, MockConnection::MockConnection(QuicConnectionId connection_id, IPEndPoint address, - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective, const QuicVersionVector& supported_versions) : QuicConnection(connection_id, @@ -283,15 +284,15 @@ MockConnection::MockConnection(QuicConnectionId connection_id, MockConnection::~MockConnection() {} void MockConnection::AdvanceTime(QuicTime::Delta delta) { - static_cast<MockHelper*>(helper())->AdvanceTime(delta); + static_cast<MockConnectionHelper*>(helper())->AdvanceTime(delta); } -PacketSavingConnection::PacketSavingConnection(MockHelper* helper, +PacketSavingConnection::PacketSavingConnection(MockConnectionHelper* helper, Perspective perspective) : MockConnection(helper, perspective) {} PacketSavingConnection::PacketSavingConnection( - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective, const QuicVersionVector& supported_versions) : MockConnection(helper, perspective, supported_versions) {} @@ -508,11 +509,11 @@ QuicEncryptedPacket* ConstructEncryptedPacket( scoped_ptr<QuicPacket> packet( BuildUnsizedDataPacket(&framer, header, frames)); EXPECT_TRUE(packet != nullptr); - char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer.EncryptPayload( - ENCRYPTION_NONE, packet_number, *packet, buffer, kMaxPacketSize)); - EXPECT_TRUE(encrypted != nullptr); - return encrypted->Clone(); + char* buffer = new char[kMaxPacketSize]; + size_t encrypted_length = framer.EncryptPayload( + ENCRYPTION_NONE, packet_number, *packet, buffer, kMaxPacketSize); + EXPECT_NE(0u, encrypted_length); + return new QuicEncryptedPacket(buffer, encrypted_length, true); } QuicEncryptedPacket* ConstructMisFramedEncryptedPacket( @@ -552,11 +553,11 @@ QuicEncryptedPacket* ConstructMisFramedEncryptedPacket( packet->mutable_data())[GetStartOfEncryptedData( connection_id_length, version_flag, packet_number_length)] = 0xFF; - char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer.EncryptPayload( - ENCRYPTION_NONE, packet_number, *packet, buffer, kMaxPacketSize)); - EXPECT_TRUE(encrypted != nullptr); - return encrypted->Clone(); + char* buffer = new char[kMaxPacketSize]; + size_t encrypted_length = framer.EncryptPayload( + ENCRYPTION_NONE, packet_number, *packet, buffer, kMaxPacketSize); + EXPECT_NE(0u, encrypted_length); + return new QuicEncryptedPacket(buffer, encrypted_length, true); } void CompareCharArraysWithHexError( @@ -755,7 +756,7 @@ MockQuicConnectionDebugVisitor::~MockQuicConnectionDebugVisitor() {} void CreateClientSessionForTest(QuicServerId server_id, bool supports_stateless_rejects, QuicTime::Delta connection_start_time, - MockHelper* helper, + MockConnectionHelper* helper, QuicCryptoClientConfig* crypto_client_config, PacketSavingConnection** client_connection, TestQuicSpdyClientSession** client_session) { @@ -778,7 +779,7 @@ void CreateClientSessionForTest(QuicServerId server_id, void CreateServerSessionForTest(QuicServerId server_id, QuicTime::Delta connection_start_time, - MockHelper* helper, + MockConnectionHelper* helper, QuicCryptoServerConfig* server_crypto_config, PacketSavingConnection** server_connection, TestQuicSpdyServerSession** server_session) { diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h index 19cc5d6..d5fdec2 100644 --- a/net/quic/test_tools/quic_test_utils.h +++ b/net/quic/test_tools/quic_test_utils.h @@ -294,10 +294,10 @@ class MockConnectionVisitor : public QuicConnectionVisitorInterface { DISALLOW_COPY_AND_ASSIGN(MockConnectionVisitor); }; -class MockHelper : public QuicConnectionHelperInterface { +class MockConnectionHelper : public QuicConnectionHelperInterface { public: - MockHelper(); - ~MockHelper() override; + MockConnectionHelper(); + ~MockConnectionHelper() override; const QuicClock* GetClock() const override; QuicRandom* GetRandomGenerator() override; QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) override; @@ -307,7 +307,7 @@ class MockHelper : public QuicConnectionHelperInterface { MockClock clock_; MockRandom random_generator_; - DISALLOW_COPY_AND_ASSIGN(MockHelper); + DISALLOW_COPY_AND_ASSIGN(MockConnectionHelper); }; class NiceMockPacketWriterFactory : public QuicConnection::PacketWriterFactory { @@ -324,32 +324,33 @@ class NiceMockPacketWriterFactory : public QuicConnection::PacketWriterFactory { class MockConnection : public QuicConnection { public: // Uses a ConnectionId of 42 and 127.0.0.1:123. - MockConnection(MockHelper* helper, Perspective perspective); + MockConnection(MockConnectionHelper* helper, Perspective perspective); // Uses a ConnectionId of 42. MockConnection(IPEndPoint address, - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective); // Uses 127.0.0.1:123. MockConnection(QuicConnectionId connection_id, - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective); // Uses a ConnectionId of 42, and 127.0.0.1:123. - MockConnection(MockHelper* helper, + MockConnection(MockConnectionHelper* helper, Perspective perspective, const QuicVersionVector& supported_versions); MockConnection(QuicConnectionId connection_id, IPEndPoint address, - MockHelper* helper, + MockConnectionHelper* helper, Perspective perspective, const QuicVersionVector& supported_versions); ~MockConnection() override; - // If the constructor that uses a MockHelper has been used then this method + // If the constructor that uses a MockConnectionHelper has been used then + // this method // will advance the time of the MockClock. void AdvanceTime(QuicTime::Delta delta); @@ -404,9 +405,9 @@ class MockConnection : public QuicConnection { class PacketSavingConnection : public MockConnection { public: - PacketSavingConnection(MockHelper* helper, Perspective perspective); + PacketSavingConnection(MockConnectionHelper* helper, Perspective perspective); - PacketSavingConnection(MockHelper* helper, + PacketSavingConnection(MockConnectionHelper* helper, Perspective perspective, const QuicVersionVector& supported_versions); @@ -448,6 +449,7 @@ class MockQuicSpdySession : public QuicSpdySession { QuicRstStreamErrorCode error, QuicStreamOffset bytes_written)); MOCK_METHOD0(IsCryptoHandshakeConfirmed, bool()); + MOCK_METHOD1(OnHeadersHeadOfLineBlocking, void(QuicTime::Delta delta)); using QuicSession::ActivateStream; @@ -730,7 +732,7 @@ class MockQuicConnectionDebugVisitor : public QuicConnectionDebugVisitor { // Needed for strike-register nonce verification. The client // connection_start_time should be synchronized witht the server // start time, otherwise nonce verification will fail. -// helper: Pointer to the MockHelper to use for the session. +// helper: Pointer to the MockConnectionHelper to use for the session. // crypto_client_config: Pointer to the crypto client config. // client_connection: Pointer reference for newly created // connection. This object will be owned by the @@ -740,7 +742,7 @@ class MockQuicConnectionDebugVisitor : public QuicConnectionDebugVisitor { void CreateClientSessionForTest(QuicServerId server_id, bool supports_stateless_rejects, QuicTime::Delta connection_start_time, - MockHelper* helper, + MockConnectionHelper* helper, QuicCryptoClientConfig* crypto_client_config, PacketSavingConnection** client_connection, TestQuicSpdyClientSession** client_session); @@ -752,7 +754,7 @@ void CreateClientSessionForTest(QuicServerId server_id, // Needed for strike-register nonce verification. The server // connection_start_time should be synchronized witht the client // start time, otherwise nonce verification will fail. -// helper: Pointer to the MockHelper to use for the session. +// helper: Pointer to the MockConnectionHelper to use for the session. // crypto_server_config: Pointer to the crypto server config. // server_connection: Pointer reference for newly created // connection. This object will be owned by the @@ -761,7 +763,7 @@ void CreateClientSessionForTest(QuicServerId server_id, // session. The new object will be owned by the caller. void CreateServerSessionForTest(QuicServerId server_id, QuicTime::Delta connection_start_time, - MockHelper* helper, + MockConnectionHelper* helper, QuicCryptoServerConfig* crypto_server_config, PacketSavingConnection** server_connection, TestQuicSpdyServerSession** server_session); diff --git a/net/tools/quic/end_to_end_test.cc b/net/tools/quic/end_to_end_test.cc index b99cf1e..0d82d6d 100644 --- a/net/tools/quic/end_to_end_test.cc +++ b/net/tools/quic/end_to_end_test.cc @@ -211,18 +211,12 @@ vector<TestParams> GetTestParams() { class ServerDelegate : public PacketDroppingTestWriter::Delegate { public: - ServerDelegate(TestWriterFactory* writer_factory, - QuicDispatcher* dispatcher) - : writer_factory_(writer_factory), - dispatcher_(dispatcher) {} + explicit ServerDelegate(QuicDispatcher* dispatcher) + : dispatcher_(dispatcher) {} ~ServerDelegate() override {} - void OnPacketSent(WriteResult result) override { - writer_factory_->OnPacketSent(result); - } void OnCanWrite() override { dispatcher_->OnCanWrite(); } private: - TestWriterFactory* writer_factory_; QuicDispatcher* dispatcher_; }; @@ -230,7 +224,6 @@ class ClientDelegate : public PacketDroppingTestWriter::Delegate { public: explicit ClientDelegate(QuicClient* client) : client_(client) {} ~ClientDelegate() override {} - void OnPacketSent(WriteResult /*result*/) override {} void OnCanWrite() override { EpollEvent event(EPOLLOUT, false); client_->OnEvent(client_->fd(), &event); @@ -400,19 +393,11 @@ class EndToEndTest : public ::testing::TestWithParam<TestParams> { packet_writer_factory); QuicDispatcherPeer::UseWriter(dispatcher, server_writer_); - if (GetParam().server_uses_stateless_rejects_if_peer_supported) { - // Enable stateless rejects and force the server to always send - // them. - FLAGS_enable_quic_stateless_reject_support = true; - FLAGS_quic_session_map_threshold_for_stateless_rejects = 0; - } else { - FLAGS_enable_quic_stateless_reject_support = false; - FLAGS_quic_session_map_threshold_for_stateless_rejects = -1; - } + FLAGS_enable_quic_stateless_reject_support = + GetParam().server_uses_stateless_rejects_if_peer_supported; - server_writer_->Initialize( - QuicDispatcherPeer::GetHelper(dispatcher), - new ServerDelegate(packet_writer_factory, dispatcher)); + server_writer_->Initialize(QuicDispatcherPeer::GetHelper(dispatcher), + new ServerDelegate(dispatcher)); if (stream_factory_ != nullptr) { static_cast<QuicTestServer*>(server_thread_->server()) ->SetSpdyStreamFactory(stream_factory_); @@ -474,7 +459,11 @@ class EndToEndTest : public ::testing::TestWithParam<TestParams> { // EXPECT_EQ(0u, client_stats.packets_lost); // } EXPECT_EQ(0u, client_stats.packets_discarded); - EXPECT_EQ(0u, client_stats.packets_dropped); + // When doing 0-RTT with stateless rejects, the encrypted requests cause + // a retranmission of the SREJ packets which are dropped by the client. + if (!BothSidesSupportStatelessRejects()) { + EXPECT_EQ(0u, client_stats.packets_dropped); + } EXPECT_EQ(client_stats.packets_received, client_stats.packets_processed); const int num_expected_stateless_rejects = @@ -926,22 +915,9 @@ TEST_P(EndToEndTest, LargePostSynchronousRequest) { TEST_P(EndToEndTest, StatelessRejectWithPacketLoss) { // In this test, we intentionally drop the first packet from the // server, which corresponds with the initial REJ/SREJ response from - // the server. The REJ case will succeed, due to redundancy in the - // stateful handshake. The SREJ will fail, because there is - // (currently) no way to recover from a loss of the first SREJ, and - // all remaining state for the first handshake is black-holed on the - // time-wait list. - // TODO(jokulik): Once redundant SREJ support is added, this test - // should succeed. + // the server. server_writer_->set_fake_drop_first_n_packets(1); - // If this test will involve version negotiation then the version - // negotiation packet will be dropped, not the SREJ, and since the - // version negotiation packet will be retransmitted the test will - // succeed. - const bool will_succeed = - !BothSidesSupportStatelessRejects() || - negotiated_version_ != client_supported_versions_.front(); - ASSERT_EQ(will_succeed, Initialize()); + ASSERT_TRUE(Initialize()); } TEST_P(EndToEndTest, SetInitialReceivedConnectionOptions) { @@ -1073,7 +1049,8 @@ TEST_P(EndToEndTest, InvalidStream) { client_->SendCustomSynchronousRequest(request); // EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error()); - EXPECT_EQ(QUIC_PACKET_FOR_NONEXISTENT_STREAM, client_->connection_error()); + EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error()); + EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error()); } // TODO(rch): this test seems to cause net_unittests timeouts :| @@ -1721,7 +1698,7 @@ TEST_P(EndToEndTest, AckNotifierWithPacketLossAndBlockedSocket) { EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); // Send another request to flush out any pending ACKs on the server. - client_->SendSynchronousRequest(request_string); + client_->SendSynchronousRequest("/bar"); // Pause the server to avoid races. server_thread_->Pause(); diff --git a/net/tools/quic/quic_client_session.cc b/net/tools/quic/quic_client_session.cc index 1ee8322..edba8ed 100644 --- a/net/tools/quic/quic_client_session.cc +++ b/net/tools/quic/quic_client_session.cc @@ -20,17 +20,18 @@ QuicClientSession::QuicClientSession(const QuicConfig& config, const QuicServerId& server_id, QuicCryptoClientConfig* crypto_config) : QuicClientSessionBase(connection, config), - crypto_stream_(new QuicCryptoClientStream( - server_id, - this, - new ProofVerifyContextChromium(0, BoundNetLog()), - crypto_config)), - respect_goaway_(true) { -} + server_id_(server_id), + crypto_config_(crypto_config), + respect_goaway_(true) {} QuicClientSession::~QuicClientSession() { } +void QuicClientSession::Initialize() { + crypto_stream_.reset(CreateQuicCryptoStream()); + QuicClientSessionBase::Initialize(); +} + void QuicClientSession::OnProofValid( const QuicCryptoClientConfig::CachedState& /*cached*/) {} @@ -61,7 +62,7 @@ QuicSpdyClientStream* QuicClientSession::CreateClientStream() { return new QuicSpdyClientStream(GetNextOutgoingStreamId(), this); } -QuicCryptoClientStream* QuicClientSession::GetCryptoStream() { +QuicCryptoClientStreamBase* QuicClientSession::GetCryptoStream() { return crypto_stream_.get(); } @@ -80,5 +81,12 @@ QuicSpdyStream* QuicClientSession::CreateIncomingDynamicStream( return nullptr; } +QuicCryptoClientStreamBase* QuicClientSession::CreateQuicCryptoStream() { + return new QuicCryptoClientStream( + server_id_, this, new ProofVerifyContextChromium(0, BoundNetLog()), + crypto_config_); +} + } // namespace tools + } // namespace net diff --git a/net/tools/quic/quic_client_session.h b/net/tools/quic/quic_client_session.h index 2f180d7..e04d143 100644 --- a/net/tools/quic/quic_client_session.h +++ b/net/tools/quic/quic_client_session.h @@ -30,10 +30,12 @@ class QuicClientSession : public QuicClientSessionBase { const QuicServerId& server_id, QuicCryptoClientConfig* crypto_config); ~QuicClientSession() override; + // Set up the QuicClientSession. Must be called prior to use. + void Initialize() override; // QuicSession methods: QuicSpdyClientStream* CreateOutgoingDynamicStream() override; - QuicCryptoClientStream* GetCryptoStream() override; + QuicCryptoClientStreamBase* GetCryptoStream() override; // QuicClientSessionBase methods: void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override; @@ -56,14 +58,22 @@ class QuicClientSession : public QuicClientSessionBase { // QuicSession methods: QuicSpdyStream* CreateIncomingDynamicStream(QuicStreamId id) override; + // Create the crypto stream. Called by Initialize() + virtual QuicCryptoClientStreamBase* CreateQuicCryptoStream(); + // Unlike CreateOutgoingDynamicStream, which applies a bunch of sanity checks, // this simply returns a new QuicSpdyClientStream. This may be used by // subclasses which want to use a subclass of QuicSpdyClientStream for streams // but wish to use the sanity checks in CreateOutgoingDynamicStream. virtual QuicSpdyClientStream* CreateClientStream(); + const QuicServerId& server_id() { return server_id_; } + QuicCryptoClientConfig* crypto_config() { return crypto_config_; } + private: - scoped_ptr<QuicCryptoClientStream> crypto_stream_; + scoped_ptr<QuicCryptoClientStreamBase> crypto_stream_; + QuicServerId server_id_; + QuicCryptoClientConfig* crypto_config_; // If this is set to false, the client will ignore server GOAWAYs and allow // the creation of streams regardless of the high chance they will fail. diff --git a/net/tools/quic/quic_client_session_test.cc b/net/tools/quic/quic_client_session_test.cc index 541f290..7ae923b 100644 --- a/net/tools/quic/quic_client_session_test.cc +++ b/net/tools/quic/quic_client_session_test.cc @@ -20,7 +20,7 @@ using net::test::ConstructMisFramedEncryptedPacket; using net::test::CryptoTestUtils; using net::test::DefaultQuicConfig; using net::test::MockConnection; -using net::test::MockHelper; +using net::test::MockConnectionHelper; using net::test::PacketSavingConnection; using net::test::QuicSpdySessionPeer; using net::test::SupportedVersions; @@ -60,12 +60,13 @@ class ToolsQuicClientSessionTest void CompleteCryptoHandshake() { session_->CryptoConnect(); - CryptoTestUtils::HandshakeWithFakeServer(&helper_, connection_, - session_->GetCryptoStream()); + QuicCryptoClientStream* stream = + static_cast<QuicCryptoClientStream*>(session_->GetCryptoStream()); + CryptoTestUtils::HandshakeWithFakeServer(&helper_, connection_, stream); } QuicCryptoClientConfig crypto_config_; - MockHelper helper_; + MockConnectionHelper helper_; PacketSavingConnection* connection_; scoped_ptr<QuicClientSession> session_; }; diff --git a/net/tools/quic/quic_dispatcher.cc b/net/tools/quic/quic_dispatcher.cc index 47d47ff..bb4bd88 100644 --- a/net/tools/quic/quic_dispatcher.cc +++ b/net/tools/quic/quic_dispatcher.cc @@ -21,12 +21,6 @@ namespace tools { using std::make_pair; using base::StringPiece; -// The threshold size for the session map, over which the dispatcher will start -// sending stateless rejects (SREJ), rather than stateful rejects (REJ) to -// clients who support them. If -1, stateless rejects will not be sent. If 0, -// the server will only send stateless rejects to clients who support them. -int32 FLAGS_quic_session_map_threshold_for_stateless_rejects = -1; - namespace { // An alarm that informs the QuicDispatcher to delete old sessions. @@ -209,7 +203,7 @@ void QuicDispatcher::ProcessPacket(const IPEndPoint& server_address, current_packet_ = &packet; // ProcessPacket will cause the packet to be dispatched in // OnUnauthenticatedPublicHeader, or sent to the time wait list manager - // in OnAuthenticatedHeader. + // in OnUnauthenticatedHeader. framer_.ProcessPacket(packet); // TODO(rjshade): Return a status describing if/why a packet was dropped, // and log somehow. Maybe expose as a varz. @@ -359,13 +353,15 @@ QuicDispatcher::QuicPacketFate QuicDispatcher::ValidityChecks( void QuicDispatcher::CleanUpSession(SessionMap::iterator it, bool should_close_statelessly) { QuicConnection* connection = it->second->connection(); - QuicEncryptedPacket* connection_close_packet = - connection->ReleaseConnectionClosePacket(); + write_blocked_list_.erase(connection); - DCHECK(!should_close_statelessly || !connection_close_packet); + if (should_close_statelessly) { + DCHECK(connection->termination_packets() != nullptr && + !connection->termination_packets()->empty()); + } time_wait_list_manager_->AddConnectionIdToTimeWait( it->first, connection->version(), should_close_statelessly, - connection_close_packet); + connection->termination_packets()); session_map_.erase(it); } @@ -460,12 +456,6 @@ QuicServerSession* QuicDispatcher::CreateQuicSession( QuicServerSession* session = new QuicServerSession(config_, connection, this, crypto_config_); session->Initialize(); - if (FLAGS_quic_session_map_threshold_for_stateless_rejects != -1 && - session_map_.size() >= - static_cast<size_t>( - FLAGS_quic_session_map_threshold_for_stateless_rejects)) { - session->set_use_stateless_rejects_if_peer_supported(true); - } return session; } diff --git a/net/tools/quic/quic_dispatcher.h b/net/tools/quic/quic_dispatcher.h index 624dcf9..49a6d6d 100644 --- a/net/tools/quic/quic_dispatcher.h +++ b/net/tools/quic/quic_dispatcher.h @@ -33,8 +33,6 @@ namespace test { class QuicDispatcherPeer; } // namespace test -extern int32 FLAGS_quic_session_map_threshold_for_stateless_rejects; - class ProcessPacketInterface { public: virtual ~ProcessPacketInterface() {} diff --git a/net/tools/quic/quic_dispatcher_test.cc b/net/tools/quic/quic_dispatcher_test.cc index b54ee29..efe6acc 100644 --- a/net/tools/quic/quic_dispatcher_test.cc +++ b/net/tools/quic/quic_dispatcher_test.cc @@ -31,7 +31,7 @@ using net::EpollServer; using net::test::ConstructEncryptedPacket; using net::test::CryptoTestUtils; using net::test::MockConnection; -using net::test::MockHelper; +using net::test::MockConnectionHelper; using net::test::ValueRestore; using net::test::TestWriterFactory; using std::string; @@ -98,7 +98,7 @@ class TestDispatcher : public QuicDispatcher { class MockServerConnection : public MockConnection { public: MockServerConnection(QuicConnectionId connection_id, - MockHelper* helper, + MockConnectionHelper* helper, QuicDispatcher* dispatcher) : MockConnection(connection_id, helper, Perspective::IS_SERVER), dispatcher_(dispatcher) {} @@ -116,7 +116,7 @@ QuicServerSession* CreateSession(QuicDispatcher* dispatcher, const QuicConfig& config, QuicConnectionId connection_id, const IPEndPoint& client_address, - MockHelper* helper, + MockConnectionHelper* helper, const QuicCryptoServerConfig* crypto_config, TestQuicSpdyServerSession** session) { MockServerConnection* connection = @@ -203,7 +203,7 @@ class QuicDispatcherTest : public ::testing::Test { EpollServer eps_; QuicEpollConnectionHelper helper_; - MockHelper mock_helper_; + MockConnectionHelper mock_helper_; QuicConfig config_; QuicCryptoServerConfig crypto_config_; IPEndPoint server_address_; @@ -328,12 +328,9 @@ class MockQuicCryptoServerStream : public QuicCryptoServerStream { struct StatelessRejectTestParams { StatelessRejectTestParams(bool enable_stateless_rejects_via_flag, - bool use_stateless_rejects_if_peer_supported, bool client_supports_statelesss_rejects, bool crypto_handshake_successful) : enable_stateless_rejects_via_flag(enable_stateless_rejects_via_flag), - use_stateless_rejects_if_peer_supported( - use_stateless_rejects_if_peer_supported), client_supports_statelesss_rejects(client_supports_statelesss_rejects), crypto_handshake_successful(crypto_handshake_successful) {} @@ -341,8 +338,6 @@ struct StatelessRejectTestParams { const StatelessRejectTestParams& p) { os << " enable_stateless_rejects_via_flag: " << p.enable_stateless_rejects_via_flag << std::endl; - os << "{ use_stateless_rejects_if_peer_supported: " - << p.use_stateless_rejects_if_peer_supported << std::endl; os << "{ client_supports_statelesss_rejects: " << p.client_supports_statelesss_rejects << std::endl; os << " crypto_handshake_successful: " << p.crypto_handshake_successful @@ -351,12 +346,8 @@ struct StatelessRejectTestParams { } // This only enables the stateless reject feature via the feature-flag. - // It does not force the crypto server to emit stateless rejects. + // This should be a no-op if the peer does not support them. bool enable_stateless_rejects_via_flag; - // If true, this forces the server to send a stateless reject when rejecting - // messages. This should be a no-op if enable_stateless_rejects_via_flag is - // false or the peer does not support them. - bool use_stateless_rejects_if_peer_supported; // Whether or not the client supports stateless rejects. bool client_supports_statelesss_rejects; // Should the initial crypto handshake succeed or not. @@ -367,14 +358,11 @@ struct StatelessRejectTestParams { vector<StatelessRejectTestParams> GetStatelessRejectTestParams() { vector<StatelessRejectTestParams> params; for (bool enable_stateless_rejects_via_flag : {true, false}) { - for (bool use_stateless_rejects_if_peer_supported : {true, false}) { - for (bool client_supports_statelesss_rejects : {true, false}) { - for (bool crypto_handshake_successful : {true, false}) { - params.push_back(StatelessRejectTestParams( - enable_stateless_rejects_via_flag, - use_stateless_rejects_if_peer_supported, - client_supports_statelesss_rejects, crypto_handshake_successful)); - } + for (bool client_supports_statelesss_rejects : {true, false}) { + for (bool crypto_handshake_successful : {true, false}) { + params.push_back(StatelessRejectTestParams( + enable_stateless_rejects_via_flag, + client_supports_statelesss_rejects, crypto_handshake_successful)); } } } @@ -404,7 +392,6 @@ class QuicDispatcherStatelessRejectTest // a stateless reject, depending upon the parameters of the test. bool ExpectStatelessReject() { return GetParam().enable_stateless_rejects_via_flag && - GetParam().use_stateless_rejects_if_peer_supported && !GetParam().crypto_handshake_successful && GetParam().client_supports_statelesss_rejects; } @@ -419,8 +406,6 @@ class QuicDispatcherStatelessRejectTest crypto_stream1_ = new MockQuicCryptoServerStream(crypto_config_, session1_); session1_->SetCryptoStream(crypto_stream1_); - crypto_stream1_->set_use_stateless_rejects_if_peer_supported( - GetParam().use_stateless_rejects_if_peer_supported); crypto_stream1_->set_handshake_confirmed_for_testing( GetParam().crypto_handshake_successful); crypto_stream1_->set_peer_supports_stateless_rejects( @@ -624,7 +609,7 @@ class QuicDispatcherWriteBlockedListTest : public QuicDispatcherTest { } protected: - MockHelper helper_; + MockConnectionHelper helper_; BlockingWriter* writer_; QuicDispatcher::WriteBlockedList* blocked_list_; }; diff --git a/net/tools/quic/quic_epoll_clock.cc b/net/tools/quic/quic_epoll_clock.cc index 450afb8..62cb6bd 100644 --- a/net/tools/quic/quic_epoll_clock.cc +++ b/net/tools/quic/quic_epoll_clock.cc @@ -25,5 +25,10 @@ QuicTime QuicEpollClock::Now() const { QuicTime::Delta::FromMicroseconds(epoll_server_->NowInUsec())); } +QuicWallTime QuicEpollClock::WallNow() const { + return QuicWallTime::FromUNIXMicroseconds( + epoll_server_->ApproximateNowInUsec()); +} + } // namespace tools } // namespace net diff --git a/net/tools/quic/quic_epoll_clock.h b/net/tools/quic/quic_epoll_clock.h index c5a1390..b5eafc8 100644 --- a/net/tools/quic/quic_epoll_clock.h +++ b/net/tools/quic/quic_epoll_clock.h @@ -27,9 +27,13 @@ class QuicEpollClock : public QuicClock { QuicTime ApproximateNow() const override; // Returns the current time as a QuicTime object. - // Note: this use significant resources please use only if needed. + // Note: this uses significant resources, please use only if needed. QuicTime Now() const override; + // Returns the current time as a QuicWallTime object. + // Note: this uses significant resources, please use only if needed. + QuicWallTime WallNow() const override; + protected: EpollServer* epoll_server_; diff --git a/net/tools/quic/quic_epoll_clock_test.cc b/net/tools/quic/quic_epoll_clock_test.cc index 1ed1d15..6fe7c0e 100644 --- a/net/tools/quic/quic_epoll_clock_test.cc +++ b/net/tools/quic/quic_epoll_clock_test.cc @@ -18,10 +18,18 @@ TEST(QuicEpollClockTest, ApproximateNowInUsec) { epoll_server.set_now_in_usec(1000000); EXPECT_EQ(1000000, clock.ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds()); + EXPECT_EQ(1u, clock.WallNow().ToUNIXSeconds()); + EXPECT_EQ(1000000u, clock.WallNow().ToUNIXMicroseconds()); epoll_server.AdvanceBy(5); EXPECT_EQ(1000005, clock.ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds()); + EXPECT_EQ(1u, clock.WallNow().ToUNIXSeconds()); + EXPECT_EQ(1000005u, clock.WallNow().ToUNIXMicroseconds()); + + epoll_server.AdvanceBy(10 * 1000000); + EXPECT_EQ(11u, clock.WallNow().ToUNIXSeconds()); + EXPECT_EQ(11000005u, clock.WallNow().ToUNIXMicroseconds()); } TEST(QuicEpollClockTest, NowInUsec) { @@ -37,21 +45,6 @@ TEST(QuicEpollClockTest, NowInUsec) { clock.Now().Subtract(QuicTime::Zero()).ToMicroseconds()); } -TEST(QuicEpollClockTest, WallNow) { - MockEpollServer epoll_server; - QuicEpollClock clock(&epoll_server); - - base::Time start = base::Time::Now(); - QuicWallTime now = clock.WallNow(); - base::Time end = base::Time::Now(); - - // If end > start, then we can check now is between start and end. - if (end > start) { - EXPECT_LE(static_cast<uint64>(start.ToTimeT()), now.ToUNIXSeconds()); - EXPECT_LE(now.ToUNIXSeconds(), static_cast<uint64>(end.ToTimeT())); - } -} - } // namespace test } // namespace tools } // namespace net diff --git a/net/tools/quic/quic_server_bin.cc b/net/tools/quic/quic_server_bin.cc index 0102a81..7678eeb 100644 --- a/net/tools/quic/quic_server_bin.cc +++ b/net/tools/quic/quic_server_bin.cc @@ -25,7 +25,7 @@ int32 FLAGS_port = 6121; net::ProofSource* CreateProofSource(const base::FilePath& cert_path, const base::FilePath& key_path) { net::ProofSourceChromium* proof_source = new net::ProofSourceChromium(); - CHECK(proof_source->Initialize(cert_path, key_path)); + CHECK(proof_source->Initialize(cert_path, key_path, base::FilePath())); return proof_source; } diff --git a/net/tools/quic/quic_server_session.h b/net/tools/quic/quic_server_session.h index 144912d..654d1ce 100644 --- a/net/tools/quic/quic_server_session.h +++ b/net/tools/quic/quic_server_session.h @@ -95,13 +95,6 @@ class QuicServerSession : public QuicSpdySession { serving_region_ = serving_region; } - void set_use_stateless_rejects_if_peer_supported( - bool use_stateless_rejects_if_peer_supported) { - DCHECK(GetCryptoStream() != nullptr); - GetCryptoStream()->set_use_stateless_rejects_if_peer_supported( - use_stateless_rejects_if_peer_supported); - } - protected: // QuicSession methods: QuicSpdyStream* CreateIncomingDynamicStream(QuicStreamId id) override; diff --git a/net/tools/quic/quic_server_session_test.cc b/net/tools/quic/quic_server_session_test.cc index a8aa434..2b1b961 100644 --- a/net/tools/quic/quic_server_session_test.cc +++ b/net/tools/quic/quic_server_session_test.cc @@ -29,7 +29,7 @@ using __gnu_cxx::vector; using net::test::CryptoTestUtils; using net::test::MockConnection; -using net::test::MockHelper; +using net::test::MockConnectionHelper; using net::test::QuicConfigPeer; using net::test::QuicConnectionPeer; using net::test::QuicSpdyStreamPeer; @@ -54,9 +54,9 @@ namespace test { class QuicServerSessionPeer { public: - static ReliableQuicStream* GetIncomingDynamicStream(QuicServerSession* s, + static ReliableQuicStream* GetOrCreateDynamicStream(QuicServerSession* s, QuicStreamId id) { - return s->GetIncomingDynamicStream(id); + return s->GetOrCreateDynamicStream(id); } static void SetCryptoStream(QuicServerSession* s, QuicCryptoServerStream* crypto_stream) { @@ -98,7 +98,7 @@ class QuicServerSessionTest : public ::testing::TestWithParam<QuicVersion> { } StrictMock<MockQuicServerSessionVisitor> owner_; - MockHelper helper_; + MockConnectionHelper helper_; StrictMock<MockConnection>* connection_; QuicConfig config_; QuicCryptoServerConfig crypto_config_; @@ -134,7 +134,8 @@ TEST_P(QuicServerSessionTest, CloseStreamDueToReset) { EXPECT_EQ(1u, session_->GetNumOpenStreams()); // Send a reset (and expect the peer to send a RST in response). - QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); + QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_ERROR_PROCESSING_STREAM, + 0); EXPECT_CALL(*connection_, SendRstStream(kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT, 0)); visitor_->OnRstStream(rst1); @@ -150,7 +151,8 @@ TEST_P(QuicServerSessionTest, CloseStreamDueToReset) { TEST_P(QuicServerSessionTest, NeverOpenStreamDueToReset) { // Send a reset (and expect the peer to send a RST in response). - QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); + QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_ERROR_PROCESSING_STREAM, + 0); EXPECT_CALL(*connection_, SendRstStream(kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT, 0)); visitor_->OnRstStream(rst1); @@ -176,7 +178,7 @@ TEST_P(QuicServerSessionTest, AcceptClosedStream) { EXPECT_EQ(2u, session_->GetNumOpenStreams()); // Send a reset (and expect the peer to send a RST in response). - QuicRstStreamFrame rst(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); + QuicRstStreamFrame rst(kClientDataStreamId1, QUIC_ERROR_PROCESSING_STREAM, 0); EXPECT_CALL(*connection_, SendRstStream(kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT, 0)); visitor_->OnRstStream(rst); @@ -211,14 +213,14 @@ TEST_P(QuicServerSessionTest, MaxOpenStreams) { QuicStreamId stream_id = kClientDataStreamId1; // Open the max configured number of streams, should be no problem. for (size_t i = 0; i < kMaxStreamsForTest; ++i) { - EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDynamicStream(session_.get(), + EXPECT_TRUE(QuicServerSessionPeer::GetOrCreateDynamicStream(session_.get(), stream_id)); stream_id += 2; } // Open more streams: server should accept slightly more than the limit. for (size_t i = 0; i < kMaxStreamsMinimumIncrement; ++i) { - EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDynamicStream(session_.get(), + EXPECT_TRUE(QuicServerSessionPeer::GetOrCreateDynamicStream(session_.get(), stream_id)); stream_id += 2; } @@ -233,7 +235,7 @@ TEST_P(QuicServerSessionTest, MaxOpenStreams) { EXPECT_CALL(*connection_, SendRstStream(stream_id, QUIC_REFUSED_STREAM, 0)); } // Even if the connection remains open, the stream creation should fail. - EXPECT_FALSE(QuicServerSessionPeer::GetIncomingDynamicStream(session_.get(), + EXPECT_FALSE(QuicServerSessionPeer::GetOrCreateDynamicStream(session_.get(), stream_id)); } @@ -253,7 +255,7 @@ TEST_P(QuicServerSessionTest, MaxAvailableStreams) { EXPECT_LE(10 * kMaxStreamsForTest, kAvailableStreamLimit); EXPECT_EQ(0u, session_->GetNumOpenStreams()); - EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDynamicStream( + EXPECT_TRUE(QuicServerSessionPeer::GetOrCreateDynamicStream( session_.get(), kClientDataStreamId1)); // Establish available streams up to the server's limit. @@ -261,7 +263,7 @@ TEST_P(QuicServerSessionTest, MaxAvailableStreams) { FLAGS_allow_many_available_streams ? kClientDataStreamId1 + (kAvailableStreamLimit)*2 + 2 : kClientDataStreamId1 + (session_->get_max_open_streams() - 1) * 2; - EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDynamicStream( + EXPECT_TRUE(QuicServerSessionPeer::GetOrCreateDynamicStream( session_.get(), kLimitingStreamId)); // A further available stream will result in connection close. @@ -273,7 +275,7 @@ TEST_P(QuicServerSessionTest, MaxAvailableStreams) { } // This forces stream kLimitingStreamId + 2 to become available, which // violates the quota. - EXPECT_FALSE(QuicServerSessionPeer::GetIncomingDynamicStream( + EXPECT_FALSE(QuicServerSessionPeer::GetOrCreateDynamicStream( session_.get(), kLimitingStreamId + 4)); } @@ -281,14 +283,14 @@ TEST_P(QuicServerSessionTest, GetEvenIncomingError) { // Incoming streams on the server session must be odd. EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID)); EXPECT_EQ(nullptr, - QuicServerSessionPeer::GetIncomingDynamicStream(session_.get(), 4)); + QuicServerSessionPeer::GetOrCreateDynamicStream(session_.get(), 4)); } TEST_P(QuicServerSessionTest, GetStreamDisconnected) { // Don't create new streams if the connection is disconnected. QuicConnectionPeer::CloseConnection(connection_); EXPECT_DFATAL( - QuicServerSessionPeer::GetIncomingDynamicStream(session_.get(), 5), + QuicServerSessionPeer::GetOrCreateDynamicStream(session_.get(), 5), "ShouldCreateIncomingDynamicStream called when disconnected"); } @@ -305,7 +307,7 @@ TEST_P(QuicServerSessionTest, SetFecProtectionFromConfig) { // optionally protected. EXPECT_EQ(FEC_PROTECT_ALWAYS, QuicSpdySessionPeer::GetHeadersStream( session_.get())->fec_policy()); - ReliableQuicStream* stream = QuicServerSessionPeer::GetIncomingDynamicStream( + ReliableQuicStream* stream = QuicServerSessionPeer::GetOrCreateDynamicStream( session_.get(), kClientDataStreamId1); ASSERT_TRUE(stream); EXPECT_EQ(FEC_PROTECT_OPTIONAL, stream->fec_policy()); diff --git a/net/tools/quic/quic_simple_server_bin.cc b/net/tools/quic/quic_simple_server_bin.cc index ef435533..e6dc518 100644 --- a/net/tools/quic/quic_simple_server_bin.cc +++ b/net/tools/quic/quic_simple_server_bin.cc @@ -25,7 +25,7 @@ int32 FLAGS_port = 6121; net::ProofSource* CreateProofSource(const base::FilePath& cert_path, const base::FilePath& key_path) { net::ProofSourceChromium* proof_source = new net::ProofSourceChromium(); - CHECK(proof_source->Initialize(cert_path, key_path)); + CHECK(proof_source->Initialize(cert_path, key_path, base::FilePath())); return proof_source; } diff --git a/net/tools/quic/quic_simple_server_test.cc b/net/tools/quic/quic_simple_server_test.cc index c401c40..dea0a72 100644 --- a/net/tools/quic/quic_simple_server_test.cc +++ b/net/tools/quic/quic_simple_server_test.cc @@ -28,7 +28,7 @@ class QuicChromeServerDispatchPacketTest : public ::testing::Test { dispatcher_(config_, &crypto_config_, new tools::QuicDispatcher::DefaultPacketWriterFactory(), - new net::test::MockHelper) { + new net::test::MockConnectionHelper) { dispatcher_.InitializeWithWriter(nullptr); } diff --git a/net/tools/quic/quic_spdy_client_stream_test.cc b/net/tools/quic/quic_spdy_client_stream_test.cc index 087cfe7..af7bbd3 100644 --- a/net/tools/quic/quic_spdy_client_stream_test.cc +++ b/net/tools/quic/quic_spdy_client_stream_test.cc @@ -17,7 +17,7 @@ using net::test::CryptoTestUtils; using net::test::DefaultQuicConfig; using net::test::MockConnection; -using net::test::MockHelper; +using net::test::MockConnectionHelper; using net::test::SupportedVersions; using net::test::kClientDataStreamId1; using net::test::kInitialSessionFlowControlWindowForTest; @@ -60,7 +60,7 @@ class QuicSpdyClientStreamTest : public ::testing::Test { stream_.reset(new QuicSpdyClientStream(kClientDataStreamId1, &session_)); } - MockHelper helper_; + MockConnectionHelper helper_; StrictMock<MockConnection>* connection_; QuicCryptoClientConfig crypto_config_; QuicClientSession session_; diff --git a/net/tools/quic/quic_spdy_server_stream_test.cc b/net/tools/quic/quic_spdy_server_stream_test.cc index 0ba92ad..dedb272 100644 --- a/net/tools/quic/quic_spdy_server_stream_test.cc +++ b/net/tools/quic/quic_spdy_server_stream_test.cc @@ -22,7 +22,7 @@ using base::StringPiece; using net::test::MockConnection; -using net::test::MockHelper; +using net::test::MockConnectionHelper; using net::test::MockQuicSpdySession; using net::test::ReliableQuicStreamPeer; using net::test::SupportedVersions; @@ -126,7 +126,7 @@ class QuicSpdyServerStreamTest : public ::testing::Test { } SpdyHeaderBlock response_headers_; - MockHelper helper_; + MockConnectionHelper helper_; StrictMock<MockConnection>* connection_; StrictMock<MockQuicSpdySession> session_; QuicSpdyServerStreamPeer* stream_; // Owned by session_. diff --git a/net/tools/quic/quic_time_wait_list_manager.cc b/net/tools/quic/quic_time_wait_list_manager.cc index 8c2b46a..c9405a5 100644 --- a/net/tools/quic/quic_time_wait_list_manager.cc +++ b/net/tools/quic/quic_time_wait_list_manager.cc @@ -97,7 +97,7 @@ QuicTimeWaitListManager::~QuicTimeWaitListManager() { for (ConnectionIdMap::iterator it = connection_id_map_.begin(); it != connection_id_map_.end(); ++it) { - delete it->second.close_packet; + STLDeleteElements(&it->second.termination_packets); } } @@ -105,23 +105,28 @@ void QuicTimeWaitListManager::AddConnectionIdToTimeWait( QuicConnectionId connection_id, QuicVersion version, bool connection_rejected_statelessly, - QuicEncryptedPacket* close_packet) { - DCHECK(!connection_rejected_statelessly || !close_packet) - << "Connections that were rejected statelessly should not " - << "have a close packet. connection_id = " << connection_id; + std::vector<QuicEncryptedPacket*>* termination_packets) { + if (connection_rejected_statelessly) { + DCHECK(termination_packets != nullptr && !termination_packets->empty()) + << "Connections that were rejected statelessly must " + << "have a close packet. connection_id = " << connection_id; + } int num_packets = 0; ConnectionIdMap::iterator it = connection_id_map_.find(connection_id); const bool new_connection_id = it == connection_id_map_.end(); if (!new_connection_id) { // Replace record if it is reinserted. num_packets = it->second.num_packets; - delete it->second.close_packet; + STLDeleteElements(&it->second.termination_packets); connection_id_map_.erase(it); } TrimTimeWaitListIfNeeded(); DCHECK_LT(num_connections(), static_cast<size_t>(FLAGS_quic_time_wait_list_max_connections)); ConnectionIdData data(num_packets, version, clock_->ApproximateNow(), - close_packet, connection_rejected_statelessly); + connection_rejected_statelessly); + if (termination_packets != nullptr) { + data.termination_packets.swap(*termination_packets); + } connection_id_map_.insert(std::make_pair(connection_id, data)); if (new_connection_id) { visitor_->OnConnectionAddedToTimeWaitList(connection_id); @@ -171,17 +176,17 @@ void QuicTimeWaitListManager::ProcessPacket( return; } - if (connection_data->close_packet) { - QueuedPacket* queued_packet = new QueuedPacket( - server_address, client_address, connection_data->close_packet->Clone()); - // Takes ownership of the packet. - SendOrQueuePacket(queued_packet); - return; - } - - if (connection_data->connection_rejected_statelessly) { - DVLOG(3) << "Time wait list not sending response for connection " - << connection_id << " due to previous stateless reject."; + if (!connection_data->termination_packets.empty()) { + if (connection_data->connection_rejected_statelessly) { + DVLOG(3) << "Time wait list sending previous stateless reject response " + << "for connection " << connection_id; + } + for (QuicEncryptedPacket* packet : connection_data->termination_packets) { + QueuedPacket* queued_packet = + new QueuedPacket(server_address, client_address, packet->Clone()); + // Takes ownership of the packet. + SendOrQueuePacket(queued_packet); + } return; } @@ -290,7 +295,7 @@ bool QuicTimeWaitListManager::MaybeExpireOldestConnection( } // This connection_id has lived its age, retire it now. const QuicConnectionId connection_id = it->first; - delete it->second.close_packet; + STLDeleteElements(&it->second.termination_packets); connection_id_map_.erase(it); visitor_->OnConnectionRemovedFromTimeWaitList(connection_id); return true; @@ -316,5 +321,17 @@ void QuicTimeWaitListManager::TrimTimeWaitListIfNeeded() { } } +QuicTimeWaitListManager::ConnectionIdData::ConnectionIdData( + int num_packets_, + QuicVersion version_, + QuicTime time_added_, + bool connection_rejected_statelessly) + : num_packets(num_packets_), + version(version_), + time_added(time_added_), + connection_rejected_statelessly(connection_rejected_statelessly) {} + +QuicTimeWaitListManager::ConnectionIdData::~ConnectionIdData() {} + } // namespace tools } // namespace net diff --git a/net/tools/quic/quic_time_wait_list_manager.h b/net/tools/quic/quic_time_wait_list_manager.h index 092f116..e36d866 100644 --- a/net/tools/quic/quic_time_wait_list_manager.h +++ b/net/tools/quic/quic_time_wait_list_manager.h @@ -48,23 +48,18 @@ class QuicTimeWaitListManager : public QuicBlockedWriterInterface { ~QuicTimeWaitListManager() override; // Adds the given connection_id to time wait state for time_wait_period_. - // Henceforth, any packet bearing this connection_id should not be processed - // while the connection_id remains in this list. If a non-nullptr - // |close_packet| is provided, the TimeWaitListManager takes ownership of it - // and sends it again when packets are received for added connection_ids. If - // nullptr, a public reset packet is sent with the specified |version|. - // DCHECKs that connection_id is not already on the list. "virtual" to - // override in tests. If "connection_rejected_statelessly" is true, it means - // that the connection was closed due to a stateless reject, and no close - // packet is expected. Any packets that are received for connection_id will - // be black-holed. - // TODO(jokulik): In the future, we plan send (redundant) SREJ packets back to - // the client in response to stray data-packets that arrive after the first - // SREJ. This requires some new plumbing, so we black-hole for now. - virtual void AddConnectionIdToTimeWait(QuicConnectionId connection_id, - QuicVersion version, - bool connection_rejected_statelessly, - QuicEncryptedPacket* close_packet); + // If |termination_packets| are provided, copies of these packets will be sent + // when a packet with this connection ID is processed. If no termination + // packets are provided, then a PUBLIC_RESET will be sent with the specified + // |version|. Any termination packets will be move from |termination_packets| + // and will become owned by the manager. If |connection_rejected_statelessly| + // is true, it means that the connection was closed due to a stateless reject, + // and termination packets are expected. + virtual void AddConnectionIdToTimeWait( + QuicConnectionId connection_id, + QuicVersion version, + bool connection_rejected_statelessly, + std::vector<QuicEncryptedPacket*>* termination_packets); // Returns true if the connection_id is in time wait state, false otherwise. // Packets received for this connection_id should not lead to creation of new @@ -151,17 +146,15 @@ class QuicTimeWaitListManager : public QuicBlockedWriterInterface { ConnectionIdData(int num_packets_, QuicVersion version_, QuicTime time_added_, - QuicEncryptedPacket* close_packet, - bool connection_rejected_statelessly) - : num_packets(num_packets_), - version(version_), - time_added(time_added_), - close_packet(close_packet), - connection_rejected_statelessly(connection_rejected_statelessly) {} + bool connection_rejected_statelessly); + + ~ConnectionIdData(); + int num_packets; QuicVersion version; QuicTime time_added; - QuicEncryptedPacket* close_packet; + // These packets may contain CONNECTION_CLOSE frames, or SREJ messages. + std::vector<QuicEncryptedPacket*> termination_packets; bool connection_rejected_statelessly; }; diff --git a/net/tools/quic/quic_time_wait_list_manager_test.cc b/net/tools/quic/quic_time_wait_list_manager_test.cc index f78fbcb..0e925cb 100644 --- a/net/tools/quic/quic_time_wait_list_manager_test.cc +++ b/net/tools/quic/quic_time_wait_list_manager_test.cc @@ -97,9 +97,6 @@ class QuicTimeWaitListManagerTest : public ::testing::Test { QuicTimeWaitListManagerTest() : helper_(&epoll_server_), time_wait_list_manager_(&writer_, &visitor_, &helper_), - framer_(QuicSupportedVersions(), - QuicTime::Zero(), - Perspective::IS_SERVER), connection_id_(45), client_address_(net::test::TestPeerIPAddress(), kTestPort), writer_is_blocked_(false) {} @@ -119,17 +116,19 @@ class QuicTimeWaitListManagerTest : public ::testing::Test { } void AddStatelessConnectionId(QuicConnectionId connection_id) { + std::vector<QuicEncryptedPacket*> termination_packets; + termination_packets.push_back(new QuicEncryptedPacket(nullptr, 0, false)); time_wait_list_manager_.AddConnectionIdToTimeWait( connection_id, QuicVersionMax(), - /*connection_rejected_statelessly=*/true, nullptr); + /*connection_rejected_statelessly=*/true, &termination_packets); } void AddConnectionId(QuicConnectionId connection_id, QuicVersion version, bool connection_rejected_statelessly, - QuicEncryptedPacket* packet) { + std::vector<QuicEncryptedPacket*>* packets) { time_wait_list_manager_.AddConnectionIdToTimeWait( - connection_id, version, connection_rejected_statelessly, packet); + connection_id, version, connection_rejected_statelessly, packets); } bool IsConnectionIdInTimeWait(QuicConnectionId connection_id) { @@ -144,33 +143,10 @@ class QuicTimeWaitListManagerTest : public ::testing::Test { } QuicEncryptedPacket* ConstructEncryptedPacket( - EncryptionLevel level, QuicConnectionId connection_id, QuicPacketNumber packet_number) { - QuicPacketHeader header; - header.public_header.connection_id = connection_id; - header.public_header.connection_id_length = PACKET_8BYTE_CONNECTION_ID; - header.public_header.version_flag = false; - header.public_header.reset_flag = false; - header.public_header.packet_number_length = PACKET_6BYTE_PACKET_NUMBER; - header.packet_number = packet_number; - header.entropy_flag = false; - header.entropy_hash = 0; - header.fec_flag = false; - header.is_in_fec_group = NOT_IN_FEC_GROUP; - header.fec_group = 0; - QuicStreamFrame stream_frame(1, false, 0, StringPiece("data")); - QuicFrame frame(&stream_frame); - QuicFrames frames; - frames.push_back(frame); - scoped_ptr<QuicPacket> packet( - BuildUnsizedDataPacket(&framer_, header, frames)); - EXPECT_TRUE(packet != nullptr); - char buffer[kMaxPacketSize]; - scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPayload( - ENCRYPTION_NONE, packet_number, *packet, buffer, kMaxPacketSize)); - EXPECT_TRUE(encrypted != nullptr); - return encrypted->Clone(); + return net::test::ConstructEncryptedPacket(connection_id, false, false, + packet_number, "data"); } NiceMock<MockFakeTimeEpollServer> epoll_server_; @@ -178,7 +154,6 @@ class QuicTimeWaitListManagerTest : public ::testing::Test { StrictMock<MockPacketWriter> writer_; StrictMock<MockQuicServerSessionVisitor> visitor_; QuicTimeWaitListManager time_wait_list_manager_; - QuicFramer framer_; QuicConnectionId connection_id_; IPEndPoint server_address_; IPEndPoint client_address_; @@ -246,10 +221,12 @@ TEST_F(QuicTimeWaitListManagerTest, CheckStatelessConnectionIdInTimeWait) { TEST_F(QuicTimeWaitListManagerTest, SendConnectionClose) { const size_t kConnectionCloseLength = 100; EXPECT_CALL(visitor_, OnConnectionAddedToTimeWaitList(connection_id_)); + std::vector<QuicEncryptedPacket*> termination_packets; + termination_packets.push_back(new QuicEncryptedPacket( + new char[kConnectionCloseLength], kConnectionCloseLength, true)); AddConnectionId(connection_id_, QuicVersionMax(), /*connection_rejected_statelessly=*/false, - new QuicEncryptedPacket(new char[kConnectionCloseLength], - kConnectionCloseLength, true)); + &termination_packets); const int kRandomSequenceNumber = 1; EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength, server_address_.address(), @@ -259,6 +236,26 @@ TEST_F(QuicTimeWaitListManagerTest, SendConnectionClose) { ProcessPacket(connection_id_, kRandomSequenceNumber); } +TEST_F(QuicTimeWaitListManagerTest, SendTwoConnectionCloses) { + const size_t kConnectionCloseLength = 100; + EXPECT_CALL(visitor_, OnConnectionAddedToTimeWaitList(connection_id_)); + std::vector<QuicEncryptedPacket*> termination_packets; + termination_packets.push_back(new QuicEncryptedPacket( + new char[kConnectionCloseLength], kConnectionCloseLength, true)); + termination_packets.push_back(new QuicEncryptedPacket( + new char[kConnectionCloseLength], kConnectionCloseLength, true)); + AddConnectionId(connection_id_, QuicVersionMax(), + /*connection_rejected_statelessly=*/false, + &termination_packets); + const int kRandomSequenceNumber = 1; + EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength, + server_address_.address(), client_address_)) + .Times(2) + .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 1))); + + ProcessPacket(connection_id_, kRandomSequenceNumber); +} + TEST_F(QuicTimeWaitListManagerTest, SendPublicReset) { EXPECT_CALL(visitor_, OnConnectionAddedToTimeWaitList(connection_id_)); AddConnectionId(connection_id_); @@ -298,6 +295,11 @@ TEST_F(QuicTimeWaitListManagerTest, NoPublicResetForStatelessConnections) { EXPECT_CALL(visitor_, OnConnectionAddedToTimeWaitList(connection_id_)); AddStatelessConnectionId(connection_id_); const int kRandomSequenceNumber = 1; + + EXPECT_CALL(writer_, + WritePacket(_, _, server_address_.address(), client_address_)) + .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1))); + ProcessPacket(connection_id_, kRandomSequenceNumber); } @@ -359,7 +361,7 @@ TEST_F(QuicTimeWaitListManagerTest, SendQueuedPackets) { AddConnectionId(connection_id); QuicPacketNumber packet_number = 234; scoped_ptr<QuicEncryptedPacket> packet( - ConstructEncryptedPacket(ENCRYPTION_NONE, connection_id, packet_number)); + ConstructEncryptedPacket(connection_id, packet_number)); // Let first write through. EXPECT_CALL(writer_, WritePacket(_, _, server_address_.address(), client_address_)) @@ -384,8 +386,8 @@ TEST_F(QuicTimeWaitListManagerTest, SendQueuedPackets) { EXPECT_CALL(visitor_, OnConnectionAddedToTimeWaitList(other_connection_id)); AddConnectionId(other_connection_id); QuicPacketNumber other_packet_number = 23423; - scoped_ptr<QuicEncryptedPacket> other_packet(ConstructEncryptedPacket( - ENCRYPTION_NONE, other_connection_id, other_packet_number)); + scoped_ptr<QuicEncryptedPacket> other_packet( + ConstructEncryptedPacket(other_connection_id, other_packet_number)); EXPECT_CALL(writer_, WritePacket(_, _, _, _)) .Times(0); EXPECT_CALL(visitor_, OnWriteBlocked(&time_wait_list_manager_)); @@ -439,10 +441,12 @@ TEST_F(QuicTimeWaitListManagerTest, AddConnectionIdTwice) { AddConnectionId(connection_id_); EXPECT_TRUE(IsConnectionIdInTimeWait(connection_id_)); const size_t kConnectionCloseLength = 100; + std::vector<QuicEncryptedPacket*> termination_packets; + termination_packets.push_back(new QuicEncryptedPacket( + new char[kConnectionCloseLength], kConnectionCloseLength, true)); AddConnectionId(connection_id_, QuicVersionMax(), /*connection_rejected_statelessly=*/false, - new QuicEncryptedPacket(new char[kConnectionCloseLength], - kConnectionCloseLength, true)); + &termination_packets); EXPECT_TRUE(IsConnectionIdInTimeWait(connection_id_)); EXPECT_EQ(1u, time_wait_list_manager_.num_connections()); diff --git a/net/tools/quic/test_tools/mock_quic_time_wait_list_manager.h b/net/tools/quic/test_tools/mock_quic_time_wait_list_manager.h index 56b2880..116fd7e 100644 --- a/net/tools/quic/test_tools/mock_quic_time_wait_list_manager.h +++ b/net/tools/quic/test_tools/mock_quic_time_wait_list_manager.h @@ -25,15 +25,16 @@ class MockTimeWaitListManager : public QuicTimeWaitListManager { void(QuicConnectionId connection_id, QuicVersion version, bool connection_rejected_statelessly, - QuicEncryptedPacket* close_packet)); + std::vector<QuicEncryptedPacket*>* termination_packets)); void QuicTimeWaitListManager_AddConnectionIdToTimeWait( QuicConnectionId connection_id, QuicVersion version, bool connection_rejected_statelessly, - QuicEncryptedPacket* close_packet) { + std::vector<QuicEncryptedPacket*>* termination_packets) { QuicTimeWaitListManager::AddConnectionIdToTimeWait( - connection_id, version, connection_rejected_statelessly, close_packet); + connection_id, version, connection_rejected_statelessly, + termination_packets); } MOCK_METHOD5(ProcessPacket, diff --git a/net/tools/quic/test_tools/packet_dropping_test_writer.h b/net/tools/quic/test_tools/packet_dropping_test_writer.h index 0082f8e..9a95bd8 100644 --- a/net/tools/quic/test_tools/packet_dropping_test_writer.h +++ b/net/tools/quic/test_tools/packet_dropping_test_writer.h @@ -30,7 +30,6 @@ class PacketDroppingTestWriter : public QuicPacketWriterWrapper { class Delegate { public: virtual ~Delegate() {} - virtual void OnPacketSent(WriteResult result) = 0; virtual void OnCanWrite() = 0; }; |