blob: 3ca0ed345b0657a8ee68cb692330ef9f414c7e2f [file] [log] [blame]
// Copyright (c) 2012 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/chromium/quic_http_stream.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "net/base/chunked_upload_data_stream.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/load_timing_info.h"
#include "net/base/load_timing_info_test_util.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/http/http_response_headers.h"
#include "net/http/transport_security_state.h"
#include "net/log/net_log_event_type.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_util.h"
#include "net/quic/chromium/crypto/proof_verifier_chromium.h"
#include "net/quic/chromium/mock_crypto_client_stream_factory.h"
#include "net/quic/chromium/quic_chromium_alarm_factory.h"
#include "net/quic/chromium/quic_chromium_connection_helper.h"
#include "net/quic/chromium/quic_chromium_packet_reader.h"
#include "net/quic/chromium/quic_chromium_packet_writer.h"
#include "net/quic/chromium/quic_http_utils.h"
#include "net/quic/chromium/quic_server_info.h"
#include "net/quic/chromium/quic_stream_factory.h"
#include "net/quic/chromium/quic_test_packet_maker.h"
#include "net/quic/chromium/test_task_runner.h"
#include "net/quic/core/congestion_control/send_algorithm_interface.h"
#include "net/quic/core/crypto/crypto_protocol.h"
#include "net/quic/core/crypto/quic_decrypter.h"
#include "net/quic/core/crypto/quic_encrypter.h"
#include "net/quic/core/quic_connection.h"
#include "net/quic/core/quic_write_blocked_list.h"
#include "net/quic/core/spdy_utils.h"
#include "net/quic/core/tls_client_handshaker.h"
#include "net/quic/platform/api/quic_string_piece.h"
#include "net/quic/test_tools/crypto_test_utils.h"
#include "net/quic/test_tools/mock_clock.h"
#include "net/quic/test_tools/mock_random.h"
#include "net/quic/test_tools/quic_connection_peer.h"
#include "net/quic/test_tools/quic_spdy_session_peer.h"
#include "net/quic/test_tools/quic_test_utils.h"
#include "net/socket/socket_performance_watcher.h"
#include "net/socket/socket_test_util.h"
#include "net/spdy/chromium/spdy_http_utils.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "net/third_party/spdy/core/spdy_frame_builder.h"
#include "net/third_party/spdy/core/spdy_framer.h"
#include "net/third_party/spdy/core/spdy_protocol.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using std::string;
using testing::_;
using testing::AnyNumber;
using testing::Return;
namespace net {
namespace test {
namespace {
const char kUploadData[] = "Really nifty data!";
const char kDefaultServerHostName[] = "www.example.org";
const uint16_t kDefaultServerPort = 443;
class TestQuicConnection : public QuicConnection {
public:
TestQuicConnection(const ParsedQuicVersionVector& versions,
QuicConnectionId connection_id,
IPEndPoint address,
QuicChromiumConnectionHelper* helper,
QuicChromiumAlarmFactory* alarm_factory,
QuicPacketWriter* writer)
: QuicConnection(connection_id,
QuicSocketAddress(QuicSocketAddressImpl(address)),
helper,
alarm_factory,
writer,
true /* owns_writer */,
Perspective::IS_CLIENT,
versions) {}
void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
}
};
// UploadDataStream that always returns errors on data read.
class ReadErrorUploadDataStream : public UploadDataStream {
public:
enum class FailureMode { SYNC, ASYNC };
explicit ReadErrorUploadDataStream(FailureMode mode)
: UploadDataStream(true, 0), async_(mode), weak_factory_(this) {}
~ReadErrorUploadDataStream() override {}
private:
void CompleteRead() { UploadDataStream::OnReadCompleted(ERR_FAILED); }
// UploadDataStream implementation:
int InitInternal(const NetLogWithSource& net_log) override { return OK; }
int ReadInternal(IOBuffer* buf, int buf_len) override {
if (async_ == FailureMode::ASYNC) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&ReadErrorUploadDataStream::CompleteRead,
weak_factory_.GetWeakPtr()));
return ERR_IO_PENDING;
}
return ERR_FAILED;
}
void ResetInternal() override {}
const FailureMode async_;
base::WeakPtrFactory<ReadErrorUploadDataStream> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ReadErrorUploadDataStream);
};
// A Callback that deletes the QuicHttpStream.
class DeleteStreamCallback : public TestCompletionCallbackBase {
public:
DeleteStreamCallback(std::unique_ptr<QuicHttpStream> stream)
: stream_(std::move(stream)),
callback_(base::Bind(&DeleteStreamCallback::DeleteStream,
base::Unretained(this))) {}
const CompletionCallback& callback() const { return callback_; }
private:
void DeleteStream(int result) {
stream_.reset();
SetResult(result);
}
std::unique_ptr<QuicHttpStream> stream_;
CompletionCallback callback_;
};
} // namespace
class QuicHttpStreamPeer {
public:
static QuicChromiumClientStream::Handle* GetQuicChromiumClientStream(
QuicHttpStream* stream) {
return stream->stream_.get();
}
};
class QuicHttpStreamTest
: public ::testing::TestWithParam<std::tuple<QuicTransportVersion, bool>> {
public:
void CloseStream(QuicHttpStream* stream, int /*rv*/) { stream->Close(false); }
protected:
static const bool kFin = true;
static const bool kIncludeVersion = true;
static const bool kIncludeCongestionFeedback = true;
// Holds a packet to be written to the wire, and the IO mode that should
// be used by the mock socket when performing the write.
struct PacketToWrite {
PacketToWrite(IoMode mode, QuicReceivedPacket* packet)
: mode(mode), packet(packet) {}
PacketToWrite(IoMode mode, int rv) : mode(mode), packet(nullptr), rv(rv) {}
IoMode mode;
QuicReceivedPacket* packet;
int rv;
};
QuicHttpStreamTest()
: version_(std::get<0>(GetParam())),
client_headers_include_h2_stream_dependency_(std::get<1>(GetParam())),
crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
TlsClientHandshaker::CreateSslCtx()),
read_buffer_(new IOBufferWithSize(4096)),
promise_id_(GetNthServerInitiatedStreamId(0)),
stream_id_(GetNthClientInitiatedStreamId(0)),
connection_id_(2),
client_maker_(version_,
connection_id_,
&clock_,
kDefaultServerHostName,
Perspective::IS_CLIENT,
client_headers_include_h2_stream_dependency_),
server_maker_(version_,
connection_id_,
&clock_,
kDefaultServerHostName,
Perspective::IS_SERVER,
false),
random_generator_(0),
response_offset_(0) {
IPAddress ip(192, 0, 2, 33);
peer_addr_ = IPEndPoint(ip, 443);
self_addr_ = IPEndPoint(ip, 8435);
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
request_.traffic_annotation =
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
}
~QuicHttpStreamTest() {
session_->CloseSessionOnError(ERR_ABORTED, QUIC_INTERNAL_ERROR);
for (size_t i = 0; i < writes_.size(); i++) {
delete writes_[i].packet;
}
}
// Adds a packet to the list of expected writes.
void AddWrite(std::unique_ptr<QuicReceivedPacket> packet) {
writes_.push_back(PacketToWrite(SYNCHRONOUS, packet.release()));
}
void AddWrite(IoMode mode, int rv) {
writes_.push_back(PacketToWrite(mode, rv));
}
// Returns the packet to be written at position |pos|.
QuicReceivedPacket* GetWrite(size_t pos) { return writes_[pos].packet; }
bool AtEof() {
return socket_data_->AllReadDataConsumed() &&
socket_data_->AllWriteDataConsumed();
}
void ProcessPacket(std::unique_ptr<QuicReceivedPacket> packet) {
connection_->ProcessUdpPacket(
QuicSocketAddress(QuicSocketAddressImpl(self_addr_)),
QuicSocketAddress(QuicSocketAddressImpl(peer_addr_)), *packet);
}
// Configures the test fixture to use the list of expected writes.
void Initialize() {
mock_writes_.reset(new MockWrite[writes_.size()]);
for (size_t i = 0; i < writes_.size(); i++) {
if (writes_[i].packet == nullptr) {
mock_writes_[i] = MockWrite(writes_[i].mode, writes_[i].rv, i);
} else {
mock_writes_[i] = MockWrite(writes_[i].mode, writes_[i].packet->data(),
writes_[i].packet->length());
}
}
socket_data_.reset(new StaticSocketDataProvider(
base::span<MockRead>(),
base::make_span(mock_writes_.get(), writes_.size())));
std::unique_ptr<MockUDPClientSocket> socket(new MockUDPClientSocket(
socket_data_.get(), net_log_.bound().net_log()));
socket->Connect(peer_addr_);
runner_ = new TestTaskRunner(&clock_);
send_algorithm_ = new MockSendAlgorithm();
EXPECT_CALL(*send_algorithm_, InRecovery()).WillRepeatedly(Return(false));
EXPECT_CALL(*send_algorithm_, InSlowStart()).WillRepeatedly(Return(false));
EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
.Times(testing::AtLeast(1));
EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
.WillRepeatedly(Return(kMaxPacketSize));
EXPECT_CALL(*send_algorithm_, PacingRate(_))
.WillRepeatedly(Return(QuicBandwidth::Zero()));
EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true));
EXPECT_CALL(*send_algorithm_, BandwidthEstimate())
.WillRepeatedly(Return(QuicBandwidth::Zero()));
EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(AnyNumber());
EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber());
EXPECT_CALL(*send_algorithm_, GetCongestionControlType())
.Times(AnyNumber());
helper_.reset(
new QuicChromiumConnectionHelper(&clock_, &random_generator_));
alarm_factory_.reset(new QuicChromiumAlarmFactory(runner_.get(), &clock_));
connection_ = new TestQuicConnection(
SupportedVersions(
net::ParsedQuicVersion(net::PROTOCOL_QUIC_CRYPTO, version_)),
connection_id_, peer_addr_, helper_.get(), alarm_factory_.get(),
new QuicChromiumPacketWriter(
socket.get(), base::ThreadTaskRunnerHandle::Get().get()));
connection_->set_visitor(&visitor_);
connection_->SetSendAlgorithm(send_algorithm_);
// Load a certificate that is valid for *.example.org
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
EXPECT_TRUE(test_cert.get());
verify_details_.cert_verify_result.verified_cert = test_cert;
verify_details_.cert_verify_result.is_issued_by_known_root = true;
crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details_);
base::TimeTicks dns_end = base::TimeTicks::Now();
base::TimeTicks dns_start = dns_end - base::TimeDelta::FromMilliseconds(1);
session_.reset(new QuicChromiumClientSession(
connection_, std::move(socket),
/*stream_factory=*/nullptr, &crypto_client_stream_factory_, &clock_,
&transport_security_state_,
base::WrapUnique(static_cast<QuicServerInfo*>(nullptr)),
QuicSessionKey(kDefaultServerHostName, kDefaultServerPort,
PRIVACY_MODE_DISABLED, SocketTag()),
/*require_confirmation=*/false, /*migrate_session_early*/ false,
/*migrate_session_on_network_change*/ false,
/*migrate_session_early_v2*/ false,
/*migrate_session_on_network_change_v2*/ false,
base::TimeDelta::FromSeconds(kMaxTimeOnNonDefaultNetworkSecs),
kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
kQuicYieldAfterPacketsRead,
QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds),
client_headers_include_h2_stream_dependency_, /*cert_verify_flags=*/0,
DefaultQuicConfig(), &crypto_config_, "CONNECTION_UNKNOWN", dns_start,
dns_end, &push_promise_index_, nullptr,
base::ThreadTaskRunnerHandle::Get().get(),
/*socket_performance_watcher=*/nullptr, net_log_.bound().net_log()));
session_->Initialize();
TestCompletionCallback callback;
session_->CryptoConnect(callback.callback());
stream_ = std::make_unique<QuicHttpStream>(
session_->CreateHandle(HostPortPair("www.example.org", 443)));
promised_stream_ = std::make_unique<QuicHttpStream>(
session_->CreateHandle(HostPortPair("www.example.org", 443)));
push_promise_[":path"] = "/bar";
push_promise_[":authority"] = "www.example.org";
push_promise_[":version"] = "HTTP/1.1";
push_promise_[":method"] = "GET";
push_promise_[":scheme"] = "https";
promised_response_[":status"] = "200 OK";
promised_response_[":version"] = "HTTP/1.1";
promised_response_["content-type"] = "text/plain";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
}
void SetRequest(const string& method,
const string& path,
RequestPriority priority) {
request_headers_ = client_maker_.GetRequestHeaders(method, "https", path);
}
void SetResponse(const string& status, const string& body) {
response_headers_ = server_maker_.GetResponseHeaders(status);
response_data_ = body;
}
std::unique_ptr<QuicReceivedPacket> InnerConstructDataPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
bool should_include_version,
bool fin,
QuicStreamOffset offset,
QuicStringPiece data,
QuicTestPacketMaker* maker) {
return maker->MakeDataPacket(packet_number, stream_id,
should_include_version, fin, offset, data);
}
std::unique_ptr<QuicReceivedPacket> ConstructClientDataPacket(
QuicPacketNumber packet_number,
bool should_include_version,
bool fin,
QuicStreamOffset offset,
QuicStringPiece data) {
return InnerConstructDataPacket(packet_number, stream_id_,
should_include_version, fin, offset, data,
&client_maker_);
}
std::unique_ptr<QuicReceivedPacket> ConstructServerDataPacket(
QuicPacketNumber packet_number,
bool should_include_version,
bool fin,
QuicStreamOffset offset,
QuicStringPiece data) {
return InnerConstructDataPacket(packet_number, stream_id_,
should_include_version, fin, offset, data,
&server_maker_);
}
std::unique_ptr<QuicReceivedPacket> InnerConstructRequestHeadersPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
bool should_include_version,
bool fin,
RequestPriority request_priority,
size_t* spdy_headers_frame_length,
QuicStreamOffset* offset) {
return InnerConstructRequestHeadersPacket(
packet_number, stream_id, should_include_version, fin, request_priority,
0, spdy_headers_frame_length, offset);
}
std::unique_ptr<QuicReceivedPacket> InnerConstructRequestHeadersPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
bool should_include_version,
bool fin,
RequestPriority request_priority,
QuicStreamId parent_stream_id,
size_t* spdy_headers_frame_length,
QuicStreamOffset* offset) {
SpdyPriority priority =
ConvertRequestPriorityToQuicPriority(request_priority);
return client_maker_.MakeRequestHeadersPacket(
packet_number, stream_id, should_include_version, fin, priority,
std::move(request_headers_), parent_stream_id,
spdy_headers_frame_length, offset);
}
std::unique_ptr<QuicReceivedPacket>
ConstructRequestHeadersAndDataFramesPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
bool should_include_version,
bool fin,
RequestPriority request_priority,
QuicStreamId parent_stream_id,
QuicStreamOffset* offset,
size_t* spdy_headers_frame_length,
const std::vector<std::string>& data_writes) {
SpdyPriority priority =
ConvertRequestPriorityToQuicPriority(request_priority);
return client_maker_.MakeRequestHeadersAndMultipleDataFramesPacket(
packet_number, stream_id, should_include_version, fin, priority,
std::move(request_headers_), parent_stream_id, offset,
spdy_headers_frame_length, data_writes);
}
std::unique_ptr<QuicReceivedPacket> ConstructRequestAndRstPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
bool should_include_version,
bool fin,
RequestPriority request_priority,
QuicStreamId parent_stream_id,
size_t* spdy_headers_frame_length,
QuicStreamOffset* header_stream_offset,
QuicRstStreamErrorCode error_code,
size_t bytes_written) {
SpdyPriority priority =
ConvertRequestPriorityToQuicPriority(request_priority);
return client_maker_.MakeRequestHeadersAndRstPacket(
packet_number, stream_id, should_include_version, fin, priority,
std::move(request_headers_), parent_stream_id,
spdy_headers_frame_length, header_stream_offset, error_code,
bytes_written);
}
std::unique_ptr<QuicReceivedPacket> ConstructRequestHeadersPacket(
QuicPacketNumber packet_number,
bool fin,
RequestPriority request_priority,
size_t* spdy_headers_frame_length) {
return InnerConstructRequestHeadersPacket(
packet_number, stream_id_, kIncludeVersion, fin, request_priority,
spdy_headers_frame_length, nullptr);
}
std::unique_ptr<QuicReceivedPacket> InnerConstructResponseHeadersPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
bool fin,
size_t* spdy_headers_frame_length) {
return server_maker_.MakeResponseHeadersPacket(
packet_number, stream_id, !kIncludeVersion, fin,
std::move(response_headers_), spdy_headers_frame_length,
&response_offset_);
}
std::unique_ptr<QuicReceivedPacket> ConstructResponseHeadersPacket(
QuicPacketNumber packet_number,
bool fin,
size_t* spdy_headers_frame_length) {
return InnerConstructResponseHeadersPacket(packet_number, stream_id_, fin,
spdy_headers_frame_length);
}
std::unique_ptr<QuicReceivedPacket> ConstructResponseHeadersPacketWithOffset(
QuicPacketNumber packet_number,
bool fin,
size_t* spdy_headers_frame_length,
QuicStreamOffset* offset) {
return server_maker_.MakeResponseHeadersPacket(
packet_number, stream_id_, !kIncludeVersion, fin,
std::move(response_headers_), spdy_headers_frame_length, offset);
}
std::unique_ptr<QuicReceivedPacket> ConstructResponseTrailersPacket(
QuicPacketNumber packet_number,
bool fin,
SpdyHeaderBlock trailers,
size_t* spdy_headers_frame_length,
QuicStreamOffset* offset) {
return server_maker_.MakeResponseHeadersPacket(
packet_number, stream_id_, !kIncludeVersion, fin, std::move(trailers),
spdy_headers_frame_length, offset);
}
std::unique_ptr<QuicReceivedPacket> ConstructClientRstStreamPacket(
QuicPacketNumber packet_number) {
return client_maker_.MakeRstPacket(packet_number, true, stream_id_,
QUIC_RST_ACKNOWLEDGEMENT);
}
std::unique_ptr<QuicReceivedPacket> ConstructClientRstStreamCancelledPacket(
QuicPacketNumber packet_number) {
return client_maker_.MakeRstPacket(packet_number, !kIncludeVersion,
stream_id_, QUIC_STREAM_CANCELLED);
}
std::unique_ptr<QuicReceivedPacket>
ConstructClientRstStreamVaryMismatchPacket(QuicPacketNumber packet_number) {
return client_maker_.MakeRstPacket(packet_number, !kIncludeVersion,
promise_id_, QUIC_PROMISE_VARY_MISMATCH);
}
std::unique_ptr<QuicReceivedPacket> ConstructAckAndRstStreamPacket(
QuicPacketNumber packet_number,
QuicPacketNumber largest_received,
QuicPacketNumber smallest_received,
QuicPacketNumber least_unacked) {
return client_maker_.MakeAckAndRstPacket(
packet_number, !kIncludeVersion, stream_id_, QUIC_STREAM_CANCELLED,
largest_received, smallest_received, least_unacked,
!kIncludeCongestionFeedback);
}
std::unique_ptr<QuicReceivedPacket> ConstructClientRstStreamErrorPacket(
QuicPacketNumber packet_number,
bool include_version) {
return client_maker_.MakeRstPacket(packet_number, include_version,
stream_id_,
QUIC_ERROR_PROCESSING_STREAM);
}
std::unique_ptr<QuicReceivedPacket> ConstructAckAndRstStreamPacket(
QuicPacketNumber packet_number) {
return ConstructAckAndRstStreamPacket(packet_number, 2, 1, 1);
}
std::unique_ptr<QuicReceivedPacket> ConstructClientAckPacket(
QuicPacketNumber packet_number,
QuicPacketNumber largest_received,
QuicPacketNumber smallest_received,
QuicPacketNumber least_unacked) {
return client_maker_.MakeAckPacket(packet_number, largest_received,
smallest_received, least_unacked,
!kIncludeCongestionFeedback);
}
std::unique_ptr<QuicReceivedPacket> ConstructServerAckPacket(
QuicPacketNumber packet_number,
QuicPacketNumber largest_received,
QuicPacketNumber smallest_received,
QuicPacketNumber least_unacked) {
return server_maker_.MakeAckPacket(packet_number, largest_received,
smallest_received, least_unacked,
!kIncludeCongestionFeedback);
}
std::unique_ptr<QuicReceivedPacket> ConstructClientPriorityPacket(
QuicPacketNumber packet_number,
bool should_include_version,
QuicStreamId id,
QuicStreamId parent_stream_id,
RequestPriority request_priority,
QuicStreamOffset* header_stream_offset) {
return client_maker_.MakePriorityPacket(
packet_number, should_include_version, id, parent_stream_id,
ConvertRequestPriorityToQuicPriority(request_priority),
header_stream_offset);
}
std::unique_ptr<QuicReceivedPacket> ConstructInitialSettingsPacket(
QuicStreamOffset* offset) {
return client_maker_.MakeInitialSettingsPacket(1, offset);
}
void ReceivePromise(QuicStreamId id) {
auto headers = AsHeaderList(push_promise_);
QuicChromiumClientStream::Handle* stream =
QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
stream->OnPromiseHeaderList(id, headers.uncompressed_header_bytes(),
headers);
}
void ExpectLoadTimingValid(const LoadTimingInfo& load_timing_info,
bool session_reused) {
EXPECT_EQ(session_reused, load_timing_info.socket_reused);
if (session_reused) {
ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
} else {
ExpectConnectTimingHasTimes(
load_timing_info.connect_timing,
CONNECT_TIMING_HAS_SSL_TIMES | CONNECT_TIMING_HAS_DNS_TIMES);
}
ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
}
QuicStreamId GetNthClientInitiatedStreamId(int n) {
return test::GetNthClientInitiatedStreamId(version_, n);
}
QuicStreamId GetNthServerInitiatedStreamId(int n) {
return test::GetNthServerInitiatedStreamId(version_, n);
}
const QuicTransportVersion version_;
const bool client_headers_include_h2_stream_dependency_;
BoundTestNetLog net_log_;
MockSendAlgorithm* send_algorithm_;
scoped_refptr<TestTaskRunner> runner_;
std::unique_ptr<MockWrite[]> mock_writes_;
MockClock clock_;
TestQuicConnection* connection_;
std::unique_ptr<QuicChromiumConnectionHelper> helper_;
std::unique_ptr<QuicChromiumAlarmFactory> alarm_factory_;
testing::StrictMock<MockQuicConnectionVisitor> visitor_;
std::unique_ptr<UploadDataStream> upload_data_stream_;
std::unique_ptr<QuicHttpStream> stream_;
TransportSecurityState transport_security_state_;
std::unique_ptr<QuicChromiumClientSession> session_;
QuicCryptoClientConfig crypto_config_;
TestCompletionCallback callback_;
HttpRequestInfo request_;
HttpRequestHeaders headers_;
HttpResponseInfo response_;
scoped_refptr<IOBufferWithSize> read_buffer_;
SpdyHeaderBlock request_headers_;
SpdyHeaderBlock response_headers_;
string request_data_;
string response_data_;
QuicClientPushPromiseIndex push_promise_index_;
// For server push testing
std::unique_ptr<QuicHttpStream> promised_stream_;
SpdyHeaderBlock push_promise_;
SpdyHeaderBlock promised_response_;
const QuicStreamId promise_id_;
string promise_url_;
const QuicStreamId stream_id_;
const QuicConnectionId connection_id_;
QuicTestPacketMaker client_maker_;
QuicTestPacketMaker server_maker_;
IPEndPoint self_addr_;
IPEndPoint peer_addr_;
MockRandom random_generator_;
ProofVerifyDetailsChromium verify_details_;
MockCryptoClientStreamFactory crypto_client_stream_factory_;
std::unique_ptr<StaticSocketDataProvider> socket_data_;
std::vector<PacketToWrite> writes_;
QuicStreamOffset response_offset_;
};
INSTANTIATE_TEST_CASE_P(
VersionIncludeStreamDependencySequence,
QuicHttpStreamTest,
::testing::Combine(::testing::ValuesIn(AllSupportedTransportVersions()),
::testing::Bool()));
TEST_P(QuicHttpStreamTest, RenewStreamForAuth) {
Initialize();
EXPECT_EQ(nullptr, stream_->RenewStreamForAuth());
}
TEST_P(QuicHttpStreamTest, CanReuseConnection) {
Initialize();
EXPECT_FALSE(stream_->CanReuseConnection());
}
TEST_P(QuicHttpStreamTest, DisableConnectionMigrationForStream) {
request_.load_flags |= LOAD_DISABLE_CONNECTION_MIGRATION;
Initialize();
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
QuicChromiumClientStream::Handle* client_stream =
QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
EXPECT_FALSE(client_stream->can_migrate());
}
TEST_P(QuicHttpStreamTest, GetRequest) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_header_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_header_frame_length,
&header_stream_offset));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
// Make sure getting load timing from the stream early does not crash.
LoadTimingInfo load_timing_info;
EXPECT_TRUE(stream_->GetLoadTimingInfo(&load_timing_info));
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
SetResponse("404 Not Found", string());
size_t spdy_response_header_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, kFin, &spdy_response_header_frame_length));
// Now that the headers have been processed, the callback will return.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(404, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
EXPECT_FALSE(response_.response_time.is_null());
EXPECT_FALSE(response_.request_time.is_null());
// There is no body, so this should return immediately.
EXPECT_EQ(0,
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
EXPECT_TRUE(stream_->GetLoadTimingInfo(&load_timing_info));
ExpectLoadTimingValid(load_timing_info, /*session_reused=*/false);
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_header_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_header_frame_length),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, LoadTimingTwoRequests) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_header_frame_length;
QuicStreamOffset offset = 0;
AddWrite(ConstructInitialSettingsPacket(&offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_header_frame_length, &offset));
// SetRequest() again for second request as |request_headers_| was moved.
SetRequest("GET", "/", DEFAULT_PRIORITY);
AddWrite(InnerConstructRequestHeadersPacket(
3, GetNthClientInitiatedStreamId(1), kIncludeVersion, kFin,
DEFAULT_PRIORITY, GetNthClientInitiatedStreamId(0),
&spdy_request_header_frame_length, &offset));
AddWrite(ConstructClientAckPacket(4, 3, 1, 1)); // Ack the responses.
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
// Start first request.
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Start a second request.
QuicHttpStream stream2(
session_->CreateHandle(HostPortPair("www.example.org", 443)));
TestCompletionCallback callback2;
EXPECT_EQ(OK,
stream2.InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback2.callback()));
EXPECT_EQ(OK,
stream2.SendRequest(headers_, &response_, callback2.callback()));
// Ack both requests.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
size_t spdy_response_header_frame_length;
SetResponse("200 OK", string());
ProcessPacket(InnerConstructResponseHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kFin,
&spdy_response_header_frame_length));
// Now that the headers have been processed, the callback will return.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
EXPECT_EQ(200, response_.headers->response_code());
// There is no body, so this should return immediately.
EXPECT_EQ(0,
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
LoadTimingInfo load_timing_info;
EXPECT_TRUE(stream_->GetLoadTimingInfo(&load_timing_info));
ExpectLoadTimingValid(load_timing_info, /*session_reused=*/false);
// SetResponse() again for second request as |response_headers_| was moved.
SetResponse("200 OK", string());
EXPECT_THAT(stream2.ReadResponseHeaders(callback2.callback()),
IsError(ERR_IO_PENDING));
ProcessPacket(InnerConstructResponseHeadersPacket(
3, GetNthClientInitiatedStreamId(1), kFin,
&spdy_response_header_frame_length));
EXPECT_THAT(callback2.WaitForResult(), IsOk());
// There is no body, so this should return immediately.
EXPECT_EQ(0,
stream2.ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback2.callback()));
EXPECT_TRUE(stream2.IsResponseBodyComplete());
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(stream2.GetLoadTimingInfo(&load_timing_info2));
ExpectLoadTimingValid(load_timing_info2, /*session_reused=*/true);
}
// QuicHttpStream does not currently support trailers. It should ignore
// trailers upon receiving them.
TEST_P(QuicHttpStreamTest, GetRequestWithTrailers) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_header_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_header_frame_length,
&header_stream_offset));
AddWrite(ConstructClientAckPacket(3, 3, 1, 1)); // Ack the data packet.
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
SetResponse("200 OK", string());
// Send the response headers.
size_t spdy_response_header_frame_length;
QuicStreamOffset offset = 0;
ProcessPacket(ConstructResponseHeadersPacketWithOffset(
2, !kFin, &spdy_response_header_frame_length, &offset));
// Now that the headers have been processed, the callback will return.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
EXPECT_FALSE(response_.response_time.is_null());
EXPECT_FALSE(response_.request_time.is_null());
// Send the response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(
ConstructServerDataPacket(3, false, !kFin, /*offset=*/0, kResponseBody));
SpdyHeaderBlock trailers;
size_t spdy_trailers_frame_length;
trailers["foo"] = "bar";
trailers[kFinalOffsetHeaderKey] = base::IntToString(strlen(kResponseBody));
ProcessPacket(ConstructResponseTrailersPacket(
4, kFin, std::move(trailers), &spdy_trailers_frame_length, &offset));
// Make sure trailers are processed.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_EQ(OK,
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_header_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(
static_cast<int64_t>(spdy_response_header_frame_length +
strlen(kResponseBody) + +spdy_trailers_frame_length),
stream_->GetTotalReceivedBytes());
// Check that NetLog was filled as expected.
TestNetLogEntry::List entries;
net_log_.GetEntries(&entries);
size_t pos = ExpectLogContainsSomewhere(
entries, /*min_offset=*/0,
NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
NetLogEventPhase::NONE);
pos = ExpectLogContainsSomewhere(
entries, /*min_offset=*/pos,
NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
NetLogEventPhase::NONE);
ExpectLogContainsSomewhere(
entries, /*min_offset=*/pos,
NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
NetLogEventPhase::NONE);
}
// Regression test for http://crbug.com/288128
TEST_P(QuicHttpStreamTest, GetRequestLargeResponse) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
response_headers_[":status"] = "200 OK";
response_headers_[":version"] = "HTTP/1.1";
response_headers_["content-type"] = "text/plain";
response_headers_["big6"] = string(1000, 'x'); // Lots of x's.
size_t spdy_response_headers_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, kFin, &spdy_response_headers_frame_length));
// Now that the headers have been processed, the callback will return.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
// There is no body, so this should return immediately.
EXPECT_EQ(0,
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length),
stream_->GetTotalReceivedBytes());
}
// Regression test for http://crbug.com/409101
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendRequest) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
session_->connection()->CloseConnection(
QUIC_NO_ERROR, "test", ConnectionCloseBehavior::SILENT_CLOSE);
EXPECT_EQ(ERR_CONNECTION_CLOSED,
stream_->SendRequest(headers_, &response_, callback_.callback()));
EXPECT_EQ(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
// Regression test for http://crbug.com/584441
TEST_P(QuicHttpStreamTest, GetSSLInfoAfterSessionClosed) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
SSLInfo ssl_info;
EXPECT_FALSE(ssl_info.is_valid());
stream_->GetSSLInfo(&ssl_info);
EXPECT_TRUE(ssl_info.is_valid());
session_->connection()->CloseConnection(
QUIC_NO_ERROR, "test", ConnectionCloseBehavior::SILENT_CLOSE);
SSLInfo ssl_info2;
stream_->GetSSLInfo(&ssl_info2);
EXPECT_TRUE(ssl_info2.is_valid());
}
TEST_P(QuicHttpStreamTest, GetAlternativeService) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
AlternativeService alternative_service;
EXPECT_TRUE(stream_->GetAlternativeService(&alternative_service));
EXPECT_EQ(AlternativeService(kProtoQUIC, "www.example.org", 443),
alternative_service);
session_->connection()->CloseConnection(
QUIC_NO_ERROR, "test", ConnectionCloseBehavior::SILENT_CLOSE);
AlternativeService alternative_service2;
EXPECT_TRUE(stream_->GetAlternativeService(&alternative_service2));
EXPECT_EQ(AlternativeService(kProtoQUIC, "www.example.org", 443),
alternative_service2);
}
TEST_P(QuicHttpStreamTest, LogGranularQuicConnectionError) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
AddWrite(ConstructAckAndRstStreamPacket(3));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
QuicConnectionCloseFrame frame;
frame.error_code = QUIC_PEER_GOING_AWAY;
session_->connection()->OnConnectionCloseFrame(frame);
NetErrorDetails details;
EXPECT_EQ(QUIC_NO_ERROR, details.quic_connection_error);
stream_->PopulateNetErrorDetails(&details);
EXPECT_EQ(QUIC_PEER_GOING_AWAY, details.quic_connection_error);
}
TEST_P(QuicHttpStreamTest, LogGranularQuicErrorIfHandshakeNotConfirmed) {
// By default the test setup defaults handshake to be confirmed. Manually set
// it to be not confirmed.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::ZERO_RTT);
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
client_maker_.SetEncryptionLevel(ENCRYPTION_INITIAL);
client_maker_.SetLongHeaderType(ZERO_RTT_PROTECTED);
AddWrite(InnerConstructRequestHeadersPacket(
1, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
QuicConnectionCloseFrame frame;
frame.error_code = QUIC_PEER_GOING_AWAY;
session_->connection()->OnConnectionCloseFrame(frame);
NetErrorDetails details;
stream_->PopulateNetErrorDetails(&details);
EXPECT_EQ(QUIC_PEER_GOING_AWAY, details.quic_connection_error);
}
// Regression test for http://crbug.com/409871
TEST_P(QuicHttpStreamTest, SessionClosedBeforeReadResponseHeaders) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
session_->connection()->CloseConnection(
QUIC_NO_ERROR, "test", ConnectionCloseBehavior::SILENT_CLOSE);
EXPECT_NE(OK, stream_->ReadResponseHeaders(callback_.callback()));
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SendPostRequest) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, 0, &header_stream_offset,
&spdy_request_headers_frame_length, {kUploadData}));
AddWrite(ConstructClientAckPacket(3, 3, 1, 1));
Initialize();
std::vector<std::unique_ptr<UploadElementReader>> element_readers;
element_readers.push_back(std::make_unique<UploadBytesElementReader>(
kUploadData, strlen(kUploadData)));
upload_data_stream_ =
std::make_unique<ElementsUploadDataStream>(std::move(element_readers), 0);
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_THAT(request_.upload_data_stream->Init(CompletionCallback(),
NetLogWithSource()),
IsOk());
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack both packets in the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", string());
size_t spdy_response_headers_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, !kFin, &spdy_response_headers_frame_length));
// The headers have already arrived.
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
// Send the response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(ConstructServerDataPacket(3, false, kFin, 0, kResponseBody));
// Since the body has already arrived, this should return immediately.
EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_EQ(0,
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length +
strlen(kUploadData)),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SendPostRequestAndReceiveSoloFin) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, 0, &header_stream_offset,
&spdy_request_headers_frame_length, {kUploadData}));
AddWrite(ConstructClientAckPacket(3, 3, 1, 1));
Initialize();
std::vector<std::unique_ptr<UploadElementReader>> element_readers;
element_readers.push_back(std::make_unique<UploadBytesElementReader>(
kUploadData, strlen(kUploadData)));
upload_data_stream_ =
std::make_unique<ElementsUploadDataStream>(std::move(element_readers), 0);
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_THAT(request_.upload_data_stream->Init(CompletionCallback(),
NetLogWithSource()),
IsOk());
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack both packets in the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", string());
size_t spdy_response_headers_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, !kFin, &spdy_response_headers_frame_length));
// The headers have already arrived.
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
// Send the response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(ConstructServerDataPacket(3, false, !kFin, 0, kResponseBody));
// Since the body has already arrived, this should return immediately.
EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
ProcessPacket(ConstructServerDataPacket(4, false, kFin,
arraysize(kResponseBody) - 1, ""));
EXPECT_EQ(0,
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length +
strlen(kUploadData)),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SendChunkedPostRequest) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t chunk_size = strlen(kUploadData);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, 0, &header_stream_offset,
&spdy_request_headers_frame_length, {kUploadData}));
AddWrite(ConstructClientDataPacket(3, kIncludeVersion, kFin, chunk_size,
kUploadData));
AddWrite(ConstructClientAckPacket(4, 3, 1, 1));
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING,
stream_->SendRequest(headers_, &response_, callback_.callback()));
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
EXPECT_THAT(callback_.WaitForResult(), IsOk());
// Ack both packets in the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", string());
size_t spdy_response_headers_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, !kFin, &spdy_response_headers_frame_length));
// The headers have already arrived.
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
// Send the response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(ConstructServerDataPacket(
3, false, kFin, response_data_.length(), kResponseBody));
// Since the body has already arrived, this should return immediately.
ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length +
strlen(kUploadData) * 2),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SendChunkedPostRequestWithFinalEmptyDataPacket) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t chunk_size = strlen(kUploadData);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, 0, &header_stream_offset,
&spdy_request_headers_frame_length, {kUploadData}));
AddWrite(ConstructClientDataPacket(3, kIncludeVersion, kFin, chunk_size, ""));
AddWrite(ConstructClientAckPacket(4, 3, 1, 1));
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING,
stream_->SendRequest(headers_, &response_, callback_.callback()));
chunked_upload_stream->AppendData(nullptr, 0, true);
EXPECT_THAT(callback_.WaitForResult(), IsOk());
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", string());
size_t spdy_response_headers_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, !kFin, &spdy_response_headers_frame_length));
// The headers have already arrived.
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
// Send the response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(ConstructServerDataPacket(
3, false, kFin, response_data_.length(), kResponseBody));
// The body has arrived, but it is delivered asynchronously
ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length +
strlen(kUploadData)),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SendChunkedPostRequestWithOneEmptyDataPacket) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
AddWrite(ConstructClientDataPacket(3, kIncludeVersion, kFin, 0, ""));
AddWrite(ConstructClientAckPacket(4, 3, 1, 1));
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING,
stream_->SendRequest(headers_, &response_, callback_.callback()));
chunked_upload_stream->AppendData(nullptr, 0, true);
EXPECT_THAT(callback_.WaitForResult(), IsOk());
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", string());
size_t spdy_response_headers_frame_length;
ProcessPacket(ConstructResponseHeadersPacket(
2, !kFin, &spdy_response_headers_frame_length));
// The headers have already arrived.
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()), IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(200, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
// Send the response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(ConstructServerDataPacket(
3, false, kFin, response_data_.length(), kResponseBody));
// The body has arrived, but it is delivered asynchronously
ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
callback_.callback()));
EXPECT_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, DestroyedEarly) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
AddWrite(ConstructAckAndRstStreamPacket(3));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(
base::Bind(&QuicHttpStreamTest::CloseStream,
base::Unretained(this), stream_.get())),
IsError(ERR_IO_PENDING));
// Send the response with a body.
SetResponse("404 OK", "hello world!");
// In the course of processing this packet, the QuicHttpStream close itself.
size_t response_size = 0;
ProcessPacket(ConstructResponseHeadersPacket(2, !kFin, &response_size));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
// The stream was closed after receiving the headers.
EXPECT_EQ(static_cast<int64_t>(response_size),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, Priority) {
SetRequest("GET", "/", MEDIUM);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, kFin, MEDIUM,
&spdy_request_headers_frame_length, &header_stream_offset));
Initialize();
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, MEDIUM, net_log_.bound(),
callback_.callback()));
EXPECT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Ack the request.
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
IsError(ERR_IO_PENDING));
// Send the response with a body.
SetResponse("404 OK", "hello world!");
size_t response_size = 0;
ProcessPacket(ConstructResponseHeadersPacket(2, kFin, &response_size));
EXPECT_EQ(OK, callback_.WaitForResult());
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(response_size),
stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SessionClosedDuringDoLoop) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, 0, &header_stream_offset,
&spdy_request_headers_frame_length, {kUploadData}));
// Second data write will result in a synchronous failure which will close
// the session.
AddWrite(SYNCHRONOUS, ERR_FAILED);
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
size_t chunk_size = strlen(kUploadData);
chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
QuicHttpStream* stream = stream_.get();
DeleteStreamCallback delete_stream_callback(std::move(stream_));
// SendRequest() completes asynchronously after the final chunk is added.
// Error does not surface yet since packet write is triggered by a packet
// flusher that tries to bundle request body writes.
ASSERT_EQ(ERR_IO_PENDING,
stream->SendRequest(headers_, &response_, callback_.callback()));
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
int rv = callback_.WaitForResult();
EXPECT_EQ(OK, rv);
// Error will be surfaced once an attempt to read the response occurs.
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
stream->ReadResponseHeaders(callback_.callback()));
}
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendHeadersComplete) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(SYNCHRONOUS, ERR_FAILED);
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Error will be surfaced once |upload_data_stream| triggers the next write.
size_t chunk_size = strlen(kUploadData);
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR, callback_.WaitForResult());
EXPECT_LE(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendHeadersCompleteReadResponse) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(SYNCHRONOUS, ERR_FAILED);
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
size_t chunk_size = strlen(kUploadData);
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(OK,
stream_->SendRequest(headers_, &response_, callback_.callback()));
// Error will be surfaced once an attempt to read the response occurs.
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
stream_->ReadResponseHeaders(callback_.callback()));
EXPECT_LE(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendBodyComplete) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
AddWrite(SYNCHRONOUS, ERR_FAILED);
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING,
stream_->SendRequest(headers_, &response_, callback_.callback()));
size_t chunk_size = strlen(kUploadData);
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
// Error does not surface yet since packet write is triggered by a packet
// flusher that tries to bundle request body writes.
ASSERT_EQ(OK, callback_.WaitForResult());
// Error will be surfaced once an attempt to read the response occurs.
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
stream_->ReadResponseHeaders(callback_.callback()));
EXPECT_LE(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendBundledBodyComplete) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, 0, &header_stream_offset,
&spdy_request_headers_frame_length, {kUploadData}));
AddWrite(SYNCHRONOUS, ERR_FAILED);
Initialize();
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
auto* chunked_upload_stream =
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
size_t chunk_size = strlen(kUploadData);
chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
ASSERT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING,
stream_->SendRequest(headers_, &response_, callback_.callback()));
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
// Error does not surface yet since packet write is triggered by a packet
// flusher that tries to bundle request body writes.
ASSERT_EQ(OK, callback_.WaitForResult());
// Error will be surfaced once an attempt to read the response occurs.
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
stream_->ReadResponseHeaders(callback_.callback()));
EXPECT_LE(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, ServerPushGetRequest) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
request_.url = GURL(promise_url_);
// Make the second stream that will exercise the first step of the
// server push rendezvous mechanism.
EXPECT_EQ(OK, promised_stream_->InitializeStream(
&request_, true, DEFAULT_PRIORITY, net_log_.bound(),
callback_.callback()));
// Receive the promised response headers.
response_headers_ = promised_response_.Clone();
size_t spdy_response_headers_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
1, promise_id_, false, &spdy_response_headers_frame_length));
// Receive the promised response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(InnerConstructDataPacket(2, promise_id_, false, kFin, 0,
kResponseBody, &server_maker_));
// Now sending a matching request will have successful rendezvous
// with the promised stream.
EXPECT_EQ(OK, promised_stream_->SendRequest(headers_, &response_,
callback_.callback()));
EXPECT_EQ(
QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
->id(),
promise_id_);
// The headers will be immediately available.
EXPECT_THAT(promised_stream_->ReadResponseHeaders(callback_.callback()),
IsOk());
// As will be the body.
EXPECT_EQ(
static_cast<int>(strlen(kResponseBody)),
promised_stream_->ReadResponseBody(
read_buffer_.get(), read_buffer_->size(), callback_.callback()));
EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
EXPECT_EQ(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
promised_stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, ServerPushGetRequestSlowResponse) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
request_.url = GURL(promise_url_);
// Make the second stream that will exercise the first step of the
// server push rendezvous mechanism.
EXPECT_EQ(OK, promised_stream_->InitializeStream(
&request_, true, DEFAULT_PRIORITY, net_log_.bound(),
callback_.callback()));
// Now sending a matching request will rendezvous with the promised
// stream, but pending secondary validation.
EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
headers_, &response_, callback_.callback()));
// Receive the promised response headers.
response_headers_ = promised_response_.Clone();
size_t spdy_response_headers_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
1, promise_id_, false, &spdy_response_headers_frame_length));
// Receive the promised response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(InnerConstructDataPacket(2, promise_id_, false, kFin, 0,
kResponseBody, &server_maker_));
base::RunLoop().RunUntilIdle();
// Rendezvous should have succeeded now, so the promised stream
// should point at our push stream, and we should be able read
// headers and data from it.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
EXPECT_EQ(
QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
->id(),
promise_id_);
EXPECT_THAT(promised_stream_->ReadResponseHeaders(callback_.callback()),
IsOk());
EXPECT_EQ(
static_cast<int>(strlen(kResponseBody)),
promised_stream_->ReadResponseBody(
read_buffer_.get(), read_buffer_->size(), callback_.callback()));
// Callback should return
EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
EXPECT_EQ(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
promised_stream_->GetTotalReceivedBytes());
}
// Verify fix for crbug.com/637349
TEST_P(QuicHttpStreamTest, ServerPushCancelHttpStreamBeforeResponse) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
request_.url = GURL(promise_url_);
// Make the second stream that will exercise the first step of the
// server push rendezvous mechanism.
EXPECT_EQ(OK, promised_stream_->InitializeStream(
&request_, true, DEFAULT_PRIORITY, net_log_.bound(),
callback_.callback()));
// Now sending a matching request will rendezvous with the promised
// stream, but pending secondary validation.
EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
headers_, &response_, callback_.callback()));
base::RunLoop().RunUntilIdle();
// Cause of FinalValidation() crash as per bug.
promised_stream_.reset();
// Receive the promised response headers.
response_headers_ = promised_response_.Clone();
size_t spdy_response_headers_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
1, promise_id_, false, &spdy_response_headers_frame_length));
}
TEST_P(QuicHttpStreamTest, ServerPushCrossOriginOK) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
push_promise_[":authority"] = "mail.example.org";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
request_.url = GURL(promise_url_);
// Make the second stream that will exercise the first step of the
// server push rendezvous mechanism.
EXPECT_EQ(OK, promised_stream_->InitializeStream(
&request_, true, DEFAULT_PRIORITY, net_log_.bound(),
callback_.callback()));
// Receive the promised response headers.
response_headers_ = promised_response_.Clone();
size_t spdy_response_headers_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
1, promise_id_, false, &spdy_response_headers_frame_length));
// Receive the promised response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(InnerConstructDataPacket(2, promise_id_, false, kFin, 0,
kResponseBody, &server_maker_));
// Now sending a matching request will have successful rendezvous
// with the promised stream.
EXPECT_EQ(OK, promised_stream_->SendRequest(headers_, &response_,
callback_.callback()));
EXPECT_EQ(
QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
->id(),
promise_id_);
// The headers will be immediately available.
EXPECT_THAT(promised_stream_->ReadResponseHeaders(callback_.callback()),
IsOk());
// As will be the body.
EXPECT_EQ(
static_cast<int>(strlen(kResponseBody)),
promised_stream_->ReadResponseBody(
read_buffer_.get(), read_buffer_->size(), callback_.callback()));
EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
EXPECT_EQ(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
promised_stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, ServerPushCrossOriginFail) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
push_promise_[":authority"] = "www.notexample.org";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
ReceivePromise(promise_id_);
// The promise will have been rejected because the cert doesn't
// match.
EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
}
TEST_P(QuicHttpStreamTest, ServerPushVaryCheckOK) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
push_promise_["accept-encoding"] = "gzip";
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
request_.url = GURL(promise_url_);
// Make the second stream that will exercise the first step of the
// server push rendezvous mechanism.
EXPECT_EQ(OK, promised_stream_->InitializeStream(
&request_, true, DEFAULT_PRIORITY, net_log_.bound(),
callback_.callback()));
headers_.SetHeader("accept-encoding", "gzip");
// Now sending a matching request will rendezvous with the promised
// stream, but pending secondary validation.
EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
headers_, &response_, callback_.callback()));
// Receive the promised response headers.
promised_response_["vary"] = "accept-encoding";
response_headers_ = promised_response_.Clone();
size_t spdy_response_headers_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
1, promise_id_, false, &spdy_response_headers_frame_length));
// Receive the promised response body.
const char kResponseBody[] = "Hello world!";
ProcessPacket(InnerConstructDataPacket(2, promise_id_, false, kFin, 0,
kResponseBody, &server_maker_));
base::RunLoop().RunUntilIdle();
// Rendezvous should have succeeded now, so the promised stream
// should point at our push stream, and we should be able read
// headers and data from it.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
EXPECT_EQ(
QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
->id(),
promise_id_);
EXPECT_THAT(promised_stream_->ReadResponseHeaders(callback_.callback()),
IsOk());
EXPECT_EQ(
static_cast<int>(strlen(kResponseBody)),
promised_stream_->ReadResponseBody(
read_buffer_.get(), read_buffer_->size(), callback_.callback()));
// Callback should return
EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
EXPECT_EQ(0, stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
strlen(kResponseBody)),
promised_stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, ServerPushVaryCheckFail) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
request_headers_[":scheme"] = "https";
request_headers_[":path"] = "/bar";
request_headers_["accept-encoding"] = "sdch";
size_t spdy_request_header_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
QuicPacketNumber client_packet_number = 2;
if (client_headers_include_h2_stream_dependency_ &&
version_ > QUIC_VERSION_42) {
AddWrite(ConstructClientPriorityPacket(
client_packet_number++, kIncludeVersion, promise_id_, 0,
DEFAULT_PRIORITY, &header_stream_offset));
}
AddWrite(ConstructClientRstStreamVaryMismatchPacket(client_packet_number++));
AddWrite(InnerConstructRequestHeadersPacket(
client_packet_number++, stream_id_ + 2, !kIncludeVersion, kFin,
DEFAULT_PRIORITY, promise_id_, &spdy_request_header_frame_length,
&header_stream_offset));
AddWrite(ConstructClientAckPacket(client_packet_number++, 3, 1, 1));
AddWrite(ConstructClientRstStreamCancelledPacket(client_packet_number++));
Initialize();
// Initialize the first stream, for receiving the promise on.
request_.method = "GET";
request_.url = GURL("https://www.example.org/");
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, true, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
push_promise_["accept-encoding"] = "gzip";
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter?
ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
request_.url = GURL(promise_url_);
// Make the second stream that will exercise the first step of the
// server push rendezvous mechanism.
EXPECT_EQ(OK, promised_stream_->InitializeStream(
&request_, true, DEFAULT_PRIORITY, net_log_.bound(),
callback_.callback()));
headers_.SetHeader("accept-encoding", "sdch");
// Now sending a matching request will rendezvous with the promised
// stream, but pending secondary validation.
EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
headers_, &response_, callback_.callback()));
// Receive the promised response headers.
promised_response_["vary"] = "accept-encoding";
response_headers_ = promised_response_.Clone();
size_t spdy_response_headers_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
1, promise_id_, false, &spdy_response_headers_frame_length));
base::RunLoop().RunUntilIdle();
// Rendezvous should have failed due to vary mismatch, so the
// promised stream should have been aborted, and instead we have a
// new, regular client initiated stream.
EXPECT_THAT(callback_.WaitForResult(), IsOk());
// Not a server-initiated stream.
EXPECT_NE(
QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
->id(),
promise_id_);
// Instead, a new client-initiated stream.
EXPECT_EQ(
QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
->id(),
stream_id_ + 2);
// After rendezvous failure, the push stream has been cancelled.
EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
// The rest of the test verifies that the retried as
// client-initiated version of |promised_stream_| works as intended.
// Ack the request.
ProcessPacket(ConstructServerAckPacket(2, 0, 0, 0));
SetResponse("404 Not Found", string());
size_t spdy_response_header_frame_length;
ProcessPacket(InnerConstructResponseHeadersPacket(
3, stream_id_ + 2, kFin, &spdy_response_header_frame_length));
base::RunLoop().RunUntilIdle();
EXPECT_THAT(promised_stream_->ReadResponseHeaders(callback_.callback()),
IsOk());
ASSERT_TRUE(response_.headers.get());
EXPECT_EQ(404, response_.headers->response_code());
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
EXPECT_FALSE(response_.response_time.is_null());
EXPECT_FALSE(response_.request_time.is_null());
// There is no body, so this should return immediately.
EXPECT_EQ(
0, promised_stream_->ReadResponseBody(
read_buffer_.get(), read_buffer_->size(), callback_.callback()));
EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
stream_->Close(true);
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
// headers and payload.
EXPECT_EQ(static_cast<int64_t>(spdy_request_header_frame_length),
promised_stream_->GetTotalSentBytes());
EXPECT_EQ(static_cast<int64_t>(spdy_response_header_frame_length),
promised_stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, DataReadErrorSynchronous) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(ConstructRequestAndRstPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, 0, &spdy_request_headers_frame_length,
&header_stream_offset, QUIC_ERROR_PROCESSING_STREAM, 0));
Initialize();
upload_data_stream_ = std::make_unique<ReadErrorUploadDataStream>(
ReadErrorUploadDataStream::FailureMode::SYNC);
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
int result = stream_->SendRequest(headers_, &response_, callback_.callback());
EXPECT_THAT(result, IsError(ERR_FAILED));
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes includes only headers.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
TEST_P(QuicHttpStreamTest, DataReadErrorAsynchronous) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;
QuicStreamOffset header_stream_offset = 0;
AddWrite(ConstructInitialSettingsPacket(&header_stream_offset));
AddWrite(InnerConstructRequestHeadersPacket(
2, GetNthClientInitiatedStreamId(0), kIncludeVersion, !kFin,
DEFAULT_PRIORITY, &spdy_request_headers_frame_length,
&header_stream_offset));
AddWrite(ConstructClientRstStreamErrorPacket(3, !kIncludeVersion));
Initialize();
upload_data_stream_ = std::make_unique<ReadErrorUploadDataStream>(
ReadErrorUploadDataStream::FailureMode::ASYNC);
request_.method = "POST";
request_.url = GURL("https://www.example.org/");
request_.upload_data_stream = upload_data_stream_.get();
ASSERT_EQ(OK, request_.upload_data_stream->Init(
TestCompletionCallback().callback(), NetLogWithSource()));
EXPECT_EQ(OK,
stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
net_log_.bound(), callback_.callback()));
int result = stream_->SendRequest(headers_, &response_, callback_.callback());
ProcessPacket(ConstructServerAckPacket(1, 0, 0, 0));
SetResponse("200 OK", string());
EXPECT_THAT(result, IsError(ERR_IO_PENDING));
EXPECT_THAT(callback_.GetResult(result), IsError(ERR_FAILED));
EXPECT_TRUE(AtEof());
// QuicHttpStream::GetTotalSent/ReceivedBytes includes only headers.
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
stream_->GetTotalSentBytes());
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
}
} // namespace test
} // namespace net