summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorzhongyi <zhongyi@chromium.org>2016-01-12 12:08:31 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-12 20:09:32 +0000
commit4a9d27b850291097879dde36c408e15a9216609a (patch)
treeace8eaeb09d1360052f117a804dcb56f7be05aa1 /net
parent8f3387ec549ec8b5d0f20f652a5e93179b65252c (diff)
downloadchromium_src-4a9d27b850291097879dde36c408e15a9216609a.zip
chromium_src-4a9d27b850291097879dde36c408e15a9216609a.tar.gz
chromium_src-4a9d27b850291097879dde36c408e15a9216609a.tar.bz2
Landing Recent QUIC changes until 1/8/2015 23:22 UTC
relnote: Allowing multipath support negotiation. Protected by --quic_enable_multipath, default value is false. Merge internal change: 111718558 https://codereview.chromium.org/1572033003 relnote: Implement server push methods in QuicSimpleServerSession. Only modify toy Quic server, not used in production. Merege internal change: 111716301 https://codereview.chromium.org/1570343005 relnote: Require QUIC handshakes to require either a valid server nonce or a remote strike register. Merge internal change: 111708360 https://codereview.chromium.org/1569853005 relnote: n/a test-only change. Change QUIC's General and TCP loss algorithm tests to create RetransmittableFrames with a QuicStreamFrame instead of no stream frames. Merge internal change: 111655037 https://codereview.chromium.org/1570363002 relnote: Re-enable strike register lookups for QUIC replay protection, until at least QUIC_VERSION_31. Not flag protected, as Chrome currently only talks QUIC_VERSION_25. Merge internal change: 111628540 https://codereview.chromium.org/1574633002 relnote: n/a (test only) Always create a SerializedPacket with at least one QuicFrame in the RetransmittableFrames. Merge internal change: 111570157 https://codereview.chromium.org/1576553003 relnote: quic_supports_push_promise should be disabled by default. Merge internal change: 111560672 https://codereview.chromium.org/1576623002 relnote: QUIC header streams support to receive PUSH_PROMISE. Protected by --quic_supports_push_promise. This is part of shared code, but client side functionality only. Merge internal change: 111542509 https://codereview.chromium.org/1572013002 relnote: always sending kFIXD And extending the lifetime of the flag. When we deprecate the old version we can flip it false again and then nuke the code. Merge internal change: 111507546 https://codereview.chromium.org/1574623002 relnote: Merge all fields of QueuedPacket into SerializedPacket. No functional change. Makes it easier to make SerializedPacket a class that owns its members in the future. Merge internal change: 111444711 https://codereview.chromium.org/1569823008 relnote: Deprecate reloadable_flag_quic_inplace_encryption. Merge internal change: 111440524 https://codereview.chromium.org/1570553002 relnote: add new unused functions for client side of QUIC server push. QUIC - Add helper functions for SpdyHeaderBlock values. Merge internal change: 111439689 https://codereview.chromium.org/1561383002 relnote: n/a (test only) QUIC - make MockConnectionHelper::TestAlarm public. This is test only, and no-op for now. It will be used by a subsequent CL in support of client-side server push. Merge internal change: 111427940 https://codereview.chromium.org/1563083002 relnote: Adds Slow Start with Large Reduction (SSLR) option in QUIC to do a large reduction of the congestion window when exiting slow start. When exiting slow start due to loss, SSLR causes the congestion window to be reduced by the number of losses encountered at the end of slow start. SSLR is expected to reduce retransmission rates when exiting slow start, and it should also help the congestion window converge rapidly when the connection's actual BDP is smaller than the IW. Merge internal change: 111385694 https://codereview.chromium.org/1566853002 relnote: Clear the FEC group in QuicPacketCreator when encryption_level is none and close the connection if SerializeFec is called. Protected by gfe2_reloadable_flag_never_write_unencrypted_data. Merge internal change: 111385311 https://codereview.chromium.org/1562833003 relnote: Moving many QUIC DFATALS over to QUIC_BUG Merge internal change: 111359954 https://codereview.chromium.org/1565883003 relnote: Change memcpy to memmove in QuicFramer::EncryptPayload, because the src and dest buffers may be identical. No functional change. Merge internal change: 111345350 https://codereview.chromium.org/1558423002 rel-note: A small refactoring to make RecordSpuriousRetransmission() of QuicSentPacketManager simpler. (no functional change) Merge internal change: 111321766 https://codereview.chromium.org/1566633002 BUG= Review URL: https://codereview.chromium.org/1579033002 Cr-Commit-Position: refs/heads/master@{#368964}
Diffstat (limited to 'net')
-rw-r--r--net/net.gypi2
-rw-r--r--net/quic/congestion_control/general_loss_algorithm_test.cc6
-rw-r--r--net/quic/congestion_control/tcp_cubic_bytes_sender.cc22
-rw-r--r--net/quic/congestion_control/tcp_cubic_bytes_sender.h3
-rw-r--r--net/quic/congestion_control/tcp_cubic_bytes_sender_test.cc57
-rw-r--r--net/quic/congestion_control/tcp_cubic_sender.cc22
-rw-r--r--net/quic/congestion_control/tcp_cubic_sender.h3
-rw-r--r--net/quic/congestion_control/tcp_cubic_sender_test.cc57
-rw-r--r--net/quic/congestion_control/tcp_loss_algorithm_test.cc6
-rw-r--r--net/quic/crypto/crypto_protocol.h4
-rw-r--r--net/quic/crypto/crypto_server_test.cc36
-rw-r--r--net/quic/crypto/quic_crypto_client_config.cc5
-rw-r--r--net/quic/crypto/quic_crypto_server_config.cc29
-rw-r--r--net/quic/quic_bug_tracker.h10
-rw-r--r--net/quic/quic_client_session_base.cc31
-rw-r--r--net/quic/quic_client_session_base.h14
-rw-r--r--net/quic/quic_config.cc12
-rw-r--r--net/quic/quic_config.h9
-rw-r--r--net/quic/quic_connection.cc131
-rw-r--r--net/quic/quic_connection.h45
-rw-r--r--net/quic/quic_connection_test.cc28
-rw-r--r--net/quic/quic_crypto_client_stream.cc6
-rw-r--r--net/quic/quic_crypto_server_stream_test.cc9
-rw-r--r--net/quic/quic_flags.cc22
-rw-r--r--net/quic/quic_flags.h6
-rw-r--r--net/quic/quic_flow_controller.cc5
-rw-r--r--net/quic/quic_framer.cc70
-rw-r--r--net/quic/quic_headers_stream.cc51
-rw-r--r--net/quic/quic_headers_stream.h36
-rw-r--r--net/quic/quic_headers_stream_test.cc80
-rw-r--r--net/quic/quic_packet_creator.cc114
-rw-r--r--net/quic/quic_packet_creator_test.cc52
-rw-r--r--net/quic/quic_packet_generator.cc9
-rw-r--r--net/quic/quic_packet_generator_test.cc1
-rw-r--r--net/quic/quic_protocol.cc4
-rw-r--r--net/quic/quic_protocol.h11
-rw-r--r--net/quic/quic_sent_packet_manager.cc41
-rw-r--r--net/quic/quic_sent_packet_manager.h9
-rw-r--r--net/quic/quic_session.cc42
-rw-r--r--net/quic/quic_session.h49
-rw-r--r--net/quic/quic_session_test.cc1
-rw-r--r--net/quic/quic_spdy_session.cc14
-rw-r--r--net/quic/quic_spdy_session.h12
-rw-r--r--net/quic/quic_spdy_stream.cc14
-rw-r--r--net/quic/quic_spdy_stream.h12
-rw-r--r--net/quic/quic_stream_sequencer.cc7
-rw-r--r--net/quic/quic_unacked_packet_map.cc10
-rw-r--r--net/quic/quic_unacked_packet_map_test.cc6
-rw-r--r--net/quic/quic_utils.cc1
-rw-r--r--net/quic/reliable_quic_stream.cc10
-rw-r--r--net/quic/spdy_utils.cc28
-rw-r--r--net/quic/spdy_utils.h8
-rw-r--r--net/quic/spdy_utils_test.cc140
-rw-r--r--net/quic/stream_sequencer_buffer.cc3
-rw-r--r--net/quic/test_tools/quic_connection_peer.cc5
-rw-r--r--net/quic/test_tools/quic_connection_peer.h2
-rw-r--r--net/quic/test_tools/quic_test_utils.cc29
-rw-r--r--net/quic/test_tools/quic_test_utils.h23
-rw-r--r--net/tools/quic/end_to_end_test.cc30
-rw-r--r--net/tools/quic/quic_client_session.h5
-rw-r--r--net/tools/quic/quic_dispatcher.cc10
-rw-r--r--net/tools/quic/quic_in_memory_cache.cc5
-rw-r--r--net/tools/quic/quic_packet_reader.cc3
-rw-r--r--net/tools/quic/quic_simple_server_session.cc132
-rw-r--r--net/tools/quic/quic_simple_server_session.h90
-rw-r--r--net/tools/quic/quic_simple_server_session_test.cc252
-rw-r--r--net/tools/quic/quic_simple_server_stream.cc26
-rw-r--r--net/tools/quic/quic_simple_server_stream_test.cc107
68 files changed, 1726 insertions, 408 deletions
diff --git a/net/net.gypi b/net/net.gypi
index 0675546..8174f97 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -327,6 +327,7 @@
'quic/quic_alarm.h',
'quic/quic_bandwidth.cc',
'quic/quic_bandwidth.h',
+ 'quic/quic_bug_tracker.h',
'quic/quic_blocked_writer_interface.h',
'quic/quic_clock.cc',
'quic/quic_clock.h',
@@ -1610,6 +1611,7 @@
'quic/quic_utils_test.cc',
'quic/quic_write_blocked_list_test.cc',
'quic/reliable_quic_stream_test.cc',
+ 'quic/spdy_utils_test.cc',
'quic/stream_sequencer_buffer_test.cc',
'quic/test_tools/crypto_test_utils.cc',
'quic/test_tools/crypto_test_utils.h',
diff --git a/net/quic/congestion_control/general_loss_algorithm_test.cc b/net/quic/congestion_control/general_loss_algorithm_test.cc
index 5e9a421..f6ea742 100644
--- a/net/quic/congestion_control/general_loss_algorithm_test.cc
+++ b/net/quic/congestion_control/general_loss_algorithm_test.cc
@@ -33,9 +33,13 @@ class GeneralLossAlgorithmTest : public ::testing::Test {
void SendDataPacket(QuicPacketNumber packet_number) {
packets_.push_back(new QuicEncryptedPacket(nullptr, kDefaultLength));
+ RetransmittableFrames* frames = new RetransmittableFrames();
+ QuicStreamFrame* frame = new QuicStreamFrame();
+ frame->stream_id = kHeadersStreamId;
+ frames->AddFrame(QuicFrame(frame));
SerializedPacket packet(kDefaultPathId, packet_number,
PACKET_1BYTE_PACKET_NUMBER, packets_.back(), 0,
- new RetransmittableFrames(), false, false);
+ frames, false, false);
unacked_packets_.AddSentPacket(&packet, 0, NOT_RETRANSMISSION, clock_.Now(),
1000, true);
}
diff --git a/net/quic/congestion_control/tcp_cubic_bytes_sender.cc b/net/quic/congestion_control/tcp_cubic_bytes_sender.cc
index 7f817eb..ef6172a 100644
--- a/net/quic/congestion_control/tcp_cubic_bytes_sender.cc
+++ b/net/quic/congestion_control/tcp_cubic_bytes_sender.cc
@@ -52,7 +52,8 @@ TcpCubicBytesSender::TcpCubicBytesSender(
initial_tcp_congestion_window_(initial_tcp_congestion_window *
kDefaultTCPMSS),
initial_max_tcp_congestion_window_(max_congestion_window *
- kDefaultTCPMSS) {}
+ kDefaultTCPMSS),
+ slow_start_large_reduction_(false) {}
TcpCubicBytesSender::~TcpCubicBytesSender() {}
@@ -75,6 +76,11 @@ void TcpCubicBytesSender::SetFromConfig(const QuicConfig& config,
min4_mode_ = true;
min_congestion_window_ = kDefaultTCPMSS;
}
+ if (config.HasReceivedConnectionOptions() &&
+ ContainsQuicTag(config.ReceivedConnectionOptions(), kSSLR)) {
+ // Slow Start Fast Exit experiment.
+ slow_start_large_reduction_ = true;
+ }
}
}
@@ -156,6 +162,12 @@ void TcpCubicBytesSender::OnPacketLost(QuicPacketNumber packet_number,
if (packet_number <= largest_sent_at_last_cutback_) {
if (last_cutback_exited_slowstart_) {
++stats_->slowstart_packets_lost;
+ if (slow_start_large_reduction_) {
+ // Reduce congestion window by 1 MSS for every loss.
+ congestion_window_ =
+ max(congestion_window_ - kDefaultTCPMSS, min_congestion_window_);
+ slowstart_threshold_ = congestion_window_;
+ }
}
DVLOG(1) << "Ignoring loss for largest_missing:" << packet_number
<< " because it was sent prior to the last CWND cutback.";
@@ -169,17 +181,21 @@ void TcpCubicBytesSender::OnPacketLost(QuicPacketNumber packet_number,
prr_.OnPacketLost(bytes_in_flight);
- if (reno_) {
+ // TODO(jri): Separate out all of slow start into a separate class.
+ if (slow_start_large_reduction_) {
+ DCHECK_LT(kDefaultTCPMSS, congestion_window_);
+ congestion_window_ = congestion_window_ - kDefaultTCPMSS;
+ } else if (reno_) {
congestion_window_ = congestion_window_ * RenoBeta();
} else {
congestion_window_ =
cubic_.CongestionWindowAfterPacketLoss(congestion_window_);
}
- slowstart_threshold_ = congestion_window_;
// Enforce TCP's minimum congestion window of 2*MSS.
if (congestion_window_ < min_congestion_window_) {
congestion_window_ = min_congestion_window_;
}
+ slowstart_threshold_ = congestion_window_;
largest_sent_at_last_cutback_ = largest_sent_packet_number_;
// Reset packet count from congestion avoidance mode. We start counting again
// when we're out of recovery.
diff --git a/net/quic/congestion_control/tcp_cubic_bytes_sender.h b/net/quic/congestion_control/tcp_cubic_bytes_sender.h
index c96b178..e1b0185 100644
--- a/net/quic/congestion_control/tcp_cubic_bytes_sender.h
+++ b/net/quic/congestion_control/tcp_cubic_bytes_sender.h
@@ -140,6 +140,9 @@ class NET_EXPORT_PRIVATE TcpCubicBytesSender : public SendAlgorithmInterface {
// set when this algorithm is created.
const QuicByteCount initial_max_tcp_congestion_window_;
+ // When true, exit slow start with large cutback of congestion window.
+ bool slow_start_large_reduction_;
+
DISALLOW_COPY_AND_ASSIGN(TcpCubicBytesSender);
};
diff --git a/net/quic/congestion_control/tcp_cubic_bytes_sender_test.cc b/net/quic/congestion_control/tcp_cubic_bytes_sender_test.cc
index b2acbee..3089435 100644
--- a/net/quic/congestion_control/tcp_cubic_bytes_sender_test.cc
+++ b/net/quic/congestion_control/tcp_cubic_bytes_sender_test.cc
@@ -232,6 +232,63 @@ TEST_F(TcpCubicBytesSenderTest, SlowStartPacketLoss) {
EXPECT_FALSE(sender_->hybrid_slow_start().started());
}
+TEST_F(TcpCubicBytesSenderTest, SlowStartPacketLossWithLargeReduction) {
+ QuicConfig config;
+ QuicTagVector options;
+ options.push_back(kSSLR);
+ QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+ sender_->SetFromConfig(config, Perspective::IS_SERVER);
+
+ sender_->SetNumEmulatedConnections(1);
+ const int kNumberOfAcks = 10;
+ for (int i = 0; i < kNumberOfAcks; ++i) {
+ // Send our full send window.
+ SendAvailableSendWindow();
+ AckNPackets(2);
+ }
+ SendAvailableSendWindow();
+ QuicByteCount expected_send_window =
+ kDefaultWindowTCP + (kDefaultTCPMSS * 2 * kNumberOfAcks);
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Lose a packet to exit slow start. We should now have fallen out of
+ // slow start with a window reduced by 1.
+ LoseNPackets(1);
+ expected_send_window -= kDefaultTCPMSS;
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Lose 5 packets in recovery and verify that congestion window is reduced
+ // further.
+ LoseNPackets(5);
+ expected_send_window -= 5 * kDefaultTCPMSS;
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ size_t packets_in_recovery_window = expected_send_window / kDefaultTCPMSS;
+
+ // Recovery phase. We need to ack every packet in the recovery window before
+ // we exit recovery.
+ size_t number_of_packets_in_window = expected_send_window / kDefaultTCPMSS;
+ DVLOG(1) << "number_packets: " << number_of_packets_in_window;
+ AckNPackets(packets_in_recovery_window);
+ SendAvailableSendWindow();
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // We need to ack an entire window before we increase CWND by 1.
+ AckNPackets(number_of_packets_in_window - 1);
+ SendAvailableSendWindow();
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Next ack should increase cwnd by 1.
+ AckNPackets(1);
+ expected_send_window += kDefaultTCPMSS;
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Now RTO and ensure slow start gets reset.
+ EXPECT_TRUE(sender_->hybrid_slow_start().started());
+ sender_->OnRetransmissionTimeout(true);
+ EXPECT_FALSE(sender_->hybrid_slow_start().started());
+}
+
TEST_F(TcpCubicBytesSenderTest, NoPRRWhenLessThanOnePacketInFlight) {
SendAvailableSendWindow();
LoseNPackets(kInitialCongestionWindowPackets - 1);
diff --git a/net/quic/congestion_control/tcp_cubic_sender.cc b/net/quic/congestion_control/tcp_cubic_sender.cc
index 256126f..ac94217 100644
--- a/net/quic/congestion_control/tcp_cubic_sender.cc
+++ b/net/quic/congestion_control/tcp_cubic_sender.cc
@@ -50,7 +50,8 @@ TcpCubicSender::TcpCubicSender(const QuicClock* clock,
last_cutback_exited_slowstart_(false),
max_tcp_congestion_window_(max_tcp_congestion_window),
initial_tcp_congestion_window_(initial_tcp_congestion_window),
- initial_max_tcp_congestion_window_(max_tcp_congestion_window) {}
+ initial_max_tcp_congestion_window_(max_tcp_congestion_window),
+ slow_start_large_reduction_(false) {}
TcpCubicSender::~TcpCubicSender() {
UMA_HISTOGRAM_COUNTS("Net.QuicSession.FinalTcpCwnd", congestion_window_);
@@ -90,6 +91,11 @@ void TcpCubicSender::SetFromConfig(const QuicConfig& config,
min4_mode_ = true;
min_congestion_window_ = 1;
}
+ if (config.HasReceivedConnectionOptions() &&
+ ContainsQuicTag(config.ReceivedConnectionOptions(), kSSLR)) {
+ // Slow Start Fast Exit experiment.
+ slow_start_large_reduction_ = true;
+ }
}
}
@@ -170,6 +176,12 @@ void TcpCubicSender::OnPacketLost(QuicPacketNumber packet_number,
if (packet_number <= largest_sent_at_last_cutback_) {
if (last_cutback_exited_slowstart_) {
++stats_->slowstart_packets_lost;
+ if (slow_start_large_reduction_) {
+ // Reduce congestion window by 1 for every loss.
+ congestion_window_ =
+ max(congestion_window_ - 1, min_congestion_window_);
+ slowstart_threshold_ = congestion_window_;
+ }
}
DVLOG(1) << "Ignoring loss for largest_missing:" << packet_number
<< " because it was sent prior to the last CWND cutback.";
@@ -183,17 +195,21 @@ void TcpCubicSender::OnPacketLost(QuicPacketNumber packet_number,
prr_.OnPacketLost(bytes_in_flight);
- if (reno_) {
+ // TODO(jri): Separate out all of slow start into a separate class.
+ if (slow_start_large_reduction_) {
+ DCHECK_LT(1u, congestion_window_);
+ congestion_window_ = congestion_window_ - 1;
+ } else if (reno_) {
congestion_window_ = congestion_window_ * RenoBeta();
} else {
congestion_window_ =
cubic_.CongestionWindowAfterPacketLoss(congestion_window_);
}
- slowstart_threshold_ = congestion_window_;
// Enforce a minimum congestion window.
if (congestion_window_ < min_congestion_window_) {
congestion_window_ = min_congestion_window_;
}
+ slowstart_threshold_ = congestion_window_;
largest_sent_at_last_cutback_ = largest_sent_packet_number_;
// reset packet count from congestion avoidance mode. We start
// counting again when we're out of recovery.
diff --git a/net/quic/congestion_control/tcp_cubic_sender.h b/net/quic/congestion_control/tcp_cubic_sender.h
index 30b3f2d..9b253a3 100644
--- a/net/quic/congestion_control/tcp_cubic_sender.h
+++ b/net/quic/congestion_control/tcp_cubic_sender.h
@@ -141,6 +141,9 @@ class NET_EXPORT_PRIVATE TcpCubicSender : public SendAlgorithmInterface {
// this algorithm is created.
const QuicPacketCount initial_max_tcp_congestion_window_;
+ // When true, exit slow start with large cutback of congestion window.
+ bool slow_start_large_reduction_;
+
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 f37e505..13ae6c1 100644
--- a/net/quic/congestion_control/tcp_cubic_sender_test.cc
+++ b/net/quic/congestion_control/tcp_cubic_sender_test.cc
@@ -240,6 +240,63 @@ TEST_F(TcpCubicSenderTest, SlowStartPacketLoss) {
EXPECT_FALSE(sender_->hybrid_slow_start().started());
}
+TEST_F(TcpCubicSenderTest, SlowStartPacketLossWithLargeReduction) {
+ QuicConfig config;
+ QuicTagVector options;
+ options.push_back(kSSLR);
+ QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+ sender_->SetFromConfig(config, Perspective::IS_SERVER);
+
+ sender_->SetNumEmulatedConnections(1);
+ const int kNumberOfAcks = 10;
+ for (int i = 0; i < kNumberOfAcks; ++i) {
+ // Send our full send window.
+ SendAvailableSendWindow();
+ AckNPackets(2);
+ }
+ SendAvailableSendWindow();
+ QuicByteCount expected_send_window =
+ kDefaultWindowTCP + (kDefaultTCPMSS * 2 * kNumberOfAcks);
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Lose a packet to exit slow start. We should now have fallen out of
+ // slow start with a window reduced by 1.
+ LoseNPackets(1);
+ expected_send_window -= kDefaultTCPMSS;
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Lose 5 packets in recovery and verify that congestion window is reduced
+ // further.
+ LoseNPackets(5);
+ expected_send_window -= 5 * kDefaultTCPMSS;
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ size_t packets_in_recovery_window = expected_send_window / kDefaultTCPMSS;
+
+ // Recovery phase. We need to ack every packet in the recovery window before
+ // we exit recovery.
+ size_t number_of_packets_in_window = expected_send_window / kDefaultTCPMSS;
+ DVLOG(1) << "number_packets: " << number_of_packets_in_window;
+ AckNPackets(packets_in_recovery_window);
+ SendAvailableSendWindow();
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // We need to ack the rest of the window before cwnd increases by 1.
+ AckNPackets(number_of_packets_in_window - 1);
+ SendAvailableSendWindow();
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Next ack should increase cwnd by 1.
+ AckNPackets(1);
+ expected_send_window += kDefaultTCPMSS;
+ EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+ // Now RTO and ensure slow start gets reset.
+ EXPECT_TRUE(sender_->hybrid_slow_start().started());
+ sender_->OnRetransmissionTimeout(true);
+ EXPECT_FALSE(sender_->hybrid_slow_start().started());
+}
+
TEST_F(TcpCubicSenderTest, NoPRRWhenLessThanOnePacketInFlight) {
SendAvailableSendWindow();
LoseNPackets(kInitialCongestionWindowPackets - 1);
diff --git a/net/quic/congestion_control/tcp_loss_algorithm_test.cc b/net/quic/congestion_control/tcp_loss_algorithm_test.cc
index d8e05bf..da51adf 100644
--- a/net/quic/congestion_control/tcp_loss_algorithm_test.cc
+++ b/net/quic/congestion_control/tcp_loss_algorithm_test.cc
@@ -33,9 +33,13 @@ class TcpLossAlgorithmTest : public ::testing::Test {
void SendDataPacket(QuicPacketNumber packet_number) {
packets_.push_back(new QuicEncryptedPacket(nullptr, kDefaultLength));
+ RetransmittableFrames* frames = new RetransmittableFrames();
+ QuicStreamFrame* frame = new QuicStreamFrame();
+ frame->stream_id = kHeadersStreamId;
+ frames->AddFrame(QuicFrame(frame));
SerializedPacket packet(kDefaultPathId, packet_number,
PACKET_1BYTE_PACKET_NUMBER, packets_.back(), 0,
- new RetransmittableFrames(), false, false);
+ frames, false, false);
unacked_packets_.AddSentPacket(&packet, 0, NOT_RETRANSMISSION, clock_.Now(),
1000, true);
}
diff --git a/net/quic/crypto/crypto_protocol.h b/net/quic/crypto/crypto_protocol.h
index 1666050..1e0fcb9 100644
--- a/net/quic/crypto/crypto_protocol.h
+++ b/net/quic/crypto/crypto_protocol.h
@@ -89,12 +89,16 @@ const QuicTag kMIN4 = TAG('M', 'I', 'N', '4'); // Min CWND of 4 packets,
const QuicTag kTLPR = TAG('T', 'L', 'P', 'R'); // Tail loss probe delay of
// 0.5RTT.
const QuicTag kACKD = TAG('A', 'C', 'K', 'D'); // Ack decimation style acking.
+const QuicTag kSSLR = TAG('S', 'S', 'L', 'R'); // Slow Start Large Reduction.
// Optional support of truncated Connection IDs. If sent by a peer, the value
// is the minimum number of bytes allowed for the connection ID sent to the
// peer.
const QuicTag kTCID = TAG('T', 'C', 'I', 'D'); // Connection ID truncation.
+// Multipath option.
+const QuicTag kMPTH = TAG('M', 'P', 'T', 'H'); // Enable multipath.
+
// FEC options
const QuicTag kFHDR = TAG('F', 'H', 'D', 'R'); // FEC protect headers
const QuicTag kFSTR = TAG('F', 'S', 'T', 'R'); // FEC protect all streams
diff --git a/net/quic/crypto/crypto_server_test.cc b/net/quic/crypto/crypto_server_test.cc
index b1ea7f0..b2fa347 100644
--- a/net/quic/crypto/crypto_server_test.cc
+++ b/net/quic/crypto/crypto_server_test.cc
@@ -359,9 +359,9 @@ class CryptoServerTest : public ::testing::TestWithParam<TestParams> {
void CheckRejectTag() {
if (RejectsAreStateless()) {
- ASSERT_EQ(kSREJ, out_.tag());
+ ASSERT_EQ(kSREJ, out_.tag()) << QuicUtils::TagToString(out_.tag());
} else {
- ASSERT_EQ(kREJ, out_.tag());
+ ASSERT_EQ(kREJ, out_.tag()) << QuicUtils::TagToString(out_.tag());
}
}
@@ -745,7 +745,7 @@ TEST_P(CryptoServerTest, CorruptMultipleTags) {
ShouldSucceed(msg);
CheckRejectTag();
- if (client_version_ <= QUIC_VERSION_26) {
+ if (client_version_ <= QUIC_VERSION_30) {
const HandshakeFailureReason kRejectReasons[] = {
SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, CLIENT_NONCE_INVALID_FAILURE,
SERVER_NONCE_DECRYPTION_FAILURE};
@@ -758,9 +758,10 @@ TEST_P(CryptoServerTest, CorruptMultipleTags) {
}
TEST_P(CryptoServerTest, ReplayProtection) {
- if (client_version_ > QUIC_VERSION_26) {
+ if (client_version_ > QUIC_VERSION_30) {
return;
}
+ FLAGS_require_strike_register_or_server_nonce = false;
// This tests that disabling replay protection works.
// clang-format off
CryptoHandshakeMessage msg = CryptoTestUtils::Message(
@@ -798,6 +799,33 @@ TEST_P(CryptoServerTest, ReplayProtection) {
CheckServerHello(out_);
}
+TEST_P(CryptoServerTest, NoServerNonce) {
+ FLAGS_require_strike_register_or_server_nonce = true;
+ // When no server nonce is present and no strike register is configured,
+ // the CHLO should be rejected.
+ // clang-format off
+ CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+ "CHLO",
+ "AEAD", "AESG",
+ "KEXS", "C255",
+ "SCID", scid_hex_.c_str(),
+ "#004b5453", srct_hex_.c_str(),
+ "PUBS", pub_hex_.c_str(),
+ "NONC", nonce_hex_.c_str(),
+ "XLCT", XlctHexString().c_str(),
+ "VER\0", client_version_string_.c_str(),
+ "$padding", static_cast<int>(kClientHelloMinimumSize),
+ nullptr);
+ // clang-format on
+
+ ShouldSucceed(msg);
+
+ CheckRejectTag();
+ const HandshakeFailureReason kRejectReasons[] = {
+ SERVER_NONCE_REQUIRED_FAILURE};
+ CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
TEST_P(CryptoServerTest, ProofForSuppliedServerConfig) {
ValueRestore<bool> old_flag(&FLAGS_quic_use_primary_config_for_proof, true);
client_address_ = IPEndPoint(Loopback6(), 1234);
diff --git a/net/quic/crypto/quic_crypto_client_config.cc b/net/quic/crypto/quic_crypto_client_config.cc
index 9268ae5..febb1a0 100644
--- a/net/quic/crypto/quic_crypto_client_config.cc
+++ b/net/quic/crypto/quic_crypto_client_config.cc
@@ -18,6 +18,7 @@
#include "net/quic/crypto/p256_key_exchange.h"
#include "net/quic/crypto/proof_verifier.h"
#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_utils.h"
using base::StringPiece;
@@ -339,7 +340,7 @@ void QuicCryptoClientConfig::CachedState::InitializeFrom(
QuicConnectionId
QuicCryptoClientConfig::CachedState::GetNextServerDesignatedConnectionId() {
if (server_designated_connection_ids_.empty()) {
- LOG(DFATAL)
+ QUIC_BUG
<< "Attempting to consume a connection id that was never designated.";
return 0;
}
@@ -350,7 +351,7 @@ QuicCryptoClientConfig::CachedState::GetNextServerDesignatedConnectionId() {
string QuicCryptoClientConfig::CachedState::GetNextServerNonce() {
if (server_nonces_.empty()) {
- LOG(DFATAL)
+ QUIC_BUG
<< "Attempting to consume a server nonce that was never designated.";
return "";
}
diff --git a/net/quic/crypto/quic_crypto_server_config.cc b/net/quic/crypto/quic_crypto_server_config.cc
index c7b83bf..f2475ba 100644
--- a/net/quic/crypto/quic_crypto_server_config.cc
+++ b/net/quic/crypto/quic_crypto_server_config.cc
@@ -1090,8 +1090,8 @@ void QuicCryptoServerConfig::EvaluateClientHello(
// Server nonce is optional, and used for key derivation if present.
client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce);
- if (version > QUIC_VERSION_26) {
- DVLOG(1) << "No 0-RTT replay protection in QUIC_VERSION_27 and higher.";
+ if (version > QUIC_VERSION_30) {
+ DVLOG(1) << "No 0-RTT replay protection in QUIC_VERSION_31 and higher.";
// If the server nonce is empty and we're requiring handshake confirmation
// for DoS reasons then we must reject the CHLO.
if (FLAGS_quic_require_handshake_confirmation &&
@@ -1142,17 +1142,28 @@ void QuicCryptoServerConfig::EvaluateClientHello(
base::AutoLock locked(strike_register_client_lock_);
if (strike_register_client_.get() == nullptr) {
- strike_register_client_.reset(new LocalStrikeRegisterClient(
- strike_register_max_entries_,
- static_cast<uint32_t>(info->now.ToUNIXSeconds()),
- strike_register_window_secs_, primary_orbit,
- strike_register_no_startup_period_
- ? StrikeRegister::NO_STARTUP_PERIOD_NEEDED
- : StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+ if (!FLAGS_require_strike_register_or_server_nonce) {
+ strike_register_client_.reset(new LocalStrikeRegisterClient(
+ strike_register_max_entries_,
+ static_cast<uint32_t>(info->now.ToUNIXSeconds()),
+ strike_register_window_secs_, primary_orbit,
+ strike_register_no_startup_period_
+ ? StrikeRegister::NO_STARTUP_PERIOD_NEEDED
+ : StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+ }
}
strike_register_client = strike_register_client_.get();
}
+ if (!strike_register_client) {
+ // Either a valid server nonces or a strike register is required.
+ // Since neither are present, reject the handshake which will send a
+ // server nonce to the client.
+ info->reject_reasons.push_back(SERVER_NONCE_REQUIRED_FAILURE);
+ helper.ValidationComplete(QUIC_NO_ERROR, "");
+ return;
+ }
+
strike_register_client->VerifyNonceIsValidAndUnique(
info->client_nonce, info->now,
new VerifyNonceIsValidAndUniqueCallback(client_hello_state, done_cb));
diff --git a/net/quic/quic_bug_tracker.h b/net/quic/quic_bug_tracker.h
new file mode 100644
index 0000000..bbec94b
--- /dev/null
+++ b/net/quic/quic_bug_tracker.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2016 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_QUIC_BUG_TRACKER_H_
+#define NET_QUIC_QUIC_BUG_TRACKER_H_
+
+// For external QUIC, QUIC_BUG should be #defined to LOG(DFATAL) as client-side
+// log rate limiting is less important and chrome doesn't LOG_FIRST_N anyway.
+#define QUIC_BUG LOG(DFATAL)
+#endif // NET_QUIC_QUIC_BUG_TRACKER_H_
diff --git a/net/quic/quic_client_session_base.cc b/net/quic/quic_client_session_base.cc
index 740263d..5ff7d12 100644
--- a/net/quic/quic_client_session_base.cc
+++ b/net/quic/quic_client_session_base.cc
@@ -28,4 +28,35 @@ void QuicClientSessionBase::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
headers_stream()->set_fec_policy(FEC_PROTECT_ALWAYS);
}
+void QuicClientSessionBase::OnPromiseHeaders(QuicStreamId stream_id,
+ StringPiece headers_data) {
+ QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnPromiseHeaders(headers_data);
+}
+
+void QuicClientSessionBase::OnPromiseHeadersComplete(
+ QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len) {
+ if (promised_stream_id != kInvalidStreamId &&
+ promised_stream_id <= largest_promised_stream_id_) {
+ CloseConnectionWithDetails(QUIC_INVALID_STREAM_ID,
+ "Received push stream id lesser or equal to the"
+ " last accepted before");
+ return;
+ }
+ largest_promised_stream_id_ = promised_stream_id;
+
+ QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnPromiseHeadersComplete(promised_stream_id, frame_len);
+}
+
} // namespace net
diff --git a/net/quic/quic_client_session_base.h b/net/quic/quic_client_session_base.h
index ba7be85..ae65ab0 100644
--- a/net/quic/quic_client_session_base.h
+++ b/net/quic/quic_client_session_base.h
@@ -33,7 +33,21 @@ class NET_EXPORT_PRIVATE QuicClientSessionBase : public QuicSpdySession {
// Override base class to set FEC policy before any data is sent by client.
void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+ // Called by |headers_stream_| when push promise headers have been
+ // received for a stream.
+ void OnPromiseHeaders(QuicStreamId stream_id,
+ StringPiece headers_data) override;
+
+ // Called by |headers_stream_| when push promise headers have been
+ // completely received. |fin| will be true if the fin flag was set
+ // in the headers.
+ void OnPromiseHeadersComplete(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len) override;
+
private:
+ QuicStreamId largest_promised_stream_id_;
+
DISALLOW_COPY_AND_ASSIGN(QuicClientSessionBase);
};
diff --git a/net/quic/quic_config.cc b/net/quic/quic_config.cc
index 51e0a9a..7dc93e5 100644
--- a/net/quic/quic_config.cc
+++ b/net/quic/quic_config.cc
@@ -343,7 +343,8 @@ QuicConfig::QuicConfig()
initial_round_trip_time_us_(kIRTT, PRESENCE_OPTIONAL),
initial_stream_flow_control_window_bytes_(kSFCW, PRESENCE_OPTIONAL),
initial_session_flow_control_window_bytes_(kCFCW, PRESENCE_OPTIONAL),
- socket_receive_buffer_(kSRBF, PRESENCE_OPTIONAL) {
+ socket_receive_buffer_(kSRBF, PRESENCE_OPTIONAL),
+ multipath_enabled_(kMPTH, PRESENCE_OPTIONAL) {
SetDefaults();
}
@@ -520,6 +521,15 @@ uint32_t QuicConfig::ReceivedSocketReceiveBuffer() const {
return socket_receive_buffer_.GetReceivedValue();
}
+void QuicConfig::SetMultipathEnabled(bool multipath_enabled) {
+ uint32_t value = multipath_enabled ? 1 : 0;
+ multipath_enabled_.set(value, value);
+}
+
+bool QuicConfig::MultipathEnabled() const {
+ return multipath_enabled_.GetUint32() > 0;
+}
+
bool QuicConfig::negotiated() const {
// TODO(ianswett): Add the negotiated parameters once and iterate over all
// of them in negotiated, ToHandshakeMessage, ProcessClientHello, and
diff --git a/net/quic/quic_config.h b/net/quic/quic_config.h
index 3609f2d..1fbb4b7 100644
--- a/net/quic/quic_config.h
+++ b/net/quic/quic_config.h
@@ -75,6 +75,8 @@ class NET_EXPORT_PRIVATE QuicNegotiableValue : public QuicConfigValue {
};
class NET_EXPORT_PRIVATE QuicNegotiableUint32 : public QuicNegotiableValue {
+ // TODO(fayang): some negotiated values use uint32 as bool (e.g., silent
+ // close). Consider adding a QuicNegotiableBool type.
public:
// Default and max values default to 0.
QuicNegotiableUint32(QuicTag name, QuicConfigPresence presence);
@@ -327,6 +329,10 @@ class NET_EXPORT_PRIVATE QuicConfig {
uint32_t ReceivedSocketReceiveBuffer() const;
+ void SetMultipathEnabled(bool multipath_enabled);
+
+ bool MultipathEnabled() const;
+
bool negotiated() const;
// ToHandshakeMessage serialises the settings in this object as a series of
@@ -373,6 +379,9 @@ class NET_EXPORT_PRIVATE QuicConfig {
// Socket receive buffer in bytes.
QuicFixedUint32 socket_receive_buffer_;
+
+ // Whether to support multipath for this connection.
+ QuicNegotiableUint32 multipath_enabled_;
};
} // namespace net
diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc
index a3298bc..6eabc9d 100644
--- a/net/quic/quic_connection.cc
+++ b/net/quic/quic_connection.cc
@@ -26,6 +26,7 @@
#include "net/quic/crypto/quic_encrypter.h"
#include "net/quic/proto/cached_network_parameters.pb.h"
#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_config.h"
#include "net/quic/quic_fec_group.h"
#include "net/quic/quic_flags.h"
@@ -230,19 +231,6 @@ class MtuDiscoveryAckListener : public QuicAckListenerInterface {
} // namespace
-QuicConnection::QueuedPacket::QueuedPacket(SerializedPacket packet)
- : serialized_packet(packet),
- transmission_type(NOT_RETRANSMISSION),
- original_packet_number(0) {}
-
-QuicConnection::QueuedPacket::QueuedPacket(
- SerializedPacket packet,
- TransmissionType transmission_type,
- QuicPacketNumber original_packet_number)
- : serialized_packet(packet),
- transmission_type(transmission_type),
- original_packet_number(original_packet_number) {}
-
#define ENDPOINT \
(perspective_ == Perspective::IS_SERVER ? "Server: " : "Client: ")
@@ -328,7 +316,8 @@ QuicConnection::QuicConnection(QuicConnectionId connection_id,
next_mtu_probe_at_(kPacketsBetweenMtuProbesBase),
largest_received_packet_size_(0),
goaway_sent_(false),
- goaway_received_(false) {
+ goaway_received_(false),
+ multipath_enabled_(false) {
DVLOG(1) << ENDPOINT
<< "Created connection with connection_id: " << connection_id;
framer_.set_visitor(this);
@@ -358,8 +347,8 @@ QuicConnection::~QuicConnection() {
void QuicConnection::ClearQueuedPackets() {
for (QueuedPacketList::iterator it = queued_packets_.begin();
it != queued_packets_.end(); ++it) {
- delete it->serialized_packet.retransmittable_frames;
- delete it->serialized_packet.packet;
+ delete it->retransmittable_frames;
+ delete it->packet;
}
queued_packets_.clear();
}
@@ -371,6 +360,9 @@ void QuicConnection::SetFromConfig(const QuicConfig& config) {
if (config.SilentClose()) {
silent_close_enabled_ = true;
}
+ if (FLAGS_quic_enable_multipath && config.MultipathEnabled()) {
+ multipath_enabled_ = true;
+ }
} else {
SetNetworkTimeouts(config.max_time_before_crypto_handshake(),
config.max_idle_time_before_crypto_handshake());
@@ -1118,7 +1110,7 @@ QuicConsumedData QuicConnection::SendStreamData(
FecProtection fec_protection,
QuicAckListenerInterface* listener) {
if (!fin && iov.total_length == 0) {
- LOG(DFATAL) << "Attempt to send empty stream frame";
+ QUIC_BUG << "Attempt to send empty stream frame";
return QuicConsumedData(0, false);
}
@@ -1160,7 +1152,7 @@ void QuicConnection::SendRstStream(QuicStreamId id,
QueuedPacketList::iterator packet_iterator = queued_packets_.begin();
while (packet_iterator != queued_packets_.end()) {
RetransmittableFrames* retransmittable_frames =
- packet_iterator->serialized_packet.retransmittable_frames;
+ packet_iterator->retransmittable_frames;
if (!retransmittable_frames) {
++packet_iterator;
continue;
@@ -1170,10 +1162,10 @@ void QuicConnection::SendRstStream(QuicStreamId id,
++packet_iterator;
continue;
}
- delete packet_iterator->serialized_packet.retransmittable_frames;
- delete packet_iterator->serialized_packet.packet;
- packet_iterator->serialized_packet.retransmittable_frames = nullptr;
- packet_iterator->serialized_packet.packet = nullptr;
+ delete packet_iterator->retransmittable_frames;
+ delete packet_iterator->packet;
+ packet_iterator->retransmittable_frames = nullptr;
+ packet_iterator->packet = nullptr;
packet_iterator = queued_packets_.erase(packet_iterator);
}
}
@@ -1470,14 +1462,17 @@ void QuicConnection::WritePendingRetransmissions() {
if (serialized_packet.packet == nullptr) {
// We failed to serialize the packet, so close the connection.
// CloseConnection does not send close packet, so no infinite loop here.
+ // TODO(ianswett): This is actually an internal error, not an encryption
+ // failure.
CloseConnection(QUIC_ENCRYPTION_FAILURE, false);
return;
}
DVLOG(1) << ENDPOINT << "Retransmitting " << pending.packet_number << " as "
<< serialized_packet.packet_number;
- SendOrQueuePacket(QueuedPacket(serialized_packet, pending.transmission_type,
- pending.packet_number));
+ serialized_packet.original_packet_number = pending.packet_number;
+ serialized_packet.transmission_type = pending.transmission_type;
+ SendOrQueuePacket(&serialized_packet);
}
}
@@ -1550,23 +1545,21 @@ bool QuicConnection::CanWrite(HasRetransmittableData retransmittable) {
return true;
}
-bool QuicConnection::WritePacket(QueuedPacket* packet) {
+bool QuicConnection::WritePacket(SerializedPacket* packet) {
if (!WritePacketInner(packet)) {
return false;
}
- delete packet->serialized_packet.retransmittable_frames;
- delete packet->serialized_packet.packet;
- packet->serialized_packet.retransmittable_frames = nullptr;
- packet->serialized_packet.packet = nullptr;
+ delete packet->retransmittable_frames;
+ delete packet->packet;
+ packet->retransmittable_frames = nullptr;
+ packet->packet = nullptr;
return true;
}
-bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
- if (packet->serialized_packet.packet_number <
- sent_packet_manager_.largest_sent_packet()) {
- LOG(DFATAL) << "Attempt to write packet:"
- << packet->serialized_packet.packet_number
- << " after:" << sent_packet_manager_.largest_sent_packet();
+bool QuicConnection::WritePacketInner(SerializedPacket* packet) {
+ if (packet->packet_number < sent_packet_manager_.largest_sent_packet()) {
+ QUIC_BUG << "Attempt to write packet:" << packet->packet_number
+ << " after:" << sent_packet_manager_.largest_sent_packet();
SendConnectionCloseWithDetails(QUIC_INTERNAL_ERROR,
"Packet written out of order.");
return true;
@@ -1581,11 +1574,11 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
return false;
}
- QuicPacketNumber packet_number = packet->serialized_packet.packet_number;
+ QuicPacketNumber packet_number = packet->packet_number;
DCHECK_LE(packet_number_of_last_sent_packet_, packet_number);
packet_number_of_last_sent_packet_ = packet_number;
- QuicEncryptedPacket* encrypted = packet->serialized_packet.packet;
+ QuicEncryptedPacket* encrypted = packet->packet;
// Termination packets are eventually owned by TimeWaitListManager.
// Others are deleted at the end of this call.
if (is_termination_packet) {
@@ -1605,14 +1598,13 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
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
+ << (packet->is_fec_packet
? "FEC "
: (IsRetransmittable(*packet) == HAS_RETRANSMITTABLE_DATA
? "data bearing "
: " ack only "))
<< ", encryption level: "
- << QuicUtils::EncryptionLevelToString(
- packet->serialized_packet.encryption_level)
+ << QuicUtils::EncryptionLevelToString(packet->encryption_level)
<< ", encrypted length:" << encrypted->length();
DVLOG(2) << ENDPOINT << "packet(" << packet_number << "): " << std::endl
<< QuicUtils::StringToHexASCIIDump(encrypted->AsStringPiece());
@@ -1640,9 +1632,9 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
}
if (result.status != WRITE_STATUS_ERROR && debug_visitor_ != nullptr) {
// Pass the write result to the visitor.
- debug_visitor_->OnPacketSent(
- packet->serialized_packet, packet->original_packet_number,
- packet->transmission_type, encrypted->length(), packet_send_time);
+ debug_visitor_->OnPacketSent(*packet, packet->original_packet_number,
+ packet->transmission_type, encrypted->length(),
+ packet_send_time);
}
if (packet->transmission_type == NOT_RETRANSMISSION) {
time_of_last_sent_new_packet_ = packet_send_time;
@@ -1665,8 +1657,8 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
sent_packet_manager_.EstimateMaxPacketsInFlight(max_packet_length()));
bool reset_retransmission_alarm = sent_packet_manager_.OnPacketSent(
- &packet->serialized_packet, packet->original_packet_number,
- packet_send_time, encrypted->length(), packet->transmission_type,
+ packet, packet->original_packet_number, packet_send_time,
+ encrypted->length(), packet->transmission_type,
IsRetransmittable(*packet));
if (reset_retransmission_alarm || !retransmission_alarm_->IsSet()) {
@@ -1694,15 +1686,15 @@ bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
return true;
}
-bool QuicConnection::ShouldDiscardPacket(const QueuedPacket& packet) {
+bool QuicConnection::ShouldDiscardPacket(const SerializedPacket& packet) {
if (!connected_) {
DVLOG(1) << ENDPOINT << "Not sending packet as connection is disconnected.";
return true;
}
- QuicPacketNumber packet_number = packet.serialized_packet.packet_number;
+ QuicPacketNumber packet_number = packet.packet_number;
if (encryption_level_ == ENCRYPTION_FORWARD_SECURE &&
- packet.serialized_packet.encryption_level == ENCRYPTION_NONE) {
+ packet.encryption_level == ENCRYPTION_NONE) {
// Drop packets that are NULL encrypted since the peer won't accept them
// anymore.
DVLOG(1) << ENDPOINT << "Dropping NULL encrypted packet: " << packet_number
@@ -1736,6 +1728,8 @@ void QuicConnection::OnSerializedPacket(SerializedPacket* serialized_packet) {
if (serialized_packet->packet == nullptr) {
// We failed to serialize the packet, so close the connection.
// CloseConnection does not send close packet, so no infinite loop here.
+ // TODO(ianswett): This is actually an internal error, not an encryption
+ // failure.
CloseConnection(QUIC_ENCRYPTION_FAILURE, false);
return;
}
@@ -1743,7 +1737,7 @@ void QuicConnection::OnSerializedPacket(SerializedPacket* serialized_packet) {
// If an FEC packet is serialized with the FEC alarm set, cancel the alarm.
fec_alarm_->Cancel();
}
- SendOrQueuePacket(QueuedPacket(*serialized_packet));
+ SendOrQueuePacket(serialized_packet);
}
void QuicConnection::OnResetFecGroup() {
@@ -1783,28 +1777,24 @@ void QuicConnection::OnHandshakeComplete() {
}
}
-void QuicConnection::SendOrQueuePacket(QueuedPacket packet) {
+void QuicConnection::SendOrQueuePacket(SerializedPacket* packet) {
// The caller of this function is responsible for checking CanWrite().
- if (packet.serialized_packet.packet == nullptr) {
- LOG(DFATAL)
- << "packet.serialized_packet.packet == nullptr in to SendOrQueuePacket";
+ if (packet->packet == nullptr) {
+ QUIC_BUG << "packet.packet == nullptr in to SendOrQueuePacket";
return;
}
- sent_entropy_manager_.RecordPacketEntropyHash(
- packet.serialized_packet.packet_number,
- packet.serialized_packet.entropy_hash);
+ sent_entropy_manager_.RecordPacketEntropyHash(packet->packet_number,
+ packet->entropy_hash);
// If there are already queued packets, queue this one immediately to ensure
// it's written in sequence number order.
- if (!queued_packets_.empty() || !WritePacket(&packet)) {
+ if (!queued_packets_.empty() || !WritePacket(packet)) {
// Take ownership of the underlying encrypted packet.
- if (!packet.serialized_packet.packet->owns_buffer()) {
- scoped_ptr<QuicEncryptedPacket> encrypted_deleter(
- packet.serialized_packet.packet);
- packet.serialized_packet.packet =
- packet.serialized_packet.packet->Clone();
+ if (!packet->packet->owns_buffer()) {
+ scoped_ptr<QuicEncryptedPacket> encrypted_deleter(packet->packet);
+ packet->packet = packet->packet->Clone();
}
- queued_packets_.push_back(packet);
+ queued_packets_.push_back(*packet);
}
// If a forward-secure encrypter is available but is not being used and the
@@ -1812,8 +1802,7 @@ void QuicConnection::SendOrQueuePacket(QueuedPacket packet) {
// forward security, start using the forward-secure encrypter.
if (encryption_level_ != ENCRYPTION_FORWARD_SECURE &&
has_forward_secure_encrypter_ &&
- packet.serialized_packet.packet_number >=
- first_required_forward_secure_packet_ - 1) {
+ packet->packet_number >= first_required_forward_secure_packet_ - 1) {
SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
}
}
@@ -2323,24 +2312,22 @@ QuicConnection::ScopedRetransmissionScheduler::
}
HasRetransmittableData QuicConnection::IsRetransmittable(
- const QueuedPacket& packet) {
+ const SerializedPacket& packet) {
// Retransmitted packets retransmittable frames are owned by the unacked
// packet map, but are not present in the serialized packet.
if (packet.transmission_type != NOT_RETRANSMISSION ||
- packet.serialized_packet.retransmittable_frames != nullptr) {
+ packet.retransmittable_frames != nullptr) {
return HAS_RETRANSMITTABLE_DATA;
} else {
return NO_RETRANSMITTABLE_DATA;
}
}
-bool QuicConnection::IsTerminationPacket(const QueuedPacket& packet) {
- const RetransmittableFrames* retransmittable_frames =
- packet.serialized_packet.retransmittable_frames;
- if (retransmittable_frames == nullptr) {
+bool QuicConnection::IsTerminationPacket(const SerializedPacket& packet) {
+ if (packet.retransmittable_frames == nullptr) {
return false;
}
- for (const QuicFrame& frame : retransmittable_frames->frames()) {
+ for (const QuicFrame& frame : packet.retransmittable_frames->frames()) {
if (frame.type == CONNECTION_CLOSE_FRAME) {
return true;
}
diff --git a/net/quic/quic_connection.h b/net/quic/quic_connection.h
index 4b23ba9..f84be90 100644
--- a/net/quic/quic_connection.h
+++ b/net/quic/quic_connection.h
@@ -327,12 +327,12 @@ class NET_EXPORT_PRIVATE QuicConnection
// If |listener| is provided, then it will be informed once ACKs have been
// received for all the packets written in this call.
// The |listener| is not owned by the QuicConnection and must outlive it.
- QuicConsumedData SendStreamData(QuicStreamId id,
- QuicIOVector iov,
- QuicStreamOffset offset,
- bool fin,
- FecProtection fec_protection,
- QuicAckListenerInterface* listener);
+ virtual QuicConsumedData SendStreamData(QuicStreamId id,
+ QuicIOVector iov,
+ QuicStreamOffset offset,
+ bool fin,
+ FecProtection fec_protection,
+ QuicAckListenerInterface* listener);
// Send a RST_STREAM frame to the peer.
virtual void SendRstStream(QuicStreamId id,
@@ -635,20 +635,6 @@ class NET_EXPORT_PRIVATE QuicConnection
bool ack_frame_updated() const;
protected:
- // Packets which have not been written to the wire.
- struct QueuedPacket {
- explicit QueuedPacket(SerializedPacket packet);
- QueuedPacket(SerializedPacket packet,
- TransmissionType transmission_type,
- QuicPacketNumber original_packet_number);
-
- SerializedPacket serialized_packet;
- TransmissionType transmission_type;
- // The packet's original packet number if it is a retransmission.
- // Otherwise it must be 0.
- QuicPacketNumber original_packet_number;
- };
-
// Do any work which logically would be done in OnPacket but can not be
// safely done until the packet is validated. Returns true if the packet
// can be handled, false otherwise. Also migrates the connection if the packet
@@ -657,7 +643,7 @@ class NET_EXPORT_PRIVATE QuicConnection
// Send a packet to the peer, and takes ownership of the packet if the packet
// cannot be written immediately.
- virtual void SendOrQueuePacket(QueuedPacket packet);
+ virtual void SendOrQueuePacket(SerializedPacket* packet);
QuicConnectionHelperInterface* helper() { return helper_; }
@@ -681,7 +667,7 @@ class NET_EXPORT_PRIVATE QuicConnection
friend class test::QuicConnectionPeer;
friend class test::PacketSavingConnection;
- typedef std::list<QueuedPacket> QueuedPacketList;
+ typedef std::list<SerializedPacket> QueuedPacketList;
typedef std::map<QuicFecGroupNumber, QuicFecGroup*> FecGroupMap;
// Writes the given packet to socket, encrypted with packet's
@@ -691,11 +677,11 @@ class NET_EXPORT_PRIVATE QuicConnection
// retransmittable frames to nullptr.
// Saves the connection close packet for later transmission, even if the
// writer is write blocked.
- bool WritePacket(QueuedPacket* packet);
+ bool WritePacket(SerializedPacket* packet);
// Does the main work of WritePacket, but does not delete the packet or
// retransmittable frames upon success.
- bool WritePacketInner(QueuedPacket* packet);
+ bool WritePacketInner(SerializedPacket* packet);
// Make sure an ack we got from our peer is sane.
// Returns nullptr for valid acks or an error std::string if it was invalid.
@@ -713,7 +699,7 @@ class NET_EXPORT_PRIVATE QuicConnection
// Clears any accumulated frames from the last received packet.
void ClearLastFrames();
- // Deletes and clears any QueuedPackets.
+ // Deletes and clears any queued packets.
void ClearQueuedPackets();
// Closes the connection if the sent or received packet manager are tracking
@@ -728,7 +714,7 @@ class NET_EXPORT_PRIVATE QuicConnection
void WritePendingRetransmissions();
// Returns true if the packet should be discarded and not sent.
- bool ShouldDiscardPacket(const QueuedPacket& packet);
+ bool ShouldDiscardPacket(const SerializedPacket& packet);
// Queues |packet| in the hopes that it can be decrypted in the
// future, when a new key is installed.
@@ -781,8 +767,8 @@ class NET_EXPORT_PRIVATE QuicConnection
void CheckForAddressMigration(const IPEndPoint& self_address,
const IPEndPoint& peer_address);
- HasRetransmittableData IsRetransmittable(const QueuedPacket& packet);
- bool IsTerminationPacket(const QueuedPacket& packet);
+ HasRetransmittableData IsRetransmittable(const SerializedPacket& packet);
+ bool IsTerminationPacket(const SerializedPacket& packet);
// Set the size of the packet we are targeting while doing path MTU discovery.
void SetMtuDiscoveryTarget(QuicByteCount target);
@@ -993,6 +979,9 @@ class NET_EXPORT_PRIVATE QuicConnection
// Whether a GoAway has been received.
bool goaway_received_;
+ // If true, multipath is enabled for this connection.
+ bool multipath_enabled_;
+
DISALLOW_COPY_AND_ASSIGN(QuicConnection);
};
diff --git a/net/quic/quic_connection_test.cc b/net/quic/quic_connection_test.cc
index dc0447a..2f06d71 100644
--- a/net/quic/quic_connection_test.cc
+++ b/net/quic/quic_connection_test.cc
@@ -694,6 +694,7 @@ class QuicConnectionTest : public ::testing::TestWithParam<TestParams> {
frame2_(1, false, 3, StringPiece(data2)),
packet_number_length_(PACKET_6BYTE_PACKET_NUMBER),
connection_id_length_(PACKET_8BYTE_CONNECTION_ID) {
+ FLAGS_quic_always_log_bugs_for_tests = true;
connection_.set_visitor(&visitor_);
connection_.SetSendAlgorithm(send_algorithm_);
connection_.SetLossAlgorithm(loss_algorithm_);
@@ -737,6 +738,7 @@ class QuicConnectionTest : public ::testing::TestWithParam<TestParams> {
// TODO(ianswett): Fix QuicConnectionTests so they don't attempt to write
// non-crypto stream data at ENCRYPTION_NONE.
FLAGS_quic_never_write_unencrypted_data = false;
+ FLAGS_quic_no_unencrypted_fec = false;
}
QuicVersion version() { return GetParam().version; }
@@ -5570,6 +5572,32 @@ TEST_P(QuicConnectionTest, SendingUnencryptedStreamDataFails) {
EXPECT_FALSE(connection_.connected());
}
+TEST_P(QuicConnectionTest, EnableMultipathNegotiation) {
+ // Test multipath negotiation during crypto handshake. Multipath is enabled
+ // when both endpoints enable multipath.
+ ValueRestore<bool> old_flag(&FLAGS_quic_enable_multipath, true);
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_FALSE(QuicConnectionPeer::IsMultipathEnabled(&connection_));
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ QuicConfig config;
+ // Enable multipath on server side.
+ config.SetMultipathEnabled(true);
+
+ // Create a handshake message enables multipath.
+ CryptoHandshakeMessage msg;
+ string error_details;
+ QuicConfig client_config;
+ // Enable multipath on client side.
+ client_config.SetMultipathEnabled(true);
+ client_config.ToHandshakeMessage(&msg);
+ const QuicErrorCode error =
+ config.ProcessPeerHello(msg, CLIENT, &error_details);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+
+ connection_.SetFromConfig(config);
+ EXPECT_TRUE(QuicConnectionPeer::IsMultipathEnabled(&connection_));
+}
+
} // namespace
} // namespace test
} // namespace net
diff --git a/net/quic/quic_crypto_client_stream.cc b/net/quic/quic_crypto_client_stream.cc
index acf0035..1b9e20d 100644
--- a/net/quic/quic_crypto_client_stream.cc
+++ b/net/quic/quic_crypto_client_stream.cc
@@ -300,10 +300,8 @@ void QuicCryptoClientStream::DoSendCHLO(
// inchoate or subsequent hello.
session()->config()->ToHandshakeMessage(&out);
- // This block and function should be removed after removing QUIC_VERSION_25.
- if (FLAGS_quic_require_fix) {
- AppendFixed(&out);
- }
+ // This call and function should be removed after removing QUIC_VERSION_25.
+ AppendFixed(&out);
if (!cached->IsComplete(session()->connection()->clock()->WallNow())) {
crypto_config_->FillInchoateClientHello(
diff --git a/net/quic/quic_crypto_server_stream_test.cc b/net/quic/quic_crypto_server_stream_test.cc
index bded756..f783bef 100644
--- a/net/quic/quic_crypto_server_stream_test.cc
+++ b/net/quic/quic_crypto_server_stream_test.cc
@@ -145,7 +145,7 @@ class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> {
}
bool AsyncStrikeRegisterVerification() {
- if (server_connection_->version() > QUIC_VERSION_26) {
+ if (server_connection_->version() > QUIC_VERSION_30) {
return false;
}
return GetParam();
@@ -377,7 +377,12 @@ TEST_P(QuicCryptoServerStreamTest, ZeroRTT) {
server_stream());
}
- EXPECT_EQ(1, client_stream()->num_sent_client_hellos());
+ if (FLAGS_require_strike_register_or_server_nonce &&
+ !AsyncStrikeRegisterVerification()) {
+ EXPECT_EQ(2, client_stream()->num_sent_client_hellos());
+ } else {
+ EXPECT_EQ(1, client_stream()->num_sent_client_hellos());
+ }
}
TEST_P(QuicCryptoServerStreamTest, MessageAfterHandshake) {
diff --git a/net/quic/quic_flags.cc b/net/quic/quic_flags.cc
index e330230..86707d6 100644
--- a/net/quic/quic_flags.cc
+++ b/net/quic/quic_flags.cc
@@ -14,7 +14,7 @@ bool FLAGS_use_early_return_when_verifying_chlo = true;
// If true, QUIC connections will support FEC protection of data while sending
// packets, to reduce latency of data delivery to the application. The client
// must also request FEC protection for the server to use FEC.
-bool FLAGS_enable_quic_fec = false;
+bool FLAGS_enable_quic_fec = true;
// When true, defaults to BBR congestion control instead of Cubic.
bool FLAGS_quic_use_bbr_congestion_control = false;
@@ -45,9 +45,15 @@ bool FLAGS_enable_quic_stateless_reject_support = true;
// client connection option ACKD.
bool FLAGS_quic_ack_decimation = true;
+// This flag is not in use, just to keep consistency for shared code.
+bool FLAGS_quic_always_log_bugs_for_tests = false;
+
// If true, flow controller may grow the receive window size if necessary.
bool FLAGS_quic_auto_tune_receive_window = true;
+// If true, multipath is enabled for the connection.
+bool FLAGS_quic_enable_multipath = false;
+
// Limits QUIC's max CWND to 200 packets.
bool FLAGS_quic_limit_max_cwnd = true;
@@ -106,12 +112,19 @@ bool FLAGS_quic_block_unencrypted_writes = true;
// If true, Close the connection instead of writing unencrypted stream data.
bool FLAGS_quic_never_write_unencrypted_data = true;
+// If true, clear the FEC group instead of sending it with ENCRYPTION_NONE.
+// Close the connection if we ever try to serialized unencrypted FEC.
+bool FLAGS_quic_no_unencrypted_fec = true;
+
// If true, reject any incoming QUIC which does not have the FIXD tag.
bool FLAGS_quic_require_fix = true;
// If true, QUIC supports sending trailers from Server to Client.
bool FLAGS_quic_supports_trailers = true;
+// If true, headers stream will support receiving PUSH_PROMISE frames.
+bool FLAGS_quic_supports_push_promise = false;
+
// Fixes a bug in QUIC_VERSION_26 by always using the primary config when
// getting the proof of possession.
bool FLAGS_quic_use_primary_config_for_proof = true;
@@ -128,9 +141,6 @@ bool FLAGS_quic_validate_stk_without_scid = true;
// If true, use the new write blocked list for QUIC.
bool FLAGS_quic_new_blocked_list = true;
-// If true, use inplace encryption for QUIC.
-bool FLAGS_quic_inplace_encryption = true;
-
// If true, QUIC will support RFC 7539 variants of ChaCha20 Poly1305.
bool FLAGS_quic_use_rfc7539 = true;
@@ -139,3 +149,7 @@ bool FLAGS_quic_drop_non_awaited_packets = false;
// If true, use the fast implementation of IncrementalHash/FNV1a_128_Hash.
bool FLAGS_quic_utils_use_fast_incremental_hash = true;
+
+// If true, require QUIC connections to use a valid server nonce or a non-local
+// strike register.
+bool FLAGS_require_strike_register_or_server_nonce = true;
diff --git a/net/quic/quic_flags.h b/net/quic/quic_flags.h
index c890f6f..93da375 100644
--- a/net/quic/quic_flags.h
+++ b/net/quic/quic_flags.h
@@ -18,7 +18,9 @@ NET_EXPORT_PRIVATE extern int64_t FLAGS_quic_time_wait_list_seconds;
NET_EXPORT_PRIVATE extern int64_t FLAGS_quic_time_wait_list_max_connections;
NET_EXPORT_PRIVATE extern bool FLAGS_enable_quic_stateless_reject_support;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_ack_decimation;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_always_log_bugs_for_tests;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_auto_tune_receive_window;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_enable_multipath;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_limit_max_cwnd;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_require_handshake_confirmation;
NET_EXPORT_PRIVATE extern bool FLAGS_shift_quic_cubic_epoch_when_app_limited;
@@ -34,14 +36,16 @@ NET_EXPORT_PRIVATE extern bool FLAGS_quic_track_single_retransmission;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_batch_writes;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_block_unencrypted_writes;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_never_write_unencrypted_data;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_no_unencrypted_fec;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_require_fix;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_supports_trailers;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_supports_push_promise;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_use_primary_config_for_proof;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_distinguish_incoming_outgoing_streams;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_validate_stk_without_scid;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_new_blocked_list;
-NET_EXPORT_PRIVATE extern bool FLAGS_quic_inplace_encryption;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_use_rfc7539;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_drop_non_awaited_packets;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_utils_use_fast_incremental_hash;
+NET_EXPORT_PRIVATE extern bool FLAGS_require_strike_register_or_server_nonce;
#endif // NET_QUIC_QUIC_FLAGS_H_
diff --git a/net/quic/quic_flow_controller.cc b/net/quic/quic_flow_controller.cc
index bf0c5ff..020261d 100644
--- a/net/quic/quic_flow_controller.cc
+++ b/net/quic/quic_flow_controller.cc
@@ -5,6 +5,7 @@
#include "net/quic/quic_flow_controller.h"
#include "base/strings/stringprintf.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_connection.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_protocol.h"
@@ -245,8 +246,8 @@ void QuicFlowController::UpdateReceiveWindowSize(QuicStreamOffset size) {
DVLOG(1) << ENDPOINT << "UpdateReceiveWindowSize for stream " << id_ << ": "
<< size;
if (receive_window_size_ != receive_window_offset_) {
- LOG(DFATAL) << "receive_window_size_:" << receive_window_size_
- << " != receive_window_offset:" << receive_window_offset_;
+ QUIC_BUG << "receive_window_size_:" << receive_window_size_
+ << " != receive_window_offset:" << receive_window_offset_;
return;
}
receive_window_size_ = size;
diff --git a/net/quic/quic_framer.cc b/net/quic/quic_framer.cc
index d4660bf..1370350 100644
--- a/net/quic/quic_framer.cc
+++ b/net/quic/quic_framer.cc
@@ -14,6 +14,7 @@
#include "net/quic/crypto/crypto_protocol.h"
#include "net/quic/crypto/quic_decrypter.h"
#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_data_reader.h"
#include "net/quic/quic_data_writer.h"
#include "net/quic/quic_flags.h"
@@ -123,7 +124,7 @@ QuicPacketNumberLength ReadSequenceNumberLength(uint8_t flags) {
case PACKET_FLAGS_1BYTE_PACKET:
return PACKET_1BYTE_PACKET_NUMBER;
default:
- LOG(DFATAL) << "Unreachable case statement.";
+ QUIC_BUG << "Unreachable case statement.";
return PACKET_6BYTE_PACKET_NUMBER;
}
}
@@ -222,7 +223,7 @@ size_t QuicFramer::GetStreamIdSize(QuicStreamId stream_id) {
return i;
}
}
- LOG(DFATAL) << "Failed to determine StreamIDSize.";
+ QUIC_BUG << "Failed to determine StreamIDSize.";
return 4;
}
@@ -240,7 +241,7 @@ size_t QuicFramer::GetStreamOffsetSize(QuicStreamOffset offset) {
return i;
}
}
- LOG(DFATAL) << "Failed to determine StreamOffsetSize.";
+ QUIC_BUG << "Failed to determine StreamOffsetSize.";
return 8;
}
@@ -269,12 +270,11 @@ size_t QuicFramer::GetSerializedFrameLength(
// Prevent a rare crash reported in b/19458523.
if ((frame.type == STREAM_FRAME || frame.type == ACK_FRAME) &&
frame.stream_frame == nullptr) {
- LOG(DFATAL) << "Cannot compute the length of a null frame. "
- << "type:" << frame.type << "free_bytes:" << free_bytes
- << " first_frame:" << first_frame
- << " last_frame:" << last_frame
- << " is_in_fec:" << is_in_fec_group
- << " seq num length:" << packet_number_length;
+ QUIC_BUG << "Cannot compute the length of a null frame. "
+ << "type:" << frame.type << "free_bytes:" << free_bytes
+ << " first_frame:" << first_frame << " last_frame:" << last_frame
+ << " is_in_fec:" << is_in_fec_group
+ << " seq num length:" << packet_number_length;
set_error(QUIC_INTERNAL_ERROR);
visitor_->OnError(this);
return 0;
@@ -322,7 +322,7 @@ size_t QuicFramer::BuildDataPacket(const QuicPacketHeader& header,
size_t packet_length) {
QuicDataWriter writer(packet_length, buffer);
if (!AppendPacketHeader(header, &writer)) {
- LOG(DFATAL) << "AppendPacketHeader failed";
+ QUIC_BUG << "AppendPacketHeader failed";
return 0;
}
@@ -333,7 +333,7 @@ size_t QuicFramer::BuildDataPacket(const QuicPacketHeader& header,
(header.is_in_fec_group == NOT_IN_FEC_GROUP) &&
(i == frames.size() - 1);
if (!AppendTypeByte(frame, no_stream_frame_length, &writer)) {
- LOG(DFATAL) << "AppendTypeByte failed";
+ QUIC_BUG << "AppendTypeByte failed";
return 0;
}
@@ -344,20 +344,20 @@ size_t QuicFramer::BuildDataPacket(const QuicPacketHeader& header,
case STREAM_FRAME:
if (!AppendStreamFrame(*frame.stream_frame, no_stream_frame_length,
&writer)) {
- LOG(DFATAL) << "AppendStreamFrame failed";
+ QUIC_BUG << "AppendStreamFrame failed";
return 0;
}
break;
case ACK_FRAME:
if (!AppendAckFrameAndTypeByte(header, *frame.ack_frame, &writer)) {
- LOG(DFATAL) << "AppendAckFrameAndTypeByte failed";
+ QUIC_BUG << "AppendAckFrameAndTypeByte failed";
return 0;
}
break;
case STOP_WAITING_FRAME:
if (!AppendStopWaitingFrame(header, *frame.stop_waiting_frame,
&writer)) {
- LOG(DFATAL) << "AppendStopWaitingFrame failed";
+ QUIC_BUG << "AppendStopWaitingFrame failed";
return 0;
}
break;
@@ -368,38 +368,38 @@ size_t QuicFramer::BuildDataPacket(const QuicPacketHeader& header,
break;
case RST_STREAM_FRAME:
if (!AppendRstStreamFrame(*frame.rst_stream_frame, &writer)) {
- LOG(DFATAL) << "AppendRstStreamFrame failed";
+ QUIC_BUG << "AppendRstStreamFrame failed";
return 0;
}
break;
case CONNECTION_CLOSE_FRAME:
if (!AppendConnectionCloseFrame(*frame.connection_close_frame,
&writer)) {
- LOG(DFATAL) << "AppendConnectionCloseFrame failed";
+ QUIC_BUG << "AppendConnectionCloseFrame failed";
return 0;
}
break;
case GOAWAY_FRAME:
if (!AppendGoAwayFrame(*frame.goaway_frame, &writer)) {
- LOG(DFATAL) << "AppendGoAwayFrame failed";
+ QUIC_BUG << "AppendGoAwayFrame failed";
return 0;
}
break;
case WINDOW_UPDATE_FRAME:
if (!AppendWindowUpdateFrame(*frame.window_update_frame, &writer)) {
- LOG(DFATAL) << "AppendWindowUpdateFrame failed";
+ QUIC_BUG << "AppendWindowUpdateFrame failed";
return 0;
}
break;
case BLOCKED_FRAME:
if (!AppendBlockedFrame(*frame.blocked_frame, &writer)) {
- LOG(DFATAL) << "AppendBlockedFrame failed";
+ QUIC_BUG << "AppendBlockedFrame failed";
return 0;
}
break;
default:
RaiseError(QUIC_INVALID_FRAME_DATA);
- LOG(DFATAL) << "QUIC_INVALID_FRAME_DATA";
+ QUIC_BUG << "QUIC_INVALID_FRAME_DATA";
return 0;
}
++i;
@@ -418,12 +418,12 @@ QuicPacket* QuicFramer::BuildFecPacket(const QuicPacketHeader& header,
scoped_ptr<char[]> buffer(new char[len]);
QuicDataWriter writer(len, buffer.get());
if (!AppendPacketHeader(header, &writer)) {
- LOG(DFATAL) << "AppendPacketHeader failed";
+ QUIC_BUG << "AppendPacketHeader failed";
return nullptr;
}
if (!writer.WriteBytes(redundancy.data(), redundancy.length())) {
- LOG(DFATAL) << "Failed to add FEC";
+ QUIC_BUG << "Failed to add FEC";
return nullptr;
}
@@ -1000,7 +1000,7 @@ uint8_t QuicFramer::GetSequenceNumberFlags(
case PACKET_6BYTE_PACKET_NUMBER:
return PACKET_FLAGS_6BYTE_PACKET;
default:
- LOG(DFATAL) << "Unreachable case statement.";
+ QUIC_BUG << "Unreachable case statement.";
return PACKET_FLAGS_6BYTE_PACKET;
}
}
@@ -1691,7 +1691,7 @@ size_t QuicFramer::EncryptPayload(EncryptionLevel level,
// Copy in the header, because the encrypter only populates the encrypted
// plaintext content.
const size_t ad_len = associated_data.length();
- memcpy(buffer, associated_data.data(), ad_len);
+ memmove(buffer, associated_data.data(), ad_len);
// Encrypt the plaintext into the buffer.
size_t output_length = 0;
if (!encrypter_[level]->EncryptPacket(packet_number, associated_data,
@@ -1859,7 +1859,7 @@ bool QuicFramer::AppendTypeByte(const QuicFrame& frame,
switch (frame.type) {
case STREAM_FRAME: {
if (frame.stream_frame == nullptr) {
- LOG(DFATAL) << "Failed to append STREAM frame with no stream_frame.";
+ QUIC_BUG << "Failed to append STREAM frame with no stream_frame.";
}
// Fin bit.
type_byte |= frame.stream_frame->fin ? kQuicStreamFinMask : 0;
@@ -1927,23 +1927,23 @@ bool QuicFramer::AppendStreamFrame(const QuicStreamFrame& frame,
bool no_stream_frame_length,
QuicDataWriter* writer) {
if (!writer->WriteBytes(&frame.stream_id, GetStreamIdSize(frame.stream_id))) {
- LOG(DFATAL) << "Writing stream id size failed.";
+ QUIC_BUG << "Writing stream id size failed.";
return false;
}
if (!writer->WriteBytes(&frame.offset, GetStreamOffsetSize(frame.offset))) {
- LOG(DFATAL) << "Writing offset size failed.";
+ QUIC_BUG << "Writing offset size failed.";
return false;
}
if (!no_stream_frame_length) {
if ((frame.frame_length > numeric_limits<uint16_t>::max()) ||
!writer->WriteUInt16(static_cast<uint16_t>(frame.frame_length))) {
- LOG(DFATAL) << "Writing stream frame length failed";
+ QUIC_BUG << "Writing stream frame length failed";
return false;
}
}
if (!writer->WriteBytes(frame.frame_buffer, frame.frame_length)) {
- LOG(DFATAL) << "Writing frame data failed.";
+ QUIC_BUG << "Writing frame data failed.";
return false;
}
return true;
@@ -2170,20 +2170,20 @@ bool QuicFramer::AppendStopWaitingFrame(const QuicPacketHeader& header,
const QuicPacketNumber length_shift =
header.public_header.packet_number_length * 8;
if (!writer->WriteUInt8(frame.entropy_hash)) {
- LOG(DFATAL) << " hash failed";
+ QUIC_BUG << " hash failed";
return false;
}
if (least_unacked_delta >> length_shift > 0) {
- LOG(DFATAL) << "packet_number_length "
- << header.public_header.packet_number_length
- << " is too small for least_unacked_delta: "
- << least_unacked_delta;
+ QUIC_BUG << "packet_number_length "
+ << header.public_header.packet_number_length
+ << " is too small for least_unacked_delta: "
+ << least_unacked_delta;
return false;
}
if (!AppendPacketSequenceNumber(header.public_header.packet_number_length,
least_unacked_delta, writer)) {
- LOG(DFATAL) << " seq failed: " << header.public_header.packet_number_length;
+ QUIC_BUG << " seq failed: " << header.public_header.packet_number_length;
return false;
}
diff --git a/net/quic/quic_headers_stream.cc b/net/quic/quic_headers_stream.cc
index 0676b54..abcaa71 100644
--- a/net/quic/quic_headers_stream.cc
+++ b/net/quic/quic_headers_stream.cc
@@ -7,6 +7,7 @@
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_headers_stream.h"
#include "net/quic/quic_spdy_session.h"
@@ -19,12 +20,6 @@ using std::string;
namespace net {
-namespace {
-
-const QuicStreamId kInvalidStreamId = 0;
-
-} // namespace
-
// A SpdyFramer visitor which passed SYN_STREAM and SYN_REPLY frames to
// the QuicSpdyStream, and closes the connection if any unexpected frames
// are received.
@@ -143,7 +138,14 @@ class QuicHeadersStream::SpdyFramerVisitor
void OnPushPromise(SpdyStreamId stream_id,
SpdyStreamId promised_stream_id,
bool end) override {
- CloseConnection("SPDY PUSH_PROMISE frame received.");
+ if (!stream_->supports_push_promise()) {
+ CloseConnection("PUSH_PROMISE not supported.");
+ return;
+ }
+ if (!stream_->IsConnected()) {
+ return;
+ }
+ stream_->OnPushPromise(stream_id, promised_stream_id, end);
}
void OnContinuation(SpdyStreamId stream_id, bool end) override {}
@@ -185,10 +187,13 @@ QuicHeadersStream::QuicHeadersStream(QuicSpdySession* session)
: ReliableQuicStream(kHeadersStreamId, session),
spdy_session_(session),
stream_id_(kInvalidStreamId),
+ promised_stream_id_(kInvalidStreamId),
fin_(false),
frame_len_(0),
measure_headers_hol_blocking_time_(
FLAGS_quic_measure_headers_hol_blocking_time),
+ supports_push_promise_(session->perspective() == Perspective::IS_CLIENT &&
+ FLAGS_quic_supports_push_promise),
cur_max_timestamp_(QuicTime::Zero()),
prev_max_timestamp_(QuicTime::Zero()),
spdy_framer_(HTTP2),
@@ -226,7 +231,7 @@ size_t QuicHeadersStream::WritePushPromise(
const SpdyHeaderBlock& headers,
QuicAckListenerInterface* ack_listener) {
if (session()->perspective() == Perspective::IS_CLIENT) {
- LOG(DFATAL) << "Client shouldn't send PUSH_PROMISE";
+ QUIC_BUG << "Client shouldn't send PUSH_PROMISE";
return 0;
}
@@ -295,10 +300,25 @@ void QuicHeadersStream::OnHeaders(SpdyStreamId stream_id,
}
}
DCHECK_EQ(kInvalidStreamId, stream_id_);
+ DCHECK_EQ(kInvalidStreamId, promised_stream_id_);
stream_id_ = stream_id;
fin_ = fin;
}
+void QuicHeadersStream::OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) {
+ if (!supports_push_promise_) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY PUSH_PROMISE not supported.");
+ return;
+ }
+ DCHECK_EQ(kInvalidStreamId, stream_id_);
+ DCHECK_EQ(kInvalidStreamId, promised_stream_id_);
+ stream_id_ = stream_id;
+ promised_stream_id_ = promised_stream_id;
+}
+
void QuicHeadersStream::OnControlFrameHeaderData(SpdyStreamId stream_id,
const char* header_data,
size_t len) {
@@ -321,13 +341,24 @@ void QuicHeadersStream::OnControlFrameHeaderData(SpdyStreamId stream_id,
prev_max_timestamp_ = std::max(prev_max_timestamp_, cur_max_timestamp_);
cur_max_timestamp_ = QuicTime::Zero();
}
- spdy_session_->OnStreamHeadersComplete(stream_id_, fin_, frame_len_);
+ if (promised_stream_id_ == kInvalidStreamId) {
+ spdy_session_->OnStreamHeadersComplete(stream_id_, fin_, frame_len_);
+ } else {
+ spdy_session_->OnPromiseHeadersComplete(stream_id_, promised_stream_id_,
+ frame_len_);
+ }
// Reset state for the next frame.
+ promised_stream_id_ = kInvalidStreamId;
stream_id_ = kInvalidStreamId;
fin_ = false;
frame_len_ = 0;
} else {
- spdy_session_->OnStreamHeaders(stream_id_, StringPiece(header_data, len));
+ if (promised_stream_id_ == kInvalidStreamId) {
+ spdy_session_->OnStreamHeaders(stream_id_, StringPiece(header_data, len));
+ } else {
+ spdy_session_->OnPromiseHeaders(stream_id_,
+ StringPiece(header_data, len));
+ }
}
}
diff --git a/net/quic/quic_headers_stream.h b/net/quic/quic_headers_stream.h
index 385650e..a8b466c 100644
--- a/net/quic/quic_headers_stream.h
+++ b/net/quic/quic_headers_stream.h
@@ -18,9 +18,10 @@ namespace net {
class QuicSpdySession;
-// Headers in QUIC are sent as HTTP/2 HEADERS frames over a reserved reliable
-// stream with the id 3. Each endpoint (client and server) will allocate an
-// instance of QuicHeadersStream to send and receive headers.
+// Headers in QUIC are sent as HTTP/2 HEADERS or PUSH_PROMISE frames
+// over a reserved reliable stream with the id 3. Each endpoint
+// (client and server) will allocate an instance of QuicHeadersStream
+// to send and receive headers.
class NET_EXPORT_PRIVATE QuicHeadersStream : public ReliableQuicStream {
public:
explicit QuicHeadersStream(QuicSpdySession* session);
@@ -29,24 +30,26 @@ class NET_EXPORT_PRIVATE QuicHeadersStream : public ReliableQuicStream {
// Writes |headers| for |stream_id| in an HTTP/2 HEADERS frame to the peer.
// If |fin| is true, the fin flag will be set on the HEADERS frame. Returns
// the size, in bytes, of the resulting HEADERS frame.
- size_t WriteHeaders(QuicStreamId stream_id,
- const SpdyHeaderBlock& headers,
- bool fin,
- SpdyPriority priority,
- QuicAckListenerInterface* ack_listener);
+ virtual size_t WriteHeaders(QuicStreamId stream_id,
+ const SpdyHeaderBlock& headers,
+ bool fin,
+ SpdyPriority priority,
+ QuicAckListenerInterface* ack_listener);
// Write |headers| for |promised_stream_id| on |original_stream_id| in a
// PUSH_PROMISE frame to peer.
// Return the size, in bytes, of the resulting PUSH_PROMISE frame.
- size_t WritePushPromise(QuicStreamId original_stream_id,
- QuicStreamId promised_stream_id,
- const SpdyHeaderBlock& headers,
- QuicAckListenerInterface* ack_listener);
+ virtual size_t WritePushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ const SpdyHeaderBlock& headers,
+ QuicAckListenerInterface* ack_listener);
// ReliableQuicStream implementation
void OnDataAvailable() override;
SpdyPriority Priority() const override;
+ bool supports_push_promise() { return supports_push_promise_; }
+
private:
class SpdyFramerVisitor;
@@ -58,6 +61,11 @@ class NET_EXPORT_PRIVATE QuicHeadersStream : public ReliableQuicStream {
SpdyPriority priority,
bool fin);
+ // Called when a PUSH_PROMISE frame has been received.
+ void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end);
+
// Called when a chunk of header data is available. This is called
// after OnHeaders.
// |stream_id| The stream receiving the header data.
@@ -78,11 +86,13 @@ class NET_EXPORT_PRIVATE QuicHeadersStream : public ReliableQuicStream {
// Data about the stream whose headers are being processed.
QuicStreamId stream_id_;
+ QuicStreamId promised_stream_id_;
bool fin_;
size_t frame_len_;
- // Helper variable that caches the corresponding feature flag.
+ // Helper variables that cache the corresponding feature flag.
bool measure_headers_hol_blocking_time_;
+ bool supports_push_promise_;
// Timestamps used to measure HOL blocking, these are recorded by
// the sequencer approximate to the time of arrival off the wire.
diff --git a/net/quic/quic_headers_stream_test.cc b/net/quic/quic_headers_stream_test.cc
index e985657..2425683 100644
--- a/net/quic/quic_headers_stream_test.cc
+++ b/net/quic/quic_headers_stream_test.cc
@@ -115,6 +115,7 @@ vector<TestParams> GetTestParams() {
params.push_back(TestParams(version, Perspective::IS_CLIENT));
params.push_back(TestParams(version, Perspective::IS_SERVER));
}
+ FLAGS_quic_supports_push_promise = true;
return params;
}
@@ -130,6 +131,7 @@ class QuicHeadersStreamTest : public ::testing::TestWithParam<TestParams> {
framer_(HTTP2),
stream_frame_(kHeadersStreamId, /*fin=*/false, /*offset=*/0, ""),
next_promised_stream_id_(2) {
+ FLAGS_quic_always_log_bugs_for_tests = true;
headers_[":version"] = "HTTP/1.1";
headers_[":status"] = "200 Ok";
headers_["content-length"] = "11";
@@ -224,7 +226,7 @@ class QuicHeadersStreamTest : public ::testing::TestWithParam<TestParams> {
void CloseConnection() { QuicConnectionPeer::CloseConnection(connection_); }
- QuicStreamId NextPromisedStreamId() { return next_promised_stream_id_++; }
+ QuicStreamId NextPromisedStreamId() { return next_promised_stream_id_ += 2; }
static const bool kFrameComplete = true;
static const bool kHasPriority = true;
@@ -337,6 +339,82 @@ TEST_P(QuicHeadersStreamTest, ProcessRawData) {
}
}
+TEST_P(QuicHeadersStreamTest, ProcessPushPromise) {
+ if (perspective() == Perspective::IS_SERVER)
+ return;
+ for (QuicStreamId stream_id = kClientDataStreamId1;
+ stream_id < kClientDataStreamId3; stream_id += 2) {
+ QuicStreamId promised_stream_id = NextPromisedStreamId();
+ scoped_ptr<SpdySerializedFrame> frame;
+ SpdyPushPromiseIR push_promise(stream_id, promised_stream_id);
+ push_promise.set_header_block(headers_);
+ frame.reset(framer_.SerializeFrame(push_promise));
+ if (perspective() == Perspective::IS_SERVER) {
+ EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "PUSH_PROMISE not supported."))
+ .WillRepeatedly(
+ InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection));
+ } else {
+ EXPECT_CALL(session_, OnPromiseHeaders(stream_id, _))
+ .WillRepeatedly(WithArgs<1>(
+ Invoke(this, &QuicHeadersStreamTest::SaveHeaderDataStringPiece)));
+ EXPECT_CALL(session_, OnPromiseHeadersComplete(
+ stream_id, promised_stream_id, frame->size()));
+ }
+ stream_frame_.frame_buffer = frame->data();
+ stream_frame_.frame_length = frame->size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ if (perspective() == Perspective::IS_CLIENT) {
+ stream_frame_.offset += frame->size();
+ CheckHeaders();
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, PushPromiseOutOfOrder) {
+ if (perspective() == Perspective::IS_SERVER)
+ return;
+
+ QuicStreamId promised_stream_id = NextPromisedStreamId();
+ QuicStreamId stream_id = kClientDataStreamId1;
+
+ scoped_ptr<SpdySerializedFrame> frame;
+ SpdyPushPromiseIR push_promise(stream_id, promised_stream_id);
+ push_promise.set_header_block(headers_);
+ frame.reset(framer_.SerializeFrame(push_promise));
+ EXPECT_CALL(session_, OnPromiseHeaders(stream_id, _))
+ .WillRepeatedly(WithArgs<1>(
+ Invoke(this, &QuicHeadersStreamTest::SaveHeaderDataStringPiece)));
+ EXPECT_CALL(session_, OnPromiseHeadersComplete(stream_id, promised_stream_id,
+ frame->size()));
+ stream_frame_.frame_buffer = frame->data();
+ stream_frame_.frame_length = frame->size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ if (perspective() == Perspective::IS_CLIENT) {
+ stream_frame_.offset += frame->size();
+ CheckHeaders();
+ }
+
+ stream_id += 2;
+ push_promise.set_stream_id(stream_id);
+ frame.reset(framer_.SerializeFrame(push_promise));
+ EXPECT_CALL(session_, OnPromiseHeaders(stream_id, _))
+ .WillRepeatedly(WithArgs<1>(
+ Invoke(this, &QuicHeadersStreamTest::SaveHeaderDataStringPiece)));
+ EXPECT_CALL(session_, OnPromiseHeadersComplete(stream_id, promised_stream_id,
+ frame->size()));
+ EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(
+ QUIC_INVALID_STREAM_ID,
+ "Received push stream id lesser or equal to the"
+ " last accepted before"))
+ .WillRepeatedly(
+ InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection));
+ stream_frame_.frame_buffer = frame->data();
+ stream_frame_.frame_length = frame->size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
TEST_P(QuicHeadersStreamTest, EmptyHeaderHOLBlockedTime) {
EXPECT_CALL(session_, OnHeadersHeadOfLineBlocking(_)).Times(0);
testing::InSequence seq;
diff --git a/net/quic/quic_packet_creator.cc b/net/quic/quic_packet_creator.cc
index 233fa9d..35072e9 100644
--- a/net/quic/quic_packet_creator.cc
+++ b/net/quic/quic_packet_creator.cc
@@ -9,6 +9,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_data_writer.h"
#include "net/quic/quic_fec_group.h"
#include "net/quic/quic_flags.h"
@@ -175,7 +176,7 @@ bool QuicPacketCreator::IsFecGroupOpen() const {
void QuicPacketCreator::StartFecProtectingPackets() {
if (max_packets_per_fec_group_ == 0) {
- LOG(DFATAL) << "Cannot start FEC protection when FEC is not enabled.";
+ QUIC_BUG << "Cannot start FEC protection when FEC is not enabled.";
return;
}
// TODO(jri): This currently requires that the generator flush out any
@@ -184,7 +185,7 @@ void QuicPacketCreator::StartFecProtectingPackets() {
// generator to check if the resulting expansion still allows the incoming
// frame to be added to the packet.
if (HasPendingFrames()) {
- LOG(DFATAL) << "Cannot start FEC protection with pending frames.";
+ QUIC_BUG << "Cannot start FEC protection with pending frames.";
return;
}
DCHECK(!fec_protect_);
@@ -193,7 +194,7 @@ void QuicPacketCreator::StartFecProtectingPackets() {
void QuicPacketCreator::StopFecProtectingPackets() {
if (fec_group_.get() != nullptr) {
- LOG(DFATAL) << "Cannot stop FEC protection with open FEC group.";
+ QUIC_BUG << "Cannot stop FEC protection with open FEC group.";
return;
}
DCHECK(fec_protect_);
@@ -465,8 +466,8 @@ void QuicPacketCreator::Flush() {
void QuicPacketCreator::OnSerializedPacket(SerializedPacket* packet) {
if (packet->packet == nullptr) {
- LOG(DFATAL) << "Failed to SerializePacket. fec_policy:" << fec_send_policy()
- << " should_fec_protect_:" << should_fec_protect_next_packet_;
+ QUIC_BUG << "Failed to SerializePacket. fec_policy:" << fec_send_policy()
+ << " should_fec_protect_:" << should_fec_protect_next_packet_;
delegate_->CloseConnection(QUIC_FAILED_TO_SERIALIZE_PACKET, false);
return;
}
@@ -569,71 +570,34 @@ SerializedPacket QuicPacketCreator::SerializePacket(
queued_frames_.back().type == ACK_FRAME;
// Use the packet_size_ instead of the buffer size to ensure smaller
// packet sizes are properly used.
- size_t encrypted_length = 0u;
- if (FLAGS_quic_inplace_encryption) {
- size_t length = framer_->BuildDataPacket(header, queued_frames_,
- encrypted_buffer, packet_size_);
- if (length == 0) {
- LOG(DFATAL) << "Failed to serialize " << queued_frames_.size()
- << " frames.";
- return NoPacket();
- }
+ size_t length = framer_->BuildDataPacket(header, queued_frames_,
+ encrypted_buffer, packet_size_);
+ if (length == 0) {
+ QUIC_BUG << "Failed to serialize " << queued_frames_.size() << " frames.";
+ return NoPacket();
+ }
- // TODO(ianswett) Consider replacing QuicPacket with something else, since
- // it's only used to provide convenience methods to FEC and encryption.
- QuicPacket packet(encrypted_buffer, length,
- /* owns_buffer */ false,
- header.public_header.connection_id_length,
- header.public_header.version_flag,
- header.public_header.packet_number_length);
- OnBuiltFecProtectedPayload(header, packet.FecProtectedData());
-
- // Because of possible truncation, we can't be confident that our
- // packet size calculation worked correctly.
- if (!possibly_truncated_by_length) {
- DCHECK_EQ(packet_size_, length);
- }
- // Immediately encrypt the packet, to ensure we don't encrypt the same
- // packet number multiple times.
- encrypted_length =
- framer_->EncryptPayload(encryption_level_, packet_number_, packet,
- encrypted_buffer, encrypted_buffer_len);
- } else {
- // The optimized encryption algorithm implementations run faster when
- // operating on aligned memory.
- // TODO(rtenneti): Change the default 64 alignas value (used the default
- // value from CACHELINE_SIZE).
- ALIGNAS(64) char buffer[kMaxPacketSize];
- size_t length =
- framer_->BuildDataPacket(header, queued_frames_, buffer, packet_size_);
- if (length == 0) {
- LOG(DFATAL) << "Failed to serialize " << queued_frames_.size()
- << " frames.";
- return NoPacket();
- }
+ // TODO(ianswett) Consider replacing QuicPacket with something else, since
+ // it's only used to provide convenience methods to FEC and encryption.
+ QuicPacket packet(encrypted_buffer, length,
+ /* owns_buffer */ false,
+ header.public_header.connection_id_length,
+ header.public_header.version_flag,
+ header.public_header.packet_number_length);
+ OnBuiltFecProtectedPayload(header, packet.FecProtectedData());
- // TODO(ianswett) Consider replacing QuicPacket with something else, since
- // it's only used to provide convenience methods to FEC and encryption.
- QuicPacket packet(buffer, length,
- /* owns_buffer */ false,
- header.public_header.connection_id_length,
- header.public_header.version_flag,
- header.public_header.packet_number_length);
- OnBuiltFecProtectedPayload(header, packet.FecProtectedData());
-
- // Because of possible truncation, we can't be confident that our
- // packet size calculation worked correctly.
- if (!possibly_truncated_by_length) {
- DCHECK_EQ(packet_size_, length);
- }
- // Immediately encrypt the packet, to ensure we don't encrypt the same
- // packet number multiple times.
- encrypted_length =
- framer_->EncryptPayload(encryption_level_, packet_number_, packet,
- encrypted_buffer, encrypted_buffer_len);
+ // Because of possible truncation, we can't be confident that our
+ // packet size calculation worked correctly.
+ if (!possibly_truncated_by_length) {
+ DCHECK_EQ(packet_size_, length);
}
+ // Immediately encrypt the packet, to ensure we don't encrypt the same
+ // packet number multiple times.
+ size_t encrypted_length =
+ framer_->EncryptPayload(encryption_level_, packet_number_, packet,
+ encrypted_buffer, encrypted_buffer_len);
if (encrypted_length == 0) {
- LOG(DFATAL) << "Failed to encrypt packet number " << packet_number_;
+ QUIC_BUG << "Failed to encrypt packet number " << packet_number_;
return NoPacket();
}
@@ -661,10 +625,15 @@ SerializedPacket QuicPacketCreator::SerializeFec(char* buffer,
size_t buffer_len) {
DCHECK_LT(0u, buffer_len);
if (fec_group_.get() == nullptr || fec_group_->NumReceivedPackets() <= 0) {
- LOG(DFATAL) << "SerializeFEC called but no group or zero packets in group.";
+ QUIC_BUG << "SerializeFEC called but no group or zero packets in group.";
// TODO(jri): Make this a public method of framer?
return NoPacket();
}
+ if (FLAGS_quic_no_unencrypted_fec && encryption_level_ == ENCRYPTION_NONE) {
+ LOG(DFATAL) << "SerializeFEC must be called with encryption.";
+ delegate_->CloseConnection(QUIC_UNENCRYPTED_FEC_DATA, false);
+ return NoPacket();
+ }
DCHECK_EQ(0u, queued_frames_.size());
QuicPacketHeader header;
FillPacketHeader(fec_group_->FecGroupNumber(), true, &header);
@@ -681,7 +650,7 @@ SerializedPacket QuicPacketCreator::SerializeFec(char* buffer,
size_t encrypted_length = framer_->EncryptPayload(
encryption_level_, packet_number_, *packet, buffer, buffer_len);
if (encrypted_length == 0) {
- LOG(DFATAL) << "Failed to encrypt packet number " << packet_number_;
+ QUIC_BUG << "Failed to encrypt packet number " << packet_number_;
return NoPacket();
}
SerializedPacket serialized(current_path_, header.packet_number,
@@ -741,7 +710,7 @@ bool QuicPacketCreator::AddFrame(const QuicFrame& frame,
if (FLAGS_quic_never_write_unencrypted_data && frame.type == STREAM_FRAME &&
frame.stream_frame->stream_id != kCryptoStreamId &&
encryption_level_ == ENCRYPTION_NONE) {
- LOG(DFATAL) << "Cannot send stream data without encryption.";
+ QUIC_BUG << "Cannot send stream data without encryption.";
delegate_->CloseConnection(QUIC_UNENCRYPTED_STREAM_DATA, false);
return false;
}
@@ -811,7 +780,9 @@ void QuicPacketCreator::MaybeStartFecProtection() {
void QuicPacketCreator::MaybeSendFecPacketAndCloseGroup(bool force_send_fec,
bool is_fec_timeout) {
if (ShouldSendFec(force_send_fec)) {
- if (fec_send_policy_ == FEC_ALARM_TRIGGER && !is_fec_timeout) {
+ if ((FLAGS_quic_no_unencrypted_fec &&
+ encryption_level_ == ENCRYPTION_NONE) ||
+ (fec_send_policy_ == FEC_ALARM_TRIGGER && !is_fec_timeout)) {
ResetFecGroup();
delegate_->OnResetFecGroup();
} else {
@@ -860,8 +831,7 @@ void QuicPacketCreator::SetCurrentPath(
}
if (HasPendingFrames()) {
- LOG(DFATAL)
- << "Unable to change paths when a packet is under construction.";
+ QUIC_BUG << "Unable to change paths when a packet is under construction.";
return;
}
diff --git a/net/quic/quic_packet_creator_test.cc b/net/quic/quic_packet_creator_test.cc
index 5dddc54..2a1c4ea 100644
--- a/net/quic/quic_packet_creator_test.cc
+++ b/net/quic/quic_packet_creator_test.cc
@@ -126,6 +126,7 @@ class QuicPacketCreatorTest : public ::testing::TestWithParam<TestParams> {
&buffer_allocator_,
&delegate_),
serialized_packet_(creator_.NoPacket()) {
+ FLAGS_quic_always_log_bugs_for_tests = true;
creator_.set_connection_id_length(GetParam().connection_id_length);
creator_.SetEncrypter(ENCRYPTION_INITIAL, new NullEncrypter());
@@ -135,6 +136,7 @@ class QuicPacketCreatorTest : public ::testing::TestWithParam<TestParams> {
server_framer_.set_visitor(&framer_visitor_);
// TODO(ianswett): Fix this test so it uses a non-null encrypter.
FLAGS_quic_never_write_unencrypted_data = false;
+ FLAGS_quic_no_unencrypted_fec = false;
}
~QuicPacketCreatorTest() override {}
@@ -1587,6 +1589,56 @@ TEST_P(QuicPacketCreatorTest, AddUnencryptedStreamDataClosesConnection) {
"Cannot send stream data without encryption.");
}
+TEST_P(QuicPacketCreatorTest, DontSendUnencryptedFec) {
+ ValueRestore<bool> old_flag(&FLAGS_quic_no_unencrypted_fec, true);
+ // Send FEC packet every 6 packets.
+ creator_.set_max_packets_per_fec_group(6);
+ // Send stream data encrypted with FEC protection.
+ creator_.set_encryption_level(ENCRYPTION_INITIAL);
+ // Turn on FEC protection.
+ QuicFrame frame;
+ QuicIOVector io_vector(MakeIOVector("test"));
+ ASSERT_TRUE(creator_.ConsumeData(kHeadersStreamId, io_vector, 0u, 0u, false,
+ false, &frame, MUST_FEC_PROTECT));
+ EXPECT_TRUE(QuicPacketCreatorPeer::IsFecProtected(&creator_));
+ // Serialize the packet.
+ EXPECT_CALL(delegate_, OnSerializedPacket(_))
+ .WillOnce(Invoke(this, &QuicPacketCreatorTest::ClearSerializedPacket));
+ creator_.Flush();
+
+ // The creator will clear the FEC group rather than try to send without
+ // encryption.
+ creator_.set_encryption_level(ENCRYPTION_NONE);
+ EXPECT_CALL(delegate_, OnResetFecGroup());
+ creator_.MaybeSendFecPacketAndCloseGroup(true, false);
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeUnencryptedFecClosesConnection) {
+ ValueRestore<bool> old_flag(&FLAGS_quic_no_unencrypted_fec, true);
+ // Send FEC packet every 6 packets.
+ creator_.set_max_packets_per_fec_group(6);
+ // Send stream data encrypted with FEC protection.
+ creator_.set_encryption_level(ENCRYPTION_INITIAL);
+ // Turn on FEC protection.
+ QuicFrame frame;
+ QuicIOVector io_vector(MakeIOVector("test"));
+ ASSERT_TRUE(creator_.ConsumeData(kHeadersStreamId, io_vector, 0u, 0u, false,
+ false, &frame, MUST_FEC_PROTECT));
+ EXPECT_TRUE(QuicPacketCreatorPeer::IsFecProtected(&creator_));
+ // Serialize the packet.
+ EXPECT_CALL(delegate_, OnSerializedPacket(_))
+ .WillOnce(Invoke(this, &QuicPacketCreatorTest::ClearSerializedPacket));
+ creator_.Flush();
+
+ // Try to send an FEC packet unencrypted.
+ creator_.set_encryption_level(ENCRYPTION_NONE);
+ EXPECT_CALL(delegate_, CloseConnection(QUIC_UNENCRYPTED_FEC_DATA, _));
+ char seralized_fec_buffer[kMaxPacketSize];
+ EXPECT_DFATAL(QuicPacketCreatorPeer::SerializeFec(
+ &creator_, seralized_fec_buffer, kMaxPacketSize),
+ "SerializeFEC must be called with encryption.");
+}
+
} // namespace
} // namespace test
} // namespace net
diff --git a/net/quic/quic_packet_generator.cc b/net/quic/quic_packet_generator.cc
index 00293a1..50d6af1 100644
--- a/net/quic/quic_packet_generator.cc
+++ b/net/quic/quic_packet_generator.cc
@@ -5,6 +5,7 @@
#include "net/quic/quic_packet_generator.h"
#include "base/logging.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_fec_group.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_utils.h"
@@ -84,7 +85,7 @@ void QuicPacketGenerator::SetShouldSendAck(bool also_send_stop_waiting) {
}
if (also_send_stop_waiting && packet_creator_.has_stop_waiting()) {
- LOG(DFATAL) << "Should only ever be one pending stop waiting frame.";
+ QUIC_BUG << "Should only ever be one pending stop waiting frame.";
return;
}
@@ -120,7 +121,7 @@ QuicConsumedData QuicPacketGenerator::ConsumeData(
}
if (!fin && (iov.total_length == 0)) {
- LOG(DFATAL) << "Attempt to consume empty data without FIN.";
+ QUIC_BUG << "Attempt to consume empty data without FIN.";
return QuicConsumedData(0, false);
}
@@ -132,7 +133,7 @@ QuicConsumedData QuicPacketGenerator::ConsumeData(
has_handshake, &frame, fec_protection)) {
// The creator is always flushed if there's not enough room for a new
// stream frame before ConsumeData, so ConsumeData should always succeed.
- LOG(DFATAL) << "Failed to ConsumeData, stream:" << id;
+ QUIC_BUG << "Failed to ConsumeData, stream:" << id;
return QuicConsumedData(0, false);
}
@@ -231,7 +232,7 @@ void QuicPacketGenerator::SendQueuedFrames(bool flush, bool is_fec_timeout) {
void QuicPacketGenerator::OnFecTimeout() {
DCHECK(!InBatchMode());
if (!packet_creator_.ShouldSendFec(true)) {
- LOG(DFATAL) << "No FEC packet to send on FEC timeout.";
+ QUIC_BUG << "No FEC packet to send on FEC timeout.";
return;
}
// Flush out any pending frames in the generator and the creator, and then
diff --git a/net/quic/quic_packet_generator_test.cc b/net/quic/quic_packet_generator_test.cc
index f8c0285..4bd81a7 100644
--- a/net/quic/quic_packet_generator_test.cc
+++ b/net/quic/quic_packet_generator_test.cc
@@ -119,6 +119,7 @@ class QuicPacketGeneratorTest : public ::testing::TestWithParam<FecSendPolicy> {
generator_.set_fec_send_policy(GetParam());
// TODO(ianswett): Fix this test so it uses a non-null encrypter.
FLAGS_quic_never_write_unencrypted_data = false;
+ FLAGS_quic_no_unencrypted_fec = false;
}
~QuicPacketGeneratorTest() override {
diff --git a/net/quic/quic_protocol.cc b/net/quic/quic_protocol.cc
index 7626f0b..a5fb29c 100644
--- a/net/quic/quic_protocol.cc
+++ b/net/quic/quic_protocol.cc
@@ -791,7 +791,9 @@ SerializedPacket::SerializedPacket(
entropy_hash(entropy_hash),
is_fec_packet(false),
has_ack(has_ack),
- has_stop_waiting(has_stop_waiting) {}
+ has_stop_waiting(has_stop_waiting),
+ original_packet_number(0),
+ transmission_type(NOT_RETRANSMISSION) {}
SerializedPacket::SerializedPacket(
QuicPathId path_id,
diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h
index a816607..4a38002 100644
--- a/net/quic/quic_protocol.h
+++ b/net/quic/quic_protocol.h
@@ -125,6 +125,9 @@ const bool kIncludeVersion = true;
// Signifies that the QuicPacket will contain path id.
const bool kIncludePathId = true;
+// Stream ID is reserved to denote an invalid ID.
+const QuicStreamId kInvalidStreamId = 0;
+
// Reserved ID for the crypto stream.
const QuicStreamId kCryptoStreamId = 1;
@@ -474,7 +477,7 @@ AdjustErrorForVersion(QuicRstStreamErrorCode error_code, QuicVersion version);
// These values must remain stable as they are uploaded to UMA histograms.
// To add a new error code, use the current value of QUIC_LAST_ERROR and
// increment QUIC_LAST_ERROR.
-// last value = 77
+// last value = 78
enum QuicErrorCode {
QUIC_NO_ERROR = 0,
@@ -494,6 +497,8 @@ enum QuicErrorCode {
QUIC_INVALID_STREAM_DATA = 46,
// STREAM frame data is not encrypted.
QUIC_UNENCRYPTED_STREAM_DATA = 61,
+ // FEC frame data is not encrypted.
+ QUIC_UNENCRYPTED_FEC_DATA = 77,
// RST_STREAM frame data is malformed.
QUIC_INVALID_RST_STREAM_DATA = 6,
// CONNECTION_CLOSE frame data is malformed.
@@ -635,7 +640,7 @@ enum QuicErrorCode {
QUIC_VERSION_NEGOTIATION_MISMATCH = 55,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 77,
+ QUIC_LAST_ERROR = 78,
};
// Must be updated any time a QuicErrorCode is deprecated.
@@ -1255,6 +1260,8 @@ struct NET_EXPORT_PRIVATE SerializedPacket {
bool is_fec_packet;
bool has_ack;
bool has_stop_waiting;
+ QuicPacketNumber original_packet_number;
+ TransmissionType transmission_type;
// Optional notifiers which will be informed when this packet has been ACKed.
std::list<AckListenerWrapper> listeners;
diff --git a/net/quic/quic_sent_packet_manager.cc b/net/quic/quic_sent_packet_manager.cc
index 3a39ada..6b71c70 100644
--- a/net/quic/quic_sent_packet_manager.cc
+++ b/net/quic/quic_sent_packet_manager.cc
@@ -11,6 +11,7 @@
#include "net/quic/congestion_control/pacing_sender.h"
#include "net/quic/crypto/crypto_protocol.h"
#include "net/quic/proto/cached_network_parameters.pb.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_connection_stats.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_utils_chromium.h"
@@ -392,6 +393,16 @@ void QuicSentPacketManager::MarkForRetransmission(
pending_retransmissions_[packet_number] = transmission_type;
}
+void QuicSentPacketManager::RecordOneSpuriousRetransmission(
+ const TransmissionInfo& info) {
+ stats_->bytes_spuriously_retransmitted += info.bytes_sent;
+ ++stats_->packets_spuriously_retransmitted;
+ if (debug_delegate_ != nullptr) {
+ debug_delegate_->OnSpuriousPacketRetransmission(info.transmission_type,
+ info.bytes_sent);
+ }
+}
+
void QuicSentPacketManager::RecordSpuriousRetransmissions(
const TransmissionInfo& info,
QuicPacketNumber acked_packet_number) {
@@ -401,12 +412,7 @@ void QuicSentPacketManager::RecordSpuriousRetransmissions(
const TransmissionInfo& retransmit_info =
unacked_packets_.GetTransmissionInfo(retransmission);
retransmission = retransmit_info.retransmission;
- stats_->bytes_spuriously_retransmitted += retransmit_info.bytes_sent;
- ++stats_->packets_spuriously_retransmitted;
- if (debug_delegate_ != nullptr) {
- debug_delegate_->OnSpuriousPacketRetransmission(
- retransmit_info.transmission_type, retransmit_info.bytes_sent);
- }
+ RecordOneSpuriousRetransmission(retransmit_info);
}
return;
}
@@ -417,20 +423,15 @@ void QuicSentPacketManager::RecordSpuriousRetransmissions(
// ianswett: Prevents crash in b/20552846.
if (*it < unacked_packets_.GetLeastUnacked() ||
*it > unacked_packets_.largest_sent_packet()) {
- LOG(DFATAL) << "Retransmission out of range:" << *it
- << " least unacked:" << unacked_packets_.GetLeastUnacked()
- << " largest sent:" << unacked_packets_.largest_sent_packet();
+ QUIC_BUG << "Retransmission out of range:" << *it
+ << " least unacked:" << unacked_packets_.GetLeastUnacked()
+ << " largest sent:" << unacked_packets_.largest_sent_packet();
return;
}
const TransmissionInfo& retransmit_info =
unacked_packets_.GetTransmissionInfo(*it);
- stats_->bytes_spuriously_retransmitted += retransmit_info.bytes_sent;
- ++stats_->packets_spuriously_retransmitted;
- if (debug_delegate_ != nullptr) {
- debug_delegate_->OnSpuriousPacketRetransmission(
- retransmit_info.transmission_type, retransmit_info.bytes_sent);
- }
+ RecordOneSpuriousRetransmission(retransmit_info);
}
}
@@ -569,9 +570,9 @@ bool QuicSentPacketManager::OnPacketSent(
if (original_packet_number != 0) {
if (!pending_retransmissions_.erase(original_packet_number)) {
- DLOG(DFATAL) << "Expected packet number to be in "
- << "pending_retransmissions_. packet_number: "
- << original_packet_number;
+ QUIC_BUG << "Expected packet number to be in "
+ << "pending_retransmissions_. packet_number: "
+ << original_packet_number;
}
}
@@ -787,8 +788,8 @@ bool QuicSentPacketManager::MaybeUpdateRTT(const QuicAckFrame& ack_frame,
unacked_packets_.GetTransmissionInfo(ack_frame.largest_observed);
// Ensure the packet has a valid sent time.
if (transmission_info.sent_time == QuicTime::Zero()) {
- LOG(DFATAL) << "Acked packet has zero sent time, largest_observed:"
- << ack_frame.largest_observed;
+ QUIC_BUG << "Acked packet has zero sent time, largest_observed:"
+ << ack_frame.largest_observed;
return false;
}
diff --git a/net/quic/quic_sent_packet_manager.h b/net/quic/quic_sent_packet_manager.h
index 24027a6..87dedbc 100644
--- a/net/quic/quic_sent_packet_manager.h
+++ b/net/quic/quic_sent_packet_manager.h
@@ -340,7 +340,14 @@ class NET_EXPORT_PRIVATE QuicSentPacketManager {
void MarkForRetransmission(QuicPacketNumber packet_number,
TransmissionType transmission_type);
- // Notify observers about spurious retransmits.
+ // Notify observers that packet with TransmissionInfo |info| is a spurious
+ // retransmission. It is caller's responsibility to guarantee the packet with
+ // TransmissionInfo |info| is a spurious retransmission before calling this
+ // function.
+ void RecordOneSpuriousRetransmission(const TransmissionInfo& info);
+
+ // Notify observers about spurious retransmits of packet with TransmissionInfo
+ // |info|.
void RecordSpuriousRetransmissions(const TransmissionInfo& info,
QuicPacketNumber acked_packet_number);
diff --git a/net/quic/quic_session.cc b/net/quic/quic_session.cc
index ffb2043..24a6e99 100644
--- a/net/quic/quic_session.cc
+++ b/net/quic/quic_session.cc
@@ -7,6 +7,7 @@
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_connection.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_flow_controller.h"
@@ -169,10 +170,7 @@ void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
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.
- UpdateFlowControlOnFinalReceivedByteOffset(frame.stream_id,
- frame.byte_offset);
+ HandleRstOnValidNonexistentStream(frame);
return; // Errors are handled by GetStream.
}
@@ -195,8 +193,7 @@ void QuicSession::OnConnectionClosed(QuicErrorCode error, bool from_peer) {
it->second->OnConnectionClosed(error, from_peer);
// The stream should call CloseStream as part of OnConnectionClosed.
if (dynamic_stream_map_.find(id) != dynamic_stream_map_.end()) {
- LOG(DFATAL) << ENDPOINT
- << "Stream failed to close under OnConnectionClosed";
+ QUIC_BUG << ENDPOINT << "Stream failed to close under OnConnectionClosed";
CloseStream(id);
}
}
@@ -260,7 +257,7 @@ void QuicSession::OnCanWrite() {
if (!(write_blocked_streams_.HasWriteBlockedCryptoOrHeadersStream() ||
write_blocked_streams_.HasWriteBlockedDataStreams())) {
// Writing one stream removed another!? Something's broken.
- LOG(DFATAL) << "WriteBlockedStream is missing";
+ QUIC_BUG << "WriteBlockedStream is missing";
connection_->CloseConnection(QUIC_INTERNAL_ERROR, false);
return;
}
@@ -319,7 +316,7 @@ void QuicSession::SendRstStream(QuicStreamId id,
QuicRstStreamErrorCode error,
QuicStreamOffset bytes_written) {
if (ContainsKey(static_stream_map_, id)) {
- LOG(DFATAL) << "Cannot send RST for a static stream with ID " << id;
+ QUIC_BUG << "Cannot send RST for a static stream with ID " << id;
return;
}
@@ -511,6 +508,28 @@ void QuicSession::AdjustInitialFlowControlWindows(size_t stream_window) {
}
}
+void QuicSession::HandleFrameOnNonexistentOutgoingStream(
+ QuicStreamId stream_id) {
+ DCHECK(!IsClosedStream(stream_id));
+ // Received a frame for a locally-created stream that is not currently
+ // active. This is an error.
+ CloseConnectionWithDetails(QUIC_INVALID_STREAM_ID,
+ "Data for nonexistent stream");
+}
+
+void QuicSession::HandleRstOnValidNonexistentStream(
+ const QuicRstStreamFrame& frame) {
+ // If the stream is neither originally in active streams nor created in
+ // GetOrCreateDynamicStream(), it could be a closed stream in which case its
+ // final received byte offset need to be updated.
+ if (IsClosedStream(frame.stream_id)) {
+ // The RST frame contains the final byte offset for the stream: we can now
+ // update the connection level flow controller if needed.
+ UpdateFlowControlOnFinalReceivedByteOffset(frame.stream_id,
+ frame.byte_offset);
+ }
+}
+
void QuicSession::OnNewStreamFlowControlWindow(QuicStreamOffset new_window) {
if (new_window < kMinimumFlowControlSendWindow) {
LOG(ERROR) << "Peer sent us an invalid stream flow control send window: "
@@ -653,10 +672,7 @@ ReliableQuicStream* QuicSession::GetOrCreateDynamicStream(
}
if (!IsIncomingStream(stream_id)) {
- // Received a frame for a locally-created stream that is not currently
- // active. This is an error.
- CloseConnectionWithDetails(QUIC_INVALID_STREAM_ID,
- "Data for nonexistent stream");
+ HandleFrameOnNonexistentOutgoingStream(stream_id);
return nullptr;
}
@@ -776,7 +792,7 @@ void QuicSession::MarkConnectionLevelWriteBlocked(QuicStreamId id,
<< "Priorities do not match. Got: " << static_cast<int>(priority)
<< " Expected: " << static_cast<int>(stream->Priority());
} else {
- LOG(DFATAL) << "Marking unknown stream " << id << " blocked.";
+ QUIC_BUG << "Marking unknown stream " << id << " blocked.";
}
#endif
diff --git a/net/quic/quic_session.h b/net/quic/quic_session.h
index 0ea60bb..87fa108 100644
--- a/net/quic/quic_session.h
+++ b/net/quic/quic_session.h
@@ -205,7 +205,7 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
ReliableQuicStream* GetStream(const QuicStreamId stream_id);
// Mark a stream as draining.
- void StreamDraining(QuicStreamId id);
+ virtual void StreamDraining(QuicStreamId id);
// Close the connection, if it is not already closed.
void CloseConnectionWithDetails(QuicErrorCode error, const char* details);
@@ -246,6 +246,21 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
// operations are being done on the streams at this time)
virtual void PostProcessAfterData();
+ // Performs the work required to close |stream_id|. If |locally_reset|
+ // then the stream has been reset by this endpoint, not by the peer.
+ virtual void CloseStreamInner(QuicStreamId stream_id, bool locally_reset);
+
+ // When a stream is closed locally, it may not yet know how many bytes the
+ // peer sent on that stream.
+ // When this data arrives (via stream frame w. FIN, or RST) this method
+ // is called, and correctly updates the connection level flow controller.
+ void UpdateFlowControlOnFinalReceivedByteOffset(
+ QuicStreamId id,
+ QuicStreamOffset final_byte_offset);
+
+ // Return true if given stream is peer initiated.
+ bool IsIncomingStream(QuicStreamId id) const;
+
StreamMap& static_streams() { return static_stream_map_; }
const StreamMap& static_streams() const { return static_stream_map_; }
@@ -277,22 +292,27 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
size_t GetNumLocallyClosedOutgoingStreamsHighestOffset() const;
+ QuicStreamId next_outgoing_stream_id() const {
+ return next_outgoing_stream_id_;
+ }
+
+ // Close connection when receive a frame for a locally-created nonexistant
+ // stream.
+ // Prerequisite: IsClosedStream(stream_id) == false
+ // Server session might need to override this method to allow server push
+ // stream to be promised before creating an active stream.
+ virtual void HandleFrameOnNonexistentOutgoingStream(QuicStreamId stream_id);
+
+ // If stream is a locally closed stream, this RST will update FIN offset.
+ // Otherwise stream is a preserved stream and the behavior of it depends on
+ // derived class's own implementation.
+ virtual void HandleRstOnValidNonexistentStream(
+ const QuicRstStreamFrame& frame);
+
private:
friend class test::QuicSessionPeer;
friend class VisitorShim;
- // Performs the work required to close |stream_id|. If |locally_reset|
- // then the stream has been reset by this endpoint, not by the peer.
- void CloseStreamInner(QuicStreamId stream_id, bool locally_reset);
-
- // When a stream is closed locally, it may not yet know how many bytes the
- // peer sent on that stream.
- // When this data arrives (via stream frame w. FIN, or RST) this method
- // is called, and correctly updates the connection level flow controller.
- void UpdateFlowControlOnFinalReceivedByteOffset(
- QuicStreamId id,
- QuicStreamOffset final_byte_offset);
-
// Called in OnConfigNegotiated when we receive a new stream level flow
// control window in a negotiated config. Closes the connection if invalid.
void OnNewStreamFlowControlWindow(QuicStreamOffset new_window);
@@ -309,9 +329,6 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
// starting with smaller flow control receive windows and auto-tuning.
void AdjustInitialFlowControlWindows(size_t stream_window);
- // Return true if given stream is peer initiated.
- bool IsIncomingStream(QuicStreamId id) const;
-
// Keep track of highest received byte offset of locally closed streams, while
// waiting for a definitive final highest offset from the peer.
std::map<QuicStreamId, QuicStreamOffset>
diff --git a/net/quic/quic_session_test.cc b/net/quic/quic_session_test.cc
index f727449..6d71a77d 100644
--- a/net/quic/quic_session_test.cc
+++ b/net/quic/quic_session_test.cc
@@ -204,6 +204,7 @@ class QuicSessionTestBase : public ::testing::TestWithParam<QuicVersion> {
perspective,
SupportedVersions(GetParam()))),
session_(connection_) {
+ FLAGS_quic_always_log_bugs_for_tests = true;
session_.config()->SetInitialStreamFlowControlWindowToSend(
kInitialStreamFlowControlWindowForTest);
session_.config()->SetInitialSessionFlowControlWindowToSend(
diff --git a/net/quic/quic_spdy_session.cc b/net/quic/quic_spdy_session.cc
index cdf2749..fc420ba 100644
--- a/net/quic/quic_spdy_session.cc
+++ b/net/quic/quic_spdy_session.cc
@@ -4,6 +4,7 @@
#include "net/quic/quic_spdy_session.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_headers_stream.h"
namespace net {
@@ -93,4 +94,17 @@ QuicSpdyStream* QuicSpdySession::GetSpdyDataStream(
return static_cast<QuicSpdyStream*>(GetOrCreateDynamicStream(stream_id));
}
+void QuicSpdySession::OnPromiseHeaders(QuicStreamId stream_id,
+ StringPiece headers_data) {
+ QUIC_BUG << "OnPromiseHeaders should be overriden in client code.";
+ connection()->CloseConnection(QUIC_INTERNAL_ERROR, false);
+}
+
+void QuicSpdySession::OnPromiseHeadersComplete(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len) {
+ QUIC_BUG << "OnPromiseHeadersComplete shoule be overriden in client code.";
+ connection()->CloseConnection(QUIC_INTERNAL_ERROR, false);
+}
+
} // namespace net
diff --git a/net/quic/quic_spdy_session.h b/net/quic/quic_spdy_session.h
index d660af7..246a6de 100644
--- a/net/quic/quic_spdy_session.h
+++ b/net/quic/quic_spdy_session.h
@@ -42,6 +42,18 @@ class NET_EXPORT_PRIVATE QuicSpdySession : public QuicSession {
bool fin,
size_t frame_len);
+ // Called by |headers_stream_| when push promise headers have been
+ // received for a stream.
+ virtual void OnPromiseHeaders(QuicStreamId stream_id,
+ StringPiece headers_data);
+
+ // Called by |headers_stream_| when push promise headers have been
+ // completely received. |fin| will be true if the fin flag was set
+ // in the headers.
+ virtual void OnPromiseHeadersComplete(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len);
+
// Writes |headers| for the stream |id| to the dedicated headers stream.
// If |fin| is true, then no more data will be sent for the stream |id|.
// If provided, |ack_notifier_delegate| will be registered to be notified when
diff --git a/net/quic/quic_spdy_stream.cc b/net/quic/quic_spdy_stream.cc
index 4f96777..394bea1 100644
--- a/net/quic/quic_spdy_stream.cc
+++ b/net/quic/quic_spdy_stream.cc
@@ -191,6 +191,20 @@ void QuicSpdyStream::OnInitialHeadersComplete(bool fin, size_t /*frame_len*/) {
}
}
+void QuicSpdyStream::OnPromiseHeaders(StringPiece headers_data) {
+ headers_data.AppendToString(&decompressed_headers_);
+}
+
+void QuicSpdyStream::OnPromiseHeadersComplete(
+ QuicStreamId /* promised_stream_id */,
+ size_t /* frame_len */) {
+ // To be overridden in QuicSpdyClientStream. Not supported on
+ // server side.
+ session()->CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Promise headers received by server");
+ return;
+}
+
void QuicSpdyStream::OnTrailingHeadersComplete(bool fin, size_t /*frame_len*/) {
DCHECK(!trailers_decompressed_);
if (fin_received()) {
diff --git a/net/quic/quic_spdy_stream.h b/net/quic/quic_spdy_stream.h
index d49d650..d821f77 100644
--- a/net/quic/quic_spdy_stream.h
+++ b/net/quic/quic_spdy_stream.h
@@ -88,6 +88,17 @@ 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);
+ // Called by the session when decompressed PUSH_PROMISE headers data
+ // is received for this stream.
+ // May be called multiple times, with each call providing additional headers
+ // data until OnPromiseHeadersComplete is called.
+ virtual void OnPromiseHeaders(StringPiece headers_data);
+
+ // Called by the session when decompressed push promise headers have
+ // been completely delivered to this stream.
+ virtual void OnPromiseHeadersComplete(QuicStreamId promised_id,
+ 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;
@@ -145,6 +156,7 @@ class NET_EXPORT_PRIVATE QuicSpdyStream : public ReliableQuicStream {
// trailing) headers are expected next.
virtual void OnInitialHeadersComplete(bool fin, size_t frame_len);
virtual void OnTrailingHeadersComplete(bool fin, size_t frame_len);
+ QuicSpdySession* spdy_session() const { return spdy_session_; }
// Returns true if headers have been fully read and consumed.
bool FinishedReadingHeaders() const;
diff --git a/net/quic/quic_stream_sequencer.cc b/net/quic/quic_stream_sequencer.cc
index ec332e8..1c4f580 100644
--- a/net/quic/quic_stream_sequencer.cc
+++ b/net/quic/quic_stream_sequencer.cc
@@ -9,6 +9,7 @@
#include <utility>
#include "base/logging.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_clock.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_frame_list.h"
@@ -158,9 +159,9 @@ void QuicStreamSequencer::MarkConsumed(size_t num_bytes_consumed) {
DCHECK(!blocked_);
bool result = buffered_frames_->MarkConsumed(num_bytes_consumed);
if (!result) {
- LOG(DFATAL) << "Invalid argument to MarkConsumed."
- << " expect to consume: " << num_bytes_consumed
- << ", but not enough bytes available.";
+ QUIC_BUG << "Invalid argument to MarkConsumed."
+ << " expect to consume: " << num_bytes_consumed
+ << ", but not enough bytes available.";
stream_->Reset(QUIC_ERROR_PROCESSING_STREAM);
return;
}
diff --git a/net/quic/quic_unacked_packet_map.cc b/net/quic/quic_unacked_packet_map.cc
index 74dde00..6beb9bc 100644
--- a/net/quic/quic_unacked_packet_map.cc
+++ b/net/quic/quic_unacked_packet_map.cc
@@ -5,7 +5,7 @@
#include "net/quic/quic_unacked_packet_map.h"
#include "base/logging.h"
-#include "base/stl_util.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_connection_stats.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_utils_chromium.h"
@@ -93,9 +93,9 @@ void QuicUnackedPacketMap::TransferRetransmissionInfo(
TransmissionInfo* info) {
if (old_packet_number < least_unacked_ ||
old_packet_number > largest_sent_packet_) {
- LOG(DFATAL) << "Old TransmissionInfo no longer exists for:"
- << old_packet_number << " least_unacked:" << least_unacked_
- << " largest_sent:" << largest_sent_packet_;
+ QUIC_BUG << "Old TransmissionInfo no longer exists for:"
+ << old_packet_number << " least_unacked:" << least_unacked_
+ << " largest_sent:" << largest_sent_packet_;
return;
}
DCHECK_GE(new_packet_number, least_unacked_ + unacked_packets_.size());
@@ -359,7 +359,7 @@ QuicTime QuicUnackedPacketMap::GetLastPacketSentTime() const {
}
++it;
}
- LOG(DFATAL) << "GetLastPacketSentTime requires in flight packets.";
+ QUIC_BUG << "GetLastPacketSentTime requires in flight packets.";
return QuicTime::Zero();
}
diff --git a/net/quic/quic_unacked_packet_map_test.cc b/net/quic/quic_unacked_packet_map_test.cc
index 97c94be..1adc819 100644
--- a/net/quic/quic_unacked_packet_map_test.cc
+++ b/net/quic/quic_unacked_packet_map_test.cc
@@ -30,10 +30,8 @@ class QuicUnackedPacketMapTest : public ::testing::Test {
~QuicUnackedPacketMapTest() override { STLDeleteElements(&packets_); }
SerializedPacket CreateRetransmittablePacket(QuicPacketNumber packet_number) {
- packets_.push_back(new QuicEncryptedPacket(nullptr, kDefaultLength));
- return SerializedPacket(kDefaultPathId, packet_number,
- PACKET_1BYTE_PACKET_NUMBER, packets_.back(), 0,
- new RetransmittableFrames(), false, false);
+ return CreateRetransmittablePacketForStream(packet_number,
+ kHeadersStreamId);
}
SerializedPacket CreateRetransmittablePacketForStream(
diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc
index 792aca4..733a517 100644
--- a/net/quic/quic_utils.cc
+++ b/net/quic/quic_utils.cc
@@ -274,6 +274,7 @@ const char* QuicUtils::ErrorToString(QuicErrorCode error) {
RETURN_STRING_LITERAL(QUIC_TIMEOUTS_WITH_OPEN_STREAMS);
RETURN_STRING_LITERAL(QUIC_FAILED_TO_SERIALIZE_PACKET);
RETURN_STRING_LITERAL(QUIC_TOO_MANY_AVAILABLE_STREAMS);
+ RETURN_STRING_LITERAL(QUIC_UNENCRYPTED_FEC_DATA);
RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
// Intentionally have no default case, so we'll break the build
// if we add errors and don't put them here.
diff --git a/net/quic/reliable_quic_stream.cc b/net/quic/reliable_quic_stream.cc
index f590074..f3ff5414 100644
--- a/net/quic/reliable_quic_stream.cc
+++ b/net/quic/reliable_quic_stream.cc
@@ -7,6 +7,7 @@
#include "base/logging.h"
#include "net/quic/iovector.h"
#include "net/quic/quic_ack_listener_interface.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_flow_controller.h"
#include "net/quic/quic_session.h"
@@ -186,12 +187,12 @@ void ReliableQuicStream::WriteOrBufferData(
bool fin,
QuicAckListenerInterface* ack_listener) {
if (data.empty() && !fin) {
- LOG(DFATAL) << "data.empty() && !fin";
+ QUIC_BUG << "data.empty() && !fin";
return;
}
if (fin_buffered_) {
- LOG(DFATAL) << "Fin already buffered";
+ QUIC_BUG << "Fin already buffered";
return;
}
if (write_side_closed_) {
@@ -229,9 +230,8 @@ void ReliableQuicStream::OnCanWrite() {
pending_data->offset >= pending_data->data.size()) {
// This should be impossible because offset tracks the amount of
// pending_data written thus far.
- LOG(DFATAL) << "Pending offset is beyond available data. offset: "
- << pending_data->offset
- << " vs: " << pending_data->data.size();
+ QUIC_BUG << "Pending offset is beyond available data. offset: "
+ << pending_data->offset << " vs: " << pending_data->data.size();
return;
}
size_t remaining_len = pending_data->data.size() - pending_data->offset;
diff --git a/net/quic/spdy_utils.cc b/net/quic/spdy_utils.cc
index 3ff1527..6f2d29f 100644
--- a/net/quic/spdy_utils.cc
+++ b/net/quic/spdy_utils.cc
@@ -13,6 +13,7 @@
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"
+#include "url/gurl.h"
using std::string;
using std::vector;
@@ -105,4 +106,31 @@ bool SpdyUtils::ParseTrailers(const char* data,
return true;
}
+// static
+string SpdyUtils::GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers) {
+ SpdyHeaderBlock::const_iterator it = headers.find(":scheme");
+ if (it == headers.end())
+ return "";
+ std::string url = it->second.as_string();
+
+ url.append("://");
+
+ it = headers.find(":authority");
+ if (it == headers.end())
+ return "";
+ url.append(it->second.as_string());
+
+ it = headers.find(":path");
+ if (it == headers.end())
+ return "";
+ url.append(it->second.as_string());
+ return url;
+}
+
+// static
+bool SpdyUtils::UrlIsValid(const SpdyHeaderBlock& headers) {
+ string url(GetUrlFromHeaderBlock(headers));
+ return url != "" && GURL(url).is_valid();
+}
+
} // namespace net
diff --git a/net/quic/spdy_utils.h b/net/quic/spdy_utils.h
index b0d8a59..2351e57 100644
--- a/net/quic/spdy_utils.h
+++ b/net/quic/spdy_utils.h
@@ -43,6 +43,14 @@ class NET_EXPORT_PRIVATE SpdyUtils {
size_t* final_byte_offset,
SpdyHeaderBlock* trailers);
+ // Returns URL composed from scheme, authority, and path header
+ // values, or empty string if any of those fields are missing.
+ static std::string GetUrlFromHeaderBlock(const net::SpdyHeaderBlock& headers);
+
+ // Returns true if result of |GetUrlFromHeaderBlock()| is non-empty
+ // and is a well-formed URL.
+ static bool UrlIsValid(const net::SpdyHeaderBlock& headers);
+
private:
DISALLOW_COPY_AND_ASSIGN(SpdyUtils);
};
diff --git a/net/quic/spdy_utils_test.cc b/net/quic/spdy_utils_test.cc
new file mode 100644
index 0000000..b727638
--- /dev/null
+++ b/net/quic/spdy_utils_test.cc
@@ -0,0 +1,140 @@
+// Copyright 2016 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/spdy_utils.h"
+
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/test/gtest_util.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+
+TEST(SpdyUtilsTest, SerializeAndParseHeaders) {
+ // Creates a SpdyHeaderBlock with some key->value pairs, serializes it, then
+ // parses the serialized output and verifies that the end result is the same
+ // as the headers that the test started with.
+
+ SpdyHeaderBlock input_headers;
+ input_headers[":pseudo1"] = "pseudo value1";
+ input_headers[":pseudo2"] = "pseudo value2";
+ input_headers["key1"] = "value1";
+ const int kContentLength = 1234;
+ input_headers["content-length"] = base::IntToString(kContentLength);
+ input_headers["key2"] = "value2";
+
+ // Serialize the header block.
+ string serialized_headers =
+ SpdyUtils::SerializeUncompressedHeaders(input_headers);
+
+ // Take the serialized header block, and parse back into SpdyHeaderBlock.
+ SpdyHeaderBlock output_headers;
+ int content_length = -1;
+ ASSERT_TRUE(SpdyUtils::ParseHeaders(serialized_headers.data(),
+ serialized_headers.size(),
+ &content_length, &output_headers));
+
+ // Should be back to the original headers.
+ EXPECT_EQ(content_length, kContentLength);
+ EXPECT_EQ(output_headers, input_headers);
+}
+
+TEST(SpdyUtilsTest, SerializeAndParseValidTrailers) {
+ // Creates a SpdyHeaderBlock with some valid Trailers key->value pairs,
+ // serializes it, then parses the serialized output and verifies that the end
+ // result is the same as the trailers that the test started with.
+ SpdyHeaderBlock input_trailers;
+ const size_t kFinalOffset = 5678;
+ input_trailers[kFinalOffsetHeaderKey] = base::IntToString(kFinalOffset);
+ input_trailers["key1"] = "value1";
+ input_trailers["key2"] = "value2";
+
+ // Serialize the trailers.
+ string serialized_trailers =
+ SpdyUtils::SerializeUncompressedHeaders(input_trailers);
+
+ // Take the serialized trailers, and parse back into a SpdyHeaderBlock.
+ SpdyHeaderBlock output_trailers;
+ size_t final_byte_offset = 0;
+ EXPECT_TRUE(SpdyUtils::ParseTrailers(serialized_trailers.data(),
+ serialized_trailers.size(),
+ &final_byte_offset, &output_trailers));
+
+ // Should be back to the original trailers, without the final offset header.
+ EXPECT_EQ(final_byte_offset, kFinalOffset);
+ input_trailers.erase(kFinalOffsetHeaderKey);
+ EXPECT_EQ(output_trailers, input_trailers);
+}
+
+TEST(SpdyUtilsTest, SerializeAndParseTrailersWithoutFinalOffset) {
+ // Verifies that parsing fails if Trailers are missing a final offset header.
+
+ SpdyHeaderBlock input_trailers;
+ input_trailers["key1"] = "value1";
+ input_trailers["key2"] = "value2";
+
+ // Serialize the trailers.
+ string serialized_trailers =
+ SpdyUtils::SerializeUncompressedHeaders(input_trailers);
+
+ // Parsing the serialized trailers fails because of the missing final offset.
+ SpdyHeaderBlock output_trailers;
+ size_t final_byte_offset = 0;
+ EXPECT_FALSE(SpdyUtils::ParseTrailers(serialized_trailers.data(),
+ serialized_trailers.size(),
+ &final_byte_offset, &output_trailers));
+ EXPECT_EQ(final_byte_offset, 0u);
+}
+
+TEST(SpdyUtilsTest, SerializeAndParseTrailersWithPseudoHeaders) {
+ // Verifies that parsing fails if Trailers include pseudo-headers.
+
+ SpdyHeaderBlock input_trailers;
+ input_trailers[kFinalOffsetHeaderKey] = "12345";
+ input_trailers[":disallowed-pseudo-header"] = "pseudo value";
+ input_trailers["key1"] = "value1";
+ input_trailers["key2"] = "value2";
+
+ // Serialize the trailers.
+ string serialized_trailers =
+ SpdyUtils::SerializeUncompressedHeaders(input_trailers);
+
+ // Parsing the serialized trailers fails because of the extra pseudo header.
+ SpdyHeaderBlock output_trailers;
+ size_t final_byte_offset = 0;
+ EXPECT_FALSE(SpdyUtils::ParseTrailers(serialized_trailers.data(),
+ serialized_trailers.size(),
+ &final_byte_offset, &output_trailers));
+}
+
+TEST(SpdyUtilsTest, GetUrlFromHeaderBlock) {
+ SpdyHeaderBlock headers;
+ EXPECT_EQ(SpdyUtils::GetUrlFromHeaderBlock(headers), "");
+ headers[":scheme"] = "https";
+ EXPECT_EQ(SpdyUtils::GetUrlFromHeaderBlock(headers), "");
+ headers[":authority"] = "www.google.com";
+ EXPECT_EQ(SpdyUtils::GetUrlFromHeaderBlock(headers), "");
+ headers[":path"] = "/index.html";
+ EXPECT_EQ(SpdyUtils::GetUrlFromHeaderBlock(headers),
+ "https://www.google.com/index.html");
+ headers["key1"] = "value1";
+ headers["key2"] = "value2";
+ EXPECT_EQ(SpdyUtils::GetUrlFromHeaderBlock(headers),
+ "https://www.google.com/index.html");
+}
+
+TEST(SpdyUtilsTest, UrlIsValid) {
+ SpdyHeaderBlock headers;
+ EXPECT_FALSE(SpdyUtils::UrlIsValid(headers));
+ headers[":scheme"] = "https";
+ EXPECT_FALSE(SpdyUtils::UrlIsValid(headers));
+ headers[":authority"] = "www.google.com";
+ EXPECT_FALSE(SpdyUtils::UrlIsValid(headers));
+ headers[":path"] = "/index.html";
+ EXPECT_TRUE(SpdyUtils::UrlIsValid(headers));
+}
+
+} // namespace test
+} // namespace net_quic
diff --git a/net/quic/stream_sequencer_buffer.cc b/net/quic/stream_sequencer_buffer.cc
index a54f13c..5b98452 100644
--- a/net/quic/stream_sequencer_buffer.cc
+++ b/net/quic/stream_sequencer_buffer.cc
@@ -5,6 +5,7 @@
#include "net/quic/stream_sequencer_buffer.h"
#include "base/logging.h"
+#include "net/quic/quic_bug_tracker.h"
using std::min;
@@ -64,7 +65,7 @@ QuicErrorCode StreamSequencerBuffer::OnStreamData(
QuicStreamOffset offset = starting_offset;
size_t size = data.size();
if (size == 0) {
- LOG(DFATAL) << "Attempted to write 0 bytes of data.";
+ QUIC_BUG << "Attempted to write 0 bytes of data.";
return QUIC_INVALID_STREAM_FRAME;
}
diff --git a/net/quic/test_tools/quic_connection_peer.cc b/net/quic/test_tools/quic_connection_peer.cc
index c2aad97..f171bff 100644
--- a/net/quic/test_tools/quic_connection_peer.cc
+++ b/net/quic/test_tools/quic_connection_peer.cc
@@ -129,6 +129,11 @@ bool QuicConnectionPeer::IsSilentCloseEnabled(QuicConnection* connection) {
}
// static
+bool QuicConnectionPeer::IsMultipathEnabled(QuicConnection* connection) {
+ return connection->multipath_enabled_;
+}
+
+// static
void QuicConnectionPeer::SwapCrypters(QuicConnection* connection,
QuicFramer* framer) {
QuicFramerPeer::SwapCrypters(framer, &connection->framer_);
diff --git a/net/quic/test_tools/quic_connection_peer.h b/net/quic/test_tools/quic_connection_peer.h
index 2f97c51..524eb15 100644
--- a/net/quic/test_tools/quic_connection_peer.h
+++ b/net/quic/test_tools/quic_connection_peer.h
@@ -80,6 +80,8 @@ class QuicConnectionPeer {
static bool IsSilentCloseEnabled(QuicConnection* connection);
+ static bool IsMultipathEnabled(QuicConnection* connection);
+
static void SwapCrypters(QuicConnection* connection, QuicFramer* framer);
static QuicConnectionHelperInterface* GetHelper(QuicConnection* connection);
diff --git a/net/quic/test_tools/quic_test_utils.cc b/net/quic/test_tools/quic_test_utils.cc
index ada8262..bd4b6f5 100644
--- a/net/quic/test_tools/quic_test_utils.cc
+++ b/net/quic/test_tools/quic_test_utils.cc
@@ -31,18 +31,6 @@ using testing::_;
namespace net {
namespace test {
-namespace {
-
-// No-op alarm implementation used by MockConnectionHelper.
-class TestAlarm : public QuicAlarm {
- public:
- explicit TestAlarm(QuicAlarm::Delegate* delegate) : QuicAlarm(delegate) {}
-
- void SetImpl() override {}
- void CancelImpl() override {}
-};
-
-} // namespace
QuicAckFrame MakeAckFrame(QuicPacketNumber largest_observed) {
QuicAckFrame ack;
@@ -206,7 +194,7 @@ QuicRandom* MockConnectionHelper::GetRandomGenerator() {
}
QuicAlarm* MockConnectionHelper::CreateAlarm(QuicAlarm::Delegate* delegate) {
- return new TestAlarm(delegate);
+ return new MockConnectionHelper::TestAlarm(delegate);
}
QuicBufferAllocator* MockConnectionHelper::GetBufferAllocator() {
@@ -294,17 +282,16 @@ PacketSavingConnection::~PacketSavingConnection() {
STLDeleteElements(&encrypted_packets_);
}
-void PacketSavingConnection::SendOrQueuePacket(QueuedPacket packet) {
- if (!packet.serialized_packet.packet->owns_buffer()) {
- scoped_ptr<QuicEncryptedPacket> encrypted_deleter(
- packet.serialized_packet.packet);
- packet.serialized_packet.packet = packet.serialized_packet.packet->Clone();
+void PacketSavingConnection::SendOrQueuePacket(SerializedPacket* packet) {
+ if (!packet->packet->owns_buffer()) {
+ scoped_ptr<QuicEncryptedPacket> encrypted_deleter(packet->packet);
+ packet->packet = packet->packet->Clone();
}
- encrypted_packets_.push_back(packet.serialized_packet.packet);
+ encrypted_packets_.push_back(packet->packet);
// Transfer ownership of the packet to the SentPacketManager and the
// ack notifier to the AckNotifierManager.
- sent_packet_manager_.OnPacketSent(&packet.serialized_packet, 0,
- QuicTime::Zero(), 1000, NOT_RETRANSMISSION,
+ sent_packet_manager_.OnPacketSent(packet, 0, QuicTime::Zero(), 1000,
+ NOT_RETRANSMISSION,
HAS_RETRANSMITTABLE_DATA);
}
diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h
index 6868410..e1eb147 100644
--- a/net/quic/test_tools/quic_test_utils.h
+++ b/net/quic/test_tools/quic_test_utils.h
@@ -305,6 +305,21 @@ class MockConnectionHelper : public QuicConnectionHelperInterface {
QuicBufferAllocator* GetBufferAllocator() override;
void AdvanceTime(QuicTime::Delta delta);
+ // No-op alarm implementation
+ class TestAlarm : public QuicAlarm {
+ public:
+ explicit TestAlarm(QuicAlarm::Delegate* delegate) : QuicAlarm(delegate) {}
+
+ void SetImpl() override {}
+ void CancelImpl() override {}
+
+ using QuicAlarm::Fire;
+ };
+
+ void FireAlarm(QuicAlarm* alarm) {
+ reinterpret_cast<TestAlarm*>(alarm)->Fire();
+ }
+
private:
MockClock clock_;
MockRandom random_generator_;
@@ -416,7 +431,7 @@ class PacketSavingConnection : public MockConnection {
~PacketSavingConnection() override;
- void SendOrQueuePacket(QueuedPacket packet) override;
+ void SendOrQueuePacket(SerializedPacket* packet) override;
std::vector<QuicEncryptedPacket*> encrypted_packets_;
@@ -454,6 +469,12 @@ class MockQuicSpdySession : public QuicSpdySession {
void(QuicStreamId stream_id, SpdyPriority priority));
MOCK_METHOD3(OnStreamHeadersComplete,
void(QuicStreamId stream_id, bool fin, size_t frame_len));
+ MOCK_METHOD2(OnPromiseHeaders,
+ void(QuicStreamId stream_id, StringPiece headers_data));
+ MOCK_METHOD3(OnPromiseHeadersComplete,
+ void(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len));
MOCK_METHOD0(IsCryptoHandshakeConfirmed, bool());
MOCK_METHOD5(WriteHeaders,
size_t(QuicStreamId id,
diff --git a/net/tools/quic/end_to_end_test.cc b/net/tools/quic/end_to_end_test.cc
index 6e40815..4e30c39 100644
--- a/net/tools/quic/end_to_end_test.cc
+++ b/net/tools/quic/end_to_end_test.cc
@@ -811,8 +811,14 @@ TEST_P(EndToEndTest, LargePostZeroRTTFailure) {
client_->WaitForResponseForMs(-1);
ASSERT_TRUE(client_->client()->connected());
EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request));
- EXPECT_EQ(1, client_->client()->session()->GetNumSentClientHellos());
- EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ if (FLAGS_require_strike_register_or_server_nonce) {
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->session()->GetNumSentClientHellos());
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->session()->GetNumSentClientHellos());
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
client_->Disconnect();
@@ -863,8 +869,14 @@ TEST_P(EndToEndTest, SynchronousRequestZeroRTTFailure) {
client_->WaitForInitialResponse();
ASSERT_TRUE(client_->client()->connected());
EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
- EXPECT_EQ(1, client_->client()->session()->GetNumSentClientHellos());
- EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ if (FLAGS_require_strike_register_or_server_nonce) {
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->session()->GetNumSentClientHellos());
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->session()->GetNumSentClientHellos());
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
client_->Disconnect();
@@ -921,8 +933,14 @@ TEST_P(EndToEndTest, LargePostSynchronousRequest) {
client_->WaitForInitialResponse();
ASSERT_TRUE(client_->client()->connected());
EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request));
- EXPECT_EQ(1, client_->client()->session()->GetNumSentClientHellos());
- EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ if (FLAGS_require_strike_register_or_server_nonce) {
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->session()->GetNumSentClientHellos());
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->session()->GetNumSentClientHellos());
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
client_->Disconnect();
diff --git a/net/tools/quic/quic_client_session.h b/net/tools/quic/quic_client_session.h
index f08d517..6d25b59 100644
--- a/net/tools/quic/quic_client_session.h
+++ b/net/tools/quic/quic_client_session.h
@@ -23,6 +23,10 @@ class ReliableQuicStream;
namespace tools {
+// The maximum time a promises stream can be reserved without being
+// claimed by a client request.
+const int64_t kPushPromiseTimeoutSecs = 60;
+
class QuicClientSession : public QuicClientSessionBase {
public:
QuicClientSession(const QuicConfig& config,
@@ -72,6 +76,7 @@ class QuicClientSession : public QuicClientSessionBase {
QuicCryptoClientConfig* crypto_config() { return crypto_config_; }
private:
+ bool ShouldCreateIncomingDynamicStream(QuicStreamId id);
scoped_ptr<QuicCryptoClientStreamBase> crypto_stream_;
QuicServerId server_id_;
QuicCryptoClientConfig* crypto_config_;
diff --git a/net/tools/quic/quic_dispatcher.cc b/net/tools/quic/quic_dispatcher.cc
index 04c8b38..2a9d15d 100644
--- a/net/tools/quic/quic_dispatcher.cc
+++ b/net/tools/quic/quic_dispatcher.cc
@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/stl_util.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_utils.h"
#include "net/tools/quic/quic_per_connection_packet_writer.h"
@@ -401,10 +402,11 @@ void QuicDispatcher::OnConnectionClosed(QuicConnectionId connection_id,
QuicErrorCode error) {
SessionMap::iterator it = session_map_.find(connection_id);
if (it == session_map_.end()) {
- LOG(DFATAL) << "ConnectionId " << connection_id
- << " does not exist in the session map. "
- << "Error: " << QuicUtils::ErrorToString(error);
- LOG(DFATAL) << base::debug::StackTrace().ToString();
+ QUIC_BUG << "ConnectionId " << connection_id
+ << " does not exist in the session map. Error: "
+ << QuicUtils::ErrorToString(error);
+ QUIC_BUG << base::debug::StackTrace().ToString();
+ ;
return;
}
diff --git a/net/tools/quic/quic_in_memory_cache.cc b/net/tools/quic/quic_in_memory_cache.cc
index 0a6b0e2..9c24841 100644
--- a/net/tools/quic/quic_in_memory_cache.cc
+++ b/net/tools/quic/quic_in_memory_cache.cc
@@ -12,6 +12,7 @@
#include "base/strings/stringprintf.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/spdy/spdy_http_utils.h"
using base::FilePath;
@@ -114,7 +115,7 @@ void QuicInMemoryCache::ResetForTests() {
void QuicInMemoryCache::InitializeFromDirectory(const string& cache_directory) {
if (cache_directory.empty()) {
- LOG(DFATAL) << "cache_directory must not be empty.";
+ QUIC_BUG << "cache_directory must not be empty.";
return;
}
VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
@@ -205,7 +206,7 @@ void QuicInMemoryCache::AddResponseImpl(
DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\"";
string key = GetKey(host, path);
if (ContainsKey(responses_, key)) {
- LOG(DFATAL) << "Response for '" << key << "' already exists!";
+ QUIC_BUG << "Response for '" << key << "' already exists!";
return;
}
Response* new_response = new Response();
diff --git a/net/tools/quic/quic_packet_reader.cc b/net/tools/quic/quic_packet_reader.cc
index 21cffdc..a6d325e 100644
--- a/net/tools/quic/quic_packet_reader.cc
+++ b/net/tools/quic/quic_packet_reader.cc
@@ -14,6 +14,7 @@
#include "base/logging.h"
#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_bug_tracker.h"
#include "net/quic/quic_flags.h"
#include "net/tools/quic/quic_dispatcher.h"
#include "net/tools/quic/quic_socket_utils.h"
@@ -88,7 +89,7 @@ bool QuicPacketReader::ReadAndDispatchPackets(
IPAddressNumber server_ip =
QuicSocketUtils::GetAddressFromMsghdr(&mmsg_hdr_[i].msg_hdr);
if (!IsInitializedAddress(server_ip)) {
- LOG(DFATAL) << "Unable to get server address.";
+ QUIC_BUG << "Unable to get server address.";
continue;
}
diff --git a/net/tools/quic/quic_simple_server_session.cc b/net/tools/quic/quic_simple_server_session.cc
index 0c6aeb8..fea72a3 100644
--- a/net/tools/quic/quic_simple_server_session.cc
+++ b/net/tools/quic/quic_simple_server_session.cc
@@ -5,12 +5,14 @@
#include "net/tools/quic/quic_simple_server_session.h"
#include "base/logging.h"
+#include "base/stl_util.h"
#include "net/quic/proto/cached_network_parameters.pb.h"
#include "net/quic/quic_connection.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_spdy_session.h"
#include "net/quic/reliable_quic_stream.h"
#include "net/tools/quic/quic_simple_server_stream.h"
+#include "url/gurl.h"
namespace net {
namespace tools {
@@ -20,7 +22,8 @@ QuicSimpleServerSession::QuicSimpleServerSession(
QuicConnection* connection,
QuicServerSessionVisitor* visitor,
const QuicCryptoServerConfig* crypto_config)
- : QuicServerSessionBase(config, connection, visitor, crypto_config) {}
+ : QuicServerSessionBase(config, connection, visitor, crypto_config),
+ highest_promised_stream_id_(0) {}
QuicSimpleServerSession::~QuicSimpleServerSession() {}
@@ -30,6 +33,41 @@ QuicSimpleServerSession::CreateQuicCryptoServerStream(
return new QuicCryptoServerStream(crypto_config, this);
}
+void QuicSimpleServerSession::StreamDraining(QuicStreamId id) {
+ QuicSpdySession::StreamDraining(id);
+ if (!IsIncomingStream(id)) {
+ HandlePromisedPushRequests();
+ }
+}
+
+void QuicSimpleServerSession::OnStreamFrame(const QuicStreamFrame& frame) {
+ if (!IsIncomingStream(frame.stream_id)) {
+ LOG(WARNING) << "Client shouldn't send data on server push stream";
+ connection()->SendConnectionCloseWithDetails(
+ QUIC_INVALID_STREAM_ID, "Client sent data on server push stream");
+ return;
+ }
+ QuicSpdySession::OnStreamFrame(frame);
+}
+
+void QuicSimpleServerSession::PromisePushResources(
+ const string& request_url,
+ const list<QuicInMemoryCache::ServerPushInfo>& resources,
+ QuicStreamId original_stream_id,
+ const SpdyHeaderBlock& original_request_headers) {
+ for (QuicInMemoryCache::ServerPushInfo resource : resources) {
+ SpdyHeaderBlock headers = SynthesizePushRequestHeaders(
+ request_url, resource, original_request_headers);
+ highest_promised_stream_id_ += 2;
+ SendPushPromise(original_stream_id, highest_promised_stream_id_, headers);
+ promised_streams_.push_back(PromisedStreamInfo(
+ headers, highest_promised_stream_id_, resource.priority));
+ }
+
+ // Procese promised push request as many as possible.
+ HandlePromisedPushRequests();
+}
+
QuicSpdyStream* QuicSimpleServerSession::CreateIncomingDynamicStream(
QuicStreamId id) {
if (!ShouldCreateIncomingDynamicStream(id)) {
@@ -52,5 +90,97 @@ QuicSimpleServerStream* QuicSimpleServerSession::CreateOutgoingDynamicStream(
return stream;
}
+void QuicSimpleServerSession::CloseStreamInner(QuicStreamId stream_id,
+ bool locally_reset) {
+ QuicSpdySession::CloseStreamInner(stream_id, locally_reset);
+ HandlePromisedPushRequests();
+}
+
+void QuicSimpleServerSession::HandleFrameOnNonexistentOutgoingStream(
+ QuicStreamId stream_id) {
+ // If this stream is a promised but not created stream (stream_id within the
+ // range of next_outgoing_stream_id_ and highes_promised_stream_id_),
+ // connection shouldn't be closed.
+ // Otherwise behave in the same way as base class.
+ if (stream_id > highest_promised_stream_id_) {
+ QuicSession::HandleFrameOnNonexistentOutgoingStream(stream_id);
+ }
+}
+
+void QuicSimpleServerSession::HandleRstOnValidNonexistentStream(
+ const QuicRstStreamFrame& frame) {
+ QuicSession::HandleRstOnValidNonexistentStream(frame);
+ if (!IsClosedStream(frame.stream_id)) {
+ // If a nonexistent stream is not a closed stream and still valid, it must
+ // be a locally preserved stream. Resetting this kind of stream means
+ // cancelling the promised server push.
+ // Since PromisedStreamInfo are queued in sequence, the corresponding
+ // index for it in promised_streams_ can be calculated.
+ DCHECK(frame.stream_id >= next_outgoing_stream_id());
+ size_t index = (frame.stream_id - next_outgoing_stream_id()) / 2;
+ DCHECK(index <= promised_streams_.size());
+ promised_streams_[index].is_cancelled = true;
+ connection()->SendRstStream(frame.stream_id, QUIC_RST_ACKNOWLEDGEMENT, 0);
+ }
+}
+
+SpdyHeaderBlock QuicSimpleServerSession::SynthesizePushRequestHeaders(
+ string request_url,
+ QuicInMemoryCache::ServerPushInfo resource,
+ const SpdyHeaderBlock& original_request_headers) {
+ GURL push_request_url = resource.request_url;
+ string path = push_request_url.path();
+
+ SpdyHeaderBlock spdy_headers = original_request_headers;
+ // :authority could be different from original request.
+ spdy_headers.ReplaceOrAppendHeader(":authority", push_request_url.host());
+ spdy_headers.ReplaceOrAppendHeader(":path", path);
+ // Push request always use GET.
+ spdy_headers.ReplaceOrAppendHeader(":method", "GET");
+ spdy_headers.ReplaceOrAppendHeader("referer", request_url);
+ spdy_headers.ReplaceOrAppendHeader(":scheme", push_request_url.scheme());
+ // It is not possible to push a response to a request that includes a request
+ // body.
+ spdy_headers.ReplaceOrAppendHeader("content-length", "0");
+ // Remove "host" field as push request is a directly generated HTTP2 request
+ // which should use ":authority" instead of "host".
+ spdy_headers.erase("host");
+ return spdy_headers;
+}
+
+void QuicSimpleServerSession::SendPushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ const SpdyHeaderBlock& headers) {
+ DVLOG(1) << "stream " << original_stream_id
+ << " send PUSH_PROMISE for promised stream " << promised_stream_id;
+ headers_stream()->WritePushPromise(original_stream_id, promised_stream_id,
+ headers, nullptr);
+}
+
+void QuicSimpleServerSession::HandlePromisedPushRequests() {
+ while (!promised_streams_.empty() && ShouldCreateOutgoingDynamicStream()) {
+ const PromisedStreamInfo& promised_info = promised_streams_.front();
+ DCHECK_EQ(next_outgoing_stream_id(), promised_info.stream_id);
+
+ if (promised_info.is_cancelled) {
+ // This stream has been reset by client. Skip this stream id.
+ promised_streams_.pop_front();
+ GetNextOutgoingStreamId();
+ return;
+ }
+
+ QuicSimpleServerStream* promised_stream =
+ static_cast<QuicSimpleServerStream*>(
+ CreateOutgoingDynamicStream(promised_info.priority));
+ DCHECK(promised_stream != nullptr);
+ DCHECK_EQ(promised_info.stream_id, promised_stream->id());
+ DVLOG(1) << "created server push stream " << promised_stream->id();
+
+ promised_stream->PushResponse(promised_info.request_headers);
+
+ promised_streams_.pop_front();
+ }
+}
+
} // namespace tools
} // namespace net
diff --git a/net/tools/quic/quic_simple_server_session.h b/net/tools/quic/quic_simple_server_session.h
index 12d2b0d..d9b2901 100644
--- a/net/tools/quic/quic_simple_server_session.h
+++ b/net/tools/quic/quic_simple_server_session.h
@@ -19,6 +19,7 @@
#include "net/quic/quic_crypto_server_stream.h"
#include "net/quic/quic_protocol.h"
#include "net/quic/quic_spdy_session.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
#include "net/tools/quic/quic_server_session_base.h"
#include "net/tools/quic/quic_simple_server_stream.h"
@@ -38,6 +39,24 @@ class QuicSimpleServerSessionPeer;
class QuicSimpleServerSession : public QuicServerSessionBase {
public:
+ // A PromisedStreamInfo is an element of the queue to store promised
+ // stream which hasn't been created yet. It keeps a mapping between promised
+ // stream id with its priority and the headers sent out in PUSH_PROMISE.
+ struct PromisedStreamInfo {
+ public:
+ PromisedStreamInfo(SpdyHeaderBlock request_headers,
+ QuicStreamId stream_id,
+ SpdyPriority priority)
+ : request_headers(request_headers),
+ stream_id(stream_id),
+ priority(priority),
+ is_cancelled(false) {}
+ SpdyHeaderBlock request_headers;
+ QuicStreamId stream_id;
+ SpdyPriority priority;
+ bool is_cancelled;
+ };
+
QuicSimpleServerSession(const QuicConfig& config,
QuicConnection* connection,
QuicServerSessionVisitor* visitor,
@@ -45,18 +64,87 @@ class QuicSimpleServerSession : public QuicServerSessionBase {
~QuicSimpleServerSession() override;
+ // When a stream is marked draining, it will decrease the number of open
+ // streams. If it is an outgoing stream, try to open a new stream to send
+ // remaing push responses.
+ void StreamDraining(QuicStreamId id) override;
+
+ // Override base class to detact client sending data on server push stream.
+ void OnStreamFrame(const QuicStreamFrame& frame) override;
+
+ // Send out PUSH_PROMISE for all |resources| promised stream id in each frame
+ // will increase by 2 for each item in |resources|.
+ // And enqueue HEADERS block in those PUSH_PROMISED for sending push response
+ // later.
+ virtual void PromisePushResources(
+ const string& request_url,
+ const list<QuicInMemoryCache::ServerPushInfo>& resources,
+ QuicStreamId original_stream_id,
+ const SpdyHeaderBlock& original_request_headers);
+
protected:
// QuicSession methods:
QuicSpdyStream* CreateIncomingDynamicStream(QuicStreamId id) override;
QuicSimpleServerStream* CreateOutgoingDynamicStream(
SpdyPriority priority) override;
-
+ // Closing an outgoing stream can reduce open outgoing stream count, try
+ // to handle queued promised streams right now.
+ void CloseStreamInner(QuicStreamId stream_id, bool locally_reset) override;
+ // Override to return true for locally preserved server push stream.
+ void HandleFrameOnNonexistentOutgoingStream(QuicStreamId stream_id) override;
+ // Override to handle reseting locally preserved streams.
+ void HandleRstOnValidNonexistentStream(
+ const QuicRstStreamFrame& frame) override;
+
+ // QuicServerSessionBaseMethod:
QuicCryptoServerStreamBase* CreateQuicCryptoServerStream(
const QuicCryptoServerConfig* crypto_config) override;
private:
friend class test::QuicSimpleServerSessionPeer;
+ // Create a server push headers block by copying request's headers block.
+ // But replace or add these pseudo-headers as they are specific to each
+ // request:
+ // :authority, :path, :method, :scheme, referer.
+ // Copying the rest headers ensures they are the same as the original
+ // request, especially cookies.
+ SpdyHeaderBlock SynthesizePushRequestHeaders(
+ string request_url,
+ QuicInMemoryCache::ServerPushInfo resource,
+ const SpdyHeaderBlock& original_request_headers);
+
+ // Send PUSH_PROMISE frame on headers stream.
+ void SendPushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ const SpdyHeaderBlock& headers);
+
+ // Fetch response from cache for request headers enqueued into
+ // promised_headers_and_streams_ and send them on dedicated stream until
+ // reaches max_open_stream_ limit.
+ // Called when return value of GetNumOpenOutgoingStreams() changes:
+ // CloseStreamInner();
+ // StreamDraining();
+ // Note that updateFlowControlOnFinalReceivedByteOffset() won't change the
+ // return value becasue all push streams are impossible to become locally
+ // closed. Since a locally preserved stream becomes remotely closed after
+ // HandlePromisedPushRequests() starts to process it, and if it is reset
+ // locally afterwards, it will be immediately become closed and never get into
+ // locally_closed_stream_highest_offset_. So all the streams in this map
+ // are not outgoing streams.
+ void HandlePromisedPushRequests();
+
+ // Keep track of the highest stream id which has been sent in PUSH_PROMISE.
+ QuicStreamId highest_promised_stream_id_;
+
+ // Promised streams which hasn't been created yet because of max_open_stream_
+ // limit. New element is added to the end of the queue.
+ // Since outgoing stream is created in sequence, stream_id of each element in
+ // the queue also increases by 2 from previous one's. The front element's
+ // stream_id is always next_outgoing_stream_id_, and the last one is always
+ // highest_promised_stream_id_.
+ std::deque<PromisedStreamInfo> promised_streams_;
+
DISALLOW_COPY_AND_ASSIGN(QuicSimpleServerSession);
};
diff --git a/net/tools/quic/quic_simple_server_session_test.cc b/net/tools/quic/quic_simple_server_session_test.cc
index f2e451d..47c3d5f 100644
--- a/net/tools/quic/quic_simple_server_session_test.cc
+++ b/net/tools/quic/quic_simple_server_session_test.cc
@@ -4,7 +4,10 @@
#include "net/tools/quic/quic_simple_server_session.h"
+#include <algorithm>
+
#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
#include "net/quic/crypto/quic_crypto_server_config.h"
#include "net/quic/crypto/quic_random.h"
#include "net/quic/proto/cached_network_parameters.pb.h"
@@ -23,6 +26,7 @@
#include "net/test/gtest_util.h"
#include "net/tools/quic/quic_simple_server_stream.h"
#include "net/tools/quic/test_tools/mock_quic_server_session_visitor.h"
+#include "net/tools/quic/test_tools/quic_in_memory_cache_peer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -47,10 +51,69 @@ using net::test::kInitialStreamFlowControlWindowForTest;
using std::string;
using testing::StrictMock;
using testing::_;
+using testing::InSequence;
+using testing::Return;
namespace net {
namespace tools {
namespace test {
+namespace {
+typedef QuicSimpleServerSession::PromisedStreamInfo PromisedStreamInfo;
+} // namespace
+
+class MockQuicHeadersStream : public QuicHeadersStream {
+ public:
+ explicit MockQuicHeadersStream(QuicSpdySession* session)
+ : QuicHeadersStream(session) {}
+
+ MOCK_METHOD4(WritePushPromise,
+ size_t(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ const SpdyHeaderBlock& headers,
+ QuicAckListenerInterface* ack_listener));
+
+ MOCK_METHOD5(WriteHeaders,
+ size_t(QuicStreamId stream_id,
+ const SpdyHeaderBlock& headers,
+ bool fin,
+ SpdyPriority priority,
+ QuicAckListenerInterface* ack_listener));
+};
+
+class MockQuicCryptoServerStream : public QuicCryptoServerStream {
+ public:
+ explicit MockQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicSession* session)
+ : QuicCryptoServerStream(crypto_config, session) {}
+ ~MockQuicCryptoServerStream() override {}
+
+ MOCK_METHOD1(SendServerConfigUpdate,
+ void(const CachedNetworkParameters* cached_network_parameters));
+
+ void set_encryption_established(bool has_established) {
+ encryption_established_ = has_established;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockQuicCryptoServerStream);
+};
+
+class MockConnectionWithSendStreamData : public MockConnection {
+ public:
+ MockConnectionWithSendStreamData(MockConnectionHelper* helper,
+ Perspective perspective,
+ const QuicVersionVector& supported_versions)
+ : MockConnection(helper, perspective, supported_versions) {}
+
+ MOCK_METHOD6(SendStreamData,
+ QuicConsumedData(QuicStreamId id,
+ QuicIOVector iov,
+ QuicStreamOffset offset,
+ bool fin,
+ FecProtection fec_protection,
+ QuicAckListenerInterface* listern));
+};
class QuicSimpleServerSessionPeer {
public:
@@ -70,6 +133,15 @@ class QuicSimpleServerSessionPeer {
SpdyPriority priority) {
return s->CreateOutgoingDynamicStream(priority);
}
+
+ static std::deque<PromisedStreamInfo>* promised_streams(
+ QuicSimpleServerSession* s) {
+ return &(s->promised_streams_);
+ }
+
+ static QuicStreamId hightest_promised_stream_id(QuicSimpleServerSession* s) {
+ return s->highest_promised_stream_id_;
+ }
};
namespace {
@@ -89,7 +161,7 @@ class QuicSimpleServerSessionTest
config_.SetInitialSessionFlowControlWindowToSend(
kInitialSessionFlowControlWindowForTest);
- connection_ = new StrictMock<MockConnection>(
+ connection_ = new StrictMock<MockConnectionWithSendStreamData>(
&helper_, Perspective::IS_SERVER, SupportedVersions(GetParam()));
session_.reset(new QuicSimpleServerSession(config_, connection_, &owner_,
&crypto_config_));
@@ -99,16 +171,67 @@ class QuicSimpleServerSessionTest
QuicCryptoServerConfig::ConfigOptions()));
session_->Initialize();
visitor_ = QuicConnectionPeer::GetVisitor(connection_);
+ headers_stream_ = new MockQuicHeadersStream(session_.get());
+ QuicSpdySessionPeer::SetHeadersStream(session_.get(), headers_stream_);
+ }
+
+ // Given |num_resources|, create this number of fake push resources and push
+ // them by sending PUSH_PROMISE for all and sending push responses for as much
+ // as possible(limited by kMaxStreamsForTest).
+ // If |num_resources| > kMaxStreamsForTest, the left over will be queued.
+ void PromisePushResources(size_t num_resources) {
+ // Assume encryption already established.
+ MockQuicCryptoServerStream* crypto_stream =
+ new MockQuicCryptoServerStream(&crypto_config_, session_.get());
+ crypto_stream->set_encryption_established(true);
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream);
+
+ QuicInMemoryCachePeer::ResetForTests();
+
+ string request_url = "mail.google.com/";
+ SpdyHeaderBlock request_headers;
+ string resource_host = "www.google.com";
+ string partial_push_resource_path = "/server_push_src";
+ string partial_push_response_body =
+ "Push resource body " + partial_push_resource_path;
+ list<QuicInMemoryCache::ServerPushInfo> push_resources;
+ string scheme = "http";
+ for (unsigned int i = 1; i <= num_resources; ++i) {
+ string path = partial_push_resource_path + base::UintToString(i);
+ string url = scheme + "://" + resource_host + "/" + path;
+ GURL resource_url = GURL(url);
+ string body = partial_push_response_body + base::UintToString(i);
+ SpdyHeaderBlock response_headers;
+ QuicInMemoryCache::GetInstance()->AddSimpleResponse(resource_host, path,
+ 200, body);
+ push_resources.push_back(QuicInMemoryCache::ServerPushInfo(
+ resource_url, response_headers, kDefaultPriority, body));
+ // PUSH_PROMISED are sent for all the resources.
+ EXPECT_CALL(*headers_stream_,
+ WritePushPromise(kClientDataStreamId1, i * 2, _, nullptr));
+ if (i <= kMaxStreamsForTest) {
+ // |kMaxStreamsForTest| promised responses should be sent.
+ EXPECT_CALL(*headers_stream_,
+ WriteHeaders(i * 2, _, false, kDefaultPriority, nullptr));
+ // Mock that SendStreamData() returns less than supposed to send to keep
+ // the stream open.
+ EXPECT_CALL(*connection_, SendStreamData(i * 2, _, 0, true, _, nullptr))
+ .WillOnce(Return(QuicConsumedData(0, false)));
+ }
+ }
+ session_->PromisePushResources(request_url, push_resources,
+ kClientDataStreamId1, request_headers);
}
StrictMock<MockQuicServerSessionVisitor> owner_;
MockConnectionHelper helper_;
- StrictMock<MockConnection>* connection_;
+ StrictMock<MockConnectionWithSendStreamData>* connection_;
QuicConfig config_;
QuicCryptoServerConfig crypto_config_;
scoped_ptr<QuicSimpleServerSession> session_;
scoped_ptr<CryptoHandshakeMessage> handshake_message_;
QuicConnectionVisitorInterface* visitor_;
+ MockQuicHeadersStream* headers_stream_;
};
INSTANTIATE_TEST_CASE_P(Tests,
@@ -122,7 +245,7 @@ TEST_P(QuicSimpleServerSessionTest, CloseStreamDueToReset) {
session_->OnStreamFrame(data1);
EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
- // Send a reset (and expect the peer to send a RST in response).
+ // Receive a reset (and send a RST in response).
QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_ERROR_PROCESSING_STREAM,
0);
EXPECT_CALL(*connection_,
@@ -212,25 +335,6 @@ TEST_P(QuicSimpleServerSessionTest, CreateIncomingDynamicStream) {
EXPECT_EQ(kClientDataStreamId1, stream->id());
}
-class MockQuicCryptoServerStream : public QuicCryptoServerStream {
- public:
- explicit MockQuicCryptoServerStream(
- const QuicCryptoServerConfig* crypto_config,
- QuicSession* session)
- : QuicCryptoServerStream(crypto_config, session) {}
- ~MockQuicCryptoServerStream() override {}
-
- MOCK_METHOD1(SendServerConfigUpdate,
- void(const CachedNetworkParameters* cached_network_parameters));
-
- void set_encryption_established(bool has_established) {
- encryption_established_ = has_established;
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockQuicCryptoServerStream);
-};
-
TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamDisconnected) {
// Tests that outgoing stream creation fails when connection is not connected.
size_t initial_num_open_stream = session_->GetNumOpenOutgoingStreams();
@@ -290,6 +394,110 @@ TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUptoLimit) {
EXPECT_EQ(2u, session_->GetNumOpenIncomingStreams());
}
+TEST_P(QuicSimpleServerSessionTest, OnStreamFrameWithEvenStreamId) {
+ QuicStreamFrame frame(2, false, 0, StringPiece());
+ EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(
+ QUIC_INVALID_STREAM_ID,
+ "Client sent data on server push stream"));
+ session_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSimpleServerSessionTest, TestPromisePushResources) {
+ // Tests that given more than kMaxOpenStreamForTest resources, all their
+ // PUSH_PROMISE's will be sent out and only |kMaxOpenStreamForTest| streams
+ // will be opened and send push response.
+ size_t num_resources = kMaxStreamsForTest + 5;
+ PromisePushResources(num_resources);
+ EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest,
+ HandlePromisedPushRequestsAfterStreamDraining) {
+ // Tests that after promised stream queued up, when an opened stream is marked
+ // draining, a queued promised stream will become open and send push response.
+ size_t num_resources = kMaxStreamsForTest + 1;
+ PromisePushResources(num_resources);
+ QuicStreamId next_out_going_stream_id = num_resources * 2;
+
+ // After an open stream is marked draining, a new stream is expected to be
+ // created and a response sent on the stream.
+ EXPECT_CALL(*headers_stream_, WriteHeaders(next_out_going_stream_id, _, false,
+ kDefaultPriority, nullptr));
+ EXPECT_CALL(*connection_,
+ SendStreamData(next_out_going_stream_id, _, 0, true, _, nullptr))
+ .WillOnce(Return(QuicConsumedData(0, false)));
+ session_->StreamDraining(2);
+ // Number of open outgoing streams should still be the same, because a new
+ // stream is opened. And the queue should be empty.
+ EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest, GetEvenIncomingError) {
+ // Tests that calling GetOrCreateDynamicStream() on an outgoing stream not
+ // promised yet should result close connection.
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(QUIC_INVALID_STREAM_ID,
+ "Data for nonexistent stream"));
+ EXPECT_EQ(nullptr,
+ QuicSessionPeer::GetOrCreateDynamicStream(session_.get(), 4));
+}
+
+TEST_P(QuicSimpleServerSessionTest, ResetPromisedStreamToCancelServerPush) {
+ // Tests that after all resources are promised, a RST frame from client can
+ // prevent a promised resource to be send out.
+
+ // Having two extra resources to be send later. One of them will be reset, so
+ // when opened stream become close, only one will become open.
+ size_t num_resources = kMaxStreamsForTest + 2;
+ PromisePushResources(num_resources);
+
+ // Reset the last stream in the queue. It should be marked cancelled.
+ QuicStreamId stream_got_reset = num_resources * 2;
+ QuicRstStreamFrame rst(stream_got_reset, QUIC_STREAM_CANCELLED, 0);
+ EXPECT_CALL(*connection_,
+ SendRstStream(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT, 0));
+ visitor_->OnRstStream(rst);
+
+ // When the first 2 streams becomes draining, the two queued up stream could
+ // be created. But since one of them was marked cancelled due to RST frame,
+ // only one queued resource will be sent out.
+ QuicStreamId stream_not_reset = (kMaxStreamsForTest + 1) * 2;
+ InSequence s;
+ EXPECT_CALL(*headers_stream_, WriteHeaders(stream_not_reset, _, false,
+ kDefaultPriority, nullptr));
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_not_reset, _, 0, true, _, nullptr))
+ .WillOnce(Return(QuicConsumedData(0, false)));
+ EXPECT_CALL(*headers_stream_, WriteHeaders(stream_got_reset, _, false,
+ kDefaultPriority, nullptr))
+ .Times(0);
+
+ session_->StreamDraining(2);
+ session_->StreamDraining(4);
+}
+
+TEST_P(QuicSimpleServerSessionTest, CloseStreamToHandleMorePromisedStream) {
+ // Tests that closing a open outgoing stream can trigger a promised resource
+ // in the queue to be send out.
+ size_t num_resources = kMaxStreamsForTest + 1;
+ PromisePushResources(num_resources);
+ QuicStreamId stream_to_open = num_resources * 2;
+
+ // Resetting 1st open stream will close the stream and give space for extra
+ // stream to be opened.
+ QuicStreamId stream_got_reset = 2;
+ EXPECT_CALL(*connection_,
+ SendRstStream(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT, _));
+ EXPECT_CALL(*headers_stream_, WriteHeaders(stream_to_open, _, false,
+ kDefaultPriority, nullptr));
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_to_open, _, 0, true, _, nullptr))
+ .WillOnce(Return(QuicConsumedData(0, false)));
+
+ QuicRstStreamFrame rst(stream_got_reset, QUIC_STREAM_CANCELLED, 0);
+ visitor_->OnRstStream(rst);
+}
+
} // namespace
} // namespace test
} // namespace tools
diff --git a/net/tools/quic/quic_simple_server_stream.cc b/net/tools/quic/quic_simple_server_stream.cc
index 3a85d84..6a34fc6 100644
--- a/net/tools/quic/quic_simple_server_stream.cc
+++ b/net/tools/quic/quic_simple_server_stream.cc
@@ -10,11 +10,11 @@
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "net/quic/quic_flags.h"
-#include "net/quic/quic_spdy_session.h"
#include "net/quic/quic_spdy_stream.h"
#include "net/quic/spdy_utils.h"
#include "net/spdy/spdy_protocol.h"
#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_simple_server_session.h"
using base::StringPiece;
using base::StringToInt;
@@ -143,9 +143,15 @@ void QuicSimpleServerStream::SendResponse() {
// Examing response status, if it was not pure integer as typical h2 response
// status, send error response.
- string request_url = request_headers_[":scheme"].as_string() + "://" +
- request_headers_[":authority"].as_string() +
- request_headers_[":path"].as_string();
+ string request_url;
+ if (!request_headers_[":scheme"].as_string().empty()) {
+ request_url = request_headers_[":scheme"].as_string() + "://" +
+ request_headers_[":authority"].as_string() +
+ request_headers_[":path"].as_string();
+ } else {
+ request_url = request_headers_[":authority"].as_string() +
+ request_headers_[":path"].as_string();
+ }
int response_code;
SpdyHeaderBlock response_headers = response->headers();
if (!base::StringToInt(response_headers[":status"], &response_code)) {
@@ -168,6 +174,18 @@ void QuicSimpleServerStream::SendResponse() {
return;
}
}
+ list<QuicInMemoryCache::ServerPushInfo> resources =
+ QuicInMemoryCache::GetInstance()->GetServerPushResources(request_url);
+ DVLOG(1) << "Found " << resources.size() << " push resources for stream "
+ << id();
+
+ if (!resources.empty()) {
+ QuicSimpleServerSession* session =
+ static_cast<QuicSimpleServerSession*>(spdy_session());
+ session->PromisePushResources(request_url, resources, id(),
+ request_headers_);
+ }
+
DVLOG(1) << "Sending response for stream " << id();
SendHeadersAndBodyAndTrailers(response->headers(), response->body(),
response->trailers());
diff --git a/net/tools/quic/quic_simple_server_stream_test.cc b/net/tools/quic/quic_simple_server_stream_test.cc
index 9d8b6e3..5c28df0 100644
--- a/net/tools/quic/quic_simple_server_stream_test.cc
+++ b/net/tools/quic/quic_simple_server_stream_test.cc
@@ -11,15 +11,18 @@
#include "net/quic/quic_protocol.h"
#include "net/quic/quic_utils.h"
#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
#include "net/quic/test_tools/quic_test_utils.h"
#include "net/quic/test_tools/reliable_quic_stream_peer.h"
#include "net/test/gtest_util.h"
#include "net/tools/epoll_server/epoll_server.h"
#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_simple_server_session.h"
#include "net/tools/quic/spdy_balsa_utils.h"
#include "net/tools/quic/test_tools/quic_in_memory_cache_peer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
using base::StringPiece;
using net::test::MockConnection;
@@ -48,6 +51,8 @@ class QuicSimpleServerStreamPeer : public QuicSimpleServerStream {
QuicSimpleServerStreamPeer(QuicStreamId stream_id, QuicSpdySession* session)
: QuicSimpleServerStream(stream_id, session) {}
+ ~QuicSimpleServerStreamPeer() override{};
+
using QuicSimpleServerStream::SendResponse;
using QuicSimpleServerStream::SendErrorResponse;
@@ -74,6 +79,62 @@ class QuicSimpleServerStreamPeer : public QuicSimpleServerStream {
}
};
+class MockQuicSimpleServerSession : public QuicSimpleServerSession {
+ public:
+ const size_t kMaxStreamsForTest = 100;
+
+ explicit MockQuicSimpleServerSession(QuicConnection* connection,
+ MockQuicServerSessionVisitor* owner,
+ QuicCryptoServerConfig* crypto_config)
+ : QuicSimpleServerSession(::net::test::DefaultQuicConfig(),
+ connection,
+ owner,
+ crypto_config) {
+ set_max_open_streams(kMaxStreamsForTest);
+ ON_CALL(*this, WritevData(_, _, _, _, _, _))
+ .WillByDefault(testing::Return(QuicConsumedData(0, false)));
+ }
+
+ ~MockQuicSimpleServerSession() override {}
+
+ MOCK_METHOD2(OnConnectionClosed, void(QuicErrorCode error, bool from_peer));
+ MOCK_METHOD1(CreateIncomingDynamicStream, QuicSpdyStream*(QuicStreamId id));
+ MOCK_METHOD6(WritevData,
+ QuicConsumedData(QuicStreamId id,
+ QuicIOVector data,
+ QuicStreamOffset offset,
+ bool fin,
+ FecProtection fec_protection,
+ QuicAckListenerInterface*));
+ MOCK_METHOD2(OnStreamHeaders,
+ void(QuicStreamId stream_id, StringPiece headers_data));
+ MOCK_METHOD2(OnStreamHeadersPriority,
+ void(QuicStreamId stream_id, SpdyPriority priority));
+ MOCK_METHOD3(OnStreamHeadersComplete,
+ void(QuicStreamId stream_id, bool fin, size_t frame_len));
+ MOCK_METHOD5(WriteHeaders,
+ size_t(QuicStreamId id,
+ const SpdyHeaderBlock& headers,
+ bool fin,
+ SpdyPriority priority,
+ QuicAckListenerInterface* ack_notifier_delegate));
+ MOCK_METHOD3(SendRstStream,
+ void(QuicStreamId stream_id,
+ QuicRstStreamErrorCode error,
+ QuicStreamOffset bytes_written));
+ MOCK_METHOD1(OnHeadersHeadOfLineBlocking, void(QuicTime::Delta delta));
+ MOCK_METHOD4(PromisePushResources,
+ void(const string&,
+ const list<QuicInMemoryCache::ServerPushInfo>&,
+ QuicStreamId,
+ const SpdyHeaderBlock&));
+
+ using QuicSession::ActivateStream;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockQuicSimpleServerSession);
+};
+
namespace {
class QuicSimpleServerStreamTest
@@ -84,7 +145,12 @@ class QuicSimpleServerStreamTest
new StrictMock<MockConnection>(&helper_,
Perspective::IS_SERVER,
SupportedVersions(GetParam()))),
- session_(connection_),
+ session_owner_(new StrictMock<MockQuicServerSessionVisitor>()),
+ crypto_config_(new QuicCryptoServerConfig(
+ QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance(),
+ ::net::test::CryptoTestUtils::ProofSourceForTesting())),
+ session_(connection_, session_owner_, crypto_config_.get()),
body_("hello world") {
SpdyHeaderBlock request_headers;
request_headers[":host"] = "";
@@ -126,7 +192,9 @@ class QuicSimpleServerStreamTest
SpdyHeaderBlock response_headers_;
MockConnectionHelper helper_;
StrictMock<MockConnection>* connection_;
- StrictMock<MockQuicSpdySession> session_;
+ StrictMock<MockQuicServerSessionVisitor>* session_owner_;
+ std::unique_ptr<QuicCryptoServerConfig> crypto_config_;
+ StrictMock<MockQuicSimpleServerSession> session_;
QuicSimpleServerStreamPeer* stream_; // Owned by session_.
string headers_string_;
string body_;
@@ -293,6 +361,41 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) {
EXPECT_TRUE(stream_->write_side_closed());
}
+TEST_P(QuicSimpleServerStreamTest, SendReponseWithPushResources) {
+ // Tests that if a reponse has push resources to be send, SendResponse() will
+ // call PromisePushResources() to handle these resources.
+
+ // Add a request and response with valid headers into cache.
+ string host = "www.google.com";
+ string request_path = "/foo";
+ string body = "Yummm";
+ SpdyHeaderBlock response_headers;
+ string url = host + "/bar";
+ QuicInMemoryCache::ServerPushInfo push_info(GURL(url), response_headers,
+ kDefaultPriority, "Push body");
+ list<QuicInMemoryCache::ServerPushInfo> push_resources;
+ push_resources.push_back(push_info);
+ QuicInMemoryCache::GetInstance()->AddSimpleResponseWithServerPushResources(
+ host, request_path, 200, body, push_resources);
+
+ SpdyHeaderBlock* request_headers = stream_->mutable_headers();
+ (*request_headers)[":path"] = request_path;
+ (*request_headers)[":authority"] = host;
+ (*request_headers)[":version"] = "HTTP/1.1";
+ (*request_headers)[":method"] = "GET";
+
+ stream_->set_fin_received(true);
+ InSequence s;
+ EXPECT_CALL(session_, PromisePushResources(host + request_path, _,
+ ::net::test::kClientDataStreamId1,
+ *request_headers));
+ EXPECT_CALL(session_, WriteHeaders(stream_->id(), _, false, _, nullptr));
+ EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Return(QuicConsumedData(body.length(), true)));
+ QuicSimpleServerStreamPeer::SendResponse(stream_);
+}
+
TEST_P(QuicSimpleServerStreamTest, PushResponseOnClientInitiatedStream) {
// Calling PushResponse() on a client initialted stream is never supposed to
// happen.