blob: c0643d4458d89d24681ea9a90b8fcb94b770cac5 [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/quic_http_stream.h"
#include <vector>
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/http/http_response_headers.h"
#include "net/quic/congestion_control/receive_algorithm_interface.h"
#include "net/quic/congestion_control/send_algorithm_interface.h"
#include "net/quic/crypto/crypto_protocol.h"
#include "net/quic/crypto/quic_decrypter.h"
#include "net/quic/crypto/quic_encrypter.h"
#include "net/quic/crypto/quic_server_info.h"
#include "net/quic/quic_client_session.h"
#include "net/quic/quic_connection.h"
#include "net/quic/quic_connection_helper.h"
#include "net/quic/quic_default_packet_writer.h"
#include "net/quic/quic_http_utils.h"
#include "net/quic/quic_reliable_client_stream.h"
#include "net/quic/quic_write_blocked_list.h"
#include "net/quic/spdy_utils.h"
#include "net/quic/test_tools/mock_clock.h"
#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
#include "net/quic/test_tools/mock_random.h"
#include "net/quic/test_tools/quic_connection_peer.h"
#include "net/quic/test_tools/quic_test_packet_maker.h"
#include "net/quic/test_tools/quic_test_utils.h"
#include "net/quic/test_tools/test_task_runner.h"
#include "net/socket/socket_test_util.h"
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/spdy/spdy_protocol.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::AnyNumber;
using testing::Return;
namespace net {
namespace test {
namespace {
const char kUploadData[] = "hello world!";
const char kServerHostname[] = "www.google.com";
const uint16 kServerPort = 80;
class TestQuicConnection : public QuicConnection {
public:
TestQuicConnection(const QuicVersionVector& versions,
QuicConnectionId connection_id,
IPEndPoint address,
QuicConnectionHelper* helper,
QuicPacketWriter* writer)
: QuicConnection(connection_id, address, helper, writer, false,
versions) {
}
void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
}
void SetReceiveAlgorithm(ReceiveAlgorithmInterface* receive_algorithm) {
QuicConnectionPeer::SetReceiveAlgorithm(this, receive_algorithm);
}
};
class TestReceiveAlgorithm : public ReceiveAlgorithmInterface {
public:
virtual bool GenerateCongestionFeedback(
QuicCongestionFeedbackFrame* /*congestion_feedback*/) {
return false;
}
MOCK_METHOD3(RecordIncomingPacket,
void(QuicByteCount, QuicPacketSequenceNumber, QuicTime));
};
// Subclass of QuicHttpStream that closes itself when the first piece of data
// is received.
class AutoClosingStream : public QuicHttpStream {
public:
explicit AutoClosingStream(const base::WeakPtr<QuicClientSession>& session)
: QuicHttpStream(session) {
}
virtual int OnDataReceived(const char* data, int length) OVERRIDE {
Close(false);
return OK;
}
};
} // namespace
class QuicHttpStreamPeer {
public:
static QuicReliableClientStream* GetQuicReliableClientStream(
QuicHttpStream* stream) {
return stream->stream_;
}
};
class QuicHttpStreamTest : public ::testing::TestWithParam<QuicVersion> {
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, QuicEncryptedPacket* packet)
: mode(mode),
packet(packet) {
}
IoMode mode;
QuicEncryptedPacket* packet;
};
QuicHttpStreamTest()
: net_log_(BoundNetLog()),
use_closing_stream_(false),
read_buffer_(new IOBufferWithSize(4096)),
connection_id_(2),
stream_id_(kClientDataStreamId1),
maker_(GetParam(), connection_id_),
random_generator_(0) {
IPAddressNumber ip;
CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
peer_addr_ = IPEndPoint(ip, 443);
self_addr_ = IPEndPoint(ip, 8435);
}
~QuicHttpStreamTest() {
session_->CloseSessionOnError(ERR_ABORTED);
for (size_t i = 0; i < writes_.size(); i++) {
delete writes_[i].packet;
}
}
// Adds a packet to the list of expected writes.
void AddWrite(scoped_ptr<QuicEncryptedPacket> packet) {
writes_.push_back(PacketToWrite(SYNCHRONOUS, packet.release()));
}
// Returns the packet to be written at position |pos|.
QuicEncryptedPacket* GetWrite(size_t pos) {
return writes_[pos].packet;
}
bool AtEof() {
return socket_data_->at_read_eof() && socket_data_->at_write_eof();
}
void ProcessPacket(scoped_ptr<QuicEncryptedPacket> packet) {
connection_->ProcessUdpPacket(self_addr_, 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++) {
mock_writes_[i] = MockWrite(writes_[i].mode,
writes_[i].packet->data(),
writes_[i].packet->length());
};
socket_data_.reset(new StaticSocketDataProvider(NULL, 0, mock_writes_.get(),
writes_.size()));
MockUDPClientSocket* socket = new MockUDPClientSocket(socket_data_.get(),
net_log_.net_log());
socket->Connect(peer_addr_);
runner_ = new TestTaskRunner(&clock_);
send_algorithm_ = new MockSendAlgorithm();
receive_algorithm_ = new TestReceiveAlgorithm();
EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _)).
Times(AnyNumber());
EXPECT_CALL(*send_algorithm_,
OnPacketSent(_, _, _, _, _)).WillRepeatedly(Return(true));
EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
Return(QuicTime::Delta::Zero()));
EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
Return(kMaxPacketSize));
EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _)).
WillRepeatedly(Return(QuicTime::Delta::Zero()));
EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillRepeatedly(
Return(QuicBandwidth::Zero()));
EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(AnyNumber());
helper_.reset(new QuicConnectionHelper(runner_.get(), &clock_,
&random_generator_));
writer_.reset(new QuicDefaultPacketWriter(socket));
connection_ = new TestQuicConnection(SupportedVersions(GetParam()),
connection_id_, peer_addr_,
helper_.get(), writer_.get());
connection_->set_visitor(&visitor_);
connection_->SetSendAlgorithm(send_algorithm_);
connection_->SetReceiveAlgorithm(receive_algorithm_);
crypto_config_.SetDefaults();
session_.reset(
new QuicClientSession(connection_,
scoped_ptr<DatagramClientSocket>(socket),
writer_.Pass(), NULL,
&crypto_client_stream_factory_,
make_scoped_ptr((QuicServerInfo*)NULL),
QuicServerId(kServerHostname, kServerPort,
false, PRIVACY_MODE_DISABLED),
DefaultQuicConfig(), &crypto_config_,
base::MessageLoop::current()->
message_loop_proxy().get(),
NULL));
session_->GetCryptoStream()->CryptoConnect();
EXPECT_TRUE(session_->IsCryptoHandshakeConfirmed());
stream_.reset(use_closing_stream_ ?
new AutoClosingStream(session_->GetWeakPtr()) :
new QuicHttpStream(session_->GetWeakPtr()));
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
}
void SetRequest(const std::string& method,
const std::string& path,
RequestPriority priority) {
request_headers_ = maker_.GetRequestHeaders(method, "http", path);
}
void SetResponse(const std::string& status, const std::string& body) {
response_headers_ = maker_.GetResponseHeaders(status);
response_data_ = body;
}
scoped_ptr<QuicEncryptedPacket> ConstructDataPacket(
QuicPacketSequenceNumber sequence_number,
bool should_include_version,
bool fin,
QuicStreamOffset offset,
base::StringPiece data) {
return maker_.MakeDataPacket(
sequence_number, stream_id_, should_include_version, fin, offset, data);
}
scoped_ptr<QuicEncryptedPacket> ConstructRequestHeadersPacket(
QuicPacketSequenceNumber sequence_number,
bool fin) {
return maker_.MakeRequestHeadersPacket(
sequence_number, stream_id_, kIncludeVersion, fin, request_headers_);
}
scoped_ptr<QuicEncryptedPacket> ConstructResponseHeadersPacket(
QuicPacketSequenceNumber sequence_number,
bool fin) {
return maker_.MakeResponseHeadersPacket(
sequence_number, stream_id_, !kIncludeVersion, fin, response_headers_);
}
scoped_ptr<QuicEncryptedPacket> ConstructRstStreamPacket(
QuicPacketSequenceNumber sequence_number) {
return maker_.MakeRstPacket(
sequence_number, true, stream_id_,
AdjustErrorForVersion(QUIC_RST_FLOW_CONTROL_ACCOUNTING, GetParam()));
}
scoped_ptr<QuicEncryptedPacket> ConstructAckAndRstStreamPacket(
QuicPacketSequenceNumber sequence_number) {
return maker_.MakeAckAndRstPacket(
sequence_number, !kIncludeVersion, stream_id_, QUIC_STREAM_CANCELLED,
2, 1, !kIncludeCongestionFeedback);
}
scoped_ptr<QuicEncryptedPacket> ConstructAckPacket(
QuicPacketSequenceNumber sequence_number,
QuicPacketSequenceNumber largest_received,
QuicPacketSequenceNumber least_unacked) {
return maker_.MakeAckPacket(sequence_number, largest_received,
least_unacked, !kIncludeCongestionFeedback);
}
BoundNetLog net_log_;
bool use_closing_stream_;
MockSendAlgorithm* send_algorithm_;
TestReceiveAlgorithm* receive_algorithm_;
scoped_refptr<TestTaskRunner> runner_;
scoped_ptr<MockWrite[]> mock_writes_;
MockClock clock_;
TestQuicConnection* connection_;
scoped_ptr<QuicConnectionHelper> helper_;
testing::StrictMock<MockConnectionVisitor> visitor_;
scoped_ptr<QuicHttpStream> stream_;
scoped_ptr<QuicDefaultPacketWriter> writer_;
scoped_ptr<QuicClientSession> session_;
QuicCryptoClientConfig crypto_config_;
TestCompletionCallback callback_;
HttpRequestInfo request_;
HttpRequestHeaders headers_;
HttpResponseInfo response_;
scoped_refptr<IOBufferWithSize> read_buffer_;
SpdyHeaderBlock request_headers_;
SpdyHeaderBlock response_headers_;
std::string request_data_;
std::string response_data_;
private:
const QuicConnectionId connection_id_;
const QuicStreamId stream_id_;
QuicTestPacketMaker maker_;
IPEndPoint self_addr_;
IPEndPoint peer_addr_;
MockRandom random_generator_;
MockCryptoClientStreamFactory crypto_client_stream_factory_;
scoped_ptr<StaticSocketDataProvider> socket_data_;
std::vector<PacketToWrite> writes_;
};
INSTANTIATE_TEST_CASE_P(Version, QuicHttpStreamTest,
::testing::ValuesIn(QuicSupportedVersions()));
TEST_P(QuicHttpStreamTest, RenewStreamForAuth) {
Initialize();
EXPECT_EQ(NULL, stream_->RenewStreamForAuth());
}
TEST_P(QuicHttpStreamTest, CanFindEndOfResponse) {
Initialize();
EXPECT_TRUE(stream_->CanFindEndOfResponse());
}
TEST_P(QuicHttpStreamTest, IsConnectionReusable) {
Initialize();
EXPECT_FALSE(stream_->IsConnectionReusable());
}
TEST_P(QuicHttpStreamTest, GetRequest) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
AddWrite(ConstructRequestHeadersPacket(1, kFin));
Initialize();
request_.method = "GET";
request_.url = GURL("http://www.google.com/");
EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
net_log_, callback_.callback()));
EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
callback_.callback()));
// Ack the request.
ProcessPacket(ConstructAckPacket(1, 0, 0));
EXPECT_EQ(ERR_IO_PENDING,
stream_->ReadResponseHeaders(callback_.callback()));
SetResponse("404 Not Found", std::string());
ProcessPacket(ConstructResponseHeadersPacket(2, kFin));
// Now that the headers have been processed, the callback will return.
EXPECT_EQ(OK, callback_.WaitForResult());
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());
}
// Regression test for http://crbug.com/288128
TEST_P(QuicHttpStreamTest, GetRequestLargeResponse) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
AddWrite(ConstructRequestHeadersPacket(1, kFin));
Initialize();
request_.method = "GET";
request_.url = GURL("http://www.google.com/");
EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
net_log_, callback_.callback()));
EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
callback_.callback()));
// Ack the request.
ProcessPacket(ConstructAckPacket(1, 0, 0));
EXPECT_EQ(ERR_IO_PENDING,
stream_->ReadResponseHeaders(callback_.callback()));
SpdyHeaderBlock headers;
headers[":status"] = "200 OK";
headers[":version"] = "HTTP/1.1";
headers["content-type"] = "text/plain";
headers["big6"] = std::string(10000, 'x'); // Lots of x's.
std::string response = SpdyUtils::SerializeUncompressedHeaders(headers);
EXPECT_LT(4096u, response.length());
stream_->OnDataReceived(response.data(), response.length());
stream_->OnClose(QUIC_NO_ERROR);
// Now that the headers have been processed, the callback will return.
EXPECT_EQ(OK, callback_.WaitForResult());
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());
}
TEST_P(QuicHttpStreamTest, SendPostRequest) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
AddWrite(ConstructRequestHeadersPacket(1, !kFin));
AddWrite(ConstructDataPacket(2, kIncludeVersion, kFin, 0, kUploadData));
AddWrite(ConstructAckPacket(3, 3, 1));
Initialize();
ScopedVector<UploadElementReader> element_readers;
element_readers.push_back(
new UploadBytesElementReader(kUploadData, strlen(kUploadData)));
UploadDataStream upload_data_stream(element_readers.Pass(), 0);
request_.method = "POST";
request_.url = GURL("http://www.google.com/");
request_.upload_data_stream = &upload_data_stream;
ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
net_log_, callback_.callback()));
EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
callback_.callback()));
// Ack both packets in the request.
ProcessPacket(ConstructAckPacket(1, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", std::string());
ProcessPacket(ConstructResponseHeadersPacket(2, !kFin));
// Since the headers have already arrived, this should return immediately.
EXPECT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
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(ConstructDataPacket(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_TRUE(stream_->IsResponseBodyComplete());
EXPECT_TRUE(AtEof());
}
TEST_P(QuicHttpStreamTest, SendChunkedPostRequest) {
SetRequest("POST", "/", DEFAULT_PRIORITY);
size_t chunk_size = strlen(kUploadData);
AddWrite(ConstructRequestHeadersPacket(1, !kFin));
AddWrite(ConstructDataPacket(2, kIncludeVersion, !kFin, 0, kUploadData));
AddWrite(ConstructDataPacket(3, kIncludeVersion, kFin, chunk_size,
kUploadData));
AddWrite(ConstructAckPacket(4, 3, 1));
Initialize();
UploadDataStream upload_data_stream(UploadDataStream::CHUNKED, 0);
upload_data_stream.AppendChunk(kUploadData, chunk_size, false);
request_.method = "POST";
request_.url = GURL("http://www.google.com/");
request_.upload_data_stream = &upload_data_stream;
ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
ASSERT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
net_log_, callback_.callback()));
ASSERT_EQ(ERR_IO_PENDING, stream_->SendRequest(headers_, &response_,
callback_.callback()));
upload_data_stream.AppendChunk(kUploadData, chunk_size, true);
// Ack both packets in the request.
ProcessPacket(ConstructAckPacket(1, 0, 0));
// Send the response headers (but not the body).
SetResponse("200 OK", std::string());
ProcessPacket(ConstructResponseHeadersPacket(2, !kFin));
// Since the headers have already arrived, this should return immediately.
ASSERT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
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(ConstructDataPacket(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());
}
TEST_P(QuicHttpStreamTest, DestroyedEarly) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
AddWrite(ConstructRequestHeadersPacket(1, kFin));
AddWrite(ConstructAckAndRstStreamPacket(2));
use_closing_stream_ = true;
Initialize();
request_.method = "GET";
request_.url = GURL("http://www.google.com/");
EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
net_log_, callback_.callback()));
EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
callback_.callback()));
// Ack the request.
ProcessPacket(ConstructAckPacket(1, 0, 0));
EXPECT_EQ(ERR_IO_PENDING,
stream_->ReadResponseHeaders(callback_.callback()));
// Send the response with a body.
SetResponse("404 OK", "hello world!");
// In the course of processing this packet, the QuicHttpStream close itself.
ProcessPacket(ConstructResponseHeadersPacket(2, kFin));
EXPECT_TRUE(AtEof());
}
TEST_P(QuicHttpStreamTest, Priority) {
SetRequest("GET", "/", MEDIUM);
AddWrite(ConstructRequestHeadersPacket(1, kFin));
AddWrite(ConstructAckAndRstStreamPacket(2));
use_closing_stream_ = true;
Initialize();
request_.method = "GET";
request_.url = GURL("http://www.google.com/");
EXPECT_EQ(OK, stream_->InitializeStream(&request_, MEDIUM,
net_log_, callback_.callback()));
// Check that priority is highest.
QuicReliableClientStream* reliable_stream =
QuicHttpStreamPeer::GetQuicReliableClientStream(stream_.get());
DCHECK(reliable_stream);
DCHECK_EQ(QuicWriteBlockedList::kHighestPriority,
reliable_stream->EffectivePriority());
EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
callback_.callback()));
// Check that priority has now dropped back to MEDIUM.
DCHECK_EQ(MEDIUM, ConvertQuicPriorityToRequestPriority(
reliable_stream->EffectivePriority()));
// Ack the request.
ProcessPacket(ConstructAckPacket(1, 0, 0));
EXPECT_EQ(ERR_IO_PENDING,
stream_->ReadResponseHeaders(callback_.callback()));
// Send the response with a body.
SetResponse("404 OK", "hello world!");
// In the course of processing this packet, the QuicHttpStream close itself.
ProcessPacket(ConstructResponseHeadersPacket(2, kFin));
EXPECT_TRUE(AtEof());
}
// Regression test for http://crbug.com/294870
TEST_P(QuicHttpStreamTest, CheckPriorityWithNoDelegate) {
SetRequest("GET", "/", MEDIUM);
use_closing_stream_ = true;
AddWrite(ConstructRstStreamPacket(1));
Initialize();
request_.method = "GET";
request_.url = GURL("http://www.google.com/");
EXPECT_EQ(OK, stream_->InitializeStream(&request_, MEDIUM,
net_log_, callback_.callback()));
// Check that priority is highest.
QuicReliableClientStream* reliable_stream =
QuicHttpStreamPeer::GetQuicReliableClientStream(stream_.get());
DCHECK(reliable_stream);
QuicReliableClientStream::Delegate* delegate = reliable_stream->GetDelegate();
DCHECK(delegate);
DCHECK_EQ(QuicWriteBlockedList::kHighestPriority,
reliable_stream->EffectivePriority());
// Set Delegate to NULL and make sure EffectivePriority returns highest
// priority.
reliable_stream->SetDelegate(NULL);
DCHECK_EQ(QuicWriteBlockedList::kHighestPriority,
reliable_stream->EffectivePriority());
reliable_stream->SetDelegate(delegate);
}
} // namespace test
} // namespace net