summaryrefslogtreecommitdiffstats
path: root/net/tools
diff options
context:
space:
mode:
authoralyssar@google.com <alyssar@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-30 17:50:10 +0000
committeralyssar@google.com <alyssar@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-30 17:50:10 +0000
commite45d688990a877326888b7ae443c1c3ee8efcfa1 (patch)
tree5f9174a862ea890f441df425ca82bfac458879ff /net/tools
parentb851da9d3d378df04225d39ea44502742c73c20f (diff)
downloadchromium_src-e45d688990a877326888b7ae443c1c3ee8efcfa1.zip
chromium_src-e45d688990a877326888b7ae443c1c3ee8efcfa1.tar.gz
chromium_src-e45d688990a877326888b7ae443c1c3ee8efcfa1.tar.bz2
Adding an end to end test for QUIC, and fixing the bugs it turned up.
Review URL: https://chromiumcodereview.appspot.com/13004022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@191529 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/tools')
-rw-r--r--net/tools/quic/end_to_end_test.cc438
-rw-r--r--net/tools/quic/quic_in_memory_cache.cc2
-rw-r--r--net/tools/quic/quic_server.cc14
-rw-r--r--net/tools/quic/quic_server.h2
-rw-r--r--net/tools/quic/quic_spdy_server_stream.cc1
-rw-r--r--net/tools/quic/spdy_utils.cc34
-rw-r--r--net/tools/quic/test_tools/http_message_test_utils.cc173
-rw-r--r--net/tools/quic/test_tools/http_message_test_utils.h131
-rw-r--r--net/tools/quic/test_tools/quic_test_client.cc181
-rw-r--r--net/tools/quic/test_tools/quic_test_client.h95
-rw-r--r--net/tools/quic/test_tools/run_all_unittests.cc11
11 files changed, 1073 insertions, 9 deletions
diff --git a/net/tools/quic/end_to_end_test.cc b/net/tools/quic/end_to_end_test.cc
new file mode 100644
index 0000000..68968ca
--- /dev/null
+++ b/net/tools/quic/end_to_end_test.cc
@@ -0,0 +1,438 @@
+// 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 <stddef.h>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_server.h"
+#include "net/tools/quic/test_tools/http_message_test_utils.h"
+#include "net/tools/quic/test_tools/quic_test_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using base::WaitableEvent;
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+const char* kFooResponseBody = "Artichoke hearts make me happy.";
+const char* kBarResponseBody = "Palm hearts are pretty delicious, also.";
+const size_t kCongestionFeedbackFrameSize = 25;
+// If kCongestionFeedbackFrameSize increase we need to expand this string
+// accordingly.
+const char* kLargeRequest =
+ "https://www.google.com/foo/test/a/request/string/longer/than/25/bytes";
+
+void GenerateBody(string* body, int length) {
+ body->clear();
+ body->reserve(length);
+ for (int i = 0; i < length; ++i) {
+ body->append(1, static_cast<char>(32 + i % (126 - 32)));
+ }
+}
+
+
+// Simple wrapper class to run GFE in a thread.
+class ServerThread : public base::SimpleThread {
+ public:
+ explicit ServerThread(IPEndPoint address)
+ : SimpleThread("server_thread"),
+ listening_(true, false),
+ quit_(true, false),
+ address_(address),
+ port_(0) {
+ }
+ virtual ~ServerThread() {
+ }
+
+ virtual void Run() {
+ server_.Listen(address_);
+
+ port_lock_.Acquire();
+ port_ = server_.port();
+ port_lock_.Release();
+
+ listening_.Signal();
+ while (!quit_.IsSignaled()) {
+ server_.WaitForEvents();
+ }
+ server_.Shutdown();
+ }
+
+ int GetPort() {
+ port_lock_.Acquire();
+ int rc = port_;
+ port_lock_.Release();
+ return rc;
+ }
+
+ WaitableEvent* listening() { return &listening_; }
+ WaitableEvent* quit() { return &quit_; }
+
+ private:
+ WaitableEvent listening_;
+ WaitableEvent quit_;
+ base::Lock port_lock_;
+ QuicServer server_;
+ IPEndPoint address_;
+ int port_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerThread);
+};
+
+class EndToEndTest : public ::testing::Test {
+ protected:
+ EndToEndTest()
+ : server_hostname_("localhost") {
+ net::IPAddressNumber ip;
+ CHECK(net::ParseIPLiteralToNumber("127.0.0.1", &ip));
+ server_address_ = IPEndPoint(ip, 0);
+
+ AddToCache("GET", kLargeRequest, "HTTP/1.1", "200", "OK", kFooResponseBody);
+ AddToCache("GET", "https://www.google.com/foo",
+ "HTTP/1.1", "200", "OK", kFooResponseBody);
+ AddToCache("GET", "https://www.google.com/bar",
+ "HTTP/1.1", "200", "OK", kBarResponseBody);
+ }
+
+ virtual QuicTestClient* CreateQuicClient() {
+ QuicTestClient* client = new QuicTestClient(server_address_,
+ server_hostname_);
+ client->Connect();
+ return client;
+ }
+
+ virtual bool Initialize() {
+ // Start the server first, because CreateQuicClient() attempts
+ // to connect to the server.
+ StartServer();
+ client_.reset(CreateQuicClient());
+ return client_->client()->connected();
+ }
+
+ virtual void TearDown() {
+ StopServer();
+ }
+
+ void StartServer() {
+ server_thread_.reset(new ServerThread(server_address_));
+ server_thread_->Start();
+ server_thread_->listening()->Wait();
+ server_address_ = IPEndPoint(server_address_.address(),
+ server_thread_->GetPort());
+ }
+
+ void StopServer() {
+ server_thread_->quit()->Signal();
+ server_thread_->Join();
+ }
+
+ void AddToCache(const StringPiece& method,
+ const StringPiece& path,
+ const StringPiece& version,
+ const StringPiece& response_code,
+ const StringPiece& response_detail,
+ const StringPiece& body) {
+ BalsaHeaders request_headers, response_headers;
+ request_headers.SetRequestFirstlineFromStringPieces(method,
+ path,
+ version);
+ response_headers.SetRequestFirstlineFromStringPieces(version,
+ response_code,
+ response_detail);
+ response_headers.AppendHeader("content-length",
+ base::IntToString(body.length()));
+
+ // Check if response already exists and matches.
+ QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance();
+ const QuicInMemoryCache::Response* cached_response =
+ cache->GetResponse(request_headers);
+ if (cached_response != NULL) {
+ string cached_response_headers_str, response_headers_str;
+ cached_response->headers().DumpToString(&cached_response_headers_str);
+ response_headers.DumpToString(&response_headers_str);
+ CHECK_EQ(cached_response_headers_str, response_headers_str);
+ CHECK_EQ(cached_response->body(), body);
+ return;
+ }
+ cache->AddResponse(request_headers, response_headers, body);
+ }
+
+ IPEndPoint server_address_;
+ string server_hostname_;
+ scoped_ptr<ServerThread> server_thread_;
+ scoped_ptr<QuicTestClient> client_;
+};
+
+TEST_F(EndToEndTest, SimpleRequestResponse) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, SimpleRequestResponsev6) {
+ IPAddressNumber ip;
+ CHECK(net::ParseIPLiteralToNumber("::", &ip));
+ server_address_ = IPEndPoint(ip, server_address_.port());
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, SeparateFinPacket) {
+ ASSERT_TRUE(Initialize());
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.set_has_complete_message(false);
+
+ client_->SendMessage(request);
+
+ client_->SendData("", true);
+
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+
+ request.AddBody("foo", true);
+
+ client_->SendMessage(request);
+ client_->SendData("", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, MultipleRequestResponse) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, MultipleClients) {
+ ASSERT_TRUE(Initialize());
+ scoped_ptr<QuicTestClient> client2(CreateQuicClient());
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddHeader("content-length", "3");
+ request.set_has_complete_message(false);
+
+ client_->SendMessage(request);
+ client2->SendMessage(request);
+
+ client_->SendData("bar", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+
+ client2->SendData("eep", true);
+ client2->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client2->response_body());
+ EXPECT_EQ(200ul, client2->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, RequestOverMultiplePackets) {
+ ASSERT_TRUE(Initialize());
+ // Set things up so we have a small payload, to guarantee fragmentation.
+ // A congestion feedback frame can't be split into multiple packets, make sure
+ // that our packet have room for at least this amount after the normal headers
+ // are added.
+
+ // TODO(rch) handle this better when we have different encryption options.
+ size_t stream_data = 3;
+ size_t stream_payload_size = QuicFramer::GetMinStreamFrameSize() +
+ stream_data;
+ size_t min_payload_size =
+ std::max(kCongestionFeedbackFrameSize, stream_payload_size);
+ size_t ciphertext_size = NullEncrypter().GetCiphertextSize(min_payload_size);
+ // TODO(satyashekhar): Fix this when versioning is implemented.
+ client_->options()->max_packet_length =
+ GetPacketHeaderSize(!kIncludeVersion) + ciphertext_size;
+
+ // Make sure our request is too large to fit in one packet.
+ EXPECT_GT(strlen(kLargeRequest), min_payload_size);
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest(kLargeRequest));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, MultipleFramesRandomOrder) {
+ ASSERT_TRUE(Initialize());
+ // Set things up so we have a small payload, to guarantee fragmentation.
+ // A congestion feedback frame can't be split into multiple packets, make sure
+ // that our packet have room for at least this amount after the normal headers
+ // are added.
+
+ // TODO(rch) handle this better when we have different encryption options.
+ size_t stream_data = 3;
+ size_t stream_payload_size = QuicFramer::GetMinStreamFrameSize() +
+ stream_data;
+ size_t min_payload_size =
+ std::max(kCongestionFeedbackFrameSize, stream_payload_size);
+ size_t ciphertext_size = NullEncrypter().GetCiphertextSize(min_payload_size);
+ // TODO(satyashekhar): Fix this when versioning is implemented.
+ client_->options()->max_packet_length =
+ GetPacketHeaderSize(!kIncludeVersion) + ciphertext_size;
+ client_->options()->random_reorder = true;
+
+ // Make sure our request is too large to fit in one packet.
+ EXPECT_GT(strlen(kLargeRequest), min_payload_size);
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest(kLargeRequest));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, PostMissingBytes) {
+ ASSERT_TRUE(Initialize());
+
+ // Add a content length header with no body.
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddHeader("content-length", "3");
+ request.set_skip_message_validation(true);
+
+ // This should be detected as stream fin without complete request,
+ // triggering an error response.
+ client_->SendCustomSynchronousRequest(request);
+ EXPECT_EQ("bad", client_->response_body());
+ EXPECT_EQ(500ul, client_->response_headers()->parsed_response_code());
+}
+
+TEST_F(EndToEndTest, LargePost) {
+ // FLAGS_fake_packet_loss_percentage = 30;
+ ASSERT_TRUE(Initialize());
+
+ string body;
+ GenerateBody(&body, 10240);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+
+ EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request));
+}
+
+TEST_F(EndToEndTest, LargePostFEC) {
+ // FLAGS_fake_packet_loss_percentage = 30;
+ ASSERT_TRUE(Initialize());
+ client_->options()->max_packets_per_fec_group = 6;
+
+ string body;
+ GenerateBody(&body, 10240);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+
+ EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request));
+}
+
+/*TEST_F(EndToEndTest, PacketTooLarge) {
+ FLAGS_quic_allow_oversized_packets_for_test = true;
+ ASSERT_TRUE(Initialize());
+
+ string body;
+ GenerateBody(&body, kMaxPacketSize);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+ client_->options()->max_packet_length = 20480;
+
+ EXPECT_EQ("", client_->SendCustomSynchronousRequest(request));
+ EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_PACKET_TOO_LARGE, client_->connection_error());
+}*/
+
+TEST_F(EndToEndTest, InvalidStream) {
+ ASSERT_TRUE(Initialize());
+
+ string body;
+ GenerateBody(&body, kMaxPacketSize);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+ // Force the client to write with a stream ID belonging to a nonexistant
+ // server-side stream.
+ QuicSessionPeer::SetNextStreamId(2, client_->client()->session());
+
+ client_->SendCustomSynchronousRequest(request);
+// EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_PACKET_FOR_NONEXISTENT_STREAM, client_->connection_error());
+}
+
+TEST_F(EndToEndTest, MultipleTermination) {
+ ASSERT_TRUE(Initialize());
+ scoped_ptr<QuicTestClient> client2(CreateQuicClient());
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddHeader("content-length", "3");
+ request.set_has_complete_message(false);
+
+ // Set the offset so we won't frame. Otherwise when we pick up termination
+ // before HTTP framing is complete, we send an error and close the stream,
+ // and the second write is picked up as writing on a closed stream.
+ QuicReliableClientStream* stream = client_->GetOrCreateStream();
+ ASSERT_TRUE(stream != NULL);
+ ReliableQuicStreamPeer::SetStreamBytesWritten(3, stream);
+
+ client_->SendData("bar", true);
+
+ // By default the stream protects itself from writes after terminte is set.
+ // Override this to test the server handling buggy clients.
+ ReliableQuicStreamPeer::SetWriteSideClosed(
+ false, client_->GetOrCreateStream());
+ EXPECT_DEBUG_DEATH({
+ client_->SendData("eep", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(QUIC_MULTIPLE_TERMINATION_OFFSETS, client_->stream_error());
+ },
+ "Check failed: !fin_buffered_");
+}
+
+/*TEST_F(EndToEndTest, Timeout) {
+ FLAGS_negotiated_timeout_us = 500;
+ // Note: we do NOT ASSERT_TRUE: we may time out during initial handshake:
+ // that's enough to validate timeout in this case.
+ Initialize();
+
+ while (client_->client()->connected()) {
+ client_->client()->WaitForEvents();
+ }
+}*/
+
+TEST_F(EndToEndTest, ResetConnection) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+ client_->ResetConnection();
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ(200ul, client_->response_headers()->parsed_response_code());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/net/tools/quic/quic_in_memory_cache.cc b/net/tools/quic/quic_in_memory_cache.cc
index 6e1f51f..ede999f 100644
--- a/net/tools/quic/quic_in_memory_cache.cc
+++ b/net/tools/quic/quic_in_memory_cache.cc
@@ -87,7 +87,7 @@ void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers,
const BalsaHeaders& response_headers,
StringPiece response_body) {
LOG(INFO) << "Adding response for: " << GetKey(request_headers);
- if (responses_.find(GetKey(request_headers)) == responses_.end()) {
+ if (ContainsKey(responses_, GetKey(request_headers))) {
LOG(DFATAL) << "Response for given request already exists!";
}
Response* new_response = new Response();
diff --git a/net/tools/quic/quic_server.cc b/net/tools/quic/quic_server.cc
index e97f635..6adee2d 100644
--- a/net/tools/quic/quic_server.cc
+++ b/net/tools/quic/quic_server.cc
@@ -93,8 +93,20 @@ bool QuicServer::Listen(const IPEndPoint& address) {
return false;
}
- epoll_server_.RegisterFD(fd_, this, kEpollFlags);
LOG(INFO) << "Listening on " << address.ToString();
+ if (port_ == 0) {
+ SockaddrStorage storage;
+ IPEndPoint server_address;
+ if (getsockname(fd_, storage.addr, &storage.addr_len) != 0 ||
+ !server_address.FromSockAddr(storage.addr, storage.addr_len)) {
+ LOG(ERROR) << "Unable to get self address. Error: " << strerror(errno);
+ return false;
+ }
+ port_ = server_address.port();
+ LOG(INFO) << "Kernel assigned port is " << port_;
+ }
+
+ epoll_server_.RegisterFD(fd_, this, kEpollFlags);
dispatcher_.reset(new QuicDispatcher(fd_, &epoll_server_));
diff --git a/net/tools/quic/quic_server.h b/net/tools/quic/quic_server.h
index b985fb5..cc779ea 100644
--- a/net/tools/quic/quic_server.h
+++ b/net/tools/quic/quic_server.h
@@ -55,6 +55,8 @@ class QuicServer : public EpollCallbackInterface {
int packets_dropped() { return packets_dropped_; }
+ int port() { return port_; }
+
private:
// Accepts data from the framer and demuxes clients to sessions.
scoped_ptr<QuicDispatcher> dispatcher_;
diff --git a/net/tools/quic/quic_spdy_server_stream.cc b/net/tools/quic/quic_spdy_server_stream.cc
index e59c37b..6d98e19 100644
--- a/net/tools/quic/quic_spdy_server_stream.cc
+++ b/net/tools/quic/quic_spdy_server_stream.cc
@@ -87,6 +87,7 @@ int QuicSpdyServerStream::ParseRequestHeaders() {
mutable_body()->append(data + len, delta);
}
+ request_headers_received_ = true;
return len;
}
diff --git a/net/tools/quic/spdy_utils.cc b/net/tools/quic/spdy_utils.cc
index f52da10..9078866 100644
--- a/net/tools/quic/spdy_utils.cc
+++ b/net/tools/quic/spdy_utils.cc
@@ -9,6 +9,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/string_piece.h"
#include "base/string_util.h"
+#include "base/strings/string_number_conversions.h"
#include "googleurl/src/gurl.h"
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_framer.h"
@@ -108,12 +109,19 @@ string SpdyUtils::SerializeRequestHeaders(
if (request_headers.request_method() == "CONNECT") {
path = url;
} else {
- path = request_uri.path() + "?" + request_uri.query();
- host_and_port = request_uri.port();
+ path = request_uri.path();
+ if (!request_uri.query().empty()) {
+ path = path + "?" + request_uri.query();
+ }
+ host_and_port = request_uri.host();
scheme = request_uri.scheme();
}
}
+ DCHECK(!scheme.empty());
+ DCHECK(!host_and_port.empty());
+ DCHECK(!path.empty());
+
SpdyHeaderBlock block;
PopulateSpdy3RequestHeaderBlock(
request_headers, scheme, host_and_port, path, &block);
@@ -170,11 +178,21 @@ bool SpdyUtils::FillBalsaRequestHeaders(
request_headers->SetRequestUri(url);
request_headers->SetRequestMethod(method_it->second);
+ BlockIt cl_it = header_block.find("content-length");
+ if (cl_it != header_block.end()) {
+ int content_length;
+ if (!base::StringToInt(cl_it->second, &content_length)) {
+ return false;
+ }
+ request_headers->SetContentLength(content_length);
+ }
+
for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) {
if (!IsSpecialSpdyHeader(it, request_headers)) {
request_headers->AppendHeader(it->first, it->second);
}
}
+
return true;
}
@@ -188,12 +206,14 @@ bool ParseReasonAndStatus(StringPiece status_and_reason,
if (status_and_reason[3] != ' ')
return false;
- const StringPiece status = StringPiece(status_and_reason.data(), 3);
-// if (!IsDigitString(status))//FIXME
-// return false;
+ const StringPiece status_str = StringPiece(status_and_reason.data(), 3);
+ int status;
+ if (!base::StringToInt(status_str, &status)) {
+ return false;
+ }
- headers->SetResponseCode(status);
- headers->set_parsed_response_code(atoi(status.data()));
+ headers->SetResponseCode(status_str);
+ headers->set_parsed_response_code(status);
StringPiece reason(status_and_reason.data() + 4,
status_and_reason.length() - 4);
diff --git a/net/tools/quic/test_tools/http_message_test_utils.cc b/net/tools/quic/test_tools/http_message_test_utils.cc
new file mode 100644
index 0000000..d88d06c
--- /dev/null
+++ b/net/tools/quic/test_tools/http_message_test_utils.cc
@@ -0,0 +1,173 @@
+// 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/tools/quic/test_tools/http_message_test_utils.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+namespace {
+
+//const char* kContentEncoding = "content-encoding";
+const char* kContentLength = "content-length";
+const char* kTransferCoding = "transfer-encoding";
+
+// Both kHTTPVersionString and kMethodString arrays are constructed to match
+// the enum values defined in Version and Method of HTTPMessage.
+const char* kHTTPVersionString[] = {
+ "",
+ "HTTP/0.9",
+ "HTTP/1.0",
+ "HTTP/1.1"
+};
+
+const char* kMethodString[] = {
+ "",
+ "OPTIONS",
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ "DELETE",
+ "TRACE",
+ "CONNECT",
+ "MKCOL",
+ "UNLOCK",
+};
+
+// Returns true if the message represents a complete request or response.
+// Messages are considered complete if:
+// - Transfer-Encoding: chunked is present and message has a final chunk.
+// - Content-Length header is present and matches the message body length.
+// - Neither Transfer-Encoding nor Content-Length is present and message
+// is tagged as complete.
+bool IsCompleteMessage(const HTTPMessage& message) {
+ return true;
+ const BalsaHeaders* headers = message.headers();
+ StringPiece content_length = headers->GetHeader(kContentLength);
+ if (!content_length.empty()) {
+ int parsed_content_length;
+ if (!base::StringToInt(content_length, &parsed_content_length)) {
+ return false;
+ }
+ return (message.body().size() == (uint)parsed_content_length);
+ } else {
+ // Assume messages without transfer coding or content-length are
+ // tagged correctly.
+ return message.has_complete_message();
+ }
+}
+
+} // namespace
+
+HTTPMessage::Method HTTPMessage::StringToMethod(StringPiece str) {
+ // Skip the first element of the array since it is empty string.
+ for (unsigned long i = 1; i < arraysize(kMethodString); ++i) {
+ if (strncmp(str.data(), kMethodString[i], str.length()) == 0) {
+ return static_cast<HTTPMessage::Method>(i);
+ }
+ }
+ return HttpConstants::UNKNOWN_METHOD;
+}
+
+HTTPMessage::Version HTTPMessage::StringToVersion(StringPiece str) {
+ // Skip the first element of the array since it is empty string.
+ for (unsigned long i = 1; i < arraysize(kHTTPVersionString); ++i) {
+ if (strncmp(str.data(), kHTTPVersionString[i], str.length()) == 0) {
+ return static_cast<HTTPMessage::Version>(i);
+ }
+ }
+ return HttpConstants::HTTP_UNKNOWN;
+}
+
+const char* HTTPMessage::MethodToString(Method method) {
+ CHECK_LT(method, arraysize(kMethodString));
+ return kMethodString[method];
+}
+
+const char* HTTPMessage::VersionToString(Version version) {
+ CHECK_LT(version, arraysize(kHTTPVersionString));
+ return kHTTPVersionString[version];
+}
+
+HTTPMessage::HTTPMessage()
+ : is_request_(true) {
+ InitializeFields();
+}
+
+HTTPMessage::HTTPMessage(Version ver, Method request, const string& path)
+ : is_request_(true) {
+ InitializeFields();
+ if (ver != HttpConstants::HTTP_0_9) {
+ headers()->SetRequestVersion(VersionToString(ver));
+ }
+ headers()->SetRequestMethod(MethodToString(request));
+ headers()->SetRequestUri(path);
+}
+
+HTTPMessage::~HTTPMessage() {
+}
+
+void HTTPMessage::InitializeFields() {
+ has_complete_message_ = true;
+ skip_message_validation_ = false;
+}
+
+void HTTPMessage::AddHeader(const string& header, const string& value) {
+ headers()->AppendHeader(header, value);
+}
+
+void HTTPMessage::RemoveHeader(const string& header) {
+ headers()->RemoveAllOfHeader(header);
+}
+
+void HTTPMessage::ReplaceHeader(const string& header, const string& value) {
+ headers()->ReplaceOrAppendHeader(header, value);
+}
+
+void HTTPMessage::AddBody(const string& body, bool add_content_length) {
+ body_ = body;
+ // Remove any transfer-encoding that was left by a previous body.
+ RemoveHeader(kTransferCoding);
+ if (add_content_length) {
+ ReplaceHeader(kContentLength, base::IntToString(body.size()));
+ } else {
+ RemoveHeader(kContentLength);
+ }
+}
+
+void HTTPMessage::ValidateMessage() const {
+ if (skip_message_validation_) {
+ return;
+ }
+
+ vector<StringPiece> transfer_encodings;
+ headers()->GetAllOfHeader(kTransferCoding, &transfer_encodings);
+ CHECK_GE(1ul, transfer_encodings.size());
+ for (vector<StringPiece>::iterator it = transfer_encodings.begin();
+ it != transfer_encodings.end();
+ ++it) {
+ CHECK(StringPieceUtils::EqualIgnoreCase("identity", *it) ||
+ StringPieceUtils::EqualIgnoreCase("chunked", *it)) << *it;
+ }
+
+ vector<StringPiece> content_lengths;
+ headers()->GetAllOfHeader(kContentLength, &content_lengths);
+ CHECK_GE(1ul, content_lengths.size());
+
+ CHECK_EQ(has_complete_message_, IsCompleteMessage(*this));
+}
+
+} // namespace test
+} // namespace net
diff --git a/net/tools/quic/test_tools/http_message_test_utils.h b/net/tools/quic/test_tools/http_message_test_utils.h
new file mode 100644
index 0000000..d594295
--- /dev/null
+++ b/net/tools/quic/test_tools/http_message_test_utils.h
@@ -0,0 +1,131 @@
+// 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.
+
+#ifndef NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+namespace test {
+
+class HttpConstants {
+ public:
+ enum Version {
+ HTTP_UNKNOWN = 0,
+ HTTP_0_9,
+ HTTP_1_0,
+ HTTP_1_1
+ };
+
+ enum Method {
+ UNKNOWN_METHOD = 0,
+ OPTIONS,
+ GET,
+ HEAD,
+ POST,
+ PUT,
+ DELETE,
+ TRACE,
+ CONNECT,
+
+ MKCOL,
+ UNLOCK,
+ };
+};
+
+// Stripped down wrapper class which basically contains headers and a body.
+class HTTPMessage {
+ public:
+ typedef HttpConstants::Version Version;
+ typedef HttpConstants::Method Method;
+
+ // Convenient functions to map strings into enums. The string passed in is
+ // not assumed to be NULL-terminated.
+ static Version StringToVersion(base::StringPiece str);
+ static Method StringToMethod(base::StringPiece str);
+
+ static const char* MethodToString(Method method);
+ static const char* VersionToString(Version version);
+
+ // Default constructor makes an empty HTTP/1.1 GET request. This is typically
+ // used to construct a message that will be Initialize()-ed.
+ HTTPMessage();
+
+ // Build a request message
+ HTTPMessage(Version version, Method request, const std::string& path);
+
+ virtual ~HTTPMessage();
+
+ const std::string& body() const { return body_; }
+
+ // Adds a header line to the message.
+ void AddHeader(const std::string& header, const std::string& value);
+
+ // Removes a header line from the message.
+ void RemoveHeader(const std::string& header);
+
+ // A utility function which calls RemoveHeader followed by AddHeader.
+ void ReplaceHeader(const std::string& header, const std::string& value);
+
+ // Adds a body and the optional content-length header field (omitted to test
+ // read until close test case). To generate a message that has a header field
+ // of 0 content-length, call AddBody("", true).
+ // Multiple calls to AddBody()/AddChunkedBody() has the effect of overwriting
+ // the previous entry without warning.
+ void AddBody(const std::string& body, bool add_content_length);
+
+ bool has_complete_message() const { return has_complete_message_; }
+ void set_has_complete_message(bool value) { has_complete_message_ = value; }
+
+ // Do some basic http message consistency checks like:
+ // - Valid transfer-encoding header
+ // - Valid content-length header
+ // - Messages we expect to be complete are complete.
+ // This check can be disabled by setting skip_message_validation.
+ void ValidateMessage() const;
+
+ bool skip_message_validation() const { return skip_message_validation_; }
+ void set_skip_message_validation(bool value) {
+ skip_message_validation_ = value;
+ }
+
+ // Allow direct access to the body string. This should be used with caution:
+ // it will not update the request headers like AddBody and AddChunkedBody do.
+ void set_body(const std::string& body) { body_ = body; }
+
+ const BalsaHeaders* headers() const { return &headers_; }
+ BalsaHeaders* headers() { return &headers_; }
+
+ protected:
+ BalsaHeaders headers_;
+
+ std::string body_; // the body with chunked framing/gzip compression
+
+ bool is_request_;
+
+ // True if the message should be considered complete during serialization.
+ // Used by SPDY and Streamed RPC clients to decide wherever or not
+ // to include fin flags and during message validation (if enabled).
+ bool has_complete_message_;
+
+ // Allows disabling message validation when creating test messages
+ // that are intentionally invalid.
+ bool skip_message_validation_;
+
+ private:
+ void InitializeFields();
+
+ DISALLOW_COPY_AND_ASSIGN(HTTPMessage);
+};
+
+} // namespace test
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_
diff --git a/net/tools/quic/test_tools/quic_test_client.cc b/net/tools/quic/test_tools/quic_test_client.cc
new file mode 100644
index 0000000..22e3827
--- /dev/null
+++ b/net/tools/quic/test_tools/quic_test_client.cc
@@ -0,0 +1,181 @@
+// 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/tools/quic/test_tools/quic_test_client.h"
+
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/quic/test_tools/http_message_test_utils.h"
+
+using std::string;
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+BalsaHeaders* MungeHeaders(const BalsaHeaders* const_headers) {
+ StringPiece uri = const_headers->request_uri();
+ if (uri.empty()) {
+ return NULL;
+ }
+ if (const_headers->request_method() == "CONNECT") {
+ return NULL;
+ }
+ BalsaHeaders* headers = new BalsaHeaders;
+ headers->CopyFrom(*const_headers);
+ if (!uri.starts_with("https://") &&
+ !uri.starts_with("http://")) {
+ // If we have a relative URL, set some defaults.
+ string full_uri = "https:/www.google.com";
+ full_uri.append(uri.as_string());
+ headers->SetRequestUri(full_uri);
+ }
+ return headers;
+}
+
+QuicTestClient::QuicTestClient(IPEndPoint address, const string& hostname)
+ : server_address_(address),
+ client_(address, hostname),
+ stream_(NULL),
+ stream_error_(QUIC_NO_ERROR),
+ connection_error_(QUIC_NO_ERROR),
+ bytes_read_(0),
+ bytes_written_(0),
+ never_connected_(true) {
+}
+
+QuicTestClient::~QuicTestClient() {
+ if (stream_) {
+ stream_->set_visitor(NULL);
+ }
+}
+
+ssize_t QuicTestClient::SendRequest(const string& uri) {
+ HTTPMessage message(HttpConstants::HTTP_1_1, HttpConstants::GET, uri);
+ return SendMessage(message);
+}
+
+ssize_t QuicTestClient::SendMessage(const HTTPMessage& message) {
+ stream_ = NULL; // Always force creation of a stream for SendMessage.
+
+ QuicReliableClientStream* stream = GetOrCreateStream();
+ if (!stream) { return 0; }
+ scoped_ptr<BalsaHeaders> munged_headers(MungeHeaders(message.headers()));
+ return GetOrCreateStream()->SendRequest(
+ munged_headers.get() ? *munged_headers.get() : *message.headers(),
+ message.body(),
+ message.has_complete_message());
+}
+
+ssize_t QuicTestClient::SendData(string data, bool last_data) {
+ QuicReliableClientStream* stream = GetOrCreateStream();
+ if (!stream) { return 0; }
+ GetOrCreateStream()->SendBody(data, last_data);
+ return data.length();
+}
+
+string QuicTestClient::SendCustomSynchronousRequest(
+ const HTTPMessage& message) {
+ SendMessage(message);
+ WaitForResponse();
+ return response_;
+}
+
+string QuicTestClient::SendSynchronousRequest(const string& uri) {
+ if (SendRequest(uri) == 0) {
+ DLOG(ERROR) << "Failed to the request for uri:" << uri;
+ return "";
+ }
+ WaitForResponse();
+ return response_;
+}
+
+QuicReliableClientStream* QuicTestClient::GetOrCreateStream() {
+ if (never_connected_ == true) {
+ if (!connected()) {
+ Connect();
+ }
+ if (!connected()) {
+ return NULL;
+ }
+ }
+ if (!stream_) {
+ stream_ = client_.CreateReliableClientStream();
+ stream_->set_visitor(this);
+ }
+ return stream_;
+}
+
+bool QuicTestClient::connected() const {
+ return client_.connected();
+}
+
+void QuicTestClient::WaitForResponse() {
+ client_.WaitForStreamToClose(stream_->id());
+}
+
+void QuicTestClient::Connect() {
+ DCHECK(!connected());
+ client_.Initialize();
+ client_.Connect();
+ never_connected_ = false;
+}
+
+void QuicTestClient::ResetConnection() {
+ Disconnect();
+ Connect();
+}
+
+void QuicTestClient::Disconnect() {
+ client_.Disconnect();
+}
+
+IPEndPoint QuicTestClient::LocalSocketAddress() const {
+ return client_.client_address();
+}
+
+void QuicTestClient::ClearPerRequestState() {
+ stream_error_ = QUIC_NO_ERROR;
+ connection_error_ = QUIC_NO_ERROR;
+ stream_ = NULL;
+ response_ = "";
+ headers_.Clear();
+ bytes_read_ = 0;
+ bytes_written_ = 0;
+}
+
+void QuicTestClient::WaitForInitialResponse() {
+ DCHECK(stream_ != NULL);
+ while (stream_ && stream_->stream_bytes_read() == 0) {
+ client_.WaitForEvents();
+ }
+}
+
+ssize_t QuicTestClient::Send(const void *buffer, size_t size) {
+ return SendData(string(static_cast<const char*>(buffer), size), false);
+}
+
+int QuicTestClient::response_size() const {
+ return bytes_read_;
+}
+
+size_t QuicTestClient::bytes_read() const {
+ return bytes_read_;
+}
+
+size_t QuicTestClient::bytes_written() const {
+ return bytes_written_;
+}
+
+void QuicTestClient::OnClose(ReliableQuicStream* stream) {
+ response_ = stream_->data();
+ headers_.CopyFrom(stream_->headers());
+ stream_error_ = stream_->error();
+ connection_error_ = stream_->error();
+ bytes_read_ = stream_->stream_bytes_read();
+ bytes_written_ = stream_->stream_bytes_written();
+ stream_ = NULL;
+}
+
+} // namespace test
+} // namespace net
diff --git a/net/tools/quic/test_tools/quic_test_client.h b/net/tools/quic/test_tools/quic_test_client.h
new file mode 100644
index 0000000..d02905a
--- /dev/null
+++ b/net/tools/quic/test_tools/quic_test_client.h
@@ -0,0 +1,95 @@
+// 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/quic/quic_client.h"
+
+namespace net {
+
+namespace test {
+
+class HTTPMessage;
+
+// A toy QUIC client used for testing.
+class QuicTestClient : public ReliableQuicStream::Visitor {
+ public:
+ QuicTestClient(IPEndPoint server_address, const string& server_hostname);
+ virtual ~QuicTestClient();
+
+ // Clears any outstanding state and sends a simple GET of 'uri' to the
+ // server. Returns 0 if the request failed and no bytes were written.
+ ssize_t SendRequest(const string& uri);
+ ssize_t SendMessage(const HTTPMessage& message);
+
+ string SendCustomSynchronousRequest(const HTTPMessage& message);
+ string SendSynchronousRequest(const string& uri);
+
+ // Wraps data in a quic packet and sends it.
+ ssize_t SendData(string data, bool last_data);
+
+ QuicPacketCreator::Options* options() { return client_.options(); }
+
+ const BalsaHeaders *response_headers() const {return &headers_;}
+
+ void WaitForResponse();
+
+ void Connect();
+ void ResetConnection();
+ void Disconnect();
+ IPEndPoint LocalSocketAddress() const;
+ void ClearPerRequestState();
+ void WaitForInitialResponse();
+ ssize_t Send(const void *buffer, size_t size);
+ int response_size() const;
+ size_t bytes_read() const;
+ size_t bytes_written() const;
+
+ // From ReliableQuicStream::Visitor
+ virtual void OnClose(ReliableQuicStream* stream) OVERRIDE;
+
+ void SetNextStreamId(QuicStreamId id);
+
+ // Returns NULL if the maximum number of streams have already been created.
+ QuicReliableClientStream* GetOrCreateStream();
+
+ QuicErrorCode stream_error() { return stream_error_; }
+ QuicErrorCode connection_error() { return connection_error_; }
+
+ QuicClient* client() { return &client_; }
+
+ const string& response_body() {return response_;}
+ bool connected() const;
+
+ private:
+ IPEndPoint server_address_;
+ IPEndPoint client_address_;
+ QuicClient client_; // The actual client
+ QuicReliableClientStream* stream_;
+
+ QuicErrorCode stream_error_;
+ QuicErrorCode connection_error_;
+
+ BalsaHeaders headers_;
+ string response_;
+ uint64 bytes_read_;
+ uint64 bytes_written_;
+ // True if the client has never connected before. The client will
+ // auto-connect exactly once before sending data. If something causes a
+ // connection reset, it will not automatically reconnect.
+ bool never_connected_;
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_
diff --git a/net/tools/quic/test_tools/run_all_unittests.cc b/net/tools/quic/test_tools/run_all_unittests.cc
new file mode 100644
index 0000000..6d42d33
--- /dev/null
+++ b/net/tools/quic/test_tools/run_all_unittests.cc
@@ -0,0 +1,11 @@
+// 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 "base/test/test_suite.h"
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+
+ return test_suite.Run();
+}