diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-09-19 22:36:51 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-09-19 22:36:51 +0100 |
commit | d0247b1b59f9c528cb6df88b4f2b9afaf80d181e (patch) | |
tree | 5c397fadc190cc71bffe2ffad1efc27a5b95309d /net | |
parent | f7571f5f07547e2f3e0addf48d1f2a7ec3632957 (diff) | |
download | chromium_org-d0247b1b59f9c528cb6df88b4f2b9afaf80d181e.tar.gz |
Merge from Chromium at DEPS revision 224184
This commit was generated by merge_to_master.py.
Change-Id: Ia3424df5abed9bea642c522b9e2358dceabd8423
Diffstat (limited to 'net')
233 files changed, 8600 insertions, 4945 deletions
diff --git a/net/android/keystore_openssl.cc b/net/android/keystore_openssl.cc index cd55ece333..5ad847344a 100644 --- a/net/android/keystore_openssl.cc +++ b/net/android/keystore_openssl.cc @@ -35,7 +35,7 @@ // // Generally speaking, OpenSSL provides many different ways to sign // digests. This code doesn't support all these cases, only the ones that -// are required to sign the MAC during the OpenSSL handshake for TLS. +// are required to sign the digest during the OpenSSL handshake for TLS. // // The OpenSSL EVP_PKEY type is a generic wrapper around key pairs. // Internally, it can hold a pointer to a RSA, DSA or ECDSA structure, @@ -106,7 +106,6 @@ typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA; typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA; typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY; typedef crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free> ScopedEC_GROUP; -typedef crypto::ScopedOpenSSL<X509_SIG, X509_SIG_free> ScopedX509_SIG; // Custom RSA_METHOD that uses the platform APIs. // Note that for now, only signing through RSA_sign() is really supported. @@ -133,14 +132,60 @@ int RsaMethodPubDec(int flen, return -1; } +// See RSA_eay_private_encrypt in +// third_party/openssl/openssl/crypto/rsa/rsa_eay.c for the default +// implementation of this function. int RsaMethodPrivEnc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { - NOTIMPLEMENTED(); - RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); - return -1; + DCHECK_EQ(RSA_PKCS1_PADDING, padding); + if (padding != RSA_PKCS1_PADDING) { + // TODO(davidben): If we need to, we can implement RSA_NO_PADDING + // by using javax.crypto.Cipher and picking either the + // "RSA/ECB/NoPadding" or "RSA/ECB/PKCS1Padding" transformation as + // appropriate. I believe support for both of these was added in + // the same Android version as the "NONEwithRSA" + // java.security.Signature algorithm, so the same version checks + // for GetRsaLegacyKey should work. + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE); + return -1; + } + + // Retrieve private key JNI reference. + jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa)); + if (!private_key) { + LOG(WARNING) << "Null JNI reference passed to RsaMethodPrivEnc!"; + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR); + return -1; + } + + base::StringPiece from_piece(reinterpret_cast<const char*>(from), flen); + std::vector<uint8> result; + // For RSA keys, this function behaves as RSA_private_encrypt with + // PKCS#1 padding. + if (!RawSignDigestWithPrivateKey(private_key, from_piece, &result)) { + LOG(WARNING) << "Could not sign message in RsaMethodPrivEnc!"; + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR); + return -1; + } + + size_t expected_size = static_cast<size_t>(RSA_size(rsa)); + if (result.size() > expected_size) { + LOG(ERROR) << "RSA Signature size mismatch, actual: " + << result.size() << ", expected <= " << expected_size; + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR); + return -1; + } + + // Copy result to OpenSSL-provided buffer. RawSignDigestWithPrivateKey + // should pad with leading 0s, but if it doesn't, pad the result. + size_t zero_pad = expected_size - result.size(); + memset(to, 0, zero_pad); + memcpy(to + zero_pad, &result[0], result.size()); + + return expected_size; } int RsaMethodPrivDec(int flen, @@ -154,8 +199,6 @@ int RsaMethodPrivDec(int flen, } int RsaMethodInit(RSA* rsa) { - // Required to ensure that RsaMethodSign will be called. - rsa->flags |= RSA_FLAG_SIGN_VER; return 0; } @@ -173,99 +216,6 @@ int RsaMethodFinish(RSA* rsa) { return 0; } -// Although these parameters are, per OpenSSL, named |message| and -// |message_len|, RsaMethodSign is actually passed a message digest, -// not the original message. -int RsaMethodSign(int type, - const unsigned char* message, - unsigned int message_len, - unsigned char* signature, - unsigned int* signature_len, - const RSA* rsa) { - // Retrieve private key JNI reference. - jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa)); - if (!private_key) { - LOG(WARNING) << "Null JNI reference passed to RsaMethodSign!"; - return 0; - } - - // See RSA_sign in third_party/openssl/openssl/crypto/rsa/rsa_sign.c. - base::StringPiece message_piece; - std::vector<uint8> buffer; // To store |message| wrapped in a DigestInfo. - if (type == NID_md5_sha1) { - // For TLS < 1.2, sign just |message|. - message_piece.set(message, static_cast<size_t>(message_len)); - } else { - // For TLS 1.2, wrap |message| in a PKCS #1 DigestInfo before signing. - ScopedX509_SIG sig(X509_SIG_new()); - if (!sig.get()) - return 0; - if (X509_ALGOR_set0(sig.get()->algor, - OBJ_nid2obj(type), V_ASN1_NULL, 0) != 1) { - return 0; - } - if (sig.get()->algor->algorithm == NULL) { - RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE); - return 0; - } - if (sig.get()->algor->algorithm->length == 0) { - RSAerr(RSA_F_RSA_SIGN, - RSA_R_THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD); - return 0; - } - if (ASN1_OCTET_STRING_set(sig.get()->digest, message, message_len) != 1) - return 0; - - int len = i2d_X509_SIG(sig.get(), NULL); - if (len < 0) { - LOG(WARNING) << "Couldn't encode X509_SIG structure"; - return 0; - } - buffer.resize(len); - // OpenSSL takes a pointer to a pointer so it can kindly increment - // it for you. - unsigned char* p = &buffer[0]; - len = i2d_X509_SIG(sig.get(), &p); - if (len < 0) { - LOG(WARNING) << "Couldn't encode X509_SIG structure"; - return 0; - } - - message_piece.set(&buffer[0], static_cast<size_t>(len)); - } - - // Sanity-check the size. - // - // TODO(davidben): Do we need to do this? OpenSSL does, but - // RawSignDigestWithPrivateKey does error on sufficiently large - // input. However, it doesn't take the padding into account. - size_t expected_size = static_cast<size_t>(RSA_size(rsa)); - if (message_piece.size() > expected_size - RSA_PKCS1_PADDING_SIZE) { - RSAerr(RSA_F_RSA_SIGN, RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY); - return 0; - } - - // Sign |message_piece| with the private key through JNI. - std::vector<uint8> result; - - if (!RawSignDigestWithPrivateKey( - private_key, message_piece, &result)) { - LOG(WARNING) << "Could not sign message in RsaMethodSign!"; - return 0; - } - - if (result.size() > expected_size) { - LOG(ERROR) << "RSA Signature size mismatch, actual: " - << result.size() << ", expected <= " << expected_size; - return 0; - } - - // Copy result to OpenSSL-provided buffer - memcpy(signature, &result[0], result.size()); - *signature_len = static_cast<unsigned int>(result.size()); - return 1; -} - const RSA_METHOD android_rsa_method = { /* .name = */ "Android signing-only RSA method", /* .rsa_pub_enc = */ RsaMethodPubEnc, @@ -281,7 +231,7 @@ const RSA_METHOD android_rsa_method = { // it's not valid for the certificate. /* .flags = */ RSA_METHOD_FLAG_NO_CHECK, /* .app_data = */ NULL, - /* .rsa_sign = */ RsaMethodSign, + /* .rsa_sign = */ NULL, /* .rsa_verify = */ NULL, /* .rsa_keygen = */ NULL, }; diff --git a/net/base/address_family.h b/net/base/address_family.h index 75beb29d64..07adfaa2f4 100644 --- a/net/base/address_family.h +++ b/net/base/address_family.h @@ -24,6 +24,8 @@ enum { HOST_RESOLVER_LOOPBACK_ONLY = 1 << 1, // Indicate the address family was set because no IPv6 support was detected. HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6 = 1 << 2, + // The resolver should only invoke getaddrinfo, not DnsClient. + HOST_RESOLVER_SYSTEM_ONLY = 1 << 3 }; typedef int HostResolverFlags; diff --git a/net/base/cache_type.h b/net/base/cache_type.h index 69b5646ece..06aa826b8f 100644 --- a/net/base/cache_type.h +++ b/net/base/cache_type.h @@ -13,7 +13,8 @@ enum CacheType { MEMORY_CACHE, // Data is stored only in memory. MEDIA_CACHE, // Optimized to handle media files. APP_CACHE, // Backing store for an AppCache. - SHADER_CACHE // Backing store for the GL shader cache. + SHADER_CACHE, // Backing store for the GL shader cache. + PNACL_CACHE, // Backing store the PNaCl translation cache }; // The types of disk cache backend, only used at backend instantiation. diff --git a/net/base/file_stream_context.cc b/net/base/file_stream_context.cc index 0d4f8d9b69..2e77475204 100644 --- a/net/base/file_stream_context.cc +++ b/net/base/file_stream_context.cc @@ -174,11 +174,6 @@ void FileStream::Context::RecordError(const IOResult& result, return; } - // The following check is against incorrect use or bug. File descriptor - // shouldn't ever be closed outside of FileStream while it still tries to do - // something with it. - DCHECK_NE(result.result, ERR_INVALID_HANDLE); - if (!orphaned_) { bound_net_log_.AddEvent( NetLog::TYPE_FILE_STREAM_ERROR, diff --git a/net/base/file_stream_unittest.cc b/net/base/file_stream_unittest.cc index addae890b5..c76f3d939a 100644 --- a/net/base/file_stream_unittest.cc +++ b/net/base/file_stream_unittest.cc @@ -1121,42 +1121,56 @@ TEST_F(FileStreamTest, AsyncOpenAndDelete) { // Verify that async Write() errors are mapped correctly. TEST_F(FileStreamTest, AsyncWriteError) { - scoped_ptr<FileStream> stream( - new FileStream(NULL, base::MessageLoopProxy::current())); - int flags = base::PLATFORM_FILE_CREATE_ALWAYS | - base::PLATFORM_FILE_WRITE | + // Try opening file as read-only and then writing to it using FileStream. + base::PlatformFile file = base::CreatePlatformFile( + temp_file_path(), + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_ASYNC, + NULL, + NULL); + ASSERT_NE(base::kInvalidPlatformFileValue, file); + + int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC; - TestCompletionCallback callback; - int rv = stream->Open(temp_file_path(), flags, callback.callback()); - EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(OK, callback.WaitForResult()); + scoped_ptr<FileStream> stream( + new FileStream(file, flags, NULL, base::MessageLoopProxy::current())); - // Try passing NULL buffer to Write() and check that it fails. - scoped_refptr<IOBuffer> buf = new WrappedIOBuffer(NULL); - rv = stream->Write(buf.get(), 1, callback.callback()); + scoped_refptr<IOBuffer> buf = new IOBuffer(1); + buf->data()[0] = 0; + + TestCompletionCallback callback; + int rv = stream->Write(buf.get(), 1, callback.callback()); if (rv == ERR_IO_PENDING) rv = callback.WaitForResult(); EXPECT_LT(rv, 0); + + base::ClosePlatformFile(file); } // Verify that async Read() errors are mapped correctly. TEST_F(FileStreamTest, AsyncReadError) { - scoped_ptr<FileStream> stream( - new FileStream(NULL, base::MessageLoopProxy::current())); - int flags = base::PLATFORM_FILE_OPEN | - base::PLATFORM_FILE_READ | + // Try opening file for write and then reading from it using FileStream. + base::PlatformFile file = base::CreatePlatformFile( + temp_file_path(), + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_ASYNC, + NULL, + NULL); + ASSERT_NE(base::kInvalidPlatformFileValue, file); + + int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | base::PLATFORM_FILE_ASYNC; - TestCompletionCallback callback; - int rv = stream->Open(temp_file_path(), flags, callback.callback()); - EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(OK, callback.WaitForResult()); + scoped_ptr<FileStream> stream( + new FileStream(file, flags, NULL, base::MessageLoopProxy::current())); - // Try passing NULL buffer to Read() and check that it fails. - scoped_refptr<IOBuffer> buf = new WrappedIOBuffer(NULL); - rv = stream->Read(buf.get(), 1, callback.callback()); + scoped_refptr<IOBuffer> buf = new IOBuffer(1); + TestCompletionCallback callback; + int rv = stream->Read(buf.get(), 1, callback.callback()); if (rv == ERR_IO_PENDING) rv = callback.WaitForResult(); EXPECT_LT(rv, 0); + + base::ClosePlatformFile(file); } } // namespace diff --git a/net/base/linked_hash_map.h b/net/base/linked_hash_map.h index d08b68d259..7948647df0 100644 --- a/net/base/linked_hash_map.h +++ b/net/base/linked_hash_map.h @@ -146,7 +146,7 @@ class linked_hash_map { std::pair<typename MapType::iterator, typename MapType::iterator> eq_range = map_.equal_range(key); - return make_pair(eq_range.first->second, eq_range.second->second); + return std::make_pair(eq_range.first->second, eq_range.second->second); } std::pair<const_iterator, const_iterator> equal_range( @@ -159,13 +159,13 @@ class linked_hash_map { const const_iterator& end_iter = eq_range.second != map_.end() ? eq_range.second->second : end(); - return make_pair(start_iter, end_iter); + return std::make_pair(start_iter, end_iter); } // Returns the value mapped to key, or an inserted iterator to that position // in the map. Value& operator[](const key_type& key) { - return (*((this->insert(make_pair(key, Value()))).first)).second; + return (*((this->insert(std::make_pair(key, Value()))).first)).second; } // Inserts an element into the map @@ -174,7 +174,7 @@ class linked_hash_map { // return a pair with an iterator to it, and false indicating that we // didn't insert anything. typename MapType::iterator found = map_.find(pair.first); - if (found != map_.end()) return make_pair(found->second, false); + if (found != map_.end()) return std::make_pair(found->second, false); // Otherwise, insert into the list first. list_.push_back(pair); @@ -184,10 +184,10 @@ class linked_hash_map { typename ListType::iterator last = list_.end(); --last; - CHECK(map_.insert(make_pair(pair.first, last)).second) + CHECK(map_.insert(std::make_pair(pair.first, last)).second) << "Map and list are inconsistent"; - return make_pair(last, true); + return std::make_pair(last, true); } size_type size() const { diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h index 4c4371cc35..837de5f3ef 100644 --- a/net/base/net_log_event_type_list.h +++ b/net/base/net_log_event_type_list.h @@ -1460,6 +1460,12 @@ EVENT_TYPE(QUIC_SESSION_PUBLIC_RESET_PACKET_RECEIVED) // } EVENT_TYPE(QUIC_SESSION_VERSION_NEGOTIATION_PACKET_RECEIVED) +// Session sucessfully negotiated QUIC version number. +// { +// "version": <String of QUIC version negotiated with the server>, +// } +EVENT_TYPE(QUIC_SESSION_VERSION_NEGOTIATED) + // Session revived a QUIC packet packet via FEC. // { // "guid": <The 64-bit GUID for this connection, as a base-10 string>, @@ -1632,14 +1638,8 @@ EVENT_TYPE(NETWORK_CHANGED) // { // "nameservers": <List of name server IPs>, // "search": <List of domain suffixes>, -// "unhandled_options": <See DnsConfig>, -// "append_to_multi_label_name": <See DnsConfig>, -// "ndots": <See DnsConfig>, -// "timeout": <See DnsConfig>, -// "attempts": <See DnsConfig>, -// "rotate": <See DnsConfig>, -// "edns0": <See DnsConfig>, -// "num_hosts": <Number of entries in the HOSTS file> +// "num_hosts": <Number of entries in the HOSTS file>, +// <other>: <See DnsConfig> // } EVENT_TYPE(DNS_CONFIG_CHANGED) diff --git a/net/base/upload_data_stream_unittest.cc b/net/base/upload_data_stream_unittest.cc index 42b71b9cbe..e0cbc27c10 100644 --- a/net/base/upload_data_stream_unittest.cc +++ b/net/base/upload_data_stream_unittest.cc @@ -14,6 +14,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/time/time.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" @@ -123,9 +124,14 @@ class MockUploadElementReader : public UploadElementReader { class UploadDataStreamTest : public PlatformTest { public: - virtual void SetUp() OVERRIDE { + virtual void SetUp() { + PlatformTest::SetUp(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } + virtual ~UploadDataStreamTest() { + element_readers_.clear(); + base::RunLoop().RunUntilIdle(); + } void FileChangedHelper(const base::FilePath& file_path, const base::Time& time, diff --git a/net/base/upload_file_element_reader_unittest.cc b/net/base/upload_file_element_reader_unittest.cc index 8224f77304..b0435019e9 100644 --- a/net/base/upload_file_element_reader_unittest.cc +++ b/net/base/upload_file_element_reader_unittest.cc @@ -7,6 +7,7 @@ #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" @@ -17,7 +18,8 @@ namespace net { class UploadFileElementReaderTest : public PlatformTest { protected: - virtual void SetUp() OVERRIDE { + virtual void SetUp() { + PlatformTest::SetUp(); // Some tests (*.ReadPartially) rely on bytes_.size() being even. const char kData[] = "123456789abcdefghi"; bytes_.assign(kData, kData + arraysize(kData) - 1); @@ -44,6 +46,11 @@ class UploadFileElementReaderTest : public PlatformTest { EXPECT_FALSE(reader_->IsInMemory()); } + virtual ~UploadFileElementReaderTest() { + reader_.reset(); + base::RunLoop().RunUntilIdle(); + } + std::vector<char> bytes_; scoped_ptr<UploadElementReader> reader_; base::ScopedTempDir temp_dir_; diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc index 3988de3c59..05f6c30b8f 100644 --- a/net/cert/cert_verify_proc.cc +++ b/net/cert/cert_verify_proc.cc @@ -374,7 +374,7 @@ bool CertVerifyProc::IsPublicKeyBlacklisted( // in 2036, but we can probably remove in a couple of years (2014). {0xd9, 0xf5, 0xc6, 0xce, 0x57, 0xff, 0xaa, 0x39, 0xcc, 0x7e, 0xd1, 0x72, 0xbd, 0x53, 0xe0, 0xd3, 0x07, 0x83, 0x4b, 0xd1}, - // Win32/Sirefef.gen!C generates fake certifciates with this public key. + // Win32/Sirefef.gen!C generates fake certificates with this public key. {0xa4, 0xf5, 0x6e, 0x9e, 0x1d, 0x9a, 0x3b, 0x7b, 0x1a, 0xc3, 0x31, 0xcf, 0x64, 0xfc, 0x76, 0x2c, 0xd0, 0x51, 0xfb, 0xa4}, }; diff --git a/net/cert_verify_result_android_java.target.darwin-arm.mk b/net/cert_verify_result_android_java.target.darwin-arm.mk index 72a2a06331..8b59c7e5f9 100644 --- a/net/cert_verify_result_android_java.target.darwin-arm.mk +++ b/net/cert_verify_result_android_java.target.darwin-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/cert_verify_result_android_java.target.darwin-mips.mk b/net/cert_verify_result_android_java.target.darwin-mips.mk index 6461ce491f..e6479204b2 100644 --- a/net/cert_verify_result_android_java.target.darwin-mips.mk +++ b/net/cert_verify_result_android_java.target.darwin-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/cert_verify_result_android_java.target.darwin-x86.mk b/net/cert_verify_result_android_java.target.darwin-x86.mk index 53ce61ed0d..f845bd663c 100644 --- a/net/cert_verify_result_android_java.target.darwin-x86.mk +++ b/net/cert_verify_result_android_java.target.darwin-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/cert_verify_result_android_java.target.linux-arm.mk b/net/cert_verify_result_android_java.target.linux-arm.mk index 72a2a06331..8b59c7e5f9 100644 --- a/net/cert_verify_result_android_java.target.linux-arm.mk +++ b/net/cert_verify_result_android_java.target.linux-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/cert_verify_result_android_java.target.linux-mips.mk b/net/cert_verify_result_android_java.target.linux-mips.mk index 6461ce491f..e6479204b2 100644 --- a/net/cert_verify_result_android_java.target.linux-mips.mk +++ b/net/cert_verify_result_android_java.target.linux-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/cert_verify_result_android_java.target.linux-x86.mk b/net/cert_verify_result_android_java.target.linux-x86.mk index 53ce61ed0d..f845bd663c 100644 --- a/net/cert_verify_result_android_java.target.linux-x86.mk +++ b/net/cert_verify_result_android_java.target.linux-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/certificate_mime_types_java.target.darwin-arm.mk b/net/certificate_mime_types_java.target.darwin-arm.mk index 9fd7fc8eed..2ac0e5a403 100644 --- a/net/certificate_mime_types_java.target.darwin-arm.mk +++ b/net/certificate_mime_types_java.target.darwin-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/certificate_mime_types_java.target.darwin-mips.mk b/net/certificate_mime_types_java.target.darwin-mips.mk index dccc1ea2ea..2686623ffc 100644 --- a/net/certificate_mime_types_java.target.darwin-mips.mk +++ b/net/certificate_mime_types_java.target.darwin-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/certificate_mime_types_java.target.darwin-x86.mk b/net/certificate_mime_types_java.target.darwin-x86.mk index d8fb3be860..17ef496271 100644 --- a/net/certificate_mime_types_java.target.darwin-x86.mk +++ b/net/certificate_mime_types_java.target.darwin-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/certificate_mime_types_java.target.linux-arm.mk b/net/certificate_mime_types_java.target.linux-arm.mk index 9fd7fc8eed..2ac0e5a403 100644 --- a/net/certificate_mime_types_java.target.linux-arm.mk +++ b/net/certificate_mime_types_java.target.linux-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/certificate_mime_types_java.target.linux-mips.mk b/net/certificate_mime_types_java.target.linux-mips.mk index dccc1ea2ea..2686623ffc 100644 --- a/net/certificate_mime_types_java.target.linux-mips.mk +++ b/net/certificate_mime_types_java.target.linux-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/certificate_mime_types_java.target.linux-x86.mk b/net/certificate_mime_types_java.target.linux-x86.mk index d8fb3be860..17ef496271 100644 --- a/net/certificate_mime_types_java.target.linux-x86.mk +++ b/net/certificate_mime_types_java.target.linux-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/cookies/cookie_monster_perftest.cc b/net/cookies/cookie_monster_perftest.cc index 8187bcb416..2bc0be8480 100644 --- a/net/cookies/cookie_monster_perftest.cc +++ b/net/cookies/cookie_monster_perftest.cc @@ -8,7 +8,7 @@ #include "base/message_loop/message_loop.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" -#include "base/test/perftimer.h" +#include "base/test/perf_time_logger.h" #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_monster.h" #include "net/cookies/cookie_monster_store_test.h" @@ -97,7 +97,7 @@ class GetCookiesCallback : public BaseCallback { TEST(ParsedCookieTest, TestParseCookies) { std::string cookie(kCookieLine); - PerfTimeLogger timer("Parsed_cookie_parse_cookies"); + base::PerfTimeLogger timer("Parsed_cookie_parse_cookies"); for (int i = 0; i < kNumCookies; ++i) { ParsedCookie pc(cookie); EXPECT_TRUE(pc.IsValid()); @@ -108,7 +108,7 @@ TEST(ParsedCookieTest, TestParseCookies) { TEST(ParsedCookieTest, TestParseBigCookies) { std::string cookie(3800, 'z'); cookie += kCookieLine; - PerfTimeLogger timer("Parsed_cookie_parse_big_cookies"); + base::PerfTimeLogger timer("Parsed_cookie_parse_big_cookies"); for (int i = 0; i < kNumCookies; ++i) { ParsedCookie pc(cookie); EXPECT_TRUE(pc.IsValid()); @@ -126,7 +126,7 @@ TEST_F(CookieMonsterTest, TestAddCookiesOnSingleHost) { SetCookieCallback setCookieCallback; // Add a bunch of cookies on a single host - PerfTimeLogger timer("Cookie_monster_add_single_host"); + base::PerfTimeLogger timer("Cookie_monster_add_single_host"); for (std::vector<std::string>::const_iterator it = cookies.begin(); it != cookies.end(); ++it) { @@ -136,14 +136,14 @@ TEST_F(CookieMonsterTest, TestAddCookiesOnSingleHost) { GetCookiesCallback getCookiesCallback; - PerfTimeLogger timer2("Cookie_monster_query_single_host"); + base::PerfTimeLogger timer2("Cookie_monster_query_single_host"); for (std::vector<std::string>::const_iterator it = cookies.begin(); it != cookies.end(); ++it) { getCookiesCallback.GetCookies(cm.get(), GURL(kGoogleURL)); } timer2.Done(); - PerfTimeLogger timer3("Cookie_monster_deleteall_single_host"); + base::PerfTimeLogger timer3("Cookie_monster_deleteall_single_host"); cm->DeleteAllAsync(CookieMonster::DeleteCallback()); base::MessageLoop::current()->RunUntilIdle(); timer3.Done(); @@ -160,7 +160,7 @@ TEST_F(CookieMonsterTest, TestAddCookieOnManyHosts) { SetCookieCallback setCookieCallback; // Add a cookie on a bunch of host - PerfTimeLogger timer("Cookie_monster_add_many_hosts"); + base::PerfTimeLogger timer("Cookie_monster_add_many_hosts"); for (std::vector<GURL>::const_iterator it = gurls.begin(); it != gurls.end(); ++it) { setCookieCallback.SetCookie(cm.get(), *it, cookie); @@ -169,14 +169,14 @@ TEST_F(CookieMonsterTest, TestAddCookieOnManyHosts) { GetCookiesCallback getCookiesCallback; - PerfTimeLogger timer2("Cookie_monster_query_many_hosts"); + base::PerfTimeLogger timer2("Cookie_monster_query_many_hosts"); for (std::vector<GURL>::const_iterator it = gurls.begin(); it != gurls.end(); ++it) { getCookiesCallback.GetCookies(cm.get(), *it); } timer2.Done(); - PerfTimeLogger timer3("Cookie_monster_deleteall_many_hosts"); + base::PerfTimeLogger timer3("Cookie_monster_deleteall_many_hosts"); cm->DeleteAllAsync(CookieMonster::DeleteCallback()); base::MessageLoop::current()->RunUntilIdle(); timer3.Done(); @@ -229,7 +229,7 @@ TEST_F(CookieMonsterTest, TestDomainTree) { std::string cookie_line = getCookiesCallback.GetCookies(cm.get(), probe_gurl); EXPECT_EQ(5, CountInString(cookie_line, '=')) << "Cookie line: " << cookie_line; - PerfTimeLogger timer("Cookie_monster_query_domain_tree"); + base::PerfTimeLogger timer("Cookie_monster_query_domain_tree"); for (int i = 0; i < kNumCookies; i++) { getCookiesCallback.GetCookies(cm.get(), probe_gurl); } @@ -269,7 +269,7 @@ TEST_F(CookieMonsterTest, TestDomainLine) { cookie_line = getCookiesCallback.GetCookies(cm.get(), probe_gurl); EXPECT_EQ(32, CountInString(cookie_line, '=')); - PerfTimeLogger timer2("Cookie_monster_query_domain_line"); + base::PerfTimeLogger timer2("Cookie_monster_query_domain_line"); for (int i = 0; i < kNumCookies; i++) { getCookiesCallback.GetCookies(cm.get(), probe_gurl); } @@ -304,7 +304,7 @@ TEST_F(CookieMonsterTest, TestImport) { // Import will happen on first access. GURL gurl("www.google.com"); CookieOptions options; - PerfTimeLogger timer("Cookie_monster_import_from_store"); + base::PerfTimeLogger timer("Cookie_monster_import_from_store"); getCookiesCallback.GetCookies(cm.get(), gurl); timer.Done(); @@ -314,7 +314,7 @@ TEST_F(CookieMonsterTest, TestImport) { TEST_F(CookieMonsterTest, TestGetKey) { scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL)); - PerfTimeLogger timer("Cookie_monster_get_key"); + base::PerfTimeLogger timer("Cookie_monster_get_key"); for (int i = 0; i < kNumCookies; i++) cm->GetKey("www.google.com"); timer.Done(); @@ -375,7 +375,7 @@ TEST_F(CookieMonsterTest, TestGCTimes) { // Trigger the Garbage collection we're allowed. setCookieCallback.SetCookie(cm.get(), gurl, cookie_line); - PerfTimeLogger timer((std::string("GC_") + test_case.name).c_str()); + base::PerfTimeLogger timer((std::string("GC_") + test_case.name).c_str()); for (int i = 0; i < kNumCookies; i++) setCookieCallback.SetCookie(cm.get(), gurl, cookie_line); timer.Done(); diff --git a/net/data/url_request_unittest/redirect-test.html.mock-http-headers b/net/data/url_request_unittest/redirect-test.html.mock-http-headers index 9fdd1c0b7b..c59ef583ab 100644 --- a/net/data/url_request_unittest/redirect-test.html.mock-http-headers +++ b/net/data/url_request_unittest/redirect-test.html.mock-http-headers @@ -1,2 +1,3 @@ HTTP/1.1 302 Redirect Location: with-headers.html +Cache-Control: max-age=10000 diff --git a/net/disk_cache/backend_impl.cc b/net/disk_cache/backend_impl.cc index cb2832f1b1..0f8c3fdd19 100644 --- a/net/disk_cache/backend_impl.cc +++ b/net/disk_cache/backend_impl.cc @@ -867,7 +867,7 @@ int32 BackendImpl::GetCurrentEntryId() const { } int BackendImpl::MaxFileSize() const { - return max_size_ / 8; + return cache_type() == net::PNACL_CACHE ? max_size_ : max_size_ / 8; } void BackendImpl::ModifyStorageSize(int32 old_size, int32 new_size) { diff --git a/net/disk_cache/backend_unittest.cc b/net/disk_cache/backend_unittest.cc index 5b7084e7f4..999cc7fffb 100644 --- a/net/disk_cache/backend_unittest.cc +++ b/net/disk_cache/backend_unittest.cc @@ -132,10 +132,8 @@ int DiskCacheBackendTest::GeneratePendingIO(net::TestCompletionCallback* cb) { return net::ERR_FAILED; } - disk_cache::EntryImpl* entry; - int rv = cache_->CreateEntry("some key", - reinterpret_cast<disk_cache::Entry**>(&entry), - cb->callback()); + disk_cache::Entry* entry; + int rv = cache_->CreateEntry("some key", &entry, cb->callback()); if (cb->GetResult(rv) != net::OK) return net::ERR_CACHE_CREATE_FAILURE; @@ -147,7 +145,13 @@ int DiskCacheBackendTest::GeneratePendingIO(net::TestCompletionCallback* cb) { // We are using the current thread as the cache thread because we want to // be able to call directly this method to make sure that the OS (instead // of us switching thread) is returning IO pending. - rv = entry->WriteDataImpl(0, i, buffer.get(), kSize, cb->callback(), false); + if (!simple_cache_mode_) { + rv = static_cast<disk_cache::EntryImpl*>(entry)->WriteDataImpl( + 0, i, buffer.get(), kSize, cb->callback(), false); + } else { + rv = entry->WriteData(0, i, buffer.get(), kSize, cb->callback(), false); + } + if (rv == net::ERR_IO_PENDING) break; if (rv != kSize) @@ -156,7 +160,11 @@ int DiskCacheBackendTest::GeneratePendingIO(net::TestCompletionCallback* cb) { // Don't call Close() to avoid going through the queue or we'll deadlock // waiting for the operation to finish. - entry->Release(); + if (!simple_cache_mode_) + static_cast<disk_cache::EntryImpl*>(entry)->Release(); + else + entry->Close(); + return rv; } @@ -502,7 +510,7 @@ void DiskCacheBackendTest::BackendShutdownWithPendingFileIO(bool fast) { cache_.reset(); if (rv == net::ERR_IO_PENDING) { - if (fast) + if (fast || simple_cache_mode_) EXPECT_FALSE(cb.have_result()); else EXPECT_TRUE(cb.have_result()); @@ -3128,9 +3136,22 @@ TEST_F(DiskCacheBackendTest, TracingBackendBasics) { TracingBackendBasics(); } -// The simple cache backend isn't intended to work on windows, which has very -// different file system guarantees from Windows. -#if !defined(OS_WIN) +// The Simple Cache backend requires a few guarantees from the filesystem like +// atomic renaming of recently open files. Those guarantees are not provided in +// general on Windows. +#if defined(OS_POSIX) + +TEST_F(DiskCacheBackendTest, SimpleCacheShutdownWithPendingCreate) { + SetCacheType(net::APP_CACHE); + SetSimpleCacheMode(); + BackendShutdownWithPendingCreate(false); +} + +TEST_F(DiskCacheBackendTest, SimpleCacheShutdownWithPendingFileIO) { + SetCacheType(net::APP_CACHE); + SetSimpleCacheMode(); + BackendShutdownWithPendingFileIO(false); +} TEST_F(DiskCacheBackendTest, SimpleCacheBasics) { SetSimpleCacheMode(); @@ -3190,10 +3211,7 @@ TEST_F(DiskCacheBackendTest, SimpleDoomBetween) { BackendDoomBetween(); } -// See http://crbug.com/237450. -// TODO(gavinp): Consider enabling this test again when -// https://codereview.chromium.org/23823002/ lands. -TEST_F(DiskCacheBackendTest, DISABLED_SimpleCacheDoomAll) { +TEST_F(DiskCacheBackendTest, SimpleCacheDoomAll) { SetSimpleCacheMode(); BackendDoomAll(); } @@ -3232,7 +3250,7 @@ TEST_F(DiskCacheBackendTest, SimpleCacheOpenMissingFile) { // Delete one of the files in the entry. base::FilePath to_delete_file = cache_path_.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0)); + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0)); EXPECT_TRUE(base::PathExists(to_delete_file)); EXPECT_TRUE(disk_cache::DeleteCacheFile(to_delete_file)); @@ -3241,9 +3259,8 @@ TEST_F(DiskCacheBackendTest, SimpleCacheOpenMissingFile) { // Confirm the rest of the files are gone. for (int i = 1; i < disk_cache::kSimpleEntryFileCount; ++i) { - base::FilePath - should_be_gone_file(cache_path_.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, i))); + base::FilePath should_be_gone_file(cache_path_.AppendASCII( + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, i))); EXPECT_FALSE(base::PathExists(should_be_gone_file)); } } @@ -3268,9 +3285,9 @@ TEST_F(DiskCacheBackendTest, SimpleCacheOpenBadFile) { entry->Close(); entry = NULL; - // Write an invalid header on stream 1. + // Write an invalid header for stream 0 and stream 1. base::FilePath entry_file1_path = cache_path_.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 1)); + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0)); disk_cache::SimpleFileHeader header; header.initial_magic_number = GG_UINT64_C(0xbadf00d); @@ -3453,4 +3470,8 @@ TEST_F(DiskCacheBackendTest, SimpleCacheEnumerationCorruption) { EXPECT_TRUE(keys_to_match.empty()); } -#endif // !defined(OS_WIN) +// TODO(pasko): Add a Simple Cache test that would simulate upgrade from the +// version with the index file in the cache directory to the version with the +// index file in subdirectory. + +#endif // defined(OS_POSIX) diff --git a/net/disk_cache/block_files.cc b/net/disk_cache/block_files.cc index 9246ef1343..896cdb1632 100644 --- a/net/disk_cache/block_files.cc +++ b/net/disk_cache/block_files.cc @@ -52,9 +52,17 @@ BlockHeader::BlockHeader(const BlockHeader& other) : header_(other.header_) { BlockHeader::~BlockHeader() { } -bool BlockHeader::CreateMapBlock(int target, int size, int* index) { - if (target <= 0 || target > kMaxNumBlocks || - size <= 0 || size > kMaxNumBlocks) { +bool BlockHeader::CreateMapBlock(int size, int* index) { + DCHECK(size > 0 && size <= kMaxNumBlocks); + int target = 0; + for (int i = size; i <= kMaxNumBlocks; i++) { + if (header_->empty[i - 1]) { + target = i; + break; + } + } + + if (!target) { NOTREACHED(); return false; } @@ -144,10 +152,9 @@ void BlockHeader::DeleteMapBlock(int index, int size) { // Note that this is a simplified version of DeleteMapBlock(). bool BlockHeader::UsedMapBlock(int index, int size) { - if (size < 0 || size > kMaxNumBlocks) { - NOTREACHED(); + if (size < 0 || size > kMaxNumBlocks) return false; - } + int byte_index = index / 8; uint8* byte_map = reinterpret_cast<uint8*>(header_->allocation_map); uint8 map_block = byte_map[byte_index]; @@ -177,7 +184,7 @@ void BlockHeader::FixAllocationCounters() { } } -bool BlockHeader::NeedToGrowBlockFile(int block_count) { +bool BlockHeader::NeedToGrowBlockFile(int block_count) const { bool have_space = false; int empty_blocks = 0; for (int i = 0; i < kMaxNumBlocks; i++) { @@ -195,9 +202,19 @@ bool BlockHeader::NeedToGrowBlockFile(int block_count) { return !have_space; } +bool BlockHeader::CanAllocate(int block_count) const { + DCHECK_GT(block_count, 0); + for (int i = block_count - 1; i < kMaxNumBlocks; i++) { + if (header_->empty[i]) + return true; + } + + return false; +} + int BlockHeader::EmptyBlocks() const { int empty_blocks = 0; - for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) { + for (int i = 0; i < kMaxNumBlocks; i++) { empty_blocks += header_->empty[i] * (i + 1); if (header_->empty[i] < 0) return 0; @@ -205,6 +222,14 @@ int BlockHeader::EmptyBlocks() const { return empty_blocks; } +int BlockHeader::MinimumAllocations() const { + return header_->empty[kMaxNumBlocks - 1]; +} + +int BlockHeader::Capacity() const { + return header_->max_entries; +} + bool BlockHeader::ValidateCounters() const { if (header_->max_entries < 0 || header_->max_entries > kMaxBlocks || header_->num_entries < 0) @@ -217,10 +242,22 @@ bool BlockHeader::ValidateCounters() const { return true; } +int BlockHeader::FileId() const { + return header_->this_file; +} + +int BlockHeader::NextFileId() const { + return header_->next_file; +} + int BlockHeader::Size() const { return static_cast<int>(sizeof(*header_)); } +BlockFileHeader* BlockHeader::Header() { + return header_; +} + // ------------------------------------------------------------------------ BlockFiles::BlockFiles(const base::FilePath& path) @@ -260,7 +297,8 @@ bool BlockFiles::Init(bool create_files) { MappedFile* BlockFiles::GetFile(Addr address) { DCHECK(thread_checker_->CalledOnValidThread()); - DCHECK(block_files_.size() >= 4); + DCHECK_GE(block_files_.size(), + static_cast<size_t>(kFirstAdditionalBlockFile)); DCHECK(address.is_block_file() || !address.is_initialized()); if (!address.is_initialized()) return NULL; @@ -272,16 +310,20 @@ MappedFile* BlockFiles::GetFile(Addr address) { if (!OpenBlockFile(file_index)) return NULL; } - DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index)); + DCHECK_GE(block_files_.size(), static_cast<unsigned int>(file_index)); return block_files_[file_index]; } bool BlockFiles::CreateBlock(FileType block_type, int block_count, Addr* block_address) { DCHECK(thread_checker_->CalledOnValidThread()); - if (block_type < RANKINGS || block_type > BLOCK_4K || - block_count < 1 || block_count > 4) + DCHECK_NE(block_type, EXTERNAL); + DCHECK_NE(block_type, BLOCK_FILES); + DCHECK_NE(block_type, BLOCK_ENTRIES); + DCHECK_NE(block_type, BLOCK_EVICTED); + if (block_count < 1 || block_count > kMaxNumBlocks) return false; + if (!init_) return false; @@ -290,22 +332,13 @@ bool BlockFiles::CreateBlock(FileType block_type, int block_count, return false; ScopedFlush flush(file); - BlockHeader header(file); + BlockHeader file_header(file); - int target_size = 0; - for (int i = block_count; i <= 4; i++) { - if (header->empty[i - 1]) { - target_size = i; - break; - } - } - - DCHECK(target_size); int index; - if (!header.CreateMapBlock(target_size, block_count, &index)) + if (!file_header.CreateMapBlock(block_count, &index)) return false; - Addr address(block_type, block_count, header->this_file, index); + Addr address(block_type, block_count, file_header.FileId(), index); block_address->set_value(address.value()); Trace("CreateBlock 0x%x", address.value()); return true; @@ -332,15 +365,17 @@ void BlockFiles::DeleteBlock(Addr address, bool deep) { if (deep) file->Write(zero_buffer_, size, offset); - BlockHeader header(file); - header.DeleteMapBlock(address.start_block(), address.num_blocks()); + BlockHeader file_header(file); + file_header.DeleteMapBlock(address.start_block(), address.num_blocks()); file->Flush(); - if (!header->num_entries) { + if (!file_header.Header()->num_entries) { // This file is now empty. Let's try to delete it. - FileType type = Addr::RequiredFileType(header->entry_size); - if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size) + FileType type = Addr::RequiredFileType(file_header.Header()->entry_size); + if (Addr::BlockSizeForFileType(RANKINGS) == + file_header.Header()->entry_size) { type = RANKINGS; + } RemoveEmptyFile(type); // Ignore failures. } } @@ -450,13 +485,14 @@ bool BlockFiles::OpenBlockFile(int index) { return false; } - BlockHeader header(file.get()); + BlockHeader file_header(file.get()); + BlockFileHeader* header = file_header.Header(); if (kBlockMagic != header->magic || kBlockVersion2 != header->version) { LOG(ERROR) << "Invalid file version or magic " << name.value(); return false; } - if (header->updating || !header.ValidateCounters()) { + if (header->updating || !file_header.ValidateCounters()) { // Last instance was not properly shutdown, or counters are out of sync. if (!FixBlockFileHeader(file.get())) { LOG(ERROR) << "Unable to fix block file " << name.value(); @@ -516,19 +552,19 @@ bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) { MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) { COMPILE_ASSERT(RANKINGS == 1, invalid_file_type); MappedFile* file = block_files_[block_type - 1]; - BlockHeader header(file); + BlockHeader file_header(file); TimeTicks start = TimeTicks::Now(); - while (header.NeedToGrowBlockFile(block_count)) { - if (kMaxBlocks == header->max_entries) { + while (file_header.NeedToGrowBlockFile(block_count)) { + if (kMaxBlocks == file_header.Header()->max_entries) { file = NextFile(file); if (!file) return NULL; - header = BlockHeader(file); + file_header = BlockHeader(file); continue; } - if (!GrowBlockFile(file, header.Get())) + if (!GrowBlockFile(file, file_header.Header())) return NULL; break; } @@ -616,38 +652,39 @@ bool BlockFiles::RemoveEmptyFile(FileType block_type) { // DCHECK on header->updating because we may be fixing a crash. bool BlockFiles::FixBlockFileHeader(MappedFile* file) { ScopedFlush flush(file); - BlockHeader header(file); + BlockHeader file_header(file); int file_size = static_cast<int>(file->GetLength()); - if (file_size < header.Size()) + if (file_size < file_header.Size()) return false; // file_size > 2GB is also an error. const int kMinBlockSize = 36; const int kMaxBlockSize = 4096; + BlockFileHeader* header = file_header.Header(); if (header->entry_size < kMinBlockSize || header->entry_size > kMaxBlockSize || header->num_entries < 0) return false; // Make sure that we survive crashes. header->updating = 1; - int expected = header->entry_size * header->max_entries + header.Size(); + int expected = header->entry_size * header->max_entries + file_header.Size(); if (file_size != expected) { - int max_expected = header->entry_size * kMaxBlocks + header.Size(); + int max_expected = header->entry_size * kMaxBlocks + file_header.Size(); if (file_size < expected || header->empty[3] || file_size > max_expected) { NOTREACHED(); LOG(ERROR) << "Unexpected file size"; return false; } // We were in the middle of growing the file. - int num_entries = (file_size - header.Size()) / header->entry_size; + int num_entries = (file_size - file_header.Size()) / header->entry_size; header->max_entries = num_entries; } - header.FixAllocationCounters(); - int empty_blocks = header.EmptyBlocks(); + file_header.FixAllocationCounters(); + int empty_blocks = file_header.EmptyBlocks(); if (empty_blocks + header->num_entries > header->max_entries) header->num_entries = header->max_entries - empty_blocks; - if (!header.ValidateCounters()) + if (!file_header.ValidateCounters()) return false; header->updating = 0; @@ -671,7 +708,7 @@ void BlockFiles::GetFileStats(int index, int* used_count, int* load) { max_blocks += header->max_entries; int used = header->max_entries; - for (int i = 0; i < 4; i++) { + for (int i = 0; i < kMaxNumBlocks; i++) { used -= header->empty[i] * (i + 1); DCHECK_GE(used, 0); } diff --git a/net/disk_cache/block_files.h b/net/disk_cache/block_files.h index 353c5663df..f8d5483a0b 100644 --- a/net/disk_cache/block_files.h +++ b/net/disk_cache/block_files.h @@ -24,7 +24,11 @@ class ThreadChecker; namespace disk_cache { // An instance of this class represents the header of a block file in memory. -// Note that this class doesn't perform any file operation. +// Note that this class doesn't perform any file operation (as in it only deals +// with entities in memory). +// The header of a block file (and hence, this object) is all that is needed to +// perform common operations like allocating or releasing space for storage; +// actual access to that storage, however, is not performed through this class. class NET_EXPORT_PRIVATE BlockHeader { public: BlockHeader(); @@ -33,10 +37,9 @@ class NET_EXPORT_PRIVATE BlockHeader { BlockHeader(const BlockHeader& other); ~BlockHeader(); - // Creates a new entry on the allocation map, updating the apropriate - // counters. |target| is the type of block to use (number of empty blocks), - // and |size| is the actual number of blocks to use. - bool CreateMapBlock(int target, int size, int* index); + // Creates a new entry of |size| blocks on the allocation map, updating the + // apropriate counters. + bool CreateMapBlock(int size, int* index); // Deletes the block pointed by |index|. void DeleteMapBlock(int index, int block_size); @@ -49,20 +52,34 @@ class NET_EXPORT_PRIVATE BlockHeader { // Returns true if the current block file should not be used as-is to store // more records. |block_count| is the number of blocks to allocate. - bool NeedToGrowBlockFile(int block_count); + bool NeedToGrowBlockFile(int block_count) const; + + // Returns true if this block file can be used to store an extra record of + // size |block_count|. + bool CanAllocate(int block_count) const; // Returns the number of empty blocks for this file. int EmptyBlocks() const; + // Returns the minumum number of allocations that can be satisfied. + int MinimumAllocations() const; + + // Returns the number of blocks that this file can store. + int Capacity() const; + // Returns true if the counters look OK. bool ValidateCounters() const; + // Returns the identifiers of this and the next file (0 if there is none). + int FileId() const; + int NextFileId() const; + // Returns the size of the wrapped structure (BlockFileHeader). int Size() const; - BlockFileHeader* operator->() { return header_; } - void operator=(const BlockHeader& other) { header_ = other.header_; } - BlockFileHeader* Get() { return header_; } + // Returns a pointer to the underlying BlockFileHeader. + // TODO(rvargas): This may be removed with the support for V2. + BlockFileHeader* Header(); private: BlockFileHeader* header_; diff --git a/net/disk_cache/disk_cache_perftest.cc b/net/disk_cache/disk_cache_perftest.cc index abf39b9b60..6adc4bca70 100644 --- a/net/disk_cache/disk_cache_perftest.cc +++ b/net/disk_cache/disk_cache_perftest.cc @@ -9,7 +9,7 @@ #include "base/bind_helpers.h" #include "base/hash.h" #include "base/strings/string_util.h" -#include "base/test/perftimer.h" +#include "base/test/perf_time_logger.h" #include "base/test/test_file_util.h" #include "base/threading/thread.h" #include "base/timer/timer.h" @@ -53,7 +53,7 @@ bool TimeWrite(int num_entries, disk_cache::Backend* cache, MessageLoopHelper helper; CallbackTest callback(&helper, true); - PerfTimeLogger timer("Write disk cache entries"); + base::PerfTimeLogger timer("Write disk cache entries"); for (int i = 0; i < num_entries; i++) { TestEntry entry; @@ -107,7 +107,7 @@ bool TimeRead(int num_entries, disk_cache::Backend* cache, const char* message = cold ? "Read disk cache entries (cold)" : "Read disk cache entries (warm)"; - PerfTimeLogger timer(message); + base::PerfTimeLogger timer(message); for (int i = 0; i < num_entries; i++) { disk_cache::Entry* cache_entry; @@ -150,7 +150,7 @@ TEST_F(DiskCacheTest, Hash) { int seed = static_cast<int>(Time::Now().ToInternalValue()); srand(seed); - PerfTimeLogger timer("Hash disk cache keys"); + base::PerfTimeLogger timer("Hash disk cache keys"); for (int i = 0; i < 300000; i++) { std::string key = GenerateKey(true); base::Hash(key); @@ -223,7 +223,7 @@ TEST_F(DiskCacheTest, BlockFilesPerformance) { const int kNumEntries = 60000; disk_cache::Addr* address = new disk_cache::Addr[kNumEntries]; - PerfTimeLogger timer1("Fill three block-files"); + base::PerfTimeLogger timer1("Fill three block-files"); // Fill up the 32-byte block file (use three files). for (int i = 0; i < kNumEntries; i++) { @@ -232,7 +232,7 @@ TEST_F(DiskCacheTest, BlockFilesPerformance) { } timer1.Done(); - PerfTimeLogger timer2("Create and delete blocks"); + base::PerfTimeLogger timer2("Create and delete blocks"); for (int i = 0; i < 200000; i++) { int entry = rand() * (kNumEntries / RAND_MAX + 1); diff --git a/net/disk_cache/disk_cache_test_base.cc b/net/disk_cache/disk_cache_test_base.cc index 34913c773e..3cf98a86b1 100644 --- a/net/disk_cache/disk_cache_test_base.cc +++ b/net/disk_cache/disk_cache_test_base.cc @@ -250,8 +250,9 @@ void DiskCacheTestWithCache::TearDown() { if (!memory_only_ && !simple_cache_mode_ && integrity_) { EXPECT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask_)); } - - PlatformTest::TearDown(); + base::RunLoop().RunUntilIdle(); + disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting(); + DiskCacheTest::TearDown(); } void DiskCacheTestWithCache::InitMemoryCache() { diff --git a/net/disk_cache/disk_format_base.h b/net/disk_cache/disk_format_base.h index c8b7490abf..31983817fc 100644 --- a/net/disk_cache/disk_format_base.h +++ b/net/disk_cache/disk_format_base.h @@ -28,6 +28,7 @@ namespace disk_cache { typedef uint32 CacheAddr; const uint32 kBlockVersion2 = 0x20000; // Version 2.0. +const uint32 kBlockCurrentVersion = 0x30000; // Version 3.0. const uint32 kBlockMagic = 0xC104CAC3; const int kBlockHeaderSize = 8192; // Two pages: almost 64k entries diff --git a/net/disk_cache/entry_unittest.cc b/net/disk_cache/entry_unittest.cc index 56371437da..705e6b2d55 100644 --- a/net/disk_cache/entry_unittest.cc +++ b/net/disk_cache/entry_unittest.cc @@ -21,6 +21,7 @@ #include "net/disk_cache/mem_entry_impl.h" #include "net/disk_cache/simple/simple_entry_format.h" #include "net/disk_cache/simple/simple_entry_impl.h" +#include "net/disk_cache/simple/simple_synchronous_entry.h" #include "net/disk_cache/simple/simple_test_util.h" #include "net/disk_cache/simple/simple_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -62,7 +63,7 @@ class DiskCacheEntryTest : public DiskCacheTestWithCache { void UpdateSparseEntry(); void DoomSparseEntry(); void PartialSparseEntry(); - bool SimpleCacheMakeBadChecksumEntry(const char* key, int* data_size); + bool SimpleCacheMakeBadChecksumEntry(const std::string& key, int* data_size); }; // This part of the test runs on the background thread. @@ -2297,8 +2298,9 @@ TEST_F(DiskCacheEntryTest, KeySanityCheck) { DisableIntegrityCheck(); } -// The simple cache backend isn't intended to work on Windows, which has very -// different file system guarantees from Linux. +// The Simple Cache backend requires a few guarantees from the filesystem like +// atomic renaming of recently open files. Those guarantees are not provided in +// general on Windows. #if defined(OS_POSIX) TEST_F(DiskCacheEntryTest, SimpleCacheInternalAsyncIO) { @@ -2413,7 +2415,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheDoomedEntry) { // Creates an entry with corrupted last byte in stream 0. // Requires SimpleCacheMode. -bool DiskCacheEntryTest::SimpleCacheMakeBadChecksumEntry(const char* key, +bool DiskCacheEntryTest::SimpleCacheMakeBadChecksumEntry(const std::string& key, int* data_size) { disk_cache::Entry* entry = NULL; @@ -2427,21 +2429,21 @@ bool DiskCacheEntryTest::SimpleCacheMakeBadChecksumEntry(const char* key, scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize)); base::strlcpy(buffer->data(), data, kDataSize); - EXPECT_EQ(kDataSize, WriteData(entry, 0, 0, buffer.get(), kDataSize, false)); + EXPECT_EQ(kDataSize, WriteData(entry, 1, 0, buffer.get(), kDataSize, false)); entry->Close(); entry = NULL; // Corrupt the last byte of the data. base::FilePath entry_file0_path = cache_path_.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0)); + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0)); int flags = base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_OPEN; base::PlatformFile entry_file0 = base::CreatePlatformFile(entry_file0_path, flags, NULL, NULL); if (entry_file0 == base::kInvalidPlatformFileValue) return false; + int64 file_offset = - disk_cache::simple_util::GetFileOffsetFromKeyAndDataOffset( - key, kDataSize - 2); + sizeof(disk_cache::SimpleFileHeader) + key.size() + kDataSize - 2; EXPECT_EQ(1, base::WritePlatformFile(entry_file0, file_offset, "X", 1)); if (!base::ClosePlatformFile(entry_file0)) return false; @@ -2465,10 +2467,10 @@ TEST_F(DiskCacheEntryTest, SimpleCacheBadChecksum) { ScopedEntryPtr entry_closer(entry); const int kReadBufferSize = 200; - EXPECT_GE(kReadBufferSize, entry->GetDataSize(0)); + EXPECT_GE(kReadBufferSize, entry->GetDataSize(1)); scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kReadBufferSize)); EXPECT_EQ(net::ERR_CACHE_CHECKSUM_MISMATCH, - ReadData(entry, 0, 0, read_buffer.get(), kReadBufferSize)); + ReadData(entry, 1, 0, read_buffer.get(), kReadBufferSize)); } // Tests that an entry that has had an IO error occur can still be Doomed(). @@ -2487,10 +2489,10 @@ TEST_F(DiskCacheEntryTest, SimpleCacheErrorThenDoom) { ScopedEntryPtr entry_closer(entry); const int kReadBufferSize = 200; - EXPECT_GE(kReadBufferSize, entry->GetDataSize(0)); + EXPECT_GE(kReadBufferSize, entry->GetDataSize(1)); scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kReadBufferSize)); EXPECT_EQ(net::ERR_CACHE_CHECKSUM_MISMATCH, - ReadData(entry, 0, 0, read_buffer.get(), kReadBufferSize)); + ReadData(entry, 1, 0, read_buffer.get(), kReadBufferSize)); entry->Doom(); // Should not crash. } @@ -2529,7 +2531,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheNoEOF) { // record. int kTruncationBytes = -implicit_cast<int>(sizeof(disk_cache::SimpleFileEOF)); const base::FilePath entry_path = cache_path_.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0)); + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0)); const int64 invalid_size = disk_cache::simple_util::GetFileSizeFromKeyAndDataSize(key, kTruncationBytes); @@ -2557,13 +2559,12 @@ TEST_F(DiskCacheEntryTest, SimpleCacheNonOptimisticOperationsBasic) { CacheTestFillBuffer(write_buffer->data(), write_buffer->size(), false); EXPECT_EQ( write_buffer->size(), - WriteData(entry, 0, 0, write_buffer.get(), write_buffer->size(), false)); + WriteData(entry, 1, 0, write_buffer.get(), write_buffer->size(), false)); scoped_refptr<net::IOBufferWithSize> read_buffer( new net::IOBufferWithSize(kBufferSize)); - EXPECT_EQ( - read_buffer->size(), - ReadData(entry, 0, 0, read_buffer.get(), read_buffer->size())); + EXPECT_EQ(read_buffer->size(), + ReadData(entry, 1, 0, read_buffer.get(), read_buffer->size())); } TEST_F(DiskCacheEntryTest, SimpleCacheNonOptimisticOperationsDontBlock) { @@ -2590,7 +2591,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheNonOptimisticOperationsDontBlock) { CacheTestFillBuffer(write_buffer->data(), write_buffer->size(), false); CallbackTest write_callback(&helper, false); int ret = entry->WriteData( - 0, + 1, 0, write_buffer.get(), write_buffer->size(), @@ -2623,7 +2624,7 @@ TEST_F(DiskCacheEntryTest, CacheTestFillBuffer(write_buffer->data(), write_buffer->size(), false); CallbackTest write_callback(&helper, false); int ret = entry->WriteData( - 0, + 1, 0, write_buffer.get(), write_buffer->size(), @@ -2636,7 +2637,7 @@ TEST_F(DiskCacheEntryTest, new net::IOBufferWithSize(kBufferSize)); CallbackTest read_callback(&helper, false); ret = entry->ReadData( - 0, + 1, 0, read_buffer.get(), read_buffer->size(), @@ -2688,7 +2689,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic) { // This write may or may not be optimistic (it depends if the previous // optimistic create already finished by the time we call the write here). int ret = entry->WriteData( - 0, + 1, 0, buffer1.get(), kSize1, @@ -2701,7 +2702,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic) { // This Read must not be optimistic, since we don't support that yet. EXPECT_EQ(net::ERR_IO_PENDING, entry->ReadData( - 0, + 1, 0, buffer1_read.get(), kSize1, @@ -2714,7 +2715,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic) { // should be empty, so the next Write operation must run as optimistic. EXPECT_EQ(kSize2, entry->WriteData( - 0, + 1, 0, buffer2.get(), kSize2, @@ -2725,7 +2726,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic) { // operation finishes and we can then test for HasOneRef() below. EXPECT_EQ(net::ERR_IO_PENDING, entry->ReadData( - 0, + 1, 0, buffer2_read.get(), kSize2, @@ -2831,7 +2832,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic4) { // operation finishes. Write must fail since we are writing in a closed entry. EXPECT_EQ( net::ERR_IO_PENDING, - entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false)); + entry->WriteData(1, 0, buffer1.get(), kSize1, cb.callback(), false)); EXPECT_EQ(net::ERR_FAILED, cb.GetResult(net::ERR_IO_PENDING)); // Finish running the pending tasks so that we fully complete the close @@ -2860,12 +2861,12 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic4) { // entry. EXPECT_EQ(kSize1, entry2->WriteData( - 0, 0, buffer1.get(), kSize1, net::CompletionCallback(), false)); + 1, 0, buffer1.get(), kSize1, net::CompletionCallback(), false)); // Lets do another read so we block until both the write and the read // operation finishes and we can then test for HasOneRef() below. EXPECT_EQ(net::ERR_IO_PENDING, - entry2->ReadData(0, 0, buffer1.get(), kSize1, cb.callback())); + entry2->ReadData(1, 0, buffer1.get(), kSize1, cb.callback())); EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING)); // Check that we are not leaking. @@ -2896,11 +2897,11 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic5) { EXPECT_EQ( net::ERR_IO_PENDING, - entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false)); + entry->WriteData(1, 0, buffer1.get(), kSize1, cb.callback(), false)); EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING)); EXPECT_EQ(net::ERR_IO_PENDING, - entry->ReadData(0, 0, buffer1.get(), kSize1, cb.callback())); + entry->ReadData(1, 0, buffer1.get(), kSize1, cb.callback())); EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING)); // Check that we are not leaking. @@ -2908,9 +2909,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic5) { static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef()); } -// TODO(gavinp): Fix this, perhaps by landing -// https://codereview.chromium.org/23823002/ -TEST_F(DiskCacheEntryTest, DISABLED_SimpleCacheOptimistic6) { +TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic6) { // Test sequence: // Create, Write, Doom, Doom, Read, Doom, Close. SetSimpleCacheMode(); @@ -2932,7 +2931,7 @@ TEST_F(DiskCacheEntryTest, DISABLED_SimpleCacheOptimistic6) { EXPECT_EQ( net::ERR_IO_PENDING, - entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false)); + entry->WriteData(1, 0, buffer1.get(), kSize1, cb.callback(), false)); EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING)); entry->Doom(); @@ -2940,7 +2939,7 @@ TEST_F(DiskCacheEntryTest, DISABLED_SimpleCacheOptimistic6) { // This Read must not be optimistic, since we don't support that yet. EXPECT_EQ(net::ERR_IO_PENDING, - entry->ReadData(0, 0, buffer1_read.get(), kSize1, cb.callback())); + entry->ReadData(1, 0, buffer1_read.get(), kSize1, cb.callback())); EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING)); EXPECT_EQ(0, memcmp(buffer1->data(), buffer1_read->data(), kSize1)); @@ -2971,7 +2970,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheOptimisticWriteReleases) { // operations. To ensure the queue is empty, we issue a write and wait until // it completes. EXPECT_EQ(kWriteSize, - WriteData(entry, 0, 0, buffer1.get(), kWriteSize, false)); + WriteData(entry, 1, 0, buffer1.get(), kWriteSize, false)); EXPECT_TRUE(buffer1->HasOneRef()); // Finally, we should perform an optimistic write and confirm that all @@ -3016,7 +3015,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheCreateDoomRace) { for (int i = 0; i < disk_cache::kSimpleEntryFileCount; ++i) { base::FilePath entry_file_path = cache_path_.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, i)); + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, i)); base::PlatformFileInfo info; EXPECT_FALSE(file_util::GetFileInfo(entry_file_path, &info)); } @@ -3053,6 +3052,67 @@ TEST_F(DiskCacheEntryTest, SimpleCacheDoomCreateRace) { EXPECT_EQ(net::OK, doom_callback.GetResult(net::ERR_IO_PENDING)); } +TEST_F(DiskCacheEntryTest, SimpleCacheDoomDoom) { + // Test sequence: + // Create, Doom, Create, Doom (1st entry), Open. + SetSimpleCacheMode(); + InitCache(); + disk_cache::Entry* null = NULL; + + const char key[] = "the first key"; + + disk_cache::Entry* entry1 = NULL; + ASSERT_EQ(net::OK, CreateEntry(key, &entry1)); + ScopedEntryPtr entry1_closer(entry1); + EXPECT_NE(null, entry1); + + EXPECT_EQ(net::OK, DoomEntry(key)); + + disk_cache::Entry* entry2 = NULL; + ASSERT_EQ(net::OK, CreateEntry(key, &entry2)); + ScopedEntryPtr entry2_closer(entry2); + EXPECT_NE(null, entry2); + + // Redundantly dooming entry1 should not delete entry2. + disk_cache::SimpleEntryImpl* simple_entry1 = + static_cast<disk_cache::SimpleEntryImpl*>(entry1); + net::TestCompletionCallback cb; + EXPECT_EQ(net::OK, + cb.GetResult(simple_entry1->DoomEntry(cb.callback()))); + + disk_cache::Entry* entry3 = NULL; + ASSERT_EQ(net::OK, OpenEntry(key, &entry3)); + ScopedEntryPtr entry3_closer(entry3); + EXPECT_NE(null, entry3); +} + +TEST_F(DiskCacheEntryTest, SimpleCacheDoomCreateDoom) { + // Test sequence: + // Create, Doom, Create, Doom. + SetSimpleCacheMode(); + InitCache(); + + disk_cache::Entry* null = NULL; + + const char key[] = "the first key"; + + disk_cache::Entry* entry1 = NULL; + ASSERT_EQ(net::OK, CreateEntry(key, &entry1)); + ScopedEntryPtr entry1_closer(entry1); + EXPECT_NE(null, entry1); + + entry1->Doom(); + + disk_cache::Entry* entry2 = NULL; + ASSERT_EQ(net::OK, CreateEntry(key, &entry2)); + ScopedEntryPtr entry2_closer(entry2); + EXPECT_NE(null, entry2); + + entry2->Doom(); + + // This test passes if it doesn't crash. +} + // Checks that an optimistic Create would fail later on a racing Open. TEST_F(DiskCacheEntryTest, SimpleCacheOptimisticCreateFailsOnOpen) { SetSimpleCacheMode(); @@ -3097,7 +3157,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheEvictOldEntries) { scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kWriteSize)); CacheTestFillBuffer(buffer->data(), kWriteSize, false); EXPECT_EQ(kWriteSize, - WriteData(entry, 0, 0, buffer.get(), kWriteSize, false)); + WriteData(entry, 1, 0, buffer.get(), kWriteSize, false)); entry->Close(); AddDelay(); @@ -3106,7 +3166,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheEvictOldEntries) { ASSERT_EQ(net::OK, CreateEntry(key2 + base::StringPrintf("%d", i), &entry)); ScopedEntryPtr entry_closer(entry); EXPECT_EQ(kWriteSize, - WriteData(entry, 0, 0, buffer.get(), kWriteSize, false)); + WriteData(entry, 1, 0, buffer.get(), kWriteSize, false)); } // TODO(pasko): Find a way to wait for the eviction task(s) to finish by using @@ -3142,7 +3202,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheInFlightTruncate) { ASSERT_EQ(net::OK, CreateEntry(key, &entry)); EXPECT_EQ(kBufferSize, - WriteData(entry, 0, 0, write_buffer.get(), kBufferSize, false)); + WriteData(entry, 1, 0, write_buffer.get(), kBufferSize, false)); entry->Close(); entry = NULL; @@ -3157,7 +3217,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheInFlightTruncate) { scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kReadBufferSize)); CallbackTest read_callback(&helper, false); EXPECT_EQ(net::ERR_IO_PENDING, - entry->ReadData(0, + entry->ReadData(1, 0, read_buffer.get(), kReadBufferSize, @@ -3171,7 +3231,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheInFlightTruncate) { CacheTestFillBuffer(truncate_buffer->data(), kReadBufferSize, false); CallbackTest truncate_callback(&helper, false); EXPECT_EQ(net::ERR_IO_PENDING, - entry->WriteData(0, + entry->WriteData(1, 0, truncate_buffer.get(), kReadBufferSize, @@ -3211,7 +3271,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheInFlightRead) { CallbackTest write_callback(&helper, false); EXPECT_EQ(net::ERR_IO_PENDING, - entry->WriteData(0, + entry->WriteData(1, 0, write_buffer.get(), kBufferSize, @@ -3223,7 +3283,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheInFlightRead) { scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kBufferSize)); CallbackTest read_callback(&helper, false); EXPECT_EQ(net::ERR_IO_PENDING, - entry->ReadData(0, + entry->ReadData(1, 0, read_buffer.get(), kBufferSize, @@ -3307,19 +3367,19 @@ TEST_F(DiskCacheEntryTest, SimpleCacheMultipleReadersCheckCRC2) { disk_cache::Entry* entry = NULL; ASSERT_EQ(net::OK, OpenEntry(key, &entry)); ScopedEntryPtr entry_closer(entry); - EXPECT_EQ(1, ReadData(entry, 0, 0, read_buffer1.get(), 1)); + EXPECT_EQ(1, ReadData(entry, 1, 0, read_buffer1.get(), 1)); // Advance the 2nd reader by the same amount. disk_cache::Entry* entry2 = NULL; EXPECT_EQ(net::OK, OpenEntry(key, &entry2)); ScopedEntryPtr entry2_closer(entry2); - EXPECT_EQ(1, ReadData(entry2, 0, 0, read_buffer2.get(), 1)); + EXPECT_EQ(1, ReadData(entry2, 1, 0, read_buffer2.get(), 1)); // Continue reading 1st. - EXPECT_GT(0, ReadData(entry, 0, 1, read_buffer1.get(), size)); + EXPECT_GT(0, ReadData(entry, 1, 1, read_buffer1.get(), size)); // This read should fail as well because we have previous read failures. - EXPECT_GT(0, ReadData(entry2, 0, 1, read_buffer2.get(), 1)); + EXPECT_GT(0, ReadData(entry2, 1, 1, read_buffer2.get(), 1)); DisableIntegrityCheck(); } @@ -3343,7 +3403,7 @@ TEST_F(DiskCacheEntryTest, SimpleCacheReadCombineCRC) { ASSERT_EQ(net::OK, CreateEntry(key, &entry)); EXPECT_NE(null, entry); - EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer1.get(), kSize, false)); + EXPECT_EQ(kSize, WriteData(entry, 1, 0, buffer1.get(), kSize, false)); entry->Close(); disk_cache::Entry* entry2 = NULL; @@ -3354,14 +3414,14 @@ TEST_F(DiskCacheEntryTest, SimpleCacheReadCombineCRC) { int offset = 0; int buf_len = kHalfSize; scoped_refptr<net::IOBuffer> buffer1_read1(new net::IOBuffer(buf_len)); - EXPECT_EQ(buf_len, ReadData(entry2, 0, offset, buffer1_read1.get(), buf_len)); + EXPECT_EQ(buf_len, ReadData(entry2, 1, offset, buffer1_read1.get(), buf_len)); EXPECT_EQ(0, memcmp(buffer1->data(), buffer1_read1->data(), buf_len)); // Read the second half of the data. offset = buf_len; buf_len = kHalfSize; scoped_refptr<net::IOBuffer> buffer1_read2(new net::IOBuffer(buf_len)); - EXPECT_EQ(buf_len, ReadData(entry2, 0, offset, buffer1_read2.get(), buf_len)); + EXPECT_EQ(buf_len, ReadData(entry2, 1, offset, buffer1_read2.get(), buf_len)); char* buffer1_data = buffer1->data() + offset; EXPECT_EQ(0, memcmp(buffer1_data, buffer1_read2->data(), buf_len)); @@ -3423,4 +3483,78 @@ TEST_F(DiskCacheEntryTest, SimpleCacheNonSequentialWrite) { entry = NULL; } +// Test that changing stream1 size does not affect stream0 (stream0 and stream1 +// are stored in the same file in Simple Cache). +TEST_F(DiskCacheEntryTest, SimpleCacheStream1SizeChanges) { + SetSimpleCacheMode(); + InitCache(); + disk_cache::Entry* entry = NULL; + const char key[] = "the key"; + const int kSize = 100; + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize)); + scoped_refptr<net::IOBuffer> buffer_read(new net::IOBuffer(kSize)); + CacheTestFillBuffer(buffer->data(), kSize, false); + + ASSERT_EQ(net::OK, CreateEntry(key, &entry)); + EXPECT_TRUE(entry); + + // Write something into stream0. + EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false)); + EXPECT_EQ(kSize, ReadData(entry, 0, 0, buffer_read.get(), kSize)); + EXPECT_EQ(0, memcmp(buffer->data(), buffer_read->data(), kSize)); + entry->Close(); + + // Extend stream1. + ASSERT_EQ(net::OK, OpenEntry(key, &entry)); + int stream1_size = 100; + EXPECT_EQ(0, WriteData(entry, 1, stream1_size, buffer.get(), 0, false)); + EXPECT_EQ(stream1_size, entry->GetDataSize(1)); + entry->Close(); + + // Check that stream0 data has not been modified and that the EOF record for + // stream 0 contains a crc. + // The entry needs to be reopened before checking the crc: Open will perform + // the synchronization with the previous Close. This ensures the EOF records + // have been written to disk before we attempt to read them independently. + ASSERT_EQ(net::OK, OpenEntry(key, &entry)); + base::FilePath entry_file0_path = cache_path_.AppendASCII( + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0)); + int flags = base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN; + base::PlatformFile entry_file0 = + base::CreatePlatformFile(entry_file0_path, flags, NULL, NULL); + ASSERT_TRUE(entry_file0 != base::kInvalidPlatformFileValue); + + int data_size[disk_cache::kSimpleEntryStreamCount] = {kSize, stream1_size, 0}; + disk_cache::SimpleEntryStat entry_stat( + base::Time::Now(), base::Time::Now(), data_size); + int eof_offset = entry_stat.GetEOFOffsetInFile(key, 0); + disk_cache::SimpleFileEOF eof_record; + ASSERT_EQ(static_cast<int>(sizeof(eof_record)), base::ReadPlatformFile( + entry_file0, + eof_offset, + reinterpret_cast<char*>(&eof_record), + sizeof(eof_record))); + EXPECT_EQ(disk_cache::kSimpleFinalMagicNumber, eof_record.final_magic_number); + EXPECT_TRUE((eof_record.flags & disk_cache::SimpleFileEOF::FLAG_HAS_CRC32) == + disk_cache::SimpleFileEOF::FLAG_HAS_CRC32); + + buffer_read = new net::IOBuffer(kSize); + EXPECT_EQ(kSize, ReadData(entry, 0, 0, buffer_read.get(), kSize)); + EXPECT_EQ(0, memcmp(buffer->data(), buffer_read->data(), kSize)); + + // Shrink stream1. + stream1_size = 50; + EXPECT_EQ(0, WriteData(entry, 1, stream1_size, buffer.get(), 0, true)); + EXPECT_EQ(stream1_size, entry->GetDataSize(1)); + entry->Close(); + + // Check that stream0 data has not been modified. + buffer_read = new net::IOBuffer(kSize); + ASSERT_EQ(net::OK, OpenEntry(key, &entry)); + EXPECT_EQ(kSize, ReadData(entry, 0, 0, buffer_read.get(), kSize)); + EXPECT_EQ(0, memcmp(buffer->data(), buffer_read->data(), kSize)); + entry->Close(); + entry = NULL; +} + #endif // defined(OS_POSIX) diff --git a/net/disk_cache/histogram_macros.h b/net/disk_cache/histogram_macros.h index 3d8011c27f..651bce96f2 100644 --- a/net/disk_cache/histogram_macros.h +++ b/net/disk_cache/histogram_macros.h @@ -115,6 +115,9 @@ case net::SHADER_CACHE:\ CACHE_HISTOGRAM_##type(my_name.data(), sample);\ break;\ + case net::PNACL_CACHE:\ + CACHE_HISTOGRAM_##type(my_name.data(), sample);\ + break;\ default:\ NOTREACHED();\ break;\ diff --git a/net/disk_cache/mem_entry_impl.h b/net/disk_cache/mem_entry_impl.h index ef91f6d7b0..b84cc39ef2 100644 --- a/net/disk_cache/mem_entry_impl.h +++ b/net/disk_cache/mem_entry_impl.h @@ -82,11 +82,7 @@ class MemEntryImpl : public Entry { return parent_ ? kChildEntry : kParentEntry; } - std::string& key() { - return key_; - } - - net::BoundNetLog& net_log() { + const net::BoundNetLog& net_log() { return net_log_; } diff --git a/net/disk_cache/simple/simple_backend_impl.cc b/net/disk_cache/simple/simple_backend_impl.cc index 983b211b49..9e926bb7e4 100644 --- a/net/disk_cache/simple/simple_backend_impl.cc +++ b/net/disk_cache/simple/simple_backend_impl.cc @@ -33,6 +33,7 @@ #include "net/disk_cache/simple/simple_index_file.h" #include "net/disk_cache/simple/simple_synchronous_entry.h" #include "net/disk_cache/simple/simple_util.h" +#include "net/disk_cache/simple/simple_version_upgrade.h" using base::Callback; using base::Closure; @@ -132,65 +133,12 @@ void DeleteBackendImpl(disk_cache::Backend** backend, // Detects if the files in the cache directory match the current disk cache // backend type and version. If the directory contains no cache, occupies it // with the fresh structure. -// -// There is a convention among disk cache backends: looking at the magic in the -// file "index" it should be sufficient to determine if the cache belongs to the -// currently running backend. The Simple Backend stores its index in the file -// "the-real-index" (see simple_index.cc) and the file "index" only signifies -// presence of the implementation's magic and version. There are two reasons for -// that: -// 1. Absence of the index is itself not a fatal error in the Simple Backend -// 2. The Simple Backend has pickled file format for the index making it hacky -// to have the magic in the right place. bool FileStructureConsistent(const base::FilePath& path) { if (!base::PathExists(path) && !file_util::CreateDirectory(path)) { LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName(); return false; } - const base::FilePath fake_index = path.AppendASCII("index"); - base::PlatformFileError error; - base::PlatformFile fake_index_file = base::CreatePlatformFile( - fake_index, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, - NULL, - &error); - if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { - base::PlatformFile file = base::CreatePlatformFile( - fake_index, - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, - NULL, &error); - disk_cache::SimpleFileHeader file_contents; - file_contents.initial_magic_number = disk_cache::kSimpleInitialMagicNumber; - file_contents.version = disk_cache::kSimpleVersion; - int bytes_written = base::WritePlatformFile( - file, 0, reinterpret_cast<char*>(&file_contents), - sizeof(file_contents)); - if (!base::ClosePlatformFile(file) || - bytes_written != sizeof(file_contents)) { - LOG(ERROR) << "Failed to write cache structure file: " - << path.LossyDisplayName(); - return false; - } - return true; - } else if (error != base::PLATFORM_FILE_OK) { - LOG(ERROR) << "Could not open cache structure file: " - << path.LossyDisplayName(); - return false; - } else { - disk_cache::SimpleFileHeader file_header; - int bytes_read = base::ReadPlatformFile( - fake_index_file, 0, reinterpret_cast<char*>(&file_header), - sizeof(file_header)); - if (!base::ClosePlatformFile(fake_index_file) || - bytes_read != sizeof(file_header) || - file_header.initial_magic_number != - disk_cache::kSimpleInitialMagicNumber || - file_header.version != disk_cache::kSimpleVersion) { - LOG(ERROR) << "File structure does not match the disk cache backend."; - return false; - } - return true; - } + return disk_cache::UpgradeSimpleCacheOnDisk(path); } // A short bindable thunk that can call a completion callback. Intended to be diff --git a/net/disk_cache/simple/simple_backend_version.h b/net/disk_cache/simple/simple_backend_version.h new file mode 100644 index 0000000000..fe350a2e88 --- /dev/null +++ b/net/disk_cache/simple/simple_backend_version.h @@ -0,0 +1,27 @@ +// Copyright 2013 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_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_ +#define NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_ + +namespace disk_cache { + +// Short rules helping to think about data upgrades within Simple Cache: +// * ALL changes of on-disk data format, backward-compatible or not, +// forward-compatible or not, require updating the |kSimpleVersion|. +// * All cache Upgrades are performed on backend start, must be finished +// before the new backend starts processing any incoming operations. +// * If the Upgrade is not implemented for transition from +// |kSimpleVersion - 1| then the whole cache directory will be cleared. +// * Dropping cache data on disk or some of its parts can be a valid way to +// Upgrade. +const uint32 kSimpleVersion = 6; + +// The version of the entry file(s) as written to disk. Must be updated iff the +// entry format changes with the overall backend version update. +const uint32 kSimpleEntryVersionOnDisk = 5; + +} // namespace disk_cache + +#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_ diff --git a/net/disk_cache/simple/simple_entry_format.h b/net/disk_cache/simple/simple_entry_format.h index d06ab1139c..8224b858dc 100644 --- a/net/disk_cache/simple/simple_entry_format.h +++ b/net/disk_cache/simple/simple_entry_format.h @@ -19,17 +19,21 @@ namespace disk_cache { const uint64 kSimpleInitialMagicNumber = GG_UINT64_C(0xfcfb6d1ba7725c30); const uint64 kSimpleFinalMagicNumber = GG_UINT64_C(0xf4fa6f45970d41d8); -// A file in the Simple cache consists of a SimpleFileHeader followed -// by data. +// A file containing stream 0 and stream 1 in the Simple cache consists of: +// - a SimpleFileHeader. +// - the key. +// - the data from stream 1. +// - a SimpleFileEOF record for stream 1. +// - the data from stream 0. +// - a SimpleFileEOF record for stream 0. -// A file in the Simple cache consists of: +// A file containing stream 2 in the Simple cache consists of: // - a SimpleFileHeader. // - the key. // - the data. // - at the end, a SimpleFileEOF record. -const uint32 kSimpleVersion = 4; - -static const int kSimpleEntryFileCount = 3; +static const int kSimpleEntryFileCount = 2; +static const int kSimpleEntryStreamCount = 3; struct NET_EXPORT_PRIVATE SimpleFileHeader { SimpleFileHeader(); @@ -40,7 +44,7 @@ struct NET_EXPORT_PRIVATE SimpleFileHeader { uint32 key_hash; }; -struct SimpleFileEOF { +struct NET_EXPORT_PRIVATE SimpleFileEOF { enum Flags { FLAG_HAS_CRC32 = (1U << 0), }; @@ -50,6 +54,8 @@ struct SimpleFileEOF { uint64 final_magic_number; uint32 flags; uint32 data_crc32; + // |stream_size| is only used in the EOF record for stream 0. + uint32 stream_size; }; } // namespace disk_cache diff --git a/net/disk_cache/simple/simple_entry_format_history.h b/net/disk_cache/simple/simple_entry_format_history.h new file mode 100644 index 0000000000..f7b818a59e --- /dev/null +++ b/net/disk_cache/simple/simple_entry_format_history.h @@ -0,0 +1,62 @@ +// Copyright 2013 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_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_ +#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_ + +#include "base/basictypes.h" +#include "base/port.h" +#include "net/base/net_export.h" + +namespace disk_cache { + +namespace simplecache_v5 { + +const uint64 kSimpleInitialMagicNumber = GG_UINT64_C(0xfcfb6d1ba7725c30); +const uint64 kSimpleFinalMagicNumber = GG_UINT64_C(0xf4fa6f45970d41d8); + +// A file containing stream 0 and stream 1 in the Simple cache consists of: +// - a SimpleFileHeader. +// - the key. +// - the data from stream 1. +// - a SimpleFileEOF record for stream 1. +// - the data from stream 0. +// - a SimpleFileEOF record for stream 0. + +// A file containing stream 2 in the Simple cache consists of: +// - a SimpleFileHeader. +// - the key. +// - the data. +// - at the end, a SimpleFileEOF record. +static const int kSimpleEntryFileCount = 2; +static const int kSimpleEntryStreamCount = 3; + +struct NET_EXPORT_PRIVATE SimpleFileHeader { + SimpleFileHeader(); + + uint64 initial_magic_number; + uint32 version; + uint32 key_length; + uint32 key_hash; +}; + +struct NET_EXPORT_PRIVATE SimpleFileEOF { + enum Flags { + FLAG_HAS_CRC32 = (1U << 0), + }; + + SimpleFileEOF(); + + uint64 final_magic_number; + uint32 flags; + uint32 data_crc32; + // |stream_size| is only used in the EOF record for stream 0. + uint32 stream_size; +}; + +} // namespace simplecache_v5 + +} // namespace disk_cache + +#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_ diff --git a/net/disk_cache/simple/simple_entry_impl.cc b/net/disk_cache/simple/simple_entry_impl.cc index e4e7220af7..5f24564f4c 100644 --- a/net/disk_cache/simple/simple_entry_impl.cc +++ b/net/disk_cache/simple/simple_entry_impl.cc @@ -28,6 +28,7 @@ #include "net/disk_cache/simple/simple_util.h" #include "third_party/zlib/zlib.h" +namespace disk_cache { namespace { // Used in histograms, please only add entries at the end. @@ -121,9 +122,17 @@ void AdjustOpenEntryCountBy(net::CacheType cache_type, int offset) { "GlobalOpenEntryCount", cache_type, g_open_entry_count); } -} // namespace +void InvokeCallbackIfBackendIsAlive( + const base::WeakPtr<SimpleBackendImpl>& backend, + const net::CompletionCallback& completion_callback, + int result) { + DCHECK(!completion_callback.is_null()); + if (!backend.get()) + return; + completion_callback.Run(result); +} -namespace disk_cache { +} // namespace using base::Closure; using base::FilePath; @@ -165,7 +174,8 @@ SimpleEntryImpl::SimpleEntryImpl(net::CacheType cache_type, state_(STATE_UNINITIALIZED), synchronous_entry_(NULL), net_log_(net::BoundNetLog::Make( - net_log, net::NetLog::SOURCE_DISK_CACHE_ENTRY)) { + net_log, net::NetLog::SOURCE_DISK_CACHE_ENTRY)), + stream_0_data_(new net::GrowableIOBuffer()) { COMPILE_ASSERT(arraysize(data_size_) == arraysize(crc32s_end_offset_), arrays_should_be_same_size); COMPILE_ASSERT(arraysize(data_size_) == arraysize(crc32s_), @@ -253,10 +263,14 @@ int SimpleEntryImpl::CreateEntry(Entry** out_entry, } int SimpleEntryImpl::DoomEntry(const CompletionCallback& callback) { + if (doomed_) + return net::OK; net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_DOOM_CALL); net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_DOOM_BEGIN); MarkAsDoomed(); + if (backend_.get()) + backend_->OnDoomStart(entry_hash_); pending_operations_.push(SimpleEntryOperation::DoomOperation(this, callback)); RunNextOperationIfNeeded(); return net::ERR_IO_PENDING; @@ -324,7 +338,7 @@ int SimpleEntryImpl::ReadData(int stream_index, false)); } - if (stream_index < 0 || stream_index >= kSimpleEntryFileCount || + if (stream_index < 0 || stream_index >= kSimpleEntryStreamCount || buf_len < 0) { if (net_log_.IsLoggingAllEvents()) { net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_END, @@ -345,6 +359,8 @@ int SimpleEntryImpl::ReadData(int stream_index, return 0; } + // TODO(clamy): return immediatly when reading from stream 0. + // TODO(felipeg): Optimization: Add support for truly parallel read // operations. bool alone_in_queue = @@ -370,8 +386,8 @@ int SimpleEntryImpl::WriteData(int stream_index, truncate)); } - if (stream_index < 0 || stream_index >= kSimpleEntryFileCount || offset < 0 || - buf_len < 0) { + if (stream_index < 0 || stream_index >= kSimpleEntryStreamCount || + offset < 0 || buf_len < 0) { if (net_log_.IsLoggingAllEvents()) { net_log_.AddEvent( net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_END, @@ -391,16 +407,11 @@ int SimpleEntryImpl::WriteData(int stream_index, } ScopedOperationRunner operation_runner(this); - // Currently, Simple Cache is only used for HTTP, which stores the headers in - // stream 0 and always writes them with a single, truncating write. Detect - // these writes and record the size and size changes of the headers. Also, - // note writes to stream 0 that violate those assumptions. - if (stream_index == 0) { - if (offset == 0 && truncate) - RecordHeaderSizeChange(cache_type_, data_size_[0], buf_len); - else - RecordUnexpectedStream0Write(cache_type_); - } + // Stream 0 data is kept in memory, so can be written immediatly if there are + // no IO operations pending. + if (stream_index == 0 && state_ == STATE_READY && + pending_operations_.size() == 0) + return SetStream0Data(buf, offset, buf_len, truncate); // We can only do optimistic Write if there is no pending operations, so // that we are sure that the next call to RunNextOperationIfNeeded will @@ -504,6 +515,17 @@ SimpleEntryImpl::~SimpleEntryImpl() { net_log_.EndEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY); } +void SimpleEntryImpl::PostClientCallback(const CompletionCallback& callback, + int result) { + if (callback.is_null()) + return; + // Note that the callback is posted rather than directly invoked to avoid + // reentrancy issues. + MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&InvokeCallbackIfBackendIsAlive, backend_, callback, result)); +} + void SimpleEntryImpl::MakeUninitialized() { state_ = STATE_UNINITIALIZED; std::memset(crc32s_end_offset_, 0, sizeof(crc32s_end_offset_)); @@ -519,6 +541,15 @@ void SimpleEntryImpl::ReturnEntryToCaller(Entry** out_entry) { DCHECK(out_entry); ++open_count_; AddRef(); // Balanced in Close() + if (!backend_.get()) { + // This method can be called when an asynchronous operation completed. + // If the backend no longer exists, the callback won't be invoked, and so we + // must close ourselves to avoid leaking. As well, there's no guarantee the + // client-provided pointer (|out_entry|) hasn't been freed, and no point + // dereferencing it, either. + Close(); + return; + } *out_entry = this; } @@ -529,9 +560,9 @@ void SimpleEntryImpl::RemoveSelfFromBackend() { } void SimpleEntryImpl::MarkAsDoomed() { + doomed_ = true; if (!backend_.get()) return; - doomed_ = true; backend_->index()->Remove(entry_hash_); RemoveSelfFromBackend(); } @@ -599,18 +630,14 @@ void SimpleEntryImpl::OpenEntryInternal(bool have_index, if (state_ == STATE_READY) { ReturnEntryToCaller(out_entry); - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(callback, - net::OK)); + PostClientCallback(callback, net::OK); net_log_.AddEvent( net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_END, CreateNetLogSimpleEntryCreationCallback(this, net::OK)); return; } if (state_ == STATE_FAILURE) { - if (!callback.is_null()) { - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - callback, net::ERR_FAILED)); - } + PostClientCallback(callback, net::ERR_FAILED); net_log_.AddEvent( net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_END, CreateNetLogSimpleEntryCreationCallback(this, net::ERR_FAILED)); @@ -652,11 +679,7 @@ void SimpleEntryImpl::CreateEntryInternal(bool have_index, net_log_.AddEvent( net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CREATE_END, CreateNetLogSimpleEntryCreationCallback(this, net::ERR_FAILED)); - - if (!callback.is_null()) { - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - callback, net::ERR_FAILED)); - } + PostClientCallback(callback, net::ERR_FAILED); return; } DCHECK_EQ(STATE_UNINITIALIZED, state_); @@ -669,7 +692,7 @@ void SimpleEntryImpl::CreateEntryInternal(bool have_index, last_used_ = last_modified_ = base::Time::Now(); // If creation succeeds, we should mark all streams to be saved on close. - for (int i = 0; i < kSimpleEntryFileCount; ++i) + for (int i = 0; i < kSimpleEntryStreamCount; ++i) have_written_[i] = true; const base::TimeTicks start_time = base::TimeTicks::Now(); @@ -704,7 +727,7 @@ void SimpleEntryImpl::CloseInternal() { if (state_ == STATE_READY) { DCHECK(synchronous_entry_); state_ = STATE_IO_PENDING; - for (int i = 0; i < kSimpleEntryFileCount; ++i) { + for (int i = 0; i < kSimpleEntryStreamCount; ++i) { if (have_written_[i]) { if (GetDataSize(i) == crc32s_end_offset_[i]) { int32 crc = GetDataSize(i) == 0 ? crc32(0, Z_NULL, 0) : crc32s_[i]; @@ -723,12 +746,13 @@ void SimpleEntryImpl::CloseInternal() { base::Bind(&SimpleSynchronousEntry::Close, base::Unretained(synchronous_entry_), SimpleEntryStat(last_used_, last_modified_, data_size_), - base::Passed(&crc32s_to_write)); + base::Passed(&crc32s_to_write), + stream_0_data_); Closure reply = base::Bind(&SimpleEntryImpl::CloseOperationComplete, this); synchronous_entry_ = NULL; worker_pool_->PostTaskAndReply(FROM_HERE, task, reply); - for (int i = 0; i < kSimpleEntryFileCount; ++i) { + for (int i = 0; i < kSimpleEntryStreamCount; ++i) { if (!have_written_[i]) { SIMPLE_CACHE_UMA(ENUMERATION, "CheckCRCResult", cache_type_, @@ -758,8 +782,11 @@ void SimpleEntryImpl::ReadDataInternal(int stream_index, if (state_ == STATE_FAILURE || state_ == STATE_UNINITIALIZED) { if (!callback.is_null()) { RecordReadResult(cache_type_, READ_RESULT_BAD_STATE); - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - callback, net::ERR_FAILED)); + // Note that the API states that client-provided callbacks for entry-level + // (i.e. non-backend) operations (e.g. read, write) are invoked even if + // the backend was already destroyed. + MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(callback, net::ERR_FAILED)); } if (net_log_.IsLoggingAllEvents()) { net_log_.AddEvent( @@ -774,27 +801,37 @@ void SimpleEntryImpl::ReadDataInternal(int stream_index, // If there is nothing to read, we bail out before setting state_ to // STATE_IO_PENDING. if (!callback.is_null()) - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - callback, 0)); + MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(callback, 0)); return; } buf_len = std::min(buf_len, GetDataSize(stream_index) - offset); + // Since stream 0 data is kept in memory, it is read immediately. + if (stream_index == 0) { + int ret_value = ReadStream0Data(buf, offset, buf_len); + if (!callback.is_null()) { + MessageLoopProxy::current()->PostTask(FROM_HERE, + base::Bind(callback, ret_value)); + } + return; + } + state_ = STATE_IO_PENDING; if (!doomed_ && backend_.get()) backend_->index()->UseIfExists(entry_hash_); scoped_ptr<uint32> read_crc32(new uint32()); scoped_ptr<int> result(new int()); - scoped_ptr<base::Time> last_used(new base::Time()); + scoped_ptr<SimpleEntryStat> entry_stat( + new SimpleEntryStat(last_used_, last_modified_, data_size_)); Closure task = base::Bind( &SimpleSynchronousEntry::ReadData, base::Unretained(synchronous_entry_), SimpleSynchronousEntry::EntryOperationData(stream_index, offset, buf_len), make_scoped_refptr(buf), read_crc32.get(), - last_used.get(), + entry_stat.get(), result.get()); Closure reply = base::Bind(&SimpleEntryImpl::ReadOperationComplete, this, @@ -802,7 +839,7 @@ void SimpleEntryImpl::ReadDataInternal(int stream_index, offset, callback, base::Passed(&read_crc32), - base::Passed(&last_used), + base::Passed(&entry_stat), base::Passed(&result)); worker_pool_->PostTaskAndReply(FROM_HERE, task, reply); } @@ -831,34 +868,30 @@ void SimpleEntryImpl::WriteDataInternal(int stream_index, CreateNetLogReadWriteCompleteCallback(net::ERR_FAILED)); } if (!callback.is_null()) { - // We need to posttask so that we don't go in a loop when we call the - // callback directly. - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - callback, net::ERR_FAILED)); + MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(callback, net::ERR_FAILED)); } // |this| may be destroyed after return here. return; } DCHECK_EQ(STATE_READY, state_); + + // Since stream 0 data is kept in memory, it will be written immediatly. + if (stream_index == 0) { + int ret_value = SetStream0Data(buf, offset, buf_len, truncate); + if (!callback.is_null()) { + MessageLoopProxy::current()->PostTask(FROM_HERE, + base::Bind(callback, ret_value)); + } + return; + } + state_ = STATE_IO_PENDING; if (!doomed_ && backend_.get()) backend_->index()->UseIfExists(entry_hash_); - // It is easy to incrementally compute the CRC from [0 .. |offset + buf_len|) - // if |offset == 0| or we have already computed the CRC for [0 .. offset). - // We rely on most write operations being sequential, start to end to compute - // the crc of the data. When we write to an entry and close without having - // done a sequential write, we don't check the CRC on read. - if (offset == 0 || crc32s_end_offset_[stream_index] == offset) { - uint32 initial_crc = (offset != 0) ? crc32s_[stream_index] - : crc32(0, Z_NULL, 0); - if (buf_len > 0) { - crc32s_[stream_index] = crc32(initial_crc, - reinterpret_cast<const Bytef*>(buf->data()), - buf_len); - } - crc32s_end_offset_[stream_index] = offset + buf_len; - } + + AdvanceCrc(buf, offset, buf_len, stream_index); // |entry_stat| needs to be initialized before modifying |data_size_|. scoped_ptr<SimpleEntryStat> entry_stat( @@ -875,6 +908,10 @@ void SimpleEntryImpl::WriteDataInternal(int stream_index, last_used_ = last_modified_ = base::Time::Now(); have_written_[stream_index] = true; + // Writing on stream 1 affects the placement of stream 0 in the file, the EOF + // record will have to be rewritten. + if (stream_index == 1) + have_written_[0] = true; scoped_ptr<int> result(new int()); Closure task = base::Bind(&SimpleSynchronousEntry::WriteData, @@ -894,8 +931,6 @@ void SimpleEntryImpl::WriteDataInternal(int stream_index, } void SimpleEntryImpl::DoomEntryInternal(const CompletionCallback& callback) { - if (backend_) - backend_->OnDoomStart(entry_hash_); PostTaskAndReplyWithResult( worker_pool_, FROM_HERE, base::Bind(&SimpleSynchronousEntry::DoomEntry, path_, key_, entry_hash_), @@ -922,11 +957,7 @@ void SimpleEntryImpl::CreationOperationComplete( MarkAsDoomed(); net_log_.AddEventWithNetErrorCode(end_event_type, net::ERR_FAILED); - - if (!completion_callback.is_null()) { - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - completion_callback, net::ERR_FAILED)); - } + PostClientCallback(completion_callback, net::ERR_FAILED); MakeUninitialized(); return; } @@ -937,6 +968,13 @@ void SimpleEntryImpl::CreationOperationComplete( state_ = STATE_READY; synchronous_entry_ = in_results->sync_entry; + if (in_results->stream_0_data) { + stream_0_data_ = in_results->stream_0_data; + // The crc was read in SimpleSynchronousEntry. + crc_check_state_[0] = CRC_CHECK_DONE; + crc32s_[0] = in_results->stream_0_crc32; + crc32s_end_offset_[0] = in_results->entry_stat.data_size(0); + } if (key_.empty()) { SetKey(synchronous_entry_->key()); } else { @@ -951,10 +989,7 @@ void SimpleEntryImpl::CreationOperationComplete( AdjustOpenEntryCountBy(cache_type_, 1); net_log_.AddEvent(end_event_type); - if (!completion_callback.is_null()) { - MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind( - completion_callback, net::OK)); - } + PostClientCallback(completion_callback, net::OK); } void SimpleEntryImpl::EntryOperationComplete( @@ -987,7 +1022,7 @@ void SimpleEntryImpl::ReadOperationComplete( int offset, const CompletionCallback& completion_callback, scoped_ptr<uint32> read_crc32, - scoped_ptr<base::Time> last_used, + scoped_ptr<SimpleEntryStat> entry_stat, scoped_ptr<int> result) { DCHECK(io_thread_checker_.CalledOnValidThread()); DCHECK(synchronous_entry_); @@ -1023,7 +1058,7 @@ void SimpleEntryImpl::ReadOperationComplete( Closure task = base::Bind(&SimpleSynchronousEntry::CheckEOFRecord, base::Unretained(synchronous_entry_), stream_index, - data_size_[stream_index], + *entry_stat, crc32s_[stream_index], new_result.get()); Closure reply = base::Bind(&SimpleEntryImpl::ChecksumOperationComplete, @@ -1052,10 +1087,7 @@ void SimpleEntryImpl::ReadOperationComplete( } EntryOperationComplete( - stream_index, - completion_callback, - SimpleEntryStat(*last_used, last_modified_, data_size_), - result.Pass()); + stream_index, completion_callback, *entry_stat, result.Pass()); } void SimpleEntryImpl::WriteOperationComplete( @@ -1142,10 +1174,10 @@ void SimpleEntryImpl::UpdateDataFromEntryStat( DCHECK(synchronous_entry_); DCHECK_EQ(STATE_READY, state_); - last_used_ = entry_stat.last_used; - last_modified_ = entry_stat.last_modified; - for (int i = 0; i < kSimpleEntryFileCount; ++i) { - data_size_[i] = entry_stat.data_size[i]; + last_used_ = entry_stat.last_used(); + last_modified_ = entry_stat.last_modified(); + for (int i = 0; i < kSimpleEntryStreamCount; ++i) { + data_size_[i] = entry_stat.data_size(i); } if (!doomed_ && backend_.get()) backend_->index()->UpdateEntrySize(entry_hash_, GetDiskUsage()); @@ -1153,7 +1185,7 @@ void SimpleEntryImpl::UpdateDataFromEntryStat( int64 SimpleEntryImpl::GetDiskUsage() const { int64 file_size = 0; - for (int i = 0; i < kSimpleEntryFileCount; ++i) { + for (int i = 0; i < kSimpleEntryStreamCount; ++i) { file_size += simple_util::GetFileSizeFromKeyAndDataSize(key_, data_size_[i]); } @@ -1231,4 +1263,76 @@ void SimpleEntryImpl::RecordWriteDependencyType( type, WRITE_DEPENDENCY_TYPE_MAX); } +int SimpleEntryImpl::ReadStream0Data(net::IOBuffer* buf, + int offset, + int buf_len) { + if (buf_len < 0) { + RecordReadResult(cache_type_, READ_RESULT_SYNC_READ_FAILURE); + return 0; + } + memcpy(buf->data(), stream_0_data_->data() + offset, buf_len); + UpdateDataFromEntryStat( + SimpleEntryStat(base::Time::Now(), last_modified_, data_size_)); + RecordReadResult(cache_type_, READ_RESULT_SUCCESS); + return buf_len; +} + +int SimpleEntryImpl::SetStream0Data(net::IOBuffer* buf, + int offset, + int buf_len, + bool truncate) { + // Currently, stream 0 is only used for HTTP headers, and always writes them + // with a single, truncating write. Detect these writes and record the size + // changes of the headers. Also, support writes to stream 0 that have + // different access patterns, as required by the API contract. + // All other clients of the Simple Cache are encouraged to use stream 1. + have_written_[0] = true; + int data_size = GetDataSize(0); + if (offset == 0 && truncate) { + RecordHeaderSizeChange(cache_type_, data_size, buf_len); + stream_0_data_->SetCapacity(buf_len); + memcpy(stream_0_data_->data(), buf->data(), buf_len); + data_size_[0] = buf_len; + } else { + RecordUnexpectedStream0Write(cache_type_); + const int buffer_size = + truncate ? offset + buf_len : std::max(offset + buf_len, data_size); + stream_0_data_->SetCapacity(buffer_size); + // If |stream_0_data_| was extended, the extension until offset needs to be + // zero-filled. + const int fill_size = offset <= data_size ? 0 : offset - data_size; + if (fill_size > 0) + memset(stream_0_data_->data() + data_size, 0, fill_size); + if (buf) + memcpy(stream_0_data_->data() + offset, buf->data(), buf_len); + data_size_[0] = buffer_size; + } + base::Time modification_time = base::Time::Now(); + AdvanceCrc(buf, offset, buf_len, 0); + UpdateDataFromEntryStat( + SimpleEntryStat(modification_time, modification_time, data_size_)); + RecordWriteResult(cache_type_, WRITE_RESULT_SUCCESS); + return buf_len; +} + +void SimpleEntryImpl::AdvanceCrc(net::IOBuffer* buffer, + int offset, + int length, + int stream_index) { + // It is easy to incrementally compute the CRC from [0 .. |offset + buf_len|) + // if |offset == 0| or we have already computed the CRC for [0 .. offset). + // We rely on most write operations being sequential, start to end to compute + // the crc of the data. When we write to an entry and close without having + // done a sequential write, we don't check the CRC on read. + if (offset == 0 || crc32s_end_offset_[stream_index] == offset) { + uint32 initial_crc = + (offset != 0) ? crc32s_[stream_index] : crc32(0, Z_NULL, 0); + if (length > 0) { + crc32s_[stream_index] = crc32( + initial_crc, reinterpret_cast<const Bytef*>(buffer->data()), length); + } + crc32s_end_offset_[stream_index] = offset + length; + } +} + } // namespace disk_cache diff --git a/net/disk_cache/simple/simple_entry_impl.h b/net/disk_cache/simple/simple_entry_impl.h index 4f07a3e18f..e2f0c63b39 100644 --- a/net/disk_cache/simple/simple_entry_impl.h +++ b/net/disk_cache/simple/simple_entry_impl.h @@ -14,6 +14,7 @@ #include "base/memory/weak_ptr.h" #include "base/threading/thread_checker.h" #include "net/base/cache_type.h" +#include "net/base/net_export.h" #include "net/base/net_log.h" #include "net/disk_cache/disk_cache.h" #include "net/disk_cache/simple/simple_entry_format.h" @@ -24,6 +25,7 @@ class TaskRunner; } namespace net { +class GrowableIOBuffer; class IOBuffer; } @@ -31,13 +33,14 @@ namespace disk_cache { class SimpleBackendImpl; class SimpleSynchronousEntry; -struct SimpleEntryStat; +class SimpleEntryStat; struct SimpleEntryCreationResults; // SimpleEntryImpl is the IO thread interface to an entry in the very simple // disk cache. It proxies for the SimpleSynchronousEntry, which performs IO // on the worker thread. -class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, +class NET_EXPORT_PRIVATE SimpleEntryImpl : public Entry, + public base::RefCounted<SimpleEntryImpl>, public base::SupportsWeakPtr<SimpleEntryImpl> { friend class base::RefCounted<SimpleEntryImpl>; public: @@ -134,6 +137,12 @@ class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, virtual ~SimpleEntryImpl(); + // Must be used to invoke a client-provided completion callback for an + // operation initiated through the backend (e.g. create, open) so that clients + // don't get notified after they deleted the backend (which they would not + // expect). + void PostClientCallback(const CompletionCallback& callback, int result); + // Sets entry to STATE_UNINITIALIZED. void MakeUninitialized(); @@ -209,7 +218,7 @@ class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, int offset, const CompletionCallback& completion_callback, scoped_ptr<uint32> read_crc32, - scoped_ptr<base::Time> last_used, + scoped_ptr<SimpleEntryStat> entry_stat, scoped_ptr<int> result); // Called after an asynchronous write completes. @@ -243,6 +252,23 @@ class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, void RecordReadIsParallelizable(const SimpleEntryOperation& operation) const; void RecordWriteDependencyType(const SimpleEntryOperation& operation) const; + // Reads from the stream 0 data kept in memory. + int ReadStream0Data(net::IOBuffer* buf, int offset, int buf_len); + + // Copies data from |buf| to the internal in-memory buffer for stream 0. If + // |truncate| is set to true, the target buffer will be truncated at |offset| + // + |buf_len| before being written. + int SetStream0Data(net::IOBuffer* buf, + int offset, int buf_len, + bool truncate); + + // Updates |crc32s_| and |crc32s_end_offset_| for a write of the data in + // |buffer| on |stream_index|, starting at |offset| and of length |length|. + void AdvanceCrc(net::IOBuffer* buffer, + int offset, + int length, + int stream_index); + // All nonstatic SimpleEntryImpl methods should always be called on the IO // thread, in all cases. |io_thread_checker_| documents and enforces this. base::ThreadChecker io_thread_checker_; @@ -260,7 +286,7 @@ class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, // TODO(clamy): Unify last_used_ with data in the index. base::Time last_used_; base::Time last_modified_; - int32 data_size_[kSimpleEntryFileCount]; + int32 data_size_[kSimpleEntryStreamCount]; // Number of times this object has been returned from Backend::OpenEntry() and // Backend::CreateEntry() without subsequent Entry::Close() calls. Used to @@ -275,15 +301,16 @@ class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, // write. For each stream, |crc32s_[index]| is the crc32 of that stream from // [0 .. |crc32s_end_offset_|). If |crc32s_end_offset_[index] == 0| then the // value of |crc32s_[index]| is undefined. - int32 crc32s_end_offset_[kSimpleEntryFileCount]; - uint32 crc32s_[kSimpleEntryFileCount]; + int32 crc32s_end_offset_[kSimpleEntryStreamCount]; + uint32 crc32s_[kSimpleEntryStreamCount]; - // If |have_written_[index]| is true, we have written to the stream |index|. - bool have_written_[kSimpleEntryFileCount]; + // If |have_written_[index]| is true, we have written to the file that + // contains stream |index|. + bool have_written_[kSimpleEntryStreamCount]; // Reflects how much CRC checking has been done with the entry. This state is // reported on closing each entry stream. - CheckCrcResult crc_check_state_[kSimpleEntryFileCount]; + CheckCrcResult crc_check_state_[kSimpleEntryStreamCount]; // The |synchronous_entry_| is the worker thread object that performs IO on // entries. It's owned by this SimpleEntryImpl whenever |executing_operation_| @@ -298,6 +325,17 @@ class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>, net::BoundNetLog net_log_; scoped_ptr<SimpleEntryOperation> executing_operation_; + + // Unlike other streams, stream 0 data is read from the disk when the entry is + // opened, and then kept in memory. All read/write operations on stream 0 + // affect the |stream_0_data_| buffer. When the entry is closed, + // |stream_0_data_| is written to the disk. + // Stream 0 is kept in memory because it is stored in the same file as stream + // 1 on disk, to reduce the number of file descriptors and save disk space. + // This strategy allows stream 1 to change size easily. Since stream 0 is only + // used to write HTTP headers, the memory consumption of keeping it in memory + // is acceptable. + scoped_refptr<net::GrowableIOBuffer> stream_0_data_; }; } // namespace disk_cache diff --git a/net/disk_cache/simple/simple_index_file.cc b/net/disk_cache/simple/simple_index_file.cc index 61318c83b9..b8ec6d7e68 100644 --- a/net/disk_cache/simple/simple_index_file.cc +++ b/net/disk_cache/simple/simple_index_file.cc @@ -14,6 +14,7 @@ #include "base/single_thread_task_runner.h" #include "base/task_runner_util.h" #include "base/threading/thread_restrictions.h" +#include "net/disk_cache/simple/simple_backend_version.h" #include "net/disk_cache/simple/simple_entry_format.h" #include "net/disk_cache/simple/simple_histogram_macros.h" #include "net/disk_cache/simple/simple_index.h" @@ -43,36 +44,43 @@ void DoomEntrySetReply(const net::CompletionCallback& reply_callback, reply_callback.Run(result); } -void WriteToDiskInternal(net::CacheType cache_type, - const base::FilePath& index_filename, - const base::FilePath& temp_index_filename, - scoped_ptr<Pickle> pickle, - const base::TimeTicks& start_time, - bool app_on_background) { +// Used in histograms. Please only add new values at the end. +enum IndexFileState { + INDEX_STATE_CORRUPT = 0, + INDEX_STATE_STALE = 1, + INDEX_STATE_FRESH = 2, + INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3, + INDEX_STATE_MAX = 4, +}; + +void UmaRecordIndexFileState(IndexFileState state, net::CacheType cache_type) { + SIMPLE_CACHE_UMA(ENUMERATION, + "IndexFileStateOnLoad", cache_type, state, INDEX_STATE_MAX); +} + +// Used in histograms. Please only add new values at the end. +enum IndexInitMethod { + INITIALIZE_METHOD_RECOVERED = 0, + INITIALIZE_METHOD_LOADED = 1, + INITIALIZE_METHOD_NEWCACHE = 2, + INITIALIZE_METHOD_MAX = 3, +}; + +void UmaRecordIndexInitMethod(IndexInitMethod method, + net::CacheType cache_type) { + SIMPLE_CACHE_UMA(ENUMERATION, + "IndexInitializeMethod", cache_type, + method, INITIALIZE_METHOD_MAX); +} + +bool WritePickleFile(Pickle* pickle, const base::FilePath& file_name) { int bytes_written = file_util::WriteFile( - temp_index_filename, - reinterpret_cast<const char*>(pickle->data()), - pickle->size()); - DCHECK_EQ(bytes_written, implicit_cast<int>(pickle->size())); - if (bytes_written != static_cast<int>(pickle->size())) { - // TODO(felipeg): Add better error handling. - LOG(ERROR) << "Could not write Simple Cache index to temporary file: " - << temp_index_filename.value(); - base::DeleteFile(temp_index_filename, /* recursive = */ false); - } else { - // Swap temp and index_file. - bool result = base::ReplaceFile(temp_index_filename, index_filename, NULL); - DCHECK(result); - } - if (app_on_background) { - SIMPLE_CACHE_UMA(TIMES, - "IndexWriteToDiskTime.Background", cache_type, - (base::TimeTicks::Now() - start_time)); - } else { - SIMPLE_CACHE_UMA(TIMES, - "IndexWriteToDiskTime.Foreground", cache_type, - (base::TimeTicks::Now() - start_time)); + file_name, static_cast<const char*>(pickle->data()), pickle->size()); + if (bytes_written != implicit_cast<int>(pickle->size())) { + base::DeleteFile(file_name, /* recursive = */ false); + return false; } + return true; } // Called for each cache directory traversal iteration. @@ -117,7 +125,7 @@ void ProcessEntryFile(SimpleIndex::EntrySet* entries, EntryMetadata(last_used_time, file_size), entries); } else { - // Summing up the total size of the entry through all the *_[0-2] files + // Summing up the total size of the entry through all the *_[0-1] files it->second.SetEntrySize(it->second.GetEntrySize() + file_size); } } @@ -137,6 +145,13 @@ void SimpleIndexLoadResult::Reset() { entries.clear(); } +// static +const char SimpleIndexFile::kIndexFileName[] = "the-real-index"; +// static +const char SimpleIndexFile::kIndexDirectory[] = "index-dir"; +// static +const char SimpleIndexFile::kTempIndexFileName[] = "temp-index"; + SimpleIndexFile::IndexMetadata::IndexMetadata() : magic_number_(kSimpleIndexMagicNumber), version_(kSimpleVersion), @@ -158,6 +173,16 @@ void SimpleIndexFile::IndexMetadata::Serialize(Pickle* pickle) const { pickle->WriteUInt64(cache_size_); } +// static +bool SimpleIndexFile::SerializeFinalData(base::Time cache_modified, + Pickle* pickle) { + if (!pickle->WriteInt64(cache_modified.ToInternalValue())) + return false; + SimpleIndexFile::PickleHeader* header_p = pickle->headerT<PickleHeader>(); + header_p->crc = CalculatePickleCRC(*pickle); + return true; +} + bool SimpleIndexFile::IndexMetadata::Deserialize(PickleIterator* it) { DCHECK(it); return it->ReadUInt64(&magic_number_) && @@ -166,10 +191,55 @@ bool SimpleIndexFile::IndexMetadata::Deserialize(PickleIterator* it) { it->ReadUInt64(&cache_size_); } +void SimpleIndexFile::SyncWriteToDisk(net::CacheType cache_type, + const base::FilePath& cache_directory, + const base::FilePath& index_filename, + const base::FilePath& temp_index_filename, + scoped_ptr<Pickle> pickle, + const base::TimeTicks& start_time, + bool app_on_background) { + // There is a chance that the index containing all the necessary data about + // newly created entries will appear to be stale. This can happen if on-disk + // part of a Create operation does not fit into the time budget for the index + // flush delay. This simple approach will be reconsidered if it does not allow + // for maintaining freshness. + base::PlatformFileInfo cache_dir_info; + base::Time cache_dir_mtime; + if (!simple_util::GetMTime(cache_directory, &cache_dir_mtime)) { + LOG(ERROR) << "Could obtain information about cache age"; + return; + } + SerializeFinalData(cache_dir_mtime, pickle.get()); + if (!WritePickleFile(pickle.get(), temp_index_filename)) { + if (!file_util::CreateDirectory(temp_index_filename.DirName())) { + LOG(ERROR) << "Could not create a directory to hold the index file"; + return; + } + if (!WritePickleFile(pickle.get(), temp_index_filename)) { + LOG(ERROR) << "Failed to write the temporary index file"; + return; + } + } + + // Atomically rename the temporary index file to become the real one. + bool result = base::ReplaceFile(temp_index_filename, index_filename, NULL); + DCHECK(result); + + if (app_on_background) { + SIMPLE_CACHE_UMA(TIMES, + "IndexWriteToDiskTime.Background", cache_type, + (base::TimeTicks::Now() - start_time)); + } else { + SIMPLE_CACHE_UMA(TIMES, + "IndexWriteToDiskTime.Foreground", cache_type, + (base::TimeTicks::Now() - start_time)); + } +} + bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() { return number_of_entries_ <= kMaxEntiresInIndex && - magic_number_ == disk_cache::kSimpleIndexMagicNumber && - version_ == disk_cache::kSimpleVersion; + magic_number_ == kSimpleIndexMagicNumber && + version_ == kSimpleVersion; } SimpleIndexFile::SimpleIndexFile( @@ -181,8 +251,10 @@ SimpleIndexFile::SimpleIndexFile( worker_pool_(worker_pool), cache_type_(cache_type), cache_directory_(cache_directory), - index_file_(cache_directory_.AppendASCII(kIndexFileName)), - temp_index_file_(cache_directory_.AppendASCII(kTempIndexFileName)) { + index_file_(cache_directory_.AppendASCII(kIndexDirectory) + .AppendASCII(kIndexFileName)), + temp_index_file_(cache_directory_.AppendASCII(kIndexDirectory) + .AppendASCII(kTempIndexFileName)) { } SimpleIndexFile::~SimpleIndexFile() {} @@ -204,8 +276,9 @@ void SimpleIndexFile::WriteToDisk(const SimpleIndex::EntrySet& entry_set, IndexMetadata index_metadata(entry_set.size(), cache_size); scoped_ptr<Pickle> pickle = Serialize(index_metadata, entry_set); cache_thread_->PostTask(FROM_HERE, base::Bind( - &WriteToDiskInternal, + &SimpleIndexFile::SyncWriteToDisk, cache_type_, + cache_directory_, index_file_, temp_index_file_, base::Passed(&pickle), @@ -231,84 +304,51 @@ void SimpleIndexFile::SyncLoadIndexEntries( const base::FilePath& cache_directory, const base::FilePath& index_file_path, SimpleIndexLoadResult* out_result) { - // TODO(felipeg): probably could load a stale index and use it for something. - const SimpleIndex::EntrySet& entries = out_result->entries; - - const bool index_file_exists = base::PathExists(index_file_path); - - // Used in histograms. Please only add new values at the end. - enum { - INDEX_STATE_CORRUPT = 0, - INDEX_STATE_STALE = 1, - INDEX_STATE_FRESH = 2, - INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3, - INDEX_STATE_MAX = 4, - } index_file_state; - - // Only load if the index is not stale. - if (IsIndexFileStale(cache_last_modified, index_file_path)) { - index_file_state = INDEX_STATE_STALE; - } else { - index_file_state = INDEX_STATE_FRESH; - base::Time latest_dir_mtime; - if (simple_util::GetMTime(cache_directory, &latest_dir_mtime) && - IsIndexFileStale(latest_dir_mtime, index_file_path)) { - // A file operation has updated the directory since we last looked at it - // during backend initialization. - index_file_state = INDEX_STATE_FRESH_CONCURRENT_UPDATES; - } - - const base::TimeTicks start = base::TimeTicks::Now(); - SyncLoadFromDisk(index_file_path, out_result); - SIMPLE_CACHE_UMA(TIMES, - "IndexLoadTime", cache_type, - base::TimeTicks::Now() - start); - SIMPLE_CACHE_UMA(COUNTS, - "IndexEntriesLoaded", cache_type, - out_result->did_load ? entries.size() : 0); - if (!out_result->did_load) - index_file_state = INDEX_STATE_CORRUPT; - } - SIMPLE_CACHE_UMA(ENUMERATION, - "IndexFileStateOnLoad", cache_type, - index_file_state, INDEX_STATE_MAX); + // Load the index and find its age. + base::Time last_cache_seen_by_index; + SyncLoadFromDisk(index_file_path, &last_cache_seen_by_index, out_result); + // Consider the index loaded if it is fresh. + const bool index_file_existed = base::PathExists(index_file_path); if (!out_result->did_load) { - const base::TimeTicks start = base::TimeTicks::Now(); - SyncRestoreFromDisk(cache_directory, index_file_path, out_result); - SIMPLE_CACHE_UMA(MEDIUM_TIMES, - "IndexRestoreTime", cache_type, - base::TimeTicks::Now() - start); - SIMPLE_CACHE_UMA(COUNTS, - "IndexEntriesRestored", cache_type, entries.size()); + if (index_file_existed) + UmaRecordIndexFileState(INDEX_STATE_CORRUPT, cache_type); + } else { + if (cache_last_modified <= last_cache_seen_by_index) { + base::Time latest_dir_mtime; + simple_util::GetMTime(cache_directory, &latest_dir_mtime); + if (LegacyIsIndexFileStale(latest_dir_mtime, index_file_path)) { + UmaRecordIndexFileState(INDEX_STATE_FRESH_CONCURRENT_UPDATES, + cache_type); + } else { + UmaRecordIndexFileState(INDEX_STATE_FRESH, cache_type); + } + UmaRecordIndexInitMethod(INITIALIZE_METHOD_LOADED, cache_type); + return; + } + UmaRecordIndexFileState(INDEX_STATE_STALE, cache_type); } - // Used in histograms. Please only add new values at the end. - enum { - INITIALIZE_METHOD_RECOVERED = 0, - INITIALIZE_METHOD_LOADED = 1, - INITIALIZE_METHOD_NEWCACHE = 2, - INITIALIZE_METHOD_MAX = 3, - }; - int initialize_method; - if (index_file_exists) { - if (out_result->flush_required) - initialize_method = INITIALIZE_METHOD_RECOVERED; - else - initialize_method = INITIALIZE_METHOD_LOADED; + // Reconstruct the index by scanning the disk for entries. + const base::TimeTicks start = base::TimeTicks::Now(); + SyncRestoreFromDisk(cache_directory, index_file_path, out_result); + SIMPLE_CACHE_UMA(MEDIUM_TIMES, "IndexRestoreTime", cache_type, + base::TimeTicks::Now() - start); + SIMPLE_CACHE_UMA(COUNTS, "IndexEntriesRestored", cache_type, + out_result->entries.size()); + if (index_file_existed) { + UmaRecordIndexInitMethod(INITIALIZE_METHOD_RECOVERED, cache_type); } else { + UmaRecordIndexInitMethod(INITIALIZE_METHOD_NEWCACHE, cache_type); SIMPLE_CACHE_UMA(COUNTS, - "IndexCreatedEntryCount", cache_type, entries.size()); - initialize_method = INITIALIZE_METHOD_NEWCACHE; + "IndexCreatedEntryCount", cache_type, + out_result->entries.size()); } - - SIMPLE_CACHE_UMA(ENUMERATION, - "IndexInitializeMethod", cache_type, - initialize_method, INITIALIZE_METHOD_MAX); } // static void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename, + base::Time* out_last_cache_seen_by_index, SimpleIndexLoadResult* out_result) { out_result->Reset(); @@ -321,7 +361,9 @@ void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename, SimpleIndexFile::Deserialize( reinterpret_cast<const char*>(index_file_map.data()), - index_file_map.length(), out_result); + index_file_map.length(), + out_last_cache_seen_by_index, + out_result); if (!out_result->did_load) base::DeleteFile(index_filename, false); @@ -339,14 +381,12 @@ scoped_ptr<Pickle> SimpleIndexFile::Serialize( pickle->WriteUInt64(it->first); it->second.Serialize(pickle.get()); } - SimpleIndexFile::PickleHeader* header_p = - pickle->headerT<SimpleIndexFile::PickleHeader>(); - header_p->crc = CalculatePickleCRC(*pickle); return pickle.Pass(); } // static void SimpleIndexFile::Deserialize(const char* data, int data_len, + base::Time* out_cache_last_modified, SimpleIndexLoadResult* out_result) { DCHECK(data); @@ -360,7 +400,6 @@ void SimpleIndexFile::Deserialize(const char* data, int data_len, } PickleIterator pickle_it(pickle); - SimpleIndexFile::PickleHeader* header_p = pickle.headerT<SimpleIndexFile::PickleHeader>(); const uint32 crc_read = header_p->crc; @@ -398,6 +437,14 @@ void SimpleIndexFile::Deserialize(const char* data, int data_len, SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries); } + int64 cache_last_modified; + if (!pickle_it.ReadInt64(&cache_last_modified)) { + entries->clear(); + return; + } + DCHECK(out_cache_last_modified); + *out_cache_last_modified = base::Time::FromInternalValue(cache_last_modified); + out_result->did_load = true; } @@ -411,10 +458,6 @@ void SimpleIndexFile::SyncRestoreFromDisk( out_result->Reset(); SimpleIndex::EntrySet* entries = &out_result->entries; - // TODO(felipeg,gavinp): Fix this once we have a one-file per entry format. - COMPILE_ASSERT(kSimpleEntryFileCount == 3, - file_pattern_must_match_file_count); - const bool did_succeed = TraverseCacheDirectory( cache_directory, base::Bind(&ProcessEntryFile, entries)); if (!did_succeed) { @@ -428,8 +471,9 @@ void SimpleIndexFile::SyncRestoreFromDisk( } // static -bool SimpleIndexFile::IsIndexFileStale(base::Time cache_last_modified, - const base::FilePath& index_file_path) { +bool SimpleIndexFile::LegacyIsIndexFileStale( + base::Time cache_last_modified, + const base::FilePath& index_file_path) { base::Time index_mtime; if (!simple_util::GetMTime(index_file_path, &index_mtime)) return true; diff --git a/net/disk_cache/simple/simple_index_file.h b/net/disk_cache/simple/simple_index_file.h index 7c7a34e536..b72ea9f749 100644 --- a/net/disk_cache/simple/simple_index_file.h +++ b/net/disk_cache/simple/simple_index_file.h @@ -113,20 +113,29 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { const base::FilePath& index_file_path, SimpleIndexLoadResult* out_result); - // Load the index file from disk returning an EntrySet. Upon failure, returns - // NULL. + // Load the index file from disk returning an EntrySet. static void SyncLoadFromDisk(const base::FilePath& index_filename, + base::Time* out_last_cache_seen_by_index, SimpleIndexLoadResult* out_result); // Returns a scoped_ptr for a newly allocated Pickle containing the serialized - // data to be written to a file. + // data to be written to a file. Note: the pickle is not in a consistent state + // immediately after calling this menthod, one needs to call + // SerializeFinalData to make it ready to write to a file. static scoped_ptr<Pickle> Serialize( const SimpleIndexFile::IndexMetadata& index_metadata, const SimpleIndex::EntrySet& entries); + // Appends cache modification time data to the serialized format. This is + // performed on a thread accessing the disk. It is not combined with the main + // serialization path to avoid extra thread hops or copying the pickle to the + // worker thread. + static bool SerializeFinalData(base::Time cache_modified, Pickle* pickle); + // Given the contents of an index file |data| of length |data_len|, returns // the corresponding EntrySet. Returns NULL on error. static void Deserialize(const char* data, int data_len, + base::Time* out_cache_last_modified, SimpleIndexLoadResult* out_result); // Implemented either in simple_index_file_posix.cc or @@ -138,6 +147,15 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { const base::FilePath& cache_path, const EntryFileCallback& entry_file_callback); + // Writes the index file to disk atomically. + static void SyncWriteToDisk(net::CacheType cache_type, + const base::FilePath& cache_directory, + const base::FilePath& index_filename, + const base::FilePath& temp_index_filename, + scoped_ptr<Pickle> pickle, + const base::TimeTicks& start_time, + bool app_on_background); + // Scan the index directory for entries, returning an EntrySet of all entries // found. static void SyncRestoreFromDisk(const base::FilePath& cache_directory, @@ -145,9 +163,11 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { SimpleIndexLoadResult* out_result); // Determines if an index file is stale relative to the time of last - // modification of the cache directory. - static bool IsIndexFileStale(base::Time cache_last_modified, - const base::FilePath& index_file_path); + // modification of the cache directory. Obsolete, used only for a histogram to + // compare with the new method. + // TODO(pasko): remove this method after getting enough data. + static bool LegacyIsIndexFileStale(base::Time cache_last_modified, + const base::FilePath& index_file_path); struct PickleHeader : public Pickle::Header { uint32 crc; @@ -160,6 +180,10 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { const base::FilePath index_file_; const base::FilePath temp_index_file_; + static const char kIndexDirectory[]; + static const char kIndexFileName[]; + static const char kTempIndexFileName[]; + DISALLOW_COPY_AND_ASSIGN(SimpleIndexFile); }; diff --git a/net/disk_cache/simple/simple_index_file_unittest.cc b/net/disk_cache/simple/simple_index_file_unittest.cc index b75237e8d2..267c6ba593 100644 --- a/net/disk_cache/simple/simple_index_file_unittest.cc +++ b/net/disk_cache/simple/simple_index_file_unittest.cc @@ -13,6 +13,7 @@ #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "net/base/cache_type.h" +#include "net/disk_cache/simple/simple_backend_version.h" #include "net/disk_cache/simple/simple_entry_format.h" #include "net/disk_cache/simple/simple_index.h" #include "net/disk_cache/simple/simple_index_file.h" @@ -25,6 +26,11 @@ using disk_cache::SimpleIndex; namespace disk_cache { +// The Simple Cache backend requires a few guarantees from the filesystem like +// atomic renaming of recently open files. Those guarantees are not provided in +// general on Windows. +#if defined(OS_POSIX) + TEST(IndexMetadataTest, Basics) { SimpleIndexFile::IndexMetadata index_metadata; @@ -58,8 +64,9 @@ TEST(IndexMetadataTest, Serialize) { class WrappedSimpleIndexFile : public SimpleIndexFile { public: using SimpleIndexFile::Deserialize; - using SimpleIndexFile::IsIndexFileStale; + using SimpleIndexFile::LegacyIsIndexFileStale; using SimpleIndexFile::Serialize; + using SimpleIndexFile::SerializeFinalData; explicit WrappedSimpleIndexFile(const base::FilePath& index_file_directory) : SimpleIndexFile(base::MessageLoopProxy::current().get(), @@ -72,6 +79,10 @@ class WrappedSimpleIndexFile : public SimpleIndexFile { const base::FilePath& GetIndexFilePath() const { return index_file_; } + + bool CreateIndexFileDirectory() const { + return file_util::CreateDirectory(index_file_.DirName()); + } }; class SimpleIndexFileTest : public testing::Test { @@ -119,12 +130,16 @@ TEST_F(SimpleIndexFileTest, Serialize) { scoped_ptr<Pickle> pickle = WrappedSimpleIndexFile::Serialize( index_metadata, entries); EXPECT_TRUE(pickle.get() != NULL); - + base::Time now = base::Time::Now(); + EXPECT_TRUE(WrappedSimpleIndexFile::SerializeFinalData(now, pickle.get())); + base::Time when_index_last_saw_cache; SimpleIndexLoadResult deserialize_result; WrappedSimpleIndexFile::Deserialize(static_cast<const char*>(pickle->data()), - pickle->size(), - &deserialize_result); + pickle->size(), + &when_index_last_saw_cache, + &deserialize_result); EXPECT_TRUE(deserialize_result.did_load); + EXPECT_EQ(now, when_index_last_saw_cache); const SimpleIndex::EntrySet& new_entries = deserialize_result.entries; EXPECT_EQ(entries.size(), new_entries.size()); @@ -135,7 +150,7 @@ TEST_F(SimpleIndexFileTest, Serialize) { } } -TEST_F(SimpleIndexFileTest, IsIndexFileStale) { +TEST_F(SimpleIndexFileTest, LegacyIsIndexFileStale) { base::ScopedTempDir cache_dir; ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); base::Time cache_mtime; @@ -143,31 +158,30 @@ TEST_F(SimpleIndexFileTest, IsIndexFileStale) { ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime)); WrappedSimpleIndexFile simple_index_file(cache_path); + ASSERT_TRUE(simple_index_file.CreateIndexFileDirectory()); const base::FilePath& index_path = simple_index_file.GetIndexFilePath(); - EXPECT_TRUE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); + EXPECT_TRUE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); const std::string kDummyData = "nothing to be seen here"; EXPECT_EQ(static_cast<int>(kDummyData.size()), file_util::WriteFile(index_path, kDummyData.data(), kDummyData.size())); ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime)); - EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); + EXPECT_FALSE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); const base::Time past_time = base::Time::Now() - base::TimeDelta::FromSeconds(10); EXPECT_TRUE(file_util::TouchFile(index_path, past_time, past_time)); EXPECT_TRUE(file_util::TouchFile(cache_path, past_time, past_time)); ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime)); - EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); - const base::Time even_older = - past_time - base::TimeDelta::FromSeconds(10); + EXPECT_FALSE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); + const base::Time even_older = past_time - base::TimeDelta::FromSeconds(10); EXPECT_TRUE(file_util::TouchFile(index_path, even_older, even_older)); - EXPECT_TRUE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); - + EXPECT_TRUE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); } TEST_F(SimpleIndexFileTest, WriteThenLoadIndex) { @@ -218,17 +232,17 @@ TEST_F(SimpleIndexFileTest, LoadCorruptIndex) { ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); WrappedSimpleIndexFile simple_index_file(cache_dir.path()); + ASSERT_TRUE(simple_index_file.CreateIndexFileDirectory()); const base::FilePath& index_path = simple_index_file.GetIndexFilePath(); const std::string kDummyData = "nothing to be seen here"; - EXPECT_EQ(static_cast<int>(kDummyData.size()), - file_util::WriteFile(index_path, - kDummyData.data(), - kDummyData.size())); + EXPECT_EQ( + implicit_cast<int>(kDummyData.size()), + file_util::WriteFile(index_path, kDummyData.data(), kDummyData.size())); base::Time fake_cache_mtime; ASSERT_TRUE(simple_util::GetMTime(simple_index_file.GetIndexFilePath(), &fake_cache_mtime)); - EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(fake_cache_mtime, - index_path)); + EXPECT_FALSE(WrappedSimpleIndexFile::LegacyIsIndexFileStale(fake_cache_mtime, + index_path)); SimpleIndexLoadResult load_index_result; simple_index_file.LoadIndexEntries(fake_cache_mtime, @@ -242,4 +256,6 @@ TEST_F(SimpleIndexFileTest, LoadCorruptIndex) { EXPECT_TRUE(load_index_result.flush_required); } +#endif // defined(OS_POSIX) + } // namespace disk_cache diff --git a/net/disk_cache/simple/simple_synchronous_entry.cc b/net/disk_cache/simple/simple_synchronous_entry.cc index a1ff1ac6ad..3b1f7a86ab 100644 --- a/net/disk_cache/simple/simple_synchronous_entry.cc +++ b/net/disk_cache/simple/simple_synchronous_entry.cc @@ -18,6 +18,7 @@ #include "base/strings/stringprintf.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" +#include "net/disk_cache/simple/simple_backend_version.h" #include "net/disk_cache/simple/simple_histogram_macros.h" #include "net/disk_cache/simple/simple_util.h" #include "third_party/zlib/zlib.h" @@ -141,27 +142,55 @@ void RecordCloseResult(net::CacheType cache_type, CloseResult result) { namespace disk_cache { -using simple_util::ConvertEntryHashKeyToHexString; using simple_util::GetEntryHashKey; -using simple_util::GetFilenameFromEntryHashAndIndex; +using simple_util::GetFilenameFromEntryHashAndFileIndex; using simple_util::GetDataSizeFromKeyAndFileSize; using simple_util::GetFileSizeFromKeyAndDataSize; -using simple_util::GetFileOffsetFromKeyAndDataOffset; +using simple_util::GetFileIndexFromStreamIndex; + +SimpleEntryStat::SimpleEntryStat(base::Time last_used, + base::Time last_modified, + const int32 data_size[]) + : last_used_(last_used), + last_modified_(last_modified) { + memcpy(data_size_, data_size, sizeof(data_size_)); +} -SimpleEntryStat::SimpleEntryStat() {} +int SimpleEntryStat::GetOffsetInFile(const std::string& key, + int offset, + int stream_index) const { + const int64 headers_size = sizeof(SimpleFileHeader) + key.size(); + const int64 additional_offset = + stream_index == 0 ? data_size_[1] + sizeof(SimpleFileEOF) : 0; + return headers_size + offset + additional_offset; +} -SimpleEntryStat::SimpleEntryStat(base::Time last_used_p, - base::Time last_modified_p, - const int32 data_size_p[]) - : last_used(last_used_p), - last_modified(last_modified_p) { - memcpy(data_size, data_size_p, sizeof(data_size)); +int SimpleEntryStat::GetEOFOffsetInFile(const std::string& key, + int stream_index) const { + return GetOffsetInFile(key, data_size_[stream_index], stream_index); +} + +int SimpleEntryStat::GetLastEOFOffsetInFile(const std::string& key, + int stream_index) const { + const int file_index = GetFileIndexFromStreamIndex(stream_index); + const int eof_data_offset = + file_index == 0 ? data_size_[0] + data_size_[1] + sizeof(SimpleFileEOF) + : data_size_[2]; + return GetOffsetInFile(key, eof_data_offset, stream_index); +} + +int SimpleEntryStat::GetFileSize(const std::string& key, int file_index) const { + const int total_data_size = + file_index == 0 ? data_size_[0] + data_size_[1] + sizeof(SimpleFileEOF) + : data_size_[2]; + return GetFileSizeFromKeyAndDataSize(key, total_data_size); } SimpleEntryCreationResults::SimpleEntryCreationResults( SimpleEntryStat entry_stat) : sync_entry(NULL), entry_stat(entry_stat), + stream_0_crc32(crc32(0, Z_NULL, 0)), result(net::OK) { } @@ -205,12 +234,16 @@ void SimpleSynchronousEntry::OpenEntry( SimpleEntryCreationResults *out_results) { SimpleSynchronousEntry* sync_entry = new SimpleSynchronousEntry(cache_type, path, "", entry_hash); - out_results->result = sync_entry->InitializeForOpen( - had_index, &out_results->entry_stat); + out_results->result = + sync_entry->InitializeForOpen(had_index, + &out_results->entry_stat, + &out_results->stream_0_data, + &out_results->stream_0_crc32); if (out_results->result != net::OK) { sync_entry->Doom(); delete sync_entry; out_results->sync_entry = NULL; + out_results->stream_0_data = NULL; return; } out_results->sync_entry = sync_entry; @@ -246,8 +279,8 @@ bool SimpleSynchronousEntry::DeleteFilesForEntryHash( const uint64 entry_hash) { bool result = true; for (int i = 0; i < kSimpleEntryFileCount; ++i) { - FilePath to_delete = path.AppendASCII( - GetFilenameFromEntryHashAndIndex(entry_hash, i)); + FilePath to_delete = + path.AppendASCII(GetFilenameFromEntryHashAndFileIndex(entry_hash, i)); if (!base::DeleteFile(to_delete, false)) { result = false; DLOG(ERROR) << "Could not delete " << to_delete.MaybeAsASCII(); @@ -279,17 +312,17 @@ int SimpleSynchronousEntry::DoomEntrySet( void SimpleSynchronousEntry::ReadData(const EntryOperationData& in_entry_op, net::IOBuffer* out_buf, uint32* out_crc32, - base::Time* out_last_used, + SimpleEntryStat* entry_stat, int* out_result) const { DCHECK(initialized_); - int64 file_offset = - GetFileOffsetFromKeyAndDataOffset(key_, in_entry_op.offset); - int bytes_read = ReadPlatformFile(files_[in_entry_op.index], - file_offset, - out_buf->data(), - in_entry_op.buf_len); + DCHECK_NE(0, in_entry_op.index); + const int64 file_offset = + entry_stat->GetOffsetInFile(key_, in_entry_op.offset, in_entry_op.index); + int file_index = GetFileIndexFromStreamIndex(in_entry_op.index); + int bytes_read = ReadPlatformFile( + files_[file_index], file_offset, out_buf->data(), in_entry_op.buf_len); if (bytes_read > 0) { - *out_last_used = Time::Now(); + entry_stat->set_last_used(Time::Now()); *out_crc32 = crc32(crc32(0L, Z_NULL, 0), reinterpret_cast<const Bytef*>(out_buf->data()), bytes_read); @@ -307,27 +340,30 @@ void SimpleSynchronousEntry::WriteData(const EntryOperationData& in_entry_op, SimpleEntryStat* out_entry_stat, int* out_result) const { DCHECK(initialized_); + DCHECK_NE(0, in_entry_op.index); int index = in_entry_op.index; + int file_index = GetFileIndexFromStreamIndex(index); int offset = in_entry_op.offset; int buf_len = in_entry_op.buf_len; int truncate = in_entry_op.truncate; - - bool extending_by_write = offset + buf_len > out_entry_stat->data_size[index]; + const int64 file_offset = out_entry_stat->GetOffsetInFile( + key_, in_entry_op.offset, in_entry_op.index); + bool extending_by_write = offset + buf_len > out_entry_stat->data_size(index); if (extending_by_write) { - // We are extending the file, and need to insure the EOF record is zeroed. - const int64 file_eof_offset = GetFileOffsetFromKeyAndDataOffset( - key_, out_entry_stat->data_size[index]); - if (!TruncatePlatformFile(files_[index], file_eof_offset)) { + // The EOF record and the eventual stream afterward need to be zeroed out. + const int64 file_eof_offset = + out_entry_stat->GetEOFOffsetInFile(key_, index); + if (!TruncatePlatformFile(files_[file_index], file_eof_offset)) { RecordWriteResult(cache_type_, WRITE_RESULT_PRETRUNCATE_FAILURE); Doom(); *out_result = net::ERR_CACHE_WRITE_FAILURE; return; } } - const int64 file_offset = GetFileOffsetFromKeyAndDataOffset(key_, offset); if (buf_len > 0) { if (WritePlatformFile( - files_[index], file_offset, in_buf->data(), buf_len) != buf_len) { + files_[file_index], file_offset, in_buf->data(), buf_len) != + buf_len) { RecordWriteResult(cache_type_, WRITE_RESULT_WRITE_FAILURE); Doom(); *out_result = net::ERR_CACHE_WRITE_FAILURE; @@ -335,79 +371,89 @@ void SimpleSynchronousEntry::WriteData(const EntryOperationData& in_entry_op, } } if (!truncate && (buf_len > 0 || !extending_by_write)) { - out_entry_stat->data_size[index] = - std::max(out_entry_stat->data_size[index], offset + buf_len); + out_entry_stat->set_data_size( + index, std::max(out_entry_stat->data_size(index), offset + buf_len)); } else { - if (!TruncatePlatformFile(files_[index], file_offset + buf_len)) { + out_entry_stat->set_data_size(index, offset + buf_len); + int file_eof_offset = out_entry_stat->GetLastEOFOffsetInFile(key_, index); + if (!TruncatePlatformFile(files_[file_index], file_eof_offset)) { RecordWriteResult(cache_type_, WRITE_RESULT_TRUNCATE_FAILURE); Doom(); *out_result = net::ERR_CACHE_WRITE_FAILURE; return; } - out_entry_stat->data_size[index] = offset + buf_len; } RecordWriteResult(cache_type_, WRITE_RESULT_SUCCESS); - out_entry_stat->last_used = out_entry_stat->last_modified = Time::Now(); + base::Time modification_time = Time::Now(); + out_entry_stat->set_last_used(modification_time); + out_entry_stat->set_last_modified(modification_time); *out_result = buf_len; } void SimpleSynchronousEntry::CheckEOFRecord(int index, - int32 data_size, + const SimpleEntryStat& entry_stat, uint32 expected_crc32, int* out_result) const { DCHECK(initialized_); - - SimpleFileEOF eof_record; - int64 file_offset = GetFileOffsetFromKeyAndDataOffset(key_, data_size); - if (ReadPlatformFile(files_[index], - file_offset, - reinterpret_cast<char*>(&eof_record), - sizeof(eof_record)) != sizeof(eof_record)) { - RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_READ_FAILURE); - Doom(); - *out_result = net::ERR_CACHE_CHECKSUM_READ_FAILURE; - return; - } - - if (eof_record.final_magic_number != kSimpleFinalMagicNumber) { - RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH); - DLOG(INFO) << "eof record had bad magic number."; + uint32 crc32; + bool has_crc32; + int stream_size; + *out_result = + GetEOFRecordData(index, entry_stat, &has_crc32, &crc32, &stream_size); + if (*out_result != net::OK) { Doom(); - *out_result = net::ERR_CACHE_CHECKSUM_READ_FAILURE; return; } - - const bool has_crc = (eof_record.flags & SimpleFileEOF::FLAG_HAS_CRC32) == - SimpleFileEOF::FLAG_HAS_CRC32; - SIMPLE_CACHE_UMA(BOOLEAN, "SyncCheckEOFHasCrc", cache_type_, has_crc); - if (has_crc && eof_record.data_crc32 != expected_crc32) { + if (has_crc32 && crc32 != expected_crc32) { + DLOG(INFO) << "EOF record had bad crc."; + *out_result = net::ERR_CACHE_CHECKSUM_MISMATCH; RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_CRC_MISMATCH); - DLOG(INFO) << "eof record had bad crc."; Doom(); - *out_result = net::ERR_CACHE_CHECKSUM_MISMATCH; return; } - RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_SUCCESS); - *out_result = net::OK; } void SimpleSynchronousEntry::Close( const SimpleEntryStat& entry_stat, - scoped_ptr<std::vector<CRCRecord> > crc32s_to_write) { + scoped_ptr<std::vector<CRCRecord> > crc32s_to_write, + net::GrowableIOBuffer* stream_0_data) { + DCHECK(stream_0_data); + // Write stream 0 data. + int stream_0_offset = entry_stat.GetOffsetInFile(key_, 0, 0); + if (WritePlatformFile(files_[0], + stream_0_offset, + stream_0_data->data(), + entry_stat.data_size(0)) != entry_stat.data_size(0)) { + RecordCloseResult(cache_type_, CLOSE_RESULT_WRITE_FAILURE); + DLOG(INFO) << "Could not write stream 0 data."; + Doom(); + } + for (std::vector<CRCRecord>::const_iterator it = crc32s_to_write->begin(); it != crc32s_to_write->end(); ++it) { SimpleFileEOF eof_record; + int index = it->index; + eof_record.stream_size = entry_stat.data_size(index); eof_record.final_magic_number = kSimpleFinalMagicNumber; eof_record.flags = 0; if (it->has_crc32) eof_record.flags |= SimpleFileEOF::FLAG_HAS_CRC32; eof_record.data_crc32 = it->data_crc32; - int64 file_offset = GetFileOffsetFromKeyAndDataOffset( - key_, entry_stat.data_size[it->index]); - if (WritePlatformFile(files_[it->index], - file_offset, + int file_index = GetFileIndexFromStreamIndex(index); + int eof_offset = entry_stat.GetEOFOffsetInFile(key_, index); + // If stream 0 changed size, the file needs to be resized, otherwise the + // next open will yield wrong stream sizes. On stream 1 and stream 2 proper + // resizing of the file is handled in SimpleSynchronousEntry::WriteData(). + if (index == 0 && !TruncatePlatformFile(files_[file_index], eof_offset)) { + RecordCloseResult(cache_type_, CLOSE_RESULT_WRITE_FAILURE); + DLOG(INFO) << "Could not truncate stream 0 file."; + Doom(); + break; + } + if (WritePlatformFile(files_[file_index], + eof_offset, reinterpret_cast<const char*>(&eof_record), sizeof(eof_record)) != sizeof(eof_record)) { RecordCloseResult(cache_type_, CLOSE_RESULT_WRITE_FAILURE); @@ -415,7 +461,11 @@ void SimpleSynchronousEntry::Close( Doom(); break; } - const int64 file_size = file_offset + sizeof(eof_record); + } + for (int i = 0; i < kSimpleEntryFileCount; ++i) { + bool did_close_file = ClosePlatformFile(files_[i]); + DCHECK(did_close_file); + const int64 file_size = entry_stat.GetFileSize(key_, i); SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "LastClusterSize", cache_type_, file_size % 4096, 0, 4097, 50); @@ -424,11 +474,6 @@ void SimpleSynchronousEntry::Close( "LastClusterLossPercent", cache_type_, cluster_loss * 100 / (cluster_loss + file_size)); } - - for (int i = 0; i < kSimpleEntryFileCount; ++i) { - bool did_close_file = ClosePlatformFile(files_[i]); - CHECK(did_close_file); - } RecordCloseResult(cache_type_, CLOSE_RESULT_SUCCESS); have_open_files_ = false; delete this; @@ -460,8 +505,8 @@ bool SimpleSynchronousEntry::OpenOrCreateFiles( bool had_index, SimpleEntryStat* out_entry_stat) { for (int i = 0; i < kSimpleEntryFileCount; ++i) { - FilePath filename = path_.AppendASCII( - GetFilenameFromEntryHashAndIndex(entry_hash_, i)); + FilePath filename = + path_.AppendASCII(GetFilenameFromEntryHashAndFileIndex(entry_hash_, i)); int flags = PLATFORM_FILE_READ | PLATFORM_FILE_WRITE; if (create) flags |= PLATFORM_FILE_CREATE; @@ -516,9 +561,11 @@ bool SimpleSynchronousEntry::OpenOrCreateFiles( have_open_files_ = true; if (create) { - out_entry_stat->last_modified = out_entry_stat->last_used = Time::Now(); - for (int i = 0; i < kSimpleEntryFileCount; ++i) - out_entry_stat->data_size[i] = 0; + base::Time creation_time = Time::Now(); + out_entry_stat->set_last_modified(creation_time); + out_entry_stat->set_last_used(creation_time); + for (int i = 0; i < kSimpleEntryStreamCount; ++i) + out_entry_stat->set_data_size(i, 0); } else { base::TimeDelta entry_age = base::Time::Now() - base::Time::UnixEpoch(); for (int i = 0; i < kSimpleEntryFileCount; ++i) { @@ -529,20 +576,31 @@ bool SimpleSynchronousEntry::OpenOrCreateFiles( DLOG(WARNING) << "Could not get platform file info."; continue; } - out_entry_stat->last_used = file_info.last_accessed; + out_entry_stat->set_last_used(file_info.last_accessed); if (simple_util::GetMTime(path_, &file_last_modified)) - out_entry_stat->last_modified = file_last_modified; + out_entry_stat->set_last_modified(file_last_modified); else - out_entry_stat->last_modified = file_info.last_modified; + out_entry_stat->set_last_modified(file_info.last_modified); base::TimeDelta stream_age = - base::Time::Now() - out_entry_stat->last_modified; + base::Time::Now() - out_entry_stat->last_modified(); if (stream_age < entry_age) entry_age = stream_age; - // Keep the file size in |data size_| briefly until the key is initialized - // properly. - out_entry_stat->data_size[i] = file_info.size; + // Two things prevent from knowing the right values for |data_size|: + // 1) The key is not known, hence its length is unknown. + // 2) Stream 0 and stream 1 are in the same file, and the exact size for + // each will only be known when reading the EOF record for stream 0. + // + // The size for file 0 and 1 is temporarily kept in + // |data_size(1)| and |data_size(2)| respectively. Reading the key in + // InitializeForOpen yields the data size for each file. In the case of + // file hash_1, this is the total size of stream 2, and is assigned to + // data_size(2). In the case of file 0, it is the combined size of stream + // 0, stream 1 and one EOF record. The exact distribution of sizes between + // stream 1 and stream 0 is only determined after reading the EOF record + // for stream 0 in ReadAndValidateStream0. + out_entry_stat->set_data_size(i + 1, file_info.size); } SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "SyncOpenEntryAge", cache_type_, @@ -560,12 +618,14 @@ void SimpleSynchronousEntry::CloseFiles() { } } -int SimpleSynchronousEntry::InitializeForOpen(bool had_index, - SimpleEntryStat* out_entry_stat) { +int SimpleSynchronousEntry::InitializeForOpen( + bool had_index, + SimpleEntryStat* out_entry_stat, + scoped_refptr<net::GrowableIOBuffer>* stream_0_data, + uint32* out_stream_0_crc32) { DCHECK(!initialized_); if (!OpenOrCreateFiles(false, had_index, out_entry_stat)) return net::ERR_FAILED; - for (int i = 0; i < kSimpleEntryFileCount; ++i) { SimpleFileHeader header; int header_read_result = @@ -586,7 +646,7 @@ int SimpleSynchronousEntry::InitializeForOpen(bool had_index, return net::ERR_FAILED; } - if (header.version != kSimpleVersion) { + if (header.version != kSimpleEntryVersionOnDisk) { DLOG(WARNING) << "Unreadable version."; RecordSyncOpenResult(cache_type_, OPEN_ENTRY_BAD_VERSION, had_index); return net::ERR_FAILED; @@ -602,12 +662,19 @@ int SimpleSynchronousEntry::InitializeForOpen(bool had_index, } key_ = std::string(key.get(), header.key_length); - out_entry_stat->data_size[i] = - GetDataSizeFromKeyAndFileSize(key_, out_entry_stat->data_size[i]); - if (out_entry_stat->data_size[i] < 0) { - // This entry can't possibly be valid, as it does not have enough space to - // store a valid SimpleFileEOF record. - return net::ERR_FAILED; + if (i == 0) { + // File size for stream 0 has been stored temporarily in data_size[1]. + int total_data_size = + GetDataSizeFromKeyAndFileSize(key_, out_entry_stat->data_size(1)); + int ret_value_stream_0 = ReadAndValidateStream0( + total_data_size, out_entry_stat, stream_0_data, out_stream_0_crc32); + if (ret_value_stream_0 != net::OK) + return ret_value_stream_0; + } else { + out_entry_stat->set_data_size( + 2, GetDataSizeFromKeyAndFileSize(key_, out_entry_stat->data_size(2))); + if (out_entry_stat->data_size(2) < 0) + return net::ERR_FAILED; } if (base::Hash(key.get(), header.key_length) != header.key_hash) { @@ -633,21 +700,23 @@ int SimpleSynchronousEntry::InitializeForCreate( for (int i = 0; i < kSimpleEntryFileCount; ++i) { SimpleFileHeader header; header.initial_magic_number = kSimpleInitialMagicNumber; - header.version = kSimpleVersion; + header.version = kSimpleEntryVersionOnDisk; header.key_length = key_.size(); header.key_hash = base::Hash(key_); - if (WritePlatformFile(files_[i], 0, reinterpret_cast<char*>(&header), - sizeof(header)) != sizeof(header)) { - DLOG(WARNING) << "Could not write headers to new cache entry."; + if (WritePlatformFile( + files_[i], 0, reinterpret_cast<char*>(&header), sizeof(header)) != + sizeof(header)) { + DLOG(WARNING) << "Could not write cache file header to cache entry."; RecordSyncCreateResult( cache_type_, CREATE_ENTRY_CANT_WRITE_HEADER, had_index); return net::ERR_FAILED; } - if (WritePlatformFile(files_[i], sizeof(header), key_.data(), - key_.size()) != implicit_cast<int>(key_.size())) { + if (WritePlatformFile( + files_[i], sizeof(SimpleFileHeader), key_.data(), key_.size()) != + implicit_cast<int>(key_.size())) { DLOG(WARNING) << "Could not write keys to new cache entry."; RecordSyncCreateResult( cache_type_, CREATE_ENTRY_CANT_WRITE_KEY, had_index); @@ -659,6 +728,88 @@ int SimpleSynchronousEntry::InitializeForCreate( return net::OK; } +int SimpleSynchronousEntry::ReadAndValidateStream0( + int total_data_size, + SimpleEntryStat* out_entry_stat, + scoped_refptr<net::GrowableIOBuffer>* stream_0_data, + uint32* out_stream_0_crc32) const { + // Temporarily assign all the data size to stream 1 in order to read the + // EOF record for stream 0, which contains the size of stream 0. + out_entry_stat->set_data_size(0, 0); + out_entry_stat->set_data_size(1, total_data_size - sizeof(SimpleFileEOF)); + + bool has_crc32; + uint32 read_crc32; + int stream_0_size; + int ret_value_crc32 = GetEOFRecordData( + 0, *out_entry_stat, &has_crc32, &read_crc32, &stream_0_size); + if (ret_value_crc32 != net::OK) + return ret_value_crc32; + + if (stream_0_size > out_entry_stat->data_size(1)) + return net::ERR_FAILED; + + // These are the real values of data size. + out_entry_stat->set_data_size(0, stream_0_size); + out_entry_stat->set_data_size( + 1, out_entry_stat->data_size(1) - stream_0_size); + + // Put stream 0 data in memory. + *stream_0_data = new net::GrowableIOBuffer(); + (*stream_0_data)->SetCapacity(stream_0_size); + int file_offset = out_entry_stat->GetOffsetInFile(key_, 0, 0); + int bytes_read = ReadPlatformFile( + files_[0], file_offset, (*stream_0_data)->data(), stream_0_size); + if (bytes_read != stream_0_size) + return net::ERR_FAILED; + + // Check the CRC32. + uint32 expected_crc32 = + stream_0_size == 0 + ? crc32(0, Z_NULL, 0) + : crc32(crc32(0, Z_NULL, 0), + reinterpret_cast<const Bytef*>((*stream_0_data)->data()), + stream_0_size); + if (has_crc32 && read_crc32 != expected_crc32) { + DLOG(INFO) << "EOF record had bad crc."; + RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_CRC_MISMATCH); + return net::ERR_FAILED; + } + *out_stream_0_crc32 = expected_crc32; + RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_SUCCESS); + return net::OK; +} + +int SimpleSynchronousEntry::GetEOFRecordData(int index, + const SimpleEntryStat& entry_stat, + bool* out_has_crc32, + uint32* out_crc32, + int* out_data_size) const { + SimpleFileEOF eof_record; + int file_offset = entry_stat.GetEOFOffsetInFile(key_, index); + int file_index = GetFileIndexFromStreamIndex(index); + if (ReadPlatformFile(files_[file_index], + file_offset, + reinterpret_cast<char*>(&eof_record), + sizeof(eof_record)) != sizeof(eof_record)) { + RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_READ_FAILURE); + return net::ERR_CACHE_CHECKSUM_READ_FAILURE; + } + + if (eof_record.final_magic_number != kSimpleFinalMagicNumber) { + RecordCheckEOFResult(cache_type_, CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH); + DLOG(INFO) << "EOF record had bad magic number."; + return net::ERR_CACHE_CHECKSUM_READ_FAILURE; + } + + *out_has_crc32 = (eof_record.flags & SimpleFileEOF::FLAG_HAS_CRC32) == + SimpleFileEOF::FLAG_HAS_CRC32; + *out_crc32 = eof_record.data_crc32; + *out_data_size = eof_record.stream_size; + SIMPLE_CACHE_UMA(BOOLEAN, "SyncCheckEOFHasCrc", cache_type_, *out_has_crc32); + return net::OK; +} + void SimpleSynchronousEntry::Doom() const { // TODO(gavinp): Consider if we should guard against redundant Doom() calls. DeleteFilesForEntryHash(path_, entry_hash_); diff --git a/net/disk_cache/simple/simple_synchronous_entry.h b/net/disk_cache/simple/simple_synchronous_entry.h index f4b5ed8102..ae270c31c1 100644 --- a/net/disk_cache/simple/simple_synchronous_entry.h +++ b/net/disk_cache/simple/simple_synchronous_entry.h @@ -11,13 +11,16 @@ #include <vector> #include "base/files/file_path.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/platform_file.h" #include "base/time/time.h" #include "net/base/cache_type.h" +#include "net/base/net_export.h" #include "net/disk_cache/simple/simple_entry_format.h" namespace net { +class GrowableIOBuffer; class IOBuffer; } @@ -25,15 +28,38 @@ namespace disk_cache { class SimpleSynchronousEntry; -struct SimpleEntryStat { - SimpleEntryStat(); - SimpleEntryStat(base::Time last_used_p, - base::Time last_modified_p, - const int32 data_size_p[]); +// This class handles the passing of data about the entry between +// SimpleEntryImplementation and SimpleSynchronousEntry and the computation of +// file offsets based on the data size for all streams. +class NET_EXPORT_PRIVATE SimpleEntryStat { + public: + SimpleEntryStat(base::Time last_used, + base::Time last_modified, + const int32 data_size[]); + + int GetOffsetInFile(const std::string& key, + int offset, + int stream_index) const; + int GetEOFOffsetInFile(const std::string& key, int stream_index) const; + int GetLastEOFOffsetInFile(const std::string& key, int file_index) const; + int GetFileSize(const std::string& key, int file_index) const; + + base::Time last_used() const { return last_used_; } + base::Time last_modified() const { return last_modified_; } + void set_last_used(base::Time last_used) { last_used_ = last_used; } + void set_last_modified(base::Time last_modified) { + last_modified_ = last_modified; + } + + int32 data_size(int stream_index) const { return data_size_[stream_index]; } + void set_data_size(int stream_index, int data_size) { + data_size_[stream_index] = data_size; + } - base::Time last_used; - base::Time last_modified; - int32 data_size[kSimpleEntryFileCount]; + private: + base::Time last_used_; + base::Time last_modified_; + int32 data_size_[kSimpleEntryStreamCount]; }; struct SimpleEntryCreationResults { @@ -41,7 +67,9 @@ struct SimpleEntryCreationResults { ~SimpleEntryCreationResults(); SimpleSynchronousEntry* sync_entry; + scoped_refptr<net::GrowableIOBuffer> stream_0_data; SimpleEntryStat entry_stat; + uint32 stream_0_crc32; int result; }; @@ -102,21 +130,22 @@ class SimpleSynchronousEntry { void ReadData(const EntryOperationData& in_entry_op, net::IOBuffer* out_buf, uint32* out_crc32, - base::Time* out_last_used, + SimpleEntryStat* entry_stat, int* out_result) const; void WriteData(const EntryOperationData& in_entry_op, net::IOBuffer* in_buf, SimpleEntryStat* out_entry_stat, int* out_result) const; void CheckEOFRecord(int index, - int data_size, + const SimpleEntryStat& entry_stat, uint32 expected_crc32, int* out_result) const; // Close all streams, and add write EOF records to streams indicated by the // CRCRecord entries in |crc32s_to_write|. void Close(const SimpleEntryStat& entry_stat, - scoped_ptr<std::vector<CRCRecord> > crc32s_to_write); + scoped_ptr<std::vector<CRCRecord> > crc32s_to_write, + net::GrowableIOBuffer* stream_0_data); const base::FilePath& path() const { return path_; } std::string key() const { return key_; } @@ -140,7 +169,10 @@ class SimpleSynchronousEntry { // Returns a net error, i.e. net::OK on success. |had_index| is passed // from the main entry for metrics purposes, and is true if the index was // initialized when the open operation began. - int InitializeForOpen(bool had_index, SimpleEntryStat* out_entry_stat); + int InitializeForOpen(bool had_index, + SimpleEntryStat* out_entry_stat, + scoped_refptr<net::GrowableIOBuffer>* stream_0_data, + uint32* out_stream_0_crc32); // Returns a net error, including net::OK on success and net::FILE_EXISTS // when the entry already exists. |had_index| is passed from the main entry @@ -148,6 +180,19 @@ class SimpleSynchronousEntry { // create operation began. int InitializeForCreate(bool had_index, SimpleEntryStat* out_entry_stat); + // Allocates and fills a buffer with stream 0 data in |stream_0_data|, then + // checks its crc32. + int ReadAndValidateStream0( + int total_data_size, + SimpleEntryStat* out_entry_stat, + scoped_refptr<net::GrowableIOBuffer>* stream_0_data, + uint32* out_stream_0_crc32) const; + + int GetEOFRecordData(int index, + const SimpleEntryStat& entry_stat, + bool* out_has_crc32, + uint32* out_crc32, + int* out_data_size) const; void Doom() const; static bool DeleteFilesForEntryHash(const base::FilePath& path, diff --git a/net/disk_cache/simple/simple_test_util.cc b/net/disk_cache/simple/simple_test_util.cc index 9f09974c3b..9a27558823 100644 --- a/net/disk_cache/simple/simple_test_util.cc +++ b/net/disk_cache/simple/simple_test_util.cc @@ -13,7 +13,7 @@ namespace simple_util { bool CreateCorruptFileForTests(const std::string& key, const base::FilePath& cache_path) { base::FilePath entry_file_path = cache_path.AppendASCII( - disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0)); + disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0)); int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE; base::PlatformFile entry_file = base::CreatePlatformFile(entry_file_path, flags, NULL, NULL); diff --git a/net/disk_cache/simple/simple_util.cc b/net/disk_cache/simple/simple_util.cc index 4a89f9b5eb..4291b1f777 100644 --- a/net/disk_cache/simple/simple_util.cc +++ b/net/disk_cache/simple/simple_util.cc @@ -77,13 +77,15 @@ uint64 GetEntryHashKey(const std::string& key) { return u.key_hash; } -std::string GetFilenameFromEntryHashAndIndex(uint64 entry_hash, - int index) { - return base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, index); +std::string GetFilenameFromEntryHashAndFileIndex(uint64 entry_hash, + int file_index) { + return base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, file_index); } -std::string GetFilenameFromKeyAndIndex(const std::string& key, int index) { - return GetEntryHashKeyAsHexString(key) + base::StringPrintf("_%1d", index); +std::string GetFilenameFromKeyAndFileIndex(const std::string& key, + int file_index) { + return GetEntryHashKeyAsHexString(key) + + base::StringPrintf("_%1d", file_index); } int32 GetDataSizeFromKeyAndFileSize(const std::string& key, int64 file_size) { @@ -98,10 +100,8 @@ int64 GetFileSizeFromKeyAndDataSize(const std::string& key, int32 data_size) { sizeof(SimpleFileEOF); } -int64 GetFileOffsetFromKeyAndDataOffset(const std::string& key, - int data_offset) { - const int64 headers_size = sizeof(disk_cache::SimpleFileHeader) + key.size(); - return headers_size + data_offset; +int GetFileIndexFromStreamIndex(int stream_index) { + return (stream_index == 2) ? 1 : 0; } // TODO(clamy, gavinp): this should go in base diff --git a/net/disk_cache/simple/simple_util.h b/net/disk_cache/simple/simple_util.h index ef51869581..60a237ecd8 100644 --- a/net/disk_cache/simple/simple_util.h +++ b/net/disk_cache/simple/simple_util.h @@ -40,12 +40,13 @@ NET_EXPORT_PRIVATE bool GetEntryHashKeyFromHexString( // Given a |key| for a (potential) entry in the simple backend and the |index| // of a stream on that entry, returns the filename in which that stream would be // stored. -NET_EXPORT_PRIVATE std::string GetFilenameFromKeyAndIndex( +NET_EXPORT_PRIVATE std::string GetFilenameFromKeyAndFileIndex( const std::string& key, - int index); + int file_index); // Same as |GetFilenameFromKeyAndIndex| above, but using a hex string. -std::string GetFilenameFromEntryHashAndIndex(uint64 entry_hash, int index); +std::string GetFilenameFromEntryHashAndFileIndex(uint64 entry_hash, + int file_index); // Given the size of a file holding a stream in the simple backend and the key // to an entry, returns the number of bytes in the stream. @@ -57,11 +58,9 @@ NET_EXPORT_PRIVATE int32 GetDataSizeFromKeyAndFileSize(const std::string& key, NET_EXPORT_PRIVATE int64 GetFileSizeFromKeyAndDataSize(const std::string& key, int32 data_size); -// Given the key to an entry, and an offset into a stream on that entry, returns -// the file offset corresponding to |data_offset|. -NET_EXPORT_PRIVATE int64 GetFileOffsetFromKeyAndDataOffset( - const std::string& key, - int data_offset); +// Given the stream index, returns the number of the file the stream is stored +// in. +NET_EXPORT_PRIVATE int GetFileIndexFromStreamIndex(int stream_index); // Fills |out_time| with the time the file last modified time. Unlike the // functions in platform_file.h, the time resolution is milliseconds. diff --git a/net/disk_cache/simple/simple_version_upgrade.cc b/net/disk_cache/simple/simple_version_upgrade.cc new file mode 100644 index 0000000000..dfc6ef4394 --- /dev/null +++ b/net/disk_cache/simple/simple_version_upgrade.cc @@ -0,0 +1,203 @@ +// Copyright 2013 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/disk_cache/simple/simple_version_upgrade.h" + +#include <cstring> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "net/disk_cache/simple/simple_backend_version.h" +#include "net/disk_cache/simple/simple_entry_format_history.h" +#include "third_party/zlib/zlib.h" + +namespace { + +// It is not possible to upgrade cache structures on disk that are of version +// below this, the entire cache should be dropped for them. +const uint32 kMinVersionAbleToUpgrade = 5; + +const char kFakeIndexFileName[] = "index"; +const char kIndexFileName[] = "the-real-index"; + +void LogMessageFailedUpgradeFromVersion(int version) { + LOG(ERROR) << "Failed to upgrade Simple Cache from version: " << version; +} + +bool WriteFakeIndexFile(const base::FilePath& file_name) { + base::PlatformFileError error; + base::PlatformFile file = base::CreatePlatformFile( + file_name, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + NULL, + &error); + disk_cache::FakeIndexData file_contents; + file_contents.initial_magic_number = + disk_cache::simplecache_v5::kSimpleInitialMagicNumber; + file_contents.version = disk_cache::kSimpleVersion; + int bytes_written = base::WritePlatformFile( + file, 0, reinterpret_cast<char*>(&file_contents), sizeof(file_contents)); + if (!base::ClosePlatformFile(file) || + bytes_written != sizeof(file_contents)) { + LOG(ERROR) << "Failed to write fake index file: " + << file_name.LossyDisplayName(); + return false; + } + return true; +} + +} // namespace + +namespace disk_cache { + +FakeIndexData::FakeIndexData() { + // Make hashing repeatable: leave no padding bytes untouched. + std::memset(this, 0, sizeof(*this)); +} + +// Migrates the cache directory from version 4 to version 5. +// Returns true iff it succeeds. +// +// The V5 and V6 caches differ in the name of the index file (it moved to a +// subdirectory) and in the file format (directory last-modified time observed +// by the index writer has gotten appended to the pickled format). +// +// To keep complexity small this specific upgrade code *deletes* the old index +// file. The directory for the new index file has to be created lazily anyway, +// so it is not done in the upgrader. +// +// Below is the detailed description of index file format differences. It is for +// reference purposes. This documentation would be useful to move closer to the +// next index upgrader when the latter gets introduced. +// +// Path: +// V5: $cachedir/the-real-index +// V6: $cachedir/index-dir/the-real-index +// +// Pickled file format: +// Both formats extend Pickle::Header by 32bit value of the CRC-32 of the +// pickled data. +// <v5-index> ::= <v5-index-metadata> <entry-info>* +// <v5-index-metadata> ::= UInt64(kSimpleIndexMagicNumber) +// UInt32(4) +// UInt64(<number-of-entries>) +// UInt64(<cache-size-in-bytes>) +// <entry-info> ::= UInt64(<hash-of-the-key>) +// Int64(<entry-last-used-time>) +// UInt64(<entry-size-in-bytes>) +// <v6-index> ::= <v6-index-metadata> +// <entry-info>* +// Int64(<cache-dir-mtime>) +// <v6-index-metadata> ::= UInt64(kSimpleIndexMagicNumber) +// UInt32(5) +// UInt64(<number-of-entries>) +// UInt64(<cache-size-in-bytes>) +// Where: +// <entry-size-in-bytes> is equal the sum of all file sizes of the entry. +// <cache-dir-mtime> is the last modification time with nanosecond precision +// of the directory, where all files for entries are stored. +// <hash-of-the-key> represent the first 64 bits of a SHA-1 of the key. +bool UpgradeIndexV5V6(const base::FilePath& cache_directory) { + const base::FilePath old_index_file = + cache_directory.AppendASCII(kIndexFileName); + if (!base::DeleteFile(old_index_file, /* recursive = */ false)) + return false; + return true; +} + +// Some points about the Upgrade process are still not clear: +// 1. if the upgrade path requires dropping cache it would be faster to just +// return an initialization error here and proceed with asynchronous cache +// cleanup in CacheCreator. Should this hack be considered valid? Some smart +// tests may fail. +// 2. Because Android process management allows for killing a process at any +// time, the upgrade process may need to deal with a partially completed +// previous upgrade. For example, while upgrading A -> A + 2 we are the +// process gets killed and some parts are remaining at version A + 1. There +// are currently no generic mechanisms to resolve this situation, co the +// upgrade codes need to ensure they can continue after being stopped in the +// middle. It also means that the "fake index" must be flushed in between the +// upgrade steps. Atomicity of this is an interesting research topic. The +// intermediate fake index flushing must be added as soon as we add more +// upgrade steps. +bool UpgradeSimpleCacheOnDisk(const base::FilePath& path) { + // There is a convention among disk cache backends: looking at the magic in + // the file "index" it should be sufficient to determine if the cache belongs + // to the currently running backend. The Simple Backend stores its index in + // the file "the-real-index" (see simple_index_file.cc) and the file "index" + // only signifies presence of the implementation's magic and version. There + // are two reasons for that: + // 1. Absence of the index is itself not a fatal error in the Simple Backend + // 2. The Simple Backend has pickled file format for the index making it hacky + // to have the magic in the right place. + const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName); + base::PlatformFileError error; + base::PlatformFile fake_index_file = base::CreatePlatformFile( + fake_index, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, + &error); + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + return WriteFakeIndexFile(fake_index); + } else if (error != base::PLATFORM_FILE_OK) { + return false; + } + FakeIndexData file_header; + int bytes_read = base::ReadPlatformFile(fake_index_file, + 0, + reinterpret_cast<char*>(&file_header), + sizeof(file_header)); + if (!base::ClosePlatformFile(fake_index_file) || + bytes_read != sizeof(file_header) || + file_header.initial_magic_number != + disk_cache::simplecache_v5::kSimpleInitialMagicNumber) { + LOG(ERROR) << "File structure does not match the disk cache backend."; + return false; + } + + uint32 version_from = file_header.version; + if (version_from < kMinVersionAbleToUpgrade || + version_from > kSimpleVersion) { + LOG(ERROR) << "Inconsistent cache version."; + return false; + } + bool upgrade_needed = (version_from != kSimpleVersion); + if (version_from == kMinVersionAbleToUpgrade) { + // Upgrade only the index for V4 -> V5 move. + if (!UpgradeIndexV5V6(path)) { + LogMessageFailedUpgradeFromVersion(file_header.version); + return false; + } + version_from++; + } + if (version_from == kSimpleVersion) { + if (!upgrade_needed) { + return true; + } else { + const base::FilePath temp_fake_index = path.AppendASCII("upgrade-index"); + if (!WriteFakeIndexFile(temp_fake_index)) { + base::DeleteFile(temp_fake_index, /* recursive = */ false); + LOG(ERROR) << "Failed to write a new fake index."; + LogMessageFailedUpgradeFromVersion(file_header.version); + return false; + } + if (!base::ReplaceFile(temp_fake_index, fake_index, NULL)) { + LOG(ERROR) << "Failed to replace the fake index."; + LogMessageFailedUpgradeFromVersion(file_header.version); + return false; + } + return true; + } + } + // Verify during the test stage that the upgraders are implemented for all + // versions. The release build would cause backend initialization failure + // which would then later lead to removing all files known to the backend. + DCHECK_EQ(kSimpleVersion, version_from); + return false; +} + +} // namespace disk_cache diff --git a/net/disk_cache/simple/simple_version_upgrade.h b/net/disk_cache/simple/simple_version_upgrade.h new file mode 100644 index 0000000000..352379b997 --- /dev/null +++ b/net/disk_cache/simple/simple_version_upgrade.h @@ -0,0 +1,50 @@ +// Copyright 2013 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_DISK_CACHE_SIMPLE_SIMPLE_VERSION_UPGRADE_H_ +#define NET_DISK_CACHE_SIMPLE_SIMPLE_VERSION_UPGRADE_H_ + +// Defines functionality to upgrade the file structure of the Simple Cache +// Backend on disk. Assumes no backend operations are running simultaneously. +// Hence must be run at cache initialization step. + +#include "base/basictypes.h" +#include "net/base/net_export.h" + +namespace base { +class FilePath; +} + +namespace disk_cache { + +// Performs all necessary disk IO to upgrade the cache structure if it is +// needed. +// +// Returns true iff no errors were found during consistency checks and all +// necessary transitions succeeded. If this function fails, there is nothing +// left to do other than dropping the whole cache directory. +NET_EXPORT_PRIVATE bool UpgradeSimpleCacheOnDisk(const base::FilePath& path); + +// The format for the fake index has mistakenly acquired two extra fields that +// do not contain any useful data. Since they were equal to zero, they are now +// mandatated to be zero. +struct NET_EXPORT_PRIVATE FakeIndexData { + FakeIndexData(); + + // Must be equal to simplecache_v4::kSimpleInitialMagicNumber. + uint64 initial_magic_number; + + // Must be equal kSimpleVersion when the cache backend is instantiated. + uint32 version; + + uint32 unused_must_be_zero1; + uint32 unused_must_be_zero2; +}; + +// Exposed for testing. +NET_EXPORT_PRIVATE bool UpgradeIndexV5V6(const base::FilePath& cache_directory); + +} // namespace disk_cache + +#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_VERSION_UPGRADE_H_ diff --git a/net/disk_cache/simple/simple_version_upgrade_unittest.cc b/net/disk_cache/simple/simple_version_upgrade_unittest.cc new file mode 100644 index 0000000000..c9d42f1066 --- /dev/null +++ b/net/disk_cache/simple/simple_version_upgrade_unittest.cc @@ -0,0 +1,146 @@ +// Copyright 2013 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/disk_cache/simple/simple_version_upgrade.h" + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "net/base/net_errors.h" +#include "net/disk_cache/simple/simple_backend_version.h" +#include "net/disk_cache/simple/simple_entry_format_history.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The migration process relies on ability to rename newly created files, which +// could be problematic on Windows XP. +#if defined(OS_POSIX) + +namespace { + +// Same as |disk_cache::kSimpleInitialMagicNumber|. +const uint64 kSimpleInitialMagicNumber = GG_UINT64_C(0xfcfb6d1ba7725c30); + +// The "fake index" file that cache backends use to distinguish whether the +// cache belongs to one backend or another. +const char kFakeIndexFileName[] = "index"; + +// Same as |SimpleIndexFile::kIndexFileName|. +const char kIndexFileName[] = "the-real-index"; + +// Same as |SimpleIndexFile::kIndexDirectory|. +const char kIndexDirectory[] = "index-dir"; + +// Same as |SimpleIndexFile::kTempIndexFileName|. +const char kTempIndexFileName[] = "temp-index"; + +bool WriteFakeIndexFileV5(const base::FilePath& cache_path) { + disk_cache::FakeIndexData data; + data.version = 5; + data.initial_magic_number = kSimpleInitialMagicNumber; + data.unused_must_be_zero1 = 0; + data.unused_must_be_zero2 = 0; + const base::FilePath file_name = cache_path.AppendASCII("index"); + return sizeof(data) == + file_util::WriteFile( + file_name, reinterpret_cast<const char*>(&data), sizeof(data)); +} + +TEST(SimpleVersionUpgradeTest, FailsToMigrateBackwards) { + base::ScopedTempDir cache_dir; + ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); + const base::FilePath cache_path = cache_dir.path(); + + disk_cache::FakeIndexData data; + data.version = 100500; + data.initial_magic_number = kSimpleInitialMagicNumber; + data.unused_must_be_zero1 = 0; + data.unused_must_be_zero2 = 0; + const base::FilePath file_name = cache_path.AppendASCII(kFakeIndexFileName); + ASSERT_EQ(implicit_cast<int>(sizeof(data)), + file_util::WriteFile( + file_name, reinterpret_cast<const char*>(&data), sizeof(data))); + EXPECT_FALSE(disk_cache::UpgradeSimpleCacheOnDisk(cache_dir.path())); +} + +TEST(SimpleVersionUpgradeTest, FakeIndexVersionGetsUpdated) { + base::ScopedTempDir cache_dir; + ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); + const base::FilePath cache_path = cache_dir.path(); + + WriteFakeIndexFileV5(cache_path); + const std::string file_contents("incorrectly serialized data"); + const base::FilePath index_file = cache_path.AppendASCII(kIndexFileName); + ASSERT_EQ(implicit_cast<int>(file_contents.size()), + file_util::WriteFile( + index_file, file_contents.data(), file_contents.size())); + + // Upgrade. + ASSERT_TRUE(disk_cache::UpgradeSimpleCacheOnDisk(cache_path)); + + // Check that the version in the fake index file is updated. + std::string new_fake_index_contents; + ASSERT_TRUE(base::ReadFileToString(cache_path.AppendASCII(kFakeIndexFileName), + &new_fake_index_contents)); + const disk_cache::FakeIndexData* fake_index_header; + EXPECT_EQ(sizeof(*fake_index_header), new_fake_index_contents.size()); + fake_index_header = reinterpret_cast<const disk_cache::FakeIndexData*>( + new_fake_index_contents.data()); + EXPECT_EQ(disk_cache::kSimpleVersion, fake_index_header->version); + EXPECT_EQ(kSimpleInitialMagicNumber, fake_index_header->initial_magic_number); +} + +TEST(SimpleVersionUpgradeTest, UpgradeV5V6IndexMustDisappear) { + base::ScopedTempDir cache_dir; + ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); + const base::FilePath cache_path = cache_dir.path(); + + WriteFakeIndexFileV5(cache_path); + const std::string file_contents("incorrectly serialized data"); + const base::FilePath index_file = cache_path.AppendASCII(kIndexFileName); + ASSERT_EQ(implicit_cast<int>(file_contents.size()), + file_util::WriteFile( + index_file, file_contents.data(), file_contents.size())); + + // Create a few entry-like files. + const uint64 kEntries = 5; + for (uint64 entry_hash = 0; entry_hash < kEntries; ++entry_hash) { + for (int index = 0; index < 3; ++index) { + std::string file_name = + base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, index); + std::string entry_contents = + file_contents + + base::StringPrintf(" %" PRIx64, implicit_cast<uint64>(entry_hash)); + ASSERT_EQ(implicit_cast<int>(entry_contents.size()), + file_util::WriteFile(cache_path.AppendASCII(file_name), + entry_contents.data(), + entry_contents.size())); + } + } + + // Upgrade. + ASSERT_TRUE(disk_cache::UpgradeIndexV5V6(cache_path)); + + // Check that the old index disappeared but the files remain unchanged. + EXPECT_FALSE(base::PathExists(index_file)); + for (uint64 entry_hash = 0; entry_hash < kEntries; ++entry_hash) { + for (int index = 0; index < 3; ++index) { + std::string file_name = + base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, index); + std::string expected_contents = + file_contents + + base::StringPrintf(" %" PRIx64, implicit_cast<uint64>(entry_hash)); + std::string real_contents; + EXPECT_TRUE(base::ReadFileToString(cache_path.AppendASCII(file_name), + &real_contents)); + EXPECT_EQ(expected_contents, real_contents); + } + } +} + +} // namespace + +#endif // defined(OS_POSIX) diff --git a/net/disk_cache/v3/block_bitmaps.cc b/net/disk_cache/v3/block_bitmaps.cc index 0d0317b39d..b68ecdd14f 100644 --- a/net/disk_cache/v3/block_bitmaps.cc +++ b/net/disk_cache/v3/block_bitmaps.cc @@ -2,143 +2,73 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/disk_cache/block_files.h" +#include "net/disk_cache/v3/block_bitmaps.h" -#include "base/atomicops.h" -#include "base/file_util.h" #include "base/metrics/histogram.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "base/threading/thread_checker.h" #include "base/time/time.h" -#include "net/disk_cache/cache_util.h" -#include "net/disk_cache/file_lock.h" +#include "net/disk_cache/disk_format_base.h" #include "net/disk_cache/trace.h" using base::TimeTicks; namespace disk_cache { -BlockFiles::BlockFiles(const base::FilePath& path) - : init_(false), zero_buffer_(NULL), path_(path) { +BlockBitmaps::BlockBitmaps() { } -BlockFiles::~BlockFiles() { - if (zero_buffer_) - delete[] zero_buffer_; - CloseFiles(); +BlockBitmaps::~BlockBitmaps() { } -bool BlockFiles::Init(bool create_files) { - DCHECK(!init_); - if (init_) - return false; - - thread_checker_.reset(new base::ThreadChecker); - - block_files_.resize(kFirstAdditionalBlockFile); - for (int i = 0; i < kFirstAdditionalBlockFile; i++) { - if (create_files) - if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true)) - return false; - - if (!OpenBlockFile(i)) - return false; - - // Walk this chain of files removing empty ones. - if (!RemoveEmptyFile(static_cast<FileType>(i + 1))) - return false; - } - - init_ = true; - return true; +void BlockBitmaps::Init(const BlockFilesBitmaps& bitmaps) { + bitmaps_ = bitmaps; } -bool BlockFiles::CreateBlock(FileType block_type, int block_count, - Addr* block_address) { - DCHECK(thread_checker_->CalledOnValidThread()); - if (block_type < RANKINGS || block_type > BLOCK_4K || - block_count < 1 || block_count > 4) - return false; - if (!init_) +bool BlockBitmaps::CreateBlock(FileType block_type, + int block_count, + Addr* block_address) { + DCHECK_NE(block_type, EXTERNAL); + DCHECK_NE(block_type, RANKINGS); + if (block_count < 1 || block_count > kMaxNumBlocks) return false; - MappedFile* file = FileForNewBlock(block_type, block_count); - if (!file) + int header_num = HeaderNumberForNewBlock(block_type, block_count); + if (header_num < 0) return false; - ScopedFlush flush(file); - BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - - int target_size = 0; - for (int i = block_count; i <= 4; i++) { - if (header->empty[i - 1]) { - target_size = i; - break; - } - } - - DCHECK(target_size); int index; - if (!CreateMapBlock(target_size, block_count, header, &index)) + if (!bitmaps_[header_num].CreateMapBlock(block_count, &index)) + return false; + + if (!index && (block_type == BLOCK_ENTRIES || block_type == BLOCK_EVICTED) && + !bitmaps_[header_num].CreateMapBlock(block_count, &index)) { + // index 0 for entries is a reserved value. return false; + } - Addr address(block_type, block_count, header->this_file, index); + Addr address(block_type, block_count, bitmaps_[header_num].FileId(), index); block_address->set_value(address.value()); Trace("CreateBlock 0x%x", address.value()); return true; } -void BlockFiles::DeleteBlock(Addr address, bool deep) { - DCHECK(thread_checker_->CalledOnValidThread()); +void BlockBitmaps::DeleteBlock(Addr address) { if (!address.is_initialized() || address.is_separate_file()) return; - if (!zero_buffer_) { - zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]; - memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4); - } - MappedFile* file = GetFile(address); - if (!file) + int header_num = GetHeaderNumber(address); + if (header_num < 0) return; Trace("DeleteBlock 0x%x", address.value()); - - size_t size = address.BlockSize() * address.num_blocks(); - size_t offset = address.start_block() * address.BlockSize() + - kBlockHeaderSize; - if (deep) - file->Write(zero_buffer_, size, offset); - - BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - DeleteMapBlock(address.start_block(), address.num_blocks(), header); - file->Flush(); - - if (!header->num_entries) { - // This file is now empty. Let's try to delete it. - FileType type = Addr::RequiredFileType(header->entry_size); - if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size) - type = RANKINGS; - RemoveEmptyFile(type); // Ignore failures. - } + bitmaps_[header_num].DeleteMapBlock(address.start_block(), + address.num_blocks()); } -void BlockFiles::CloseFiles() { - if (init_) { - DCHECK(thread_checker_->CalledOnValidThread()); - } - init_ = false; - for (unsigned int i = 0; i < block_files_.size(); i++) { - if (block_files_[i]) { - block_files_[i]->Release(); - block_files_[i] = NULL; - } - } - block_files_.clear(); +void BlockBitmaps::Clear() { + bitmaps_.clear(); } -void BlockFiles::ReportStats() { - DCHECK(thread_checker_->CalledOnValidThread()); +void BlockBitmaps::ReportStats() { int used_blocks[kFirstAdditionalBlockFile]; int load[kFirstAdditionalBlockFile]; for (int i = 0; i < kFirstAdditionalBlockFile; i++) { @@ -155,176 +85,92 @@ void BlockFiles::ReportStats() { UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101); } -bool BlockFiles::IsValid(Addr address) { +bool BlockBitmaps::IsValid(Addr address) { #ifdef NDEBUG return true; #else if (!address.is_initialized() || address.is_separate_file()) return false; - MappedFile* file = GetFile(address); - if (!file) + int header_num = GetHeaderNumber(address); + if (header_num < 0) return false; - BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - bool rv = UsedMapBlock(address.start_block(), address.num_blocks(), header); + bool rv = bitmaps_[header_num].UsedMapBlock(address.start_block(), + address.num_blocks()); DCHECK(rv); - - static bool read_contents = false; - if (read_contents) { - scoped_ptr<char[]> buffer; - buffer.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]); - size_t size = address.BlockSize() * address.num_blocks(); - size_t offset = address.start_block() * address.BlockSize() + - kBlockHeaderSize; - bool ok = file->Read(buffer.get(), size, offset); - DCHECK(ok); - } - return rv; #endif } -MappedFile* BlockFiles::GetFile(Addr address) { - DCHECK(thread_checker_->CalledOnValidThread()); - DCHECK(block_files_.size() >= 4); +int BlockBitmaps::GetHeaderNumber(Addr address) { + DCHECK_GE(bitmaps_.size(), static_cast<size_t>(kFirstAdditionalBlockFileV3)); DCHECK(address.is_block_file() || !address.is_initialized()); if (!address.is_initialized()) - return NULL; + return -1; int file_index = address.FileNumber(); - if (static_cast<unsigned int>(file_index) >= block_files_.size() || - !block_files_[file_index]) { - // We need to open the file - if (!OpenBlockFile(file_index)) - return NULL; - } - DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index)); - return block_files_[file_index]; -} - -bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) { - if (kMaxBlocks == header->max_entries) - return false; - - ScopedFlush flush(file); - DCHECK(!header->empty[3]); - int new_size = header->max_entries + 1024; - if (new_size > kMaxBlocks) - new_size = kMaxBlocks; - - int new_size_bytes = new_size * header->entry_size + sizeof(*header); - - if (!file->SetLength(new_size_bytes)) { - // Most likely we are trying to truncate the file, so the header is wrong. - if (header->updating < 10 && !FixBlockFileHeader(file)) { - // If we can't fix the file increase the lock guard so we'll pick it on - // the next start and replace it. - header->updating = 100; - return false; - } - return (header->max_entries >= new_size); - } + if (static_cast<unsigned int>(file_index) >= bitmaps_.size()) + return -1; - FileLock lock(header); - header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries - header->max_entries = new_size; - - return true; + return file_index; } -MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) { - COMPILE_ASSERT(RANKINGS == 1, invalid_file_type); - MappedFile* file = block_files_[block_type - 1]; - BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); +int BlockBitmaps::HeaderNumberForNewBlock(FileType block_type, + int block_count) { + DCHECK_GT(block_type, 0); + int header_num = block_type - 1; + bool found = true; TimeTicks start = TimeTicks::Now(); - while (NeedToGrowBlockFile(header, block_count)) { - if (kMaxBlocks == header->max_entries) { - file = NextFile(file); - if (!file) - return NULL; - header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - continue; + while (bitmaps_[header_num].NeedToGrowBlockFile(block_count)) { + header_num = bitmaps_[header_num].NextFileId(); + if (!header_num) { + found = false; + break; } - - if (!GrowBlockFile(file, header)) - return NULL; - break; } - HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start); - return file; -} -// Note that we expect to be called outside of a FileLock... however, we cannot -// DCHECK on header->updating because we may be fixing a crash. -bool BlockFiles::FixBlockFileHeader(MappedFile* file) { - ScopedFlush flush(file); - BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - int file_size = static_cast<int>(file->GetLength()); - if (file_size < static_cast<int>(sizeof(*header))) - return false; // file_size > 2GB is also an error. - - const int kMinBlockSize = 36; - const int kMaxBlockSize = 4096; - if (header->entry_size < kMinBlockSize || - header->entry_size > kMaxBlockSize || header->num_entries < 0) - return false; - - // Make sure that we survive crashes. - header->updating = 1; - int expected = header->entry_size * header->max_entries + sizeof(*header); - if (file_size != expected) { - int max_expected = header->entry_size * kMaxBlocks + sizeof(*header); - if (file_size < expected || header->empty[3] || file_size > max_expected) { - NOTREACHED(); - LOG(ERROR) << "Unexpected file size"; - return false; - } - // We were in the middle of growing the file. - int num_entries = (file_size - sizeof(*header)) / header->entry_size; - header->max_entries = num_entries; + if (!found) { + // Restart the search, looking for any file with space. We know that all + // files of this type are low on free blocks, but we cannot grow any file + // at this time. + header_num = block_type - 1; + do { + if (bitmaps_[header_num].CanAllocate(block_count)) { + found = true; // Make sure file 0 is not mistaken with a failure. + break; + } + header_num = bitmaps_[header_num].NextFileId(); + } while (header_num); + + if (!found) + header_num = -1; } - FixAllocationCounters(header); - int empty_blocks = EmptyBlocks(header); - if (empty_blocks + header->num_entries > header->max_entries) - header->num_entries = header->max_entries - empty_blocks; - - if (!ValidateCounters(header)) - return false; - - header->updating = 0; - return true; + HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start); + return header_num; } // We are interested in the total number of blocks used by this file type, and // the max number of blocks that we can store (reported as the percentage of // used blocks). In order to find out the number of used blocks, we have to // substract the empty blocks from the total blocks for each file in the chain. -void BlockFiles::GetFileStats(int index, int* used_count, int* load) { +void BlockBitmaps::GetFileStats(int index, int* used_count, int* load) { int max_blocks = 0; *used_count = 0; *load = 0; - for (;;) { - if (!block_files_[index] && !OpenBlockFile(index)) - return; - - BlockFileHeader* header = - reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer()); - - max_blocks += header->max_entries; - int used = header->max_entries; - for (int i = 0; i < 4; i++) { - used -= header->empty[i] * (i + 1); - DCHECK_GE(used, 0); - } + do { + int capacity = bitmaps_[index].Capacity(); + int used = capacity - bitmaps_[index].EmptyBlocks(); + DCHECK_GE(used, 0); + + max_blocks += capacity; *used_count += used; - if (!header->next_file) - break; - index = header->next_file; - } + index = bitmaps_[index].NextFileId(); + } while (index); + if (max_blocks) *load = *used_count * 100 / max_blocks; } diff --git a/net/disk_cache/v3/block_bitmaps.h b/net/disk_cache/v3/block_bitmaps.h index eaf8760991..111d57b0b1 100644 --- a/net/disk_cache/v3/block_bitmaps.h +++ b/net/disk_cache/v3/block_bitmaps.h @@ -4,43 +4,39 @@ // See net/disk_cache/disk_cache.h for the public interface. -#ifndef NET_DISK_CACHE_BLOCK_FILES_H_ -#define NET_DISK_CACHE_BLOCK_FILES_H_ - -#include <vector> +#ifndef NET_DISK_CACHE_V3_BLOCK_BITMAPS_H_ +#define NET_DISK_CACHE_V3_BLOCK_BITMAPS_H_ #include "base/files/file_path.h" -#include "base/gtest_prod_util.h" -#include "base/memory/scoped_ptr.h" #include "net/base/net_export.h" #include "net/disk_cache/addr.h" -#include "net/disk_cache/mapped_file.h" +#include "net/disk_cache/block_files.h" namespace disk_cache { -// This class handles the set of block-files open by the disk cache. -class NET_EXPORT_PRIVATE BlockFiles { +class BackendImplV3; + +// This class is the interface in the v3 disk cache to the set of files holding +// cached data that is small enough to not be efficiently stored in a dedicated +// file (i.e. < kMaxBlockSize). It is primarily used to allocate and free +// regions in those files used to store data. +class NET_EXPORT_PRIVATE BlockBitmaps { public: - explicit BlockFiles(const base::FilePath& path); - ~BlockFiles(); + BlockBitmaps(); + ~BlockBitmaps(); - // Performs the object initialization. create_files indicates if the backing - // files should be created or just open. - bool Init(bool create_files); + void Init(const BlockFilesBitmaps& bitmaps); // Creates a new entry on a block file. block_type indicates the size of block // to be used (as defined on cache_addr.h), block_count is the number of // blocks to allocate, and block_address is the address of the new entry. bool CreateBlock(FileType block_type, int block_count, Addr* block_address); - // Removes an entry from the block files. If deep is true, the storage is zero - // filled; otherwise the entry is removed but the data is not altered (must be - // already zeroed). - void DeleteBlock(Addr address, bool deep); + // Removes an entry from the block files. + void DeleteBlock(Addr address); - // Close all the files and set the internal state to be initializad again. The - // cache is being purged. - void CloseFiles(); + // Releases the internal bitmaps. The cache is being purged. + void Clear(); // Sends UMA stats. void ReportStats(); @@ -50,26 +46,20 @@ class NET_EXPORT_PRIVATE BlockFiles { bool IsValid(Addr address); private: - // Returns the file that stores a given address. - MappedFile* GetFile(Addr address); - - // Attemp to grow this file. Fails if the file cannot be extended anymore. - bool GrowBlockFile(MappedFile* file, BlockFileHeader* header); - - // Returns the appropriate file to use for a new block. - MappedFile* FileForNewBlock(FileType block_type, int block_count); + // Returns the header number that stores a given address. + int GetHeaderNumber(Addr address); - // Restores the header of a potentially inconsistent file. - bool FixBlockFileHeader(MappedFile* file); + // Returns the appropriate header to use for a new block. + int HeaderNumberForNewBlock(FileType block_type, int block_count); // Retrieves stats for the given file index. void GetFileStats(int index, int* used_count, int* load); - bool init_; + BlockFilesBitmaps bitmaps_; - DISALLOW_COPY_AND_ASSIGN(BlockFiles); + DISALLOW_COPY_AND_ASSIGN(BlockBitmaps); }; } // namespace disk_cache -#endif // NET_DISK_CACHE_BLOCK_FILES_H_ +#endif // NET_DISK_CACHE_V3_BLOCK_BITMAPS_H_ diff --git a/net/disk_cache/v3/block_bitmaps_unittest.cc b/net/disk_cache/v3/block_bitmaps_unittest.cc index fa7c5dbb74..981bdecfce 100644 --- a/net/disk_cache/v3/block_bitmaps_unittest.cc +++ b/net/disk_cache/v3/block_bitmaps_unittest.cc @@ -2,342 +2,64 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/file_util.h" -#include "base/files/file_enumerator.h" +#include "net/disk_cache/addr.h" #include "net/disk_cache/block_files.h" -#include "net/disk_cache/disk_cache.h" -#include "net/disk_cache/disk_cache_test_base.h" -#include "net/disk_cache/disk_cache_test_util.h" +#include "net/disk_cache/disk_format_base.h" +#include "net/disk_cache/v3/block_bitmaps.h" #include "testing/gtest/include/gtest/gtest.h" -using base::Time; - -namespace { - -// Returns the number of files in this folder. -int NumberOfFiles(const base::FilePath& path) { - base::FileEnumerator iter(path, false, base::FileEnumerator::FILES); - int count = 0; - for (base::FilePath file = iter.Next(); !file.value().empty(); - file = iter.Next()) { - count++; - } - return count; -} - -} // namespace; - -namespace disk_cache { - -TEST_F(DiskCacheTest, BlockFiles_Grow) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - - const int kMaxSize = 35000; - Addr address[kMaxSize]; - - // Fill up the 32-byte block file (use three files). - for (int i = 0; i < kMaxSize; i++) { - EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[i])); - } - EXPECT_EQ(6, NumberOfFiles(cache_path_)); - - // Make sure we don't keep adding files. - for (int i = 0; i < kMaxSize * 4; i += 2) { - int target = i % kMaxSize; - files.DeleteBlock(address[target], false); - EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[target])); - } - EXPECT_EQ(6, NumberOfFiles(cache_path_)); -} - -// We should be able to delete empty block files. -TEST_F(DiskCacheTest, BlockFiles_Shrink) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - - const int kMaxSize = 35000; - Addr address[kMaxSize]; - - // Fill up the 32-byte block file (use three files). - for (int i = 0; i < kMaxSize; i++) { - EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[i])); - } - - // Now delete all the blocks, so that we can delete the two extra files. - for (int i = 0; i < kMaxSize; i++) { - files.DeleteBlock(address[i], false); - } - EXPECT_EQ(4, NumberOfFiles(cache_path_)); -} - -// Handling of block files not properly closed. -TEST_F(DiskCacheTest, BlockFiles_Recover) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - - const int kNumEntries = 2000; - CacheAddr entries[kNumEntries]; - - int seed = static_cast<int>(Time::Now().ToInternalValue()); - srand(seed); - for (int i = 0; i < kNumEntries; i++) { - Addr address(0); - int size = (rand() % 4) + 1; - EXPECT_TRUE(files.CreateBlock(RANKINGS, size, &address)); - entries[i] = address.value(); - } - - for (int i = 0; i < kNumEntries; i++) { - int source1 = rand() % kNumEntries; - int source2 = rand() % kNumEntries; - CacheAddr temp = entries[source1]; - entries[source1] = entries[source2]; - entries[source2] = temp; - } - - for (int i = 0; i < kNumEntries / 2; i++) { - Addr address(entries[i]); - files.DeleteBlock(address, false); - } - - // At this point, there are kNumEntries / 2 entries on the file, randomly - // distributed both on location and size. - - Addr address(entries[kNumEntries / 2]); - MappedFile* file = files.GetFile(address); - ASSERT_TRUE(NULL != file); - - BlockFileHeader* header = - reinterpret_cast<BlockFileHeader*>(file->buffer()); - ASSERT_TRUE(NULL != header); - - ASSERT_EQ(0, header->updating); - - int max_entries = header->max_entries; - int empty_1 = header->empty[0]; - int empty_2 = header->empty[1]; - int empty_3 = header->empty[2]; - int empty_4 = header->empty[3]; - - // Corrupt the file. - header->max_entries = header->empty[0] = 0; - header->empty[1] = header->empty[2] = header->empty[3] = 0; - header->updating = -1; - - files.CloseFiles(); - - ASSERT_TRUE(files.Init(false)); - - // The file must have been fixed. - file = files.GetFile(address); - ASSERT_TRUE(NULL != file); - - header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - ASSERT_TRUE(NULL != header); - - ASSERT_EQ(0, header->updating); - - EXPECT_EQ(max_entries, header->max_entries); - EXPECT_EQ(empty_1, header->empty[0]); - EXPECT_EQ(empty_2, header->empty[1]); - EXPECT_EQ(empty_3, header->empty[2]); - EXPECT_EQ(empty_4, header->empty[3]); -} - -// Handling of truncated files. -TEST_F(DiskCacheTest, BlockFiles_ZeroSizeFile) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - - base::FilePath filename = files.Name(0); - files.CloseFiles(); - // Truncate one of the files. - { - scoped_refptr<File> file(new File); - ASSERT_TRUE(file->Init(filename)); - EXPECT_TRUE(file->SetLength(0)); - } - - // Initializing should fail, not crash. - ASSERT_FALSE(files.Init(false)); -} - -// Handling of truncated files (non empty). -TEST_F(DiskCacheTest, BlockFiles_TruncatedFile) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - Addr address; - EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address)); - - base::FilePath filename = files.Name(0); - files.CloseFiles(); - // Truncate one of the files. - { - scoped_refptr<File> file(new File); - ASSERT_TRUE(file->Init(filename)); - EXPECT_TRUE(file->SetLength(15000)); - } - - // Initializing should fail, not crash. - ASSERT_FALSE(files.Init(false)); -} - -// Tests detection of out of sync counters. -TEST_F(DiskCacheTest, BlockFiles_Counters) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - - // Create a block of size 2. - Addr address(0); - EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address)); - - MappedFile* file = files.GetFile(address); - ASSERT_TRUE(NULL != file); - - BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - ASSERT_TRUE(NULL != header); - ASSERT_EQ(0, header->updating); - - // Alter the counters so that the free space doesn't add up. - header->empty[2] = 50; // 50 free blocks of size 3. - files.CloseFiles(); - - ASSERT_TRUE(files.Init(false)); - file = files.GetFile(address); - ASSERT_TRUE(NULL != file); - header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - ASSERT_TRUE(NULL != header); - - // The file must have been fixed. - ASSERT_EQ(0, header->empty[2]); - - // Change the number of entries. - header->num_entries = 3; - header->updating = 1; - files.CloseFiles(); - - ASSERT_TRUE(files.Init(false)); - file = files.GetFile(address); - ASSERT_TRUE(NULL != file); - header = reinterpret_cast<BlockFileHeader*>(file->buffer()); - ASSERT_TRUE(NULL != header); - - // The file must have been "fixed". - ASSERT_EQ(2, header->num_entries); - - // Change the number of entries. - header->num_entries = -1; - header->updating = 1; - files.CloseFiles(); - - // Detect the error. - ASSERT_FALSE(files.Init(false)); -} - -// An invalid file can be detected after init. -TEST_F(DiskCacheTest, BlockFiles_InvalidFile) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); - - // Let's access block 10 of file 5. (There is no file). - Addr addr(BLOCK_256, 1, 5, 10); - EXPECT_TRUE(NULL == files.GetFile(addr)); - - // Let's create an invalid file. - base::FilePath filename(files.Name(5)); - char header[kBlockHeaderSize]; - memset(header, 'a', kBlockHeaderSize); - EXPECT_EQ(kBlockHeaderSize, - file_util::WriteFile(filename, header, kBlockHeaderSize)); - - EXPECT_TRUE(NULL == files.GetFile(addr)); - - // The file should not have been changed (it is still invalid). - EXPECT_TRUE(NULL == files.GetFile(addr)); -} - -// Tests that we generate the correct file stats. -TEST_F(DiskCacheTest, BlockFiles_Stats) { - ASSERT_TRUE(CopyTestCache("remove_load1")); - - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(false)); - int used, load; - - files.GetFileStats(0, &used, &load); - EXPECT_EQ(101, used); - EXPECT_EQ(9, load); - - files.GetFileStats(1, &used, &load); - EXPECT_EQ(203, used); - EXPECT_EQ(19, load); - - files.GetFileStats(2, &used, &load); - EXPECT_EQ(0, used); - EXPECT_EQ(0, load); -} - // Tests that we add and remove blocks correctly. -TEST_F(DiskCacheTest, AllocationMap) { - ASSERT_TRUE(CleanupCacheDir()); - ASSERT_TRUE(file_util::CreateDirectory(cache_path_)); +TEST(DiskCacheBlockBitmaps, V3AllocationMap) { + disk_cache::BlockBitmaps block_bitmaps; + disk_cache::BlockFilesBitmaps bitmaps; + + const int kNumHeaders = 10; + disk_cache::BlockFileHeader headers[kNumHeaders]; + for (int i = 0; i < kNumHeaders; i++) { + memset(&headers[i], 0, sizeof(headers[i])); + headers[i].magic = disk_cache::kBlockMagic; + headers[i].version = disk_cache::kBlockCurrentVersion; + headers[i].this_file = static_cast<int16>(i); + headers[i].empty[3] = 200; + headers[i].max_entries = 800; + bitmaps.push_back(disk_cache::BlockHeader(&headers[i])); + } - BlockFiles files(cache_path_); - ASSERT_TRUE(files.Init(true)); + block_bitmaps.Init(bitmaps); // Create a bunch of entries. const int kSize = 100; - Addr address[kSize]; + disk_cache::Addr address[kSize]; for (int i = 0; i < kSize; i++) { SCOPED_TRACE(i); int block_size = i % 4 + 1; - EXPECT_TRUE(files.CreateBlock(BLOCK_1K, block_size, &address[i])); - EXPECT_EQ(BLOCK_1K, address[i].file_type()); + ASSERT_TRUE(block_bitmaps.CreateBlock(disk_cache::BLOCK_1K, block_size, + &address[i])); + EXPECT_EQ(disk_cache::BLOCK_1K, address[i].file_type()); EXPECT_EQ(block_size, address[i].num_blocks()); int start = address[i].start_block(); + + // Verify that the allocated entry doesn't cross a 4 block boundary. EXPECT_EQ(start / 4, (start + block_size - 1) / 4); } for (int i = 0; i < kSize; i++) { SCOPED_TRACE(i); - EXPECT_TRUE(files.IsValid(address[i])); + EXPECT_TRUE(block_bitmaps.IsValid(address[i])); } // The first part of the allocation map should be completely filled. We used - // 10 bits per each four entries, so 250 bits total. - BlockFileHeader* header = - reinterpret_cast<BlockFileHeader*>(files.GetFile(address[0])->buffer()); - uint8* buffer = reinterpret_cast<uint8*>(&header->allocation_map); - for (int i =0; i < 29; i++) { + // 10 bits per each of four entries, so 250 bits total. All entries should go + // to the third file. + uint8* buffer = reinterpret_cast<uint8*>(&headers[2].allocation_map); + for (int i = 0; i < 29; i++) { SCOPED_TRACE(i); EXPECT_EQ(0xff, buffer[i]); } for (int i = 0; i < kSize; i++) { SCOPED_TRACE(i); - files.DeleteBlock(address[i], false); + block_bitmaps.DeleteBlock(address[i]); } // The allocation map should be empty. @@ -346,5 +68,3 @@ TEST_F(DiskCacheTest, AllocationMap) { EXPECT_EQ(0, buffer[i]); } } - -} // namespace disk_cache diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc index d7af988cf8..6613182557 100644 --- a/net/dns/dns_config_service.cc +++ b/net/dns/dns_config_service.cc @@ -21,7 +21,8 @@ DnsConfig::DnsConfig() timeout(base::TimeDelta::FromSeconds(kDnsTimeoutSeconds)), attempts(2), rotate(false), - edns0(false) {} + edns0(false), + use_local_ipv6(false) {} DnsConfig::~DnsConfig() {} @@ -38,7 +39,8 @@ bool DnsConfig::EqualsIgnoreHosts(const DnsConfig& d) const { (timeout == d.timeout) && (attempts == d.attempts) && (rotate == d.rotate) && - (edns0 == d.edns0); + (edns0 == d.edns0) && + (use_local_ipv6 == d.use_local_ipv6); } void DnsConfig::CopyIgnoreHosts(const DnsConfig& d) { @@ -51,6 +53,7 @@ void DnsConfig::CopyIgnoreHosts(const DnsConfig& d) { attempts = d.attempts; rotate = d.rotate; edns0 = d.edns0; + use_local_ipv6 = d.use_local_ipv6; } base::Value* DnsConfig::ToValue() const { @@ -73,6 +76,7 @@ base::Value* DnsConfig::ToValue() const { dict->SetInteger("attempts", attempts); dict->SetBoolean("rotate", rotate); dict->SetBoolean("edns0", edns0); + dict->SetBoolean("use_local_ipv6", use_local_ipv6); dict->SetInteger("num_hosts", hosts.size()); return dict; diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h index d14a2cd66f..7386e60179 100644 --- a/net/dns/dns_config_service.h +++ b/net/dns/dns_config_service.h @@ -83,6 +83,11 @@ struct NET_EXPORT_PRIVATE DnsConfig { bool rotate; // Enable EDNS0 extensions. bool edns0; + + // Indicates system configuration uses local IPv6 connectivity, e.g., + // DirectAccess. This is exposed for HostResolver to skip IPv6 probes, + // as it may cause them to return incorrect results. + bool use_local_ipv6; }; diff --git a/net/dns/dns_config_service_win.cc b/net/dns/dns_config_service_win.cc index 6d12155d8c..fe97b74f73 100644 --- a/net/dns/dns_config_service_win.cc +++ b/net/dns/dns_config_service_win.cc @@ -43,8 +43,19 @@ namespace { // Interval between retries to parse config. Used only until parsing succeeds. const int kRetryIntervalSeconds = 5; +// Registry key paths. +const wchar_t* const kTcpipPath = + L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; +const wchar_t* const kTcpip6Path = + L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters"; +const wchar_t* const kDnscachePath = + L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters"; +const wchar_t* const kPolicyPath = + L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient"; const wchar_t* const kPrimaryDnsSuffixPath = L"SOFTWARE\\Policies\\Microsoft\\System\\DNSClient"; +const wchar_t* const kNRPTPath = + L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig"; enum HostsParseWinResult { HOSTS_PARSE_WIN_OK = 0, @@ -198,6 +209,10 @@ ConfigParseWinResult ReadSystemSettings(DnsSystemSettings* settings) { &settings->primary_dns_suffix)) { return CONFIG_PARSE_WIN_READ_PRIMARY_SUFFIX; } + + base::win::RegistryKeyIterator nrpt_rules(HKEY_LOCAL_MACHINE, kNRPTPath); + settings->have_name_resolution_policy = (nrpt_rules.SubkeyCount() > 0); + return CONFIG_PARSE_WIN_OK; } @@ -330,8 +345,7 @@ bool IsStatelessDiscoveryAddress(const IPAddressNumber& address) { address.begin()) && (address.back() < 4); } -} // namespace - +// Returns the path to the HOSTS file. base::FilePath GetHostsPath() { TCHAR buffer[MAX_PATH]; UINT rc = GetSystemDirectory(buffer, MAX_PATH); @@ -340,6 +354,92 @@ base::FilePath GetHostsPath() { FILE_PATH_LITERAL("drivers\\etc\\hosts")); } +void ConfigureSuffixSearch(const DnsSystemSettings& settings, + DnsConfig* config) { + // SearchList takes precedence, so check it first. + if (settings.policy_search_list.set) { + std::vector<std::string> search; + if (ParseSearchList(settings.policy_search_list.value, &search)) { + config->search.swap(search); + return; + } + // Even if invalid, the policy disables the user-specified setting below. + } else if (settings.tcpip_search_list.set) { + std::vector<std::string> search; + if (ParseSearchList(settings.tcpip_search_list.value, &search)) { + config->search.swap(search); + return; + } + } + + // In absence of explicit search list, suffix search is: + // [primary suffix, connection-specific suffix, devolution of primary suffix]. + // Primary suffix can be set by policy (primary_dns_suffix) or + // user setting (tcpip_domain). + // + // The policy (primary_dns_suffix) can be edited via Group Policy Editor + // (gpedit.msc) at Local Computer Policy => Computer Configuration + // => Administrative Template => Network => DNS Client => Primary DNS Suffix. + // + // The user setting (tcpip_domain) can be configurred at Computer Name in + // System Settings + std::string primary_suffix; + if ((settings.primary_dns_suffix.set && + ParseDomainASCII(settings.primary_dns_suffix.value, &primary_suffix)) || + (settings.tcpip_domain.set && + ParseDomainASCII(settings.tcpip_domain.value, &primary_suffix))) { + // Primary suffix goes in front. + config->search.insert(config->search.begin(), primary_suffix); + } else { + return; // No primary suffix, hence no devolution. + } + + // Devolution is determined by precedence: policy > dnscache > tcpip. + // |enabled|: UseDomainNameDevolution and |level|: DomainNameDevolutionLevel + // are overridden independently. + DnsSystemSettings::DevolutionSetting devolution = settings.policy_devolution; + + if (!devolution.enabled.set) + devolution.enabled = settings.dnscache_devolution.enabled; + if (!devolution.enabled.set) + devolution.enabled = settings.tcpip_devolution.enabled; + if (devolution.enabled.set && (devolution.enabled.value == 0)) + return; // Devolution disabled. + + // By default devolution is enabled. + + if (!devolution.level.set) + devolution.level = settings.dnscache_devolution.level; + if (!devolution.level.set) + devolution.level = settings.tcpip_devolution.level; + + // After the recent update, Windows will try to determine a safe default + // value by comparing the forest root domain (FRD) to the primary suffix. + // See http://support.microsoft.com/kb/957579 for details. + // For now, if the level is not set, we disable devolution, assuming that + // we will fallback to the system getaddrinfo anyway. This might cause + // performance loss for resolutions which depend on the system default + // devolution setting. + // + // If the level is explicitly set below 2, devolution is disabled. + if (!devolution.level.set || devolution.level.value < 2) + return; // Devolution disabled. + + // Devolve the primary suffix. This naive logic matches the observed + // behavior (see also ParseSearchList). If a suffix is not valid, it will be + // discarded when the fully-qualified name is converted to DNS format. + + unsigned num_dots = std::count(primary_suffix.begin(), + primary_suffix.end(), '.'); + + for (size_t offset = 0; num_dots >= devolution.level.value; --num_dots) { + offset = primary_suffix.find('.', offset + 1); + config->search.push_back(primary_suffix.substr(offset + 1)); + } +} + +} // namespace + bool ParseSearchList(const base::string16& value, std::vector<std::string>* output) { DCHECK(output); @@ -429,87 +529,16 @@ ConfigParseWinResult ConvertSettingsToDnsConfig( (settings.append_to_multi_label_name.value != 0); } - // SearchList takes precedence, so check it first. - if (settings.policy_search_list.set) { - std::vector<std::string> search; - if (ParseSearchList(settings.policy_search_list.value, &search)) { - config->search.swap(search); - return CONFIG_PARSE_WIN_OK; - } - // Even if invalid, the policy disables the user-specified setting below. - } else if (settings.tcpip_search_list.set) { - std::vector<std::string> search; - if (ParseSearchList(settings.tcpip_search_list.value, &search)) { - config->search.swap(search); - return CONFIG_PARSE_WIN_OK; - } + ConfigParseWinResult result = CONFIG_PARSE_WIN_OK; + if (settings.have_name_resolution_policy) { + config->unhandled_options = true; + // TODO(szym): only set this to true if NRPT has DirectAccess rules. + config->use_local_ipv6 = true; + result = CONFIG_PARSE_WIN_UNHANDLED_OPTIONS; } - // In absence of explicit search list, suffix search is: - // [primary suffix, connection-specific suffix, devolution of primary suffix]. - // Primary suffix can be set by policy (primary_dns_suffix) or - // user setting (tcpip_domain). - // - // The policy (primary_dns_suffix) can be edited via Group Policy Editor - // (gpedit.msc) at Local Computer Policy => Computer Configuration - // => Administrative Template => Network => DNS Client => Primary DNS Suffix. - // - // The user setting (tcpip_domain) can be configurred at Computer Name in - // System Settings - std::string primary_suffix; - if ((settings.primary_dns_suffix.set && - ParseDomainASCII(settings.primary_dns_suffix.value, &primary_suffix)) || - (settings.tcpip_domain.set && - ParseDomainASCII(settings.tcpip_domain.value, &primary_suffix))) { - // Primary suffix goes in front. - config->search.insert(config->search.begin(), primary_suffix); - } else { - return CONFIG_PARSE_WIN_OK; // No primary suffix, hence no devolution. - } - - // Devolution is determined by precedence: policy > dnscache > tcpip. - // |enabled|: UseDomainNameDevolution and |level|: DomainNameDevolutionLevel - // are overridden independently. - DnsSystemSettings::DevolutionSetting devolution = settings.policy_devolution; - - if (!devolution.enabled.set) - devolution.enabled = settings.dnscache_devolution.enabled; - if (!devolution.enabled.set) - devolution.enabled = settings.tcpip_devolution.enabled; - if (devolution.enabled.set && (devolution.enabled.value == 0)) - return CONFIG_PARSE_WIN_OK; // Devolution disabled. - - // By default devolution is enabled. - - if (!devolution.level.set) - devolution.level = settings.dnscache_devolution.level; - if (!devolution.level.set) - devolution.level = settings.tcpip_devolution.level; - - // After the recent update, Windows will try to determine a safe default - // value by comparing the forest root domain (FRD) to the primary suffix. - // See http://support.microsoft.com/kb/957579 for details. - // For now, if the level is not set, we disable devolution, assuming that - // we will fallback to the system getaddrinfo anyway. This might cause - // performance loss for resolutions which depend on the system default - // devolution setting. - // - // If the level is explicitly set below 2, devolution is disabled. - if (!devolution.level.set || devolution.level.value < 2) - return CONFIG_PARSE_WIN_OK; // Devolution disabled. - - // Devolve the primary suffix. This naive logic matches the observed - // behavior (see also ParseSearchList). If a suffix is not valid, it will be - // discarded when the fully-qualified name is converted to DNS format. - - unsigned num_dots = std::count(primary_suffix.begin(), - primary_suffix.end(), '.'); - - for (size_t offset = 0; num_dots >= devolution.level.value; --num_dots) { - offset = primary_suffix.find('.', offset + 1); - config->search.push_back(primary_suffix.substr(offset + 1)); - } - return CONFIG_PARSE_WIN_OK; + ConfigureSuffixSearch(settings, config); + return result; } // Watches registry and HOSTS file for changes. Must live on a thread which @@ -606,7 +635,8 @@ class DnsConfigServiceWin::ConfigReader : public SerialWorker { ConfigParseWinResult result = ReadSystemSettings(&settings); if (result == CONFIG_PARSE_WIN_OK) result = ConvertSettingsToDnsConfig(settings, &dns_config_); - success_ = (result == CONFIG_PARSE_WIN_OK); + success_ = (result == CONFIG_PARSE_WIN_OK || + result == CONFIG_PARSE_WIN_UNHANDLED_OPTIONS); UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParseWin", result, CONFIG_PARSE_WIN_MAX); UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_); diff --git a/net/dns/dns_config_service_win.h b/net/dns/dns_config_service_win.h index 06fc0d9663..9503dc8593 100644 --- a/net/dns/dns_config_service_win.h +++ b/net/dns/dns_config_service_win.h @@ -34,19 +34,6 @@ namespace net { namespace internal { -// Registry key paths. -const wchar_t* const kTcpipPath = - L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; -const wchar_t* const kTcpip6Path = - L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters"; -const wchar_t* const kDnscachePath = - L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters"; -const wchar_t* const kPolicyPath = - L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient"; - -// Returns the path to the HOSTS file. -base::FilePath GetHostsPath(); - // Parses |value| as search list (comma-delimited list of domain names) from // a registry key and stores it in |out|. Returns true on success. Empty // entries (e.g., "chromium.org,,org") terminate the list. Non-ascii hostnames @@ -98,6 +85,10 @@ struct NET_EXPORT_PRIVATE DnsSystemSettings { // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\AppendToMultiLabelName RegDword append_to_multi_label_name; + + // True when the Name Resolution Policy Table (NRPT) has at least one rule: + // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig\Rule* + bool have_name_resolution_policy; }; enum ConfigParseWinResult { @@ -113,6 +104,7 @@ enum ConfigParseWinResult { CONFIG_PARSE_WIN_READ_PRIMARY_SUFFIX, CONFIG_PARSE_WIN_BAD_ADDRESS, CONFIG_PARSE_WIN_NO_NAMESERVERS, + CONFIG_PARSE_WIN_UNHANDLED_OPTIONS, CONFIG_PARSE_WIN_MAX // Bounding values for enumeration. }; diff --git a/net/dns/dns_config_service_win_unittest.cc b/net/dns/dns_config_service_win_unittest.cc index b28b8e9554..3f3e4ed1e3 100644 --- a/net/dns/dns_config_service_win_unittest.cc +++ b/net/dns/dns_config_service_win_unittest.cc @@ -4,6 +4,7 @@ #include "net/dns/dns_config_service_win.h" +#include "base/basictypes.h" #include "base/logging.h" #include "base/win/windows_version.h" #include "net/dns/dns_protocol.h" @@ -420,10 +421,46 @@ TEST(DnsConfigServiceWinTest, AppendToMultiLabelName) { DnsConfig config; EXPECT_EQ(internal::CONFIG_PARSE_WIN_OK, internal::ConvertSettingsToDnsConfig(settings, &config)); - EXPECT_EQ(config.append_to_multi_label_name, t.expected_output); + EXPECT_EQ(t.expected_output, config.append_to_multi_label_name); } } +// Setting have_name_resolution_policy_table should set unhandled_options. +TEST(DnsConfigServiceWinTest, HaveNRPT) { + AdapterInfo infos[2] = { + { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } }, + { 0 }, + }; + + const struct TestCase { + bool have_nrpt; + bool unhandled_options; + internal::ConfigParseWinResult result; + } cases[] = { + { false, false, internal::CONFIG_PARSE_WIN_OK }, + { true, true, internal::CONFIG_PARSE_WIN_UNHANDLED_OPTIONS }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + const TestCase& t = cases[i]; + internal::DnsSystemSettings settings = { + CreateAdapterAddresses(infos), + { false }, { false }, { false }, { false }, + { { false }, { false } }, + { { false }, { false } }, + { { false }, { false } }, + { false }, + t.have_nrpt, + }; + DnsConfig config; + EXPECT_EQ(t.result, + internal::ConvertSettingsToDnsConfig(settings, &config)); + EXPECT_EQ(t.unhandled_options, config.unhandled_options); + EXPECT_EQ(t.have_nrpt, config.use_local_ipv6); + } +} + + } // namespace } // namespace net diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc index 52c6f93bb5..e4098d003b 100644 --- a/net/dns/host_resolver_impl.cc +++ b/net/dns/host_resolver_impl.cc @@ -1455,8 +1455,12 @@ class HostResolverImpl::Job : public PrioritizedDispatcher::Job, queue_time_after_change); } + bool system_only = + (key_.host_resolver_flags & HOST_RESOLVER_SYSTEM_ONLY) != 0; + // Caution: Job::Start must not complete synchronously. - if (had_dns_config_ && !ResemblesMulticastDNSName(key_.hostname)) { + if (!system_only && had_dns_config_ && + !ResemblesMulticastDNSName(key_.hostname)) { StartDnsTask(); } else { StartProcTask(); @@ -1799,6 +1803,7 @@ HostResolverImpl::HostResolverImpl( received_dns_config_(false), num_dns_failures_(0), probe_ipv6_support_(true), + use_local_ipv6_(false), resolved_known_ipv6_hostname_(false), additional_resolver_flags_(0), fallback_to_proctask_(true) { @@ -1824,12 +1829,12 @@ HostResolverImpl::HostResolverImpl( EnsureDnsReloaderInit(); #endif - // TODO(szym): Remove when received_dns_config_ is removed, once - // http://crbug.com/137914 is resolved. { DnsConfig dns_config; NetworkChangeNotifier::GetDnsConfig(&dns_config); received_dns_config_ = dns_config.IsValid(); + // Conservatively assume local IPv6 is needed when DnsConfig is not valid. + use_local_ipv6_ = !dns_config.IsValid() || dns_config.use_local_ipv6; } fallback_to_proctask_ = !ConfigureAsyncDnsNoFallbackFieldTrial(); @@ -2142,7 +2147,7 @@ HostResolverImpl::Key HostResolverImpl::GetEffectiveKeyForRequest( AddressFamily effective_address_family = info.address_family(); if (info.address_family() == ADDRESS_FAMILY_UNSPECIFIED) { - if (probe_ipv6_support_) { + if (probe_ipv6_support_ && !use_local_ipv6_) { base::TimeTicks start_time = base::TimeTicks::Now(); // Google DNS address. const uint8 kIPv6Address[] = @@ -2266,6 +2271,8 @@ void HostResolverImpl::OnDNSChanged() { // TODO(szym): Remove once http://crbug.com/137914 is resolved. received_dns_config_ = dns_config.IsValid(); + // Conservatively assume local IPv6 is needed when DnsConfig is not valid. + use_local_ipv6_ = !dns_config.IsValid() || dns_config.use_local_ipv6; num_dns_failures_ = 0; diff --git a/net/dns/host_resolver_impl.h b/net/dns/host_resolver_impl.h index 2d93258b16..e7f5f6541c 100644 --- a/net/dns/host_resolver_impl.h +++ b/net/dns/host_resolver_impl.h @@ -279,6 +279,10 @@ class NET_EXPORT HostResolverImpl // explicit setting in |default_address_family_| is used. bool probe_ipv6_support_; + // True if DnsConfigService detected that system configuration depends on + // local IPv6 connectivity. Disables probing. + bool use_local_ipv6_; + // True iff ProcTask has successfully resolved a hostname known to have IPv6 // addresses using ADDRESS_FAMILY_UNSPECIFIED. Reset on IP address change. bool resolved_known_ipv6_hostname_; diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc index 24a00a9d69..c314f80c24 100644 --- a/net/dns/host_resolver_impl_unittest.cc +++ b/net/dns/host_resolver_impl_unittest.cc @@ -1563,6 +1563,24 @@ TEST_F(HostResolverImplDnsTest, BypassDnsTask) { EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i; } +TEST_F(HostResolverImplDnsTest, SystemOnlyBypassesDnsTask) { + ChangeDnsConfig(CreateValidDnsConfig()); + + proc_->AddRuleForAllFamilies(std::string(), std::string()); + + HostResolver::RequestInfo info_bypass(HostPortPair("ok", 80)); + info_bypass.set_host_resolver_flags(HOST_RESOLVER_SYSTEM_ONLY); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(info_bypass, MEDIUM)->Resolve()); + + HostResolver::RequestInfo info(HostPortPair("ok", 80)); + EXPECT_EQ(ERR_IO_PENDING, CreateRequest(info, MEDIUM)->Resolve()); + + proc_->SignalMultiple(requests_.size()); + + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[0]->WaitForResult()); + EXPECT_EQ(OK, requests_[1]->WaitForResult()); +} + TEST_F(HostResolverImplDnsTest, DisableDnsClientOnPersistentFailure) { ChangeDnsConfig(CreateValidDnsConfig()); diff --git a/net/dns/mdns_client.cc b/net/dns/mdns_client.cc index 631b01a706..d0273c5784 100644 --- a/net/dns/mdns_client.cc +++ b/net/dns/mdns_client.cc @@ -4,14 +4,43 @@ #include "net/dns/mdns_client.h" +#include "net/dns/dns_protocol.h" #include "net/dns/mdns_client_impl.h" namespace net { +namespace { + +const char kMDnsMulticastGroupIPv4[] = "224.0.0.251"; +const char kMDnsMulticastGroupIPv6[] = "FF02::FB"; + +IPEndPoint GetMDnsIPEndPoint(const char* address) { + IPAddressNumber multicast_group_number; + bool success = ParseIPLiteralToNumber(address, + &multicast_group_number); + DCHECK(success); + return IPEndPoint(multicast_group_number, + dns_protocol::kDefaultPortMulticast); +} + +} // namespace + // static scoped_ptr<MDnsClient> MDnsClient::CreateDefault() { return scoped_ptr<MDnsClient>( new MDnsClientImpl(MDnsConnection::SocketFactory::CreateDefault())); } +IPEndPoint GetMDnsIPEndPoint(AddressFamily address_family) { + switch (address_family) { + case ADDRESS_FAMILY_IPV4: + return GetMDnsIPEndPoint(kMDnsMulticastGroupIPv4); + case ADDRESS_FAMILY_IPV6: + return GetMDnsIPEndPoint(kMDnsMulticastGroupIPv6); + default: + NOTREACHED(); + return IPEndPoint(); + } +} + } // namespace net diff --git a/net/dns/mdns_client.h b/net/dns/mdns_client.h index f12c6e292c..71006d69b0 100644 --- a/net/dns/mdns_client.h +++ b/net/dns/mdns_client.h @@ -9,6 +9,7 @@ #include <vector> #include "base/callback.h" +#include "net/base/ip_endpoint.h" #include "net/dns/dns_query.h" #include "net/dns/dns_response.h" #include "net/dns/record_parsed.h" @@ -154,5 +155,7 @@ class NET_EXPORT MDnsClient { static scoped_ptr<MDnsClient> CreateDefault(); }; +IPEndPoint NET_EXPORT GetMDnsIPEndPoint(AddressFamily address_family); + } // namespace net #endif // NET_DNS_MDNS_CLIENT_H_ diff --git a/net/dns/mdns_client_impl.cc b/net/dns/mdns_client_impl.cc index d8e5e9b760..4a3046847d 100644 --- a/net/dns/mdns_client_impl.cc +++ b/net/dns/mdns_client_impl.cc @@ -24,8 +24,6 @@ namespace net { namespace { -const char kMDnsMulticastGroupIPv4[] = "224.0.0.251"; -const char kMDnsMulticastGroupIPv6[] = "FF02::FB"; const unsigned MDnsTransactionTimeoutSeconds = 3; } @@ -99,11 +97,9 @@ int MDnsConnection::SocketHandler::Bind() { MDnsConnection::MDnsConnection(MDnsConnection::SocketFactory* socket_factory, MDnsConnection::Delegate* delegate) - : socket_handler_ipv4_(this, - GetMDnsIPEndPoint(kMDnsMulticastGroupIPv4), + : socket_handler_ipv4_(this, GetMDnsIPEndPoint(ADDRESS_FAMILY_IPV4), socket_factory), - socket_handler_ipv6_(this, - GetMDnsIPEndPoint(kMDnsMulticastGroupIPv6), + socket_handler_ipv6_(this, GetMDnsIPEndPoint(ADDRESS_FAMILY_IPV6), socket_factory), delegate_(delegate) { } @@ -146,15 +142,6 @@ void MDnsConnection::OnError(SocketHandler* loop, delegate_->OnConnectionError(error); } -IPEndPoint MDnsConnection::GetMDnsIPEndPoint(const char* address) { - IPAddressNumber multicast_group_number; - bool success = ParseIPLiteralToNumber(address, - &multicast_group_number); - DCHECK(success); - return IPEndPoint(multicast_group_number, - dns_protocol::kDefaultPortMulticast); -} - void MDnsConnection::OnDatagramReceived( DnsResponse* response, const IPEndPoint& recv_addr, diff --git a/net/dns/mdns_client_impl.h b/net/dns/mdns_client_impl.h index 86a7ba9b36..cbb9f6da3e 100644 --- a/net/dns/mdns_client_impl.h +++ b/net/dns/mdns_client_impl.h @@ -85,8 +85,6 @@ class NET_EXPORT_PRIVATE MDnsConnection { void OnError(SocketHandler* loop, int error); - IPEndPoint GetMDnsIPEndPoint(const char* address); - SocketHandler socket_handler_ipv4_; SocketHandler socket_handler_ipv6_; diff --git a/net/dns/mock_host_resolver.cc b/net/dns/mock_host_resolver.cc index a1d46d06d3..ff5ffbc2f7 100644 --- a/net/dns/mock_host_resolver.cc +++ b/net/dns/mock_host_resolver.cc @@ -347,12 +347,14 @@ int RuleBasedHostResolverProc::Resolve(const std::string& host, bool matches_address_family = r->address_family == ADDRESS_FAMILY_UNSPECIFIED || r->address_family == address_family; + // Ignore HOST_RESOLVER_SYSTEM_ONLY, since it should have no impact on + // whether a rule matches. + HostResolverFlags flags = host_resolver_flags & ~HOST_RESOLVER_SYSTEM_ONLY; // Flags match if all of the bitflags in host_resolver_flags are enabled // in the rule's host_resolver_flags. However, the rule may have additional // flags specified, in which case the flags should still be considered a // match. - bool matches_flags = (r->host_resolver_flags & host_resolver_flags) == - host_resolver_flags; + bool matches_flags = (r->host_resolver_flags & flags) == flags; if (matches_flags && matches_address_family && MatchPattern(host, r->host_pattern)) { if (r->latency_ms != 0) { diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc index 4cdcbb6cd8..49591da8ff 100644 --- a/net/http/http_cache.cc +++ b/net/http/http_cache.cc @@ -392,7 +392,7 @@ void HttpCache::CloseAllConnections() { HttpNetworkSession* session = network->GetSession(); if (session) session->CloseAllConnections(); - } +} void HttpCache::CloseIdleConnections() { net::HttpNetworkLayer* network = @@ -595,6 +595,9 @@ int HttpCache::AsyncDoomEntry(const std::string& key, Transaction* trans) { } void HttpCache::DoomMainEntryForUrl(const GURL& url) { + if (!disk_cache_) + return; + HttpRequestInfo temp_info; temp_info.url = url; temp_info.method = "GET"; diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc index a69bcc0b43..a710afb3c3 100644 --- a/net/http/http_cache_transaction.cc +++ b/net/http/http_cache_transaction.cc @@ -449,10 +449,13 @@ bool HttpCache::Transaction::GetFullRequestHeaders( void HttpCache::Transaction::DoneReading() { if (cache_.get() && entry_) { - DCHECK(reading_); DCHECK_NE(mode_, UPDATE); - if (mode_ & WRITE) + if (mode_ & WRITE) { DoneWritingToEntry(true); + } else { + cache_->DoneReadingFromEntry(entry_, this); + entry_ = NULL; + } } } @@ -952,7 +955,7 @@ int HttpCache::Transaction::DoSuccessfulSendRequest() { mode_ = NONE; } - if (mode_ != NONE && request_->method == "POST" && + if (request_->method == "POST" && NonErrorResponse(new_response->headers->response_code())) { cache_->DoomMainEntryForUrl(request_->url); } @@ -1340,10 +1343,6 @@ int HttpCache::Transaction::DoTruncateCachedMetadataComplete(int result) { } } - // If this response is a redirect, then we can stop writing now. (We don't - // need to cache the response body of a redirect.) - if (response_.headers->IsRedirect(NULL)) - DoneWritingToEntry(true); next_state_ = STATE_PARTIAL_HEADERS_RECEIVED; return OK; } diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc index 01909ae3c5..cc4874f624 100644 --- a/net/http/http_cache_unittest.cc +++ b/net/http/http_cache_unittest.cc @@ -2895,6 +2895,67 @@ TEST(HttpCache, SimplePOST_Invalidate_205) { RemoveMockTransaction(&transaction); } +// Tests that a successful POST invalidates a previously cached GET, even when +// there is no upload identifier. +TEST(HttpCache, SimplePOST_NoUploadId_Invalidate_205) { + MockHttpCache cache; + + MockTransaction transaction(kSimpleGET_Transaction); + AddMockTransaction(&transaction); + MockHttpRequest req1(transaction); + + // Attempt to populate the cache. + RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + ScopedVector<net::UploadElementReader> element_readers; + element_readers.push_back(new net::UploadBytesElementReader("hello", 5)); + net::UploadDataStream upload_data_stream(&element_readers, 0); + + transaction.method = "POST"; + transaction.status = "HTTP/1.1 205 No Content"; + MockHttpRequest req2(transaction); + req2.upload_data_stream = &upload_data_stream; + + RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL); + + EXPECT_EQ(3, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); + RemoveMockTransaction(&transaction); +} + +// Tests that processing a POST before creating the backend doesn't crash. +TEST(HttpCache, SimplePOST_NoUploadId_NoBackend) { + // This will initialize a cache object with NULL backend. + MockBlockingBackendFactory* factory = new MockBlockingBackendFactory(); + factory->set_fail(true); + factory->FinishCreation(); + MockHttpCache cache(factory); + + ScopedVector<net::UploadElementReader> element_readers; + element_readers.push_back(new net::UploadBytesElementReader("hello", 5)); + net::UploadDataStream upload_data_stream(&element_readers, 0); + + MockTransaction transaction(kSimplePOST_Transaction); + AddMockTransaction(&transaction); + MockHttpRequest req(transaction); + req.upload_data_stream = &upload_data_stream; + + RunTransactionTestWithRequest(cache.http_cache(), transaction, req, NULL); + + RemoveMockTransaction(&transaction); +} + // Tests that we don't invalidate entries as a result of a failed POST. TEST(HttpCache, SimplePOST_DontInvalidate_100) { MockHttpCache cache; @@ -5333,7 +5394,7 @@ TEST(HttpCache, CachedRedirect) { MockHttpRequest request(kTestTransaction); net::TestCompletionCallback callback; - // write to the cache + // Write to the cache. { scoped_ptr<net::HttpTransaction> trans; int rv = cache.http_cache()->CreateTransaction( @@ -5355,6 +5416,9 @@ TEST(HttpCache, CachedRedirect) { info->headers->EnumerateHeader(NULL, "Location", &location); EXPECT_EQ(location, "http://www.bar.com/"); + // Mark the transaction as completed so it is cached. + trans->DoneReading(); + // Destroy transaction when going out of scope. We have not actually // read the response body -- want to test that it is still getting cached. } @@ -5362,7 +5426,12 @@ TEST(HttpCache, CachedRedirect) { EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); - // read from the cache + // Active entries in the cache are not retired synchronously. Make + // sure the next run hits the MockHttpCache and open_count is + // correct. + base::MessageLoop::current()->RunUntilIdle(); + + // Read from the cache. { scoped_ptr<net::HttpTransaction> trans; int rv = cache.http_cache()->CreateTransaction( @@ -5384,6 +5453,9 @@ TEST(HttpCache, CachedRedirect) { info->headers->EnumerateHeader(NULL, "Location", &location); EXPECT_EQ(location, "http://www.bar.com/"); + // Mark the transaction as completed so it is cached. + trans->DoneReading(); + // Destroy transaction when going out of scope. We have not actually // read the response body -- want to test that it is still getting cached. } @@ -5838,6 +5910,39 @@ TEST(HttpCache, FilterCompletion) { EXPECT_EQ(1, cache.disk_cache()->create_count()); } +// Tests that we don't mark entries as truncated and release the cache +// entry when DoneReading() is called before any Read() calls, such as +// for a redirect. +TEST(HttpCache, DoneReading) { + MockHttpCache cache; + net::TestCompletionCallback callback; + + ScopedMockTransaction transaction(kSimpleGET_Transaction); + transaction.data = ""; + + scoped_ptr<net::HttpTransaction> trans; + int rv = cache.http_cache()->CreateTransaction( + net::DEFAULT_PRIORITY, &trans, NULL); + EXPECT_EQ(net::OK, rv); + + MockHttpRequest request(transaction); + rv = trans->Start(&request, callback.callback(), net::BoundNetLog()); + EXPECT_EQ(net::OK, callback.GetResult(rv)); + + trans->DoneReading(); + // Leave the transaction around. + + // Make sure that the ActiveEntry is gone. + base::MessageLoop::current()->RunUntilIdle(); + + // Read from the cache. This should not deadlock. + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + // Tests that we stop caching when told. TEST(HttpCache, StopCachingDeletesEntry) { MockHttpCache cache; diff --git a/net/http/http_transaction.h b/net/http/http_transaction.h index ec3fc088a0..d44050080b 100644 --- a/net/http/http_transaction.h +++ b/net/http/http_transaction.h @@ -109,6 +109,9 @@ class NET_EXPORT_PRIVATE HttpTransaction { // of the stream. This is equivalent to performing an extra Read() at the end // that should return 0 bytes. This method should not be called if the // transaction is busy processing a previous operation (like a pending Read). + // + // DoneReading may also be called before the first Read() to notify that the + // entire response body is to be ignored (e.g., in a redirect). virtual void DoneReading() = 0; // Returns the response info for this transaction or NULL if the response diff --git a/net/http/transport_security_state_static.certs b/net/http/transport_security_state_static.certs index 2fdd9af801..90a11c97f1 100644 --- a/net/http/transport_security_state_static.certs +++ b/net/http/transport_security_state_static.certs @@ -106,6 +106,44 @@ q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv -----END CERTIFICATE----- +VeriSignClass3SSPIntermediateCA +-----BEGIN CERTIFICATE----- +MIIGVDCCBTygAwIBAgIQGYH0QFTS4OtUK7v7RciQfjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzMwHhcNMTEwMTA3MDAwMDAwWhcNMTMxMjMxMjM1OTU5WjB2MQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxLTArBgNVBAMTJFZlcmlTaWduIENsYXNzIDMg +U1NQIEludGVybWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANfMaBonchSI7reVYNNe3hhSwUY/fbEmnDwCoonR2MFXsQkP9n8yNaU1nhRT +Eovg4zAetI+e0bDAt9/0Lw/n1x/FdiTTPdMN6SxKLqc8z7xql0MZ+MBzyhsstmIB +RmJWkGisFFAZ51BYB/k9AfLtHjQnvc1yHYBgo0ySG2a6ejkJd2r6U/dvjgbu2dSj +Eo5XJGl//xSSLKs4HPhkuAsdZr2HqPiBwjlFpCd//Fs8he43JBI60+bRSBiUKpQC +ssu6oAj2rvKcy2AMTvjIAlz9Iy3B92fB1Q1JxpbWcLochUca7/NFQTkKMaVeBXxy +i2D+SFWfuBLtcl7p/kbtwqfiDbMCAwEAAaOCAocwggKDMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMIHoBgNVHSAEgeAwgd0wDwYNYIZIAYb4RQEHFwMB +BjAPBg1ghkgBhvhFAQcXAwEHMA8GDWCGSAGG+EUBBxcDAQgwDwYNYIZIAYb4RQEH +FwMBDTAPBg1ghkgBhvhFAQcXAwEOMA8GDWCGSAGG+EUBBxcDAQ8wDwYNYIZIAYb4 +RQEHFwMBETAPBg1ghkgBhvhFAQcXAwEUMA8GDWCGSAGG+EUBBxcDARcwDwYNYIZI +AYb4RQEHFwMBGDAPBg1ghkgBhvhFAQcXAwEZMA8GDWCGSAGG+EUBBxcDARowDwYN +YIZIAYb4RQEHFwMBGzA4BgNVHR8EMTAvMC2gK6AphidodHRwOi8vc3NwLWNybC52 +ZXJpc2lnbi5jb20vcGNhMy1nMy5jcmwwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMT +EFZlcmlTaWduTVBLSS0xLTgwHQYDVR0OBBYEFCwx/8HOq/lN6IkVwGry5atCfUL6 +MIHxBgNVHSMEgekwgeahgdCkgc0wgcoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5W +ZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazE6 +MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMyBQdWJsaWMgUHJp +bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczghEAm34GSaM+YrnV7pBI +cSnvVzANBgkqhkiG9w0BAQUFAAOCAQEAIS19vzG9j+KXiQ0G1bOuJCeiD9KKW1+8 +69cutvgDf3hEvrw39Gr2ek3cAdso7dvwW0Z17muzpHV08gWTjjKba8mBzjijmgr9 +I2vE2K/Ls72WJvTDUjCAHfBJKeK1q8v7xv1xtf2Jz7BV8sNH3kDB7jhhE++8zLVC +gyFilU0KZfhBpLPVlVYnLozRdvsHfNnO/JskJvRqhDYbeC5ginQT0m5sTQiyTYqL +/IU+i82TxANXjC7syl0dfcGr8pJ85T9bF1EZLxdgikAYLKPGTuXMwOGqT5bR0dKD +lWShiGTRl7HW0KJMg05F0HjOnYpdOYGaFrQghecrkcrRPRevSdFVHQ== +-----END CERTIFICATE----- + EquifaxSecureCA -----BEGIN CERTIFICATE----- MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV diff --git a/net/http/transport_security_state_static.h b/net/http/transport_security_state_static.h index eb72813753..36289ccd4d 100644 --- a/net/http/transport_security_state_static.h +++ b/net/http/transport_security_state_static.h @@ -46,6 +46,10 @@ static const char kSPKIHash_ThawteSGCCA[] = "\x87\x31\xea\x0e\x3d\xf5\xe8\x70\x3e\x83" "\x72\x57\x77\xa9\x65\x3b\x3b\xfa\x5e\x14"; +static const char kSPKIHash_VeriSignClass3SSPIntermediateCA[] = + "\x99\x6a\x20\x6a\x85\x57\x62\xcb\x9a\xf2" + "\x02\x37\xb3\xc0\x69\x5d\xa9\x1e\xc2\x22"; + static const char kSPKIHash_EquifaxSecureCA[] = "\x48\xe6\x68\xf9\x2b\xd2\xb2\x95\xd7\x47" "\xd8\x23\x20\x10\x4f\x33\x98\x90\x9f\xd4"; @@ -277,6 +281,7 @@ static const char* const kGoogleRejectedCerts[] = { kSPKIHash_TCTrustCenter, kSPKIHash_Vodafone, kSPKIHash_ThawteSGCCA, + kSPKIHash_VeriSignClass3SSPIntermediateCA, NULL, }; #define kGooglePins { \ @@ -862,6 +867,9 @@ static const struct HSTSPreload kPreloadedSTS[] = { {12, false, "\006bcrook\003com", true, kNoPins, DOMAIN_NOT_PINNED }, {17, true, "\004wiki\006python\003org", true, kNoPins, DOMAIN_NOT_PINNED }, {9, false, "\004lumi\002do", true, kNoPins, DOMAIN_NOT_PINNED }, + {22, true, "\020appseccalifornia\003org", true, kNoPins, DOMAIN_NOT_PINNED }, + {17, true, "\013crowdcurity\003com", true, kNoPins, DOMAIN_NOT_PINNED }, + {19, true, "\013saturngames\002co\002uk", true, kNoPins, DOMAIN_NOT_PINNED }, }; static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json index 085e872109..8e8e7ee86a 100644 --- a/net/http/transport_security_state_static.json +++ b/net/http/transport_security_state_static.json @@ -57,7 +57,8 @@ "Intel", "TCTrustCenter", "Vodafone", - "ThawteSGCCA" + "ThawteSGCCA", + "VeriSignClass3SSPIntermediateCA" ] }, { @@ -640,6 +641,9 @@ { "name": "bcrook.com", "mode": "force-https" }, { "name": "wiki.python.org", "include_subdomains": true, "mode": "force-https" }, { "name": "lumi.do", "mode": "force-https" }, + { "name": "appseccalifornia.org", "include_subdomains": true, "mode": "force-https" }, + { "name": "crowdcurity.com", "include_subdomains": true, "mode": "force-https" }, + { "name": "saturngames.co.uk", "include_subdomains": true, "mode": "force-https" }, // Entries that are only valid if the client supports SNI. { "name": "gmail.com", "mode": "force-https", "pins": "google", "snionly": true }, diff --git a/net/http_server.target.darwin-arm.mk b/net/http_server.target.darwin-arm.mk index 343421b557..5effb06f23 100644 --- a/net/http_server.target.darwin-arm.mk +++ b/net/http_server.target.darwin-arm.mk @@ -65,7 +65,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -150,7 +150,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/http_server.target.darwin-mips.mk b/net/http_server.target.darwin-mips.mk index dfc92f44fa..6694afb87d 100644 --- a/net/http_server.target.darwin-mips.mk +++ b/net/http_server.target.darwin-mips.mk @@ -64,7 +64,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -148,7 +148,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/http_server.target.darwin-x86.mk b/net/http_server.target.darwin-x86.mk index 5c8201f340..9649d4fcbc 100644 --- a/net/http_server.target.darwin-x86.mk +++ b/net/http_server.target.darwin-x86.mk @@ -67,7 +67,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -154,7 +154,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/http_server.target.linux-arm.mk b/net/http_server.target.linux-arm.mk index 343421b557..5effb06f23 100644 --- a/net/http_server.target.linux-arm.mk +++ b/net/http_server.target.linux-arm.mk @@ -65,7 +65,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -150,7 +150,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/http_server.target.linux-mips.mk b/net/http_server.target.linux-mips.mk index dfc92f44fa..6694afb87d 100644 --- a/net/http_server.target.linux-mips.mk +++ b/net/http_server.target.linux-mips.mk @@ -64,7 +64,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -148,7 +148,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/http_server.target.linux-x86.mk b/net/http_server.target.linux-x86.mk index 5c8201f340..9649d4fcbc 100644 --- a/net/http_server.target.linux-x86.mk +++ b/net/http_server.target.linux-x86.mk @@ -67,7 +67,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -154,7 +154,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net.gyp b/net/net.gyp index dae94a22e9..c7a49dd8c5 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -390,8 +390,10 @@ 'disk_cache/tracing_cache_backend.h', 'disk_cache/simple/simple_backend_impl.cc', 'disk_cache/simple/simple_backend_impl.h', + 'disk_cache/simple/simple_backend_version.h', 'disk_cache/simple/simple_entry_format.cc', 'disk_cache/simple/simple_entry_format.h', + 'disk_cache/simple/simple_entry_format_history.h', 'disk_cache/simple/simple_entry_impl.cc', 'disk_cache/simple/simple_entry_impl.h', 'disk_cache/simple/simple_entry_operation.cc', @@ -409,6 +411,8 @@ 'disk_cache/simple/simple_synchronous_entry.h', 'disk_cache/simple/simple_util.cc', 'disk_cache/simple/simple_util.h', + 'disk_cache/simple/simple_version_upgrade.cc', + 'disk_cache/simple/simple_version_upgrade.h', 'disk_cache/flash/flash_entry_impl.cc', 'disk_cache/flash/flash_entry_impl.h', 'disk_cache/flash/format.h', @@ -422,6 +426,8 @@ 'disk_cache/flash/segment.h', 'disk_cache/flash/storage.cc', 'disk_cache/flash/storage.h', + 'disk_cache/v3/block_bitmaps.cc', + 'disk_cache/v3/block_bitmaps.h', 'disk_cache/v3/disk_format_v3.h', 'dns/address_sorter.h', 'dns/address_sorter_posix.cc', @@ -823,6 +829,8 @@ 'quic/quic_framer.h', 'quic/quic_http_stream.cc', 'quic/quic_http_stream.h', + 'quic/quic_http_utils.cc', + 'quic/quic_http_utils.h', 'quic/quic_packet_creator.cc', 'quic/quic_packet_creator.h', 'quic/quic_packet_generator.cc', @@ -835,6 +843,8 @@ 'quic/quic_reliable_client_stream.h', 'quic/quic_sent_entropy_manager.cc', 'quic/quic_sent_entropy_manager.h', + 'quic/quic_sent_packet_manager.cc', + 'quic/quic_sent_packet_manager.h', 'quic/quic_session.cc', 'quic/quic_session.h', 'quic/quic_spdy_compressor.cc', @@ -905,14 +915,11 @@ 'socket/stream_socket.h', 'socket/tcp_client_socket.cc', 'socket/tcp_client_socket.h', - 'socket/tcp_client_socket_libevent.cc', - 'socket/tcp_client_socket_libevent.h', - 'socket/tcp_client_socket_win.cc', - 'socket/tcp_client_socket_win.h', 'socket/tcp_listen_socket.cc', 'socket/tcp_listen_socket.h', 'socket/tcp_server_socket.cc', 'socket/tcp_server_socket.h', + 'socket/tcp_socket.cc', 'socket/tcp_socket.h', 'socket/tcp_socket_libevent.cc', 'socket/tcp_socket_libevent.h', @@ -1103,6 +1110,10 @@ 'websockets/websocket_deflater.cc', 'websockets/websocket_errors.cc', 'websockets/websocket_errors.h', + 'websockets/websocket_extension.cc', + 'websockets/websocket_extension.h', + 'websockets/websocket_extension_parser.cc', + 'websockets/websocket_extension_parser.h', 'websockets/websocket_frame.cc', 'websockets/websocket_frame.h', 'websockets/websocket_frame_parser.cc', @@ -1374,8 +1385,6 @@ [ 'OS == "win"', { 'sources!': [ 'http/http_auth_handler_ntlm_portable.cc', - 'socket/tcp_client_socket_libevent.cc', - 'socket/tcp_client_socket_libevent.h', 'socket/tcp_socket_libevent.cc', 'socket/tcp_socket_libevent.h', 'ssl/client_cert_store_impl_nss.cc', @@ -1577,11 +1586,13 @@ 'disk_cache/simple/simple_test_util.h', 'disk_cache/simple/simple_test_util.cc', 'disk_cache/simple/simple_util_unittest.cc', + 'disk_cache/simple/simple_version_upgrade_unittest.cc', 'disk_cache/storage_block_unittest.cc', 'disk_cache/flash/log_store_entry_unittest.cc', 'disk_cache/flash/log_store_unittest.cc', 'disk_cache/flash/segment_unittest.cc', 'disk_cache/flash/storage_unittest.cc', + 'disk_cache/v3/block_bitmaps_unittest.cc', 'dns/address_sorter_posix_unittest.cc', 'dns/address_sorter_unittest.cc', 'dns/dns_config_service_posix_unittest.cc', @@ -1767,6 +1778,7 @@ 'quic/quic_fec_group_test.cc', 'quic/quic_framer_test.cc', 'quic/quic_http_stream_test.cc', + 'quic/quic_http_utils_test.cc', 'quic/quic_network_transaction_unittest.cc', 'quic/quic_packet_creator_test.cc', 'quic/quic_packet_generator_test.cc', @@ -1774,6 +1786,7 @@ 'quic/quic_received_packet_manager_test.cc', 'quic/quic_reliable_client_stream_test.cc', 'quic/quic_sent_entropy_manager_test.cc', + 'quic/quic_sent_packet_manager_test.cc', 'quic/quic_session_test.cc', 'quic/quic_spdy_compressor_test.cc', 'quic/quic_spdy_decompressor_test.cc', @@ -1875,6 +1888,7 @@ 'websockets/websocket_channel_test.cc', 'websockets/websocket_deflater_test.cc', 'websockets/websocket_errors_test.cc', + 'websockets/websocket_extension_parser_test.cc', 'websockets/websocket_frame_parser_test.cc', 'websockets/websocket_frame_test.cc', 'websockets/websocket_handshake_handler_test.cc', @@ -1908,8 +1922,10 @@ 'tools/quic/quic_in_memory_cache_test.cc', 'tools/quic/quic_reliable_client_stream_test.cc', 'tools/quic/quic_reliable_server_stream_test.cc', + 'tools/quic/quic_server_session_test.cc', 'tools/quic/quic_server_test.cc', 'tools/quic/quic_spdy_server_stream_test.cc', + 'tools/quic/quic_time_wait_list_manager_test.cc', 'tools/quic/test_tools/http_message_test_utils.cc', 'tools/quic/test_tools/http_message_test_utils.h', 'tools/quic/test_tools/mock_epoll_server.cc', @@ -2005,6 +2021,9 @@ 'socket/ssl_client_socket_openssl_unittest.cc', 'ssl/openssl_client_key_store_unittest.cc', ], + 'sources/': [ + ['exclude', '^tools/flip_server'], + ], }, ], [ 'enable_websockets != 1', { diff --git a/net/net.target.darwin-arm.mk b/net/net.target.darwin-arm.mk index d484b247fa..3bd52d9b88 100644 --- a/net/net.target.darwin-arm.mk +++ b/net/net.target.darwin-arm.mk @@ -158,12 +158,14 @@ LOCAL_SRC_FILES := \ net/disk_cache/simple/simple_net_log_parameters.cc \ net/disk_cache/simple/simple_synchronous_entry.cc \ net/disk_cache/simple/simple_util.cc \ + net/disk_cache/simple/simple_version_upgrade.cc \ net/disk_cache/flash/flash_entry_impl.cc \ net/disk_cache/flash/internal_entry.cc \ net/disk_cache/flash/log_store.cc \ net/disk_cache/flash/log_store_entry.cc \ net/disk_cache/flash/segment.cc \ net/disk_cache/flash/storage.cc \ + net/disk_cache/v3/block_bitmaps.cc \ net/dns/address_sorter_posix.cc \ net/dns/dns_client.cc \ net/dns/dns_config_service.cc \ @@ -328,12 +330,14 @@ LOCAL_SRC_FILES := \ net/quic/quic_fec_group.cc \ net/quic/quic_framer.cc \ net/quic/quic_http_stream.cc \ + net/quic/quic_http_utils.cc \ net/quic/quic_packet_creator.cc \ net/quic/quic_packet_generator.cc \ net/quic/quic_protocol.cc \ net/quic/quic_received_packet_manager.cc \ net/quic/quic_reliable_client_stream.cc \ net/quic/quic_sent_entropy_manager.cc \ + net/quic/quic_sent_packet_manager.cc \ net/quic/quic_session.cc \ net/quic/quic_spdy_compressor.cc \ net/quic/quic_spdy_decompressor.cc \ @@ -364,9 +368,9 @@ LOCAL_SRC_FILES := \ net/socket/stream_listen_socket.cc \ net/socket/stream_socket.cc \ net/socket/tcp_client_socket.cc \ - net/socket/tcp_client_socket_libevent.cc \ net/socket/tcp_listen_socket.cc \ net/socket/tcp_server_socket.cc \ + net/socket/tcp_socket.cc \ net/socket/tcp_socket_libevent.cc \ net/socket/transport_client_socket_pool.cc \ net/socket/unix_domain_socket_posix.cc \ @@ -447,6 +451,8 @@ LOCAL_SRC_FILES := \ net/websockets/websocket_channel.cc \ net/websockets/websocket_deflater.cc \ net/websockets/websocket_errors.cc \ + net/websockets/websocket_extension.cc \ + net/websockets/websocket_extension_parser.cc \ net/websockets/websocket_frame.cc \ net/websockets/websocket_frame_parser.cc \ net/websockets/websocket_handshake_constants.cc \ @@ -492,7 +498,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -587,7 +593,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net.target.darwin-mips.mk b/net/net.target.darwin-mips.mk index d7a3f17ecf..b52aecc91a 100644 --- a/net/net.target.darwin-mips.mk +++ b/net/net.target.darwin-mips.mk @@ -158,12 +158,14 @@ LOCAL_SRC_FILES := \ net/disk_cache/simple/simple_net_log_parameters.cc \ net/disk_cache/simple/simple_synchronous_entry.cc \ net/disk_cache/simple/simple_util.cc \ + net/disk_cache/simple/simple_version_upgrade.cc \ net/disk_cache/flash/flash_entry_impl.cc \ net/disk_cache/flash/internal_entry.cc \ net/disk_cache/flash/log_store.cc \ net/disk_cache/flash/log_store_entry.cc \ net/disk_cache/flash/segment.cc \ net/disk_cache/flash/storage.cc \ + net/disk_cache/v3/block_bitmaps.cc \ net/dns/address_sorter_posix.cc \ net/dns/dns_client.cc \ net/dns/dns_config_service.cc \ @@ -328,12 +330,14 @@ LOCAL_SRC_FILES := \ net/quic/quic_fec_group.cc \ net/quic/quic_framer.cc \ net/quic/quic_http_stream.cc \ + net/quic/quic_http_utils.cc \ net/quic/quic_packet_creator.cc \ net/quic/quic_packet_generator.cc \ net/quic/quic_protocol.cc \ net/quic/quic_received_packet_manager.cc \ net/quic/quic_reliable_client_stream.cc \ net/quic/quic_sent_entropy_manager.cc \ + net/quic/quic_sent_packet_manager.cc \ net/quic/quic_session.cc \ net/quic/quic_spdy_compressor.cc \ net/quic/quic_spdy_decompressor.cc \ @@ -364,9 +368,9 @@ LOCAL_SRC_FILES := \ net/socket/stream_listen_socket.cc \ net/socket/stream_socket.cc \ net/socket/tcp_client_socket.cc \ - net/socket/tcp_client_socket_libevent.cc \ net/socket/tcp_listen_socket.cc \ net/socket/tcp_server_socket.cc \ + net/socket/tcp_socket.cc \ net/socket/tcp_socket_libevent.cc \ net/socket/transport_client_socket_pool.cc \ net/socket/unix_domain_socket_posix.cc \ @@ -447,6 +451,8 @@ LOCAL_SRC_FILES := \ net/websockets/websocket_channel.cc \ net/websockets/websocket_deflater.cc \ net/websockets/websocket_errors.cc \ + net/websockets/websocket_extension.cc \ + net/websockets/websocket_extension_parser.cc \ net/websockets/websocket_frame.cc \ net/websockets/websocket_frame_parser.cc \ net/websockets/websocket_handshake_constants.cc \ @@ -491,7 +497,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -585,7 +591,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net.target.darwin-x86.mk b/net/net.target.darwin-x86.mk index af07ef16a9..7be29d9c5e 100644 --- a/net/net.target.darwin-x86.mk +++ b/net/net.target.darwin-x86.mk @@ -158,12 +158,14 @@ LOCAL_SRC_FILES := \ net/disk_cache/simple/simple_net_log_parameters.cc \ net/disk_cache/simple/simple_synchronous_entry.cc \ net/disk_cache/simple/simple_util.cc \ + net/disk_cache/simple/simple_version_upgrade.cc \ net/disk_cache/flash/flash_entry_impl.cc \ net/disk_cache/flash/internal_entry.cc \ net/disk_cache/flash/log_store.cc \ net/disk_cache/flash/log_store_entry.cc \ net/disk_cache/flash/segment.cc \ net/disk_cache/flash/storage.cc \ + net/disk_cache/v3/block_bitmaps.cc \ net/dns/address_sorter_posix.cc \ net/dns/dns_client.cc \ net/dns/dns_config_service.cc \ @@ -328,12 +330,14 @@ LOCAL_SRC_FILES := \ net/quic/quic_fec_group.cc \ net/quic/quic_framer.cc \ net/quic/quic_http_stream.cc \ + net/quic/quic_http_utils.cc \ net/quic/quic_packet_creator.cc \ net/quic/quic_packet_generator.cc \ net/quic/quic_protocol.cc \ net/quic/quic_received_packet_manager.cc \ net/quic/quic_reliable_client_stream.cc \ net/quic/quic_sent_entropy_manager.cc \ + net/quic/quic_sent_packet_manager.cc \ net/quic/quic_session.cc \ net/quic/quic_spdy_compressor.cc \ net/quic/quic_spdy_decompressor.cc \ @@ -364,9 +368,9 @@ LOCAL_SRC_FILES := \ net/socket/stream_listen_socket.cc \ net/socket/stream_socket.cc \ net/socket/tcp_client_socket.cc \ - net/socket/tcp_client_socket_libevent.cc \ net/socket/tcp_listen_socket.cc \ net/socket/tcp_server_socket.cc \ + net/socket/tcp_socket.cc \ net/socket/tcp_socket_libevent.cc \ net/socket/transport_client_socket_pool.cc \ net/socket/unix_domain_socket_posix.cc \ @@ -447,6 +451,8 @@ LOCAL_SRC_FILES := \ net/websockets/websocket_channel.cc \ net/websockets/websocket_deflater.cc \ net/websockets/websocket_errors.cc \ + net/websockets/websocket_extension.cc \ + net/websockets/websocket_extension_parser.cc \ net/websockets/websocket_frame.cc \ net/websockets/websocket_frame_parser.cc \ net/websockets/websocket_handshake_constants.cc \ @@ -494,7 +500,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -591,7 +597,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net.target.linux-arm.mk b/net/net.target.linux-arm.mk index d484b247fa..3bd52d9b88 100644 --- a/net/net.target.linux-arm.mk +++ b/net/net.target.linux-arm.mk @@ -158,12 +158,14 @@ LOCAL_SRC_FILES := \ net/disk_cache/simple/simple_net_log_parameters.cc \ net/disk_cache/simple/simple_synchronous_entry.cc \ net/disk_cache/simple/simple_util.cc \ + net/disk_cache/simple/simple_version_upgrade.cc \ net/disk_cache/flash/flash_entry_impl.cc \ net/disk_cache/flash/internal_entry.cc \ net/disk_cache/flash/log_store.cc \ net/disk_cache/flash/log_store_entry.cc \ net/disk_cache/flash/segment.cc \ net/disk_cache/flash/storage.cc \ + net/disk_cache/v3/block_bitmaps.cc \ net/dns/address_sorter_posix.cc \ net/dns/dns_client.cc \ net/dns/dns_config_service.cc \ @@ -328,12 +330,14 @@ LOCAL_SRC_FILES := \ net/quic/quic_fec_group.cc \ net/quic/quic_framer.cc \ net/quic/quic_http_stream.cc \ + net/quic/quic_http_utils.cc \ net/quic/quic_packet_creator.cc \ net/quic/quic_packet_generator.cc \ net/quic/quic_protocol.cc \ net/quic/quic_received_packet_manager.cc \ net/quic/quic_reliable_client_stream.cc \ net/quic/quic_sent_entropy_manager.cc \ + net/quic/quic_sent_packet_manager.cc \ net/quic/quic_session.cc \ net/quic/quic_spdy_compressor.cc \ net/quic/quic_spdy_decompressor.cc \ @@ -364,9 +368,9 @@ LOCAL_SRC_FILES := \ net/socket/stream_listen_socket.cc \ net/socket/stream_socket.cc \ net/socket/tcp_client_socket.cc \ - net/socket/tcp_client_socket_libevent.cc \ net/socket/tcp_listen_socket.cc \ net/socket/tcp_server_socket.cc \ + net/socket/tcp_socket.cc \ net/socket/tcp_socket_libevent.cc \ net/socket/transport_client_socket_pool.cc \ net/socket/unix_domain_socket_posix.cc \ @@ -447,6 +451,8 @@ LOCAL_SRC_FILES := \ net/websockets/websocket_channel.cc \ net/websockets/websocket_deflater.cc \ net/websockets/websocket_errors.cc \ + net/websockets/websocket_extension.cc \ + net/websockets/websocket_extension_parser.cc \ net/websockets/websocket_frame.cc \ net/websockets/websocket_frame_parser.cc \ net/websockets/websocket_handshake_constants.cc \ @@ -492,7 +498,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -587,7 +593,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net.target.linux-mips.mk b/net/net.target.linux-mips.mk index d7a3f17ecf..b52aecc91a 100644 --- a/net/net.target.linux-mips.mk +++ b/net/net.target.linux-mips.mk @@ -158,12 +158,14 @@ LOCAL_SRC_FILES := \ net/disk_cache/simple/simple_net_log_parameters.cc \ net/disk_cache/simple/simple_synchronous_entry.cc \ net/disk_cache/simple/simple_util.cc \ + net/disk_cache/simple/simple_version_upgrade.cc \ net/disk_cache/flash/flash_entry_impl.cc \ net/disk_cache/flash/internal_entry.cc \ net/disk_cache/flash/log_store.cc \ net/disk_cache/flash/log_store_entry.cc \ net/disk_cache/flash/segment.cc \ net/disk_cache/flash/storage.cc \ + net/disk_cache/v3/block_bitmaps.cc \ net/dns/address_sorter_posix.cc \ net/dns/dns_client.cc \ net/dns/dns_config_service.cc \ @@ -328,12 +330,14 @@ LOCAL_SRC_FILES := \ net/quic/quic_fec_group.cc \ net/quic/quic_framer.cc \ net/quic/quic_http_stream.cc \ + net/quic/quic_http_utils.cc \ net/quic/quic_packet_creator.cc \ net/quic/quic_packet_generator.cc \ net/quic/quic_protocol.cc \ net/quic/quic_received_packet_manager.cc \ net/quic/quic_reliable_client_stream.cc \ net/quic/quic_sent_entropy_manager.cc \ + net/quic/quic_sent_packet_manager.cc \ net/quic/quic_session.cc \ net/quic/quic_spdy_compressor.cc \ net/quic/quic_spdy_decompressor.cc \ @@ -364,9 +368,9 @@ LOCAL_SRC_FILES := \ net/socket/stream_listen_socket.cc \ net/socket/stream_socket.cc \ net/socket/tcp_client_socket.cc \ - net/socket/tcp_client_socket_libevent.cc \ net/socket/tcp_listen_socket.cc \ net/socket/tcp_server_socket.cc \ + net/socket/tcp_socket.cc \ net/socket/tcp_socket_libevent.cc \ net/socket/transport_client_socket_pool.cc \ net/socket/unix_domain_socket_posix.cc \ @@ -447,6 +451,8 @@ LOCAL_SRC_FILES := \ net/websockets/websocket_channel.cc \ net/websockets/websocket_deflater.cc \ net/websockets/websocket_errors.cc \ + net/websockets/websocket_extension.cc \ + net/websockets/websocket_extension_parser.cc \ net/websockets/websocket_frame.cc \ net/websockets/websocket_frame_parser.cc \ net/websockets/websocket_handshake_constants.cc \ @@ -491,7 +497,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -585,7 +591,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net.target.linux-x86.mk b/net/net.target.linux-x86.mk index af07ef16a9..7be29d9c5e 100644 --- a/net/net.target.linux-x86.mk +++ b/net/net.target.linux-x86.mk @@ -158,12 +158,14 @@ LOCAL_SRC_FILES := \ net/disk_cache/simple/simple_net_log_parameters.cc \ net/disk_cache/simple/simple_synchronous_entry.cc \ net/disk_cache/simple/simple_util.cc \ + net/disk_cache/simple/simple_version_upgrade.cc \ net/disk_cache/flash/flash_entry_impl.cc \ net/disk_cache/flash/internal_entry.cc \ net/disk_cache/flash/log_store.cc \ net/disk_cache/flash/log_store_entry.cc \ net/disk_cache/flash/segment.cc \ net/disk_cache/flash/storage.cc \ + net/disk_cache/v3/block_bitmaps.cc \ net/dns/address_sorter_posix.cc \ net/dns/dns_client.cc \ net/dns/dns_config_service.cc \ @@ -328,12 +330,14 @@ LOCAL_SRC_FILES := \ net/quic/quic_fec_group.cc \ net/quic/quic_framer.cc \ net/quic/quic_http_stream.cc \ + net/quic/quic_http_utils.cc \ net/quic/quic_packet_creator.cc \ net/quic/quic_packet_generator.cc \ net/quic/quic_protocol.cc \ net/quic/quic_received_packet_manager.cc \ net/quic/quic_reliable_client_stream.cc \ net/quic/quic_sent_entropy_manager.cc \ + net/quic/quic_sent_packet_manager.cc \ net/quic/quic_session.cc \ net/quic/quic_spdy_compressor.cc \ net/quic/quic_spdy_decompressor.cc \ @@ -364,9 +368,9 @@ LOCAL_SRC_FILES := \ net/socket/stream_listen_socket.cc \ net/socket/stream_socket.cc \ net/socket/tcp_client_socket.cc \ - net/socket/tcp_client_socket_libevent.cc \ net/socket/tcp_listen_socket.cc \ net/socket/tcp_server_socket.cc \ + net/socket/tcp_socket.cc \ net/socket/tcp_socket_libevent.cc \ net/socket/transport_client_socket_pool.cc \ net/socket/unix_domain_socket_posix.cc \ @@ -447,6 +451,8 @@ LOCAL_SRC_FILES := \ net/websockets/websocket_channel.cc \ net/websockets/websocket_deflater.cc \ net/websockets/websocket_errors.cc \ + net/websockets/websocket_extension.cc \ + net/websockets/websocket_extension_parser.cc \ net/websockets/websocket_frame.cc \ net/websockets/websocket_frame_parser.cc \ net/websockets/websocket_handshake_constants.cc \ @@ -494,7 +500,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -591,7 +597,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_errors_java.target.darwin-arm.mk b/net/net_errors_java.target.darwin-arm.mk index 987298e26c..107b79e346 100644 --- a/net/net_errors_java.target.darwin-arm.mk +++ b/net/net_errors_java.target.darwin-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_errors_java.target.darwin-mips.mk b/net/net_errors_java.target.darwin-mips.mk index 53b7da1a8a..02ce8c1e14 100644 --- a/net/net_errors_java.target.darwin-mips.mk +++ b/net/net_errors_java.target.darwin-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_errors_java.target.darwin-x86.mk b/net/net_errors_java.target.darwin-x86.mk index 0d84633890..5bd0c4d917 100644 --- a/net/net_errors_java.target.darwin-x86.mk +++ b/net/net_errors_java.target.darwin-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_errors_java.target.linux-arm.mk b/net/net_errors_java.target.linux-arm.mk index 987298e26c..107b79e346 100644 --- a/net/net_errors_java.target.linux-arm.mk +++ b/net/net_errors_java.target.linux-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_errors_java.target.linux-mips.mk b/net/net_errors_java.target.linux-mips.mk index 53b7da1a8a..02ce8c1e14 100644 --- a/net/net_errors_java.target.linux-mips.mk +++ b/net/net_errors_java.target.linux-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_errors_java.target.linux-x86.mk b/net/net_errors_java.target.linux-x86.mk index 0d84633890..5bd0c4d917 100644 --- a/net/net_errors_java.target.linux-x86.mk +++ b/net/net_errors_java.target.linux-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_jni_headers.target.darwin-arm.mk b/net/net_jni_headers.target.darwin-arm.mk index f0be7a3924..16301bab70 100644 --- a/net/net_jni_headers.target.darwin-arm.mk +++ b/net/net_jni_headers.target.darwin-arm.mk @@ -126,7 +126,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -206,7 +206,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_jni_headers.target.darwin-mips.mk b/net/net_jni_headers.target.darwin-mips.mk index 45f5b82065..b410703875 100644 --- a/net/net_jni_headers.target.darwin-mips.mk +++ b/net/net_jni_headers.target.darwin-mips.mk @@ -125,7 +125,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -204,7 +204,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_jni_headers.target.darwin-x86.mk b/net/net_jni_headers.target.darwin-x86.mk index d8ada7afe7..f50b55eff2 100644 --- a/net/net_jni_headers.target.darwin-x86.mk +++ b/net/net_jni_headers.target.darwin-x86.mk @@ -128,7 +128,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -211,7 +211,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_jni_headers.target.linux-arm.mk b/net/net_jni_headers.target.linux-arm.mk index f0be7a3924..16301bab70 100644 --- a/net/net_jni_headers.target.linux-arm.mk +++ b/net/net_jni_headers.target.linux-arm.mk @@ -126,7 +126,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -206,7 +206,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_jni_headers.target.linux-mips.mk b/net/net_jni_headers.target.linux-mips.mk index 45f5b82065..b410703875 100644 --- a/net/net_jni_headers.target.linux-mips.mk +++ b/net/net_jni_headers.target.linux-mips.mk @@ -125,7 +125,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -204,7 +204,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/net_jni_headers.target.linux-x86.mk b/net/net_jni_headers.target.linux-x86.mk index d8ada7afe7..f50b55eff2 100644 --- a/net/net_jni_headers.target.linux-x86.mk +++ b/net/net_jni_headers.target.linux-x86.mk @@ -128,7 +128,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -211,7 +211,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/private_key_types_java.target.darwin-arm.mk b/net/private_key_types_java.target.darwin-arm.mk index 0b5ee03d75..de9863a6f3 100644 --- a/net/private_key_types_java.target.darwin-arm.mk +++ b/net/private_key_types_java.target.darwin-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/private_key_types_java.target.darwin-mips.mk b/net/private_key_types_java.target.darwin-mips.mk index 0767d5f707..afcf3f6676 100644 --- a/net/private_key_types_java.target.darwin-mips.mk +++ b/net/private_key_types_java.target.darwin-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/private_key_types_java.target.darwin-x86.mk b/net/private_key_types_java.target.darwin-x86.mk index 4ca791d4fa..5039c8c46c 100644 --- a/net/private_key_types_java.target.darwin-x86.mk +++ b/net/private_key_types_java.target.darwin-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/private_key_types_java.target.linux-arm.mk b/net/private_key_types_java.target.linux-arm.mk index 0b5ee03d75..de9863a6f3 100644 --- a/net/private_key_types_java.target.linux-arm.mk +++ b/net/private_key_types_java.target.linux-arm.mk @@ -77,7 +77,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -157,7 +157,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/private_key_types_java.target.linux-mips.mk b/net/private_key_types_java.target.linux-mips.mk index 0767d5f707..afcf3f6676 100644 --- a/net/private_key_types_java.target.linux-mips.mk +++ b/net/private_key_types_java.target.linux-mips.mk @@ -76,7 +76,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -155,7 +155,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/private_key_types_java.target.linux-x86.mk b/net/private_key_types_java.target.linux-x86.mk index 4ca791d4fa..5039c8c46c 100644 --- a/net/private_key_types_java.target.linux-x86.mk +++ b/net/private_key_types_java.target.linux-x86.mk @@ -79,7 +79,7 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ @@ -162,7 +162,7 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ - '-DWTF_VECTOR_INITIAL_SIZE=16' \ + '-DWTF_VECTOR_INITIAL_SIZE=4' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ diff --git a/net/proxy/proxy_resolver_perftest.cc b/net/proxy/proxy_resolver_perftest.cc index 1630d9e378..12ffd1bd91 100644 --- a/net/proxy/proxy_resolver_perftest.cc +++ b/net/proxy/proxy_resolver_perftest.cc @@ -7,7 +7,7 @@ #include "base/file_util.h" #include "base/path_service.h" #include "base/strings/string_util.h" -#include "base/test/perftimer.h" +#include "base/test/perf_time_logger.h" #include "net/base/net_errors.h" #include "net/dns/mock_host_resolver.h" #include "net/proxy/proxy_info.h" @@ -130,7 +130,7 @@ class PacPerfSuiteRunner { // Start the perf timer. std::string perf_test_name = resolver_name_ + "_" + script_name; - PerfTimeLogger timer(perf_test_name.c_str()); + base::PerfTimeLogger timer(perf_test_name.c_str()); for (int i = 0; i < kNumIterations; ++i) { // Round-robin between URLs to resolve. diff --git a/net/proxy/proxy_script_decider.cc b/net/proxy/proxy_script_decider.cc index 38bf751cd4..f6316a0215 100644 --- a/net/proxy/proxy_script_decider.cc +++ b/net/proxy/proxy_script_decider.cc @@ -9,6 +9,7 @@ #include "base/compiler_specific.h" #include "base/format_macros.h" #include "base/logging.h" +#include "base/metrics/histogram.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" @@ -16,6 +17,7 @@ #include "net/proxy/dhcp_proxy_script_fetcher.h" #include "net/proxy/dhcp_proxy_script_fetcher_factory.h" #include "net/proxy/proxy_script_fetcher.h" +#include "net/url_request/url_request_context.h" namespace net { @@ -45,7 +47,10 @@ bool LooksLikePacScript(const base::string16& script) { // // For more details, also check out this comment: // http://code.google.com/p/chromium/issues/detail?id=18575#c20 -static const char kWpadUrl[] = "http://wpad/wpad.dat"; +namespace { +const char kWpadUrl[] = "http://wpad/wpad.dat"; +const int kQuickCheckDelayMs = 1000; +}; base::Value* ProxyScriptDecider::PacSource::NetLogCallback( const GURL* effective_pac_url, @@ -82,6 +87,12 @@ ProxyScriptDecider::ProxyScriptDecider( net_log_(BoundNetLog::Make( net_log, NetLog::SOURCE_PROXY_SCRIPT_DECIDER)), fetch_pac_bytes_(false) { + if (proxy_script_fetcher && + proxy_script_fetcher->GetRequestContext() && + proxy_script_fetcher->GetRequestContext()->host_resolver()) { + host_resolver_.reset(new SingleRequestHostResolver( + proxy_script_fetcher->GetRequestContext()->host_resolver())); + } } ProxyScriptDecider::~ProxyScriptDecider() { @@ -106,6 +117,7 @@ int ProxyScriptDecider::Start( wait_delay_ = base::TimeDelta(); pac_mandatory_ = config.pac_mandatory(); + have_custom_pac_url_ = config.has_pac_url(); pac_sources_ = BuildPacSourcesFallbackList(config); DCHECK(!pac_sources_.empty()); @@ -172,6 +184,13 @@ int ProxyScriptDecider::DoLoop(int result) { case STATE_WAIT_COMPLETE: rv = DoWaitComplete(rv); break; + case STATE_QUICK_CHECK: + DCHECK_EQ(OK, rv); + rv = DoQuickCheck(); + break; + case STATE_QUICK_CHECK_COMPLETE: + rv = DoQuickCheckComplete(rv); + break; case STATE_FETCH_PAC_SCRIPT: DCHECK_EQ(OK, rv); rv = DoFetchPacScript(); @@ -221,10 +240,64 @@ int ProxyScriptDecider::DoWaitComplete(int result) { net_log_.EndEventWithNetErrorCode(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT, result); } - next_state_ = GetStartState(); + next_state_ = STATE_QUICK_CHECK; return OK; } +int ProxyScriptDecider::DoQuickCheck() { + if (host_resolver_.get() == NULL) { + // If we have no resolver, skip QuickCheck altogether. + next_state_ = GetStartState(); + return OK; + } + + if (have_custom_pac_url_) { + // If there's a custom URL, skip QuickCheck. + next_state_ = GetStartState(); + return OK; + } + + quick_check_start_time_ = base::Time::Now(); + HostResolver::RequestInfo reqinfo(HostPortPair("wpad", 80)); + reqinfo.set_host_resolver_flags(HOST_RESOLVER_SYSTEM_ONLY); + CompletionCallback callback = base::Bind( + &ProxyScriptDecider::OnIOCompletion, + base::Unretained(this)); + + + // We use HIGHEST here because proxy decision blocks doing any other requests. + int rv = host_resolver_->Resolve(reqinfo, HIGHEST, &wpad_addresses_, + callback, net_log_); + + // We can't get an error response - the name is known to be valid, and we + // don't cache negative dns responses. + DCHECK(rv == OK || rv == ERR_IO_PENDING); + + if (rv == OK) { + next_state_ = GetStartState(); + } else { + quick_check_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds( + kQuickCheckDelayMs), + base::Bind(callback, ERR_NAME_NOT_RESOLVED)); + next_state_ = STATE_QUICK_CHECK_COMPLETE; + } + return rv; +} + +int ProxyScriptDecider::DoQuickCheckComplete(int result) { + base::TimeDelta delta = base::Time::Now() - quick_check_start_time_; + if (result == OK) + UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckSuccess", delta); + else + UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckFailure", delta); + host_resolver_->Cancel(); + quick_check_timer_.Stop(); + if (result == OK) + next_state_ = GetStartState(); + return result; +} + int ProxyScriptDecider::DoFetchPacScript() { DCHECK(fetch_pac_bytes_); diff --git a/net/proxy/proxy_script_decider.h b/net/proxy/proxy_script_decider.h index 9a77938ec8..23fa7af85c 100644 --- a/net/proxy/proxy_script_decider.h +++ b/net/proxy/proxy_script_decider.h @@ -12,9 +12,12 @@ #include "base/strings/string16.h" #include "base/time/time.h" #include "base/timer/timer.h" +#include "net/base/address_list.h" #include "net/base/completion_callback.h" #include "net/base/net_export.h" #include "net/base/net_log.h" +#include "net/dns/host_resolver.h" +#include "net/dns/single_request_host_resolver.h" #include "net/proxy/proxy_config.h" #include "net/proxy/proxy_resolver.h" #include "url/gurl.h" @@ -105,6 +108,8 @@ class NET_EXPORT_PRIVATE ProxyScriptDecider { STATE_NONE, STATE_WAIT, STATE_WAIT_COMPLETE, + STATE_QUICK_CHECK, + STATE_QUICK_CHECK_COMPLETE, STATE_FETCH_PAC_SCRIPT, STATE_FETCH_PAC_SCRIPT_COMPLETE, STATE_VERIFY_PAC_SCRIPT, @@ -121,6 +126,9 @@ class NET_EXPORT_PRIVATE ProxyScriptDecider { int DoWait(); int DoWaitComplete(int result); + int DoQuickCheck(); + int DoQuickCheckComplete(int result); + int DoFetchPacScript(); int DoFetchPacScriptComplete(int result); @@ -161,6 +169,9 @@ class NET_EXPORT_PRIVATE ProxyScriptDecider { // (i.e. fallback to direct connections are prohibited). bool pac_mandatory_; + // Whether we have an existing custom PAC URL. + bool have_custom_pac_url_; + PacSourceList pac_sources_; State next_state_; @@ -175,6 +186,10 @@ class NET_EXPORT_PRIVATE ProxyScriptDecider { ProxyConfig effective_config_; scoped_refptr<ProxyResolverScriptData> script_data_; + AddressList wpad_addresses_; + base::OneShotTimer<ProxyScriptDecider> quick_check_timer_; + scoped_ptr<SingleRequestHostResolver> host_resolver_; + base::Time quick_check_start_time_; DISALLOW_COPY_AND_ASSIGN(ProxyScriptDecider); }; diff --git a/net/proxy/proxy_script_decider_unittest.cc b/net/proxy/proxy_script_decider_unittest.cc index 977bd4df64..61978ab6e6 100644 --- a/net/proxy/proxy_script_decider_unittest.cc +++ b/net/proxy/proxy_script_decider_unittest.cc @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" @@ -14,11 +15,13 @@ #include "net/base/net_log.h" #include "net/base/net_log_unittest.h" #include "net/base/test_completion_callback.h" +#include "net/dns/mock_host_resolver.h" #include "net/proxy/dhcp_proxy_script_fetcher.h" #include "net/proxy/proxy_config.h" #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_script_decider.h" #include "net/proxy/proxy_script_fetcher.h" +#include "net/url_request/url_request_context.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { @@ -93,7 +96,12 @@ class Rules { class RuleBasedProxyScriptFetcher : public ProxyScriptFetcher { public: - explicit RuleBasedProxyScriptFetcher(const Rules* rules) : rules_(rules) {} + explicit RuleBasedProxyScriptFetcher(const Rules* rules) + : rules_(rules), request_context_(NULL) {} + + virtual void SetRequestContext(URLRequestContext* context) { + request_context_ = context; + } // ProxyScriptFetcher implementation. virtual int Fetch(const GURL& url, @@ -109,10 +117,13 @@ class RuleBasedProxyScriptFetcher : public ProxyScriptFetcher { virtual void Cancel() OVERRIDE {} - virtual URLRequestContext* GetRequestContext() const OVERRIDE { return NULL; } + virtual URLRequestContext* GetRequestContext() const OVERRIDE { + return request_context_; + } private: const Rules* rules_; + URLRequestContext* request_context_; }; // Succeed using custom PAC script. @@ -243,6 +254,91 @@ TEST(ProxyScriptDeciderTest, AutodetectSuccess) { EXPECT_EQ(rule.url, decider.effective_config().pac_url()); } +class ProxyScriptDeciderQuickCheckTest : public ::testing::Test { + public: + ProxyScriptDeciderQuickCheckTest() + : rule_(rules_.AddSuccessRule("http://wpad/wpad.dat")), + fetcher_(&rules_) { } + + virtual void SetUp() OVERRIDE { + request_context_.set_host_resolver(&resolver_); + fetcher_.SetRequestContext(&request_context_); + config_.set_auto_detect(true); + decider_.reset(new ProxyScriptDecider(&fetcher_, &dhcp_fetcher_, NULL)); + } + + int StartDecider() { + return decider_->Start(config_, base::TimeDelta(), true, + callback_.callback()); + } + + protected: + scoped_ptr<ProxyScriptDecider> decider_; + MockHostResolver resolver_; + Rules rules_; + Rules::Rule rule_; + TestCompletionCallback callback_; + + private: + URLRequestContext request_context_; + + RuleBasedProxyScriptFetcher fetcher_; + DoNothingDhcpProxyScriptFetcher dhcp_fetcher_; + + ProxyConfig config_; +}; + +// Fails if a synchronous DNS lookup success for wpad causes QuickCheck to fail. +TEST_F(ProxyScriptDeciderQuickCheckTest, SyncSuccess) { + resolver_.set_synchronous_mode(true); + resolver_.rules()->AddRule("wpad", "1.2.3.4"); + + EXPECT_EQ(OK, StartDecider()); + EXPECT_EQ(rule_.text(), decider_->script_data()->utf16()); + + EXPECT_TRUE(decider_->effective_config().has_pac_url()); + EXPECT_EQ(rule_.url, decider_->effective_config().pac_url()); +} + +// Fails if an asynchronous DNS lookup success for wpad causes QuickCheck to +// fail. +TEST_F(ProxyScriptDeciderQuickCheckTest, AsyncSuccess) { + resolver_.set_ondemand_mode(true); + resolver_.rules()->AddRule("wpad", "1.2.3.4"); + + EXPECT_EQ(ERR_IO_PENDING, StartDecider()); + ASSERT_TRUE(resolver_.has_pending_requests()); + resolver_.ResolveAllPending(); + callback_.WaitForResult(); + EXPECT_FALSE(resolver_.has_pending_requests()); + EXPECT_EQ(rule_.text(), decider_->script_data()->utf16()); + EXPECT_TRUE(decider_->effective_config().has_pac_url()); + EXPECT_EQ(rule_.url, decider_->effective_config().pac_url()); +} + +// Fails if an asynchronous DNS lookup failure (i.e. an NXDOMAIN) still causes +// ProxyScriptDecider to yield a PAC URL. +TEST_F(ProxyScriptDeciderQuickCheckTest, AsyncFail) { + resolver_.set_ondemand_mode(true); + resolver_.rules()->AddSimulatedFailure("wpad"); + EXPECT_EQ(ERR_IO_PENDING, StartDecider()); + ASSERT_TRUE(resolver_.has_pending_requests()); + resolver_.ResolveAllPending(); + callback_.WaitForResult(); + EXPECT_FALSE(decider_->effective_config().has_pac_url()); +} + +// Fails if a DNS lookup timeout either causes ProxyScriptDecider to yield a PAC +// URL or causes ProxyScriptDecider not to cancel its pending resolution. +TEST_F(ProxyScriptDeciderQuickCheckTest, AsyncTimeout) { + resolver_.set_ondemand_mode(true); + EXPECT_EQ(ERR_IO_PENDING, StartDecider()); + ASSERT_TRUE(resolver_.has_pending_requests()); + callback_.WaitForResult(); + EXPECT_FALSE(resolver_.has_pending_requests()); + EXPECT_FALSE(decider_->effective_config().has_pac_url()); +} + // Fails at WPAD (downloading), but succeeds in choosing the custom PAC. TEST(ProxyScriptDeciderTest, AutodetectFailCustomSuccess1) { Rules rules; diff --git a/net/quic/congestion_control/fix_rate_sender.cc b/net/quic/congestion_control/fix_rate_sender.cc index 22f759fa03..99aa10fabf 100644 --- a/net/quic/congestion_control/fix_rate_sender.cc +++ b/net/quic/congestion_control/fix_rate_sender.cc @@ -60,15 +60,18 @@ void FixRateSender::OnIncomingLoss(QuicTime /*ack_receive_time*/) { // Ignore losses for fix rate sender. } -void FixRateSender::SentPacket(QuicTime sent_time, - QuicPacketSequenceNumber /*sequence_number*/, - QuicByteCount bytes, - Retransmission is_retransmission) { +bool FixRateSender::SentPacket( + QuicTime sent_time, + QuicPacketSequenceNumber /*sequence_number*/, + QuicByteCount bytes, + Retransmission is_retransmission, + HasRetransmittableData /*has_retransmittable_data*/) { fix_rate_leaky_bucket_.Add(sent_time, bytes); paced_sender_.SentPacket(sent_time, bytes); if (is_retransmission == NOT_RETRANSMISSION) { data_in_flight_ += bytes; } + return true; } void FixRateSender::AbandoningPacket( diff --git a/net/quic/congestion_control/fix_rate_sender.h b/net/quic/congestion_control/fix_rate_sender.h index 38cebad165..781deade1c 100644 --- a/net/quic/congestion_control/fix_rate_sender.h +++ b/net/quic/congestion_control/fix_rate_sender.h @@ -32,10 +32,12 @@ class NET_EXPORT_PRIVATE FixRateSender : public SendAlgorithmInterface { QuicByteCount acked_bytes, QuicTime::Delta rtt) OVERRIDE; virtual void OnIncomingLoss(QuicTime ack_receive_time) OVERRIDE; - virtual void SentPacket(QuicTime sent_time, - QuicPacketSequenceNumber equence_number, - QuicByteCount bytes, - Retransmission is_retransmission) OVERRIDE; + virtual bool SentPacket( + QuicTime sent_time, + QuicPacketSequenceNumber equence_number, + QuicByteCount bytes, + Retransmission is_retransmission, + HasRetransmittableData has_retransmittable_data) OVERRIDE; virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number, QuicByteCount abandoned_bytes) OVERRIDE; virtual QuicTime::Delta TimeUntilSend( diff --git a/net/quic/congestion_control/fix_rate_test.cc b/net/quic/congestion_control/fix_rate_test.cc index f914ed671a..e316f6517a 100644 --- a/net/quic/congestion_control/fix_rate_test.cc +++ b/net/quic/congestion_control/fix_rate_test.cc @@ -63,11 +63,14 @@ TEST_F(FixRateTest, SenderAPI) { EXPECT_EQ(300000, sender_->BandwidthEstimate().ToBytesPerSecond()); EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); - sender_->SentPacket(clock_.Now(), 1, kMaxPacketSize, NOT_RETRANSMISSION); + sender_->SentPacket(clock_.Now(), 1, kMaxPacketSize, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); - sender_->SentPacket(clock_.Now(), 2, kMaxPacketSize, NOT_RETRANSMISSION); - sender_->SentPacket(clock_.Now(), 3, 600, NOT_RETRANSMISSION); + sender_->SentPacket(clock_.Now(), 2, kMaxPacketSize, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); + sender_->SentPacket(clock_.Now(), 3, 600, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE)); @@ -98,12 +101,12 @@ TEST_F(FixRateTest, FixRatePacing) { NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); sender_->SentPacket(clock_.Now(), sequence_number++, packet_size, - NOT_RETRANSMISSION); + NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA); EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); sender_->SentPacket(clock_.Now(), sequence_number++, packet_size, - NOT_RETRANSMISSION); + NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA); QuicTime::Delta advance_time = sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE); clock_.AdvanceTime(advance_time); diff --git a/net/quic/congestion_control/inter_arrival_sender.cc b/net/quic/congestion_control/inter_arrival_sender.cc index 3afa378529..5640a731ae 100644 --- a/net/quic/congestion_control/inter_arrival_sender.cc +++ b/net/quic/congestion_control/inter_arrival_sender.cc @@ -235,14 +235,17 @@ void InterArrivalSender::OnIncomingLoss(QuicTime ack_receive_time) { } } -void InterArrivalSender::SentPacket(QuicTime sent_time, - QuicPacketSequenceNumber sequence_number, - QuicByteCount bytes, - Retransmission /*retransmit*/) { +bool InterArrivalSender::SentPacket( + QuicTime sent_time, + QuicPacketSequenceNumber sequence_number, + QuicByteCount bytes, + Retransmission /*is_retransmit*/, + HasRetransmittableData /*has_retransmittable_data*/) { if (probing_) { probe_->OnSentPacket(bytes); } paced_sender_->SentPacket(sent_time, bytes); + return true; } void InterArrivalSender::AbandoningPacket( diff --git a/net/quic/congestion_control/inter_arrival_sender.h b/net/quic/congestion_control/inter_arrival_sender.h index ad28ecd215..2c455cc9d1 100644 --- a/net/quic/congestion_control/inter_arrival_sender.h +++ b/net/quic/congestion_control/inter_arrival_sender.h @@ -43,10 +43,12 @@ class NET_EXPORT_PRIVATE InterArrivalSender : public SendAlgorithmInterface { virtual void OnIncomingLoss(QuicTime ack_receive_time) OVERRIDE; - virtual void SentPacket(QuicTime sent_time, - QuicPacketSequenceNumber sequence_number, - QuicByteCount bytes, - Retransmission is_retransmit) OVERRIDE; + virtual bool SentPacket( + QuicTime sent_time, + QuicPacketSequenceNumber sequence_number, + QuicByteCount bytes, + Retransmission is_retransmit, + HasRetransmittableData has_retransmittable_data) OVERRIDE; virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number, QuicByteCount abandoned_bytes) OVERRIDE; diff --git a/net/quic/congestion_control/inter_arrival_sender_test.cc b/net/quic/congestion_control/inter_arrival_sender_test.cc index d0faca0f8f..7392b1a3a6 100644 --- a/net/quic/congestion_control/inter_arrival_sender_test.cc +++ b/net/quic/congestion_control/inter_arrival_sender_test.cc @@ -41,7 +41,7 @@ class InterArrivalSenderTest : public ::testing::Test { bytes_in_packet, send_clock_.Now()); sender_.SentPacket(send_clock_.Now(), sequence_number_, bytes_in_packet, - NOT_RETRANSMISSION); + NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA); sequence_number_++; } EXPECT_FALSE(sender_.TimeUntilSend(send_clock_.Now(), diff --git a/net/quic/congestion_control/quic_congestion_control_test.cc b/net/quic/congestion_control/quic_congestion_control_test.cc index 0051acab1f..457538f455 100644 --- a/net/quic/congestion_control/quic_congestion_control_test.cc +++ b/net/quic/congestion_control/quic_congestion_control_test.cc @@ -49,7 +49,8 @@ TEST_F(QuicCongestionControlTest, FixedRateSenderAPI) { clock_.Now()); EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket(1, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION); + manager_->SentPacket(1, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); EXPECT_EQ(QuicTime::Delta::FromMilliseconds(40), manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE)); @@ -78,7 +79,8 @@ TEST_F(QuicCongestionControlTest, FixedRatePacing) { for (QuicPacketSequenceNumber i = 1; i <= 100; ++i) { EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket(i, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION); + manager_->SentPacket(i, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); QuicTime::Delta advance_time = manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE); clock_.AdvanceTime(advance_time); @@ -108,10 +110,12 @@ TEST_F(QuicCongestionControlTest, Pacing) { for (QuicPacketSequenceNumber i = 1; i <= 100;) { EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket(i++, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION); + manager_->SentPacket(i++, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket(i++, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION); + manager_->SentPacket(i++, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); QuicTime::Delta advance_time = manager_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE); clock_.AdvanceTime(advance_time); diff --git a/net/quic/congestion_control/quic_congestion_manager.cc b/net/quic/congestion_control/quic_congestion_manager.cc index ba6bab83ba..ec519db7c9 100644 --- a/net/quic/congestion_control/quic_congestion_manager.cc +++ b/net/quic/congestion_control/quic_congestion_manager.cc @@ -48,18 +48,21 @@ QuicCongestionManager::~QuicCongestionManager() { STLDeleteValues(&packet_history_map_); } -void QuicCongestionManager::SentPacket(QuicPacketSequenceNumber sequence_number, - QuicTime sent_time, - QuicByteCount bytes, - Retransmission retransmission) { +void QuicCongestionManager::SentPacket( + QuicPacketSequenceNumber sequence_number, + QuicTime sent_time, + QuicByteCount bytes, + Retransmission retransmission, + HasRetransmittableData has_retransmittable_data) { DCHECK(!ContainsKey(pending_packets_, sequence_number)); - send_algorithm_->SentPacket(sent_time, sequence_number, bytes, - retransmission); - packet_history_map_[sequence_number] = - new class SendAlgorithmInterface::SentPacket(bytes, sent_time); - pending_packets_[sequence_number] = bytes; - CleanupPacketHistory(); + if (send_algorithm_->SentPacket(sent_time, sequence_number, bytes, + retransmission, has_retransmittable_data)) { + packet_history_map_[sequence_number] = + new class SendAlgorithmInterface::SentPacket(bytes, sent_time); + pending_packets_[sequence_number] = bytes; + CleanupPacketHistory(); + } } // Called when a packet is timed out. @@ -156,6 +159,23 @@ const QuicTime::Delta QuicCongestionManager::DefaultRetransmissionTime() { return QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs); } +// Ensures that the Delayed Ack timer is always set to a value lesser +// than the retransmission timer's minimum value (MinRTO). We want the +// delayed ack to get back to the QUIC peer before the sender's +// retransmission timer triggers. Since we do not know the +// reverse-path one-way delay, we assume equal delays for forward and +// reverse paths, and ensure that the timer is set to less than half +// of the MinRTO. +// There may be a value in making this delay adaptive with the help of +// the sender and a signaling mechanism -- if the sender uses a +// different MinRTO, we may get spurious retransmissions. May not have +// any benefits, but if the delayed ack becomes a significant source +// of (likely, tail) latency, then consider such a mechanism. + +const QuicTime::Delta QuicCongestionManager::DelayedAckTime() { + return QuicTime::Delta::FromMilliseconds(kMinRetransmissionTimeMs/2); +} + const QuicTime::Delta QuicCongestionManager::GetRetransmissionDelay( size_t unacked_packets_count, size_t number_retransmissions) { diff --git a/net/quic/congestion_control/quic_congestion_manager.h b/net/quic/congestion_control/quic_congestion_manager.h index 8bfa3c1d42..66303439ba 100644 --- a/net/quic/congestion_control/quic_congestion_manager.h +++ b/net/quic/congestion_control/quic_congestion_manager.h @@ -1,7 +1,3 @@ -// Copyright (c) 2013 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. - // 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. @@ -50,7 +46,8 @@ class NET_EXPORT_PRIVATE QuicCongestionManager { virtual void SentPacket(QuicPacketSequenceNumber sequence_number, QuicTime sent_time, QuicByteCount bytes, - Retransmission retransmission); + Retransmission retransmission, + HasRetransmittableData has_retransmittable_data); // Called when a packet is timed out. virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number); @@ -85,6 +82,9 @@ class NET_EXPORT_PRIVATE QuicCongestionManager { const QuicTime::Delta DefaultRetransmissionTime(); + // Returns amount of time for delayed ack timer. + const QuicTime::Delta DelayedAckTime(); + const QuicTime::Delta GetRetransmissionDelay( size_t unacked_packets_count, size_t number_retransmissions); diff --git a/net/quic/congestion_control/quic_congestion_manager_test.cc b/net/quic/congestion_control/quic_congestion_manager_test.cc index 1cf44a2bdf..80460f552b 100644 --- a/net/quic/congestion_control/quic_congestion_manager_test.cc +++ b/net/quic/congestion_control/quic_congestion_manager_test.cc @@ -14,6 +14,7 @@ using testing::_; using testing::StrictMock; +using testing::Return; namespace net { namespace test { @@ -64,7 +65,8 @@ TEST_F(QuicCongestionManagerTest, Bandwidth) { clock_.AdvanceTime(advance_time); EXPECT_TRUE(manager_->TimeUntilSend( clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket(i, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(i, clock_.Now(), 1000, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); // Ack the packet we sent. ack.received_info.largest_observed = i; manager_->OnIncomingAckFrame(ack, clock_.Now()); @@ -92,8 +94,8 @@ TEST_F(QuicCongestionManagerTest, BandwidthWith1SecondGap) { clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); EXPECT_TRUE(manager_->TimeUntilSend( clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket( - sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(sequence_number, clock_.Now(), 1000, + NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA); // Ack the packet we sent. ack.received_info.largest_observed = sequence_number; manager_->OnIncomingAckFrame(ack, clock_.Now()); @@ -118,7 +120,8 @@ TEST_F(QuicCongestionManagerTest, BandwidthWith1SecondGap) { for (int i = 1; i <= 150; ++i) { EXPECT_TRUE(manager_->TimeUntilSend( clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE).IsZero()); - manager_->SentPacket(i + 100, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(i + 100, clock_.Now(), 1000, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); // Ack the packet we sent. ack.received_info.largest_observed = i + 100; @@ -141,11 +144,13 @@ TEST_F(QuicCongestionManagerTest, Rtt) { QuicPacketSequenceNumber sequence_number = 1; QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(15); - EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _, _)) + .Times(1).WillOnce(Return(true)); EXPECT_CALL(*send_algorithm, OnIncomingAck(sequence_number, _, expected_rtt)).Times(1); - manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20)); QuicAckFrame ack; @@ -167,11 +172,13 @@ TEST_F(QuicCongestionManagerTest, RttWithInvalidDelta) { QuicPacketSequenceNumber sequence_number = 1; QuicTime::Delta expected_rtt = QuicTime::Delta::Infinite(); - EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _, _)) + .Times(1).WillOnce(Return(true)); EXPECT_CALL(*send_algorithm, OnIncomingAck(sequence_number, _, expected_rtt)).Times(1); - manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); QuicAckFrame ack; @@ -193,11 +200,13 @@ TEST_F(QuicCongestionManagerTest, RttInfiniteDelta) { QuicPacketSequenceNumber sequence_number = 1; QuicTime::Delta expected_rtt = QuicTime::Delta::Infinite(); - EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _, _)) + .Times(1).WillOnce(Return(true)); EXPECT_CALL(*send_algorithm, OnIncomingAck(sequence_number, _, expected_rtt)).Times(1); - manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); QuicAckFrame ack; @@ -218,11 +227,13 @@ TEST_F(QuicCongestionManagerTest, RttZeroDelta) { QuicPacketSequenceNumber sequence_number = 1; QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(10); - EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1); - EXPECT_CALL(*send_algorithm, - OnIncomingAck(sequence_number, _, expected_rtt)).Times(1); + EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _, _)) + .Times(1).WillOnce(Return(true)); + EXPECT_CALL(*send_algorithm, OnIncomingAck(sequence_number, _, expected_rtt)) + .Times(1); - manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION); + manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA); clock_.AdvanceTime(expected_rtt); QuicAckFrame ack; diff --git a/net/quic/congestion_control/send_algorithm_interface.cc b/net/quic/congestion_control/send_algorithm_interface.cc index ce24a00b1a..90399e7d05 100644 --- a/net/quic/congestion_control/send_algorithm_interface.cc +++ b/net/quic/congestion_control/send_algorithm_interface.cc @@ -14,7 +14,7 @@ const bool kUseReno = false; // TODO(ianswett): Increase the max congestion window once the RTO logic is // improved, particularly in cases when RTT is larger than the RTO. b/10075719 // Maximum number of outstanding packets for tcp. -const QuicTcpCongestionWindow kMaxTcpCongestionWindow = 50; +const QuicTcpCongestionWindow kMaxTcpCongestionWindow = 100; // Factory for send side congestion control algorithm. SendAlgorithmInterface* SendAlgorithmInterface::Create( diff --git a/net/quic/congestion_control/send_algorithm_interface.h b/net/quic/congestion_control/send_algorithm_interface.h index 8896b2b06d..c29f22545d 100644 --- a/net/quic/congestion_control/send_algorithm_interface.h +++ b/net/quic/congestion_control/send_algorithm_interface.h @@ -55,11 +55,15 @@ class NET_EXPORT_PRIVATE SendAlgorithmInterface { virtual void OnIncomingLoss(QuicTime ack_receive_time) = 0; // Inform that we sent x bytes to the wire, and if that was a retransmission. + // Returns true if the packet should be tracked by the congestion manager, + // false otherwise. This is used by implementations such as tcp_cubic_sender + // that do not count outgoing ACK packets against the congestion window. // Note: this function must be called for every packet sent to the wire. - virtual void SentPacket(QuicTime sent_time, + virtual bool SentPacket(QuicTime sent_time, QuicPacketSequenceNumber sequence_number, QuicByteCount bytes, - Retransmission is_retransmission) = 0; + Retransmission is_retransmission, + HasRetransmittableData is_retransmittable) = 0; // Called when a packet is timed out. virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number, diff --git a/net/quic/congestion_control/tcp_cubic_sender.cc b/net/quic/congestion_control/tcp_cubic_sender.cc index 438dbe912e..52c910e859 100644 --- a/net/quic/congestion_control/tcp_cubic_sender.cc +++ b/net/quic/congestion_control/tcp_cubic_sender.cc @@ -65,6 +65,7 @@ void TcpCubicSender::OnIncomingQuicCongestionFeedbackFrame( void TcpCubicSender::OnIncomingAck( QuicPacketSequenceNumber acked_sequence_number, QuicByteCount acked_bytes, QuicTime::Delta rtt) { + DCHECK_GE(bytes_in_flight_, acked_bytes); bytes_in_flight_ -= acked_bytes; CongestionAvoidance(acked_sequence_number); AckAccounting(rtt); @@ -93,10 +94,16 @@ void TcpCubicSender::OnIncomingLoss(QuicTime /*ack_receive_time*/) { DLOG(INFO) << "Incoming loss; congestion window:" << congestion_window_; } -void TcpCubicSender::SentPacket(QuicTime /*sent_time*/, +bool TcpCubicSender::SentPacket(QuicTime /*sent_time*/, QuicPacketSequenceNumber sequence_number, QuicByteCount bytes, - Retransmission is_retransmission) { + Retransmission is_retransmission, + HasRetransmittableData is_retransmittable) { + // Only update bytes_in_flight_ for data packets. + if (is_retransmittable != HAS_RETRANSMITTABLE_DATA) { + return false; + } + bytes_in_flight_ += bytes; if (is_retransmission == NOT_RETRANSMISSION && update_end_sequence_number_) { end_sequence_number_ = sequence_number; @@ -105,10 +112,12 @@ void TcpCubicSender::SentPacket(QuicTime /*sent_time*/, DLOG(INFO) << "Stop update end sequence number @" << sequence_number; } } + return true; } void TcpCubicSender::AbandoningPacket(QuicPacketSequenceNumber sequence_number, QuicByteCount abandoned_bytes) { + DCHECK_GE(bytes_in_flight_, abandoned_bytes); bytes_in_flight_ -= abandoned_bytes; } diff --git a/net/quic/congestion_control/tcp_cubic_sender.h b/net/quic/congestion_control/tcp_cubic_sender.h index 8cea0aa44e..db829c29bb 100644 --- a/net/quic/congestion_control/tcp_cubic_sender.h +++ b/net/quic/congestion_control/tcp_cubic_sender.h @@ -41,10 +41,12 @@ class NET_EXPORT_PRIVATE TcpCubicSender : public SendAlgorithmInterface { QuicByteCount acked_bytes, QuicTime::Delta rtt) OVERRIDE; virtual void OnIncomingLoss(QuicTime ack_receive_time) OVERRIDE; - virtual void SentPacket(QuicTime sent_time, - QuicPacketSequenceNumber sequence_number, - QuicByteCount bytes, - Retransmission is_retransmission) OVERRIDE; + virtual bool SentPacket( + QuicTime sent_time, + QuicPacketSequenceNumber sequence_number, + QuicByteCount bytes, + Retransmission is_retransmission, + HasRetransmittableData is_retransmittable) OVERRIDE; virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number, QuicByteCount abandoned_bytes) OVERRIDE; virtual QuicTime::Delta TimeUntilSend( diff --git a/net/quic/congestion_control/tcp_cubic_sender_test.cc b/net/quic/congestion_control/tcp_cubic_sender_test.cc index fb67fcd2f5..c7046fcb2f 100644 --- a/net/quic/congestion_control/tcp_cubic_sender_test.cc +++ b/net/quic/congestion_control/tcp_cubic_sender_test.cc @@ -45,7 +45,7 @@ class TcpCubicSenderTest : public ::testing::Test { while (bytes_to_send > 0) { QuicByteCount bytes_in_packet = std::min(kMaxPacketSize, bytes_to_send); sender_->SentPacket(clock_.Now(), sequence_number_++, bytes_in_packet, - NOT_RETRANSMISSION); + NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA); bytes_to_send -= bytes_in_packet; if (bytes_to_send > 0) { EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION, @@ -352,5 +352,22 @@ TEST_F(TcpCubicSenderTest, TcpCubicMaxCongestionWindow) { EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow()); } +TEST_F(TcpCubicSenderTest, CongestionWindowNotAffectedByAcks) { + QuicByteCount congestion_window = sender_->AvailableCongestionWindow(); + + // Send a packet with no retransmittable data, and ensure that the congestion + // window doesn't change. + QuicByteCount bytes_in_packet = std::min(kMaxPacketSize, congestion_window); + sender_->SentPacket(clock_.Now(), sequence_number_++, bytes_in_packet, + NOT_RETRANSMISSION, NO_RETRANSMITTABLE_DATA); + EXPECT_EQ(congestion_window, sender_->AvailableCongestionWindow()); + + // Send a data packet with retransmittable data, and ensure that the + // congestion window has shrunk. + sender_->SentPacket(clock_.Now(), sequence_number_++, bytes_in_packet, + NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA); + EXPECT_GT(congestion_window, sender_->AvailableCongestionWindow()); +} + } // namespace test } // namespace net diff --git a/net/quic/crypto/crypto_server_config.cc b/net/quic/crypto/crypto_server_config.cc index c5caa8c838..89cea42862 100644 --- a/net/quic/crypto/crypto_server_config.cc +++ b/net/quic/crypto/crypto_server_config.cc @@ -306,7 +306,6 @@ struct ClientHelloInfo { QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( const CryptoHandshakeMessage& client_hello, - QuicVersion version, QuicGuid guid, const IPEndPoint& client_ip, const QuicClock* clock, @@ -361,8 +360,7 @@ QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( !info.client_nonce_well_formed || !info.unique || !requested_config.get()) { - BuildRejection(version, primary_config.get(), client_hello, info, rand, - out); + BuildRejection(primary_config.get(), client_hello, info, rand, out); return QUIC_NO_ERROR; } @@ -668,7 +666,6 @@ QuicErrorCode QuicCryptoServerConfig::EvaluateClientHello( } void QuicCryptoServerConfig::BuildRejection( - QuicVersion version, const scoped_refptr<Config>& config, const CryptoHandshakeMessage& client_hello, const ClientHelloInfo& info, @@ -712,9 +709,8 @@ void QuicCryptoServerConfig::BuildRejection( const vector<string>* certs; string signature; - if (!proof_source_->GetProof(version, info.sni.as_string(), - config->serialized, x509_ecdsa_supported, - &certs, &signature)) { + if (!proof_source_->GetProof(info.sni.as_string(), config->serialized, + x509_ecdsa_supported, &certs, &signature)) { return; } diff --git a/net/quic/crypto/crypto_server_config.h b/net/quic/crypto/crypto_server_config.h index cc40cb0d34..4255d22861 100644 --- a/net/quic/crypto/crypto_server_config.h +++ b/net/quic/crypto/crypto_server_config.h @@ -116,8 +116,6 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { // an error code is returned. // // client_hello: the incoming client hello message. - // version: the QUIC version for the connection. TODO(wtc): Remove once - // QUIC_VERSION_7 and before are removed. // guid: the GUID for the connection, which is used in key derivation. // client_ip: the IP address of the client, which is used to generate and // validate source-address tokens. @@ -129,7 +127,6 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { // out: the resulting handshake message (either REJ or SHLO) // error_details: used to store a string describing any error. QuicErrorCode ProcessClientHello(const CryptoHandshakeMessage& client_hello, - QuicVersion version, QuicGuid guid, const IPEndPoint& client_ip, const QuicClock* clock, @@ -266,7 +263,6 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { // BuildRejection sets |out| to be a REJ message in reply to |client_hello|. void BuildRejection( - QuicVersion version, const scoped_refptr<Config>& config, const CryptoHandshakeMessage& client_hello, const ClientHelloInfo& info, diff --git a/net/quic/crypto/crypto_server_test.cc b/net/quic/crypto/crypto_server_test.cc index 6744d12e5e..b2cdf820c3 100644 --- a/net/quic/crypto/crypto_server_test.cc +++ b/net/quic/crypto/crypto_server_test.cc @@ -72,8 +72,8 @@ class CryptoServerTest : public ::testing::Test { void ShouldSucceed(const CryptoHandshakeMessage& message) { string error_details; QuicErrorCode error = config_.ProcessClientHello( - message, QuicVersionMax(), 1 /* GUID */, addr_, - &clock_, rand_, ¶ms_, &out_, &error_details); + message, 1 /* GUID */, addr_, &clock_, + rand_, ¶ms_, &out_, &error_details); ASSERT_EQ(error, QUIC_NO_ERROR) << "Message failed with error " << error_details << ": " @@ -84,8 +84,8 @@ class CryptoServerTest : public ::testing::Test { const CryptoHandshakeMessage& message) { string error_details; QuicErrorCode error = config_.ProcessClientHello( - message, QuicVersionMax(), 1 /* GUID */, addr_, - &clock_, rand_, ¶ms_, &out_, &error_details); + message, 1 /* GUID */, addr_, &clock_, + rand_, ¶ms_, &out_, &error_details); ASSERT_NE(error, QUIC_NO_ERROR) << "Message didn't fail: " << message.DebugString(); diff --git a/net/quic/crypto/proof_source.h b/net/quic/crypto/proof_source.h index ba5087b0f6..4482dd9956 100644 --- a/net/quic/crypto/proof_source.h +++ b/net/quic/crypto/proof_source.h @@ -9,7 +9,6 @@ #include <vector> #include "net/base/net_export.h" -#include "net/quic/quic_protocol.h" namespace net { @@ -28,9 +27,6 @@ class NET_EXPORT_PRIVATE ProofSource { // // The signature uses SHA-256 as the hash function when the key is ECDSA. // - // |version| is the QUIC version for the connection. TODO(wtc): Remove once - // QUIC_VERSION_7 and before are removed. - // // If |ecdsa_ok| is true, the signature may use an ECDSA key. Otherwise, the // signature must use an RSA key. // @@ -49,8 +45,7 @@ class NET_EXPORT_PRIVATE ProofSource { // used. // // This function may be called concurrently. - virtual bool GetProof(QuicVersion version, - const std::string& hostname, + virtual bool GetProof(const std::string& hostname, const std::string& server_config, bool ecdsa_ok, const std::vector<std::string>** out_certs, diff --git a/net/quic/crypto/proof_source_chromium.cc b/net/quic/crypto/proof_source_chromium.cc index 4c1fe263b6..7522631381 100644 --- a/net/quic/crypto/proof_source_chromium.cc +++ b/net/quic/crypto/proof_source_chromium.cc @@ -12,8 +12,7 @@ namespace net { ProofSourceChromium::ProofSourceChromium() { } -bool ProofSourceChromium::GetProof(QuicVersion version, - const string& hostname, +bool ProofSourceChromium::GetProof(const string& hostname, const string& server_config, bool ecdsa_ok, const vector<string>** out_certs, diff --git a/net/quic/crypto/proof_source_chromium.h b/net/quic/crypto/proof_source_chromium.h index 2b93e2d9a4..70ab92d91c 100644 --- a/net/quic/crypto/proof_source_chromium.h +++ b/net/quic/crypto/proof_source_chromium.h @@ -23,8 +23,7 @@ class NET_EXPORT_PRIVATE ProofSourceChromium : public ProofSource { virtual ~ProofSourceChromium() {} // ProofSource interface - virtual bool GetProof(QuicVersion version, - const std::string& hostname, + virtual bool GetProof(const std::string& hostname, const std::string& server_config, bool ecdsa_ok, const std::vector<std::string>** out_certs, diff --git a/net/quic/crypto/proof_test.cc b/net/quic/crypto/proof_test.cc index 97b0dcb4ea..e4e661a298 100644 --- a/net/quic/crypto/proof_test.cc +++ b/net/quic/crypto/proof_test.cc @@ -25,21 +25,7 @@ using std::vector; namespace net { namespace test { -class ProofTest : public ::testing::TestWithParam<QuicVersion> { - protected: - ProofTest() { - version_ = GetParam(); - } - - QuicVersion version_; -}; - -// Run all ProofTests with QUIC versions 7 and 8. -INSTANTIATE_TEST_CASE_P(ProofTests, - ProofTest, - ::testing::Values(QUIC_VERSION_7, QUIC_VERSION_8)); - -TEST_P(ProofTest, Verify) { +TEST(ProofTest, Verify) { // TODO(rtenneti): Enable testing of ProofVerifier. #if 0 scoped_ptr<ProofSource> source(CryptoTestUtils::ProofSourceForTesting()); @@ -53,11 +39,10 @@ TEST_P(ProofTest, Verify) { string error_details, signature, first_signature; CertVerifyResult cert_verify_result; - ASSERT_TRUE(source->GetProof(version_, hostname, server_config, - false /* no ECDSA */, &first_certs, - &first_signature)); - ASSERT_TRUE(source->GetProof(version_, hostname, server_config, - false /* no ECDSA */, &certs, &signature)); + ASSERT_TRUE(source->GetProof(hostname, server_config, false /* no ECDSA */, + &first_certs, &first_signature)); + ASSERT_TRUE(source->GetProof(hostname, server_config, false /* no ECDSA */, + &certs, &signature)); // Check that the proof source is caching correctly: ASSERT_EQ(first_certs, certs); @@ -65,23 +50,22 @@ TEST_P(ProofTest, Verify) { int rv; TestCompletionCallback callback; - rv = verifier->VerifyProof(version_, hostname, server_config, *certs, - signature, &error_details, &cert_verify_result, + rv = verifier->VerifyProof(hostname, server_config, *certs, signature, + &error_details, &cert_verify_result, callback.callback()); rv = callback.GetResult(rv); ASSERT_EQ(OK, rv); ASSERT_EQ("", error_details); ASSERT_FALSE(IsCertStatusError(cert_verify_result.cert_status)); - rv = verifier->VerifyProof(version_, "foo.com", server_config, *certs, - signature, &error_details, &cert_verify_result, + rv = verifier->VerifyProof("foo.com", server_config, *certs, signature, + &error_details, &cert_verify_result, callback.callback()); rv = callback.GetResult(rv); ASSERT_EQ(ERR_FAILED, rv); ASSERT_NE("", error_details); - rv = verifier->VerifyProof(version_, hostname, - server_config.substr(1, string::npos), + rv = verifier->VerifyProof(hostname, server_config.substr(1, string::npos), *certs, signature, &error_details, &cert_verify_result, callback.callback()); rv = callback.GetResult(rv); @@ -89,7 +73,7 @@ TEST_P(ProofTest, Verify) { ASSERT_NE("", error_details); const string corrupt_signature = "1" + signature; - rv = verifier->VerifyProof(version_, hostname, server_config, *certs, + rv = verifier->VerifyProof(hostname, server_config, *certs, corrupt_signature, &error_details, &cert_verify_result, callback.callback()); rv = callback.GetResult(rv); @@ -100,8 +84,8 @@ TEST_P(ProofTest, Verify) { for (size_t i = 1; i < certs->size(); i++) { wrong_certs.push_back((*certs)[i]); } - rv = verifier->VerifyProof(version_, "foo.com", server_config, wrong_certs, - signature, &error_details, &cert_verify_result, + rv = verifier->VerifyProof("foo.com", server_config, wrong_certs, signature, + &error_details, &cert_verify_result, callback.callback()); rv = callback.GetResult(rv); ASSERT_EQ(ERR_FAILED, rv); @@ -138,8 +122,7 @@ class TestProofVerifierCallback : public ProofVerifierCallback { // RunVerification runs |verifier->VerifyProof| and asserts that the result // matches |expected_ok|. -static void RunVerification(QuicVersion version, - ProofVerifier* verifier, +static void RunVerification(ProofVerifier* verifier, const std::string& hostname, const std::string& server_config, const vector<std::string>& certs, @@ -153,7 +136,7 @@ static void RunVerification(QuicVersion version, new TestProofVerifierCallback(&comp_callback, &ok, &error_details); ProofVerifier::Status status = verifier->VerifyProof( - version, hostname, server_config, certs, proof, &error_details, &details, + hostname, server_config, certs, proof, &error_details, &details, callback); switch (status) { @@ -185,56 +168,11 @@ static string PEMCertFileToDER(const string& file_name) { // A known answer test that allows us to test ProofVerifier without a working // ProofSource. -TEST_P(ProofTest, VerifyRSAKnownAnswerTest) { +TEST(ProofTest, VerifyRSAKnownAnswerTest) { // These sample signatures were generated by running the Proof.Verify test // and dumping the bytes of the |signature| output of ProofSource::GetProof(). // sLen = special value -2 used by OpenSSL. static const unsigned char signature_data_0[] = { - 0x4c, 0x68, 0x3c, 0xc2, 0x1f, 0x31, 0x73, 0xa5, 0x29, 0xd3, - 0x56, 0x75, 0xb1, 0xbf, 0xbd, 0x31, 0x17, 0xfb, 0x2e, 0x24, - 0xb3, 0xc4, 0x0d, 0xfa, 0x56, 0xb8, 0x65, 0x94, 0x12, 0x38, - 0x6e, 0xff, 0xb3, 0x10, 0x2e, 0xf8, 0x5c, 0xc1, 0x21, 0x9d, - 0x29, 0x0c, 0x3a, 0x0a, 0x1a, 0xbf, 0x6b, 0x1c, 0x63, 0x77, - 0xf7, 0x86, 0xd3, 0xa4, 0x36, 0xf2, 0xb1, 0x6f, 0xac, 0xc3, - 0x23, 0x8d, 0xda, 0xe6, 0xd5, 0x83, 0xba, 0xdf, 0x28, 0x3e, - 0x7f, 0x4e, 0x79, 0xfc, 0xba, 0xdb, 0xf7, 0xd0, 0x4b, 0xad, - 0x79, 0xd0, 0xeb, 0xcf, 0xfa, 0x6e, 0x84, 0x44, 0x7a, 0x26, - 0xb1, 0x29, 0xa3, 0x08, 0xa8, 0x63, 0xfd, 0xed, 0x85, 0xff, - 0x9a, 0xe6, 0x79, 0x8b, 0xb6, 0x81, 0x13, 0x2c, 0xde, 0xe2, - 0xd8, 0x31, 0x29, 0xa4, 0xe0, 0x1b, 0x75, 0x2d, 0x8a, 0xf8, - 0x27, 0x55, 0xbc, 0xc7, 0x3b, 0x1e, 0xc1, 0x42, - }; - static const unsigned char signature_data_1[] = { - 0xbb, 0xd1, 0x17, 0x43, 0xf3, 0x42, 0x16, 0xe9, 0xf9, 0x76, - 0xe6, 0xe3, 0xaa, 0x50, 0x47, 0x5f, 0x93, 0xb6, 0x7d, 0x35, - 0x03, 0x49, 0x0a, 0x07, 0x61, 0xd5, 0xf1, 0x9c, 0x6b, 0xaf, - 0xaa, 0xd7, 0x64, 0xe4, 0x0a, 0x0c, 0xab, 0x97, 0xfb, 0x4e, - 0x5c, 0x14, 0x08, 0xf6, 0xb9, 0xa9, 0x1d, 0xa9, 0xf8, 0x6d, - 0xb0, 0x2b, 0x2a, 0x0e, 0xc4, 0xd0, 0xd2, 0xe9, 0x96, 0x4f, - 0x44, 0x70, 0x90, 0x46, 0xb9, 0xd5, 0x89, 0x72, 0xb9, 0xa8, - 0xe4, 0xfb, 0x88, 0xbc, 0x69, 0x7f, 0xc9, 0xdc, 0x84, 0x87, - 0x18, 0x21, 0x9b, 0xde, 0x22, 0x33, 0xde, 0x16, 0x3f, 0xe6, - 0xfd, 0x27, 0x56, 0xd3, 0xa4, 0x97, 0x91, 0x65, 0x1a, 0xe7, - 0x5e, 0x80, 0x9a, 0xbf, 0xbf, 0x1a, 0x29, 0x8a, 0xbe, 0xa2, - 0x8c, 0x9c, 0x23, 0xf4, 0xcb, 0xba, 0x79, 0x31, 0x28, 0xab, - 0x77, 0x94, 0x92, 0xb2, 0xc2, 0x35, 0xb2, 0xfa, - }; - static const unsigned char signature_data_2[] = { - 0x7e, 0x17, 0x01, 0xcb, 0x76, 0x9e, 0x9f, 0xce, 0xeb, 0x66, - 0x3e, 0xaa, 0xc9, 0x36, 0x5b, 0x7e, 0x48, 0x25, 0x99, 0xf8, - 0x0d, 0xe1, 0xa8, 0x48, 0x93, 0x3c, 0xe8, 0x97, 0x2e, 0x98, - 0xd6, 0x73, 0x0f, 0xd0, 0x74, 0x9c, 0x17, 0xef, 0xee, 0xf8, - 0x0e, 0x2a, 0x27, 0x3f, 0xc6, 0x55, 0xc6, 0xb9, 0xfe, 0x17, - 0xcc, 0xeb, 0x5d, 0xa1, 0xdc, 0xbd, 0x64, 0xd9, 0x5e, 0xec, - 0x57, 0x9d, 0xc3, 0xdc, 0x11, 0xbf, 0x23, 0x02, 0x58, 0xc4, - 0xf1, 0x18, 0xc1, 0x6f, 0x3f, 0xef, 0x18, 0x4d, 0xa6, 0x1e, - 0xe8, 0x25, 0x32, 0x8f, 0x92, 0x1e, 0xad, 0xbc, 0xbe, 0xde, - 0x83, 0x2a, 0x92, 0xd5, 0x59, 0x6f, 0xe4, 0x95, 0x6f, 0xe6, - 0xb1, 0xf9, 0xaf, 0x3f, 0xdb, 0x69, 0x6f, 0xae, 0xa6, 0x36, - 0xd2, 0x50, 0x81, 0x78, 0x41, 0x13, 0x2c, 0x65, 0x9c, 0x9e, - 0xf4, 0xd2, 0xd5, 0x58, 0x5b, 0x8b, 0x87, 0xcf, - }; - static const unsigned char signature_data_4[] = { 0x9e, 0xe6, 0x74, 0x3b, 0x8f, 0xb8, 0x66, 0x77, 0x57, 0x09, 0x8a, 0x04, 0xe9, 0xf0, 0x7c, 0x91, 0xa9, 0x5c, 0xe9, 0xdf, 0x12, 0x4d, 0x23, 0x82, 0x8c, 0x29, 0x72, 0x7f, 0xc2, 0x20, @@ -249,7 +187,7 @@ TEST_P(ProofTest, VerifyRSAKnownAnswerTest) { 0x78, 0xc8, 0x8b, 0xf5, 0xb9, 0x36, 0x5d, 0x72, 0x1f, 0xfc, 0x14, 0xff, 0xa7, 0x81, 0x27, 0x49, 0xae, 0xe1, }; - static const unsigned char signature_data_5[] = { + static const unsigned char signature_data_1[] = { 0x5e, 0xc2, 0xab, 0x6b, 0x16, 0xe6, 0x55, 0xf3, 0x16, 0x46, 0x35, 0xdc, 0xcc, 0xde, 0xd0, 0xbd, 0x6c, 0x66, 0xb2, 0x3d, 0xd3, 0x14, 0x78, 0xed, 0x47, 0x55, 0xfb, 0xdb, 0xe1, 0x7d, @@ -264,7 +202,7 @@ TEST_P(ProofTest, VerifyRSAKnownAnswerTest) { 0xaf, 0x6b, 0x47, 0xbc, 0x16, 0x55, 0x37, 0x0a, 0xbe, 0x0e, 0xc5, 0x75, 0x3f, 0x3d, 0x8e, 0xe8, 0x44, 0xe3, }; - static const unsigned char signature_data_6[] = { + static const unsigned char signature_data_2[] = { 0x8e, 0x5c, 0x78, 0x63, 0x74, 0x99, 0x2e, 0x96, 0xc0, 0x14, 0x8d, 0xb5, 0x13, 0x74, 0xa3, 0xa4, 0xe0, 0x43, 0x3e, 0x85, 0xba, 0x8f, 0x3c, 0x5e, 0x14, 0x64, 0x0e, 0x5e, 0xff, 0x89, @@ -295,52 +233,41 @@ TEST_P(ProofTest, VerifyRSAKnownAnswerTest) { // Signatures are nondeterministic, so we test multiple signatures on the // same server_config. vector<string> signatures(3); - if (version_ < QUIC_VERSION_8) { - signatures[0].assign(reinterpret_cast<const char*>(signature_data_0), - sizeof(signature_data_0)); - signatures[1].assign(reinterpret_cast<const char*>(signature_data_1), - sizeof(signature_data_1)); - signatures[2].assign(reinterpret_cast<const char*>(signature_data_2), - sizeof(signature_data_2)); - } else { - signatures[0].assign(reinterpret_cast<const char*>(signature_data_4), - sizeof(signature_data_4)); - signatures[1].assign(reinterpret_cast<const char*>(signature_data_5), - sizeof(signature_data_5)); - signatures[2].assign(reinterpret_cast<const char*>(signature_data_6), - sizeof(signature_data_6)); - } + signatures[0].assign(reinterpret_cast<const char*>(signature_data_0), + sizeof(signature_data_0)); + signatures[1].assign(reinterpret_cast<const char*>(signature_data_1), + sizeof(signature_data_1)); + signatures[2].assign(reinterpret_cast<const char*>(signature_data_2), + sizeof(signature_data_2)); for (size_t i = 0; i < signatures.size(); i++) { const string& signature = signatures[i]; RunVerification( - version_, verifier.get(), hostname, server_config, certs, signature, - true); + verifier.get(), hostname, server_config, certs, signature, true); RunVerification( - version_, verifier.get(), "foo.com", server_config, certs, signature, - false); + verifier.get(), "foo.com", server_config, certs, signature, false); RunVerification( - version_, verifier.get(), hostname, - server_config.substr(1, string::npos), certs, signature, false); + verifier.get(), hostname, server_config.substr(1, string::npos), + certs, signature, false); const string corrupt_signature = "1" + signature; RunVerification( - version_, verifier.get(), hostname, server_config, certs, - corrupt_signature, false); + verifier.get(), hostname, server_config, certs, corrupt_signature, + false); vector<string> wrong_certs; for (size_t i = 1; i < certs.size(); i++) { wrong_certs.push_back(certs[i]); } - RunVerification(version_, verifier.get(), hostname, server_config, - wrong_certs, signature, false); + RunVerification(verifier.get(), hostname, server_config, wrong_certs, + signature, false); } } // A known answer test that allows us to test ProofVerifier without a working // ProofSource. -TEST_P(ProofTest, VerifyECDSAKnownAnswerTest) { +TEST(ProofTest, VerifyECDSAKnownAnswerTest) { // Disable this test on platforms that do not support ECDSA certificates. #if defined(OS_WIN) if (base::win::GetVersion() < base::win::VERSION_VISTA) @@ -406,36 +333,34 @@ TEST_P(ProofTest, VerifyECDSAKnownAnswerTest) { const string& signature = signatures[i]; RunVerification( - version_, verifier.get(), hostname, server_config, certs, signature, - true); + verifier.get(), hostname, server_config, certs, signature, true); RunVerification( - version_, verifier.get(), "foo.com", server_config, certs, signature, - false); + verifier.get(), "foo.com", server_config, certs, signature, false); RunVerification( - version_, verifier.get(), hostname, - server_config.substr(1, string::npos), certs, signature, false); + verifier.get(), hostname, server_config.substr(1, string::npos), + certs, signature, false); // An ECDSA signature is DER-encoded. Corrupt the last byte so that the // signature can still be DER-decoded correctly. string corrupt_signature = signature; corrupt_signature[corrupt_signature.size() - 1] += 1; RunVerification( - version_, verifier.get(), hostname, server_config, certs, - corrupt_signature, false); + verifier.get(), hostname, server_config, certs, corrupt_signature, + false); // Prepending a "1" makes the DER invalid. const string bad_der_signature1 = "1" + signature; RunVerification( - version_, verifier.get(), hostname, server_config, certs, - bad_der_signature1, false); + verifier.get(), hostname, server_config, certs, bad_der_signature1, + false); vector<string> wrong_certs; for (size_t i = 1; i < certs.size(); i++) { wrong_certs.push_back(certs[i]); } RunVerification( - version_, verifier.get(), hostname, server_config, wrong_certs, - signature, false); + verifier.get(), hostname, server_config, wrong_certs, signature, + false); } } diff --git a/net/quic/crypto/proof_verifier.h b/net/quic/crypto/proof_verifier.h index ecab113e69..f469c55295 100644 --- a/net/quic/crypto/proof_verifier.h +++ b/net/quic/crypto/proof_verifier.h @@ -10,7 +10,6 @@ #include "net/base/completion_callback.h" #include "net/base/net_export.h" -#include "net/quic/quic_protocol.h" namespace net { @@ -71,11 +70,7 @@ class NET_EXPORT_PRIVATE ProofVerifier { // // The signature uses SHA-256 as the hash function and PSS padding in the // case of RSA. - // - // |version| is the QUIC version for the connection. TODO(wtc): Remove once - // QUIC_VERSION_7 and before are removed. - virtual Status VerifyProof(QuicVersion version, - const std::string& hostname, + virtual Status VerifyProof(const std::string& hostname, const std::string& server_config, const std::vector<std::string>& certs, const std::string& signature, diff --git a/net/quic/crypto/proof_verifier_chromium.cc b/net/quic/crypto/proof_verifier_chromium.cc index 88653053f3..8c4796204e 100644 --- a/net/quic/crypto/proof_verifier_chromium.cc +++ b/net/quic/crypto/proof_verifier_chromium.cc @@ -42,7 +42,6 @@ ProofVerifierChromium::~ProofVerifierChromium() { } ProofVerifierChromium::Status ProofVerifierChromium::VerifyProof( - QuicVersion version, const string& hostname, const string& server_config, const vector<string>& certs, @@ -90,7 +89,7 @@ ProofVerifierChromium::Status ProofVerifierChromium::VerifyProof( // We call VerifySignature first to avoid copying of server_config and // signature. - if (!VerifySignature(version, server_config, signature, certs[0])) { + if (!VerifySignature(server_config, signature, certs[0])) { *error_details = "Failed to verify signature of server config"; DLOG(WARNING) << *error_details; verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID; @@ -177,8 +176,7 @@ int ProofVerifierChromium::DoVerifyCertComplete(int result) { return result; } -bool ProofVerifierChromium::VerifySignature(QuicVersion version, - const string& signed_data, +bool ProofVerifierChromium::VerifySignature(const string& signed_data, const string& signature, const string& cert) { StringPiece spki; @@ -198,11 +196,9 @@ bool ProofVerifierChromium::VerifySignature(QuicVersion version, crypto::SignatureVerifier::SHA256; crypto::SignatureVerifier::HashAlgorithm mask_hash_alg = hash_alg; unsigned int hash_len = 32; // 32 is the length of a SHA-256 hash. - unsigned int salt_len = - version >= QUIC_VERSION_8 ? hash_len : signature.size() - hash_len - 2; bool ok = verifier.VerifyInitRSAPSS( - hash_alg, mask_hash_alg, salt_len, + hash_alg, mask_hash_alg, hash_len, reinterpret_cast<const uint8*>(signature.data()), signature.size(), reinterpret_cast<const uint8*>(spki.data()), spki.size()); if (!ok) { diff --git a/net/quic/crypto/proof_verifier_chromium.h b/net/quic/crypto/proof_verifier_chromium.h index 8786e52e7d..4969cc8aa5 100644 --- a/net/quic/crypto/proof_verifier_chromium.h +++ b/net/quic/crypto/proof_verifier_chromium.h @@ -39,8 +39,7 @@ class NET_EXPORT_PRIVATE ProofVerifierChromium : public ProofVerifier { virtual ~ProofVerifierChromium(); // ProofVerifier interface - virtual Status VerifyProof(QuicVersion version, - const std::string& hostname, + virtual Status VerifyProof(const std::string& hostname, const std::string& server_config, const std::vector<std::string>& certs, const std::string& signature, @@ -60,8 +59,7 @@ class NET_EXPORT_PRIVATE ProofVerifierChromium : public ProofVerifier { int DoVerifyCert(int result); int DoVerifyCertComplete(int result); - bool VerifySignature(QuicVersion version, - const std::string& signed_data, + bool VerifySignature(const std::string& signed_data, const std::string& signature, const std::string& cert); diff --git a/net/quic/quic_client_session.cc b/net/quic/quic_client_session.cc index ec87d779d5..ca6941c7e1 100644 --- a/net/quic/quic_client_session.cc +++ b/net/quic/quic_client_session.cc @@ -314,6 +314,12 @@ void QuicClientSession::ConnectionClose(QuicErrorCode error, bool from_peer) { NotifyFactoryOfSessionCloseLater(); } +void QuicClientSession::OnSuccessfulVersionNegotiation( + const QuicVersion& version) { + logger_.OnSuccessfulVersionNegotiation(version); + QuicSession::OnSuccessfulVersionNegotiation(version); +} + void QuicClientSession::StartReading() { if (read_pending_) { return; diff --git a/net/quic/quic_client_session.h b/net/quic/quic_client_session.h index a5973b6b61..d167237d80 100644 --- a/net/quic/quic_client_session.h +++ b/net/quic/quic_client_session.h @@ -113,6 +113,8 @@ class NET_EXPORT_PRIVATE QuicClientSession : public QuicSession { // QuicConnectionVisitorInterface methods: virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE; + virtual void OnSuccessfulVersionNegotiation( + const QuicVersion& version) OVERRIDE; // Performs a crypto handshake with the server. int CryptoConnect(bool require_confirmation, diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc index 748821e445..7417bd435a 100644 --- a/net/quic/quic_connection.cc +++ b/net/quic/quic_connection.cc @@ -19,6 +19,7 @@ using std::list; using std::make_pair; using std::min; using std::max; +using std::numeric_limits; using std::vector; using std::set; using std::string; @@ -32,7 +33,7 @@ const QuicPacketSequenceNumber kMaxPacketGap = 5000; // We want to make sure if we get a large nack packet, we don't queue up too // many packets at once. 10 is arbitrary. -const int kMaxRetransmissionsPerAck = 10; +const size_t kMaxRetransmissionsPerAck = 10; // TCP retransmits after 2 nacks. We allow for a third in case of out-of-order // delivery. @@ -125,6 +126,21 @@ class TimeoutAlarm : public QuicAlarm::Delegate { QuicConnection* connection_; }; +// Indicates if any of the frames are intended to be sent with FORCE. +// Returns true when one of the frames is a CONNECTION_CLOSE_FRAME. +net::QuicConnection::Force HasForcedFrames( + const RetransmittableFrames* retransmittable_frames) { + if (!retransmittable_frames) { + return net::QuicConnection::NO_FORCE; + } + for (size_t i = 0; i < retransmittable_frames->frames().size(); ++i) { + if (retransmittable_frames->frames()[i].type == CONNECTION_CLOSE_FRAME) { + return net::QuicConnection::FORCE; + } + } + return net::QuicConnection::NO_FORCE; +} + } // namespace // TODO(rch): Remove this. @@ -168,6 +184,7 @@ QuicConnection::QuicConnection(QuicGuid guid, time_of_last_received_packet_(clock_->ApproximateNow()), time_of_last_sent_packet_(clock_->ApproximateNow()), congestion_manager_(clock_, kTCP), + sent_packet_manager_(is_server, this), version_negotiation_state_(START_NEGOTIATION), max_packets_per_retransmission_alarm_(kMaxPacketsPerRetransmissionAlarm), is_server_(is_server), @@ -192,7 +209,6 @@ QuicConnection::QuicConnection(QuicGuid guid, QuicConnection::~QuicConnection() { STLDeleteElements(&ack_notifiers_); STLDeleteElements(&undecryptable_packets_); - STLDeleteValues(&unacked_packets_); STLDeleteValues(&group_map_); for (QueuedPacketList::iterator it = queued_packets_.begin(); it != queued_packets_.end(); ++it) { @@ -282,6 +298,7 @@ bool QuicConnection::OnProtocolVersionMismatch(QuicVersion received_version) { } version_negotiation_state_ = NEGOTIATED_VERSION; + visitor_->OnSuccessfulVersionNegotiation(received_version); // Store the new version. framer_.set_version(received_version); @@ -380,6 +397,7 @@ bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { DCHECK_EQ(1u, header.public_header.versions.size()); DCHECK_EQ(header.public_header.versions[0], version()); version_negotiation_state_ = NEGOTIATED_VERSION; + visitor_->OnSuccessfulVersionNegotiation(version()); } } else { DCHECK(!header.public_header.version_flag); @@ -387,6 +405,7 @@ bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { // it should stop sending version since the version negotiation is done. packet_creator_.StopSendingVersion(); version_negotiation_state_ = NEGOTIATED_VERSION; + visitor_->OnSuccessfulVersionNegotiation(version()); } } @@ -433,6 +452,17 @@ bool QuicConnection::OnAckFrame(const QuicAckFrame& incoming_ack) { SendConnectionClose(QUIC_INVALID_ACK_DATA); return false; } + + // Reset the RTO timeout for each packet when an ack is received. + if (retransmission_alarm_->IsSet()) { + retransmission_alarm_->Cancel(); + QuicTime::Delta retransmission_delay = + congestion_manager_.GetRetransmissionDelay( + sent_packet_manager_.GetNumUnackedPackets(), 0); + retransmission_alarm_->Set(clock_->ApproximateNow().Add( + retransmission_delay)); + } + last_ack_frames_.push_back(incoming_ack); return connected_; } @@ -452,12 +482,11 @@ void QuicConnection::ProcessAckFrame(const QuicAckFrame& incoming_ack) { sent_entropy_manager_.ClearEntropyBefore( received_packet_manager_.least_packet_awaited_by_peer() - 1); + retransmitted_nacked_packet_count_ = 0; SequenceNumberSet acked_packets; - HandleAckForSentPackets(incoming_ack, &acked_packets); - HandleAckForSentFecPackets(incoming_ack, &acked_packets); + sent_packet_manager_.HandleAckForSentPackets(incoming_ack, &acked_packets); + sent_packet_manager_.HandleAckForSentFecPackets(incoming_ack, &acked_packets); if (acked_packets.size() > 0) { - visitor_->OnAck(acked_packets); - // Inform all the registered AckNotifiers of the new ACKs. // TODO(rjshade): Make this more efficient by maintaining a mapping of // <sequence number, set<AckNotifierList>> so that OnAck @@ -475,6 +504,13 @@ void QuicConnection::ProcessAckFrame(const QuicAckFrame& incoming_ack) { } } } + // Clear the earliest retransmission timeouts that are no longer unacked to + // ensure the priority queue doesn't become too large. + while (!retransmission_timeouts_.empty() && + !sent_packet_manager_.IsUnacked( + retransmission_timeouts_.top().sequence_number)) { + retransmission_timeouts_.pop(); + } congestion_manager_.OnIncomingAckFrame(incoming_ack, time_of_last_received_packet_); } @@ -563,73 +599,6 @@ bool QuicConnection::ValidateAckFrame(const QuicAckFrame& incoming_ack) { return true; } -void QuicConnection::HandleAckForSentPackets(const QuicAckFrame& incoming_ack, - SequenceNumberSet* acked_packets) { - int retransmitted_packets = 0; - // Go through the packets we have not received an ack for and see if this - // incoming_ack shows they've been seen by the peer. - UnackedPacketMap::iterator it = unacked_packets_.begin(); - while (it != unacked_packets_.end()) { - QuicPacketSequenceNumber sequence_number = it->first; - if (sequence_number > - received_packet_manager_.peer_largest_observed_packet()) { - // These are very new sequence_numbers. - break; - } - RetransmittableFrames* unacked = it->second; - if (!IsAwaitingPacket(incoming_ack.received_info, sequence_number)) { - // Packet was acked, so remove it from our unacked packet list. - DVLOG(1) << ENDPOINT <<"Got an ack for packet " << sequence_number; - acked_packets->insert(sequence_number); - delete unacked; - unacked_packets_.erase(it++); - retransmission_map_.erase(sequence_number); - } else { - // This is a packet which we planned on retransmitting and has not been - // seen at the time of this ack being sent out. See if it's our new - // lowest unacked packet. - DVLOG(1) << ENDPOINT << "still missing packet " << sequence_number; - ++it; - // The peer got packets after this sequence number. This is an explicit - // nack. - RetransmissionMap::iterator retransmission_it = - retransmission_map_.find(sequence_number); - ++(retransmission_it->second.number_nacks); - if (retransmission_it->second.number_nacks >= - kNumberOfNacksBeforeRetransmission && - retransmitted_packets < kMaxRetransmissionsPerAck) { - ++retransmitted_packets; - DVLOG(1) << ENDPOINT << "Trying to retransmit packet " - << sequence_number - << " as it has been nacked 3 or more times."; - // RetransmitPacket will retransmit with a new sequence_number. - RetransmitPacket(sequence_number); - } - } - } -} - -void QuicConnection::HandleAckForSentFecPackets( - const QuicAckFrame& incoming_ack, SequenceNumberSet* acked_packets) { - UnackedPacketMap::iterator it = unacked_fec_packets_.begin(); - while (it != unacked_fec_packets_.end()) { - QuicPacketSequenceNumber sequence_number = it->first; - if (sequence_number > - received_packet_manager_.peer_largest_observed_packet()) { - break; - } - if (!IsAwaitingPacket(incoming_ack.received_info, sequence_number)) { - DVLOG(1) << ENDPOINT << "Got an ack for fec packet: " << sequence_number; - acked_packets->insert(sequence_number); - unacked_fec_packets_.erase(it++); - } else { - DVLOG(1) << ENDPOINT << "Still missing ack for fec packet: " - << sequence_number; - ++it; - } - } -} - void QuicConnection::OnFecData(const QuicFecData& fec) { DCHECK_EQ(IN_FEC_GROUP, last_header_.is_in_fec_group); DCHECK_NE(0u, last_header_.fec_group); @@ -699,9 +668,8 @@ void QuicConnection::OnPacketComplete() { // from unacket_packets_, increasing the least_unacked. const bool last_packet_should_instigate_ack = ShouldLastPacketInstigateAck(); - if ((last_stream_frames_.empty() || - visitor_->OnPacket(self_address_, peer_address_, - last_header_, last_stream_frames_))) { + if (last_stream_frames_.empty() || + visitor_->OnStreamFrames(last_stream_frames_)) { received_packet_manager_.RecordPacketReceived( last_header_, time_of_last_received_packet_); } @@ -759,13 +727,10 @@ bool QuicConnection::ShouldLastPacketInstigateAck() { // the high water mark. if (!last_ack_frames_.empty() && !last_ack_frames_.back().received_info.missing_packets.empty() && - !unacked_packets_.empty()) { - if (unacked_packets_.begin()->first > - *last_ack_frames_.back().received_info.missing_packets.begin()) { - return true; - } + sent_packet_manager_.HasUnackedPackets()) { + return sent_packet_manager_.GetLeastUnackedSentPacket() > + *last_ack_frames_.back().received_info.missing_packets.begin(); } - return false; } @@ -781,7 +746,7 @@ void QuicConnection::MaybeSendInResponseToPacket( // Set the ack alarm for when any retransmittable frame is received. if (!ack_alarm_->IsSet()) { ack_alarm_->Set(clock_->ApproximateNow().Add( - congestion_manager_.DefaultRetransmissionTime())); + congestion_manager_.DelayedAckTime())); } } send_ack_in_response_to_packet_ = !send_ack_in_response_to_packet_; @@ -817,39 +782,85 @@ void QuicConnection::SendVersionNegotiationPacket() { delete encrypted; } -QuicConsumedData QuicConnection::SendStreamData(QuicStreamId id, - StringPiece data, - QuicStreamOffset offset, - bool fin) { - // To make reasoning about crypto frames easier, we don't combine them with - // any other frames in a single packet. - const bool crypto_frame_while_batch_mode = - id == kCryptoStreamId && packet_generator_.InBatchMode(); +QuicConsumedData QuicConnection::SendvStreamDataInner( + QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin, + QuicAckNotifier* notifier) { + // TODO(ianswett): Further improve sending by passing the iovec down + // instead of batching into multiple stream frames in a single packet. + const bool already_in_batch_mode = packet_generator_.InBatchMode(); + packet_generator_.StartBatchOperations(); - if (crypto_frame_while_batch_mode) { - // Flush pending frames to make room for a crypto frame. - packet_generator_.FinishBatchOperations(); + size_t bytes_written = 0; + bool fin_consumed = false; + + for (int i = 0; i < iov_count; ++i) { + bool send_fin = fin && (i == iov_count - 1); + if (!send_fin && iov[i].iov_len == 0) { + LOG(DFATAL) << "Attempt to send empty stream frame"; + } + + StringPiece data(static_cast<char*>(iov[i].iov_base), iov[i].iov_len); + int currentOffset = offset + bytes_written; + QuicConsumedData consumed_data = + packet_generator_.ConsumeData(id, + data, + currentOffset, + send_fin, + notifier); + + DCHECK_LE(consumed_data.bytes_consumed, numeric_limits<uint32>::max()); + bytes_written += consumed_data.bytes_consumed; + fin_consumed = consumed_data.fin_consumed; + // If no bytes were consumed, bail now, because the stream can not write + // more data. + if (consumed_data.bytes_consumed < iov[i].iov_len) { + break; + } } - QuicConsumedData consumed_data = - packet_generator_.ConsumeData(id, data, offset, fin); - if (crypto_frame_while_batch_mode) { - // Restore batch mode. - packet_generator_.StartBatchOperations(); + // Handle the 0 byte write properly. + if (iov_count == 0) { + DCHECK(fin); + QuicConsumedData consumed_data = packet_generator_.ConsumeData( + id, StringPiece(), offset, fin, NULL); + fin_consumed = consumed_data.fin_consumed; } - return consumed_data; + + // Leave the generator in the original batch state. + if (!already_in_batch_mode) { + packet_generator_.FinishBatchOperations(); + } + DCHECK_EQ(already_in_batch_mode, packet_generator_.InBatchMode()); + + return QuicConsumedData(bytes_written, fin_consumed); } -QuicConsumedData QuicConnection::SendStreamDataAndNotifyWhenAcked( +QuicConsumedData QuicConnection::SendvStreamData(QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin) { + return SendvStreamDataInner(id, iov, iov_count, offset, fin, NULL); +} + +QuicConsumedData QuicConnection::SendvStreamDataAndNotifyWhenAcked( QuicStreamId id, - StringPiece data, + const struct iovec* iov, + int iov_count, QuicStreamOffset offset, bool fin, QuicAckNotifier::DelegateInterface* delegate) { + if (!fin && iov_count == 0) { + LOG(DFATAL) << "Attempt to send empty stream frame"; + } // This notifier will be deleted in ProcessAckFrame once it has seen ACKs for // all the consumed data (or below if no data was consumed). QuicAckNotifier* notifier = new QuicAckNotifier(delegate); QuicConsumedData consumed_data = - packet_generator_.ConsumeData(id, data, offset, fin, notifier); + SendvStreamDataInner(id, iov, iov_count, offset, fin, notifier); if (consumed_data.bytes_consumed > 0) { // If some data was consumed, then the delegate should be registered for @@ -938,38 +949,30 @@ bool QuicConnection::DoWrite() { DCHECK(!write_blocked_); WriteQueuedPackets(); - // We are postulating if we are not yet forward secure, the visitor may have - // handshake messages to send. - // TODO(jar): add a new visitor_ method that returns whether it has handshake - // messages to send, and call it and pass the return value to each CanWrite - // call. - const IsHandshake maybe_handshake = - encryption_level_ == ENCRYPTION_FORWARD_SECURE ? NOT_HANDSHAKE - : IS_HANDSHAKE; - + IsHandshake pending_handshake = visitor_->HasPendingHandshake() ? + IS_HANDSHAKE : NOT_HANDSHAKE; // Sending queued packets may have caused the socket to become write blocked, // or the congestion manager to prohibit sending. If we've sent everything // we had queued and we're still not blocked, let the visitor know it can // write more. if (CanWrite(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, - maybe_handshake)) { - const bool in_batch_mode = packet_generator_.InBatchMode(); - if (!in_batch_mode) { + pending_handshake)) { + const bool already_in_batch_mode = packet_generator_.InBatchMode(); + if (!already_in_batch_mode) { packet_generator_.StartBatchOperations(); } bool all_bytes_written = visitor_->OnCanWrite(); - if (!in_batch_mode) { + if (!already_in_batch_mode) { packet_generator_.FinishBatchOperations(); } // After the visitor writes, it may have caused the socket to become write // blocked or the congestion manager to prohibit sending, so check again. - // TODO(jar): we need to pass NOT_HANDSHAKE instead of maybe_handshake to - // this CanWrite call to avoid getting into an infinite loop calling - // DoWrite. + pending_handshake = visitor_->HasPendingHandshake() ? IS_HANDSHAKE + : NOT_HANDSHAKE; if (!write_blocked_ && !all_bytes_written && CanWrite(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, - NOT_HANDSHAKE)) { + pending_handshake)) { // We're not write blocked, but some stream didn't write out all of its // bytes. Register for 'immediate' resumption so we'll keep writing after // other quic connections have had a chance to use the socket. @@ -1003,7 +1006,7 @@ bool QuicConnection::WriteQueuedPackets() { packet_iterator->sequence_number, packet_iterator->packet, packet_iterator->retransmittable, - NO_FORCE)) { + packet_iterator->forced)) { packet_iterator = queued_packets_.erase(packet_iterator); } else { // Continue, because some queued packets may still be writable. @@ -1017,101 +1020,72 @@ bool QuicConnection::WriteQueuedPackets() { bool QuicConnection::MaybeRetransmitPacketForRTO( QuicPacketSequenceNumber sequence_number) { - DCHECK_EQ(ContainsKey(unacked_packets_, sequence_number), - ContainsKey(retransmission_map_, sequence_number)); - - if (!ContainsKey(unacked_packets_, sequence_number)) { + if (!sent_packet_manager_.IsUnacked(sequence_number)) { DVLOG(2) << ENDPOINT << "alarm fired for " << sequence_number << " but it has been acked or already retransmitted with" - << " different sequence number."; + << " a different sequence number."; // So no extra delay is added for this packet. return true; } - RetransmissionMap::iterator retransmission_it = - retransmission_map_.find(sequence_number); // If the packet hasn't been acked and we're getting truncated acks, ignore // any RTO for packets larger than the peer's largest observed packet; it may // have been received by the peer and just wasn't acked due to the ack frame // running out of space. - if (received_truncated_ack_ && sequence_number > - received_packet_manager_.peer_largest_observed_packet() && + if (received_truncated_ack_ && + sequence_number > GetPeerLargestObservedPacket() && // We allow retransmission of already retransmitted packets so that we // retransmit packets that were retransmissions of the packet with // sequence number < the largest observed field of the truncated ack. - retransmission_it->second.number_retransmissions == 0) { + !sent_packet_manager_.IsRetransmission(sequence_number)) { return false; - } else { - ++stats_.rto_count; - RetransmitPacket(sequence_number); - return true; } + + ++stats_.rto_count; + RetransmitPacket(sequence_number); + return true; } void QuicConnection::RetransmitUnackedPackets( RetransmissionType retransmission_type) { - if (unacked_packets_.empty()) { + SequenceNumberSet unacked_packets = sent_packet_manager_.GetUnackedPackets(); + if (unacked_packets.empty()) { return; } - UnackedPacketMap::iterator next_it = unacked_packets_.begin(); - QuicPacketSequenceNumber end_sequence_number = - unacked_packets_.rbegin()->first; - do { - UnackedPacketMap::iterator current_it = next_it; - ++next_it; + for (SequenceNumberSet::const_iterator unacked_it = unacked_packets.begin(); + unacked_it != unacked_packets.end(); ++unacked_it) { + const RetransmittableFrames& frames = + sent_packet_manager_.GetRetransmittableFrames(*unacked_it); if (retransmission_type == ALL_PACKETS || - current_it->second->encryption_level() == ENCRYPTION_INITIAL) { + frames.encryption_level() == ENCRYPTION_INITIAL) { // TODO(satyamshekhar): Think about congestion control here. // Specifically, about the retransmission count of packets being sent // proactively to achieve 0 (minimal) RTT. - RetransmitPacket(current_it->first); + RetransmitPacket(*unacked_it); } - } while (next_it != unacked_packets_.end() && - next_it->first <= end_sequence_number); + } } void QuicConnection::RetransmitPacket( QuicPacketSequenceNumber sequence_number) { - UnackedPacketMap::iterator unacked_it = - unacked_packets_.find(sequence_number); - RetransmissionMap::iterator retransmission_it = - retransmission_map_.find(sequence_number); - // There should always be an entry corresponding to |sequence_number| in - // both |retransmission_map_| and |unacked_packets_|. Retransmissions due to - // RTO for sequence numbers that are already acked or retransmitted are - // ignored by MaybeRetransmitPacketForRTO. - DCHECK(unacked_it != unacked_packets_.end()); - DCHECK(retransmission_it != retransmission_map_.end()); - RetransmittableFrames* unacked = unacked_it->second; + DCHECK(sent_packet_manager_.IsUnacked(sequence_number)); + // TODO(pwestin): Need to fix potential issue with FEC and a 1 packet // congestion window see b/8331807 for details. congestion_manager_.AbandoningPacket(sequence_number); + const RetransmittableFrames& retransmittable_frames = + sent_packet_manager_.GetRetransmittableFrames(sequence_number); + // Re-packetize the frames with a new sequence number for retransmission. // Retransmitted data packets do not use FEC, even when it's enabled. // Retransmitted packets use the same sequence number length as the original. QuicSequenceNumberLength original_sequence_number_length = - retransmission_it->second.sequence_number_length; + sent_packet_manager_.GetSequenceNumberLength(sequence_number); SerializedPacket serialized_packet = - packet_creator_.ReserializeAllFrames(unacked->frames(), + packet_creator_.ReserializeAllFrames(retransmittable_frames.frames(), original_sequence_number_length); - RetransmissionInfo retransmission_info( - serialized_packet.sequence_number, - serialized_packet.sequence_number_length); - retransmission_info.number_retransmissions = - retransmission_it->second.number_retransmissions + 1; - // Remove info with old sequence number. - unacked_packets_.erase(unacked_it); - retransmission_map_.erase(retransmission_it); - DLOG(INFO) << ENDPOINT << "Retransmitting unacked packet " << sequence_number - << " as " << serialized_packet.sequence_number; - DCHECK(unacked_packets_.empty() || - unacked_packets_.rbegin()->first < serialized_packet.sequence_number); - unacked_packets_.insert(make_pair(serialized_packet.sequence_number, - unacked)); - retransmission_map_.insert(make_pair(serialized_packet.sequence_number, - retransmission_info)); // A notifier may be waiting to hear about ACKs for the original sequence // number. Inform them that the sequence number has changed. @@ -1121,15 +1095,21 @@ void QuicConnection::RetransmitPacket( serialized_packet.sequence_number); } + DLOG(INFO) << ENDPOINT << "Retransmitting " << sequence_number << " as " + << serialized_packet.sequence_number; if (debug_visitor_) { debug_visitor_->OnPacketRetransmitted(sequence_number, serialized_packet.sequence_number); } - SendOrQueuePacket(unacked->encryption_level(), + sent_packet_manager_.OnRetransmittedPacket(sequence_number, + serialized_packet.sequence_number); + + SendOrQueuePacket(retransmittable_frames.encryption_level(), serialized_packet.sequence_number, serialized_packet.packet, serialized_packet.entropy_hash, - HAS_RETRANSMITTABLE_DATA); + HAS_RETRANSMITTABLE_DATA, + HasForcedFrames(serialized_packet.retransmittable_frames)); } bool QuicConnection::CanWrite(Retransmission retransmission, @@ -1157,30 +1137,22 @@ bool QuicConnection::CanWrite(Retransmission retransmission, return true; } -bool QuicConnection::IsRetransmission( - QuicPacketSequenceNumber sequence_number) { - RetransmissionMap::iterator it = retransmission_map_.find(sequence_number); - return it != retransmission_map_.end() && - it->second.number_retransmissions > 0; -} - void QuicConnection::SetupRetransmission( QuicPacketSequenceNumber sequence_number, EncryptionLevel level) { - RetransmissionMap::iterator it = retransmission_map_.find(sequence_number); - if (it == retransmission_map_.end()) { + if (!sent_packet_manager_.IsUnacked(sequence_number)) { DVLOG(1) << ENDPOINT << "Will not retransmit packet " << sequence_number; return; } - - RetransmissionInfo retransmission_info = it->second; + size_t retransmission_count = + sent_packet_manager_.GetRetransmissionCount(sequence_number); // TODO(rch): consider using a much smaller retransmisison_delay // for the ENCRYPTION_NONE packets. size_t effective_retransmission_count = - level == ENCRYPTION_NONE ? 0 : retransmission_info.number_retransmissions; + level == ENCRYPTION_NONE ? 0 : retransmission_count; QuicTime::Delta retransmission_delay = congestion_manager_.GetRetransmissionDelay( - unacked_packets_.size(), + sent_packet_manager_.GetNumUnackedPackets(), effective_retransmission_count); retransmission_timeouts_.push(RetransmissionTime( @@ -1201,7 +1173,6 @@ void QuicConnection::SetupRetransmission( void QuicConnection::SetupAbandonFecTimer( QuicPacketSequenceNumber sequence_number) { - DCHECK(ContainsKey(unacked_fec_packets_, sequence_number)); QuicTime::Delta retransmission_delay = QuicTime::Delta::FromMilliseconds( congestion_manager_.DefaultRetransmissionTime().ToMilliseconds() * 3); @@ -1211,21 +1182,6 @@ void QuicConnection::SetupAbandonFecTimer( true)); } -void QuicConnection::DropPacket(QuicPacketSequenceNumber sequence_number) { - UnackedPacketMap::iterator unacked_it = - unacked_packets_.find(sequence_number); - // Packet was not meant to be retransmitted. - if (unacked_it == unacked_packets_.end()) { - DCHECK(!ContainsKey(retransmission_map_, sequence_number)); - return; - } - // Delete the unacked packet. - delete unacked_it->second; - unacked_packets_.erase(unacked_it); - retransmission_map_.erase(sequence_number); - return; -} - bool QuicConnection::WritePacket(EncryptionLevel level, QuicPacketSequenceNumber sequence_number, QuicPacket* packet, @@ -1244,14 +1200,15 @@ bool QuicConnection::WritePacket(EncryptionLevel level, level == ENCRYPTION_NONE) { // Drop packets that are NULL encrypted since the peer won't accept them // anymore. - DLOG(INFO) << ENDPOINT << "Dropped packet: " << sequence_number + DLOG(INFO) << ENDPOINT << "Dropping packet: " << sequence_number << " since the packet is NULL encrypted."; - DropPacket(sequence_number); + sent_packet_manager_.DiscardPacket(sequence_number); delete packet; return true; } - Retransmission retransmission = IsRetransmission(sequence_number) ? + Retransmission retransmission = + sent_packet_manager_.IsRetransmission(sequence_number) ? IS_RETRANSMISSION : NOT_RETRANSMISSION; // TODO(wtc): use the same logic that is used in the packet generator. // Namely, a packet is a handshake if it contains a stream frame for the @@ -1305,7 +1262,11 @@ bool QuicConnection::WritePacket(EncryptionLevel level, // If the socket buffers the the data, then the packet should not // be queued and sent again, which would result in an unnecessary // duplicate packet being sent. - return helper_->IsWriteBlockedDataBuffered(); + if (helper_->IsWriteBlockedDataBuffered()) { + delete packet; + return true; + } + return false; } // We can't send an error as the socket is presumably borked. CloseConnection(QUIC_PACKET_WRITE_ERROR, false); @@ -1323,11 +1284,13 @@ bool QuicConnection::WritePacket(EncryptionLevel level, // TODO(ianswett): Change the sequence number length and other packet creator // options by a more explicit API than setting a struct value directly. - packet_creator_.options()->send_sequence_number_length = - CalculateSequenceNumberLength(sequence_number); + packet_creator_.UpdateSequenceNumberLength( + received_packet_manager_.least_packet_awaited_by_peer(), + congestion_manager_.BandwidthEstimate().ToBytesPerPeriod( + congestion_manager_.SmoothedRtt())); congestion_manager_.SentPacket(sequence_number, now, packet->length(), - retransmission); + retransmission, retransmittable); stats_.bytes_sent += encrypted->length(); ++stats_.packets_sent; @@ -1355,73 +1318,51 @@ int QuicConnection::WritePacketToWire(QuicPacketSequenceNumber sequence_number, return bytes_written; } -QuicSequenceNumberLength QuicConnection::CalculateSequenceNumberLength( - QuicPacketSequenceNumber sequence_number) { - DCHECK_LE(received_packet_manager_.least_packet_awaited_by_peer(), - sequence_number); - // Since the packet creator will not change sequence number length mid FEC - // group, include the size of an FEC group to be safe. - const QuicPacketSequenceNumber current_delta = - packet_creator_.options()->max_packets_per_fec_group + sequence_number - - received_packet_manager_.least_packet_awaited_by_peer(); - const uint64 congestion_window = - congestion_manager_.BandwidthEstimate().ToBytesPerPeriod( - congestion_manager_.SmoothedRtt()) / - packet_creator_.options()->max_packet_length; - const uint64 delta = max(current_delta, congestion_window); - - if (delta < 1 << ((PACKET_1BYTE_SEQUENCE_NUMBER * 8) - 2)) { - return PACKET_1BYTE_SEQUENCE_NUMBER; - } else if (delta < 1 << ((PACKET_2BYTE_SEQUENCE_NUMBER * 8) - 2)) { - return PACKET_2BYTE_SEQUENCE_NUMBER; - } else if (delta < 1 << ((PACKET_4BYTE_SEQUENCE_NUMBER * 8) - 2)) { - return PACKET_4BYTE_SEQUENCE_NUMBER; - } - return PACKET_6BYTE_SEQUENCE_NUMBER; -} - bool QuicConnection::OnSerializedPacket( const SerializedPacket& serialized_packet) { - if (serialized_packet.retransmittable_frames != NULL) { - DCHECK(unacked_packets_.empty() || - unacked_packets_.rbegin()->first < - serialized_packet.sequence_number); - // Retransmitted frames will be sent with the same encryption level as the - // original. + if (serialized_packet.retransmittable_frames) { serialized_packet.retransmittable_frames->set_encryption_level( encryption_level_); - unacked_packets_.insert( - make_pair(serialized_packet.sequence_number, - serialized_packet.retransmittable_frames)); - // All unacked packets might be retransmitted. - retransmission_map_.insert( - make_pair(serialized_packet.sequence_number, - RetransmissionInfo( - serialized_packet.sequence_number, - serialized_packet.sequence_number_length))); - } else if (serialized_packet.packet->is_fec_packet()) { - unacked_fec_packets_.insert(make_pair( - serialized_packet.sequence_number, - serialized_packet.retransmittable_frames)); } + sent_packet_manager_.OnSerializedPacket(serialized_packet); return SendOrQueuePacket(encryption_level_, serialized_packet.sequence_number, serialized_packet.packet, serialized_packet.entropy_hash, serialized_packet.retransmittable_frames != NULL ? HAS_RETRANSMITTABLE_DATA : - NO_RETRANSMITTABLE_DATA); + NO_RETRANSMITTABLE_DATA, + HasForcedFrames( + serialized_packet.retransmittable_frames)); +} + +QuicPacketSequenceNumber QuicConnection::GetPeerLargestObservedPacket() { + return received_packet_manager_.peer_largest_observed_packet(); +} + +QuicPacketSequenceNumber QuicConnection::GetNextPacketSequenceNumber() { + return packet_creator_.sequence_number() + 1; +} + +void QuicConnection::OnPacketNacked(QuicPacketSequenceNumber sequence_number, + size_t nack_count) { + if (nack_count >= kNumberOfNacksBeforeRetransmission && + retransmitted_nacked_packet_count_ < kMaxRetransmissionsPerAck) { + ++retransmitted_nacked_packet_count_; + RetransmitPacket(sequence_number); + } } bool QuicConnection::SendOrQueuePacket(EncryptionLevel level, QuicPacketSequenceNumber sequence_number, QuicPacket* packet, QuicPacketEntropyHash entropy_hash, - HasRetransmittableData retransmittable) { + HasRetransmittableData retransmittable, + Force forced) { sent_entropy_manager_.RecordPacketEntropyHash(sequence_number, entropy_hash); - if (!WritePacket(level, sequence_number, packet, retransmittable, NO_FORCE)) { + if (!WritePacket(level, sequence_number, packet, retransmittable, forced)) { queued_packets_.push_back(QueuedPacket(sequence_number, packet, level, - retransmittable)); + retransmittable, forced)); return false; } return true; @@ -1437,14 +1378,7 @@ bool QuicConnection::ShouldSimulateLostPacket() { } void QuicConnection::UpdateSentPacketInfo(SentPacketInfo* sent_info) { - if (!unacked_packets_.empty()) { - sent_info->least_unacked = unacked_packets_.begin()->first; - } else { - // If there are no unacked packets, set the least unacked packet to - // sequence_number() + 1 since that will be the sequence number of this - // ack packet whenever it is sent. - sent_info->least_unacked = packet_creator_.sequence_number() + 1; - } + sent_info->least_unacked = sent_packet_manager_.GetLeastUnackedSentPacket(); sent_info->entropy_hash = sent_entropy_manager_.EntropyHash( sent_info->least_unacked - 1); } @@ -1468,13 +1402,12 @@ void QuicConnection::SendAck() { void QuicConnection::MaybeAbandonFecPacket( QuicPacketSequenceNumber sequence_number) { - if (!ContainsKey(unacked_fec_packets_, sequence_number)) { + if (!sent_packet_manager_.IsFecUnacked(sequence_number)) { DVLOG(2) << ENDPOINT << "no need to abandon fec packet: " << sequence_number << "; it's already acked'"; return; } congestion_manager_.AbandoningPacket(sequence_number); - // TODO(satyashekhar): Should this decrease the congestion window? } QuicTime QuicConnection::OnRetransmissionTimeout() { @@ -1641,36 +1574,6 @@ void QuicConnection::SendConnectionClose(QuicErrorCode error) { SendConnectionCloseWithDetails(error, string()); } -void QuicConnection::SendConnectionClosePacket(QuicErrorCode error, - const string& details) { - DLOG(INFO) << ENDPOINT << "Force closing with error " - << QuicUtils::ErrorToString(error) << " (" << error << ") " - << details; - QuicConnectionCloseFrame frame; - frame.error_code = error; - frame.error_details = details; - UpdateSentPacketInfo(&frame.ack_frame.sent_info); - received_packet_manager_.UpdateReceivedPacketInfo( - &frame.ack_frame.received_info, clock_->ApproximateNow()); - - SerializedPacket serialized_packet = - packet_creator_.SerializeConnectionClose(&frame); - - // We need to update the sent entropy hash for all sent packets. - sent_entropy_manager_.RecordPacketEntropyHash( - serialized_packet.sequence_number, - serialized_packet.entropy_hash); - - if (!WritePacket(encryption_level_, - serialized_packet.sequence_number, - serialized_packet.packet, - serialized_packet.retransmittable_frames != NULL ? - HAS_RETRANSMITTABLE_DATA : NO_RETRANSMITTABLE_DATA, - FORCE)) { - delete serialized_packet.packet; - } -} - void QuicConnection::SendConnectionCloseWithDetails(QuicErrorCode error, const string& details) { if (!write_blocked_) { @@ -1679,6 +1582,21 @@ void QuicConnection::SendConnectionCloseWithDetails(QuicErrorCode error, CloseConnection(error, false); } +void QuicConnection::SendConnectionClosePacket(QuicErrorCode error, + const string& details) { + DLOG(INFO) << ENDPOINT << "Force closing with error " + << QuicUtils::ErrorToString(error) << " (" << error << ") " + << details; + QuicConnectionCloseFrame* frame = new QuicConnectionCloseFrame(); + frame->error_code = error; + frame->error_details = details; + UpdateSentPacketInfo(&frame->ack_frame.sent_info); + received_packet_manager_.UpdateReceivedPacketInfo( + &frame->ack_frame.received_info, clock_->ApproximateNow()); + packet_generator_.AddControlFrame(QuicFrame(frame)); + Flush(); +} + void QuicConnection::CloseConnection(QuicErrorCode error, bool from_peer) { DCHECK(connected_); connected_ = false; @@ -1716,6 +1634,14 @@ void QuicConnection::CloseFecGroupsBefore( } } +void QuicConnection::Flush() { + if (!packet_generator_.InBatchMode()) { + return; + } + packet_generator_.FinishBatchOperations(); + packet_generator_.StartBatchOperations(); +} + bool QuicConnection::HasQueuedData() const { return !queued_packets_.empty() || packet_generator_.HasQueuedFrames(); } diff --git a/net/quic/quic_connection.h b/net/quic/quic_connection.h index a23ecf049c..1524909772 100644 --- a/net/quic/quic_connection.h +++ b/net/quic/quic_connection.h @@ -24,6 +24,7 @@ #include <vector> #include "base/containers/hash_tables.h" +#include "net/base/iovec.h" #include "net/base/ip_endpoint.h" #include "net/base/linked_hash_map.h" #include "net/quic/congestion_control/quic_congestion_manager.h" @@ -37,6 +38,7 @@ #include "net/quic/quic_protocol.h" #include "net/quic/quic_received_packet_manager.h" #include "net/quic/quic_sent_entropy_manager.h" +#include "net/quic/quic_sent_packet_manager.h" namespace net { @@ -49,6 +51,8 @@ namespace test { class QuicConnectionPeer; } // namespace test +// Class that receives callbacks from the connection when frames are received +// and when other interesting events happen. class NET_EXPORT_PRIVATE QuicConnectionVisitorInterface { public: virtual ~QuicConnectionVisitorInterface() {} @@ -57,10 +61,7 @@ class NET_EXPORT_PRIVATE QuicConnectionVisitorInterface { // should determine if all frames will be accepted, and return true if so. // If any frames can't be processed or buffered, none of the data should // be used, and the callee should return false. - virtual bool OnPacket(const IPEndPoint& self_address, - const IPEndPoint& peer_address, - const QuicPacketHeader& header, - const std::vector<QuicStreamFrame>& frame) = 0; + virtual bool OnStreamFrames(const std::vector<QuicStreamFrame>& frames) = 0; // Called when the stream is reset by the peer. virtual void OnRstStream(const QuicRstStreamFrame& frame) = 0; @@ -73,13 +74,16 @@ class NET_EXPORT_PRIVATE QuicConnectionVisitorInterface { virtual void ConnectionClose(QuicErrorCode error, bool from_peer) = 0; - // Called when packets are acked by the peer. - virtual void OnAck(const SequenceNumberSet& acked_packets) = 0; + // Called once a specific QUIC version is agreed by both endpoints. + virtual void OnSuccessfulVersionNegotiation(const QuicVersion& version) = 0; // Called when a blocked socket becomes writable. If all pending bytes for // this visitor are consumed by the connection successfully this should // return true, otherwise it should return false. virtual bool OnCanWrite() = 0; + + // Called to ask if any handshake messages are pending in this visitor. + virtual bool HasPendingHandshake() const = 0; }; // Interface which gets callbacks from the QuicConnection at interesting @@ -184,7 +188,8 @@ class NET_EXPORT_PRIVATE QuicConnectionHelperInterface { class NET_EXPORT_PRIVATE QuicConnection : public QuicFramerVisitorInterface, public QuicBlockedWriterInterface, - public QuicPacketGenerator::DelegateInterface { + public QuicPacketGenerator::DelegateInterface, + public QuicSentPacketManager::HelperInterface { public: enum Force { NO_FORCE, @@ -205,21 +210,24 @@ class NET_EXPORT_PRIVATE QuicConnection QuicVersion version); virtual ~QuicConnection(); - // Send the data payload to the peer. + // Send the data in |iov| to the peer in as few packets as possible. // Returns a pair with the number of bytes consumed from data, and a boolean // indicating if the fin bit was consumed. This does not indicate the data // has been sent on the wire: it may have been turned into a packet and queued // if the socket was unexpectedly blocked. - QuicConsumedData SendStreamData(QuicStreamId id, - base::StringPiece data, - QuicStreamOffset offset, - bool fin); - // Same as above, except that the provided delegate will be informed once ACKs - // have been received for all the packets written. + QuicConsumedData SendvStreamData(QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin); + + // Same as SendvStreamData, except the provided delegate will be informed + // once ACKs have been received for all the packets written. // The |delegate| is not owned by the QuicConnection and must outlive it. - QuicConsumedData SendStreamDataAndNotifyWhenAcked( + QuicConsumedData SendvStreamDataAndNotifyWhenAcked( QuicStreamId id, - base::StringPiece data, + const struct iovec* iov, + int iov_count, QuicStreamOffset offset, bool fin, QuicAckNotifier::DelegateInterface* delegate); @@ -240,7 +248,7 @@ class NET_EXPORT_PRIVATE QuicConnection virtual void SendConnectionCloseWithDetails(QuicErrorCode error, const std::string& details); // Notifies the visitor of the close and marks the connection as disconnected. - void CloseConnection(QuicErrorCode error, bool from_peer); + virtual void CloseConnection(QuicErrorCode error, bool from_peer) OVERRIDE; virtual void SendGoAway(QuicErrorCode error, QuicStreamId last_good_stream_id, const std::string& reason); @@ -303,6 +311,12 @@ class NET_EXPORT_PRIVATE QuicConnection virtual QuicCongestionFeedbackFrame* CreateFeedbackFrame() OVERRIDE; virtual bool OnSerializedPacket(const SerializedPacket& packet) OVERRIDE; + // QuicSentPacketManager::HelperInterface + virtual QuicPacketSequenceNumber GetPeerLargestObservedPacket() OVERRIDE; + virtual QuicPacketSequenceNumber GetNextPacketSequenceNumber() OVERRIDE; + virtual void OnPacketNacked(QuicPacketSequenceNumber sequence_number, + size_t nack_count) OVERRIDE; + // Accessors void set_visitor(QuicConnectionVisitorInterface* visitor) { visitor_ = visitor; @@ -335,6 +349,10 @@ class NET_EXPORT_PRIVATE QuicConnection // Testing only. size_t NumQueuedPackets() const { return queued_packets_.size(); } + // Flush any queued frames immediately. Preserves the batch write mode and + // does nothing if there are no pending frames. + void Flush(); + // Returns true if the connection has queued packets or frames. bool HasQueuedData() const; @@ -405,14 +423,17 @@ class NET_EXPORT_PRIVATE QuicConnection // is present in the |retransmission_map_|, then contents of this packet will // be retransmitted with a new sequence number if it's not acked by the peer. // Deletes |packet| via WritePacket call or transfers ownership to - // QueuedPacket, ultimately deleted via WritePacket. Also, it updates the + // QueuedPacket, ultimately deleted via WritePacket. Updates the // entropy map corresponding to |sequence_number| using |entropy_hash|. + // |retransmittable| is supplied to the congestion manager, and when |forced| + // is true, it bypasses the congestion manager. // TODO(wtc): none of the callers check the return value. virtual bool SendOrQueuePacket(EncryptionLevel level, QuicPacketSequenceNumber sequence_number, QuicPacket* packet, QuicPacketEntropyHash entropy_hash, - HasRetransmittableData retransmittable); + HasRetransmittableData retransmittable, + Force forced); // Writes the given packet to socket, encrypted with |level|, with the help // of helper. Returns true on successful write, false otherwise. However, @@ -448,23 +469,35 @@ class NET_EXPORT_PRIVATE QuicConnection private: friend class test::QuicConnectionPeer; + // Inner helper function to SendvStreamData and + // SendvStreamDataAndNotifyWhenAcked. + QuicConsumedData SendvStreamDataInner(QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin, + QuicAckNotifier *notifier); + // Packets which have not been written to the wire. // Owns the QuicPacket* packet. struct QueuedPacket { QueuedPacket(QuicPacketSequenceNumber sequence_number, QuicPacket* packet, EncryptionLevel level, - HasRetransmittableData retransmittable) + HasRetransmittableData retransmittable, + Force forced) : sequence_number(sequence_number), packet(packet), encryption_level(level), - retransmittable(retransmittable) { + retransmittable(retransmittable), + forced(forced) { } QuicPacketSequenceNumber sequence_number; QuicPacket* packet; const EncryptionLevel encryption_level; HasRetransmittableData retransmittable; + Force forced; }; struct RetransmissionInfo { @@ -506,11 +539,7 @@ class NET_EXPORT_PRIVATE QuicConnection }; typedef std::list<QueuedPacket> QueuedPacketList; - typedef linked_hash_map<QuicPacketSequenceNumber, - RetransmittableFrames*> UnackedPacketMap; typedef std::map<QuicFecGroupNumber, QuicFecGroup*> FecGroupMap; - typedef base::hash_map<QuicPacketSequenceNumber, - RetransmissionInfo> RetransmissionMap; typedef std::priority_queue<RetransmissionTime, std::vector<RetransmissionTime>, RetransmissionTimeComparator> @@ -562,11 +591,6 @@ class NET_EXPORT_PRIVATE QuicConnection void ProcessAckFrame(const QuicAckFrame& incoming_ack); - void HandleAckForSentPackets(const QuicAckFrame& incoming_ack, - SequenceNumberSet* acked_packets); - void HandleAckForSentFecPackets(const QuicAckFrame& incoming_ack, - SequenceNumberSet* acked_packets); - // Update the |sent_info| for an outgoing ack. void UpdateSentPacketInfo(SentPacketInfo* sent_info); @@ -605,6 +629,9 @@ class NET_EXPORT_PRIVATE QuicConnection std::vector<QuicCongestionFeedbackFrame> last_congestion_frames_; std::vector<QuicRstStreamFrame> last_rst_frames_; std::vector<QuicGoAwayFrame> last_goaway_frames_; + // Then number of packets retransmitted because of nacks + // while processed the current ack frame. + size_t retransmitted_nacked_packet_count_; QuicCongestionFeedbackFrame outgoing_congestion_feedback_; @@ -612,16 +639,6 @@ class NET_EXPORT_PRIVATE QuicConnection // Largest sequence sent by the peer which had an ack frame (latest ack info). QuicPacketSequenceNumber largest_seen_packet_with_ack_; - // When new packets are created which may be retransmitted, they are added - // to this map, which contains owning pointers to the contained frames. - UnackedPacketMap unacked_packets_; - - // Pending fec packets that have not been acked yet. These packets need to be - // cleared out of the cgst_window after a timeout since FEC packets are never - // retransmitted. - // Ask: What should be the timeout for these packets? - UnackedPacketMap unacked_fec_packets_; - // Collection of packets which were received before encryption was // established, but which could not be decrypted. We buffer these on // the assumption that they could not be processed because they were @@ -636,9 +653,6 @@ class NET_EXPORT_PRIVATE QuicConnection // contains all packets that have been retransmitted x times. RetransmissionTimeouts retransmission_timeouts_; - // Map from sequence number to the retransmission info. - RetransmissionMap retransmission_map_; - // True while OnRetransmissionTimeout is running to prevent // SetRetransmissionAlarm from being called erroneously. bool handling_retransmission_timeout_; @@ -692,6 +706,10 @@ class NET_EXPORT_PRIVATE QuicConnection // as well as collecting and generating congestion feedback. QuicCongestionManager congestion_manager_; + // Sent packet manager which tracks the status of packets sent by this + // connection. + QuicSentPacketManager sent_packet_manager_; + // The state of connection in version negotiation finite state machine. QuicVersionNegotiationState version_negotiation_state_; diff --git a/net/quic/quic_connection_helper_test.cc b/net/quic/quic_connection_helper_test.cc index 9f1bcac501..aaa692b548 100644 --- a/net/quic/quic_connection_helper_test.cc +++ b/net/quic/quic_connection_helper_test.cc @@ -9,6 +9,7 @@ #include "net/base/net_errors.h" #include "net/quic/crypto/quic_decrypter.h" #include "net/quic/crypto/quic_encrypter.h" +#include "net/quic/quic_connection.h" #include "net/quic/test_tools/mock_clock.h" #include "net/quic/test_tools/quic_connection_peer.h" #include "net/quic/test_tools/quic_test_utils.h" @@ -18,6 +19,8 @@ #include "testing/gtest/include/gtest/gtest.h" using testing::_; +using testing::AnyNumber; +using testing::Return; namespace net { namespace test { @@ -120,11 +123,14 @@ class QuicConnectionHelperTest : public ::testing::Test { &random_generator_, socket_.get()); send_algorithm_ = new testing::StrictMock<MockSendAlgorithm>(); EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)). - WillRepeatedly(testing::Return(QuicTime::Delta::Zero())); + WillRepeatedly(Return(QuicTime::Delta::Zero())); EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillRepeatedly( - testing::Return(QuicBandwidth::FromKBitsPerSecond(100))); + Return(QuicBandwidth::FromKBitsPerSecond(100))); EXPECT_CALL(*send_algorithm_, SmoothedRtt()).WillRepeatedly( - testing::Return(QuicTime::Delta::FromMilliseconds(100))); + Return(QuicTime::Delta::FromMilliseconds(100))); + ON_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)) + .WillByDefault(Return(true)); + EXPECT_CALL(visitor_, HasPendingHandshake()).Times(AnyNumber()); connection_.reset(new TestConnection(guid_, IPEndPoint(), helper_)); connection_->set_visitor(&visitor_); connection_->SetSendAlgorithm(send_algorithm_); @@ -309,18 +315,20 @@ TEST_F(QuicConnectionHelperTest, TestRetransmission) { Initialize(); EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( - testing::Return(QuicTime::Delta::Zero())); + Return(QuicTime::Delta::Zero())); QuicTime::Delta kDefaultRetransmissionTime = QuicTime::Delta::FromMilliseconds(500); QuicTime start = clock_.ApproximateNow(); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION, _)); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)); // Send a packet. - connection_->SendStreamData(1, kData, 0, false); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, IS_RETRANSMISSION)); + struct iovec iov = {const_cast<char*>(kData), + static_cast<size_t>(strlen(kData))}; + connection_->SendvStreamData(1, &iov, 1, 0, false); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, IS_RETRANSMISSION, _)); // Since no ack was received, the retransmission alarm will fire and // retransmit it. runner_->RunNextTask(); @@ -337,17 +345,20 @@ TEST_F(QuicConnectionHelperTest, TestMultipleRetransmission) { Initialize(); EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( - testing::Return(QuicTime::Delta::Zero())); + Return(QuicTime::Delta::Zero())); QuicTime::Delta kDefaultRetransmissionTime = QuicTime::Delta::FromMilliseconds(500); QuicTime start = clock_.ApproximateNow(); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION, _)); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)); + // Send a packet. - connection_->SendStreamData(1, kData, 0, false); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, IS_RETRANSMISSION)); + struct iovec iov = {const_cast<char*>(kData), + static_cast<size_t>(strlen(kData))}; + connection_->SendvStreamData(1, &iov, 1, 0, false); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, IS_RETRANSMISSION, _)); // Since no ack was received, the retransmission alarm will fire and // retransmit it. runner_->RunNextTask(); @@ -357,7 +368,7 @@ TEST_F(QuicConnectionHelperTest, TestMultipleRetransmission) { // Since no ack was received, the retransmission alarm will fire and // retransmit it. - EXPECT_CALL(*send_algorithm_, SentPacket(_, 3, _, IS_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 3, _, IS_RETRANSMISSION, _)); EXPECT_CALL(*send_algorithm_, AbandoningPacket(2, _)); runner_->RunNextTask(); @@ -376,7 +387,10 @@ TEST_F(QuicConnectionHelperTest, InitialTimeout) { EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultInitialTimeoutSecs), runner_->GetPostedTasks().front().delay); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA)); + EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillOnce( + Return(QuicTime::Delta::FromMicroseconds(1))); // After we run the next task, we should close the connection. EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false)); @@ -403,7 +417,7 @@ TEST_F(QuicConnectionHelperTest, WritePacketToWireAsync) { AddWrite(ASYNC, ConstructClosePacket(1, 0)); Initialize(); - EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true)); + EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(Return(true)); int error = 0; EXPECT_EQ(-1, helper_->WritePacketToWire(*GetWrite(0), &error)); EXPECT_EQ(ERR_IO_PENDING, error); @@ -423,7 +437,8 @@ TEST_F(QuicConnectionHelperTest, TimeoutAfterSend) { // kDefaultInitialTimeoutSecs. clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(5000)); EXPECT_EQ(5000u, clock_.ApproximateNow().Subtract(start).ToMicroseconds()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, + SentPacket(_, 1, _, NOT_RETRANSMISSION, NO_RETRANSMITTABLE_DATA)); // Send an ack so we don't set the retransmission alarm. connection_->SendAck(); @@ -439,7 +454,10 @@ TEST_F(QuicConnectionHelperTest, TimeoutAfterSend) { // This time, we should time out. EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA)); + EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillOnce( + Return(QuicTime::Delta::FromMicroseconds(1))); runner_->RunNextTask(); EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000 + 5000, clock_.ApproximateNow().Subtract( @@ -454,23 +472,26 @@ TEST_F(QuicConnectionHelperTest, SendSchedulerDelayThenSend) { // Test that if we send a packet with a delay, it ends up queued. EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( - testing::Return(QuicTime::Delta::Zero())); + Return(QuicTime::Delta::Zero())); EXPECT_CALL( *send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( - testing::Return(QuicTime::Delta::FromMicroseconds(1))); + Return(QuicTime::Delta::FromMicroseconds(1))); QuicPacket* packet = ConstructRawDataPacket(1); - connection_->SendOrQueuePacket( - ENCRYPTION_NONE, 1, packet, 0, HAS_RETRANSMITTABLE_DATA); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + connection_->SendOrQueuePacket(ENCRYPTION_NONE, 1, packet, 0, + HAS_RETRANSMITTABLE_DATA, + QuicConnection::NO_FORCE); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION, + _)); EXPECT_EQ(1u, connection_->NumQueuedPackets()); // Advance the clock to fire the alarm, and configure the scheduler // to permit the packet to be sent. EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly( - testing::Return(QuicTime::Delta::Zero())); - EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true)); + Return(QuicTime::Delta::Zero())); + EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(Return(true)); + EXPECT_CALL(visitor_, HasPendingHandshake()).Times(AnyNumber()); runner_->RunNextTask(); EXPECT_EQ(0u, connection_->NumQueuedPackets()); EXPECT_TRUE(AtEof()); diff --git a/net/quic/quic_connection_logger.cc b/net/quic/quic_connection_logger.cc index 2221b50123..5195f4391a 100644 --- a/net/quic/quic_connection_logger.cc +++ b/net/quic/quic_connection_logger.cc @@ -12,6 +12,8 @@ #include "net/base/net_log.h" #include "net/quic/crypto/crypto_handshake.h" +using std::string; + namespace net { namespace { @@ -416,4 +418,11 @@ void QuicConnectionLogger::OnConnectionClose(QuicErrorCode error, base::Bind(&NetLogQuicConnectionClosedCallback, error, from_peer)); } +void QuicConnectionLogger::OnSuccessfulVersionNegotiation( + const QuicVersion& version) { + string quic_version = QuicVersionToString(version); + net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_VERSION_NEGOTIATED, + NetLog::StringCallback("version", &quic_version)); +} + } // namespace net diff --git a/net/quic/quic_connection_logger.h b/net/quic/quic_connection_logger.h index f9080d643e..d498b128bd 100644 --- a/net/quic/quic_connection_logger.h +++ b/net/quic/quic_connection_logger.h @@ -56,6 +56,7 @@ class NET_EXPORT_PRIVATE QuicConnectionLogger void OnCryptoHandshakeMessageSent( const CryptoHandshakeMessage& message); void OnConnectionClose(QuicErrorCode error, bool from_peer); + void OnSuccessfulVersionNegotiation(const QuicVersion& version); private: BoundNetLog net_log_; diff --git a/net/quic/quic_connection_test.cc b/net/quic/quic_connection_test.cc index e57e7aea19..afe8a7663b 100644 --- a/net/quic/quic_connection_test.cc +++ b/net/quic/quic_connection_test.cc @@ -389,6 +389,40 @@ class TestConnection : public QuicConnection { QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm); } + bool SendOrQueuePacket(EncryptionLevel level, + QuicPacketSequenceNumber sequence_number, + QuicPacket* packet, + QuicPacketEntropyHash entropy_hash, + HasRetransmittableData retransmittable) { + return SendOrQueuePacket(level, + sequence_number, + packet, + entropy_hash, + retransmittable, + NO_FORCE); + } + + QuicConsumedData SendStreamData(QuicStreamId id, + StringPiece data, + QuicStreamOffset offset, + bool fin) { + struct iovec iov = {const_cast<char*>(data.data()), + static_cast<size_t>(data.size())}; + return SendvStreamData(id, &iov, 1, offset, fin); + } + + QuicConsumedData SendStreamDataAndNotifyWhenAcked( + QuicStreamId id, + StringPiece data, + QuicStreamOffset offset, + bool fin, + QuicAckNotifier::DelegateInterface* delegate) { + struct iovec iov = {const_cast<char*>(data.data()), + static_cast<size_t>(data.size())}; + return SendvStreamDataAndNotifyWhenAcked(id, &iov, 1, offset, fin, + delegate); + } + QuicConsumedData SendStreamData3() { return SendStreamData(kStreamId3, "food", 0, !kFin); } @@ -403,7 +437,11 @@ class TestConnection : public QuicConnection { // split needlessly across packet boundaries). As a result, we have separate // tests for some cases for this stream. QuicConsumedData SendCryptoStreamData() { - return SendStreamData(kCryptoStreamId, "chlo", 0, !kFin); + this->Flush(); + QuicConsumedData consumed = + SendStreamData(kCryptoStreamId, "chlo", 0, !kFin); + this->Flush(); + return consumed; } bool is_server() { @@ -467,15 +505,18 @@ class QuicConnectionTest : public ::testing::Test { QuicTime::Delta::Zero())); EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _, _)).Times(AnyNumber()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(AnyNumber()); EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( Return(QuicTime::Delta::Zero())); EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillRepeatedly(Return( QuicBandwidth::FromKBitsPerSecond(100))); EXPECT_CALL(*send_algorithm_, SmoothedRtt()).WillRepeatedly(Return( QuicTime::Delta::FromMilliseconds(100))); + ON_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)) + .WillByDefault(Return(true)); // TODO(rch): remove this. QuicConnection::g_acks_do_not_instigate_acks = true; + EXPECT_CALL(visitor_, HasPendingHandshake()).Times(AnyNumber()); } ~QuicConnectionTest() { @@ -513,8 +554,7 @@ class QuicConnectionTest : public ::testing::Test { } void ProcessPacket(QuicPacketSequenceNumber number) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)) - .WillOnce(Return(accept_packet_)); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillOnce(Return(accept_packet_)); ProcessDataPacket(number, 0, !kEntropyFlag); } @@ -562,10 +602,9 @@ class QuicConnectionTest : public ::testing::Test { size_t ProcessFecProtectedPacket(QuicPacketSequenceNumber number, bool expect_revival, bool entropy_flag) { if (expect_revival) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(DoAll( - SaveArg<2>(&revived_header_), Return(accept_packet_))); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillOnce(Return(accept_packet_)); } - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(Return(accept_packet_)) + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillOnce(Return(accept_packet_)) .RetiresOnSaturation(); return ProcessDataPacket(number, 1, entropy_flag); } @@ -578,8 +617,7 @@ class QuicConnectionTest : public ::testing::Test { bool entropy_flag, QuicPacket* packet) { if (expect_revival) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(DoAll( - SaveArg<2>(&revived_header_), Return(accept_packet_))); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillOnce(Return(accept_packet_)); } // Construct the decrypted data packet so we can compute the correct @@ -630,21 +668,21 @@ class QuicConnectionTest : public ::testing::Test { QuicStreamOffset offset, bool fin, QuicPacketSequenceNumber* last_packet) { QuicByteCount packet_size; - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).WillOnce( - SaveArg<2>(&packet_size)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)) + .WillOnce(DoAll(SaveArg<2>(&packet_size), Return(true))); connection_.SendStreamData(id, data, offset, fin); if (last_packet != NULL) { *last_packet = QuicConnectionPeer::GetPacketCreator(&connection_)->sequence_number(); } - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(AnyNumber()); return packet_size; } void SendAckPacketToPeer() { - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); connection_.SendAck(); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(AnyNumber()); } QuicPacketEntropyHash ProcessAckPacket(QuicAckFrame* frame, @@ -730,7 +768,6 @@ class QuicConnectionTest : public ::testing::Test { StrictMock<MockConnectionVisitor> visitor_; QuicPacketHeader header_; - QuicPacketHeader revived_header_; QuicStreamFrame frame1_; QuicStreamFrame frame2_; scoped_ptr<QuicAckFrame> outgoing_ack_; @@ -741,6 +778,8 @@ class QuicConnectionTest : public ::testing::Test { }; TEST_F(QuicConnectionTest, PacketsInOrder) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessPacket(1); EXPECT_EQ(1u, outgoing_ack()->received_info.largest_observed); EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size()); @@ -755,6 +794,8 @@ TEST_F(QuicConnectionTest, PacketsInOrder) { } TEST_F(QuicConnectionTest, PacketsRejected) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessPacket(1); EXPECT_EQ(1u, outgoing_ack()->received_info.largest_observed); EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size()); @@ -767,6 +808,8 @@ TEST_F(QuicConnectionTest, PacketsRejected) { } TEST_F(QuicConnectionTest, PacketsOutOfOrder) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessPacket(3); EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed); EXPECT_TRUE(IsMissing(2)); @@ -784,13 +827,15 @@ TEST_F(QuicConnectionTest, PacketsOutOfOrder) { } TEST_F(QuicConnectionTest, DuplicatePacket) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessPacket(3); EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed); EXPECT_TRUE(IsMissing(2)); EXPECT_TRUE(IsMissing(1)); // Send packet 3 again, but do not set the expectation that - // the visitor OnPacket() will be called. + // the visitor OnStreamFrames() will be called. ProcessDataPacket(3, 0, !kEntropyFlag); EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed); EXPECT_TRUE(IsMissing(2)); @@ -798,6 +843,8 @@ TEST_F(QuicConnectionTest, DuplicatePacket) { } TEST_F(QuicConnectionTest, PacketsOutOfOrderWithAdditionsAndLeastAwaiting) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessPacket(3); EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed); EXPECT_TRUE(IsMissing(2)); @@ -833,7 +880,7 @@ TEST_F(QuicConnectionTest, RejectPacketTooFarOut) { } TEST_F(QuicConnectionTest, TruncatedAck) { - EXPECT_CALL(visitor_, OnAck(_)).Times(testing::AnyNumber()); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(2); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); for (int i = 0; i < 200; ++i) { @@ -861,6 +908,8 @@ TEST_F(QuicConnectionTest, TruncatedAck) { } TEST_F(QuicConnectionTest, AckReceiptCausesAckSendBadEntropy) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessPacket(1); // Delay sending, then queue up an ack. EXPECT_CALL(*send_algorithm_, @@ -880,11 +929,13 @@ TEST_F(QuicConnectionTest, AckReceiptCausesAckSendBadEntropy) { } TEST_F(QuicConnectionTest, AckReceiptCausesAckSend) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); QuicPacketSequenceNumber largest_observed; QuicByteCount packet_size; - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION)) - .WillOnce(DoAll(SaveArg<1>(&largest_observed), SaveArg<2>(&packet_size))); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)) + .WillOnce(DoAll(SaveArg<1>(&largest_observed), SaveArg<2>(&packet_size), + Return(true))); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1); connection_.SendStreamData(1, "foo", 0, !kFin); QuicAckFrame frame(1, QuicTime::Zero(), largest_observed); @@ -895,13 +946,13 @@ TEST_F(QuicConnectionTest, AckReceiptCausesAckSend) { ProcessAckPacket(&frame, true); // Third nack should retransmit the largest observed packet. EXPECT_CALL(*send_algorithm_, SentPacket(_, _, packet_size - kQuicVersionSize, - IS_RETRANSMISSION)); + IS_RETRANSMISSION, _)); ProcessAckPacket(&frame, true); // Now if the peer sends an ack which still reports the retransmitted packet // as missing, then that will count as a packet which instigates an ack. ProcessAckPacket(&frame, true); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)); ProcessAckPacket(&frame, true); // But an ack with no new missing packest will not send an ack. @@ -911,6 +962,8 @@ TEST_F(QuicConnectionTest, AckReceiptCausesAckSend) { } TEST_F(QuicConnectionTest, LeastUnackedLower) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + SendStreamDataToPeer(1, "foo", 0, !kFin, NULL); SendStreamDataToPeer(1, "bar", 3, !kFin, NULL); SendStreamDataToPeer(1, "eep", 6, !kFin, NULL); @@ -930,12 +983,14 @@ TEST_F(QuicConnectionTest, LeastUnackedLower) { // Now claim it's one, but set the ordering so it was sent "after" the first // one. This should cause a connection error. EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); creator_.set_sequence_number(7); ProcessAckPacket(&frame2, false); } TEST_F(QuicConnectionTest, LargestObservedLower) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + SendStreamDataToPeer(1, "foo", 0, !kFin, NULL); SendStreamDataToPeer(1, "bar", 3, !kFin, NULL); SendStreamDataToPeer(1, "eep", 6, !kFin, NULL); @@ -945,7 +1000,6 @@ TEST_F(QuicConnectionTest, LargestObservedLower) { QuicAckFrame frame(2, QuicTime::Zero(), 0); frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash( &connection_, 2); - EXPECT_CALL(visitor_, OnAck(_)); ProcessAckPacket(&frame, true); // Now change it to 1, and it should cause a connection error. @@ -955,8 +1009,9 @@ TEST_F(QuicConnectionTest, LargestObservedLower) { } TEST_F(QuicConnectionTest, LeastUnackedGreaterThanPacketSequenceNumber) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); // Create an ack with least_unacked is 2 in packet number 1. creator_.set_sequence_number(0); QuicAckFrame frame(0, QuicTime::Zero(), 2); @@ -965,12 +1020,14 @@ TEST_F(QuicConnectionTest, LeastUnackedGreaterThanPacketSequenceNumber) { TEST_F(QuicConnectionTest, NackSequenceNumberGreaterThanLargestReceived) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + SendStreamDataToPeer(1, "foo", 0, !kFin, NULL); SendStreamDataToPeer(1, "bar", 3, !kFin, NULL); SendStreamDataToPeer(1, "eep", 6, !kFin, NULL); EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); QuicAckFrame frame(0, QuicTime::Zero(), 1); frame.received_info.missing_packets.insert(3); ProcessAckPacket(&frame, false); @@ -979,12 +1036,14 @@ TEST_F(QuicConnectionTest, TEST_F(QuicConnectionTest, AckUnsentData) { // Ack a packet which has not been sent. EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); QuicAckFrame frame(1, QuicTime::Zero(), 0); ProcessAckPacket(&frame, false); } TEST_F(QuicConnectionTest, AckAll) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessPacket(1); creator_.set_sequence_number(1); @@ -1093,6 +1152,7 @@ TEST_F(QuicConnectionTest, SendingDifferentSequenceNumberLengthsUnackedDelta) { } TEST_F(QuicConnectionTest, BasicSending) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(6); QuicPacketSequenceNumber last_packet; SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet); // Packet 1 @@ -1109,11 +1169,7 @@ TEST_F(QuicConnectionTest, BasicSending) { SendAckPacketToPeer(); // Packet 5 EXPECT_EQ(1u, last_ack()->sent_info.least_unacked); - SequenceNumberSet expected_acks; - expected_acks.insert(1); - // Peer acks up to packet 3. - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); QuicAckFrame frame(3, QuicTime::Zero(), 0); frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(&connection_, 3); @@ -1124,11 +1180,7 @@ TEST_F(QuicConnectionTest, BasicSending) { // ack for 4. EXPECT_EQ(4u, last_ack()->sent_info.least_unacked); - expected_acks.clear(); - expected_acks.insert(4); - // Peer acks up to packet 4, the last packet. - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); QuicAckFrame frame2(6, QuicTime::Zero(), 0); frame2.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(&connection_, 6); @@ -1162,7 +1214,7 @@ TEST_F(QuicConnectionTest, FECSending) { connection_.options()->max_packets_per_fec_group = 2; // Send 4 data packets and 2 FEC packets. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(6); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(6); // The first stream frame will consume 2 fewer bytes than the other three. const string payload(payload_length * 4 - 6, 'a'); connection_.SendStreamData(1, payload, 0, !kFin); @@ -1192,7 +1244,7 @@ TEST_F(QuicConnectionTest, FECQueueing) { TEST_F(QuicConnectionTest, AbandonFECFromCongestionWindow) { connection_.options()->max_packets_per_fec_group = 1; // 1 Data and 1 FEC packet. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(2); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(2); connection_.SendStreamData(1, "foo", 0, !kFin); // Larger timeout for FEC bytes to expire. @@ -1201,7 +1253,7 @@ TEST_F(QuicConnectionTest, AbandonFECFromCongestionWindow) { clock_.AdvanceTime(retransmission_time); // Send only data packet. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); // Abandon both FEC and data packet. EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2); @@ -1209,12 +1261,13 @@ TEST_F(QuicConnectionTest, AbandonFECFromCongestionWindow) { } TEST_F(QuicConnectionTest, DontAbandonAckedFEC) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); connection_.options()->max_packets_per_fec_group = 1; const QuicPacketSequenceNumber sequence_number = QuicConnectionPeer::GetPacketCreator(&connection_)->sequence_number() + 1; // 1 Data and 1 FEC packet. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(2); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(2); connection_.SendStreamData(1, "foo", 0, !kFin); QuicAckFrame ack_fec(2, QuicTime::Zero(), 1); @@ -1224,7 +1277,6 @@ TEST_F(QuicConnectionTest, DontAbandonAckedFEC) { QuicConnectionPeer::GetSentEntropyHash(&connection_, 2) ^ QuicConnectionPeer::GetSentEntropyHash(&connection_, 1); - EXPECT_CALL(visitor_, OnAck(_)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); @@ -1235,7 +1287,7 @@ TEST_F(QuicConnectionTest, DontAbandonAckedFEC) { // Abandon only data packet, FEC has been acked. EXPECT_CALL(*send_algorithm_, AbandoningPacket(sequence_number, _)).Times(1); // Send only data packet. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); connection_.OnRetransmissionTimeout(); } @@ -1256,7 +1308,7 @@ TEST_F(QuicConnectionTest, FramePacking) { // Unblock the connection. connection_.GetSendAlarm()->Cancel(); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)) + SentPacket(_, _, _, NOT_RETRANSMISSION, _)) .Times(1); connection_.OnCanWrite(); EXPECT_EQ(0u, connection_.NumQueuedPackets()); @@ -1289,7 +1341,7 @@ TEST_F(QuicConnectionTest, FramePackingNonCryptoThenCrypto) { // Unblock the connection. connection_.GetSendAlarm()->Cancel(); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)) + SentPacket(_, _, _, NOT_RETRANSMISSION, _)) .Times(2); connection_.OnCanWrite(); EXPECT_EQ(0u, connection_.NumQueuedPackets()); @@ -1320,7 +1372,7 @@ TEST_F(QuicConnectionTest, FramePackingCryptoThenNonCrypto) { // Unblock the connection. connection_.GetSendAlarm()->Cancel(); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)) + SentPacket(_, _, _, NOT_RETRANSMISSION, _)) .Times(3); connection_.OnCanWrite(); EXPECT_EQ(0u, connection_.NumQueuedPackets()); @@ -1352,7 +1404,7 @@ TEST_F(QuicConnectionTest, FramePackingFEC) { // Unblock the connection. connection_.GetSendAlarm()->Cancel(); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)).Times(2); + SentPacket(_, _, _, NOT_RETRANSMISSION, _)).Times(2); connection_.OnCanWrite(); EXPECT_EQ(0u, connection_.NumQueuedPackets()); EXPECT_FALSE(connection_.HasQueuedData()); @@ -1362,6 +1414,76 @@ TEST_F(QuicConnectionTest, FramePackingFEC) { EXPECT_EQ(0u, helper_->frame_count()); } +TEST_F(QuicConnectionTest, FramePackingSendv) { + // Send two stream frames in 1 packet by using writev. + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)); + + char data[] = "ABCD"; + iovec iov[2] = { {static_cast<void*>(data), 2}, + {static_cast<void*>(data + 2), 2} }; + connection_.SendvStreamData(1, iov, 2, 0, !kFin); + + EXPECT_EQ(0u, connection_.NumQueuedPackets()); + EXPECT_FALSE(connection_.HasQueuedData()); + + // Parse the last packet and ensure it's two stream frames from one stream. + // TODO(ianswett): Ideally this would arrive in one frame in the future. + EXPECT_EQ(2u, helper_->frame_count()); + EXPECT_EQ(2u, helper_->stream_frames()->size()); + EXPECT_EQ(1u, (*helper_->stream_frames())[0].stream_id); + EXPECT_EQ(1u, (*helper_->stream_frames())[1].stream_id); +} + +TEST_F(QuicConnectionTest, FramePackingSendvQueued) { + // Try to send two stream frames in 1 packet by using writev. + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)); + + helper_->set_blocked(true); + char data[] = "ABCD"; + iovec iov[2] = { {static_cast<void*>(data), 2}, + {static_cast<void*>(data + 2), 2} }; + connection_.SendvStreamData(1, iov, 2, 0, !kFin); + + EXPECT_EQ(1u, connection_.NumQueuedPackets()); + EXPECT_TRUE(connection_.HasQueuedData()); + + // Attempt to send all packets, but since we're actually still + // blocked, they should all remain queued. + EXPECT_FALSE(connection_.OnCanWrite()); + EXPECT_EQ(1u, connection_.NumQueuedPackets()); + + // Unblock the writes and actually send. + helper_->set_blocked(false); + EXPECT_CALL(visitor_, OnCanWrite()); + EXPECT_TRUE(connection_.OnCanWrite()); + EXPECT_EQ(0u, connection_.NumQueuedPackets()); + + // Parse the last packet and ensure it's two stream frames from one stream. + // TODO(ianswett): Ideally this would arrive in one frame in the future. + EXPECT_EQ(2u, helper_->frame_count()); + EXPECT_EQ(2u, helper_->stream_frames()->size()); + EXPECT_EQ(1u, (*helper_->stream_frames())[0].stream_id); + EXPECT_EQ(1u, (*helper_->stream_frames())[1].stream_id); +} + +TEST_F(QuicConnectionTest, SendingZeroBytes) { + // Send a zero byte write with a fin using writev. + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)); + + iovec iov[1]; + connection_.SendvStreamData(1, iov, 0, 0, kFin); + + EXPECT_EQ(0u, connection_.NumQueuedPackets()); + EXPECT_FALSE(connection_.HasQueuedData()); + + // Parse the last packet and ensure it's two stream frames from one stream. + // TODO(ianswett): Ideally this would arrive in one frame in the future. + EXPECT_EQ(1u, helper_->frame_count()); + EXPECT_EQ(1u, helper_->stream_frames()->size()); + EXPECT_EQ(1u, (*helper_->stream_frames())[0].stream_id); + EXPECT_TRUE((*helper_->stream_frames())[0].fin); +} + TEST_F(QuicConnectionTest, OnCanWrite) { // Visitor's OnCanWill send data, but will return false. EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll( @@ -1396,9 +1518,7 @@ TEST_F(QuicConnectionTest, RetransmitOnNack) { SendStreamDataToPeer(1, "foos", 3, !kFin, &last_packet); // Packet 2 SendStreamDataToPeer(1, "fooos", 7, !kFin, &last_packet); // Packet 3 - SequenceNumberSet expected_acks; - expected_acks.insert(1); - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); // Peer acks one but not two or three. Right now we only retransmit on // explicit nack, so it should not trigger a retransimission. @@ -1409,10 +1529,6 @@ TEST_F(QuicConnectionTest, RetransmitOnNack) { ProcessAckPacket(&ack_one, true); ProcessAckPacket(&ack_one, true); - expected_acks.clear(); - expected_acks.insert(3); - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); - // Peer acks up to 3 with two explicitly missing. Two nacks should cause no // change. QuicAckFrame nack_two(3, QuicTime::Zero(), 0); @@ -1427,16 +1543,18 @@ TEST_F(QuicConnectionTest, RetransmitOnNack) { // The third nack should trigger a retransimission. EXPECT_CALL(*send_algorithm_, SentPacket(_, _, second_packet_size - kQuicVersionSize, - IS_RETRANSMISSION)).Times(1); + IS_RETRANSMISSION, _)).Times(1); ProcessAckPacket(&nack_two, true); } TEST_F(QuicConnectionTest, RetransmitNackedLargestObserved) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); QuicPacketSequenceNumber largest_observed; QuicByteCount packet_size; - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION)) - .WillOnce(DoAll(SaveArg<1>(&largest_observed), SaveArg<2>(&packet_size))); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)) + .WillOnce(DoAll(SaveArg<1>(&largest_observed), SaveArg<2>(&packet_size), + Return(true))); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1); connection_.SendStreamData(1, "foo", 0, !kFin); QuicAckFrame frame(1, QuicTime::Zero(), largest_observed); @@ -1447,13 +1565,13 @@ TEST_F(QuicConnectionTest, RetransmitNackedLargestObserved) { ProcessAckPacket(&frame, true); // Third nack should retransmit the largest observed packet. EXPECT_CALL(*send_algorithm_, SentPacket(_, _, packet_size - kQuicVersionSize, - IS_RETRANSMISSION)); + IS_RETRANSMISSION, _)); ProcessAckPacket(&frame, true); } TEST_F(QuicConnectionTest, RetransmitNackedPacketsOnTruncatedAck) { for (int i = 0; i < 200; ++i) { - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); connection_.SendStreamData(1, "foo", i * 3, !kFin); } @@ -1467,7 +1585,7 @@ TEST_F(QuicConnectionTest, RetransmitNackedPacketsOnTruncatedAck) { EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); - EXPECT_CALL(visitor_, OnAck(_)).Times(1); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessAckPacket(&frame, true); EXPECT_TRUE(QuicConnectionPeer::GetReceivedTruncatedAck(&connection_)); @@ -1475,7 +1593,7 @@ TEST_F(QuicConnectionTest, RetransmitNackedPacketsOnTruncatedAck) { clock_.AdvanceTime(DefaultRetransmissionTime()); // Only packets that are less than largest observed should be retransmitted. EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(192); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(192); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(192); connection_.OnRetransmissionTimeout(); clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds( @@ -1483,11 +1601,12 @@ TEST_F(QuicConnectionTest, RetransmitNackedPacketsOnTruncatedAck) { // Retransmit already retransmitted packets event though the sequence number // greater than the largest observed. EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(192); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(192); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(192); connection_.OnRetransmissionTimeout(); } TEST_F(QuicConnectionTest, LimitPacketsPerNack) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(*send_algorithm_, OnIncomingAck(12, _, _)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(11); @@ -1507,19 +1626,16 @@ TEST_F(QuicConnectionTest, LimitPacketsPerNack) { nack.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(&connection_, 12) ^ QuicConnectionPeer::GetSentEntropyHash(&connection_, 11); - SequenceNumberSet expected_acks; - expected_acks.insert(12); - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); // Nack three times. ProcessAckPacket(&nack, true); ProcessAckPacket(&nack, true); // The third call should trigger retransmitting 10 packets. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(10); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(10); ProcessAckPacket(&nack, true); // The fourth call should trigger retransmitting the 11th packet. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); ProcessAckPacket(&nack, true); } @@ -1548,26 +1664,15 @@ TEST_F(QuicConnectionTest, MultipleAcks) { QuicConnectionPeer::GetSentEntropyHash(&connection_, 2) ^ QuicConnectionPeer::GetSentEntropyHash(&connection_, 1); - // The connection should pass up acks for 1, 4, 5. 2 is not acked, and 3 was - // an ackframe so should not be passed up. - SequenceNumberSet expected_acks; - expected_acks.insert(1); - expected_acks.insert(4); - expected_acks.insert(5); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); ProcessAckPacket(&frame1, true); // Now the client implicitly acks 2, and explicitly acks 6 QuicAckFrame frame2(6, QuicTime::Zero(), 0); frame2.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(&connection_, 6); - expected_acks.clear(); - // Both acks should be passed up. - expected_acks.insert(2); - expected_acks.insert(6); - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); ProcessAckPacket(&frame2, true); } @@ -1576,12 +1681,7 @@ TEST_F(QuicConnectionTest, DontLatchUnackedPacket) { SendStreamDataToPeer(1, "foo", 0, !kFin, NULL); // Packet 1; SendAckPacketToPeer(); // Packet 2 - // This sets least unacked to 3 (unsent packet), since we don't need - // an ack for Packet 2 (ack packet). - SequenceNumberSet expected_acks; - expected_acks.insert(1); - // Peer acks packet 1. - EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks))); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); QuicAckFrame frame(1, QuicTime::Zero(), 0); frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash( &connection_, 1); @@ -1606,42 +1706,57 @@ TEST_F(QuicConnectionTest, DontLatchUnackedPacket) { } TEST_F(QuicConnectionTest, ReviveMissingPacketAfterFecPacket) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + // Don't send missing packet 1. ProcessFecPacket(2, 1, true, !kEntropyFlag, NULL); - EXPECT_FALSE(revived_header_.entropy_flag); + // Entropy flag should be false, so entropy should be 0. + EXPECT_EQ(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2)); } TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPacketThenFecPacket) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessFecProtectedPacket(1, false, kEntropyFlag); // Don't send missing packet 2. ProcessFecPacket(3, 1, true, !kEntropyFlag, NULL); - EXPECT_TRUE(revived_header_.entropy_flag); + // Entropy flag should be true, so entropy should not be 0. + EXPECT_NE(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2)); } TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPacketsThenFecPacket) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessFecProtectedPacket(1, false, !kEntropyFlag); // Don't send missing packet 2. ProcessFecProtectedPacket(3, false, !kEntropyFlag); ProcessFecPacket(4, 1, true, kEntropyFlag, NULL); - EXPECT_TRUE(revived_header_.entropy_flag); + // Entropy flag should be true, so entropy should not be 0. + EXPECT_NE(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2)); } TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPacket) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + // Don't send missing packet 1. ProcessFecPacket(3, 1, false, !kEntropyFlag, NULL); // out of order ProcessFecProtectedPacket(2, true, !kEntropyFlag); - EXPECT_FALSE(revived_header_.entropy_flag); + // Entropy flag should be false, so entropy should be 0. + EXPECT_EQ(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2)); } TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPackets) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + ProcessFecProtectedPacket(1, false, !kEntropyFlag); // Don't send missing packet 2. ProcessFecPacket(6, 1, false, kEntropyFlag, NULL); ProcessFecProtectedPacket(3, false, kEntropyFlag); ProcessFecProtectedPacket(4, false, kEntropyFlag); ProcessFecProtectedPacket(5, true, !kEntropyFlag); - EXPECT_TRUE(revived_header_.entropy_flag); + // Entropy flag should be true, so entropy should be 0. + EXPECT_NE(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2)); } TEST_F(QuicConnectionTest, TestRetransmit) { @@ -1655,7 +1770,7 @@ TEST_F(QuicConnectionTest, TestRetransmit) { connection_.GetRetransmissionAlarm()->deadline()); // Simulate the retransimission alarm firing clock_.AdvanceTime(DefaultRetransmissionTime()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1); connection_.RetransmitPacket(1); EXPECT_EQ(2u, last_header()->packet_sequence_number); @@ -1684,12 +1799,12 @@ TEST_F(QuicConnectionTest, RetransmitWithSameEncryptionLevel) { clock_.AdvanceTime(DefaultRetransmissionTime()); EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); connection_.RetransmitPacket(1); // Packet should have been sent with ENCRYPTION_NONE. EXPECT_EQ(0x01010101u, final_bytes_of_last_packet()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); connection_.RetransmitPacket(2); // Packet should have been sent with ENCRYPTION_INITIAL. EXPECT_EQ(0x02020202u, final_bytes_of_last_packet()); @@ -1706,7 +1821,7 @@ TEST_F(QuicConnectionTest, new TaggingEncrypter(0x02)); connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(0); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(0); EXPECT_CALL(*send_algorithm_, AbandoningPacket(sequence_number, _)).Times(1); QuicTime default_retransmission_time = clock_.ApproximateNow().Add( @@ -1731,13 +1846,14 @@ TEST_F(QuicConnectionTest, RetransmitPacketsWithInitialEncryption) { SendStreamDataToPeer(2, "bar", 0, !kFin, NULL); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(1); connection_.RetransmitUnackedPackets(QuicConnection::INITIAL_ENCRYPTION_ONLY); } TEST_F(QuicConnectionTest, BufferNonDecryptablePackets) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); use_tagging_decrypter(); const uint8 tag = 0x07; @@ -1753,26 +1869,26 @@ TEST_F(QuicConnectionTest, BufferNonDecryptablePackets) { connection_.SetDecrypter(new StrictTaggingDecrypter(tag)); connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(tag)); - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(2).WillRepeatedly( + EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(2).WillRepeatedly( Return(true)); ProcessDataPacketAtLevel(2, false, kEntropyFlag, ENCRYPTION_INITIAL); // Finally, process a third packet and note that we do not // reprocess the buffered packet. - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(Return(true)); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillOnce(Return(true)); ProcessDataPacketAtLevel(3, false, kEntropyFlag, ENCRYPTION_INITIAL); } TEST_F(QuicConnectionTest, TestRetransmitOrder) { QuicByteCount first_packet_size; - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).WillOnce( - SaveArg<2>(&first_packet_size)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).WillOnce( + DoAll(SaveArg<2>(&first_packet_size), Return(true))); EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2); connection_.SendStreamData(1, "first_packet", 0, !kFin); QuicByteCount second_packet_size; - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).WillOnce( - SaveArg<2>(&second_packet_size)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).WillOnce( + DoAll(SaveArg<2>(&second_packet_size), Return(true))); connection_.SendStreamData(1, "second_packet", 12, !kFin); EXPECT_NE(first_packet_size, second_packet_size); // Advance the clock by huge time to make sure packets will be retransmitted. @@ -1780,20 +1896,20 @@ TEST_F(QuicConnectionTest, TestRetransmitOrder) { { InSequence s; EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, first_packet_size, _)); + SentPacket(_, _, first_packet_size, _, _)); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, second_packet_size, _)); + SentPacket(_, _, second_packet_size, _, _)); } connection_.OnRetransmissionTimeout(); } TEST_F(QuicConnectionTest, TestRetransmissionCountCalculation) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2); QuicPacketSequenceNumber original_sequence_number; - EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)) - .WillOnce(SaveArg<1>(&original_sequence_number)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)) + .WillOnce(DoAll(SaveArg<1>(&original_sequence_number), Return(true))); connection_.SendStreamData(1, "foo", 0, !kFin); EXPECT_TRUE(QuicConnectionPeer::IsSavedForRetransmission( &connection_, original_sequence_number)); @@ -1802,9 +1918,8 @@ TEST_F(QuicConnectionTest, TestRetransmissionCountCalculation) { // Force retransmission due to RTO. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10)); QuicPacketSequenceNumber rto_sequence_number; - EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, IS_RETRANSMISSION)) - .WillOnce(SaveArg<1>(&rto_sequence_number)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, IS_RETRANSMISSION, _)) + .WillOnce(DoAll(SaveArg<1>(&rto_sequence_number), Return(true))); connection_.OnRetransmissionTimeout(); EXPECT_FALSE(QuicConnectionPeer::IsSavedForRetransmission( &connection_, original_sequence_number)); @@ -1816,10 +1931,10 @@ TEST_F(QuicConnectionTest, TestRetransmissionCountCalculation) { QuicPacketSequenceNumber nack_sequence_number; // Ack packets might generate some other packets, which are not // retransmissions. (More ack packets). - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION)) + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)) .Times(AnyNumber()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, IS_RETRANSMISSION)) - .WillOnce(SaveArg<1>(&nack_sequence_number)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, IS_RETRANSMISSION, _)) + .WillOnce(DoAll(SaveArg<1>(&nack_sequence_number), Return(true))); QuicAckFrame ack(rto_sequence_number, QuicTime::Zero(), 0); // Ack the retransmitted packet. ack.received_info.missing_packets.insert(rto_sequence_number); @@ -1849,6 +1964,45 @@ TEST_F(QuicConnectionTest, SetRTOAfterWritingToSocket) { EXPECT_EQ(1u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_)); } +TEST_F(QuicConnectionTest, DelayRTOWithAckReceipt) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION, _)) + .Times(2); + connection_.SendStreamData(1, "foo", 0, !kFin); + connection_.SendStreamData(2, "bar", 0, !kFin); + EXPECT_EQ(2u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_)); + + // Advance the time right before the RTO, then receive an ack for the first + // packet to delay the RTO. + clock_.AdvanceTime(DefaultRetransmissionTime()); + EXPECT_EQ(2u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_)); + EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1); + QuicAckFrame ack(1, QuicTime::Zero(), 0); + ProcessAckPacket(&ack, true); + EXPECT_EQ(1u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_)); + + // Move forward past the original RTO and ensure the RTO is still pending. + clock_.AdvanceTime(DefaultRetransmissionTime()); + EXPECT_EQ(1u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_)); + + // Ensure the second packet gets retransmitted when it finally fires. + EXPECT_TRUE( + QuicConnectionPeer::GetRetransmissionAlarm(&connection_)->IsSet()); + EXPECT_GE( + QuicConnectionPeer::GetRetransmissionAlarm(&connection_)->deadline(), + clock_.ApproximateNow()); + clock_.AdvanceTime(DefaultRetransmissionTime()); + EXPECT_LT( + QuicConnectionPeer::GetRetransmissionAlarm(&connection_)->deadline(), + clock_.ApproximateNow()); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, IS_RETRANSMISSION, _)); + EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)); + connection_.OnRetransmissionTimeout(); + + // The new retransmitted sequence number should now be in the timeout queue. + EXPECT_EQ(1u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_)); +} + TEST_F(QuicConnectionTest, TestQueued) { EXPECT_EQ(0u, connection_.NumQueuedPackets()); helper_->set_blocked(true); @@ -1868,6 +2022,7 @@ TEST_F(QuicConnectionTest, TestQueued) { } TEST_F(QuicConnectionTest, CloseFecGroup) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); // Don't send missing packet 1 // Don't send missing packet 2 ProcessFecProtectedPacket(3, false, !kEntropyFlag); @@ -1900,10 +2055,12 @@ TEST_F(QuicConnectionTest, WithQuicCongestionFeedbackFrame) { TEST_F(QuicConnectionTest, UpdateQuicCongestionFeedbackFrame) { SendAckPacketToPeer(); EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _, _)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessPacket(1); } TEST_F(QuicConnectionTest, DontUpdateQuicCongestionFeedbackFrameForRevived) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); SendAckPacketToPeer(); // Process an FEC packet, and revive the missing data packet // but only contact the receive_algorithm once. @@ -1914,7 +2071,7 @@ TEST_F(QuicConnectionTest, DontUpdateQuicCongestionFeedbackFrameForRevived) { TEST_F(QuicConnectionTest, InitialTimeout) { EXPECT_TRUE(connection_.connected()); EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); QuicTime default_timeout = clock_.ApproximateNow().Add( QuicTime::Delta::FromSeconds(kDefaultInitialTimeoutSecs)); @@ -1953,7 +2110,7 @@ TEST_F(QuicConnectionTest, TimeoutAfterSend) { // This time, we should time out. EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); EXPECT_EQ(default_timeout.Add(QuicTime::Delta::FromMilliseconds(5)), clock_.ApproximateNow()); @@ -1968,7 +2125,7 @@ TEST_F(QuicConnectionTest, SendScheduler) { EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( testing::Return(QuicTime::Delta::Zero())); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); connection_.SendOrQueuePacket( ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA); EXPECT_EQ(0u, connection_.NumQueuedPackets()); @@ -1980,7 +2137,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelay) { EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( testing::Return(QuicTime::Delta::FromMicroseconds(1))); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _)).Times(0); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _, _)).Times(0); connection_.SendOrQueuePacket( ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA); EXPECT_EQ(1u, connection_.NumQueuedPackets()); @@ -1991,7 +2148,7 @@ TEST_F(QuicConnectionTest, SendSchedulerForce) { QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag); EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, IS_RETRANSMISSION, _, _)).Times(0); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); connection_.SendOrQueuePacket( ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA); // XXX: fixme. was: connection_.SendOrQueuePacket(1, packet, kForce); @@ -2004,7 +2161,7 @@ TEST_F(QuicConnectionTest, SendSchedulerEAGAIN) { EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( testing::Return(QuicTime::Delta::Zero())); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _)).Times(0); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _, _)).Times(0); connection_.SendOrQueuePacket( ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA); EXPECT_EQ(1u, connection_.NumQueuedPackets()); @@ -2027,7 +2184,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelayThenSend) { testing::Return(QuicTime::Delta::Zero())); clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(1)); connection_.GetSendAlarm()->Cancel(); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)); EXPECT_CALL(visitor_, OnCanWrite()); connection_.OnCanWrite(); EXPECT_EQ(0u, connection_.NumQueuedPackets()); @@ -2038,7 +2195,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelayThenRetransmit) { .WillRepeatedly(testing::Return(QuicTime::Delta::Zero())); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1); EXPECT_CALL(*send_algorithm_, - SentPacket(_, 1, _, NOT_RETRANSMISSION)); + SentPacket(_, 1, _, NOT_RETRANSMISSION, _)); connection_.SendStreamData(1, "foo", 0, !kFin); EXPECT_EQ(0u, connection_.NumQueuedPackets()); // Advance the time for retransmission of lost packet. @@ -2058,7 +2215,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelayThenRetransmit) { // Ensure the scheduler is notified this is a retransmit. EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, IS_RETRANSMISSION)); + SentPacket(_, _, _, IS_RETRANSMISSION, _)); clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(1)); connection_.GetSendAlarm()->Cancel(); EXPECT_CALL(visitor_, OnCanWrite()); @@ -2083,6 +2240,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelayAndQueue) { } TEST_F(QuicConnectionTest, SendSchedulerDelayThenAckAndSend) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag); EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( @@ -2098,7 +2256,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelayThenAckAndSend) { TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly( testing::Return(QuicTime::Delta::Zero())); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, _)); + SentPacket(_, _, _, _, _)); ProcessAckPacket(&frame, true); EXPECT_EQ(0u, connection_.NumQueuedPackets()); @@ -2107,6 +2265,7 @@ TEST_F(QuicConnectionTest, SendSchedulerDelayThenAckAndSend) { } TEST_F(QuicConnectionTest, SendSchedulerDelayThenAckAndHold) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag); EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( @@ -2168,7 +2327,7 @@ TEST_F(QuicConnectionTest, LoopThroughSendingPackets) { NOT_IN_FEC_GROUP, &payload_length); // Queue the first packet. - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(7); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(7); // The first stream frame will consume 2 fewer bytes than the other six. const string payload(payload_length * 7 - 12, 'a'); EXPECT_EQ(payload.size(), @@ -2176,10 +2335,11 @@ TEST_F(QuicConnectionTest, LoopThroughSendingPackets) { } TEST_F(QuicConnectionTest, NoAckForClose) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessPacket(1); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(0); EXPECT_CALL(visitor_, ConnectionClose(QUIC_PEER_GOING_AWAY, true)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(0); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(0); ProcessClosePacket(2, 0); } @@ -2189,7 +2349,7 @@ TEST_F(QuicConnectionTest, SendWhenDisconnected) { connection_.CloseConnection(QUIC_PEER_GOING_AWAY, false); EXPECT_FALSE(connection_.connected()); QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _)).Times(0); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _, _)).Times(0); connection_.SendOrQueuePacket( ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA); } @@ -2207,6 +2367,8 @@ TEST_F(QuicConnectionTest, PublicReset) { } TEST_F(QuicConnectionTest, GoAway) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + QuicGoAwayFrame goaway; goaway.last_good_stream_id = 1; goaway.error_code = QUIC_PEER_GOING_AWAY; @@ -2219,12 +2381,14 @@ TEST_F(QuicConnectionTest, MissingPacketsBeforeLeastUnacked) { QuicAckFrame ack(0, QuicTime::Zero(), 4); // Set the sequence number of the ack packet to be least unacked (4) creator_.set_sequence_number(3); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessAckPacket(&ack, true); EXPECT_TRUE(outgoing_ack()->received_info.missing_packets.empty()); } TEST_F(QuicConnectionTest, ReceivedEntropyHashCalculation) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessDataPacket(1, 1, kEntropyFlag); ProcessDataPacket(4, 1, kEntropyFlag); ProcessDataPacket(3, 1, !kEntropyFlag); @@ -2233,7 +2397,8 @@ TEST_F(QuicConnectionTest, ReceivedEntropyHashCalculation) { } TEST_F(QuicConnectionTest, UpdateEntropyForReceivedPackets) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessDataPacket(1, 1, kEntropyFlag); ProcessDataPacket(5, 1, kEntropyFlag); ProcessDataPacket(4, 1, !kEntropyFlag); @@ -2253,7 +2418,8 @@ TEST_F(QuicConnectionTest, UpdateEntropyForReceivedPackets) { } TEST_F(QuicConnectionTest, UpdateEntropyHashUptoCurrentPacket) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessDataPacket(1, 1, kEntropyFlag); ProcessDataPacket(5, 1, !kEntropyFlag); ProcessDataPacket(22, 1, kEntropyFlag); @@ -2272,7 +2438,8 @@ TEST_F(QuicConnectionTest, UpdateEntropyHashUptoCurrentPacket) { } TEST_F(QuicConnectionTest, EntropyCalculationForTruncatedAck) { - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnStreamFrames(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); QuicPacketEntropyHash entropy[51]; entropy[0] = 0; for (int i = 1; i < 51; ++i) { @@ -2396,7 +2563,8 @@ TEST_F(QuicConnectionTest, ClientHandlesVersionNegotiation) { scoped_ptr<QuicPacket> packet( framer_.BuildUnsizedDataPacket(header, frames).packet); encrypted.reset(framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet)); - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(1); + EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted); ASSERT_FALSE(QuicPacketCreatorPeer::SendVersionInPacket( @@ -2431,18 +2599,18 @@ TEST_F(QuicConnectionTest, BadVersionNegotiation) { TEST_F(QuicConnectionTest, CheckSendStats) { EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(3); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)); + SentPacket(_, _, _, NOT_RETRANSMISSION, _)); connection_.SendStreamData(1u, "first", 0, !kFin); size_t first_packet_size = last_sent_packet_size(); EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, NOT_RETRANSMISSION)); + SentPacket(_, _, _, NOT_RETRANSMISSION, _)); connection_.SendStreamData(1u, "second", 0, !kFin); size_t second_packet_size = last_sent_packet_size(); // 2 retransmissions due to rto, 1 due to explicit nack. EXPECT_CALL(*send_algorithm_, - SentPacket(_, _, _, IS_RETRANSMISSION)).Times(3); + SentPacket(_, _, _, IS_RETRANSMISSION, _)).Times(3); // Retransmit due to RTO. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10)); @@ -2456,10 +2624,10 @@ TEST_F(QuicConnectionTest, CheckSendStats) { QuicConnectionPeer::GetSentEntropyHash(&connection_, 3) ^ QuicConnectionPeer::GetSentEntropyHash(&connection_, 2); QuicFrame frame(&nack_three); - EXPECT_CALL(visitor_, OnAck(_)); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); EXPECT_CALL(visitor_, OnCanWrite()).Times(3).WillRepeatedly(Return(true)); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessFramePacket(frame); ProcessFramePacket(frame); @@ -2481,6 +2649,8 @@ TEST_F(QuicConnectionTest, CheckSendStats) { } TEST_F(QuicConnectionTest, CheckReceiveStats) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + size_t received_bytes = 0; received_bytes += ProcessFecProtectedPacket(1, false, !kEntropyFlag); received_bytes += ProcessFecProtectedPacket(3, false, !kEntropyFlag); @@ -2549,7 +2719,8 @@ TEST_F(QuicConnectionTest, DontProcessFramesIfPacketClosedConnection) { ENCRYPTION_NONE, 1, *packet)); EXPECT_CALL(visitor_, ConnectionClose(QUIC_PEER_GOING_AWAY, true)); - EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(0); + EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(0); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted); } @@ -2622,6 +2793,8 @@ TEST_F(QuicConnectionTest, ConnectionCloseWhenNothingPending) { } TEST_F(QuicConnectionTest, AckNotifierTriggerCallback) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + // Create a delegate which we expect to be called. MockAckNotifierDelegate delegate; EXPECT_CALL(delegate, OnAckNotification()).Times(1);; @@ -2630,18 +2803,18 @@ TEST_F(QuicConnectionTest, AckNotifierTriggerCallback) { connection_.SendStreamDataAndNotifyWhenAcked(1, "foo", 0, !kFin, &delegate); // Process an ACK from the server which should trigger the callback. - EXPECT_CALL(visitor_, OnAck(_)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1); QuicAckFrame frame(1, QuicTime::Zero(), 0); ProcessAckPacket(&frame, true); } TEST_F(QuicConnectionTest, AckNotifierFailToTriggerCallback) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + // Create a delegate which we don't expect to be called. MockAckNotifierDelegate delegate; EXPECT_CALL(delegate, OnAckNotification()).Times(0);; - EXPECT_CALL(visitor_, OnAck(_)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(2); EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1); @@ -2661,13 +2834,12 @@ TEST_F(QuicConnectionTest, AckNotifierFailToTriggerCallback) { } TEST_F(QuicConnectionTest, AckNotifierCallbackAfterRetransmission) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + // Create a delegate which we expect to be called. MockAckNotifierDelegate delegate; EXPECT_CALL(delegate, OnAckNotification()).Times(1);; - // OnAck called twice: once with missing packet, once after retransmit. - EXPECT_CALL(visitor_, OnAck(_)).Times(2); - // In total expect ACKs for all 4 packets. EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(4); @@ -2688,7 +2860,7 @@ TEST_F(QuicConnectionTest, AckNotifierCallbackAfterRetransmission) { // Advance time to trigger RTO, for packet 2 (which should be retransmitted as // packet 5). EXPECT_CALL(*send_algorithm_, AbandoningPacket(2, _)).Times(1); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(1); clock_.AdvanceTime(DefaultRetransmissionTime()); connection_.OnRetransmissionTimeout(); @@ -2702,6 +2874,7 @@ TEST_F(QuicConnectionTest, AckNotifierCallbackAfterRetransmission) { // TODO(rjshade): Add a similar test that FEC recovery on peer (and resulting // ACK) triggers notification on our end. TEST_F(QuicConnectionTest, AckNotifierCallbackAfterFECRecovery) { + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); EXPECT_CALL(visitor_, OnCanWrite()).Times(1).WillOnce(Return(true)); // Create a delegate which we expect to be called. @@ -2709,7 +2882,6 @@ TEST_F(QuicConnectionTest, AckNotifierCallbackAfterFECRecovery) { EXPECT_CALL(delegate, OnAckNotification()).Times(1);; // Expect ACKs for 1 packet. - EXPECT_CALL(visitor_, OnAck(_)).Times(1); EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1); // Send one packet, and register to be notified on ACK. @@ -2723,8 +2895,7 @@ TEST_F(QuicConnectionTest, AckNotifierCallbackAfterFECRecovery) { frames.push_back(QuicFrame(&ack_frame)); // Dummy stream frame to satisfy expectations set elsewhere. - QuicFrame frame(&frame1_); - frames.push_back(frame); + frames.push_back(QuicFrame(&frame1_)); QuicPacketHeader ack_header; ack_header.public_header.guid = guid_; diff --git a/net/quic/quic_crypto_client_stream.cc b/net/quic/quic_crypto_client_stream.cc index e43406fbc3..b0f81174b7 100644 --- a/net/quic/quic_crypto_client_stream.cc +++ b/net/quic/quic_crypto_client_stream.cc @@ -264,7 +264,6 @@ void QuicCryptoClientStream::DoHandshakeLoop( verify_ok_ = false; ProofVerifier::Status status = verifier->VerifyProof( - session()->connection()->version(), server_hostname_, cached->server_config(), cached->certs(), diff --git a/net/quic/quic_crypto_server_stream.cc b/net/quic/quic_crypto_server_stream.cc index a23a34d5b4..63637f5429 100644 --- a/net/quic/quic_crypto_server_stream.cc +++ b/net/quic/quic_crypto_server_stream.cc @@ -131,7 +131,6 @@ QuicErrorCode QuicCryptoServerStream::ProcessClientHello( string* error_details) { return crypto_config_.ProcessClientHello( message, - session()->connection()->version(), session()->connection()->guid(), session()->connection()->peer_address(), session()->connection()->clock(), diff --git a/net/quic/quic_crypto_stream.cc b/net/quic/quic_crypto_stream.cc index 569648f1ba..3c10c5bfbf 100644 --- a/net/quic/quic_crypto_stream.cc +++ b/net/quic/quic_crypto_stream.cc @@ -59,8 +59,12 @@ void QuicCryptoStream::SendHandshakeMessage( const CryptoHandshakeMessage& message) { session()->OnCryptoHandshakeMessageSent(message); const QuicData& data = message.GetSerialized(); + // To make reasoning about crypto frames easier, we don't combine them with + // any other frames in a single packet. + session()->connection()->Flush(); // TODO(wtc): check the return value. WriteData(string(data.data(), data.length()), false); + session()->connection()->Flush(); } const QuicCryptoNegotiatedParameters& diff --git a/net/quic/quic_framer.cc b/net/quic/quic_framer.cc index 4160ddcb94..cfc8c95774 100644 --- a/net/quic/quic_framer.cc +++ b/net/quic/quic_framer.cc @@ -1040,7 +1040,7 @@ bool QuicFramer::ProcessFrameData() { // TODO(jri): Retain this else block when support for // QUIC version < 10 removed. Remove above if block. - // Special frame type processing for QUIC version >= 10 + // Special frame type processing for QUIC version >= 10. if (frame_type & kQuicFrameTypeSpecialMask) { // Stream Frame if (frame_type & kQuicFrameTypeStreamMask) { diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc index a91b9e2658..0122c64b1a 100644 --- a/net/quic/quic_http_stream.cc +++ b/net/quic/quic_http_stream.cc @@ -11,6 +11,7 @@ #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "net/quic/quic_client_session.h" +#include "net/quic/quic_http_utils.h" #include "net/quic/quic_reliable_client_stream.h" #include "net/quic/quic_utils.h" #include "net/socket/next_proto.h" @@ -29,6 +30,7 @@ QuicHttpStream::QuicHttpStream(const base::WeakPtr<QuicClientSession> session) stream_(NULL), request_info_(NULL), request_body_stream_(NULL), + priority_(MINIMUM_PRIORITY), response_info_(NULL), response_status_(OK), response_headers_received_(false), @@ -52,6 +54,7 @@ int QuicHttpStream::InitializeStream(const HttpRequestInfo* request_info, stream_net_log_ = stream_net_log; request_info_ = request_info; + priority_ = priority; int rv = stream_request_.StartRequest( session_, &stream_, base::Bind(&QuicHttpStream::OnStreamReady, @@ -82,6 +85,8 @@ int QuicHttpStream::SendRequest(const HttpRequestHeaders& request_headers, CHECK(!callback.is_null()); CHECK(response); + QuicPriority priority = ConvertRequestPriorityToQuicPriority(priority_); + stream_->set_priority(priority); // Store the serialized request headers. SpdyHeaderBlock headers; CreateSpdyHeadersFromHttpRequest(*request_info_, request_headers, @@ -89,7 +94,8 @@ int QuicHttpStream::SendRequest(const HttpRequestHeaders& request_headers, if (session_->connection()->version() < QUIC_VERSION_9) { request_ = stream_->compressor()->CompressHeaders(headers); } else { - request_ = stream_->compressor()->CompressHeadersWithPriority(0, headers); + request_ = stream_->compressor()->CompressHeadersWithPriority(priority, + headers); } // Log the actual request with the URL Request's net log. stream_net_log_.AddEvent( @@ -207,7 +213,7 @@ void QuicHttpStream::Close(bool not_reusable) { stream_->SetDelegate(NULL); // TODO(rch): use new CANCELLED error code here once quic 11 // is everywhere. - stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM); + stream_->Close(QUIC_ERROR_PROCESSING_STREAM); stream_ = NULL; } } @@ -264,7 +270,7 @@ void QuicHttpStream::Drain(HttpNetworkSession* session) { } void QuicHttpStream::SetPriority(RequestPriority priority) { - // Nothing to do here (yet). + priority_ = priority; } int QuicHttpStream::OnSendData() { @@ -336,6 +342,10 @@ void QuicHttpStream::OnError(int error) { DoCallback(response_status_); } +bool QuicHttpStream::HasSendHeadersComplete() { + return next_state_ > STATE_SEND_HEADERS_COMPLETE; +} + void QuicHttpStream::OnIOComplete(int rv) { rv = DoLoop(rv); diff --git a/net/quic/quic_http_stream.h b/net/quic/quic_http_stream.h index cc2b973e87..71fb515335 100644 --- a/net/quic/quic_http_stream.h +++ b/net/quic/quic_http_stream.h @@ -15,6 +15,10 @@ namespace net { +namespace test { +class QuicHttpStreamPeer; +} // namespace test + // The QuicHttpStream is a QUIC-specific HttpStream subclass. It holds a // non-owning pointer to a QuicReliableClientStream which it uses to // send and receive data. @@ -62,8 +66,11 @@ class NET_EXPORT_PRIVATE QuicHttpStream : virtual int OnDataReceived(const char* data, int length) OVERRIDE; virtual void OnClose(QuicErrorCode error) OVERRIDE; virtual void OnError(int error) OVERRIDE; + virtual bool HasSendHeadersComplete() OVERRIDE; private: + friend class test::QuicHttpStreamPeer; + enum State { STATE_NONE, STATE_SEND_HEADERS, @@ -106,6 +113,8 @@ class NET_EXPORT_PRIVATE QuicHttpStream : const HttpRequestInfo* request_info_; // The request body to send, if any, owned by the caller. UploadDataStream* request_body_stream_; + // The priority of the request. + RequestPriority priority_; // |response_info_| is the HTTP response data object which is filled in // when a the response headers are read. It is not owned by this stream. HttpResponseInfo* response_info_; diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc index 6a584f1800..37242bf8cd 100644 --- a/net/quic/quic_http_stream_test.cc +++ b/net/quic/quic_http_stream_test.cc @@ -19,6 +19,8 @@ #include "net/quic/quic_client_session.h" #include "net/quic/quic_connection.h" #include "net/quic/quic_connection_helper.h" +#include "net/quic/quic_http_utils.h" +#include "net/quic/quic_reliable_client_stream.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" @@ -31,6 +33,7 @@ #include "net/spdy/spdy_framer.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" +#include "net/spdy/write_blocked_list.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -102,6 +105,14 @@ class AutoClosingStream : public QuicHttpStream { } // namespace +class QuicHttpStreamPeer { + public: + static QuicReliableClientStream* GetQuicReliableClientStream( + QuicHttpStream* stream) { + return stream->stream_; + } +}; + class QuicHttpStreamTest : public ::testing::TestWithParam<bool> { protected: const static bool kFin = true; @@ -177,7 +188,7 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> { receive_algorithm_ = new TestReceiveAlgorithm(NULL); EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _, _)). Times(AnyNumber()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)).Times(AnyNumber()); EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( Return(QuicTime::Delta::Zero())); EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)). @@ -206,14 +217,16 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> { new QuicHttpStream(session_->GetWeakPtr())); } - void SetRequestString(const std::string& method, const std::string& path) { + void SetRequestString(const std::string& method, + const std::string& path, + RequestPriority priority) { SpdyHeaderBlock headers; headers[":method"] = method; headers[":host"] = "www.google.com"; headers[":path"] = path; headers[":scheme"] = "http"; headers[":version"] = "HTTP/1.1"; - request_data_ = SerializeHeaderBlock(headers, true); + request_data_ = SerializeHeaderBlock(headers, true, priority); } void SetResponseString(const std::string& status, const std::string& body) { @@ -221,14 +234,17 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> { headers[":status"] = status; headers[":version"] = "HTTP/1.1"; headers["content-type"] = "text/plain"; - response_data_ = SerializeHeaderBlock(headers, false) + body; + response_data_ = SerializeHeaderBlock(headers, false, DEFAULT_PRIORITY) + + body; } std::string SerializeHeaderBlock(const SpdyHeaderBlock& headers, - bool write_priority) { + bool write_priority, + RequestPriority priority) { QuicSpdyCompressor compressor; if (framer_.version() >= QUIC_VERSION_9 && write_priority) { - return compressor.CompressHeadersWithPriority(0, headers); + return compressor.CompressHeadersWithPriority( + ConvertRequestPriorityToQuicPriority(priority), headers); } return compressor.CompressHeaders(headers); } @@ -249,7 +265,7 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> { QuicEncryptedPacket* ConstructRstStreamPacket( QuicPacketSequenceNumber sequence_number) { InitializeHeader(sequence_number, false); - QuicRstStreamFrame frame(3, QUIC_SERVER_ERROR_PROCESSING_STREAM); + QuicRstStreamFrame frame(3, QUIC_ERROR_PROCESSING_STREAM); return ConstructPacket(header_, QuicFrame(&frame)); } @@ -350,7 +366,7 @@ TEST_F(QuicHttpStreamTest, IsConnectionReusable) { } TEST_F(QuicHttpStreamTest, GetRequest) { - SetRequestString("GET", "/"); + SetRequestString("GET", "/", DEFAULT_PRIORITY); AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_)); Initialize(); @@ -393,7 +409,7 @@ TEST_F(QuicHttpStreamTest, GetRequest) { // Regression test for http://crbug.com/288128 TEST_F(QuicHttpStreamTest, GetRequestLargeResponse) { - SetRequestString("GET", "/"); + SetRequestString("GET", "/", DEFAULT_PRIORITY); AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_)); Initialize(); @@ -440,7 +456,7 @@ TEST_F(QuicHttpStreamTest, GetRequestLargeResponse) { } TEST_F(QuicHttpStreamTest, GetRequestFullResponseInSinglePacket) { - SetRequestString("GET", "/"); + SetRequestString("GET", "/", DEFAULT_PRIORITY); AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_)); Initialize(); @@ -482,7 +498,7 @@ TEST_F(QuicHttpStreamTest, GetRequestFullResponseInSinglePacket) { } TEST_F(QuicHttpStreamTest, SendPostRequest) { - SetRequestString("POST", "/"); + SetRequestString("POST", "/", DEFAULT_PRIORITY); AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, !kFin, 0, request_data_)); AddWrite(SYNCHRONOUS, ConstructDataPacket(2, true, kFin, request_data_.length(), @@ -539,7 +555,7 @@ TEST_F(QuicHttpStreamTest, SendPostRequest) { } TEST_F(QuicHttpStreamTest, SendChunkedPostRequest) { - SetRequestString("POST", "/"); + SetRequestString("POST", "/", DEFAULT_PRIORITY); size_t chunk_size = strlen(kUploadData); AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, !kFin, 0, request_data_)); AddWrite(SYNCHRONOUS, ConstructDataPacket(2, true, !kFin, @@ -601,7 +617,7 @@ TEST_F(QuicHttpStreamTest, SendChunkedPostRequest) { } TEST_F(QuicHttpStreamTest, DestroyedEarly) { - SetRequestString("GET", "/"); + SetRequestString("GET", "/", DEFAULT_PRIORITY); AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_)); AddWrite(SYNCHRONOUS, ConstructRstStreamPacket(2)); use_closing_stream_ = true; @@ -613,9 +629,54 @@ TEST_F(QuicHttpStreamTest, DestroyedEarly) { EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_, callback_.callback())); EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_, - callback_.callback())); + callback_.callback())); + EXPECT_EQ(&response_, stream_->GetResponseInfo()); + + // Ack the request. + scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0)); + ProcessPacket(*ack); + EXPECT_EQ(ERR_IO_PENDING, + stream_->ReadResponseHeaders(callback_.callback())); + + // Send the response with a body. + SetResponseString("404 OK", "hello world!"); + scoped_ptr<QuicEncryptedPacket> resp( + ConstructDataPacket(2, false, kFin, 0, response_data_)); + + // In the course of processing this packet, the QuicHttpStream close itself. + ProcessPacket(*resp); + + EXPECT_TRUE(AtEof()); +} + +TEST_F(QuicHttpStreamTest, Priority) { + SetRequestString("GET", "/", MEDIUM); + AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_)); + AddWrite(SYNCHRONOUS, ConstructRstStreamPacket(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(static_cast<QuicPriority>(kHighestPriority), + reliable_stream->EffectivePriority()); + + EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_, + callback_.callback())); EXPECT_EQ(&response_, stream_->GetResponseInfo()); + // Check that priority has now dropped back to MEDIUM. + DCHECK_EQ(MEDIUM, ConvertQuicPriorityToRequestPriority( + reliable_stream->EffectivePriority())); + // Ack the request. scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0)); ProcessPacket(*ack); diff --git a/net/quic/quic_http_utils.cc b/net/quic/quic_http_utils.cc new file mode 100644 index 0000000000..4a48626854 --- /dev/null +++ b/net/quic/quic_http_utils.cc @@ -0,0 +1,23 @@ +// Copyright 2013 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_utils.h" + +namespace net { + +QuicPriority ConvertRequestPriorityToQuicPriority( + const RequestPriority priority) { + DCHECK_GE(priority, MINIMUM_PRIORITY); + DCHECK_LT(priority, NUM_PRIORITIES); + return static_cast<QuicPriority>(HIGHEST - priority); +} + +NET_EXPORT_PRIVATE RequestPriority ConvertQuicPriorityToRequestPriority( + QuicPriority priority) { + // Handle invalid values gracefully. + return (priority >= 5) ? + IDLE : static_cast<RequestPriority>(HIGHEST - priority); +} + +} // namespace net diff --git a/net/quic/quic_http_utils.h b/net/quic/quic_http_utils.h new file mode 100644 index 0000000000..c7e031ae60 --- /dev/null +++ b/net/quic/quic_http_utils.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_QUIC_QUIC_HTTP_UTILS_H_ +#define NET_QUIC_QUIC_HTTP_UTILS_H_ + +#include "net/base/net_export.h" +#include "net/base/request_priority.h" +#include "net/quic/quic_protocol.h" + +namespace net { + +NET_EXPORT_PRIVATE QuicPriority ConvertRequestPriorityToQuicPriority( + RequestPriority priority); + +NET_EXPORT_PRIVATE RequestPriority ConvertQuicPriorityToRequestPriority( + QuicPriority priority); + +} // namespace net + +#endif // NET_QUIC_QUIC_HTTP_UTILS_H_ diff --git a/net/quic/quic_http_utils_test.cc b/net/quic/quic_http_utils_test.cc new file mode 100644 index 0000000000..93b62e2e9d --- /dev/null +++ b/net/quic/quic_http_utils_test.cc @@ -0,0 +1,35 @@ +// Copyright 2013 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_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +TEST(QuicHttpUtilsTest, ConvertRequestPriorityToQuicPriority) { + EXPECT_EQ(0u, ConvertRequestPriorityToQuicPriority(HIGHEST)); + EXPECT_EQ(1u, ConvertRequestPriorityToQuicPriority(MEDIUM)); + EXPECT_EQ(2u, ConvertRequestPriorityToQuicPriority(LOW)); + EXPECT_EQ(3u, ConvertRequestPriorityToQuicPriority(LOWEST)); + EXPECT_EQ(4u, ConvertRequestPriorityToQuicPriority(IDLE)); +} + +TEST(QuicHttpUtilsTest, ConvertQuicPriorityToRequestPriority) { + EXPECT_EQ(HIGHEST, ConvertQuicPriorityToRequestPriority(0)); + EXPECT_EQ(MEDIUM, ConvertQuicPriorityToRequestPriority(1)); + EXPECT_EQ(LOW, ConvertQuicPriorityToRequestPriority(2)); + EXPECT_EQ(LOWEST, ConvertQuicPriorityToRequestPriority(3)); + EXPECT_EQ(IDLE, ConvertQuicPriorityToRequestPriority(4)); + // These are invalid values, but we should still handle them + // gracefully. TODO(rtenneti): should we test for all possible values of + // uint32? + for (int i = 5; i < kuint8max; ++i) { + EXPECT_EQ(IDLE, ConvertQuicPriorityToRequestPriority(i)); + } +} + +} // namespace test +} // namespace net diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc index 801c7ef75e..a6cbff1b81 100644 --- a/net/quic/quic_network_transaction_unittest.cc +++ b/net/quic/quic_network_transaction_unittest.cc @@ -25,6 +25,7 @@ #include "net/quic/crypto/quic_decrypter.h" #include "net/quic/crypto/quic_encrypter.h" #include "net/quic/quic_framer.h" +#include "net/quic/quic_http_utils.h" #include "net/quic/test_tools/crypto_test_utils.h" #include "net/quic/test_tools/mock_clock.h" #include "net/quic/test_tools/mock_crypto_client_stream_factory.h" @@ -178,7 +179,8 @@ class QuicNetworkTransactionTest : public PlatformTest { std::string SerializeHeaderBlock(const SpdyHeaderBlock& headers) { QuicSpdyCompressor compressor; if (QuicVersionMax() >= QUIC_VERSION_9) { - return compressor.CompressHeadersWithPriority(0, headers); + return compressor.CompressHeadersWithPriority( + ConvertRequestPriorityToQuicPriority(DEFAULT_PRIORITY), headers); } return compressor.CompressHeaders(headers); } diff --git a/net/quic/quic_packet_creator.cc b/net/quic/quic_packet_creator.cc index e1d0e21e19..609ebcbd73 100644 --- a/net/quic/quic_packet_creator.cc +++ b/net/quic/quic_packet_creator.cc @@ -12,6 +12,7 @@ using base::StringPiece; using std::make_pair; +using std::max; using std::min; using std::pair; using std::vector; @@ -31,7 +32,7 @@ QuicPacketCreator::QuicPacketCreator(QuicGuid guid, send_version_in_packet_(!is_server), sequence_number_length_(options_.send_sequence_number_length), packet_size_(0) { - framer_->set_fec_builder(reinterpret_cast<QuicFecBuilderInterface*>(this)); + framer_->set_fec_builder(this); } QuicPacketCreator::~QuicPacketCreator() { @@ -71,6 +72,30 @@ void QuicPacketCreator::StopSendingVersion() { } } +void QuicPacketCreator::UpdateSequenceNumberLength( + QuicPacketSequenceNumber least_packet_awaited_by_peer, + QuicByteCount bytes_per_second) { + DCHECK_LE(least_packet_awaited_by_peer, sequence_number_ + 1); + // Since the packet creator will not change sequence number length mid FEC + // group, include the size of an FEC group to be safe. + const QuicPacketSequenceNumber current_delta = + options_.max_packets_per_fec_group + sequence_number_ + 1 + - least_packet_awaited_by_peer; + const uint64 congestion_window = + bytes_per_second / options_.max_packet_length; + const uint64 delta = max(current_delta, congestion_window); + + if (delta < 1 << ((PACKET_1BYTE_SEQUENCE_NUMBER * 8) - 2)) { + options_.send_sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER; + } else if (delta < 1 << ((PACKET_2BYTE_SEQUENCE_NUMBER * 8) - 2)) { + options_.send_sequence_number_length = PACKET_2BYTE_SEQUENCE_NUMBER; + } else if (delta < 1 << ((PACKET_4BYTE_SEQUENCE_NUMBER * 8) - 2)) { + options_.send_sequence_number_length = PACKET_4BYTE_SEQUENCE_NUMBER; + } else { + options_.send_sequence_number_length = PACKET_6BYTE_SEQUENCE_NUMBER; + } +} + bool QuicPacketCreator::HasRoomForStreamFrame(QuicStreamId id, QuicStreamOffset offset) const { return BytesFree() > @@ -99,7 +124,12 @@ size_t QuicPacketCreator::CreateStreamFrame(QuicStreamId id, StreamFramePacketOverhead( framer_->version(), PACKET_8BYTE_GUID, kIncludeVersion, PACKET_6BYTE_SEQUENCE_NUMBER, IN_FEC_GROUP)); - DCHECK(HasRoomForStreamFrame(id, offset)); + if (!HasRoomForStreamFrame(id, offset)) { + LOG(DFATAL) << "No room for Stream frame, BytesFree: " << BytesFree() + << " MinStreamFrameSize: " + << QuicFramer::GetMinStreamFrameSize( + framer_->version(), id, offset, true); + } const size_t free_bytes = BytesFree(); size_t bytes_consumed = 0; diff --git a/net/quic/quic_packet_creator.h b/net/quic/quic_packet_creator.h index 0e0a8c7789..68b62166fe 100644 --- a/net/quic/quic_packet_creator.h +++ b/net/quic/quic_packet_creator.h @@ -70,6 +70,12 @@ class NET_EXPORT_PRIVATE QuicPacketCreator : public QuicFecBuilderInterface { // Makes the framer not serialize the protocol version in sent packets. void StopSendingVersion(); + // Update the sequence number length to use in future packets as soon as it + // can be safely changed. + void UpdateSequenceNumberLength( + QuicPacketSequenceNumber least_packet_awaited_by_peer, + QuicByteCount bytes_per_second); + // The overhead the framing will add for a packet with one frame. static size_t StreamFramePacketOverhead( QuicVersion version, @@ -152,6 +158,8 @@ class NET_EXPORT_PRIVATE QuicPacketCreator : public QuicFecBuilderInterface { QuicEncryptedPacket* SerializeVersionNegotiationPacket( const QuicVersionVector& supported_versions); + // Sequence number of the last created packet, or 0 if no packets have been + // created. QuicPacketSequenceNumber sequence_number() const { return sequence_number_; } diff --git a/net/quic/quic_packet_creator_test.cc b/net/quic/quic_packet_creator_test.cc index 193bb8895f..51133b6880 100644 --- a/net/quic/quic_packet_creator_test.cc +++ b/net/quic/quic_packet_creator_test.cc @@ -333,6 +333,53 @@ TEST_F(QuicPacketCreatorTest, SerializeVersionNegotiationPacket) { client_framer_.ProcessPacket(*encrypted.get()); } +TEST_F(QuicPacketCreatorTest, UpdatePacketSequenceNumberLengthLeastAwaiting) { + EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.set_sequence_number(64); + creator_.UpdateSequenceNumberLength(2, 10000); + EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.set_sequence_number(64 * 256); + creator_.UpdateSequenceNumberLength(2, 10000); + EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.set_sequence_number(64 * 256 * 256); + creator_.UpdateSequenceNumberLength(2, 10000); + EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.set_sequence_number(GG_UINT64_C(64) * 256 * 256 * 256 * 256); + creator_.UpdateSequenceNumberLength(2, 10000); + EXPECT_EQ(PACKET_6BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); +} + +TEST_F(QuicPacketCreatorTest, UpdatePacketSequenceNumberLengthBandwidth) { + EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.UpdateSequenceNumberLength(1, 10000); + EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.UpdateSequenceNumberLength(1, 10000 * 256); + EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.UpdateSequenceNumberLength(1, 10000 * 256 * 256); + EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); + + creator_.UpdateSequenceNumberLength( + 1, GG_UINT64_C(1000) * 256 * 256 * 256 * 256); + EXPECT_EQ(PACKET_6BYTE_SEQUENCE_NUMBER, + creator_.options()->send_sequence_number_length); +} + INSTANTIATE_TEST_CASE_P(ToggleVersionSerialization, QuicPacketCreatorTest, ::testing::Values(false, true)); diff --git a/net/quic/quic_packet_generator.cc b/net/quic/quic_packet_generator.cc index 41161eb215..9151cb883b 100644 --- a/net/quic/quic_packet_generator.cc +++ b/net/quic/quic_packet_generator.cc @@ -70,13 +70,6 @@ void QuicPacketGenerator::AddControlFrame(const QuicFrame& frame) { QuicConsumedData QuicPacketGenerator::ConsumeData(QuicStreamId id, StringPiece data, QuicStreamOffset offset, - bool fin) { - return ConsumeData(id, data, offset, fin, NULL); -} - -QuicConsumedData QuicPacketGenerator::ConsumeData(QuicStreamId id, - StringPiece data, - QuicStreamOffset offset, bool fin, QuicAckNotifier* notifier) { IsHandshake handshake = id == kCryptoStreamId ? IS_HANDSHAKE : NOT_HANDSHAKE; @@ -88,6 +81,9 @@ QuicConsumedData QuicPacketGenerator::ConsumeData(QuicStreamId id, size_t total_bytes_consumed = 0; bool fin_consumed = false; + if (!packet_creator_->HasRoomForStreamFrame(id, offset)) { + SerializeAndSendPacket(); + } while (delegate_->CanWrite(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, handshake)) { QuicFrame frame; @@ -100,8 +96,13 @@ QuicConsumedData QuicPacketGenerator::ConsumeData(QuicStreamId id, bytes_consumed = packet_creator_->CreateStreamFrame( id, data, offset + total_bytes_consumed, fin, &frame); } - bool success = AddFrame(frame); - DCHECK(success); + if (!AddFrame(frame)) { + LOG(DFATAL) << "Failed to add stream frame."; + // Inability to add a STREAM frame creates an unrecoverable hole in a + // the stream, so it's best to close the connection. + delegate_->CloseConnection(QUIC_INTERNAL_ERROR, false); + return QuicConsumedData(0, false); + } total_bytes_consumed += bytes_consumed; fin_consumed = fin && bytes_consumed == data.size(); @@ -195,7 +196,7 @@ bool QuicPacketGenerator::HasPendingFrames() const { bool QuicPacketGenerator::AddNextPendingFrame() { if (should_send_ack_) { - pending_ack_frame_.reset((delegate_->CreateAckFrame())); + pending_ack_frame_.reset(delegate_->CreateAckFrame()); // If we can't this add the frame now, then we still need to do so later. should_send_ack_ = !AddFrame(QuicFrame(pending_ack_frame_.get())); // Return success if we have cleared out this flag (i.e., added the frame). @@ -204,7 +205,7 @@ bool QuicPacketGenerator::AddNextPendingFrame() { } if (should_send_feedback_) { - pending_feedback_frame_.reset((delegate_->CreateFeedbackFrame())); + pending_feedback_frame_.reset(delegate_->CreateFeedbackFrame()); // If we can't this add the frame now, then we still need to do so later. should_send_feedback_ = !AddFrame(QuicFrame(pending_feedback_frame_.get())); // Return success if we have cleared out this flag (i.e., added the frame). diff --git a/net/quic/quic_packet_generator.h b/net/quic/quic_packet_generator.h index 75472a17f5..940b259adf 100644 --- a/net/quic/quic_packet_generator.h +++ b/net/quic/quic_packet_generator.h @@ -15,7 +15,7 @@ // If the Delegate is not writable, then no operations will cause // a packet to be serialized. In particular: // * SetShouldSendAck will simply record that an ack is to be sent. -// * AddControlFram will enqueue the control frame. +// * AddControlFrame will enqueue the control frame. // * ConsumeData will do nothing. // // If the Delegate is writable, then the behavior depends on the second @@ -71,6 +71,7 @@ class NET_EXPORT_PRIVATE QuicPacketGenerator { virtual QuicCongestionFeedbackFrame* CreateFeedbackFrame() = 0; // Takes ownership of |packet.packet| and |packet.retransmittable_frames|. virtual bool OnSerializedPacket(const SerializedPacket& packet) = 0; + virtual void CloseConnection(QuicErrorCode error, bool from_peer) = 0; }; // Interface which gets callbacks from the QuicPacketGenerator at interesting @@ -90,20 +91,20 @@ class NET_EXPORT_PRIVATE QuicPacketGenerator { virtual ~QuicPacketGenerator(); + // Indicates that an ACK frame should be sent. If |also_send_feedback| is + // true, then it also indicates a CONGESTION_FEEDBACK frame should be sent. + // The contents of the frame(s) will be generated via a call to the delegates + // CreateAckFrame() and CreateFeedbackFrame() when the packet is serialized. void SetShouldSendAck(bool also_send_feedback); void AddControlFrame(const QuicFrame& frame); - // Given some data, may consume part or all of it and pass it to the packet - // creator to be serialized into packets. If not in batch mode, these packets - // will also be sent during this call. - QuicConsumedData ConsumeData(QuicStreamId id, - base::StringPiece data, - QuicStreamOffset offset, - bool fin); - - // As above, but attaches a QuicAckNotifier to any created stream frames, - // which will be called once the frame is ACKed by the peer. - // The QuicAckNotifier is owned by the QuicConnection. + // Given some data, may consume part or all of it and pass it to the + // packet creator to be serialized into packets. If not in batch + // mode, these packets will also be sent during this call. Also + // attaches a QuicAckNotifier to any created stream frames, which + // will be called once the frame is ACKed by the peer. The + // QuicAckNotifier is owned by the QuicConnection. |notifier| may + // be NULL. QuicConsumedData ConsumeData(QuicStreamId id, base::StringPiece data, QuicStreamOffset offset, diff --git a/net/quic/quic_packet_generator_test.cc b/net/quic/quic_packet_generator_test.cc index 88320426d2..ec8dd56a67 100644 --- a/net/quic/quic_packet_generator_test.cc +++ b/net/quic/quic_packet_generator_test.cc @@ -40,6 +40,7 @@ class MockDelegate : public QuicPacketGenerator::DelegateInterface { MOCK_METHOD0(CreateAckFrame, QuicAckFrame*()); MOCK_METHOD0(CreateFeedbackFrame, QuicCongestionFeedbackFrame*()); MOCK_METHOD1(OnSerializedPacket, bool(const SerializedPacket& packet)); + MOCK_METHOD2(CloseConnection, void(QuicErrorCode, bool)); void SetCanWriteAnything() { EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, _, _)) @@ -326,7 +327,7 @@ TEST_F(QuicPacketGeneratorTest, AddControlFrame_WritableAndShouldFlush) { TEST_F(QuicPacketGeneratorTest, ConsumeData_NotWritable) { delegate_.SetCanNotWrite(); - QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true); + QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true, NULL); EXPECT_EQ(0u, consumed.bytes_consumed); EXPECT_FALSE(consumed.fin_consumed); EXPECT_FALSE(generator_.HasQueuedFrames()); @@ -336,7 +337,7 @@ TEST_F(QuicPacketGeneratorTest, ConsumeData_WritableAndShouldNotFlush) { delegate_.SetCanWriteAnything(); generator_.StartBatchOperations(); - QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true); + QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true, NULL); EXPECT_EQ(3u, consumed.bytes_consumed); EXPECT_TRUE(consumed.fin_consumed); EXPECT_TRUE(generator_.HasQueuedFrames()); @@ -347,7 +348,7 @@ TEST_F(QuicPacketGeneratorTest, ConsumeData_WritableAndShouldFlush) { EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce( DoAll(SaveArg<0>(&packet_), Return(true))); - QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true); + QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true, NULL); EXPECT_EQ(3u, consumed.bytes_consumed); EXPECT_TRUE(consumed.fin_consumed); EXPECT_FALSE(generator_.HasQueuedFrames()); @@ -362,8 +363,8 @@ TEST_F(QuicPacketGeneratorTest, delegate_.SetCanWriteAnything(); generator_.StartBatchOperations(); - generator_.ConsumeData(1, "foo", 2, true); - QuicConsumedData consumed = generator_.ConsumeData(3, "quux", 7, false); + generator_.ConsumeData(1, "foo", 2, true, NULL); + QuicConsumedData consumed = generator_.ConsumeData(3, "quux", 7, false, NULL); EXPECT_EQ(4u, consumed.bytes_consumed); EXPECT_FALSE(consumed.fin_consumed); EXPECT_TRUE(generator_.HasQueuedFrames()); @@ -373,8 +374,8 @@ TEST_F(QuicPacketGeneratorTest, ConsumeData_BatchOperations) { delegate_.SetCanWriteAnything(); generator_.StartBatchOperations(); - generator_.ConsumeData(1, "foo", 2, true); - QuicConsumedData consumed = generator_.ConsumeData(3, "quux", 7, false); + generator_.ConsumeData(1, "foo", 2, true, NULL); + QuicConsumedData consumed = generator_.ConsumeData(3, "quux", 7, false, NULL); EXPECT_EQ(4u, consumed.bytes_consumed); EXPECT_FALSE(consumed.fin_consumed); EXPECT_TRUE(generator_.HasQueuedFrames()); @@ -413,7 +414,7 @@ TEST_F(QuicPacketGeneratorTest, ConsumeDataFEC) { // Send enough data to create 3 packets: two full and one partial. size_t data_len = 2 * kMaxPacketSize + 100; QuicConsumedData consumed = - generator_.ConsumeData(3, CreateData(data_len), 0, true); + generator_.ConsumeData(3, CreateData(data_len), 0, true, NULL); EXPECT_EQ(data_len, consumed.bytes_consumed); EXPECT_TRUE(consumed.fin_consumed); EXPECT_FALSE(generator_.HasQueuedFrames()); @@ -445,7 +446,7 @@ TEST_F(QuicPacketGeneratorTest, ConsumeDataSendsFecAtEnd) { // Send enough data to create 2 packets: one full and one partial. size_t data_len = 1 * kMaxPacketSize + 100; QuicConsumedData consumed = - generator_.ConsumeData(3, CreateData(data_len), 0, true); + generator_.ConsumeData(3, CreateData(data_len), 0, true, NULL); EXPECT_EQ(data_len, consumed.bytes_consumed); EXPECT_TRUE(consumed.fin_consumed); EXPECT_FALSE(generator_.HasQueuedFrames()); @@ -455,6 +456,49 @@ TEST_F(QuicPacketGeneratorTest, ConsumeDataSendsFecAtEnd) { CheckPacketIsFec(packet3_, 1); } +TEST_F(QuicPacketGeneratorTest, ConsumeData_FramesPreviouslyQueued) { + // Set the packet size be enough for two stream frames with 0 stream offset, + // but not enough for a stream frame of 0 offset and one with non-zero offset. + creator_.options()->max_packet_length = + NullEncrypter().GetCiphertextSize(0) + + GetPacketHeaderSize(creator_.options()->send_guid_length, + true, + creator_.options()->send_sequence_number_length, + NOT_IN_FEC_GROUP) + + // Add an extra 3 bytes for the payload and 1 byte so BytesFree is larger + // than the GetMinStreamFrameSize. + QuicFramer::GetMinStreamFrameSize(framer_.version(), 1, 0, false) + 3 + + QuicFramer::GetMinStreamFrameSize(framer_.version(), 1, 0, true) + 1; + delegate_.SetCanWriteAnything(); + { + InSequence dummy; + EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce( + DoAll(SaveArg<0>(&packet_), Return(true))); + EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce( + DoAll(SaveArg<0>(&packet2_), Return(true))); + } + generator_.StartBatchOperations(); + // Queue enough data to prevent a stream frame with a non-zero offset from + // fitting. + QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 0, false, NULL); + EXPECT_EQ(3u, consumed.bytes_consumed); + EXPECT_FALSE(consumed.fin_consumed); + EXPECT_TRUE(generator_.HasQueuedFrames()); + + // This frame will not fit with the existing frame, causing the queued frame + // to be serialized, and it will not fit with another frame like it, so it is + // serialized by itself. + consumed = generator_.ConsumeData(1, "bar", 3, true, NULL); + EXPECT_EQ(3u, consumed.bytes_consumed); + EXPECT_TRUE(consumed.fin_consumed); + EXPECT_FALSE(generator_.HasQueuedFrames()); + + PacketContents contents; + contents.num_stream_frames = 1; + CheckPacketContains(contents, packet_); + CheckPacketContains(contents, packet2_); +} + TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations) { delegate_.SetCanNotWrite(); @@ -473,7 +517,7 @@ TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations) { Return(CreateFeedbackFrame())); // Send some data and a control frame - generator_.ConsumeData(3, "quux", 7, false); + generator_.ConsumeData(3, "quux", 7, false, NULL); generator_.AddControlFrame(QuicFrame(CreateGoAwayFrame())); // All five frames will be flushed out in a single packet. @@ -520,7 +564,7 @@ TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations2) { // Send enough data to exceed one packet size_t data_len = kMaxPacketSize + 100; QuicConsumedData consumed = - generator_.ConsumeData(3, CreateData(data_len), 0, true); + generator_.ConsumeData(3, CreateData(data_len), 0, true, NULL); EXPECT_EQ(data_len, consumed.bytes_consumed); EXPECT_TRUE(consumed.fin_consumed); generator_.AddControlFrame(QuicFrame(CreateGoAwayFrame())); diff --git a/net/quic/quic_protocol.cc b/net/quic/quic_protocol.cc index cbd4cad639..cdf3c6ce4d 100644 --- a/net/quic/quic_protocol.cc +++ b/net/quic/quic_protocol.cc @@ -121,8 +121,6 @@ QuicVersion QuicVersionMin() { QuicTag QuicVersionToQuicTag(const QuicVersion version) { switch (version) { - case QUIC_VERSION_7: - return MakeQuicTag('Q', '0', '0', '7'); case QUIC_VERSION_8: return MakeQuicTag('Q', '0', '0', '8'); case QUIC_VERSION_9: @@ -138,14 +136,11 @@ QuicTag QuicVersionToQuicTag(const QuicVersion version) { } QuicVersion QuicTagToQuicVersion(const QuicTag version_tag) { - const QuicTag quic_tag_v7 = MakeQuicTag('Q', '0', '0', '7'); const QuicTag quic_tag_v8 = MakeQuicTag('Q', '0', '0', '8'); const QuicTag quic_tag_v9 = MakeQuicTag('Q', '0', '0', '9'); const QuicTag quic_tag_v10 = MakeQuicTag('Q', '0', '1', '0'); - if (version_tag == quic_tag_v7) { - return QUIC_VERSION_7; - } else if (version_tag == quic_tag_v8) { + if (version_tag == quic_tag_v8) { return QUIC_VERSION_8; } else if (version_tag == quic_tag_v9) { return QUIC_VERSION_9; @@ -165,7 +160,6 @@ return #x string QuicVersionToString(const QuicVersion version) { switch (version) { - RETURN_STRING_LITERAL(QUIC_VERSION_7); RETURN_STRING_LITERAL(QUIC_VERSION_8); RETURN_STRING_LITERAL(QUIC_VERSION_9); RETURN_STRING_LITERAL(QUIC_VERSION_10); diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h index 75f6f58016..26e6c02768 100644 --- a/net/quic/quic_protocol.h +++ b/net/quic/quic_protocol.h @@ -189,7 +189,6 @@ enum QuicVersion { // Special case to indicate unknown/unsupported QUIC version. QUIC_VERSION_UNSUPPORTED = 0, - QUIC_VERSION_7 = 7, QUIC_VERSION_8 = 8, QUIC_VERSION_9 = 9, QUIC_VERSION_10 = 10, // Current version. @@ -270,8 +269,8 @@ NET_EXPORT_PRIVATE size_t GetStartOfEncryptedData( enum QuicRstStreamErrorCode { QUIC_STREAM_NO_ERROR = 0, - // There was some server error which halted stream processing. - QUIC_SERVER_ERROR_PROCESSING_STREAM, + // There was some error which halted stream processing. + QUIC_ERROR_PROCESSING_STREAM, // We got two fin or reset offsets which did not match. QUIC_MULTIPLE_TERMINATION_OFFSETS, // We got bad payload and can not respond to it at the protocol level. @@ -334,6 +333,8 @@ enum QuicErrorCode { QUIC_PEER_GOING_AWAY = 16, // A stream ID was invalid. QUIC_INVALID_STREAM_ID = 17, + // A priority was invalid. + QUIC_INVALID_PRIORITY = 49, // Too many streams already open. QUIC_TOO_MANY_OPEN_STREAMS = 18, // Received public reset for this connection. @@ -356,6 +357,8 @@ enum QuicErrorCode { QUIC_PACKET_WRITE_ERROR = 27, // There was an error while reading from the socket. QUIC_PACKET_READ_ERROR = 51, + // We received a STREAM_FRAME with no data and no fin flag set. + QUIC_INVALID_STREAM_FRAME = 50, // Crypto errors. diff --git a/net/quic/quic_protocol_test.cc b/net/quic/quic_protocol_test.cc index 4144aeeb9a..52ed6645c9 100644 --- a/net/quic/quic_protocol_test.cc +++ b/net/quic/quic_protocol_test.cc @@ -56,8 +56,8 @@ TEST(QuicProtocolTest, QuicVersionToQuicTag) { #endif // Explicitly test a specific version. - EXPECT_EQ(MakeQuicTag('Q', '0', '0', '7'), - QuicVersionToQuicTag(QUIC_VERSION_7)); + EXPECT_EQ(MakeQuicTag('Q', '0', '1', '0'), + QuicVersionToQuicTag(QUIC_VERSION_10)); // Loop over all supported versions and make sure that we never hit the // default case (i.e. all supported versions should be successfully converted @@ -95,8 +95,8 @@ TEST(QuicProtocolTest, QuicTagToQuicVersion) { #endif // Explicitly test specific versions. - EXPECT_EQ(QUIC_VERSION_7, - QuicTagToQuicVersion(MakeQuicTag('Q', '0', '0', '7'))); + EXPECT_EQ(QUIC_VERSION_10, + QuicTagToQuicVersion(MakeQuicTag('Q', '0', '1', '0'))); for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) { const QuicVersion& version = kSupportedQuicVersions[i]; @@ -127,17 +127,17 @@ TEST(QuicProtocolTest, QuicTagToQuicVersionUnsupported) { } TEST(QuicProtocolTest, QuicVersionToString) { - EXPECT_EQ("QUIC_VERSION_7", - QuicVersionToString(QUIC_VERSION_7)); + EXPECT_EQ("QUIC_VERSION_8", + QuicVersionToString(QUIC_VERSION_8)); EXPECT_EQ("QUIC_VERSION_UNSUPPORTED", QuicVersionToString(QUIC_VERSION_UNSUPPORTED)); - QuicVersion single_version[] = {QUIC_VERSION_7}; - EXPECT_EQ("QUIC_VERSION_7,", QuicVersionArrayToString( + QuicVersion single_version[] = {QUIC_VERSION_8}; + EXPECT_EQ("QUIC_VERSION_8,", QuicVersionArrayToString( single_version, arraysize(single_version))); QuicVersion multiple_versions[] = - {QUIC_VERSION_9, QUIC_VERSION_8, QUIC_VERSION_7}; - EXPECT_EQ("QUIC_VERSION_9,QUIC_VERSION_8,QUIC_VERSION_7,", + {QUIC_VERSION_10, QUIC_VERSION_9, QUIC_VERSION_8}; + EXPECT_EQ("QUIC_VERSION_10,QUIC_VERSION_9,QUIC_VERSION_8,", QuicVersionArrayToString(multiple_versions, arraysize(multiple_versions))); } diff --git a/net/quic/quic_reliable_client_stream.cc b/net/quic/quic_reliable_client_stream.cc index 26e6815bb1..06b3178cda 100644 --- a/net/quic/quic_reliable_client_stream.cc +++ b/net/quic/quic_reliable_client_stream.cc @@ -7,6 +7,7 @@ #include "base/callback_helpers.h" #include "net/base/net_errors.h" #include "net/quic/quic_session.h" +#include "net/spdy/write_blocked_list.h" namespace net { @@ -54,6 +55,13 @@ void QuicReliableClientStream::OnCanWrite() { } } +QuicPriority QuicReliableClientStream::EffectivePriority() const { + if (delegate_ && delegate_->HasSendHeadersComplete()) { + return ReliableQuicStream::EffectivePriority(); + } + return kHighestPriority; +} + int QuicReliableClientStream::WriteStreamData( base::StringPiece data, bool fin, diff --git a/net/quic/quic_reliable_client_stream.h b/net/quic/quic_reliable_client_stream.h index c482ee67fd..bf3fc158d4 100644 --- a/net/quic/quic_reliable_client_stream.h +++ b/net/quic/quic_reliable_client_stream.h @@ -48,6 +48,9 @@ class NET_EXPORT_PRIVATE QuicReliableClientStream : public ReliableQuicStream { // Called when the stream is closed because of an error. virtual void OnError(int error) = 0; + // Returns true if sending of headers has completed. + virtual bool HasSendHeadersComplete() = 0; + protected: virtual ~Delegate() {} @@ -65,6 +68,11 @@ class NET_EXPORT_PRIVATE QuicReliableClientStream : public ReliableQuicStream { virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE; virtual void TerminateFromPeer(bool half_close) OVERRIDE; virtual void OnCanWrite() OVERRIDE; + virtual QuicPriority EffectivePriority() const OVERRIDE; + + // While the server's set_priority shouldn't be called externally, the creator + // of client-side streams should be able to set the priority. + using ReliableQuicStream::set_priority; int WriteStreamData(base::StringPiece data, bool fin, diff --git a/net/quic/quic_reliable_client_stream_test.cc b/net/quic/quic_reliable_client_stream_test.cc index 504159159c..aaebda27fe 100644 --- a/net/quic/quic_reliable_client_stream_test.cc +++ b/net/quic/quic_reliable_client_stream_test.cc @@ -28,6 +28,7 @@ class MockDelegate : public QuicReliableClientStream::Delegate { MOCK_METHOD2(OnDataReceived, int(const char*, int)); MOCK_METHOD1(OnClose, void(QuicErrorCode)); MOCK_METHOD1(OnError, void(int)); + MOCK_METHOD0(HasSendHeadersComplete, bool()); private: DISALLOW_COPY_AND_ASSIGN(MockDelegate); @@ -86,7 +87,7 @@ TEST_F(QuicReliableClientStreamTest, WriteStreamData) { const size_t kDataLen = arraysize(kData1); // All data written. - EXPECT_CALL(session_, WriteData(stream_.id(), _, _, _)).WillOnce( + EXPECT_CALL(session_, WritevData(stream_.id(), _, _, _, _)).WillOnce( Return(QuicConsumedData(kDataLen, true))); TestCompletionCallback callback; EXPECT_EQ(OK, stream_.WriteStreamData(base::StringPiece(kData1, kDataLen), @@ -94,13 +95,14 @@ TEST_F(QuicReliableClientStreamTest, WriteStreamData) { } TEST_F(QuicReliableClientStreamTest, WriteStreamDataAsync) { + EXPECT_CALL(delegate_, HasSendHeadersComplete()); EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR)); const char kData1[] = "hello world"; const size_t kDataLen = arraysize(kData1); // No data written. - EXPECT_CALL(session_, WriteData(stream_.id(), _, _, _)).WillOnce( + EXPECT_CALL(session_, WritevData(stream_.id(), _, _, _, _)).WillOnce( Return(QuicConsumedData(0, false))); TestCompletionCallback callback; EXPECT_EQ(ERR_IO_PENDING, @@ -109,7 +111,7 @@ TEST_F(QuicReliableClientStreamTest, WriteStreamDataAsync) { ASSERT_FALSE(callback.have_result()); // All data written. - EXPECT_CALL(session_, WriteData(stream_.id(), _, _, _)).WillOnce( + EXPECT_CALL(session_, WritevData(stream_.id(), _, _, _, _)).WillOnce( Return(QuicConsumedData(kDataLen, true))); stream_.OnCanWrite(); ASSERT_TRUE(callback.have_result()); diff --git a/net/quic/quic_sent_packet_manager.cc b/net/quic/quic_sent_packet_manager.cc new file mode 100644 index 0000000000..a8960f07aa --- /dev/null +++ b/net/quic/quic_sent_packet_manager.cc @@ -0,0 +1,221 @@ +// Copyright 2013 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_sent_packet_manager.h" + +#include "base/logging.h" +#include "base/stl_util.h" + +using std::make_pair; + +namespace net { + +#define ENDPOINT (is_server_ ? "Server: " : " Client: ") + +QuicSentPacketManager::HelperInterface::~HelperInterface() { +} + +QuicSentPacketManager::QuicSentPacketManager(bool is_server, + HelperInterface* helper) + : is_server_(is_server), + helper_(helper) { +} + +QuicSentPacketManager::~QuicSentPacketManager() { + STLDeleteValues(&unacked_packets_); +} + +void QuicSentPacketManager::OnSerializedPacket( + const SerializedPacket& serialized_packet) { + if (serialized_packet.packet->is_fec_packet()) { + unacked_fec_packets_.insert(make_pair( + serialized_packet.sequence_number, + serialized_packet.retransmittable_frames)); + return; + } + + if (serialized_packet.retransmittable_frames == NULL) { + // Don't track ack/congestion feedback packets. + return; + } + + DCHECK(unacked_packets_.empty() || + unacked_packets_.rbegin()->first < + serialized_packet.sequence_number); + unacked_packets_[serialized_packet.sequence_number] = + serialized_packet.retransmittable_frames; + retransmission_map_[serialized_packet.sequence_number] = + RetransmissionInfo(serialized_packet.sequence_number, + serialized_packet.sequence_number_length); +} + +void QuicSentPacketManager::OnRetransmittedPacket( + QuicPacketSequenceNumber old_sequence_number, + QuicPacketSequenceNumber new_sequence_number) { + DCHECK(ContainsKey(unacked_packets_, old_sequence_number)); + DCHECK(ContainsKey(retransmission_map_, old_sequence_number)); + DCHECK(unacked_packets_.empty() || + unacked_packets_.rbegin()->first < new_sequence_number); + + RetransmissionInfo retransmission_info( + new_sequence_number, GetSequenceNumberLength(old_sequence_number)); + retransmission_info.number_retransmissions = + retransmission_map_[old_sequence_number].number_retransmissions + 1; + retransmission_map_.erase(old_sequence_number); + retransmission_map_[new_sequence_number] = retransmission_info; + + RetransmittableFrames* frames = unacked_packets_[old_sequence_number]; + DCHECK(frames); + unacked_packets_.erase(old_sequence_number); + unacked_packets_[new_sequence_number] = frames; +} + +void QuicSentPacketManager::HandleAckForSentPackets( + const QuicAckFrame& incoming_ack, + SequenceNumberSet* acked_packets) { + // Go through the packets we have not received an ack for and see if this + // incoming_ack shows they've been seen by the peer. + UnackedPacketMap::iterator it = unacked_packets_.begin(); + while (it != unacked_packets_.end()) { + QuicPacketSequenceNumber sequence_number = it->first; + if (sequence_number > helper_->GetPeerLargestObservedPacket()) { + // These are very new sequence_numbers. + break; + } + RetransmittableFrames* unacked = it->second; + if (!IsAwaitingPacket(incoming_ack.received_info, sequence_number)) { + // Packet was acked, so remove it from our unacked packet list. + DVLOG(1) << ENDPOINT <<"Got an ack for packet " << sequence_number; + acked_packets->insert(sequence_number); + delete unacked; + unacked_packets_.erase(it++); + retransmission_map_.erase(sequence_number); + } else { + // This is a packet which we planned on retransmitting and has not been + // seen at the time of this ack being sent out. See if it's our new + // lowest unacked packet. + DVLOG(1) << ENDPOINT << "still missing packet " << sequence_number; + ++it; + // The peer got packets after this sequence number. This is an explicit + // nack. + RetransmissionMap::iterator retransmission_it = + retransmission_map_.find(sequence_number); + if (retransmission_it == retransmission_map_.end()) { + continue; + } + size_t nack_count = ++(retransmission_it->second.number_nacks); + helper_->OnPacketNacked(sequence_number, nack_count); + } + } +} + +void QuicSentPacketManager::HandleAckForSentFecPackets( + const QuicAckFrame& incoming_ack, + SequenceNumberSet* acked_packets) { + UnackedPacketMap::iterator it = unacked_fec_packets_.begin(); + while (it != unacked_fec_packets_.end()) { + QuicPacketSequenceNumber sequence_number = it->first; + if (sequence_number > helper_->GetPeerLargestObservedPacket()) { + break; + } + if (!IsAwaitingPacket(incoming_ack.received_info, sequence_number)) { + DVLOG(1) << ENDPOINT << "Got an ack for fec packet: " << sequence_number; + acked_packets->insert(sequence_number); + unacked_fec_packets_.erase(it++); + } else { + DVLOG(1) << ENDPOINT << "Still missing ack for fec packet: " + << sequence_number; + ++it; + } + } +} + +void QuicSentPacketManager::DiscardPacket( + QuicPacketSequenceNumber sequence_number) { + UnackedPacketMap::iterator unacked_it = + unacked_packets_.find(sequence_number); + if (unacked_it == unacked_packets_.end()) { + // Packet was not meant to be retransmitted. + DCHECK(!ContainsKey(retransmission_map_, sequence_number)); + return; + } + + // Delete the unacked packet. + delete unacked_it->second; + unacked_packets_.erase(unacked_it); + retransmission_map_.erase(sequence_number); +} + +bool QuicSentPacketManager::IsRetransmission( + QuicPacketSequenceNumber sequence_number) const { + RetransmissionMap::const_iterator it = + retransmission_map_.find(sequence_number); + return it != retransmission_map_.end() && + it->second.number_retransmissions > 0; +} + +size_t QuicSentPacketManager::GetRetransmissionCount( + QuicPacketSequenceNumber sequence_number) const { + DCHECK(ContainsKey(retransmission_map_, sequence_number)); + RetransmissionMap::const_iterator it = + retransmission_map_.find(sequence_number); + return it->second.number_retransmissions; +} + +bool QuicSentPacketManager::IsUnacked( + QuicPacketSequenceNumber sequence_number) const { + return ContainsKey(unacked_packets_, sequence_number); +} + +bool QuicSentPacketManager::IsFecUnacked( + QuicPacketSequenceNumber sequence_number) const { + return ContainsKey(unacked_fec_packets_, sequence_number); +} + +const RetransmittableFrames& QuicSentPacketManager::GetRetransmittableFrames( + QuicPacketSequenceNumber sequence_number) const { + DCHECK(ContainsKey(unacked_packets_, sequence_number)); + DCHECK(ContainsKey(retransmission_map_, sequence_number)); + + return *unacked_packets_.find(sequence_number)->second; +} + +QuicSequenceNumberLength QuicSentPacketManager::GetSequenceNumberLength( + QuicPacketSequenceNumber sequence_number) const { + DCHECK(ContainsKey(unacked_packets_, sequence_number)); + DCHECK(ContainsKey(retransmission_map_, sequence_number)); + + return retransmission_map_.find( + sequence_number)->second.sequence_number_length; +} + +bool QuicSentPacketManager::HasUnackedPackets() const { + return !unacked_packets_.empty(); +} + +size_t QuicSentPacketManager::GetNumUnackedPackets() const { + return unacked_packets_.size(); +} + +QuicPacketSequenceNumber +QuicSentPacketManager::GetLeastUnackedSentPacket() const { + if (unacked_packets_.empty()) { + // If there are no unacked packets, set the least unacked packet to + // the sequence number of the next packet sent. + return helper_->GetNextPacketSequenceNumber(); + } + + return unacked_packets_.begin()->first; +} + +SequenceNumberSet QuicSentPacketManager::GetUnackedPackets() const { + SequenceNumberSet unacked_packets; + for (UnackedPacketMap::const_iterator it = unacked_packets_.begin(); + it != unacked_packets_.end(); ++it) { + unacked_packets.insert(it->first); + } + return unacked_packets; +} + +} // namespace net diff --git a/net/quic/quic_sent_packet_manager.h b/net/quic/quic_sent_packet_manager.h new file mode 100644 index 0000000000..355ea498b6 --- /dev/null +++ b/net/quic/quic_sent_packet_manager.h @@ -0,0 +1,140 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_QUIC_QUIC_SENT_PACKET_MANAGER_H_ +#define NET_QUIC_QUIC_SENT_PACKET_MANAGER_H_ + +#include <deque> +#include <list> +#include <map> +#include <queue> +#include <set> +#include <utility> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "net/base/linked_hash_map.h" +#include "net/quic/quic_protocol.h" + +namespace net { + +class NET_EXPORT_PRIVATE QuicSentPacketManager { + public: + class NET_EXPORT_PRIVATE HelperInterface { + public: + virtual QuicPacketSequenceNumber GetPeerLargestObservedPacket() = 0; + virtual QuicPacketSequenceNumber GetNextPacketSequenceNumber() = 0; + + // Called when a packet has been explicitly NACKd + virtual void OnPacketNacked(QuicPacketSequenceNumber sequence_number, + size_t nack_count) = 0; + virtual ~HelperInterface(); + }; + + QuicSentPacketManager(bool is_server, HelperInterface* helper); + virtual ~QuicSentPacketManager(); + + // Called when a new packet is serialized. If the packet contains + // retransmittable data, it will be added to the unacked packet map. + void OnSerializedPacket(const SerializedPacket& serialized_packet); + + // Called when a packet is retransmitted with a new sequence number. + // Replaces the old entry in the unacked packet map with the new + // sequence number. + void OnRetransmittedPacket(QuicPacketSequenceNumber old_sequence_number, + QuicPacketSequenceNumber new_sequence_number); + + // Process the incoming ack looking for newly ack'd data packets. + void HandleAckForSentPackets(const QuicAckFrame& incoming_ack, + SequenceNumberSet* acked_packets); + + // Process the incoming ack looking for newly ack'd FEC packets. + void HandleAckForSentFecPackets(const QuicAckFrame& incoming_ack, + SequenceNumberSet* acked_packets); + + // Discards all information about packet |sequence_number|. + void DiscardPacket(QuicPacketSequenceNumber sequence_number); + + // Returns true if |sequence_number| is a retransmission of a packet. + bool IsRetransmission(QuicPacketSequenceNumber sequence_number) const; + + // Returns the number of times the data in the packet |sequence_number| + // has been transmitted. + size_t GetRetransmissionCount( + QuicPacketSequenceNumber sequence_number) const; + + // Returns true if the non-FEC packet |sequence_number| is unacked. + bool IsUnacked(QuicPacketSequenceNumber sequence_number) const; + + // Returns true if the FEC packet |sequence_number| is unacked. + bool IsFecUnacked(QuicPacketSequenceNumber sequence_number) const; + + // Returns the RetransmittableFrames for |sequence_number|. + const RetransmittableFrames& GetRetransmittableFrames( + QuicPacketSequenceNumber sequence_number) const; + + // Returns the length of the serialized sequence number for + // the packet |sequence_number|. + QuicSequenceNumberLength GetSequenceNumberLength( + QuicPacketSequenceNumber sequence_number) const; + + // Returns true if there are any unacked packets. + bool HasUnackedPackets() const; + + // Returns the number of unacked packets. + size_t GetNumUnackedPackets() const; + + // Returns the smallest sequence number of a sent packet which has not + // been acked by the peer. If all packets have been acked, returns the + // sequence number of the next packet that will be sent. + QuicPacketSequenceNumber GetLeastUnackedSentPacket() const; + + // Returns the set of unacked packet sequence numbers. + SequenceNumberSet GetUnackedPackets() const; + + private: + struct RetransmissionInfo { + RetransmissionInfo() {} + explicit RetransmissionInfo(QuicPacketSequenceNumber sequence_number, + QuicSequenceNumberLength sequence_number_length) + : sequence_number(sequence_number), + sequence_number_length(sequence_number_length), + number_nacks(0), + number_retransmissions(0) { + } + + QuicPacketSequenceNumber sequence_number; + QuicSequenceNumberLength sequence_number_length; + size_t number_nacks; + size_t number_retransmissions; + }; + + typedef linked_hash_map<QuicPacketSequenceNumber, + RetransmittableFrames*> UnackedPacketMap; + typedef base::hash_map<QuicPacketSequenceNumber, + RetransmissionInfo> RetransmissionMap; + + // When new packets are created which may be retransmitted, they are added + // to this map, which contains owning pointers to the contained frames. + UnackedPacketMap unacked_packets_; + + // Pending fec packets that have not been acked yet. These packets need to be + // cleared out of the cgst_window after a timeout since FEC packets are never + // retransmitted. + // TODO(satyamshekhar): What should be the timeout for these packets? + UnackedPacketMap unacked_fec_packets_; + + // Map from sequence number to the retransmission info. + RetransmissionMap retransmission_map_; + + // Tracks if the connection was created by the server. + bool is_server_; + HelperInterface* helper_; + + DISALLOW_COPY_AND_ASSIGN(QuicSentPacketManager); +}; + +} // namespace net + +#endif // NET_QUIC_QUIC_SENT_PACKET_MANAGER_H_ diff --git a/net/quic/quic_sent_packet_manager_test.cc b/net/quic/quic_sent_packet_manager_test.cc new file mode 100644 index 0000000000..8f5cb3d19d --- /dev/null +++ b/net/quic/quic_sent_packet_manager_test.cc @@ -0,0 +1,80 @@ +// Copyright 2013 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_sent_packet_manager.h" + +#include "net/quic/test_tools/quic_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Return; +using testing::StrictMock; + +namespace net { +namespace test { +namespace { + +class MockHelper : public QuicSentPacketManager::HelperInterface { + public: + MOCK_METHOD0(GetPeerLargestObservedPacket, QuicPacketSequenceNumber()); + MOCK_METHOD0(GetNextPacketSequenceNumber, QuicPacketSequenceNumber()); + MOCK_METHOD2(OnPacketNacked, void(QuicPacketSequenceNumber sequence_number, + size_t nack_count)); +}; + +class QuicSentPacketManagerTest : public ::testing::Test { + protected: + QuicSentPacketManagerTest() + : manager_(true, &helper_) { + } + + testing::StrictMock<MockHelper> helper_; + QuicSentPacketManager manager_; +}; + +TEST_F(QuicSentPacketManagerTest, GetLeastUnackedSentPacket) { + EXPECT_CALL(helper_, GetNextPacketSequenceNumber()).WillOnce(Return(1u)); + EXPECT_EQ(1u, manager_.GetLeastUnackedSentPacket()); +} + +TEST_F(QuicSentPacketManagerTest, GetLeastUnackedSentPacketUnacked) { + scoped_ptr<QuicPacket> packet(QuicPacket::NewDataPacket( + NULL, 0, false, PACKET_8BYTE_GUID, false, PACKET_6BYTE_SEQUENCE_NUMBER)); + SerializedPacket serialized_packet(1u, PACKET_6BYTE_SEQUENCE_NUMBER, + packet.get(), 7u, + new RetransmittableFrames()); + + manager_.OnSerializedPacket(serialized_packet); + EXPECT_EQ(1u, manager_.GetLeastUnackedSentPacket()); +} + +TEST_F(QuicSentPacketManagerTest, GetLeastUnackedSentPacketUnackedFec) { + scoped_ptr<QuicPacket> packet(QuicPacket::NewFecPacket( + NULL, 0, false, PACKET_8BYTE_GUID, false, PACKET_6BYTE_SEQUENCE_NUMBER)); + SerializedPacket serialized_packet(1u, PACKET_6BYTE_SEQUENCE_NUMBER, + packet.get(), 7u, NULL); + + manager_.OnSerializedPacket(serialized_packet); + // FEC packets do not count as "unacked". + EXPECT_CALL(helper_, GetNextPacketSequenceNumber()).WillOnce(Return(2u)); + EXPECT_EQ(2u, manager_.GetLeastUnackedSentPacket()); +} + +TEST_F(QuicSentPacketManagerTest, GetLeastUnackedSentPacketDiscardUnacked) { + scoped_ptr<QuicPacket> packet(QuicPacket::NewDataPacket( + NULL, 0, false, PACKET_8BYTE_GUID, false, PACKET_6BYTE_SEQUENCE_NUMBER)); + SerializedPacket serialized_packet(1u, PACKET_6BYTE_SEQUENCE_NUMBER, + packet.get(), 7u, + new RetransmittableFrames()); + + manager_.OnSerializedPacket(serialized_packet); + manager_.DiscardPacket(1u); + EXPECT_CALL(helper_, GetNextPacketSequenceNumber()).WillOnce(Return(2u)); + EXPECT_EQ(2u, manager_.GetLeastUnackedSentPacket()); +} + +} // namespace +} // namespace test +} // namespace net diff --git a/net/quic/quic_session.cc b/net/quic/quic_session.cc index 5b7dc388f7..9389af403c 100644 --- a/net/quic/quic_session.cc +++ b/net/quic/quic_session.cc @@ -33,12 +33,8 @@ class VisitorShim : public QuicConnectionVisitorInterface { public: explicit VisitorShim(QuicSession* session) : session_(session) {} - virtual bool OnPacket(const IPEndPoint& self_address, - const IPEndPoint& peer_address, - const QuicPacketHeader& header, - const vector<QuicStreamFrame>& frame) OVERRIDE { - bool accepted = session_->OnPacket(self_address, peer_address, header, - frame); + virtual bool OnStreamFrames(const vector<QuicStreamFrame>& frames) OVERRIDE { + bool accepted = session_->OnStreamFrames(frames); session_->PostProcessAfterData(); return accepted; } @@ -52,22 +48,26 @@ class VisitorShim : public QuicConnectionVisitorInterface { session_->PostProcessAfterData(); } - virtual void OnAck(const SequenceNumberSet& acked_packets) OVERRIDE { - session_->OnAck(acked_packets); - session_->PostProcessAfterData(); - } - virtual bool OnCanWrite() OVERRIDE { bool rc = session_->OnCanWrite(); session_->PostProcessAfterData(); return rc; } + virtual void OnSuccessfulVersionNegotiation( + const QuicVersion& version) OVERRIDE { + session_->OnSuccessfulVersionNegotiation(version); + } + virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE { session_->ConnectionClose(error, from_peer); // The session will go away, so don't bother with cleanup. } + virtual bool HasPendingHandshake() const OVERRIDE { + return session_->HasPendingHandshake(); + } + private: QuicSession* session_; }; @@ -84,7 +84,8 @@ QuicSession::QuicSession(QuicConnection* connection, largest_peer_created_stream_id_(0), error_(QUIC_NO_ERROR), goaway_received_(false), - goaway_sent_(false) { + goaway_sent_(false), + has_pending_handshake_(false) { connection_->set_visitor(visitor_shim_.get()); connection_->SetIdleNetworkTimeout(config_.idle_connection_state_lifetime()); @@ -100,16 +101,7 @@ QuicSession::~QuicSession() { STLDeleteValues(&stream_map_); } -bool QuicSession::OnPacket(const IPEndPoint& self_address, - const IPEndPoint& peer_address, - const QuicPacketHeader& header, - const vector<QuicStreamFrame>& frames) { - if (header.public_header.guid != connection()->guid()) { - DLOG(INFO) << ENDPOINT << "Got packet header for invalid GUID: " - << header.public_header.guid; - return false; - } - +bool QuicSession::OnStreamFrames(const vector<QuicStreamFrame>& frames) { for (size_t i = 0; i < frames.size(); ++i) { // TODO(rch) deal with the error case of stream id 0 if (IsClosedStream(frames[i].stream_id)) { @@ -217,11 +209,17 @@ bool QuicSession::OnCanWrite() { while (!connection_->HasQueuedData() && remaining_writes > 0) { DCHECK(write_blocked_streams_.HasWriteBlockedStreams()); - ReliableQuicStream* stream = NULL; int index = write_blocked_streams_.GetHighestPriorityWriteBlockedList(); - if (index != -1) { - stream = GetStream(write_blocked_streams_.PopFront(index)); + if (index == -1) { + LOG(DFATAL) << "WriteBlockedStream is missing"; + connection_->CloseConnection(QUIC_INTERNAL_ERROR, false); + return true; // We have no write blocked streams. } + QuicStreamId stream_id = write_blocked_streams_.PopFront(index); + if (stream_id == kCryptoStreamId) { + has_pending_handshake_ = false; // We just popped it. + } + ReliableQuicStream* stream = GetStream(stream_id); if (stream != NULL) { // If the stream can't write all bytes, it'll re-add itself to the blocked // list. @@ -233,11 +231,16 @@ bool QuicSession::OnCanWrite() { return !write_blocked_streams_.HasWriteBlockedStreams(); } -QuicConsumedData QuicSession::WriteData(QuicStreamId id, - StringPiece data, - QuicStreamOffset offset, - bool fin) { - return connection_->SendStreamData(id, data, offset, fin); +bool QuicSession::HasPendingHandshake() const { + return has_pending_handshake_; +} + +QuicConsumedData QuicSession::WritevData(QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin) { + return connection_->SendvStreamData(id, iov, iov_count, offset, fin); } void QuicSession::SendRstStream(QuicStreamId id, @@ -375,7 +378,7 @@ QuicConfig* QuicSession::config() { void QuicSession::ActivateStream(ReliableQuicStream* stream) { DLOG(INFO) << ENDPOINT << "num_streams: " << stream_map_.size() << ". activating " << stream->id(); - DCHECK(stream_map_.count(stream->id()) == 0); + DCHECK_EQ(stream_map_.count(stream->id()), 0u); stream_map_[stream->id()] = stream; } @@ -474,8 +477,16 @@ size_t QuicSession::GetNumOpenStreams() const { zombie_streams_.size(); } -void QuicSession::MarkWriteBlocked(QuicStreamId id) { - write_blocked_streams_.PushBack(id, 0); +void QuicSession::MarkWriteBlocked(QuicStreamId id, QuicPriority priority) { + if (id == kCryptoStreamId) { + DCHECK(!has_pending_handshake_); + has_pending_handshake_ = true; + // TODO(jar): Be sure to use the highest priority for the crypto stream, + // perhaps by adding a "special" priority for it that is higher than + // kHighestPriority. + priority = kHighestPriority; + } + write_blocked_streams_.PushBack(id, priority); } void QuicSession::MarkDecompressionBlocked(QuicHeaderId header_id, diff --git a/net/quic/quic_session.h b/net/quic/quic_session.h index c37b6e11c3..b58feb2742 100644 --- a/net/quic/quic_session.h +++ b/net/quic/quic_session.h @@ -59,26 +59,28 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface { virtual ~QuicSession(); // QuicConnectionVisitorInterface methods: - virtual bool OnPacket(const IPEndPoint& self_address, - const IPEndPoint& peer_address, - const QuicPacketHeader& header, - const std::vector<QuicStreamFrame>& frame) OVERRIDE; + virtual bool OnStreamFrames( + const std::vector<QuicStreamFrame>& frames) OVERRIDE; virtual void OnRstStream(const QuicRstStreamFrame& frame) OVERRIDE; virtual void OnGoAway(const QuicGoAwayFrame& frame) OVERRIDE; virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE; + virtual void OnSuccessfulVersionNegotiation( + const QuicVersion& version) OVERRIDE{} // Not needed for HTTP. - virtual void OnAck(const SequenceNumberSet& acked_packets) OVERRIDE {} virtual bool OnCanWrite() OVERRIDE; + virtual bool HasPendingHandshake() const OVERRIDE; // Called by streams when they want to write data to the peer. // Returns a pair with the number of bytes consumed from data, and a boolean // indicating if the fin bit was consumed. This does not indicate the data // has been sent on the wire: it may have been turned into a packet and queued // if the socket was unexpectedly blocked. - virtual QuicConsumedData WriteData(QuicStreamId id, - base::StringPiece data, - QuicStreamOffset offset, - bool fin); + virtual QuicConsumedData WritevData(QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin); + // Called by streams when they want to close the stream in both directions. virtual void SendRstStream(QuicStreamId id, QuicRstStreamErrorCode error); @@ -137,7 +139,7 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface { // been implicitly created. virtual size_t GetNumOpenStreams() const; - void MarkWriteBlocked(QuicStreamId id); + void MarkWriteBlocked(QuicStreamId id, QuicPriority priority); // Marks that |stream_id| is blocked waiting to decompress the // headers identified by |decompression_id|. @@ -282,6 +284,9 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface { // Whether a GoAway has been sent. bool goaway_sent_; + // Indicate if there is pending data for the crypto stream. + bool has_pending_handshake_; + DISALLOW_COPY_AND_ASSIGN(QuicSession); }; diff --git a/net/quic/quic_session_test.cc b/net/quic/quic_session_test.cc index b7953aee11..e90b20f81c 100644 --- a/net/quic/quic_session_test.cc +++ b/net/quic/quic_session_test.cc @@ -23,12 +23,15 @@ using std::set; using std::vector; using testing::_; using testing::InSequence; +using testing::InvokeWithoutArgs; using testing::StrictMock; namespace net { namespace test { namespace { +const QuicPriority kSomeMiddlePriority = 2; + class TestCryptoStream : public QuicCryptoStream { public: explicit TestCryptoStream(QuicSession* session) @@ -47,6 +50,8 @@ class TestCryptoStream : public QuicCryptoStream { EXPECT_EQ(QUIC_NO_ERROR, error); session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED); } + + MOCK_METHOD0(OnCanWrite, void()); }; class TestStream : public ReliableQuicStream { @@ -64,6 +69,23 @@ class TestStream : public ReliableQuicStream { MOCK_METHOD0(OnCanWrite, void()); }; +// Poor man's functor for use as callback in a mock. +class StreamBlocker { + public: + StreamBlocker(QuicSession* session, QuicStreamId stream_id) + : session_(session), + stream_id_(stream_id) { + } + + void MarkWriteBlocked() { + session_->MarkWriteBlocked(stream_id_, kSomeMiddlePriority); + } + + private: + QuicSession* const session_; + const QuicStreamId stream_id_; +}; + class TestSession : public QuicSession { public: TestSession(QuicConnection* connection, bool is_server) @@ -71,7 +93,7 @@ class TestSession : public QuicSession { crypto_stream_(this) { } - virtual QuicCryptoStream* GetCryptoStream() OVERRIDE { + virtual TestCryptoStream* GetCryptoStream() OVERRIDE { return &crypto_stream_; } @@ -93,11 +115,6 @@ class TestSession : public QuicSession { return QuicSession::GetIncomingReliableStream(stream_id); } - // Helper method for gmock - void MarkTwoWriteBlocked() { - this->MarkWriteBlocked(2); - } - TestCryptoStream crypto_stream_; }; @@ -240,29 +257,77 @@ TEST_F(QuicSessionTest, OnCanWrite) { TestStream* stream4 = session_.CreateOutgoingReliableStream(); TestStream* stream6 = session_.CreateOutgoingReliableStream(); - session_.MarkWriteBlocked(2); - session_.MarkWriteBlocked(6); - session_.MarkWriteBlocked(4); + session_.MarkWriteBlocked(stream2->id(), kSomeMiddlePriority); + session_.MarkWriteBlocked(stream6->id(), kSomeMiddlePriority); + session_.MarkWriteBlocked(stream4->id(), kSomeMiddlePriority); InSequence s; + StreamBlocker stream2_blocker(&session_, stream2->id()); EXPECT_CALL(*stream2, OnCanWrite()).WillOnce( // Reregister, to test the loop limit. - testing::InvokeWithoutArgs(&session_, &TestSession::MarkTwoWriteBlocked)); + InvokeWithoutArgs(&stream2_blocker, &StreamBlocker::MarkWriteBlocked)); EXPECT_CALL(*stream6, OnCanWrite()); EXPECT_CALL(*stream4, OnCanWrite()); EXPECT_FALSE(session_.OnCanWrite()); } +TEST_F(QuicSessionTest, BufferedHandshake) { + EXPECT_FALSE(session_.HasPendingHandshake()); // Default value. + + // Test that blocking other streams does not change our status. + TestStream* stream2 = session_.CreateOutgoingReliableStream(); + StreamBlocker stream2_blocker(&session_, stream2->id()); + stream2_blocker.MarkWriteBlocked(); + EXPECT_FALSE(session_.HasPendingHandshake()); + + TestStream* stream3 = session_.CreateOutgoingReliableStream(); + StreamBlocker stream3_blocker(&session_, stream3->id()); + stream3_blocker.MarkWriteBlocked(); + EXPECT_FALSE(session_.HasPendingHandshake()); + + // Blocking (due to buffering of) the Crypto stream is detected. + session_.MarkWriteBlocked(kCryptoStreamId, kSomeMiddlePriority); + EXPECT_TRUE(session_.HasPendingHandshake()); + + TestStream* stream4 = session_.CreateOutgoingReliableStream(); + StreamBlocker stream4_blocker(&session_, stream4->id()); + stream4_blocker.MarkWriteBlocked(); + EXPECT_TRUE(session_.HasPendingHandshake()); + + InSequence s; + // Force most streams to re-register, which is common scenario when we block + // the Crypto stream, and only the crypto stream can "really" write. + + // Due to prioritization, we *should* be asked to write the crypto stream + // first. + // Don't re-register the crypto stream (which signals complete writing). + TestCryptoStream* crypto_stream = session_.GetCryptoStream(); + EXPECT_CALL(*crypto_stream, OnCanWrite()); + + // Re-register all other streams, to show they weren't able to proceed. + EXPECT_CALL(*stream2, OnCanWrite()).WillOnce( + InvokeWithoutArgs(&stream2_blocker, &StreamBlocker::MarkWriteBlocked)); + + EXPECT_CALL(*stream3, OnCanWrite()).WillOnce( + InvokeWithoutArgs(&stream3_blocker, &StreamBlocker::MarkWriteBlocked)); + + EXPECT_CALL(*stream4, OnCanWrite()).WillOnce( + InvokeWithoutArgs(&stream4_blocker, &StreamBlocker::MarkWriteBlocked)); + + EXPECT_FALSE(session_.OnCanWrite()); + EXPECT_FALSE(session_.HasPendingHandshake()); // Crypto stream wrote. +} + TEST_F(QuicSessionTest, OnCanWriteWithClosedStream) { TestStream* stream2 = session_.CreateOutgoingReliableStream(); TestStream* stream4 = session_.CreateOutgoingReliableStream(); - session_.CreateOutgoingReliableStream(); // stream 6 + TestStream* stream6 = session_.CreateOutgoingReliableStream(); - session_.MarkWriteBlocked(2); - session_.MarkWriteBlocked(6); - session_.MarkWriteBlocked(4); - CloseStream(6); + session_.MarkWriteBlocked(stream2->id(), kSomeMiddlePriority); + session_.MarkWriteBlocked(stream6->id(), kSomeMiddlePriority); + session_.MarkWriteBlocked(stream4->id(), kSomeMiddlePriority); + CloseStream(stream6->id()); InSequence s; EXPECT_CALL(*stream2, OnCanWrite()); @@ -293,11 +358,11 @@ TEST_F(QuicSessionTest, OutOfOrderHeaders) { // Process the second frame first. This will cause the headers to // be queued up and processed after the first frame is processed. frames.push_back(frame2); - session_.OnPacket(IPEndPoint(), IPEndPoint(), header, frames); + session_.OnStreamFrames(frames); // Process the first frame, and un-cork the buffered headers. frames[0] = frame1; - session_.OnPacket(IPEndPoint(), IPEndPoint(), header, frames); + session_.OnStreamFrames(frames); // Ensure that the streams actually close and we don't DCHECK. connection_->CloseConnection(QUIC_CONNECTION_TIMED_OUT, true); @@ -356,7 +421,8 @@ TEST_F(QuicSessionTest, ZombieStream) { // be queued up and processed after the first frame is processed. frames.push_back(frame1); EXPECT_FALSE(stream3->headers_decompressed()); - session.OnPacket(IPEndPoint(), IPEndPoint(), header, frames); + + session.OnStreamFrames(frames); EXPECT_EQ(1u, session.GetNumOpenStreams()); EXPECT_TRUE(connection->connected()); diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc index e5319fb474..e4b4d99a9a 100644 --- a/net/quic/quic_stream_factory.cc +++ b/net/quic/quic_stream_factory.cc @@ -159,16 +159,6 @@ int QuicStreamFactory::Job::DoResolveHostComplete(int rv) { if (rv != OK) return rv; - // TODO(rch): remove this code! - AddressList::iterator it = address_list_.begin(); - while (it != address_list_.end()) { - if (it->GetFamily() == ADDRESS_FAMILY_IPV6) { - it = address_list_.erase(it); - } else { - it++; - } - } - DCHECK(!factory_->HasActiveSession(host_port_proxy_pair_)); io_state_ = STATE_CONNECT; return OK; @@ -307,8 +297,7 @@ int QuicStreamFactory::Create(const HostPortProxyPair& host_port_proxy_pair, void QuicStreamFactory::OnJobComplete(Job* job, int rv) { if (rv == OK) { - // TODO(rch): Uncomment this once we trust 0-RTT - // require_confirmation_ = false; + require_confirmation_ = false; // Create all the streams, but do not notify them yet. for (RequestSet::iterator it = job_requests_map_[job].begin(); @@ -437,18 +426,10 @@ QuicClientSession* QuicStreamFactory::CreateSession( // revisit this setting and test for its impact. const int32 kSocketBufferSize(kMaxPacketSize * 100); // Support 100 packets. socket->SetReceiveBufferSize(kSocketBufferSize); - // TODO(jar): What should the UDP send buffer be set to? If the send buffer - // is too large, then we might(?) wastefully queue packets in the OS, when - // we'd rather construct packets just in time. We do however expect that the - // calculated send rate (paced, or ack clocked), will be well below the egress - // rate of the local machine, so that *shouldn't* be a problem. - // If the buffer setting is too small, then we will starve our outgoing link - // on a fast connection, because we won't respond fast enough to the many - // async callbacks to get data from us. On the other hand, until we have real - // pacing support (beyond ack-clocked pacing), we get a bit of adhoc-pacing by - // requiring the application to refill this OS buffer (ensuring that we don't - // blast a pile of packets at the kernel's max egress rate). - // socket->SetSendBufferSize(????); + // Set a buffer large enough to contain the initial CWND's worth of packet + // to work around the problem with CHLO packets being sent out with the + // wrong encryption level, when the send buffer is full. + socket->SetSendBufferSize(kMaxPacketSize * 20); // Support 20 packets. QuicConnectionHelper* helper = new QuicConnectionHelper( base::MessageLoop::current()->message_loop_proxy().get(), diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc index fb9d474eb8..a546d874ea 100644 --- a/net/quic/quic_stream_factory_test.cc +++ b/net/quic/quic_stream_factory_test.cc @@ -52,7 +52,7 @@ class QuicStreamFactoryTest : public ::testing::Test { header.fec_flag = false; header.fec_group = 0; - QuicRstStreamFrame rst(stream_id, QUIC_SERVER_ERROR_PROCESSING_STREAM); + QuicRstStreamFrame rst(stream_id, QUIC_ERROR_PROCESSING_STREAM); return scoped_ptr<QuicEncryptedPacket>( ConstructPacket(header, QuicFrame(&rst))); } diff --git a/net/quic/quic_stream_sequencer.cc b/net/quic/quic_stream_sequencer.cc index 7cf67d351e..a57c05f83c 100644 --- a/net/quic/quic_stream_sequencer.cc +++ b/net/quic/quic_stream_sequencer.cc @@ -74,17 +74,21 @@ bool QuicStreamSequencer::OnStreamFrame(const QuicStreamFrame& frame) { return true; } - if (frame.fin) { - CloseStreamAtOffset(frame.offset + frame.data.size()); - } - QuicStreamOffset byte_offset = frame.offset; const char* data = frame.data.data(); size_t data_len = frame.data.size(); - if (data_len == 0) { - // TODO(rch): Close the stream if there was no data and no fin. - return true; + if (data_len == 0 && !frame.fin) { + // Stream frames must have data or a fin flag. + stream_->ConnectionClose(QUIC_INVALID_STREAM_FRAME, false); + return false; + } + + if (frame.fin) { + CloseStreamAtOffset(frame.offset + frame.data.size()); + if (data_len == 0) { + return true; + } } if (byte_offset == num_bytes_consumed_) { @@ -96,7 +100,7 @@ bool QuicStreamSequencer::OnStreamFrame(const QuicStreamFrame& frame) { return true; } if (bytes_consumed > data_len) { - stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM); + stream_->Close(QUIC_ERROR_PROCESSING_STREAM); return false; } else if (bytes_consumed == data_len) { FlushBufferedFrames(); @@ -211,7 +215,7 @@ void QuicStreamSequencer::MarkConsumed(size_t num_bytes_consumed) { << " end_offset: " << end_offset << " offset: " << it->first << " length: " << it->second.length(); - stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM); + stream_->Close(QUIC_ERROR_PROCESSING_STREAM); return; } @@ -262,7 +266,7 @@ void QuicStreamSequencer::FlushBufferedFrames() { return; } if (bytes_consumed > data->size()) { - stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM); // Programming error + stream_->Close(QUIC_ERROR_PROCESSING_STREAM); // Programming error return; } else if (bytes_consumed == data->size()) { frames_.erase(it); diff --git a/net/quic/quic_stream_sequencer_test.cc b/net/quic/quic_stream_sequencer_test.cc index 878585ba87..21568d6065 100644 --- a/net/quic/quic_stream_sequencer_test.cc +++ b/net/quic/quic_stream_sequencer_test.cc @@ -71,6 +71,7 @@ class MockStream : public ReliableQuicStream { MOCK_METHOD1(TerminateFromPeer, void(bool half_close)); MOCK_METHOD2(ProcessData, uint32(const char* data, uint32 data_len)); + MOCK_METHOD2(ConnectionClose, void(QuicErrorCode error, bool from_peer)); MOCK_METHOD1(Close, void(QuicRstStreamErrorCode error)); MOCK_METHOD0(OnCanWrite, void()); }; @@ -182,7 +183,8 @@ TEST_F(QuicStreamSequencerTest, FullFrameConsumed) { } TEST_F(QuicStreamSequencerTest, EmptyFrame) { - EXPECT_TRUE(sequencer_->OnFrame(0, "")); + EXPECT_CALL(stream_, ConnectionClose(QUIC_INVALID_STREAM_FRAME, false)); + EXPECT_FALSE(sequencer_->OnFrame(0, "")); EXPECT_EQ(0u, sequencer_->frames()->size()); EXPECT_EQ(0u, sequencer_->num_bytes_consumed()); } @@ -399,7 +401,7 @@ TEST_F(QuicStreamSequencerTest, MarkConsumedError) { // Now, attempt to mark consumed more data than was readable // and expect the stream to be closed. - EXPECT_CALL(stream_, Close(QUIC_SERVER_ERROR_PROCESSING_STREAM)); + EXPECT_CALL(stream_, Close(QUIC_ERROR_PROCESSING_STREAM)); EXPECT_DFATAL(sequencer_->MarkConsumed(4), "Invalid argument to MarkConsumed. num_bytes_consumed_: 3 " "end_offset: 4 offset: 9 length: 17"); diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc index 74c90a0bc3..cfb3cb7db5 100644 --- a/net/quic/quic_utils.cc +++ b/net/quic/quic_utils.cc @@ -120,7 +120,7 @@ const char* QuicUtils::StreamErrorToString(QuicRstStreamErrorCode error) { switch (error) { RETURN_STRING_LITERAL(QUIC_STREAM_NO_ERROR); RETURN_STRING_LITERAL(QUIC_STREAM_CONNECTION_ERROR); - RETURN_STRING_LITERAL(QUIC_SERVER_ERROR_PROCESSING_STREAM); + RETURN_STRING_LITERAL(QUIC_ERROR_PROCESSING_STREAM); RETURN_STRING_LITERAL(QUIC_MULTIPLE_TERMINATION_OFFSETS); RETURN_STRING_LITERAL(QUIC_BAD_APPLICATION_PAYLOAD); RETURN_STRING_LITERAL(QUIC_STREAM_PEER_GOING_AWAY); @@ -171,6 +171,7 @@ const char* QuicUtils::ErrorToString(QuicErrorCode error) { RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP); RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND); RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_ID); + RETURN_STRING_LITERAL(QUIC_INVALID_PRIORITY); RETURN_STRING_LITERAL(QUIC_TOO_MANY_OPEN_STREAMS); RETURN_STRING_LITERAL(QUIC_PUBLIC_RESET); RETURN_STRING_LITERAL(QUIC_INVALID_VERSION); @@ -182,6 +183,7 @@ const char* QuicUtils::ErrorToString(QuicErrorCode error) { RETURN_STRING_LITERAL(QUIC_ERROR_MIGRATING_ADDRESS); RETURN_STRING_LITERAL(QUIC_PACKET_WRITE_ERROR); RETURN_STRING_LITERAL(QUIC_PACKET_READ_ERROR); + RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_FRAME); RETURN_STRING_LITERAL(QUIC_PROOF_INVALID); RETURN_STRING_LITERAL(QUIC_CRYPTO_DUPLICATE_TAG); RETURN_STRING_LITERAL(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT); diff --git a/net/quic/reliable_quic_stream.cc b/net/quic/reliable_quic_stream.cc index 45b4fce4ae..41cb4576b5 100644 --- a/net/quic/reliable_quic_stream.cc +++ b/net/quic/reliable_quic_stream.cc @@ -6,6 +6,7 @@ #include "net/quic/quic_session.h" #include "net/quic/quic_spdy_decompressor.h" +#include "net/spdy/write_blocked_list.h" using base::StringPiece; using std::min; @@ -193,6 +194,12 @@ QuicConsumedData ReliableQuicStream::WriteData(StringPiece data, bool fin) { return WriteOrBuffer(data, fin); } + +void ReliableQuicStream::set_priority(QuicPriority priority) { + DCHECK_EQ(0u, stream_bytes_written_); + priority_ = priority; +} + QuicConsumedData ReliableQuicStream::WriteOrBuffer(StringPiece data, bool fin) { DCHECK(!fin_buffered_); @@ -235,27 +242,43 @@ void ReliableQuicStream::OnCanWrite() { QuicConsumedData ReliableQuicStream::WriteDataInternal( StringPiece data, bool fin) { + struct iovec iov = {const_cast<char*>(data.data()), + static_cast<size_t>(data.size())}; + return WritevDataInternal(&iov, 1, fin); +} + +QuicConsumedData ReliableQuicStream::WritevDataInternal(const struct iovec* iov, + int iov_count, + bool fin) { if (write_side_closed_) { DLOG(ERROR) << "Attempt to write when the write side is closed"; return QuicConsumedData(0, false); } + size_t write_length = 0u; + for (int i = 0; i < iov_count; ++i) { + write_length += iov[i].iov_len; + } QuicConsumedData consumed_data = - session()->WriteData(id(), data, stream_bytes_written_, fin); + session()->WritevData(id(), iov, iov_count, stream_bytes_written_, fin); stream_bytes_written_ += consumed_data.bytes_consumed; - if (consumed_data.bytes_consumed == data.length()) { + if (consumed_data.bytes_consumed == write_length) { if (fin && consumed_data.fin_consumed) { fin_sent_ = true; CloseWriteSide(); } else if (fin && !consumed_data.fin_consumed) { - session_->MarkWriteBlocked(id()); + session_->MarkWriteBlocked(id(), EffectivePriority()); } } else { - session_->MarkWriteBlocked(id()); + session_->MarkWriteBlocked(id(), EffectivePriority()); } return consumed_data; } +QuicPriority ReliableQuicStream::EffectivePriority() const { + return priority(); +} + void ReliableQuicStream::CloseReadSide() { if (read_side_closed_) { return; @@ -270,10 +293,8 @@ void ReliableQuicStream::CloseReadSide() { } uint32 ReliableQuicStream::ProcessRawData(const char* data, uint32 data_len) { + DCHECK_NE(0u, data_len); if (id() == kCryptoStreamId) { - if (data_len == 0) { - return 0; - } // The crypto stream does not use compression. return ProcessData(data, data_len); } @@ -283,7 +304,7 @@ uint32 ReliableQuicStream::ProcessRawData(const char* data, uint32 data_len) { total_bytes_consumed += StripPriorityAndHeaderId(data, data_len); data += total_bytes_consumed; data_len -= total_bytes_consumed; - if (data_len == 0) { + if (data_len == 0 || !session_->connection()->connected()) { return total_bytes_consumed; } } @@ -465,11 +486,18 @@ uint32 ReliableQuicStream::StripPriorityAndHeaderId( if (!priority_parsed_ && session_->connection()->version() >= QUIC_VERSION_9 && session_->connection()->is_server()) { + QuicPriority temporary_priority = priority_; total_bytes_parsed = StripUint32( - data, data_len, &headers_id_and_priority_buffer_, &priority_); + data, data_len, &headers_id_and_priority_buffer_, &temporary_priority); if (total_bytes_parsed > 0 && headers_id_and_priority_buffer_.size() == 0) { - // TODO(alyssar) check for priority out of bounds. priority_parsed_ = true; + // Spdy priorities are inverted, so the highest numerical value is the + // lowest legal priority. + if (temporary_priority > static_cast<QuicPriority>(kLowestPriority)) { + session_->connection()->SendConnectionClose(QUIC_INVALID_PRIORITY); + return 0; + } + priority_ = temporary_priority; } data += total_bytes_parsed; data_len -= total_bytes_parsed; diff --git a/net/quic/reliable_quic_stream.h b/net/quic/reliable_quic_stream.h index 7ba5428412..807882ca2d 100644 --- a/net/quic/reliable_quic_stream.h +++ b/net/quic/reliable_quic_stream.h @@ -95,6 +95,12 @@ class NET_EXPORT_PRIVATE ReliableQuicStream : public // becomes unblocked. virtual void OnDecompressorAvailable(); + // By default, this is the same as priority(), however it allows streams + // to temporarily alter effective priority. For example if a SPDY stream has + // compressed but not written headers it can write the headers with a higher + // priority. + virtual QuicPriority EffectivePriority() const; + QuicStreamId id() const { return id_; } QuicRstStreamErrorCode stream_error() const { return stream_error_; } @@ -116,7 +122,6 @@ class NET_EXPORT_PRIVATE ReliableQuicStream : public bool GetSSLInfo(SSLInfo* ssl_info); bool headers_decompressed() const { return headers_decompressed_; } - QuicPriority priority() const { return priority_; } protected: // Returns a pair with the number of bytes consumed from data, and a boolean @@ -141,6 +146,13 @@ class NET_EXPORT_PRIVATE ReliableQuicStream : public QuicSession* session() { return session_; } + // Sets priority_ to priority. This should only be called before bytes are + // written to the server. + void set_priority(QuicPriority priority); + // This is protected because external classes should use EffectivePriority + // instead. + QuicPriority priority() const { return priority_; } + // Sends as much of 'data' to the connection as the connection will consume, // and then buffers any remaining data in queued_data_. // Returns (data.size(), true) as it always consumed all data: it returns for @@ -151,6 +163,13 @@ class NET_EXPORT_PRIVATE ReliableQuicStream : public // Returns the number of bytes consumed by the connection. QuicConsumedData WriteDataInternal(base::StringPiece data, bool fin); + // Sends as many bytes in the first |count| buffers of |iov| to the connection + // as the connection will consume. + // Returns the number of bytes consumed by the connection. + QuicConsumedData WritevDataInternal(const struct iovec* iov, + int iov_count, + bool fin); + private: friend class test::ReliableQuicStreamPeer; friend class QuicStreamUtils; diff --git a/net/quic/reliable_quic_stream_test.cc b/net/quic/reliable_quic_stream_test.cc index 1df13cfc14..063554d82c 100644 --- a/net/quic/reliable_quic_stream_test.cc +++ b/net/quic/reliable_quic_stream_test.cc @@ -126,9 +126,7 @@ TEST_F(ReliableQuicStreamTest, WriteAllData) { 1 + QuicPacketCreator::StreamFramePacketOverhead( connection_->version(), PACKET_8BYTE_GUID, !kIncludeVersion, PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP); - // TODO(rch): figure out how to get StrEq working here. - //EXPECT_CALL(*session_, WriteData(kStreamId, StrEq(kData1), _, _)).WillOnce( - EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce( + EXPECT_CALL(*session_, WritevData(kStreamId, _, 1, _, _)).WillOnce( Return(QuicConsumedData(kDataLen, true))); EXPECT_EQ(kDataLen, stream_->WriteData(kData1, false).bytes_consumed); EXPECT_FALSE(write_blocked_list_->HasWriteBlockedStreams()); @@ -142,7 +140,7 @@ TEST_F(ReliableQuicStreamTest, NoBlockingIfNoDataOrFin) { // Write no data and no fin. If we consume nothing we should not be write // blocked. EXPECT_DEBUG_DEATH({ - EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce( + EXPECT_CALL(*session_, WritevData(kStreamId, _, 1, _, _)).WillOnce( Return(QuicConsumedData(0, false))); stream_->WriteData(StringPiece(), false); EXPECT_FALSE(write_blocked_list_->HasWriteBlockedStreams()); @@ -155,7 +153,7 @@ TEST_F(ReliableQuicStreamTest, BlockIfOnlySomeDataConsumed) { // Write some data and no fin. If we consume some but not all of the data, // we should be write blocked a not all the data was consumed. - EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce( + EXPECT_CALL(*session_, WritevData(kStreamId, _, 1, _, _)).WillOnce( Return(QuicConsumedData(1, false))); stream_->WriteData(StringPiece(kData1, 2), false); ASSERT_EQ(1, write_blocked_list_->NumBlockedStreams()); @@ -169,7 +167,7 @@ TEST_F(ReliableQuicStreamTest, BlockIfFinNotConsumedWithData) { // we should be write blocked because the fin was not consumed. // (This should never actually happen as the fin should be sent out with the // last data) - EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce( + EXPECT_CALL(*session_, WritevData(kStreamId, _, 1, _, _)).WillOnce( Return(QuicConsumedData(2, false))); stream_->WriteData(StringPiece(kData1, 2), true); ASSERT_EQ(1, write_blocked_list_->NumBlockedStreams()); @@ -180,7 +178,7 @@ TEST_F(ReliableQuicStreamTest, BlockIfSoloFinNotConsumed) { // Write no data and a fin. If we consume nothing we should be write blocked, // as the fin was not consumed. - EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce( + EXPECT_CALL(*session_, WritevData(kStreamId, _, 1, _, _)).WillOnce( Return(QuicConsumedData(0, false))); stream_->WriteData(StringPiece(), true); ASSERT_EQ(1, write_blocked_list_->NumBlockedStreams()); @@ -194,9 +192,7 @@ TEST_F(ReliableQuicStreamTest, WriteData) { 1 + QuicPacketCreator::StreamFramePacketOverhead( connection_->version(), PACKET_8BYTE_GUID, !kIncludeVersion, PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP); - // TODO(rch): figure out how to get StrEq working here. - //EXPECT_CALL(*session_, WriteData(_, StrEq(kData1), _, _)).WillOnce( - EXPECT_CALL(*session_, WriteData(_, _, _, _)).WillOnce( + EXPECT_CALL(*session_, WritevData(_, _, 1, _, _)).WillOnce( Return(QuicConsumedData(kDataLen - 1, false))); // The return will be kDataLen, because the last byte gets buffered. EXPECT_EQ(kDataLen, stream_->WriteData(kData1, false).bytes_consumed); @@ -207,17 +203,14 @@ TEST_F(ReliableQuicStreamTest, WriteData) { // Make sure we get the tail of the first write followed by the bytes_consumed InSequence s; - //EXPECT_CALL(*session_, WriteData(_, StrEq(&kData1[kDataLen - 1]), _, _)). - EXPECT_CALL(*session_, WriteData(_, _, _, _)). + EXPECT_CALL(*session_, WritevData(_, _, 1, _, _)). WillOnce(Return(QuicConsumedData(1, false))); - //EXPECT_CALL(*session_, WriteData(_, StrEq(kData2), _, _)). - EXPECT_CALL(*session_, WriteData(_, _, _, _)). + EXPECT_CALL(*session_, WritevData(_, _, 1, _, _)). WillOnce(Return(QuicConsumedData(kDataLen - 2, false))); stream_->OnCanWrite(); - // And finally the end of the bytes_consumed - //EXPECT_CALL(*session_, WriteData(_, StrEq(&kData2[kDataLen - 2]), _, _)). - EXPECT_CALL(*session_, WriteData(_, _, _, _)). + // And finally the end of the bytes_consumed. + EXPECT_CALL(*session_, WritevData(_, _, 1, _, _)). WillOnce(Return(QuicConsumedData(2, true))); stream_->OnCanWrite(); } @@ -238,19 +231,20 @@ TEST_F(ReliableQuicStreamTest, ProcessHeaders) { Initialize(kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); QuicStreamFrame frame(kStreamId, false, 0, compressed_headers); stream_->OnStreamFrame(frame); EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_), stream_->data()); - EXPECT_EQ(0u, stream_->priority()); + EXPECT_EQ(static_cast<QuicPriority>(kHighestPriority), + stream_->EffectivePriority()); } TEST_F(ReliableQuicStreamTest, ProcessHeadersWithInvalidHeaderId) { Initialize(kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); compressed_headers.replace(4, 1, 1, '\xFF'); // Illegal header id. QuicStreamFrame frame(kStreamId, false, 0, compressed_headers); @@ -262,7 +256,7 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBody) { Initialize(kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); string body = "this is the body"; string data = compressed_headers + body; QuicStreamFrame frame(kStreamId, false, 0, data); @@ -276,7 +270,7 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyFragments) { Initialize(kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(7, headers_); + compressor_->CompressHeadersWithPriority(kLowestPriority, headers_); string body = "this is the body"; string data = compressed_headers + body; @@ -308,14 +302,15 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyFragments) { ASSERT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body, stream_->data()) << "split_point: " << split_point; } - EXPECT_EQ(7u, stream_->priority()); + EXPECT_EQ(static_cast<QuicPriority>(kLowestPriority), + stream_->EffectivePriority()); } TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyReadv) { Initialize(!kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); string body = "this is the body"; string data = compressed_headers + body; QuicStreamFrame frame(kStreamId, false, 0, data); @@ -345,7 +340,7 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyIncrementalReadv) { Initialize(!kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); string body = "this is the body"; string data = compressed_headers + body; QuicStreamFrame frame(kStreamId, false, 0, data); @@ -371,7 +366,7 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) { Initialize(!kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); string body = "this is the body"; string data = compressed_headers + body; QuicStreamFrame frame(kStreamId, false, 0, data); @@ -401,14 +396,14 @@ TEST_F(ReliableQuicStreamTest, ProcessCorruptHeadersEarly) { Initialize(kShouldProcessData); string compressed_headers1 = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers1); string decompressed_headers1 = SpdyUtils::SerializeUncompressedHeaders(headers_); headers_["content-type"] = "text/plain"; string compressed_headers2 = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); // Corrupt the compressed data. compressed_headers2[compressed_headers2.length() - 1] ^= 0xA1; QuicStreamFrame frame2(stream2_->id(), false, 0, compressed_headers2); @@ -441,14 +436,14 @@ TEST_F(ReliableQuicStreamTest, ProcessPartialHeadersEarly) { Initialize(kShouldProcessData); string compressed_headers1 = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers1); string decompressed_headers1 = SpdyUtils::SerializeUncompressedHeaders(headers_); headers_["content-type"] = "text/plain"; string compressed_headers2 = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); string partial_compressed_headers = compressed_headers2.substr(0, compressed_headers2.length() / 2); QuicStreamFrame frame2(stream2_->id(), false, 0, partial_compressed_headers); @@ -492,14 +487,14 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersEarly) { Initialize(kShouldProcessData); string compressed_headers1 = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers1); string decompressed_headers1 = SpdyUtils::SerializeUncompressedHeaders(headers_); headers_["content-type"] = "text/plain"; string compressed_headers2 = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); QuicStreamFrame frame2(stream2_->id(), false, 0, compressed_headers2); string decompressed_headers2 = SpdyUtils::SerializeUncompressedHeaders(headers_); @@ -528,7 +523,7 @@ TEST_F(ReliableQuicStreamTest, ProcessHeadersDelay) { Initialize(!kShouldProcessData); string compressed_headers = - compressor_->CompressHeadersWithPriority(0, headers_); + compressor_->CompressHeadersWithPriority(kHighestPriority, headers_); QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers); string decompressed_headers = SpdyUtils::SerializeUncompressedHeaders(headers_); diff --git a/net/quic/test_tools/quic_connection_peer.cc b/net/quic/test_tools/quic_connection_peer.cc index c168313202..5d715db4ef 100644 --- a/net/quic/test_tools/quic_connection_peer.cc +++ b/net/quic/test_tools/quic_connection_peer.cc @@ -70,17 +70,15 @@ QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout( bool QuicConnectionPeer::IsSavedForRetransmission( QuicConnection* connection, QuicPacketSequenceNumber sequence_number) { - return ContainsKey(connection->retransmission_map_, sequence_number); + return connection->sent_packet_manager_.IsUnacked(sequence_number); } // static size_t QuicConnectionPeer::GetRetransmissionCount( QuicConnection* connection, QuicPacketSequenceNumber sequence_number) { - QuicConnection::RetransmissionMap::iterator it = - connection->retransmission_map_.find(sequence_number); - DCHECK(connection->retransmission_map_.end() != it); - return it->second.number_retransmissions; + return connection->sent_packet_manager_.GetRetransmissionCount( + sequence_number); } // static diff --git a/net/quic/test_tools/quic_test_utils.cc b/net/quic/test_tools/quic_test_utils.cc index 9381e05a23..8ba46b8f4a 100644 --- a/net/quic/test_tools/quic_test_utils.cc +++ b/net/quic/test_tools/quic_test_utils.cc @@ -243,8 +243,9 @@ bool PacketSavingConnection::SendOrQueuePacket( EncryptionLevel level, QuicPacketSequenceNumber sequence_number, QuicPacket* packet, - QuicPacketEntropyHash entropy_hash, - HasRetransmittableData retransmittable) { + QuicPacketEntropyHash /* entropy_hash */, + HasRetransmittableData /* retransmittable */, + Force /* forced */) { packets_.push_back(packet); QuicEncryptedPacket* encrypted = framer_.EncryptPacket(level, sequence_number, *packet); @@ -254,7 +255,7 @@ bool PacketSavingConnection::SendOrQueuePacket( MockSession::MockSession(QuicConnection* connection, bool is_server) : QuicSession(connection, DefaultQuicConfig(), is_server) { - ON_CALL(*this, WriteData(_, _, _, _)) + ON_CALL(*this, WritevData(_, _, _, _, _)) .WillByDefault(testing::Return(QuicConsumedData(0, false))); } diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h index a90b21270a..64a9d30ada 100644 --- a/net/quic/test_tools/quic_test_utils.h +++ b/net/quic/test_tools/quic_test_utils.h @@ -175,15 +175,14 @@ class MockConnectionVisitor : public QuicConnectionVisitorInterface { MockConnectionVisitor(); virtual ~MockConnectionVisitor(); - MOCK_METHOD4(OnPacket, bool(const IPEndPoint& self_address, - const IPEndPoint& peer_address, - const QuicPacketHeader& header, - const std::vector<QuicStreamFrame>& frame)); + MOCK_METHOD1(OnStreamFrames, bool(const std::vector<QuicStreamFrame>& frame)); MOCK_METHOD1(OnRstStream, void(const QuicRstStreamFrame& frame)); MOCK_METHOD1(OnGoAway, void(const QuicGoAwayFrame& frame)); MOCK_METHOD2(ConnectionClose, void(QuicErrorCode error, bool from_peer)); - MOCK_METHOD1(OnAck, void(const SequenceNumberSet& acked_packets)); MOCK_METHOD0(OnCanWrite, bool()); + MOCK_CONST_METHOD0(HasPendingHandshake, bool()); + MOCK_METHOD1(OnSuccessfulVersionNegotiation, + void(const QuicVersion& version)); private: DISALLOW_COPY_AND_ASSIGN(MockConnectionVisitor); @@ -201,7 +200,7 @@ class MockHelper : public QuicConnectionHelperInterface { MOCK_METHOD2(WritePacketToWire, int(const QuicEncryptedPacket& packet, int* error)); MOCK_METHOD0(IsWriteBlockedDataBuffered, bool()); - MOCK_METHOD1(IsWriteBlocked, bool(int)); + MOCK_METHOD1(IsWriteBlocked, bool(int stream_id)); virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate); private: @@ -235,6 +234,7 @@ class MockConnection : public QuicConnection { QuicStreamId last_good_stream_id, const string& reason)); MOCK_METHOD0(OnCanWrite, bool()); + MOCK_CONST_METHOD0(HasPendingHandshake, bool()); void ProcessUdpPacketInternal(const IPEndPoint& self_address, const IPEndPoint& peer_address, @@ -262,7 +262,8 @@ class PacketSavingConnection : public MockConnection { QuicPacketSequenceNumber sequence_number, QuicPacket* packet, QuicPacketEntropyHash entropy_hash, - HasRetransmittableData has_retransmittable_data) OVERRIDE; + HasRetransmittableData has_retransmittable_data, + Force forced) OVERRIDE; std::vector<QuicPacket*> packets_; std::vector<QuicEncryptedPacket*> encrypted_packets_; @@ -285,10 +286,11 @@ class MockSession : public QuicSession { ReliableQuicStream*(QuicStreamId id)); MOCK_METHOD0(GetCryptoStream, QuicCryptoStream*()); MOCK_METHOD0(CreateOutgoingReliableStream, ReliableQuicStream*()); - MOCK_METHOD4(WriteData, QuicConsumedData(QuicStreamId id, - base::StringPiece data, - QuicStreamOffset offset, - bool fin)); + MOCK_METHOD5(WritevData, QuicConsumedData(QuicStreamId id, + const struct iovec* iov, + int count, + QuicStreamOffset offset, + bool fin)); MOCK_METHOD0(IsHandshakeComplete, bool()); private: @@ -327,8 +329,9 @@ class MockSendAlgorithm : public SendAlgorithmInterface { MOCK_METHOD3(OnIncomingAck, void(QuicPacketSequenceNumber, QuicByteCount, QuicTime::Delta)); MOCK_METHOD1(OnIncomingLoss, void(QuicTime)); - MOCK_METHOD4(SentPacket, void(QuicTime sent_time, QuicPacketSequenceNumber, - QuicByteCount, Retransmission)); + MOCK_METHOD5(SentPacket, + bool(QuicTime sent_time, QuicPacketSequenceNumber, QuicByteCount, + Retransmission, HasRetransmittableData)); MOCK_METHOD2(AbandoningPacket, void(QuicPacketSequenceNumber sequence_number, QuicByteCount abandoned_bytes)); MOCK_METHOD4(TimeUntilSend, QuicTime::Delta(QuicTime now, Retransmission, diff --git a/net/socket/tcp_client_socket.cc b/net/socket/tcp_client_socket.cc index a91d33d24c..22aea47778 100644 --- a/net/socket/tcp_client_socket.cc +++ b/net/socket/tcp_client_socket.cc @@ -4,56 +4,317 @@ #include "net/socket/tcp_client_socket.h" -#include "base/file_util.h" -#include "base/files/file_path.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" namespace net { -namespace { +TCPClientSocket::TCPClientSocket(const AddressList& addresses, + net::NetLog* net_log, + const net::NetLog::Source& source) + : socket_(new TCPSocket(net_log, source)), + addresses_(addresses), + current_address_index_(-1), + next_connect_state_(CONNECT_STATE_NONE), + previously_disconnected_(false) { +} + +TCPClientSocket::TCPClientSocket(scoped_ptr<TCPSocket> connected_socket, + const IPEndPoint& peer_address) + : socket_(connected_socket.Pass()), + addresses_(AddressList(peer_address)), + current_address_index_(0), + next_connect_state_(CONNECT_STATE_NONE), + previously_disconnected_(false) { + DCHECK(socket_); + + socket_->SetDefaultOptionsForClient(); + use_history_.set_was_ever_connected(); +} + +TCPClientSocket::~TCPClientSocket() { +} + +int TCPClientSocket::Bind(const IPEndPoint& address) { + if (current_address_index_ >= 0 || bind_address_) { + // Cannot bind the socket if we are already connected or connecting. + NOTREACHED(); + return ERR_UNEXPECTED; + } + + int result = OK; + if (!socket_->IsValid()) { + result = OpenSocket(address.GetFamily()); + if (result != OK) + return result; + } + + result = socket_->Bind(address); + if (result != OK) + return result; + + bind_address_.reset(new IPEndPoint(address)); + return OK; +} + +int TCPClientSocket::Connect(const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + + // If connecting or already connected, then just return OK. + if (socket_->IsValid() && current_address_index_ >= 0) + return OK; + + socket_->StartLoggingMultipleConnectAttempts(addresses_); + + // We will try to connect to each address in addresses_. Start with the + // first one in the list. + next_connect_state_ = CONNECT_STATE_CONNECT; + current_address_index_ = 0; + + int rv = DoConnectLoop(OK); + if (rv == ERR_IO_PENDING) { + connect_callback_ = callback; + } else { + socket_->EndLoggingMultipleConnectAttempts(rv); + } + + return rv; +} -#if defined(OS_LINUX) +int TCPClientSocket::DoConnectLoop(int result) { + DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE); -// Checks to see if the system supports TCP FastOpen. Notably, it requires -// kernel support. Additionally, this checks system configuration to ensure that -// it's enabled. -bool SystemSupportsTCPFastOpen() { - static const base::FilePath::CharType kTCPFastOpenProcFilePath[] = - "/proc/sys/net/ipv4/tcp_fastopen"; - std::string system_enabled_tcp_fastopen; - if (!base::ReadFileToString( - base::FilePath(kTCPFastOpenProcFilePath), - &system_enabled_tcp_fastopen)) { - return false; + int rv = result; + do { + ConnectState state = next_connect_state_; + next_connect_state_ = CONNECT_STATE_NONE; + switch (state) { + case CONNECT_STATE_CONNECT: + DCHECK_EQ(OK, rv); + rv = DoConnect(); + break; + case CONNECT_STATE_CONNECT_COMPLETE: + rv = DoConnectComplete(rv); + break; + default: + NOTREACHED() << "bad state " << state; + rv = ERR_UNEXPECTED; + break; + } + } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE); + + return rv; +} + +int TCPClientSocket::DoConnect() { + DCHECK_GE(current_address_index_, 0); + DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); + + const IPEndPoint& endpoint = addresses_[current_address_index_]; + + if (previously_disconnected_) { + use_history_.Reset(); + previously_disconnected_ = false; } - // As per http://lxr.linux.no/linux+v3.7.7/include/net/tcp.h#L225 - // TFO_CLIENT_ENABLE is the LSB - if (system_enabled_tcp_fastopen.empty() || - (system_enabled_tcp_fastopen[0] & 0x1) == 0) { - return false; + next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE; + + if (socket_->IsValid()) { + DCHECK(bind_address_); + } else { + int result = OpenSocket(endpoint.GetFamily()); + if (result != OK) + return result; + + if (bind_address_) { + result = socket_->Bind(*bind_address_); + if (result != OK) { + socket_->Close(); + return result; + } + } + } + + // |socket_| is owned by this class and the callback won't be run once + // |socket_| is gone. Therefore, it is safe to use base::Unretained() here. + return socket_->Connect(endpoint, + base::Bind(&TCPClientSocket::DidCompleteConnect, + base::Unretained(this))); +} + +int TCPClientSocket::DoConnectComplete(int result) { + if (result == OK) { + use_history_.set_was_ever_connected(); + return OK; // Done! + } + + // Close whatever partially connected socket we currently have. + DoDisconnect(); + + // Try to fall back to the next address in the list. + if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) { + next_connect_state_ = CONNECT_STATE_CONNECT; + ++current_address_index_; + return OK; + } + + // Otherwise there is nothing to fall back to, so give up. + return result; +} + +void TCPClientSocket::Disconnect() { + DoDisconnect(); + current_address_index_ = -1; + bind_address_.reset(); +} + +void TCPClientSocket::DoDisconnect() { + // If connecting or already connected, record that the socket has been + // disconnected. + previously_disconnected_ = socket_->IsValid() && current_address_index_ >= 0; + socket_->Close(); +} + +bool TCPClientSocket::IsConnected() const { + return socket_->IsConnected(); +} + +bool TCPClientSocket::IsConnectedAndIdle() const { + return socket_->IsConnectedAndIdle(); +} + +int TCPClientSocket::GetPeerAddress(IPEndPoint* address) const { + return socket_->GetPeerAddress(address); +} + +int TCPClientSocket::GetLocalAddress(IPEndPoint* address) const { + DCHECK(address); + + if (!socket_->IsValid()) { + if (bind_address_) { + *address = *bind_address_; + return OK; + } + return ERR_SOCKET_NOT_CONNECTED; } - return true; + return socket_->GetLocalAddress(address); +} + +const BoundNetLog& TCPClientSocket::NetLog() const { + return socket_->net_log(); +} + +void TCPClientSocket::SetSubresourceSpeculation() { + use_history_.set_subresource_speculation(); +} + +void TCPClientSocket::SetOmniboxSpeculation() { + use_history_.set_omnibox_speculation(); +} + +bool TCPClientSocket::WasEverUsed() const { + return use_history_.was_used_to_convey_data(); +} + +bool TCPClientSocket::UsingTCPFastOpen() const { + return socket_->UsingTCPFastOpen(); +} + +bool TCPClientSocket::WasNpnNegotiated() const { + return false; } -#else +NextProto TCPClientSocket::GetNegotiatedProtocol() const { + return kProtoUnknown; +} -bool SystemSupportsTCPFastOpen() { +bool TCPClientSocket::GetSSLInfo(SSLInfo* ssl_info) { return false; } -#endif +int TCPClientSocket::Read(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + + // |socket_| is owned by this class and the callback won't be run once + // |socket_| is gone. Therefore, it is safe to use base::Unretained() here. + CompletionCallback read_callback = base::Bind( + &TCPClientSocket::DidCompleteReadWrite, base::Unretained(this), callback); + int result = socket_->Read(buf, buf_len, read_callback); + if (result > 0) + use_history_.set_was_used_to_convey_data(); + + return result; +} + +int TCPClientSocket::Write(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + + // |socket_| is owned by this class and the callback won't be run once + // |socket_| is gone. Therefore, it is safe to use base::Unretained() here. + CompletionCallback write_callback = base::Bind( + &TCPClientSocket::DidCompleteReadWrite, base::Unretained(this), callback); + int result = socket_->Write(buf, buf_len, write_callback); + if (result > 0) + use_history_.set_was_used_to_convey_data(); + return result; } -static bool g_tcp_fastopen_enabled = false; +bool TCPClientSocket::SetReceiveBufferSize(int32 size) { + return socket_->SetReceiveBufferSize(size); +} + +bool TCPClientSocket::SetSendBufferSize(int32 size) { + return socket_->SetSendBufferSize(size); +} + +bool TCPClientSocket::SetKeepAlive(bool enable, int delay) { + return socket_->SetKeepAlive(enable, delay); +} -void SetTCPFastOpenEnabled(bool value) { - g_tcp_fastopen_enabled = value && SystemSupportsTCPFastOpen(); +bool TCPClientSocket::SetNoDelay(bool no_delay) { + return socket_->SetNoDelay(no_delay); } -bool IsTCPFastOpenEnabled() { - return g_tcp_fastopen_enabled; +void TCPClientSocket::DidCompleteConnect(int result) { + DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE); + DCHECK_NE(result, ERR_IO_PENDING); + DCHECK(!connect_callback_.is_null()); + + result = DoConnectLoop(result); + if (result != ERR_IO_PENDING) { + socket_->EndLoggingMultipleConnectAttempts(result); + base::ResetAndReturn(&connect_callback_).Run(result); + } +} + +void TCPClientSocket::DidCompleteReadWrite(const CompletionCallback& callback, + int result) { + if (result > 0) + use_history_.set_was_used_to_convey_data(); + + callback.Run(result); +} + +int TCPClientSocket::OpenSocket(AddressFamily family) { + DCHECK(!socket_->IsValid()); + + int result = socket_->Open(family); + if (result != OK) + return result; + + socket_->SetDefaultOptionsForClient(); + + return OK; } } // namespace net diff --git a/net/socket/tcp_client_socket.h b/net/socket/tcp_client_socket.h index 8a2c0cd73f..fabcbc1b39 100644 --- a/net/socket/tcp_client_socket.h +++ b/net/socket/tcp_client_socket.h @@ -5,30 +5,116 @@ #ifndef NET_SOCKET_TCP_CLIENT_SOCKET_H_ #define NET_SOCKET_TCP_CLIENT_SOCKET_H_ -#include "build/build_config.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/address_list.h" +#include "net/base/completion_callback.h" #include "net/base/net_export.h" - -#if defined(OS_WIN) -#include "net/socket/tcp_client_socket_win.h" -#elif defined(OS_POSIX) -#include "net/socket/tcp_client_socket_libevent.h" -#endif +#include "net/base/net_log.h" +#include "net/socket/stream_socket.h" +#include "net/socket/tcp_socket.h" namespace net { // A client socket that uses TCP as the transport layer. -#if defined(OS_WIN) -typedef TCPClientSocketWin TCPClientSocket; -#elif defined(OS_POSIX) -typedef TCPClientSocketLibevent TCPClientSocket; -#endif - -// Enable/disable experimental TCP FastOpen option. -// Not thread safe. Must be called during initialization/startup only. -NET_EXPORT void SetTCPFastOpenEnabled(bool value); - -// Check if the TCP FastOpen option is enabled. -bool IsTCPFastOpenEnabled(); +class NET_EXPORT TCPClientSocket : public StreamSocket { + public: + // The IP address(es) and port number to connect to. The TCP socket will try + // each IP address in the list until it succeeds in establishing a + // connection. + TCPClientSocket(const AddressList& addresses, + net::NetLog* net_log, + const net::NetLog::Source& source); + + // Adopts the given, connected socket and then acts as if Connect() had been + // called. This function is used by TCPServerSocket and for testing. + TCPClientSocket(scoped_ptr<TCPSocket> connected_socket, + const IPEndPoint& peer_address); + + virtual ~TCPClientSocket(); + + // Binds the socket to a local IP address and port. + int Bind(const IPEndPoint& address); + + // StreamSocket implementation. + virtual int Connect(const CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectedAndIdle() const OVERRIDE; + virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE; + virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE; + virtual const BoundNetLog& NetLog() const OVERRIDE; + virtual void SetSubresourceSpeculation() OVERRIDE; + virtual void SetOmniboxSpeculation() OVERRIDE; + virtual bool WasEverUsed() const OVERRIDE; + virtual bool UsingTCPFastOpen() const OVERRIDE; + virtual bool WasNpnNegotiated() const OVERRIDE; + virtual NextProto GetNegotiatedProtocol() const OVERRIDE; + virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; + + // Socket implementation. + // Multiple outstanding requests are not supported. + // Full duplex mode (reading and writing at the same time) is supported. + virtual int Read(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual int Write(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + + virtual bool SetKeepAlive(bool enable, int delay); + virtual bool SetNoDelay(bool no_delay); + + private: + // State machine for connecting the socket. + enum ConnectState { + CONNECT_STATE_CONNECT, + CONNECT_STATE_CONNECT_COMPLETE, + CONNECT_STATE_NONE, + }; + + // State machine used by Connect(). + int DoConnectLoop(int result); + int DoConnect(); + int DoConnectComplete(int result); + + // Helper used by Disconnect(), which disconnects minus resetting + // current_address_index_ and bind_address_. + void DoDisconnect(); + + void DidCompleteConnect(int result); + void DidCompleteReadWrite(const CompletionCallback& callback, int result); + + int OpenSocket(AddressFamily family); + + scoped_ptr<TCPSocket> socket_; + + // Local IP address and port we are bound to. Set to NULL if Bind() + // wasn't called (in that case OS chooses address/port). + scoped_ptr<IPEndPoint> bind_address_; + + // The list of addresses we should try in order to establish a connection. + AddressList addresses_; + + // Where we are in above list. Set to -1 if uninitialized. + int current_address_index_; + + // External callback; called when connect is complete. + CompletionCallback connect_callback_; + + // The next state for the Connect() state machine. + ConnectState next_connect_state_; + + // This socket was previously disconnected and has not been re-connected. + bool previously_disconnected_; + + // Record of connectivity and transmissions, for use in speculative connection + // histograms. + UseHistory use_history_; + + DISALLOW_COPY_AND_ASSIGN(TCPClientSocket); +}; } // namespace net diff --git a/net/socket/tcp_client_socket_libevent.cc b/net/socket/tcp_client_socket_libevent.cc deleted file mode 100644 index cbcd25fb38..0000000000 --- a/net/socket/tcp_client_socket_libevent.cc +++ /dev/null @@ -1,830 +0,0 @@ -// 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/socket/tcp_client_socket.h" - -#include <errno.h> -#include <fcntl.h> -#include <netdb.h> -#include <sys/socket.h> -#include <netinet/tcp.h> -#if defined(OS_POSIX) -#include <netinet/in.h> -#endif - -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/metrics/histogram.h" -#include "base/metrics/stats_counters.h" -#include "base/posix/eintr_wrapper.h" -#include "base/strings/string_util.h" -#include "net/base/connection_type_histograms.h" -#include "net/base/io_buffer.h" -#include "net/base/ip_endpoint.h" -#include "net/base/net_errors.h" -#include "net/base/net_log.h" -#include "net/base/net_util.h" -#include "net/base/network_change_notifier.h" -#include "net/socket/socket_descriptor.h" -#include "net/socket/socket_net_log_params.h" - -// If we don't have a definition for TCPI_OPT_SYN_DATA, create one. -#ifndef TCPI_OPT_SYN_DATA -#define TCPI_OPT_SYN_DATA 32 -#endif - -namespace net { - -namespace { - -const int kTCPKeepAliveSeconds = 45; - -// SetTCPNoDelay turns on/off buffering in the kernel. By default, TCP sockets -// will wait up to 200ms for more data to complete a packet before transmitting. -// After calling this function, the kernel will not wait. See TCP_NODELAY in -// `man 7 tcp`. -bool SetTCPNoDelay(int fd, bool no_delay) { - int on = no_delay ? 1 : 0; - int error = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, - sizeof(on)); - return error == 0; -} - -// SetTCPKeepAlive sets SO_KEEPALIVE. -bool SetTCPKeepAlive(int fd, bool enable, int delay) { - int on = enable ? 1 : 0; - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) { - PLOG(ERROR) << "Failed to set SO_KEEPALIVE on fd: " << fd; - return false; - } -#if defined(OS_LINUX) || defined(OS_ANDROID) - // Set seconds until first TCP keep alive. - if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) { - PLOG(ERROR) << "Failed to set TCP_KEEPIDLE on fd: " << fd; - return false; - } - // Set seconds between TCP keep alives. - if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &delay, sizeof(delay))) { - PLOG(ERROR) << "Failed to set TCP_KEEPINTVL on fd: " << fd; - return false; - } -#endif - return true; -} - -// Sets socket parameters. Returns the OS error code (or 0 on -// success). -int SetupSocket(int socket) { - if (SetNonBlocking(socket)) - return errno; - - // This mirrors the behaviour on Windows. See the comment in - // tcp_client_socket_win.cc after searching for "NODELAY". - SetTCPNoDelay(socket, true); // If SetTCPNoDelay fails, we don't care. - SetTCPKeepAlive(socket, true, kTCPKeepAliveSeconds); - - return 0; -} - -// Creates a new socket and sets default parameters for it. Returns -// the OS error code (or 0 on success). -int CreateSocket(int family, int* socket) { - *socket = CreatePlatformSocket(family, SOCK_STREAM, IPPROTO_TCP); - if (*socket == kInvalidSocket) - return errno; - int error = SetupSocket(*socket); - if (error) { - if (HANDLE_EINTR(close(*socket)) < 0) - PLOG(ERROR) << "close"; - *socket = kInvalidSocket; - return error; - } - return 0; -} - -int MapConnectError(int os_error) { - switch (os_error) { - case EACCES: - return ERR_NETWORK_ACCESS_DENIED; - case ETIMEDOUT: - return ERR_CONNECTION_TIMED_OUT; - default: { - int net_error = MapSystemError(os_error); - if (net_error == ERR_FAILED) - return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED. - - // Give a more specific error when the user is offline. - if (net_error == ERR_ADDRESS_UNREACHABLE && - NetworkChangeNotifier::IsOffline()) { - return ERR_INTERNET_DISCONNECTED; - } - return net_error; - } - } -} - -} // namespace - -//----------------------------------------------------------------------------- - -TCPClientSocketLibevent::TCPClientSocketLibevent( - const AddressList& addresses, - net::NetLog* net_log, - const net::NetLog::Source& source) - : socket_(kInvalidSocket), - bound_socket_(kInvalidSocket), - addresses_(addresses), - current_address_index_(-1), - read_watcher_(this), - write_watcher_(this), - next_connect_state_(CONNECT_STATE_NONE), - connect_os_error_(0), - net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)), - previously_disconnected_(false), - use_tcp_fastopen_(IsTCPFastOpenEnabled()), - tcp_fastopen_connected_(false), - fast_open_status_(FAST_OPEN_STATUS_UNKNOWN) { - net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, - source.ToEventParametersCallback()); -} - -TCPClientSocketLibevent::~TCPClientSocketLibevent() { - Disconnect(); - net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); - if (tcp_fastopen_connected_) { - UMA_HISTOGRAM_ENUMERATION("Net.TcpFastOpenSocketConnection", - fast_open_status_, FAST_OPEN_MAX_VALUE); - } -} - -int TCPClientSocketLibevent::AdoptSocket(int socket) { - DCHECK_EQ(socket_, kInvalidSocket); - - int error = SetupSocket(socket); - if (error) - return MapSystemError(error); - - socket_ = socket; - - // This is to make GetPeerAddress() work. It's up to the caller ensure - // that |address_| contains a reasonable address for this - // socket. (i.e. at least match IPv4 vs IPv6!). - current_address_index_ = 0; - use_history_.set_was_ever_connected(); - - return OK; -} - -int TCPClientSocketLibevent::Bind(const IPEndPoint& address) { - if (current_address_index_ >= 0 || bind_address_.get()) { - // Cannot bind the socket if we are already bound connected or - // connecting. - return ERR_UNEXPECTED; - } - - SockaddrStorage storage; - if (!address.ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - - // Create |bound_socket_| and try to bind it to |address|. - int error = CreateSocket(address.GetSockAddrFamily(), &bound_socket_); - if (error) - return MapSystemError(error); - - if (HANDLE_EINTR(bind(bound_socket_, storage.addr, storage.addr_len))) { - error = errno; - if (HANDLE_EINTR(close(bound_socket_)) < 0) - PLOG(ERROR) << "close"; - bound_socket_ = kInvalidSocket; - return MapSystemError(error); - } - - bind_address_.reset(new IPEndPoint(address)); - - return 0; -} - -int TCPClientSocketLibevent::Connect(const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - - // If already connected, then just return OK. - if (socket_ != kInvalidSocket) - return OK; - - base::StatsCounter connects("tcp.connect"); - connects.Increment(); - - DCHECK(!waiting_connect()); - - net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT, - addresses_.CreateNetLogCallback()); - - // We will try to connect to each address in addresses_. Start with the - // first one in the list. - next_connect_state_ = CONNECT_STATE_CONNECT; - current_address_index_ = 0; - - int rv = DoConnectLoop(OK); - if (rv == ERR_IO_PENDING) { - // Synchronous operation not supported. - DCHECK(!callback.is_null()); - write_callback_ = callback; - } else { - LogConnectCompletion(rv); - } - - return rv; -} - -int TCPClientSocketLibevent::DoConnectLoop(int result) { - DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE); - - int rv = result; - do { - ConnectState state = next_connect_state_; - next_connect_state_ = CONNECT_STATE_NONE; - switch (state) { - case CONNECT_STATE_CONNECT: - DCHECK_EQ(OK, rv); - rv = DoConnect(); - break; - case CONNECT_STATE_CONNECT_COMPLETE: - rv = DoConnectComplete(rv); - break; - default: - LOG(DFATAL) << "bad state"; - rv = ERR_UNEXPECTED; - break; - } - } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE); - - return rv; -} - -int TCPClientSocketLibevent::DoConnect() { - DCHECK_GE(current_address_index_, 0); - DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); - DCHECK_EQ(0, connect_os_error_); - - const IPEndPoint& endpoint = addresses_[current_address_index_]; - - if (previously_disconnected_) { - use_history_.Reset(); - previously_disconnected_ = false; - } - - net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, - CreateNetLogIPEndPointCallback(&endpoint)); - - next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE; - - if (bound_socket_ != kInvalidSocket) { - DCHECK(bind_address_.get()); - socket_ = bound_socket_; - bound_socket_ = kInvalidSocket; - } else { - // Create a non-blocking socket. - connect_os_error_ = CreateSocket(endpoint.GetSockAddrFamily(), &socket_); - if (connect_os_error_) - return MapSystemError(connect_os_error_); - - if (bind_address_.get()) { - SockaddrStorage storage; - if (!bind_address_->ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - if (HANDLE_EINTR(bind(socket_, storage.addr, storage.addr_len))) - return MapSystemError(errno); - } - } - - // Connect the socket. - if (!use_tcp_fastopen_) { - SockaddrStorage storage; - if (!endpoint.ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - - if (!HANDLE_EINTR(connect(socket_, storage.addr, storage.addr_len))) { - // Connected without waiting! - return OK; - } - } else { - // With TCP FastOpen, we pretend that the socket is connected. - DCHECK(!tcp_fastopen_connected_); - return OK; - } - - // Check if the connect() failed synchronously. - connect_os_error_ = errno; - if (connect_os_error_ != EINPROGRESS) - return MapConnectError(connect_os_error_); - - // Otherwise the connect() is going to complete asynchronously, so watch - // for its completion. - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - socket_, true, base::MessageLoopForIO::WATCH_WRITE, - &write_socket_watcher_, &write_watcher_)) { - connect_os_error_ = errno; - DVLOG(1) << "WatchFileDescriptor failed: " << connect_os_error_; - return MapSystemError(connect_os_error_); - } - - return ERR_IO_PENDING; -} - -int TCPClientSocketLibevent::DoConnectComplete(int result) { - // Log the end of this attempt (and any OS error it threw). - int os_error = connect_os_error_; - connect_os_error_ = 0; - if (result != OK) { - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, - NetLog::IntegerCallback("os_error", os_error)); - } else { - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT); - } - - if (result == OK) { - write_socket_watcher_.StopWatchingFileDescriptor(); - use_history_.set_was_ever_connected(); - return OK; // Done! - } - - // Close whatever partially connected socket we currently have. - DoDisconnect(); - - // Try to fall back to the next address in the list. - if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) { - next_connect_state_ = CONNECT_STATE_CONNECT; - ++current_address_index_; - return OK; - } - - // Otherwise there is nothing to fall back to, so give up. - return result; -} - -void TCPClientSocketLibevent::Disconnect() { - DCHECK(CalledOnValidThread()); - - DoDisconnect(); - current_address_index_ = -1; - bind_address_.reset(); -} - -void TCPClientSocketLibevent::DoDisconnect() { - if (socket_ == kInvalidSocket) - return; - - bool ok = read_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); - ok = write_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); - if (HANDLE_EINTR(close(socket_)) < 0) - PLOG(ERROR) << "close"; - socket_ = kInvalidSocket; - previously_disconnected_ = true; -} - -bool TCPClientSocketLibevent::IsConnected() const { - DCHECK(CalledOnValidThread()); - - if (socket_ == kInvalidSocket || waiting_connect()) - return false; - - if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { - // With TCP FastOpen, we pretend that the socket is connected. - // This allows GetPeerAddress() to return current_ai_ as the peer - // address. Since we don't fail over to the next address if - // sendto() fails, current_ai_ is the only possible peer address. - CHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); - return true; - } - - // Check if connection is alive. - char c; - int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); - if (rv == 0) - return false; - if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK) - return false; - - return true; -} - -bool TCPClientSocketLibevent::IsConnectedAndIdle() const { - DCHECK(CalledOnValidThread()); - - if (socket_ == kInvalidSocket || waiting_connect()) - return false; - - // TODO(wtc): should we also handle the TCP FastOpen case here, - // as we do in IsConnected()? - - // Check if connection is alive and we haven't received any data - // unexpectedly. - char c; - int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); - if (rv >= 0) - return false; - if (errno != EAGAIN && errno != EWOULDBLOCK) - return false; - - return true; -} - -int TCPClientSocketLibevent::Read(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - DCHECK_NE(kInvalidSocket, socket_); - DCHECK(!waiting_connect()); - DCHECK(read_callback_.is_null()); - // Synchronous operation not supported - DCHECK(!callback.is_null()); - DCHECK_GT(buf_len, 0); - - int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len)); - if (nread >= 0) { - base::StatsCounter read_bytes("tcp.read_bytes"); - read_bytes.Add(nread); - if (nread > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, nread, - buf->data()); - RecordFastOpenStatus(); - return nread; - } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - int net_error = MapSystemError(errno); - net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, - CreateNetLogSocketErrorCallback(net_error, errno)); - return net_error; - } - - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - socket_, true, base::MessageLoopForIO::WATCH_READ, - &read_socket_watcher_, &read_watcher_)) { - DVLOG(1) << "WatchFileDescriptor failed on read, errno " << errno; - return MapSystemError(errno); - } - - read_buf_ = buf; - read_buf_len_ = buf_len; - read_callback_ = callback; - return ERR_IO_PENDING; -} - -int TCPClientSocketLibevent::Write(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - DCHECK_NE(kInvalidSocket, socket_); - DCHECK(!waiting_connect()); - DCHECK(write_callback_.is_null()); - // Synchronous operation not supported - DCHECK(!callback.is_null()); - DCHECK_GT(buf_len, 0); - - int nwrite = InternalWrite(buf, buf_len); - if (nwrite >= 0) { - base::StatsCounter write_bytes("tcp.write_bytes"); - write_bytes.Add(nwrite); - if (nwrite > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, nwrite, - buf->data()); - return nwrite; - } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - int net_error = MapSystemError(errno); - net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, - CreateNetLogSocketErrorCallback(net_error, errno)); - return net_error; - } - - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - socket_, true, base::MessageLoopForIO::WATCH_WRITE, - &write_socket_watcher_, &write_watcher_)) { - DVLOG(1) << "WatchFileDescriptor failed on write, errno " << errno; - return MapSystemError(errno); - } - - write_buf_ = buf; - write_buf_len_ = buf_len; - write_callback_ = callback; - return ERR_IO_PENDING; -} - -int TCPClientSocketLibevent::InternalWrite(IOBuffer* buf, int buf_len) { - int nwrite; - if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { - SockaddrStorage storage; - if (!addresses_[current_address_index_].ToSockAddr(storage.addr, - &storage.addr_len)) { - errno = EINVAL; - return -1; - } - - int flags = 0x20000000; // Magic flag to enable TCP_FASTOPEN. -#if defined(OS_LINUX) - // sendto() will fail with EPIPE when the system doesn't support TCP Fast - // Open. Theoretically that shouldn't happen since the caller should check - // for system support on startup, but users may dynamically disable TCP Fast - // Open via sysctl. - flags |= MSG_NOSIGNAL; -#endif // defined(OS_LINUX) - nwrite = HANDLE_EINTR(sendto(socket_, - buf->data(), - buf_len, - flags, - storage.addr, - storage.addr_len)); - tcp_fastopen_connected_ = true; - - if (nwrite < 0) { - DCHECK_NE(EPIPE, errno); - - // If errno == EINPROGRESS, that means the kernel didn't have a cookie - // and would block. The kernel is internally doing a connect() though. - // Remap EINPROGRESS to EAGAIN so we treat this the same as our other - // asynchronous cases. Note that the user buffer has not been copied to - // kernel space. - if (errno == EINPROGRESS) { - errno = EAGAIN; - fast_open_status_ = FAST_OPEN_SLOW_CONNECT_RETURN; - } else { - fast_open_status_ = FAST_OPEN_ERROR; - } - } else { - fast_open_status_ = FAST_OPEN_FAST_CONNECT_RETURN; - } - } else { - nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len)); - } - return nwrite; -} - -bool TCPClientSocketLibevent::SetReceiveBufferSize(int32 size) { - DCHECK(CalledOnValidThread()); - int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF, - reinterpret_cast<const char*>(&size), - sizeof(size)); - DCHECK(!rv) << "Could not set socket receive buffer size: " << errno; - return rv == 0; -} - -bool TCPClientSocketLibevent::SetSendBufferSize(int32 size) { - DCHECK(CalledOnValidThread()); - int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, - reinterpret_cast<const char*>(&size), - sizeof(size)); - DCHECK(!rv) << "Could not set socket send buffer size: " << errno; - return rv == 0; -} - -bool TCPClientSocketLibevent::SetKeepAlive(bool enable, int delay) { - int socket = socket_ != kInvalidSocket ? socket_ : bound_socket_; - return SetTCPKeepAlive(socket, enable, delay); -} - -bool TCPClientSocketLibevent::SetNoDelay(bool no_delay) { - int socket = socket_ != kInvalidSocket ? socket_ : bound_socket_; - return SetTCPNoDelay(socket, no_delay); -} - -void TCPClientSocketLibevent::ReadWatcher::OnFileCanReadWithoutBlocking(int) { - socket_->RecordFastOpenStatus(); - if (!socket_->read_callback_.is_null()) - socket_->DidCompleteRead(); -} - -void TCPClientSocketLibevent::WriteWatcher::OnFileCanWriteWithoutBlocking(int) { - if (socket_->waiting_connect()) { - socket_->DidCompleteConnect(); - } else if (!socket_->write_callback_.is_null()) { - socket_->DidCompleteWrite(); - } -} - -void TCPClientSocketLibevent::LogConnectCompletion(int net_error) { - if (net_error == OK) - UpdateConnectionTypeHistograms(CONNECTION_ANY); - - if (net_error != OK) { - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error); - return; - } - - SockaddrStorage storage; - int rv = getsockname(socket_, storage.addr, &storage.addr_len); - if (rv != 0) { - PLOG(ERROR) << "getsockname() [rv: " << rv << "] error: "; - NOTREACHED(); - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv); - return; - } - - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT, - CreateNetLogSourceAddressCallback(storage.addr, - storage.addr_len)); -} - -void TCPClientSocketLibevent::DoReadCallback(int rv) { - DCHECK_NE(rv, ERR_IO_PENDING); - DCHECK(!read_callback_.is_null()); - - // since Run may result in Read being called, clear read_callback_ up front. - CompletionCallback c = read_callback_; - read_callback_.Reset(); - c.Run(rv); -} - -void TCPClientSocketLibevent::DoWriteCallback(int rv) { - DCHECK_NE(rv, ERR_IO_PENDING); - DCHECK(!write_callback_.is_null()); - - // since Run may result in Write being called, clear write_callback_ up front. - CompletionCallback c = write_callback_; - write_callback_.Reset(); - c.Run(rv); -} - -void TCPClientSocketLibevent::DidCompleteConnect() { - DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE); - - // Get the error that connect() completed with. - int os_error = 0; - socklen_t len = sizeof(os_error); - if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0) - os_error = errno; - - // TODO(eroman): Is this check really necessary? - if (os_error == EINPROGRESS || os_error == EALREADY) { - NOTREACHED(); // This indicates a bug in libevent or our code. - return; - } - - connect_os_error_ = os_error; - int rv = DoConnectLoop(MapConnectError(os_error)); - if (rv != ERR_IO_PENDING) { - LogConnectCompletion(rv); - DoWriteCallback(rv); - } -} - -void TCPClientSocketLibevent::DidCompleteRead() { - int bytes_transferred; - bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(), - read_buf_len_)); - - int result; - if (bytes_transferred >= 0) { - result = bytes_transferred; - base::StatsCounter read_bytes("tcp.read_bytes"); - read_bytes.Add(bytes_transferred); - if (bytes_transferred > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, result, - read_buf_->data()); - } else { - result = MapSystemError(errno); - if (result != ERR_IO_PENDING) { - net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, - CreateNetLogSocketErrorCallback(result, errno)); - } - } - - if (result != ERR_IO_PENDING) { - read_buf_ = NULL; - read_buf_len_ = 0; - bool ok = read_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); - DoReadCallback(result); - } -} - -void TCPClientSocketLibevent::DidCompleteWrite() { - int bytes_transferred; - bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(), - write_buf_len_)); - - int result; - if (bytes_transferred >= 0) { - result = bytes_transferred; - base::StatsCounter write_bytes("tcp.write_bytes"); - write_bytes.Add(bytes_transferred); - if (bytes_transferred > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, result, - write_buf_->data()); - } else { - result = MapSystemError(errno); - if (result != ERR_IO_PENDING) { - net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, - CreateNetLogSocketErrorCallback(result, errno)); - } - } - - if (result != ERR_IO_PENDING) { - write_buf_ = NULL; - write_buf_len_ = 0; - write_socket_watcher_.StopWatchingFileDescriptor(); - DoWriteCallback(result); - } -} - -int TCPClientSocketLibevent::GetPeerAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - if (!IsConnected()) - return ERR_SOCKET_NOT_CONNECTED; - *address = addresses_[current_address_index_]; - return OK; -} - -int TCPClientSocketLibevent::GetLocalAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - if (socket_ == kInvalidSocket) { - if (bind_address_.get()) { - *address = *bind_address_; - return OK; - } - return ERR_SOCKET_NOT_CONNECTED; - } - - SockaddrStorage storage; - if (getsockname(socket_, storage.addr, &storage.addr_len)) - return MapSystemError(errno); - if (!address->FromSockAddr(storage.addr, storage.addr_len)) - return ERR_FAILED; - - return OK; -} - -void TCPClientSocketLibevent::RecordFastOpenStatus() { - if (use_tcp_fastopen_ && - (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN || - fast_open_status_ == FAST_OPEN_SLOW_CONNECT_RETURN)) { - DCHECK_NE(FAST_OPEN_STATUS_UNKNOWN, fast_open_status_); - bool getsockopt_success(false); - bool server_acked_data(false); -#if defined(TCP_INFO) - // Probe to see the if the socket used TCP Fast Open. - tcp_info info; - socklen_t info_len = sizeof(tcp_info); - getsockopt_success = - getsockopt(socket_, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0 && - info_len == sizeof(tcp_info); - server_acked_data = getsockopt_success && - (info.tcpi_options & TCPI_OPT_SYN_DATA); -#endif - if (getsockopt_success) { - if (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN) { - fast_open_status_ = (server_acked_data ? FAST_OPEN_SYN_DATA_ACK : - FAST_OPEN_SYN_DATA_NACK); - } else { - fast_open_status_ = (server_acked_data ? FAST_OPEN_NO_SYN_DATA_ACK : - FAST_OPEN_NO_SYN_DATA_NACK); - } - } else { - fast_open_status_ = (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN ? - FAST_OPEN_SYN_DATA_FAILED : - FAST_OPEN_NO_SYN_DATA_FAILED); - } - } -} - -const BoundNetLog& TCPClientSocketLibevent::NetLog() const { - return net_log_; -} - -void TCPClientSocketLibevent::SetSubresourceSpeculation() { - use_history_.set_subresource_speculation(); -} - -void TCPClientSocketLibevent::SetOmniboxSpeculation() { - use_history_.set_omnibox_speculation(); -} - -bool TCPClientSocketLibevent::WasEverUsed() const { - return use_history_.was_used_to_convey_data(); -} - -bool TCPClientSocketLibevent::UsingTCPFastOpen() const { - return use_tcp_fastopen_; -} - -bool TCPClientSocketLibevent::WasNpnNegotiated() const { - return false; -} - -NextProto TCPClientSocketLibevent::GetNegotiatedProtocol() const { - return kProtoUnknown; -} - -bool TCPClientSocketLibevent::GetSSLInfo(SSLInfo* ssl_info) { - return false; -} - -} // namespace net diff --git a/net/socket/tcp_client_socket_libevent.h b/net/socket/tcp_client_socket_libevent.h deleted file mode 100644 index e5a0d8deab..0000000000 --- a/net/socket/tcp_client_socket_libevent.h +++ /dev/null @@ -1,256 +0,0 @@ -// 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_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_ -#define NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_ - -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" -#include "base/threading/non_thread_safe.h" -#include "net/base/address_list.h" -#include "net/base/completion_callback.h" -#include "net/base/net_log.h" -#include "net/socket/stream_socket.h" - -namespace net { - -class BoundNetLog; - -// A client socket that uses TCP as the transport layer. -class NET_EXPORT_PRIVATE TCPClientSocketLibevent : public StreamSocket, - public base::NonThreadSafe { - public: - // The IP address(es) and port number to connect to. The TCP socket will try - // each IP address in the list until it succeeds in establishing a - // connection. - TCPClientSocketLibevent(const AddressList& addresses, - net::NetLog* net_log, - const net::NetLog::Source& source); - - virtual ~TCPClientSocketLibevent(); - - // AdoptSocket causes the given, connected socket to be adopted as a TCP - // socket. This object must not be connected. This object takes ownership of - // the given socket and then acts as if Connect() had been called. This - // function is used by TCPServerSocket() to adopt accepted connections - // and for testing. - int AdoptSocket(int socket); - - // Binds the socket to a local IP address and port. - int Bind(const IPEndPoint& address); - - // StreamSocket implementation. - virtual int Connect(const CompletionCallback& callback) OVERRIDE; - virtual void Disconnect() OVERRIDE; - virtual bool IsConnected() const OVERRIDE; - virtual bool IsConnectedAndIdle() const OVERRIDE; - virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE; - virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE; - virtual const BoundNetLog& NetLog() const OVERRIDE; - virtual void SetSubresourceSpeculation() OVERRIDE; - virtual void SetOmniboxSpeculation() OVERRIDE; - virtual bool WasEverUsed() const OVERRIDE; - virtual bool UsingTCPFastOpen() const OVERRIDE; - virtual bool WasNpnNegotiated() const OVERRIDE; - virtual NextProto GetNegotiatedProtocol() const OVERRIDE; - virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; - - // Socket implementation. - // Multiple outstanding requests are not supported. - // Full duplex mode (reading and writing at the same time) is supported - virtual int Read(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) OVERRIDE; - virtual int Write(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) OVERRIDE; - virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; - virtual bool SetSendBufferSize(int32 size) OVERRIDE; - - virtual bool SetKeepAlive(bool enable, int delay); - virtual bool SetNoDelay(bool no_delay); - - private: - // State machine for connecting the socket. - enum ConnectState { - CONNECT_STATE_CONNECT, - CONNECT_STATE_CONNECT_COMPLETE, - CONNECT_STATE_NONE, - }; - - // States that a fast open socket attempt can result in. - enum FastOpenStatus { - FAST_OPEN_STATUS_UNKNOWN, - - // The initial fast open connect attempted returned synchronously, - // indicating that we had and sent a cookie along with the initial data. - FAST_OPEN_FAST_CONNECT_RETURN, - - // The initial fast open connect attempted returned asynchronously, - // indicating that we did not have a cookie for the server. - FAST_OPEN_SLOW_CONNECT_RETURN, - - // Some other error occurred on connection, so we couldn't tell if - // fast open would have worked. - FAST_OPEN_ERROR, - - // An attempt to do a fast open succeeded immediately - // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server - // had acked the data we sent. - FAST_OPEN_SYN_DATA_ACK, - - // An attempt to do a fast open succeeded immediately - // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server - // had nacked the data we sent. - FAST_OPEN_SYN_DATA_NACK, - - // An attempt to do a fast open succeeded immediately - // (FAST_OPEN_FAST_CONNECT_RETURN) and our probe to determine if the - // socket was using fast open failed. - FAST_OPEN_SYN_DATA_FAILED, - - // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) - // and we later confirmed that the server had acked initial data. This - // should never happen (we didn't send data, so it shouldn't have - // been acked). - FAST_OPEN_NO_SYN_DATA_ACK, - - // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) - // and we later discovered that the server had nacked initial data. This - // is the expected case results for FAST_OPEN_SLOW_CONNECT_RETURN. - FAST_OPEN_NO_SYN_DATA_NACK, - - // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) - // and our later probe for ack/nack state failed. - FAST_OPEN_NO_SYN_DATA_FAILED, - - FAST_OPEN_MAX_VALUE - }; - - class ReadWatcher : public base::MessageLoopForIO::Watcher { - public: - explicit ReadWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {} - - // MessageLoopForIO::Watcher methods - - virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE; - - virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE {} - - private: - TCPClientSocketLibevent* const socket_; - - DISALLOW_COPY_AND_ASSIGN(ReadWatcher); - }; - - class WriteWatcher : public base::MessageLoopForIO::Watcher { - public: - explicit WriteWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {} - - // MessageLoopForIO::Watcher implementation. - virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE {} - virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE; - - private: - TCPClientSocketLibevent* const socket_; - - DISALLOW_COPY_AND_ASSIGN(WriteWatcher); - }; - - // State machine used by Connect(). - int DoConnectLoop(int result); - int DoConnect(); - int DoConnectComplete(int result); - - // Helper used by Disconnect(), which disconnects minus the logging and - // resetting of current_address_index_. - void DoDisconnect(); - - void DoReadCallback(int rv); - void DoWriteCallback(int rv); - void DidCompleteRead(); - void DidCompleteWrite(); - void DidCompleteConnect(); - - // Returns true if a Connect() is in progress. - bool waiting_connect() const { - return next_connect_state_ != CONNECT_STATE_NONE; - } - - // Helper to add a TCP_CONNECT (end) event to the NetLog. - void LogConnectCompletion(int net_error); - - // Internal function to write to a socket. - int InternalWrite(IOBuffer* buf, int buf_len); - - // Called when the socket is known to be in a connected state. - void RecordFastOpenStatus(); - - int socket_; - - // Local IP address and port we are bound to. Set to NULL if Bind() - // was't called (in that cases OS chooses address/port). - scoped_ptr<IPEndPoint> bind_address_; - - // Stores bound socket between Bind() and Connect() calls. - int bound_socket_; - - // The list of addresses we should try in order to establish a connection. - AddressList addresses_; - - // Where we are in above list. Set to -1 if uninitialized. - int current_address_index_; - - // The socket's libevent wrappers - base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_; - base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_; - - // The corresponding watchers for reads and writes. - ReadWatcher read_watcher_; - WriteWatcher write_watcher_; - - // The buffer used by OnSocketReady to retry Read requests - scoped_refptr<IOBuffer> read_buf_; - int read_buf_len_; - - // The buffer used by OnSocketReady to retry Write requests - scoped_refptr<IOBuffer> write_buf_; - int write_buf_len_; - - // External callback; called when read is complete. - CompletionCallback read_callback_; - - // External callback; called when write is complete. - CompletionCallback write_callback_; - - // The next state for the Connect() state machine. - ConnectState next_connect_state_; - - // The OS error that CONNECT_STATE_CONNECT last completed with. - int connect_os_error_; - - BoundNetLog net_log_; - - // This socket was previously disconnected and has not been re-connected. - bool previously_disconnected_; - - // Record of connectivity and transmissions, for use in speculative connection - // histograms. - UseHistory use_history_; - - // Enables experimental TCP FastOpen option. - const bool use_tcp_fastopen_; - - // True when TCP FastOpen is in use and we have done the connect. - bool tcp_fastopen_connected_; - - enum FastOpenStatus fast_open_status_; - - DISALLOW_COPY_AND_ASSIGN(TCPClientSocketLibevent); -}; - -} // namespace net - -#endif // NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_ diff --git a/net/socket/tcp_client_socket_win.cc b/net/socket/tcp_client_socket_win.cc deleted file mode 100644 index f1334e78a4..0000000000 --- a/net/socket/tcp_client_socket_win.cc +++ /dev/null @@ -1,956 +0,0 @@ -// 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/socket/tcp_client_socket_win.h" - -#include <mstcpip.h> - -#include "base/basictypes.h" -#include "base/compiler_specific.h" -#include "base/metrics/stats_counters.h" -#include "base/strings/string_util.h" -#include "base/win/object_watcher.h" -#include "base/win/windows_version.h" -#include "net/base/connection_type_histograms.h" -#include "net/base/io_buffer.h" -#include "net/base/ip_endpoint.h" -#include "net/base/net_errors.h" -#include "net/base/net_log.h" -#include "net/base/net_util.h" -#include "net/base/network_change_notifier.h" -#include "net/base/winsock_init.h" -#include "net/base/winsock_util.h" -#include "net/socket/socket_descriptor.h" -#include "net/socket/socket_net_log_params.h" - -namespace net { - -namespace { - -const int kTCPKeepAliveSeconds = 45; -bool g_disable_overlapped_reads = false; - -bool SetSocketReceiveBufferSize(SOCKET socket, int32 size) { - int rv = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, - reinterpret_cast<const char*>(&size), sizeof(size)); - DCHECK(!rv) << "Could not set socket receive buffer size: " << GetLastError(); - return rv == 0; -} - -bool SetSocketSendBufferSize(SOCKET socket, int32 size) { - int rv = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, - reinterpret_cast<const char*>(&size), sizeof(size)); - DCHECK(!rv) << "Could not set socket send buffer size: " << GetLastError(); - return rv == 0; -} - -// Disable Nagle. -// The Nagle implementation on windows is governed by RFC 896. The idea -// behind Nagle is to reduce small packets on the network. When Nagle is -// enabled, if a partial packet has been sent, the TCP stack will disallow -// further *partial* packets until an ACK has been received from the other -// side. Good applications should always strive to send as much data as -// possible and avoid partial-packet sends. However, in most real world -// applications, there are edge cases where this does not happen, and two -// partial packets may be sent back to back. For a browser, it is NEVER -// a benefit to delay for an RTT before the second packet is sent. -// -// As a practical example in Chromium today, consider the case of a small -// POST. I have verified this: -// Client writes 649 bytes of header (partial packet #1) -// Client writes 50 bytes of POST data (partial packet #2) -// In the above example, with Nagle, a RTT delay is inserted between these -// two sends due to nagle. RTTs can easily be 100ms or more. The best -// fix is to make sure that for POSTing data, we write as much data as -// possible and minimize partial packets. We will fix that. But disabling -// Nagle also ensure we don't run into this delay in other edge cases. -// See also: -// http://technet.microsoft.com/en-us/library/bb726981.aspx -bool DisableNagle(SOCKET socket, bool disable) { - BOOL val = disable ? TRUE : FALSE; - int rv = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast<const char*>(&val), - sizeof(val)); - DCHECK(!rv) << "Could not disable nagle"; - return rv == 0; -} - -// Enable TCP Keep-Alive to prevent NAT routers from timing out TCP -// connections. See http://crbug.com/27400 for details. -bool SetTCPKeepAlive(SOCKET socket, BOOL enable, int delay_secs) { - int delay = delay_secs * 1000; - struct tcp_keepalive keepalive_vals = { - enable ? 1 : 0, // TCP keep-alive on. - delay, // Delay seconds before sending first TCP keep-alive packet. - delay, // Delay seconds between sending TCP keep-alive packets. - }; - DWORD bytes_returned = 0xABAB; - int rv = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &keepalive_vals, - sizeof(keepalive_vals), NULL, 0, - &bytes_returned, NULL, NULL); - DCHECK(!rv) << "Could not enable TCP Keep-Alive for socket: " << socket - << " [error: " << WSAGetLastError() << "]."; - - // Disregard any failure in disabling nagle or enabling TCP Keep-Alive. - return rv == 0; -} - -// Sets socket parameters. Returns the OS error code (or 0 on -// success). -int SetupSocket(SOCKET socket) { - // Increase the socket buffer sizes from the default sizes for WinXP. In - // performance testing, there is substantial benefit by increasing from 8KB - // to 64KB. - // See also: - // http://support.microsoft.com/kb/823764/EN-US - // On Vista, if we manually set these sizes, Vista turns off its receive - // window auto-tuning feature. - // http://blogs.msdn.com/wndp/archive/2006/05/05/Winhec-blog-tcpip-2.aspx - // Since Vista's auto-tune is better than any static value we can could set, - // only change these on pre-vista machines. - if (base::win::GetVersion() < base::win::VERSION_VISTA) { - const int32 kSocketBufferSize = 64 * 1024; - SetSocketReceiveBufferSize(socket, kSocketBufferSize); - SetSocketSendBufferSize(socket, kSocketBufferSize); - } - - DisableNagle(socket, true); - SetTCPKeepAlive(socket, true, kTCPKeepAliveSeconds); - return 0; -} - -// Creates a new socket and sets default parameters for it. Returns -// the OS error code (or 0 on success). -int CreateSocket(int family, SOCKET* socket) { - *socket = CreatePlatformSocket(family, SOCK_STREAM, IPPROTO_TCP); - if (*socket == INVALID_SOCKET) { - int os_error = WSAGetLastError(); - LOG(ERROR) << "CreatePlatformSocket failed: " << os_error; - return os_error; - } - int error = SetupSocket(*socket); - if (error) { - if (closesocket(*socket) < 0) - PLOG(ERROR) << "closesocket"; - *socket = INVALID_SOCKET; - return error; - } - return 0; -} - -int MapConnectError(int os_error) { - switch (os_error) { - // connect fails with WSAEACCES when Windows Firewall blocks the - // connection. - case WSAEACCES: - return ERR_NETWORK_ACCESS_DENIED; - case WSAETIMEDOUT: - return ERR_CONNECTION_TIMED_OUT; - default: { - int net_error = MapSystemError(os_error); - if (net_error == ERR_FAILED) - return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED. - - // Give a more specific error when the user is offline. - if (net_error == ERR_ADDRESS_UNREACHABLE && - NetworkChangeNotifier::IsOffline()) { - return ERR_INTERNET_DISCONNECTED; - } - - return net_error; - } - } -} - -} // namespace - -//----------------------------------------------------------------------------- - -// This class encapsulates all the state that has to be preserved as long as -// there is a network IO operation in progress. If the owner TCPClientSocketWin -// is destroyed while an operation is in progress, the Core is detached and it -// lives until the operation completes and the OS doesn't reference any resource -// declared on this class anymore. -class TCPClientSocketWin::Core : public base::RefCounted<Core> { - public: - explicit Core(TCPClientSocketWin* socket); - - // Start watching for the end of a read or write operation. - void WatchForRead(); - void WatchForWrite(); - - // The TCPClientSocketWin is going away. - void Detach() { socket_ = NULL; } - - // The separate OVERLAPPED variables for asynchronous operation. - // |read_overlapped_| is used for both Connect() and Read(). - // |write_overlapped_| is only used for Write(); - OVERLAPPED read_overlapped_; - OVERLAPPED write_overlapped_; - - // The buffers used in Read() and Write(). - scoped_refptr<IOBuffer> read_iobuffer_; - scoped_refptr<IOBuffer> write_iobuffer_; - int read_buffer_length_; - int write_buffer_length_; - - bool non_blocking_reads_initialized_; - - private: - friend class base::RefCounted<Core>; - - class ReadDelegate : public base::win::ObjectWatcher::Delegate { - public: - explicit ReadDelegate(Core* core) : core_(core) {} - virtual ~ReadDelegate() {} - - // base::ObjectWatcher::Delegate methods: - virtual void OnObjectSignaled(HANDLE object); - - private: - Core* const core_; - }; - - class WriteDelegate : public base::win::ObjectWatcher::Delegate { - public: - explicit WriteDelegate(Core* core) : core_(core) {} - virtual ~WriteDelegate() {} - - // base::ObjectWatcher::Delegate methods: - virtual void OnObjectSignaled(HANDLE object); - - private: - Core* const core_; - }; - - ~Core(); - - // The socket that created this object. - TCPClientSocketWin* socket_; - - // |reader_| handles the signals from |read_watcher_|. - ReadDelegate reader_; - // |writer_| handles the signals from |write_watcher_|. - WriteDelegate writer_; - - // |read_watcher_| watches for events from Connect() and Read(). - base::win::ObjectWatcher read_watcher_; - // |write_watcher_| watches for events from Write(); - base::win::ObjectWatcher write_watcher_; - - DISALLOW_COPY_AND_ASSIGN(Core); -}; - -TCPClientSocketWin::Core::Core( - TCPClientSocketWin* socket) - : read_buffer_length_(0), - write_buffer_length_(0), - non_blocking_reads_initialized_(false), - socket_(socket), - reader_(this), - writer_(this) { - memset(&read_overlapped_, 0, sizeof(read_overlapped_)); - memset(&write_overlapped_, 0, sizeof(write_overlapped_)); - - read_overlapped_.hEvent = WSACreateEvent(); - write_overlapped_.hEvent = WSACreateEvent(); -} - -TCPClientSocketWin::Core::~Core() { - // Make sure the message loop is not watching this object anymore. - read_watcher_.StopWatching(); - write_watcher_.StopWatching(); - - WSACloseEvent(read_overlapped_.hEvent); - memset(&read_overlapped_, 0xaf, sizeof(read_overlapped_)); - WSACloseEvent(write_overlapped_.hEvent); - memset(&write_overlapped_, 0xaf, sizeof(write_overlapped_)); -} - -void TCPClientSocketWin::Core::WatchForRead() { - // We grab an extra reference because there is an IO operation in progress. - // Balanced in ReadDelegate::OnObjectSignaled(). - AddRef(); - read_watcher_.StartWatching(read_overlapped_.hEvent, &reader_); -} - -void TCPClientSocketWin::Core::WatchForWrite() { - // We grab an extra reference because there is an IO operation in progress. - // Balanced in WriteDelegate::OnObjectSignaled(). - AddRef(); - write_watcher_.StartWatching(write_overlapped_.hEvent, &writer_); -} - -void TCPClientSocketWin::Core::ReadDelegate::OnObjectSignaled( - HANDLE object) { - DCHECK_EQ(object, core_->read_overlapped_.hEvent); - if (core_->socket_) { - if (core_->socket_->waiting_connect()) - core_->socket_->DidCompleteConnect(); - else - core_->socket_->DidSignalRead(); - } - - core_->Release(); -} - -void TCPClientSocketWin::Core::WriteDelegate::OnObjectSignaled( - HANDLE object) { - DCHECK_EQ(object, core_->write_overlapped_.hEvent); - if (core_->socket_) - core_->socket_->DidCompleteWrite(); - - core_->Release(); -} - -//----------------------------------------------------------------------------- - -TCPClientSocketWin::TCPClientSocketWin(const AddressList& addresses, - net::NetLog* net_log, - const net::NetLog::Source& source) - : socket_(INVALID_SOCKET), - bound_socket_(INVALID_SOCKET), - addresses_(addresses), - current_address_index_(-1), - waiting_read_(false), - waiting_write_(false), - next_connect_state_(CONNECT_STATE_NONE), - connect_os_error_(0), - net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)), - previously_disconnected_(false) { - net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, - source.ToEventParametersCallback()); - EnsureWinsockInit(); -} - -TCPClientSocketWin::~TCPClientSocketWin() { - Disconnect(); - net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); -} - -int TCPClientSocketWin::AdoptSocket(SOCKET socket) { - DCHECK_EQ(socket_, INVALID_SOCKET); - - int error = SetupSocket(socket); - if (error) - return MapSystemError(error); - - socket_ = socket; - SetNonBlocking(socket_); - - core_ = new Core(this); - current_address_index_ = 0; - use_history_.set_was_ever_connected(); - - return OK; -} - -int TCPClientSocketWin::Bind(const IPEndPoint& address) { - if (current_address_index_ >= 0 || bind_address_.get()) { - // Cannot bind the socket if we are already connected or connecting. - return ERR_UNEXPECTED; - } - - SockaddrStorage storage; - if (!address.ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - - // Create |bound_socket_| and try to bind it to |address|. - int error = CreateSocket(address.GetSockAddrFamily(), &bound_socket_); - if (error) - return MapSystemError(error); - - if (bind(bound_socket_, storage.addr, storage.addr_len)) { - error = errno; - if (closesocket(bound_socket_) < 0) - PLOG(ERROR) << "closesocket"; - bound_socket_ = INVALID_SOCKET; - return MapSystemError(error); - } - - bind_address_.reset(new IPEndPoint(address)); - - return 0; -} - - -int TCPClientSocketWin::Connect(const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - - // If already connected, then just return OK. - if (socket_ != INVALID_SOCKET) - return OK; - - base::StatsCounter connects("tcp.connect"); - connects.Increment(); - - net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT, - addresses_.CreateNetLogCallback()); - - // We will try to connect to each address in addresses_. Start with the - // first one in the list. - next_connect_state_ = CONNECT_STATE_CONNECT; - current_address_index_ = 0; - - int rv = DoConnectLoop(OK); - if (rv == ERR_IO_PENDING) { - // Synchronous operation not supported. - DCHECK(!callback.is_null()); - // TODO(ajwong): Is setting read_callback_ the right thing to do here?? - read_callback_ = callback; - } else { - LogConnectCompletion(rv); - } - - return rv; -} - -int TCPClientSocketWin::DoConnectLoop(int result) { - DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE); - - int rv = result; - do { - ConnectState state = next_connect_state_; - next_connect_state_ = CONNECT_STATE_NONE; - switch (state) { - case CONNECT_STATE_CONNECT: - DCHECK_EQ(OK, rv); - rv = DoConnect(); - break; - case CONNECT_STATE_CONNECT_COMPLETE: - rv = DoConnectComplete(rv); - break; - default: - LOG(DFATAL) << "bad state " << state; - rv = ERR_UNEXPECTED; - break; - } - } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE); - - return rv; -} - -int TCPClientSocketWin::DoConnect() { - DCHECK_GE(current_address_index_, 0); - DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); - DCHECK_EQ(0, connect_os_error_); - - const IPEndPoint& endpoint = addresses_[current_address_index_]; - - if (previously_disconnected_) { - use_history_.Reset(); - previously_disconnected_ = false; - } - - net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, - CreateNetLogIPEndPointCallback(&endpoint)); - - next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE; - - if (bound_socket_ != INVALID_SOCKET) { - DCHECK(bind_address_.get()); - socket_ = bound_socket_; - bound_socket_ = INVALID_SOCKET; - } else { - connect_os_error_ = CreateSocket(endpoint.GetSockAddrFamily(), &socket_); - if (connect_os_error_ != 0) - return MapSystemError(connect_os_error_); - - if (bind_address_.get()) { - SockaddrStorage storage; - if (!bind_address_->ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - if (bind(socket_, storage.addr, storage.addr_len)) - return MapSystemError(errno); - } - } - - DCHECK(!core_); - core_ = new Core(this); - // WSAEventSelect sets the socket to non-blocking mode as a side effect. - // Our connect() and recv() calls require that the socket be non-blocking. - WSAEventSelect(socket_, core_->read_overlapped_.hEvent, FD_CONNECT); - - SockaddrStorage storage; - if (!endpoint.ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - if (!connect(socket_, storage.addr, storage.addr_len)) { - // Connected without waiting! - // - // The MSDN page for connect says: - // With a nonblocking socket, the connection attempt cannot be completed - // immediately. In this case, connect will return SOCKET_ERROR, and - // WSAGetLastError will return WSAEWOULDBLOCK. - // which implies that for a nonblocking socket, connect never returns 0. - // It's not documented whether the event object will be signaled or not - // if connect does return 0. So the code below is essentially dead code - // and we don't know if it's correct. - NOTREACHED(); - - if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) - return OK; - } else { - int os_error = WSAGetLastError(); - if (os_error != WSAEWOULDBLOCK) { - LOG(ERROR) << "connect failed: " << os_error; - connect_os_error_ = os_error; - return MapConnectError(os_error); - } - } - - core_->WatchForRead(); - return ERR_IO_PENDING; -} - -int TCPClientSocketWin::DoConnectComplete(int result) { - // Log the end of this attempt (and any OS error it threw). - int os_error = connect_os_error_; - connect_os_error_ = 0; - if (result != OK) { - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, - NetLog::IntegerCallback("os_error", os_error)); - } else { - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT); - } - - if (result == OK) { - use_history_.set_was_ever_connected(); - return OK; // Done! - } - - // Close whatever partially connected socket we currently have. - DoDisconnect(); - - // Try to fall back to the next address in the list. - if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) { - next_connect_state_ = CONNECT_STATE_CONNECT; - ++current_address_index_; - return OK; - } - - // Otherwise there is nothing to fall back to, so give up. - return result; -} - -void TCPClientSocketWin::Disconnect() { - DCHECK(CalledOnValidThread()); - - DoDisconnect(); - current_address_index_ = -1; - bind_address_.reset(); -} - -void TCPClientSocketWin::DoDisconnect() { - DCHECK(CalledOnValidThread()); - - if (socket_ == INVALID_SOCKET) - return; - - // Note: don't use CancelIo to cancel pending IO because it doesn't work - // when there is a Winsock layered service provider. - - // In most socket implementations, closing a socket results in a graceful - // connection shutdown, but in Winsock we have to call shutdown explicitly. - // See the MSDN page "Graceful Shutdown, Linger Options, and Socket Closure" - // at http://msdn.microsoft.com/en-us/library/ms738547.aspx - shutdown(socket_, SD_SEND); - - // This cancels any pending IO. - closesocket(socket_); - socket_ = INVALID_SOCKET; - - if (waiting_connect()) { - // We closed the socket, so this notification will never come. - // From MSDN' WSAEventSelect documentation: - // "Closing a socket with closesocket also cancels the association and - // selection of network events specified in WSAEventSelect for the socket". - core_->Release(); - } - - waiting_read_ = false; - waiting_write_ = false; - - core_->Detach(); - core_ = NULL; - - previously_disconnected_ = true; -} - -bool TCPClientSocketWin::IsConnected() const { - DCHECK(CalledOnValidThread()); - - if (socket_ == INVALID_SOCKET || waiting_connect()) - return false; - - if (waiting_read_) - return true; - - // Check if connection is alive. - char c; - int rv = recv(socket_, &c, 1, MSG_PEEK); - if (rv == 0) - return false; - if (rv == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) - return false; - - return true; -} - -bool TCPClientSocketWin::IsConnectedAndIdle() const { - DCHECK(CalledOnValidThread()); - - if (socket_ == INVALID_SOCKET || waiting_connect()) - return false; - - if (waiting_read_) - return true; - - // Check if connection is alive and we haven't received any data - // unexpectedly. - char c; - int rv = recv(socket_, &c, 1, MSG_PEEK); - if (rv >= 0) - return false; - if (WSAGetLastError() != WSAEWOULDBLOCK) - return false; - - return true; -} - -int TCPClientSocketWin::GetPeerAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - if (!IsConnected()) - return ERR_SOCKET_NOT_CONNECTED; - *address = addresses_[current_address_index_]; - return OK; -} - -int TCPClientSocketWin::GetLocalAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - if (socket_ == INVALID_SOCKET) { - if (bind_address_.get()) { - *address = *bind_address_; - return OK; - } - return ERR_SOCKET_NOT_CONNECTED; - } - - struct sockaddr_storage addr_storage; - socklen_t addr_len = sizeof(addr_storage); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - if (getsockname(socket_, addr, &addr_len)) - return MapSystemError(WSAGetLastError()); - if (!address->FromSockAddr(addr, addr_len)) - return ERR_FAILED; - return OK; -} - -void TCPClientSocketWin::SetSubresourceSpeculation() { - use_history_.set_subresource_speculation(); -} - -void TCPClientSocketWin::SetOmniboxSpeculation() { - use_history_.set_omnibox_speculation(); -} - -bool TCPClientSocketWin::WasEverUsed() const { - return use_history_.was_used_to_convey_data(); -} - -bool TCPClientSocketWin::UsingTCPFastOpen() const { - // Not supported on windows. - return false; -} - -bool TCPClientSocketWin::WasNpnNegotiated() const { - return false; -} - -NextProto TCPClientSocketWin::GetNegotiatedProtocol() const { - return kProtoUnknown; -} - -bool TCPClientSocketWin::GetSSLInfo(SSLInfo* ssl_info) { - return false; -} - -int TCPClientSocketWin::Read(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - DCHECK_NE(socket_, INVALID_SOCKET); - DCHECK(!waiting_read_); - DCHECK(read_callback_.is_null()); - DCHECK(!core_->read_iobuffer_); - - return DoRead(buf, buf_len, callback); -} - -int TCPClientSocketWin::Write(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - DCHECK_NE(socket_, INVALID_SOCKET); - DCHECK(!waiting_write_); - DCHECK(write_callback_.is_null()); - DCHECK_GT(buf_len, 0); - DCHECK(!core_->write_iobuffer_); - - base::StatsCounter writes("tcp.writes"); - writes.Increment(); - - WSABUF write_buffer; - write_buffer.len = buf_len; - write_buffer.buf = buf->data(); - - // TODO(wtc): Remove the assertion after enough testing. - AssertEventNotSignaled(core_->write_overlapped_.hEvent); - DWORD num; - int rv = WSASend(socket_, &write_buffer, 1, &num, 0, - &core_->write_overlapped_, NULL); - if (rv == 0) { - if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) { - rv = static_cast<int>(num); - if (rv > buf_len || rv < 0) { - // It seems that some winsock interceptors report that more was written - // than was available. Treat this as an error. http://crbug.com/27870 - LOG(ERROR) << "Detected broken LSP: Asked to write " << buf_len - << " bytes, but " << rv << " bytes reported."; - return ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES; - } - base::StatsCounter write_bytes("tcp.write_bytes"); - write_bytes.Add(rv); - if (rv > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, rv, - buf->data()); - return rv; - } - } else { - int os_error = WSAGetLastError(); - if (os_error != WSA_IO_PENDING) { - int net_error = MapSystemError(os_error); - net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, - CreateNetLogSocketErrorCallback(net_error, os_error)); - return net_error; - } - } - waiting_write_ = true; - write_callback_ = callback; - core_->write_iobuffer_ = buf; - core_->write_buffer_length_ = buf_len; - core_->WatchForWrite(); - return ERR_IO_PENDING; -} - -bool TCPClientSocketWin::SetReceiveBufferSize(int32 size) { - DCHECK(CalledOnValidThread()); - return SetSocketReceiveBufferSize(socket_, size); -} - -bool TCPClientSocketWin::SetSendBufferSize(int32 size) { - DCHECK(CalledOnValidThread()); - return SetSocketSendBufferSize(socket_, size); -} - -bool TCPClientSocketWin::SetKeepAlive(bool enable, int delay) { - return SetTCPKeepAlive(socket_, enable, delay); -} - -bool TCPClientSocketWin::SetNoDelay(bool no_delay) { - return DisableNagle(socket_, no_delay); -} - -void TCPClientSocketWin::LogConnectCompletion(int net_error) { - if (net_error == OK) - UpdateConnectionTypeHistograms(CONNECTION_ANY); - - if (net_error != OK) { - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error); - return; - } - - struct sockaddr_storage source_address; - socklen_t addrlen = sizeof(source_address); - int rv = getsockname( - socket_, reinterpret_cast<struct sockaddr*>(&source_address), &addrlen); - if (rv != 0) { - LOG(ERROR) << "getsockname() [rv: " << rv - << "] error: " << WSAGetLastError(); - NOTREACHED(); - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv); - return; - } - - net_log_.EndEvent( - NetLog::TYPE_TCP_CONNECT, - CreateNetLogSourceAddressCallback( - reinterpret_cast<const struct sockaddr*>(&source_address), - sizeof(source_address))); -} - -int TCPClientSocketWin::DoRead(IOBuffer* buf, int buf_len, - const CompletionCallback& callback) { - if (!core_->non_blocking_reads_initialized_) { - WSAEventSelect(socket_, core_->read_overlapped_.hEvent, - FD_READ | FD_CLOSE); - core_->non_blocking_reads_initialized_ = true; - } - int rv = recv(socket_, buf->data(), buf_len, 0); - if (rv == SOCKET_ERROR) { - int os_error = WSAGetLastError(); - if (os_error != WSAEWOULDBLOCK) { - int net_error = MapSystemError(os_error); - net_log_.AddEvent( - NetLog::TYPE_SOCKET_READ_ERROR, - CreateNetLogSocketErrorCallback(net_error, os_error)); - return net_error; - } - } else { - base::StatsCounter read_bytes("tcp.read_bytes"); - if (rv > 0) { - use_history_.set_was_used_to_convey_data(); - read_bytes.Add(rv); - } - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, rv, - buf->data()); - return rv; - } - - waiting_read_ = true; - read_callback_ = callback; - core_->read_iobuffer_ = buf; - core_->read_buffer_length_ = buf_len; - core_->WatchForRead(); - return ERR_IO_PENDING; -} - -void TCPClientSocketWin::DoReadCallback(int rv) { - DCHECK_NE(rv, ERR_IO_PENDING); - DCHECK(!read_callback_.is_null()); - - // Since Run may result in Read being called, clear read_callback_ up front. - CompletionCallback c = read_callback_; - read_callback_.Reset(); - c.Run(rv); -} - -void TCPClientSocketWin::DoWriteCallback(int rv) { - DCHECK_NE(rv, ERR_IO_PENDING); - DCHECK(!write_callback_.is_null()); - - // Since Run may result in Write being called, clear write_callback_ up front. - CompletionCallback c = write_callback_; - write_callback_.Reset(); - c.Run(rv); -} - -void TCPClientSocketWin::DidCompleteConnect() { - DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE); - int result; - - WSANETWORKEVENTS events; - int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent, - &events); - int os_error = 0; - if (rv == SOCKET_ERROR) { - NOTREACHED(); - os_error = WSAGetLastError(); - result = MapSystemError(os_error); - } else if (events.lNetworkEvents & FD_CONNECT) { - os_error = events.iErrorCode[FD_CONNECT_BIT]; - result = MapConnectError(os_error); - } else { - NOTREACHED(); - result = ERR_UNEXPECTED; - } - - connect_os_error_ = os_error; - rv = DoConnectLoop(result); - if (rv != ERR_IO_PENDING) { - LogConnectCompletion(rv); - DoReadCallback(rv); - } -} - -void TCPClientSocketWin::DidCompleteWrite() { - DCHECK(waiting_write_); - - DWORD num_bytes, flags; - BOOL ok = WSAGetOverlappedResult(socket_, &core_->write_overlapped_, - &num_bytes, FALSE, &flags); - WSAResetEvent(core_->write_overlapped_.hEvent); - waiting_write_ = false; - int rv; - if (!ok) { - int os_error = WSAGetLastError(); - rv = MapSystemError(os_error); - net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, - CreateNetLogSocketErrorCallback(rv, os_error)); - } else { - rv = static_cast<int>(num_bytes); - if (rv > core_->write_buffer_length_ || rv < 0) { - // It seems that some winsock interceptors report that more was written - // than was available. Treat this as an error. http://crbug.com/27870 - LOG(ERROR) << "Detected broken LSP: Asked to write " - << core_->write_buffer_length_ << " bytes, but " << rv - << " bytes reported."; - rv = ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES; - } else { - base::StatsCounter write_bytes("tcp.write_bytes"); - write_bytes.Add(num_bytes); - if (num_bytes > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, num_bytes, - core_->write_iobuffer_->data()); - } - } - core_->write_iobuffer_ = NULL; - DoWriteCallback(rv); -} - -void TCPClientSocketWin::DidSignalRead() { - DCHECK(waiting_read_); - int os_error = 0; - WSANETWORKEVENTS network_events; - int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent, - &network_events); - if (rv == SOCKET_ERROR) { - os_error = WSAGetLastError(); - rv = MapSystemError(os_error); - } else if (network_events.lNetworkEvents) { - DCHECK_EQ(network_events.lNetworkEvents & ~(FD_READ | FD_CLOSE), 0); - // If network_events.lNetworkEvents is FD_CLOSE and - // network_events.iErrorCode[FD_CLOSE_BIT] is 0, it is a graceful - // connection closure. It is tempting to directly set rv to 0 in - // this case, but the MSDN pages for WSAEventSelect and - // WSAAsyncSelect recommend we still call DoRead(): - // FD_CLOSE should only be posted after all data is read from a - // socket, but an application should check for remaining data upon - // receipt of FD_CLOSE to avoid any possibility of losing data. - // - // If network_events.iErrorCode[FD_READ_BIT] or - // network_events.iErrorCode[FD_CLOSE_BIT] is nonzero, still call - // DoRead() because recv() reports a more accurate error code - // (WSAECONNRESET vs. WSAECONNABORTED) when the connection was - // reset. - rv = DoRead(core_->read_iobuffer_, core_->read_buffer_length_, - read_callback_); - if (rv == ERR_IO_PENDING) - return; - } else { - // This may happen because Read() may succeed synchronously and - // consume all the received data without resetting the event object. - core_->WatchForRead(); - return; - } - waiting_read_ = false; - core_->read_iobuffer_ = NULL; - core_->read_buffer_length_ = 0; - DoReadCallback(rv); -} - -} // namespace net diff --git a/net/socket/tcp_client_socket_win.h b/net/socket/tcp_client_socket_win.h deleted file mode 100644 index c899f27e70..0000000000 --- a/net/socket/tcp_client_socket_win.h +++ /dev/null @@ -1,157 +0,0 @@ -// 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_SOCKET_TCP_CLIENT_SOCKET_WIN_H_ -#define NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_ - -#include <winsock2.h> - -#include "base/memory/scoped_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "net/base/address_list.h" -#include "net/base/completion_callback.h" -#include "net/base/net_log.h" -#include "net/socket/stream_socket.h" - -namespace net { - -class BoundNetLog; - -class NET_EXPORT TCPClientSocketWin : public StreamSocket, - NON_EXPORTED_BASE(base::NonThreadSafe) { - public: - // The IP address(es) and port number to connect to. The TCP socket will try - // each IP address in the list until it succeeds in establishing a - // connection. - TCPClientSocketWin(const AddressList& addresses, - net::NetLog* net_log, - const net::NetLog::Source& source); - - virtual ~TCPClientSocketWin(); - - // AdoptSocket causes the given, connected socket to be adopted as a TCP - // socket. This object must not be connected. This object takes ownership of - // the given socket and then acts as if Connect() had been called. This - // function is used by TCPServerSocket() to adopt accepted connections - // and for testing. - int AdoptSocket(SOCKET socket); - - // Binds the socket to a local IP address and port. - int Bind(const IPEndPoint& address); - - // StreamSocket implementation. - virtual int Connect(const CompletionCallback& callback); - virtual void Disconnect(); - virtual bool IsConnected() const; - virtual bool IsConnectedAndIdle() const; - virtual int GetPeerAddress(IPEndPoint* address) const; - virtual int GetLocalAddress(IPEndPoint* address) const; - virtual const BoundNetLog& NetLog() const { return net_log_; } - virtual void SetSubresourceSpeculation(); - virtual void SetOmniboxSpeculation(); - virtual bool WasEverUsed() const; - virtual bool UsingTCPFastOpen() const; - virtual bool WasNpnNegotiated() const OVERRIDE; - virtual NextProto GetNegotiatedProtocol() const OVERRIDE; - virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; - - // Socket implementation. - // Multiple outstanding requests are not supported. - // Full duplex mode (reading and writing at the same time) is supported - virtual int Read(IOBuffer* buf, int buf_len, - const CompletionCallback& callback); - virtual int Write(IOBuffer* buf, int buf_len, - const CompletionCallback& callback); - - virtual bool SetReceiveBufferSize(int32 size); - virtual bool SetSendBufferSize(int32 size); - - virtual bool SetKeepAlive(bool enable, int delay); - virtual bool SetNoDelay(bool no_delay); - - private: - // State machine for connecting the socket. - enum ConnectState { - CONNECT_STATE_CONNECT, - CONNECT_STATE_CONNECT_COMPLETE, - CONNECT_STATE_NONE, - }; - - class Core; - - // State machine used by Connect(). - int DoConnectLoop(int result); - int DoConnect(); - int DoConnectComplete(int result); - - // Helper used by Disconnect(), which disconnects minus the logging and - // resetting of current_address_index_. - void DoDisconnect(); - - // Returns true if a Connect() is in progress. - bool waiting_connect() const { - return next_connect_state_ != CONNECT_STATE_NONE; - } - - // Called after Connect() has completed with |net_error|. - void LogConnectCompletion(int net_error); - - int DoRead(IOBuffer* buf, int buf_len, const CompletionCallback& callback); - void DoReadCallback(int rv); - void DoWriteCallback(int rv); - void DidCompleteConnect(); - void DidCompleteWrite(); - void DidSignalRead(); - - SOCKET socket_; - - // Local IP address and port we are bound to. Set to NULL if Bind() - // was't called (in that cases OS chooses address/port). - scoped_ptr<IPEndPoint> bind_address_; - - // Stores bound socket between Bind() and Connect() calls. - SOCKET bound_socket_; - - // The list of addresses we should try in order to establish a connection. - AddressList addresses_; - - // Where we are in above list. Set to -1 if uninitialized. - int current_address_index_; - - // The various states that the socket could be in. - bool waiting_read_; - bool waiting_write_; - - // The core of the socket that can live longer than the socket itself. We pass - // resources to the Windows async IO functions and we have to make sure that - // they are not destroyed while the OS still references them. - scoped_refptr<Core> core_; - - // External callback; called when connect or read is complete. - CompletionCallback read_callback_; - - // External callback; called when write is complete. - CompletionCallback write_callback_; - - // The next state for the Connect() state machine. - ConnectState next_connect_state_; - - // The OS error that CONNECT_STATE_CONNECT last completed with. - int connect_os_error_; - - BoundNetLog net_log_; - - // This socket was previously disconnected and has not been re-connected. - bool previously_disconnected_; - - // Record of connectivity and transmissions, for use in speculative connection - // histograms. - UseHistory use_history_; - - DISALLOW_COPY_AND_ASSIGN(TCPClientSocketWin); -}; - -} // namespace net - -#endif // NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_ diff --git a/net/socket/tcp_server_socket.cc b/net/socket/tcp_server_socket.cc index 13e9de1e59..a25f73f6c6 100644 --- a/net/socket/tcp_server_socket.cc +++ b/net/socket/tcp_server_socket.cc @@ -7,7 +7,6 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" -#include "build/build_config.h" #include "net/base/net_errors.h" #include "net/socket/tcp_client_socket.h" @@ -22,7 +21,7 @@ TCPServerSocket::~TCPServerSocket() { } int TCPServerSocket::Listen(const IPEndPoint& address, int backlog) { - int result = socket_.Create(address.GetFamily()); + int result = socket_.Open(address.GetFamily()); if (result != OK) return result; @@ -88,26 +87,9 @@ int TCPServerSocket::ConvertAcceptedSocket( if (result != OK) return result; - scoped_ptr<TCPClientSocket> client_socket(new TCPClientSocket( - AddressList(accepted_address_), - temp_accepted_socket->net_log().net_log(), - temp_accepted_socket->net_log().source())); - // TODO(yzshen): Once we switch TCPClientSocket::AdoptSocket() to take a - // TCPSocket object, we don't need to do platform-specific handling. -#if defined(OS_WIN) - SOCKET raw_socket = temp_accepted_socket->Release(); -#elif defined(OS_POSIX) - int raw_socket = temp_accepted_socket->Release(); -#endif - result = client_socket->AdoptSocket(raw_socket); - if (result != OK) { - // |client_socket| won't take ownership of |raw_socket| on failure. - // Therefore, we put it back into |temp_accepted_socket| to close it. - temp_accepted_socket->Adopt(raw_socket); - return result; - } + output_accepted_socket->reset(new TCPClientSocket( + temp_accepted_socket.Pass(), accepted_address_)); - *output_accepted_socket = client_socket.Pass(); return OK; } diff --git a/net/socket/tcp_socket.cc b/net/socket/tcp_socket.cc new file mode 100644 index 0000000000..fd72f6b464 --- /dev/null +++ b/net/socket/tcp_socket.cc @@ -0,0 +1,59 @@ +// Copyright 2013 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/socket/tcp_socket.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" + +namespace net { + +namespace { + +#if defined(OS_LINUX) + +// Checks to see if the system supports TCP FastOpen. Notably, it requires +// kernel support. Additionally, this checks system configuration to ensure that +// it's enabled. +bool SystemSupportsTCPFastOpen() { + static const base::FilePath::CharType kTCPFastOpenProcFilePath[] = + "/proc/sys/net/ipv4/tcp_fastopen"; + std::string system_enabled_tcp_fastopen; + if (!base::ReadFileToString( + base::FilePath(kTCPFastOpenProcFilePath), + &system_enabled_tcp_fastopen)) { + return false; + } + + // As per http://lxr.linux.no/linux+v3.7.7/include/net/tcp.h#L225 + // TFO_CLIENT_ENABLE is the LSB + if (system_enabled_tcp_fastopen.empty() || + (system_enabled_tcp_fastopen[0] & 0x1) == 0) { + return false; + } + + return true; +} + +#else + +bool SystemSupportsTCPFastOpen() { + return false; +} + +#endif + +bool g_tcp_fastopen_enabled = false; + +} // namespace + +void SetTCPFastOpenEnabled(bool value) { + g_tcp_fastopen_enabled = value && SystemSupportsTCPFastOpen(); +} + +bool IsTCPFastOpenEnabled() { + return g_tcp_fastopen_enabled; +} + +} // namespace net diff --git a/net/socket/tcp_socket.h b/net/socket/tcp_socket.h index aea8f12d65..8b36fade75 100644 --- a/net/socket/tcp_socket.h +++ b/net/socket/tcp_socket.h @@ -6,6 +6,7 @@ #define NET_SOCKET_TCP_SOCKET_H_ #include "build/build_config.h" +#include "net/base/net_export.h" #if defined(OS_WIN) #include "net/socket/tcp_socket_win.h" @@ -15,6 +16,13 @@ namespace net { +// Enable/disable experimental TCP FastOpen option. +// Not thread safe. Must be called during initialization/startup only. +NET_EXPORT void SetTCPFastOpenEnabled(bool value); + +// Check if the TCP FastOpen option is enabled. +bool IsTCPFastOpenEnabled(); + // TCPSocket provides a platform-independent interface for TCP sockets. // // It is recommended to use TCPClientSocket/TCPServerSocket instead of this diff --git a/net/socket/tcp_socket_libevent.cc b/net/socket/tcp_socket_libevent.cc index 0bceaa49a8..66416f7020 100644 --- a/net/socket/tcp_socket_libevent.cc +++ b/net/socket/tcp_socket_libevent.cc @@ -2,46 +2,159 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/socket/tcp_socket_libevent.h" +#include "net/socket/tcp_socket.h" #include <errno.h> #include <fcntl.h> #include <netdb.h> -#include <sys/socket.h> - -#include "build/build_config.h" - -#if defined(OS_POSIX) #include <netinet/in.h> -#endif +#include <netinet/tcp.h> +#include <sys/socket.h> +#include "base/callback_helpers.h" #include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/metrics/stats_counters.h" #include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "net/base/address_list.h" +#include "net/base/connection_type_histograms.h" +#include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" -#include "net/socket/socket_descriptor.h" +#include "net/base/network_change_notifier.h" #include "net/socket/socket_net_log_params.h" +// If we don't have a definition for TCPI_OPT_SYN_DATA, create one. +#ifndef TCPI_OPT_SYN_DATA +#define TCPI_OPT_SYN_DATA 32 +#endif + namespace net { +namespace { + +const int kTCPKeepAliveSeconds = 45; + +// SetTCPNoDelay turns on/off buffering in the kernel. By default, TCP sockets +// will wait up to 200ms for more data to complete a packet before transmitting. +// After calling this function, the kernel will not wait. See TCP_NODELAY in +// `man 7 tcp`. +bool SetTCPNoDelay(int fd, bool no_delay) { + int on = no_delay ? 1 : 0; + int error = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + return error == 0; +} + +// SetTCPKeepAlive sets SO_KEEPALIVE. +bool SetTCPKeepAlive(int fd, bool enable, int delay) { + int on = enable ? 1 : 0; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) { + PLOG(ERROR) << "Failed to set SO_KEEPALIVE on fd: " << fd; + return false; + } +#if defined(OS_LINUX) || defined(OS_ANDROID) + // Set seconds until first TCP keep alive. + if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) { + PLOG(ERROR) << "Failed to set TCP_KEEPIDLE on fd: " << fd; + return false; + } + // Set seconds between TCP keep alives. + if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &delay, sizeof(delay))) { + PLOG(ERROR) << "Failed to set TCP_KEEPINTVL on fd: " << fd; + return false; + } +#endif + return true; +} + +int MapConnectError(int os_error) { + switch (os_error) { + case EACCES: + return ERR_NETWORK_ACCESS_DENIED; + case ETIMEDOUT: + return ERR_CONNECTION_TIMED_OUT; + default: { + int net_error = MapSystemError(os_error); + if (net_error == ERR_FAILED) + return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED. + + // Give a more specific error when the user is offline. + if (net_error == ERR_ADDRESS_UNREACHABLE && + NetworkChangeNotifier::IsOffline()) { + return ERR_INTERNET_DISCONNECTED; + } + return net_error; + } + } +} + +} // namespace + +//----------------------------------------------------------------------------- + +TCPSocketLibevent::Watcher::Watcher( + const base::Closure& read_ready_callback, + const base::Closure& write_ready_callback) + : read_ready_callback_(read_ready_callback), + write_ready_callback_(write_ready_callback) { +} + +TCPSocketLibevent::Watcher::~Watcher() { +} + +void TCPSocketLibevent::Watcher::OnFileCanReadWithoutBlocking(int /* fd */) { + if (!read_ready_callback_.is_null()) + read_ready_callback_.Run(); + else + NOTREACHED(); +} + +void TCPSocketLibevent::Watcher::OnFileCanWriteWithoutBlocking(int /* fd */) { + if (!write_ready_callback_.is_null()) + write_ready_callback_.Run(); + else + NOTREACHED(); +} + TCPSocketLibevent::TCPSocketLibevent(NetLog* net_log, const NetLog::Source& source) : socket_(kInvalidSocket), + accept_watcher_(base::Bind(&TCPSocketLibevent::DidCompleteAccept, + base::Unretained(this)), + base::Closure()), accept_socket_(NULL), accept_address_(NULL), + read_watcher_(base::Bind(&TCPSocketLibevent::DidCompleteRead, + base::Unretained(this)), + base::Closure()), + write_watcher_(base::Closure(), + base::Bind(&TCPSocketLibevent::DidCompleteConnectOrWrite, + base::Unretained(this))), + read_buf_len_(0), + write_buf_len_(0), + use_tcp_fastopen_(IsTCPFastOpenEnabled()), + tcp_fastopen_connected_(false), + fast_open_status_(FAST_OPEN_STATUS_UNKNOWN), + waiting_connect_(false), + connect_os_error_(0), + logging_multiple_connect_attempts_(false), net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) { net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, source.ToEventParametersCallback()); } TCPSocketLibevent::~TCPSocketLibevent() { - if (socket_ != kInvalidSocket) - Close(); net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); + if (tcp_fastopen_connected_) { + UMA_HISTOGRAM_ENUMERATION("Net.TcpFastOpenSocketConnection", + fast_open_status_, FAST_OPEN_MAX_VALUE); + } + Close(); } -int TCPSocketLibevent::Create(AddressFamily family) { +int TCPSocketLibevent::Open(AddressFamily family) { DCHECK(CalledOnValidThread()); DCHECK_EQ(socket_, kInvalidSocket); @@ -61,7 +174,8 @@ int TCPSocketLibevent::Create(AddressFamily family) { return OK; } -int TCPSocketLibevent::Adopt(int socket) { +int TCPSocketLibevent::AdoptConnectedSocket(int socket, + const IPEndPoint& peer_address) { DCHECK(CalledOnValidThread()); DCHECK_EQ(socket_, kInvalidSocket); @@ -73,16 +187,9 @@ int TCPSocketLibevent::Adopt(int socket) { return result; } - return OK; -} - -int TCPSocketLibevent::Release() { - DCHECK(CalledOnValidThread()); - DCHECK(accept_callback_.is_null()); + peer_address_.reset(new IPEndPoint(peer_address)); - int result = socket_; - socket_ = kInvalidSocket; - return result; + return OK; } int TCPSocketLibevent::Bind(const IPEndPoint& address) { @@ -102,19 +209,6 @@ int TCPSocketLibevent::Bind(const IPEndPoint& address) { return OK; } -int TCPSocketLibevent::GetLocalAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - - SockaddrStorage storage; - if (getsockname(socket_, storage.addr, &storage.addr_len) < 0) - return MapSystemError(errno); - if (!address->FromSockAddr(storage.addr, storage.addr_len)) - return ERR_FAILED; - - return OK; -} - int TCPSocketLibevent::Listen(int backlog) { DCHECK(CalledOnValidThread()); DCHECK_GT(backlog, 0); @@ -145,7 +239,7 @@ int TCPSocketLibevent::Accept(scoped_ptr<TCPSocketLibevent>* socket, if (result == ERR_IO_PENDING) { if (!base::MessageLoopForIO::current()->WatchFileDescriptor( socket_, true, base::MessageLoopForIO::WATCH_READ, - &accept_socket_watcher_, this)) { + &accept_socket_watcher_, &accept_watcher_)) { PLOG(ERROR) << "WatchFileDescriptor failed on read"; return MapSystemError(errno); } @@ -158,11 +252,201 @@ int TCPSocketLibevent::Accept(scoped_ptr<TCPSocketLibevent>* socket, return result; } +int TCPSocketLibevent::Connect(const IPEndPoint& address, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(socket_, kInvalidSocket); + DCHECK(!waiting_connect_); + + // |peer_address_| will be non-NULL if Connect() has been called. Unless + // Close() is called to reset the internal state, a second call to Connect() + // is not allowed. + // Please note that we don't allow a second Connect() even if the previous + // Connect() has failed. Connecting the same |socket_| again after a + // connection attempt failed results in unspecified behavior according to + // POSIX. + DCHECK(!peer_address_); + + if (!logging_multiple_connect_attempts_) + LogConnectBegin(AddressList(address)); + + peer_address_.reset(new IPEndPoint(address)); + + int rv = DoConnect(); + if (rv == ERR_IO_PENDING) { + // Synchronous operation not supported. + DCHECK(!callback.is_null()); + write_callback_ = callback; + waiting_connect_ = true; + } else { + DoConnectComplete(rv); + } + + return rv; +} + +bool TCPSocketLibevent::IsConnected() const { + DCHECK(CalledOnValidThread()); + + if (socket_ == kInvalidSocket || waiting_connect_) + return false; + + if (use_tcp_fastopen_ && !tcp_fastopen_connected_ && peer_address_) { + // With TCP FastOpen, we pretend that the socket is connected. + // This allows GetPeerAddress() to return peer_address_. + return true; + } + + // Check if connection is alive. + char c; + int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); + if (rv == 0) + return false; + if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK) + return false; + + return true; +} + +bool TCPSocketLibevent::IsConnectedAndIdle() const { + DCHECK(CalledOnValidThread()); + + if (socket_ == kInvalidSocket || waiting_connect_) + return false; + + // TODO(wtc): should we also handle the TCP FastOpen case here, + // as we do in IsConnected()? + + // Check if connection is alive and we haven't received any data + // unexpectedly. + char c; + int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); + if (rv >= 0) + return false; + if (errno != EAGAIN && errno != EWOULDBLOCK) + return false; + + return true; +} + +int TCPSocketLibevent::Read(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(kInvalidSocket, socket_); + DCHECK(!waiting_connect_); + DCHECK(read_callback_.is_null()); + // Synchronous operation not supported + DCHECK(!callback.is_null()); + DCHECK_GT(buf_len, 0); + + int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len)); + if (nread >= 0) { + base::StatsCounter read_bytes("tcp.read_bytes"); + read_bytes.Add(nread); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, nread, + buf->data()); + RecordFastOpenStatus(); + return nread; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) { + int net_error = MapSystemError(errno); + net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, + CreateNetLogSocketErrorCallback(net_error, errno)); + return net_error; + } + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_READ, + &read_socket_watcher_, &read_watcher_)) { + DVLOG(1) << "WatchFileDescriptor failed on read, errno " << errno; + return MapSystemError(errno); + } + + read_buf_ = buf; + read_buf_len_ = buf_len; + read_callback_ = callback; + return ERR_IO_PENDING; +} + +int TCPSocketLibevent::Write(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(kInvalidSocket, socket_); + DCHECK(!waiting_connect_); + DCHECK(write_callback_.is_null()); + // Synchronous operation not supported + DCHECK(!callback.is_null()); + DCHECK_GT(buf_len, 0); + + int nwrite = InternalWrite(buf, buf_len); + if (nwrite >= 0) { + base::StatsCounter write_bytes("tcp.write_bytes"); + write_bytes.Add(nwrite); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, nwrite, + buf->data()); + return nwrite; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) { + int net_error = MapSystemError(errno); + net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, + CreateNetLogSocketErrorCallback(net_error, errno)); + return net_error; + } + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_WRITE, + &write_socket_watcher_, &write_watcher_)) { + DVLOG(1) << "WatchFileDescriptor failed on write, errno " << errno; + return MapSystemError(errno); + } + + write_buf_ = buf; + write_buf_len_ = buf_len; + write_callback_ = callback; + return ERR_IO_PENDING; +} + +int TCPSocketLibevent::GetLocalAddress(IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + DCHECK(address); + + SockaddrStorage storage; + if (getsockname(socket_, storage.addr, &storage.addr_len) < 0) + return MapSystemError(errno); + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + + return OK; +} + +int TCPSocketLibevent::GetPeerAddress(IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + DCHECK(address); + if (!IsConnected()) + return ERR_SOCKET_NOT_CONNECTED; + *address = *peer_address_; + return OK; +} + int TCPSocketLibevent::SetDefaultOptionsForServer() { + DCHECK(CalledOnValidThread()); return SetAddressReuse(true); } +void TCPSocketLibevent::SetDefaultOptionsForClient() { + DCHECK(CalledOnValidThread()); + + // This mirrors the behaviour on Windows. See the comment in + // tcp_socket_win.cc after searching for "NODELAY". + SetTCPNoDelay(socket_, true); // If SetTCPNoDelay fails, we don't care. + SetTCPKeepAlive(socket_, true, kTCPKeepAliveSeconds); +} + int TCPSocketLibevent::SetAddressReuse(bool allow) { + DCHECK(CalledOnValidThread()); + // SO_REUSEADDR is useful for server sockets to bind to a recently unbound // port. When a socket is closed, the end point changes its state to TIME_WAIT // and wait for 2 MSL (maximum segment lifetime) to ensure the remote peer @@ -184,14 +468,96 @@ int TCPSocketLibevent::SetAddressReuse(bool allow) { return OK; } +bool TCPSocketLibevent::SetReceiveBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF, + reinterpret_cast<const char*>(&size), + sizeof(size)); + DCHECK(!rv) << "Could not set socket receive buffer size: " << errno; + return rv == 0; +} + +bool TCPSocketLibevent::SetSendBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast<const char*>(&size), + sizeof(size)); + DCHECK(!rv) << "Could not set socket send buffer size: " << errno; + return rv == 0; +} + +bool TCPSocketLibevent::SetKeepAlive(bool enable, int delay) { + DCHECK(CalledOnValidThread()); + return SetTCPKeepAlive(socket_, enable, delay); +} + +bool TCPSocketLibevent::SetNoDelay(bool no_delay) { + DCHECK(CalledOnValidThread()); + return SetTCPNoDelay(socket_, no_delay); +} + void TCPSocketLibevent::Close() { + DCHECK(CalledOnValidThread()); + + bool ok = accept_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + ok = read_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + ok = write_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + if (socket_ != kInvalidSocket) { - bool ok = accept_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); if (HANDLE_EINTR(close(socket_)) < 0) PLOG(ERROR) << "close"; socket_ = kInvalidSocket; } + + if (!accept_callback_.is_null()) { + accept_socket_ = NULL; + accept_address_ = NULL; + accept_callback_.Reset(); + } + + if (!read_callback_.is_null()) { + read_buf_ = NULL; + read_buf_len_ = 0; + read_callback_.Reset(); + } + + if (!write_callback_.is_null()) { + write_buf_ = NULL; + write_buf_len_ = 0; + write_callback_.Reset(); + } + + tcp_fastopen_connected_ = false; + fast_open_status_ = FAST_OPEN_STATUS_UNKNOWN; + waiting_connect_ = false; + peer_address_.reset(); + connect_os_error_ = 0; +} + +bool TCPSocketLibevent::UsingTCPFastOpen() const { + return use_tcp_fastopen_; +} + +void TCPSocketLibevent::StartLoggingMultipleConnectAttempts( + const AddressList& addresses) { + if (!logging_multiple_connect_attempts_) { + logging_multiple_connect_attempts_ = true; + LogConnectBegin(addresses); + } else { + NOTREACHED(); + } +} + +void TCPSocketLibevent::EndLoggingMultipleConnectAttempts(int net_error) { + if (logging_multiple_connect_attempts_) { + LogConnectEnd(net_error); + logging_multiple_connect_attempts_ = false; + } else { + NOTREACHED(); + } } int TCPSocketLibevent::AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, @@ -212,12 +578,13 @@ int TCPSocketLibevent::AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, NOTREACHED(); if (HANDLE_EINTR(close(new_socket)) < 0) PLOG(ERROR) << "close"; - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, ERR_FAILED); - return ERR_FAILED; + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, + ERR_ADDRESS_INVALID); + return ERR_ADDRESS_INVALID; } scoped_ptr<TCPSocketLibevent> tcp_socket(new TCPSocketLibevent( net_log_.net_log(), net_log_.source())); - int adopt_result = tcp_socket->Adopt(new_socket); + int adopt_result = tcp_socket->AdoptConnectedSocket(new_socket, ip_end_point); if (adopt_result != OK) { net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, adopt_result); return adopt_result; @@ -229,7 +596,183 @@ int TCPSocketLibevent::AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, return OK; } -void TCPSocketLibevent::OnFileCanReadWithoutBlocking(int fd) { +int TCPSocketLibevent::DoConnect() { + DCHECK_EQ(0, connect_os_error_); + + net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, + CreateNetLogIPEndPointCallback(peer_address_.get())); + + // Connect the socket. + if (!use_tcp_fastopen_) { + SockaddrStorage storage; + if (!peer_address_->ToSockAddr(storage.addr, &storage.addr_len)) + return ERR_INVALID_ARGUMENT; + + if (!HANDLE_EINTR(connect(socket_, storage.addr, storage.addr_len))) { + // Connected without waiting! + return OK; + } + } else { + // With TCP FastOpen, we pretend that the socket is connected. + DCHECK(!tcp_fastopen_connected_); + return OK; + } + + // Check if the connect() failed synchronously. + connect_os_error_ = errno; + if (connect_os_error_ != EINPROGRESS) + return MapConnectError(connect_os_error_); + + // Otherwise the connect() is going to complete asynchronously, so watch + // for its completion. + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_WRITE, + &write_socket_watcher_, &write_watcher_)) { + connect_os_error_ = errno; + DVLOG(1) << "WatchFileDescriptor failed: " << connect_os_error_; + return MapSystemError(connect_os_error_); + } + + return ERR_IO_PENDING; +} + +void TCPSocketLibevent::DoConnectComplete(int result) { + // Log the end of this attempt (and any OS error it threw). + int os_error = connect_os_error_; + connect_os_error_ = 0; + if (result != OK) { + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, + NetLog::IntegerCallback("os_error", os_error)); + } else { + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT); + } + + if (!logging_multiple_connect_attempts_) + LogConnectEnd(result); +} + +void TCPSocketLibevent::LogConnectBegin(const AddressList& addresses) { + base::StatsCounter connects("tcp.connect"); + connects.Increment(); + + net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT, + addresses.CreateNetLogCallback()); +} + +void TCPSocketLibevent::LogConnectEnd(int net_error) { + if (net_error == OK) + UpdateConnectionTypeHistograms(CONNECTION_ANY); + + if (net_error != OK) { + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error); + return; + } + + SockaddrStorage storage; + int rv = getsockname(socket_, storage.addr, &storage.addr_len); + if (rv != 0) { + PLOG(ERROR) << "getsockname() [rv: " << rv << "] error: "; + NOTREACHED(); + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv); + return; + } + + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT, + CreateNetLogSourceAddressCallback(storage.addr, + storage.addr_len)); +} + +void TCPSocketLibevent::DidCompleteRead() { + RecordFastOpenStatus(); + if (read_callback_.is_null()) + return; + + int bytes_transferred; + bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(), + read_buf_len_)); + + int result; + if (bytes_transferred >= 0) { + result = bytes_transferred; + base::StatsCounter read_bytes("tcp.read_bytes"); + read_bytes.Add(bytes_transferred); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, result, + read_buf_->data()); + } else { + result = MapSystemError(errno); + if (result != ERR_IO_PENDING) { + net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, + CreateNetLogSocketErrorCallback(result, errno)); + } + } + + if (result != ERR_IO_PENDING) { + read_buf_ = NULL; + read_buf_len_ = 0; + bool ok = read_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + base::ResetAndReturn(&read_callback_).Run(result); + } +} + +void TCPSocketLibevent::DidCompleteWrite() { + if (write_callback_.is_null()) + return; + + int bytes_transferred; + bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(), + write_buf_len_)); + + int result; + if (bytes_transferred >= 0) { + result = bytes_transferred; + base::StatsCounter write_bytes("tcp.write_bytes"); + write_bytes.Add(bytes_transferred); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, result, + write_buf_->data()); + } else { + result = MapSystemError(errno); + if (result != ERR_IO_PENDING) { + net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, + CreateNetLogSocketErrorCallback(result, errno)); + } + } + + if (result != ERR_IO_PENDING) { + write_buf_ = NULL; + write_buf_len_ = 0; + write_socket_watcher_.StopWatchingFileDescriptor(); + base::ResetAndReturn(&write_callback_).Run(result); + } +} + +void TCPSocketLibevent::DidCompleteConnect() { + DCHECK(waiting_connect_); + + // Get the error that connect() completed with. + int os_error = 0; + socklen_t len = sizeof(os_error); + if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0) + os_error = errno; + + int result = MapConnectError(os_error); + connect_os_error_ = os_error; + if (result != ERR_IO_PENDING) { + DoConnectComplete(result); + waiting_connect_ = false; + write_socket_watcher_.StopWatchingFileDescriptor(); + base::ResetAndReturn(&write_callback_).Run(result); + } +} + +void TCPSocketLibevent::DidCompleteConnectOrWrite() { + if (waiting_connect_) + DidCompleteConnect(); + else + DidCompleteWrite(); +} + +void TCPSocketLibevent::DidCompleteAccept() { DCHECK(CalledOnValidThread()); int result = AcceptInternal(accept_socket_, accept_address_); @@ -244,8 +787,85 @@ void TCPSocketLibevent::OnFileCanReadWithoutBlocking(int fd) { } } -void TCPSocketLibevent::OnFileCanWriteWithoutBlocking(int fd) { - NOTREACHED(); +int TCPSocketLibevent::InternalWrite(IOBuffer* buf, int buf_len) { + int nwrite; + if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { + SockaddrStorage storage; + if (!peer_address_->ToSockAddr(storage.addr, &storage.addr_len)) { + errno = EINVAL; + return -1; + } + + int flags = 0x20000000; // Magic flag to enable TCP_FASTOPEN. +#if defined(OS_LINUX) + // sendto() will fail with EPIPE when the system doesn't support TCP Fast + // Open. Theoretically that shouldn't happen since the caller should check + // for system support on startup, but users may dynamically disable TCP Fast + // Open via sysctl. + flags |= MSG_NOSIGNAL; +#endif // defined(OS_LINUX) + nwrite = HANDLE_EINTR(sendto(socket_, + buf->data(), + buf_len, + flags, + storage.addr, + storage.addr_len)); + tcp_fastopen_connected_ = true; + + if (nwrite < 0) { + DCHECK_NE(EPIPE, errno); + + // If errno == EINPROGRESS, that means the kernel didn't have a cookie + // and would block. The kernel is internally doing a connect() though. + // Remap EINPROGRESS to EAGAIN so we treat this the same as our other + // asynchronous cases. Note that the user buffer has not been copied to + // kernel space. + if (errno == EINPROGRESS) { + errno = EAGAIN; + fast_open_status_ = FAST_OPEN_SLOW_CONNECT_RETURN; + } else { + fast_open_status_ = FAST_OPEN_ERROR; + } + } else { + fast_open_status_ = FAST_OPEN_FAST_CONNECT_RETURN; + } + } else { + nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len)); + } + return nwrite; +} + +void TCPSocketLibevent::RecordFastOpenStatus() { + if (use_tcp_fastopen_ && + (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN || + fast_open_status_ == FAST_OPEN_SLOW_CONNECT_RETURN)) { + DCHECK_NE(FAST_OPEN_STATUS_UNKNOWN, fast_open_status_); + bool getsockopt_success(false); + bool server_acked_data(false); +#if defined(TCP_INFO) + // Probe to see the if the socket used TCP Fast Open. + tcp_info info; + socklen_t info_len = sizeof(tcp_info); + getsockopt_success = + getsockopt(socket_, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0 && + info_len == sizeof(tcp_info); + server_acked_data = getsockopt_success && + (info.tcpi_options & TCPI_OPT_SYN_DATA); +#endif + if (getsockopt_success) { + if (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN) { + fast_open_status_ = (server_acked_data ? FAST_OPEN_SYN_DATA_ACK : + FAST_OPEN_SYN_DATA_NACK); + } else { + fast_open_status_ = (server_acked_data ? FAST_OPEN_NO_SYN_DATA_ACK : + FAST_OPEN_NO_SYN_DATA_NACK); + } + } else { + fast_open_status_ = (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN ? + FAST_OPEN_SYN_DATA_FAILED : + FAST_OPEN_NO_SYN_DATA_FAILED); + } + } } } // namespace net diff --git a/net/socket/tcp_socket_libevent.h b/net/socket/tcp_socket_libevent.h index 91a3738f9c..a50caf0ad5 100644 --- a/net/socket/tcp_socket_libevent.h +++ b/net/socket/tcp_socket_libevent.h @@ -6,7 +6,9 @@ #define NET_SOCKET_TCP_SOCKET_LIBEVENT_H_ #include "base/basictypes.h" +#include "base/callback.h" #include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/threading/non_thread_safe.h" @@ -14,54 +16,215 @@ #include "net/base/completion_callback.h" #include "net/base/net_export.h" #include "net/base/net_log.h" +#include "net/socket/socket_descriptor.h" namespace net { +class AddressList; +class IOBuffer; class IPEndPoint; -// TODO(yzshen): This class is incomplete. TCP client operations (Connect/Read/ -// Write/etc.) will be added. And TCPClientSocket will be changed to be a -// wrapper around TCPSocket. -class NET_EXPORT TCPSocketLibevent : public base::NonThreadSafe, - public base::MessageLoopForIO::Watcher { +class NET_EXPORT TCPSocketLibevent : public base::NonThreadSafe { public: TCPSocketLibevent(NetLog* net_log, const NetLog::Source& source); virtual ~TCPSocketLibevent(); - int Create(AddressFamily family); + int Open(AddressFamily family); // Takes ownership of |socket|. - int Adopt(int socket); - // Returns a socket file descriptor. The ownership is transferred to the - // caller. - int Release(); + int AdoptConnectedSocket(int socket, const IPEndPoint& peer_address); + int Bind(const IPEndPoint& address); - int GetLocalAddress(IPEndPoint* address) const; + int Listen(int backlog); int Accept(scoped_ptr<TCPSocketLibevent>* socket, IPEndPoint* address, const CompletionCallback& callback); + + int Connect(const IPEndPoint& address, const CompletionCallback& callback); + bool IsConnected() const; + bool IsConnectedAndIdle() const; + + // Multiple outstanding requests are not supported. + // Full duplex mode (reading and writing at the same time) is supported. + int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + + int GetLocalAddress(IPEndPoint* address) const; + int GetPeerAddress(IPEndPoint* address) const; + + // Sets various socket options. + // The commonly used options for server listening sockets: + // - SetAddressReuse(true). int SetDefaultOptionsForServer(); + // The commonly used options for client sockets and accepted sockets: + // - SetNoDelay(true); + // - SetKeepAlive(true, 45). + void SetDefaultOptionsForClient(); int SetAddressReuse(bool allow); + bool SetReceiveBufferSize(int32 size); + bool SetSendBufferSize(int32 size); + bool SetKeepAlive(bool enable, int delay); + bool SetNoDelay(bool no_delay); + void Close(); - const BoundNetLog& net_log() const { return net_log_; } + bool UsingTCPFastOpen() const; + bool IsValid() const { return socket_ != kInvalidSocket; } + + // Marks the start/end of a series of connect attempts for logging purpose. + // + // TCPClientSocket may attempt to connect to multiple addresses until it + // succeeds in establishing a connection. The corresponding log will have + // multiple NetLog::TYPE_TCP_CONNECT_ATTEMPT entries nested within a + // NetLog::TYPE_TCP_CONNECT. These methods set the start/end of + // NetLog::TYPE_TCP_CONNECT. + // + // TODO(yzshen): Change logging format and let TCPClientSocket log the + // start/end of a series of connect attempts itself. + void StartLoggingMultipleConnectAttempts(const AddressList& addresses); + void EndLoggingMultipleConnectAttempts(int net_error); - // MessageLoopForIO::Watcher implementation. - virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; - virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + const BoundNetLog& net_log() const { return net_log_; } private: + // States that a fast open socket attempt can result in. + enum FastOpenStatus { + FAST_OPEN_STATUS_UNKNOWN, + + // The initial fast open connect attempted returned synchronously, + // indicating that we had and sent a cookie along with the initial data. + FAST_OPEN_FAST_CONNECT_RETURN, + + // The initial fast open connect attempted returned asynchronously, + // indicating that we did not have a cookie for the server. + FAST_OPEN_SLOW_CONNECT_RETURN, + + // Some other error occurred on connection, so we couldn't tell if + // fast open would have worked. + FAST_OPEN_ERROR, + + // An attempt to do a fast open succeeded immediately + // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server + // had acked the data we sent. + FAST_OPEN_SYN_DATA_ACK, + + // An attempt to do a fast open succeeded immediately + // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server + // had nacked the data we sent. + FAST_OPEN_SYN_DATA_NACK, + + // An attempt to do a fast open succeeded immediately + // (FAST_OPEN_FAST_CONNECT_RETURN) and our probe to determine if the + // socket was using fast open failed. + FAST_OPEN_SYN_DATA_FAILED, + + // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) + // and we later confirmed that the server had acked initial data. This + // should never happen (we didn't send data, so it shouldn't have + // been acked). + FAST_OPEN_NO_SYN_DATA_ACK, + + // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) + // and we later discovered that the server had nacked initial data. This + // is the expected case results for FAST_OPEN_SLOW_CONNECT_RETURN. + FAST_OPEN_NO_SYN_DATA_NACK, + + // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) + // and our later probe for ack/nack state failed. + FAST_OPEN_NO_SYN_DATA_FAILED, + + FAST_OPEN_MAX_VALUE + }; + + // Watcher simply forwards notifications to Closure objects set via the + // constructor. + class Watcher: public base::MessageLoopForIO::Watcher { + public: + Watcher(const base::Closure& read_ready_callback, + const base::Closure& write_ready_callback); + virtual ~Watcher(); + + // base::MessageLoopForIO::Watcher methods. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + base::Closure read_ready_callback_; + base::Closure write_ready_callback_; + + DISALLOW_COPY_AND_ASSIGN(Watcher); + }; + int AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, IPEndPoint* address); + int DoConnect(); + void DoConnectComplete(int result); + + void LogConnectBegin(const AddressList& addresses); + void LogConnectEnd(int net_error); + + void DidCompleteRead(); + void DidCompleteWrite(); + void DidCompleteConnect(); + void DidCompleteConnectOrWrite(); + void DidCompleteAccept(); + + // Internal function to write to a socket. Returns an OS error. + int InternalWrite(IOBuffer* buf, int buf_len); + + // Called when the socket is known to be in a connected state. + void RecordFastOpenStatus(); + int socket_; base::MessageLoopForIO::FileDescriptorWatcher accept_socket_watcher_; + Watcher accept_watcher_; scoped_ptr<TCPSocketLibevent>* accept_socket_; IPEndPoint* accept_address_; CompletionCallback accept_callback_; + // The socket's libevent wrappers for reads and writes. + base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_; + base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_; + + // The corresponding watchers for reads and writes. + Watcher read_watcher_; + Watcher write_watcher_; + + // The buffer used for reads. + scoped_refptr<IOBuffer> read_buf_; + int read_buf_len_; + + // The buffer used for writes. + scoped_refptr<IOBuffer> write_buf_; + int write_buf_len_; + + // External callback; called when read is complete. + CompletionCallback read_callback_; + + // External callback; called when write or connect is complete. + CompletionCallback write_callback_; + + // Enables experimental TCP FastOpen option. + const bool use_tcp_fastopen_; + + // True when TCP FastOpen is in use and we have done the connect. + bool tcp_fastopen_connected_; + + FastOpenStatus fast_open_status_; + + // A connect operation is pending. In this case, |write_callback_| needs to be + // called when connect is complete. + bool waiting_connect_; + + scoped_ptr<IPEndPoint> peer_address_; + // The OS error that a connect attempt last completed with. + int connect_os_error_; + + bool logging_multiple_connect_attempts_; + BoundNetLog net_log_; DISALLOW_COPY_AND_ASSIGN(TCPSocketLibevent); diff --git a/net/socket/tcp_socket_unittest.cc b/net/socket/tcp_socket_unittest.cc index e20bdd8758..a45fcba016 100644 --- a/net/socket/tcp_socket_unittest.cc +++ b/net/socket/tcp_socket_unittest.cc @@ -4,10 +4,15 @@ #include "net/socket/tcp_socket.h" +#include <string.h> + #include <string> +#include <vector> +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "net/base/address_list.h" +#include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" @@ -29,7 +34,7 @@ class TCPSocketTest : public PlatformTest { IPEndPoint address; ParseAddress("127.0.0.1", 0, &address); - ASSERT_EQ(OK, socket_.Create(ADDRESS_FAMILY_IPV4)); + ASSERT_EQ(OK, socket_.Open(ADDRESS_FAMILY_IPV4)); ASSERT_EQ(OK, socket_.Bind(address)); ASSERT_EQ(OK, socket_.Listen(kListenBacklog)); ASSERT_EQ(OK, socket_.GetLocalAddress(&local_address_)); @@ -40,7 +45,7 @@ class TCPSocketTest : public PlatformTest { IPEndPoint address; ParseAddress("::1", 0, &address); - if (socket_.Create(ADDRESS_FAMILY_IPV6) != OK || + if (socket_.Open(ADDRESS_FAMILY_IPV6) != OK || socket_.Bind(address) != OK || socket_.Listen(kListenBacklog) != OK) { LOG(ERROR) << "Failed to listen on ::1 - probably because IPv6 is " @@ -194,5 +199,65 @@ TEST_F(TCPSocketTest, AcceptIPv6) { EXPECT_EQ(OK, connect_callback.WaitForResult()); } +TEST_F(TCPSocketTest, ReadWrite) { + ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4()); + + TestCompletionCallback connect_callback; + TCPSocket connecting_socket(NULL, NetLog::Source()); + int result = connecting_socket.Open(ADDRESS_FAMILY_IPV4); + ASSERT_EQ(OK, result); + connecting_socket.Connect(local_address_, connect_callback.callback()); + + TestCompletionCallback accept_callback; + scoped_ptr<TCPSocket> accepted_socket; + IPEndPoint accepted_address; + result = socket_.Accept(&accepted_socket, &accepted_address, + accept_callback.callback()); + ASSERT_EQ(OK, accept_callback.GetResult(result)); + + ASSERT_TRUE(accepted_socket.get()); + + // Both sockets should be on the loopback network interface. + EXPECT_EQ(accepted_address.address(), local_address_.address()); + + EXPECT_EQ(OK, connect_callback.WaitForResult()); + + const std::string message("test message"); + std::vector<char> buffer(message.size()); + + size_t bytes_written = 0; + while (bytes_written < message.size()) { + scoped_refptr<IOBufferWithSize> write_buffer( + new IOBufferWithSize(message.size() - bytes_written)); + memmove(write_buffer->data(), message.data() + bytes_written, + message.size() - bytes_written); + + TestCompletionCallback write_callback; + int write_result = accepted_socket->Write( + write_buffer.get(), write_buffer->size(), write_callback.callback()); + write_result = write_callback.GetResult(write_result); + ASSERT_TRUE(write_result >= 0); + bytes_written += write_result; + ASSERT_TRUE(bytes_written <= message.size()); + } + + size_t bytes_read = 0; + while (bytes_read < message.size()) { + scoped_refptr<IOBufferWithSize> read_buffer( + new IOBufferWithSize(message.size() - bytes_read)); + TestCompletionCallback read_callback; + int read_result = connecting_socket.Read( + read_buffer.get(), read_buffer->size(), read_callback.callback()); + read_result = read_callback.GetResult(read_result); + ASSERT_TRUE(read_result >= 0); + ASSERT_TRUE(bytes_read + read_result <= message.size()); + memmove(&buffer[bytes_read], read_buffer->data(), read_result); + bytes_read += read_result; + } + + std::string received_message(buffer.begin(), buffer.end()); + ASSERT_EQ(message, received_message); +} + } // namespace } // namespace net diff --git a/net/socket/tcp_socket_win.cc b/net/socket/tcp_socket_win.cc index f69c7609e7..7d76232f96 100644 --- a/net/socket/tcp_socket_win.cc +++ b/net/socket/tcp_socket_win.cc @@ -6,10 +6,17 @@ #include <mstcpip.h> +#include "base/callback_helpers.h" #include "base/logging.h" +#include "base/metrics/stats_counters.h" +#include "base/win/windows_version.h" +#include "net/base/address_list.h" +#include "net/base/connection_type_histograms.h" +#include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/base/network_change_notifier.h" #include "net/base/winsock_init.h" #include "net/base/winsock_util.h" #include "net/socket/socket_descriptor.h" @@ -17,12 +24,251 @@ namespace net { +namespace { + +const int kTCPKeepAliveSeconds = 45; + +bool SetSocketReceiveBufferSize(SOCKET socket, int32 size) { + int rv = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, + reinterpret_cast<const char*>(&size), sizeof(size)); + DCHECK(!rv) << "Could not set socket receive buffer size: " << GetLastError(); + return rv == 0; +} + +bool SetSocketSendBufferSize(SOCKET socket, int32 size) { + int rv = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast<const char*>(&size), sizeof(size)); + DCHECK(!rv) << "Could not set socket send buffer size: " << GetLastError(); + return rv == 0; +} + +// Disable Nagle. +// The Nagle implementation on windows is governed by RFC 896. The idea +// behind Nagle is to reduce small packets on the network. When Nagle is +// enabled, if a partial packet has been sent, the TCP stack will disallow +// further *partial* packets until an ACK has been received from the other +// side. Good applications should always strive to send as much data as +// possible and avoid partial-packet sends. However, in most real world +// applications, there are edge cases where this does not happen, and two +// partial packets may be sent back to back. For a browser, it is NEVER +// a benefit to delay for an RTT before the second packet is sent. +// +// As a practical example in Chromium today, consider the case of a small +// POST. I have verified this: +// Client writes 649 bytes of header (partial packet #1) +// Client writes 50 bytes of POST data (partial packet #2) +// In the above example, with Nagle, a RTT delay is inserted between these +// two sends due to nagle. RTTs can easily be 100ms or more. The best +// fix is to make sure that for POSTing data, we write as much data as +// possible and minimize partial packets. We will fix that. But disabling +// Nagle also ensure we don't run into this delay in other edge cases. +// See also: +// http://technet.microsoft.com/en-us/library/bb726981.aspx +bool DisableNagle(SOCKET socket, bool disable) { + BOOL val = disable ? TRUE : FALSE; + int rv = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast<const char*>(&val), + sizeof(val)); + DCHECK(!rv) << "Could not disable nagle"; + return rv == 0; +} + +// Enable TCP Keep-Alive to prevent NAT routers from timing out TCP +// connections. See http://crbug.com/27400 for details. +bool SetTCPKeepAlive(SOCKET socket, BOOL enable, int delay_secs) { + int delay = delay_secs * 1000; + struct tcp_keepalive keepalive_vals = { + enable ? 1 : 0, // TCP keep-alive on. + delay, // Delay seconds before sending first TCP keep-alive packet. + delay, // Delay seconds between sending TCP keep-alive packets. + }; + DWORD bytes_returned = 0xABAB; + int rv = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &keepalive_vals, + sizeof(keepalive_vals), NULL, 0, + &bytes_returned, NULL, NULL); + DCHECK(!rv) << "Could not enable TCP Keep-Alive for socket: " << socket + << " [error: " << WSAGetLastError() << "]."; + + // Disregard any failure in disabling nagle or enabling TCP Keep-Alive. + return rv == 0; +} + +int MapConnectError(int os_error) { + switch (os_error) { + // connect fails with WSAEACCES when Windows Firewall blocks the + // connection. + case WSAEACCES: + return ERR_NETWORK_ACCESS_DENIED; + case WSAETIMEDOUT: + return ERR_CONNECTION_TIMED_OUT; + default: { + int net_error = MapSystemError(os_error); + if (net_error == ERR_FAILED) + return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED. + + // Give a more specific error when the user is offline. + if (net_error == ERR_ADDRESS_UNREACHABLE && + NetworkChangeNotifier::IsOffline()) { + return ERR_INTERNET_DISCONNECTED; + } + + return net_error; + } + } +} + +} // namespace + +//----------------------------------------------------------------------------- + +// This class encapsulates all the state that has to be preserved as long as +// there is a network IO operation in progress. If the owner TCPSocketWin is +// destroyed while an operation is in progress, the Core is detached and it +// lives until the operation completes and the OS doesn't reference any resource +// declared on this class anymore. +class TCPSocketWin::Core : public base::RefCounted<Core> { + public: + explicit Core(TCPSocketWin* socket); + + // Start watching for the end of a read or write operation. + void WatchForRead(); + void WatchForWrite(); + + // The TCPSocketWin is going away. + void Detach() { socket_ = NULL; } + + // The separate OVERLAPPED variables for asynchronous operation. + // |read_overlapped_| is used for both Connect() and Read(). + // |write_overlapped_| is only used for Write(); + OVERLAPPED read_overlapped_; + OVERLAPPED write_overlapped_; + + // The buffers used in Read() and Write(). + scoped_refptr<IOBuffer> read_iobuffer_; + scoped_refptr<IOBuffer> write_iobuffer_; + int read_buffer_length_; + int write_buffer_length_; + + bool non_blocking_reads_initialized_; + + private: + friend class base::RefCounted<Core>; + + class ReadDelegate : public base::win::ObjectWatcher::Delegate { + public: + explicit ReadDelegate(Core* core) : core_(core) {} + virtual ~ReadDelegate() {} + + // base::ObjectWatcher::Delegate methods: + virtual void OnObjectSignaled(HANDLE object); + + private: + Core* const core_; + }; + + class WriteDelegate : public base::win::ObjectWatcher::Delegate { + public: + explicit WriteDelegate(Core* core) : core_(core) {} + virtual ~WriteDelegate() {} + + // base::ObjectWatcher::Delegate methods: + virtual void OnObjectSignaled(HANDLE object); + + private: + Core* const core_; + }; + + ~Core(); + + // The socket that created this object. + TCPSocketWin* socket_; + + // |reader_| handles the signals from |read_watcher_|. + ReadDelegate reader_; + // |writer_| handles the signals from |write_watcher_|. + WriteDelegate writer_; + + // |read_watcher_| watches for events from Connect() and Read(). + base::win::ObjectWatcher read_watcher_; + // |write_watcher_| watches for events from Write(); + base::win::ObjectWatcher write_watcher_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +TCPSocketWin::Core::Core(TCPSocketWin* socket) + : read_buffer_length_(0), + write_buffer_length_(0), + non_blocking_reads_initialized_(false), + socket_(socket), + reader_(this), + writer_(this) { + memset(&read_overlapped_, 0, sizeof(read_overlapped_)); + memset(&write_overlapped_, 0, sizeof(write_overlapped_)); + + read_overlapped_.hEvent = WSACreateEvent(); + write_overlapped_.hEvent = WSACreateEvent(); +} + +TCPSocketWin::Core::~Core() { + // Make sure the message loop is not watching this object anymore. + read_watcher_.StopWatching(); + write_watcher_.StopWatching(); + + WSACloseEvent(read_overlapped_.hEvent); + memset(&read_overlapped_, 0xaf, sizeof(read_overlapped_)); + WSACloseEvent(write_overlapped_.hEvent); + memset(&write_overlapped_, 0xaf, sizeof(write_overlapped_)); +} + +void TCPSocketWin::Core::WatchForRead() { + // We grab an extra reference because there is an IO operation in progress. + // Balanced in ReadDelegate::OnObjectSignaled(). + AddRef(); + read_watcher_.StartWatching(read_overlapped_.hEvent, &reader_); +} + +void TCPSocketWin::Core::WatchForWrite() { + // We grab an extra reference because there is an IO operation in progress. + // Balanced in WriteDelegate::OnObjectSignaled(). + AddRef(); + write_watcher_.StartWatching(write_overlapped_.hEvent, &writer_); +} + +void TCPSocketWin::Core::ReadDelegate::OnObjectSignaled(HANDLE object) { + DCHECK_EQ(object, core_->read_overlapped_.hEvent); + if (core_->socket_) { + if (core_->socket_->waiting_connect_) + core_->socket_->DidCompleteConnect(); + else + core_->socket_->DidSignalRead(); + } + + core_->Release(); +} + +void TCPSocketWin::Core::WriteDelegate::OnObjectSignaled( + HANDLE object) { + DCHECK_EQ(object, core_->write_overlapped_.hEvent); + if (core_->socket_) + core_->socket_->DidCompleteWrite(); + + core_->Release(); +} + +//----------------------------------------------------------------------------- + TCPSocketWin::TCPSocketWin(net::NetLog* net_log, const net::NetLog::Source& source) : socket_(INVALID_SOCKET), - socket_event_(WSA_INVALID_EVENT), + accept_event_(WSA_INVALID_EVENT), accept_socket_(NULL), accept_address_(NULL), + waiting_connect_(false), + waiting_read_(false), + waiting_write_(false), + connect_os_error_(0), + logging_multiple_connect_attempts_(false), net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) { net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, source.ToEventParametersCallback()); @@ -34,7 +280,7 @@ TCPSocketWin::~TCPSocketWin() { net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); } -int TCPSocketWin::Create(AddressFamily family) { +int TCPSocketWin::Open(AddressFamily family) { DCHECK(CalledOnValidThread()); DCHECK_EQ(socket_, INVALID_SOCKET); @@ -54,9 +300,11 @@ int TCPSocketWin::Create(AddressFamily family) { return OK; } -int TCPSocketWin::Adopt(SOCKET socket) { +int TCPSocketWin::AdoptConnectedSocket(SOCKET socket, + const IPEndPoint& peer_address) { DCHECK(CalledOnValidThread()); DCHECK_EQ(socket_, INVALID_SOCKET); + DCHECK(!core_); socket_ = socket; @@ -66,17 +314,10 @@ int TCPSocketWin::Adopt(SOCKET socket) { return result; } - return OK; -} - -SOCKET TCPSocketWin::Release() { - DCHECK(CalledOnValidThread()); - DCHECK_EQ(socket_event_, WSA_INVALID_EVENT); - DCHECK(accept_callback_.is_null()); + core_ = new Core(this); + peer_address_.reset(new IPEndPoint(peer_address)); - SOCKET result = socket_; - socket_ = INVALID_SOCKET; - return result; + return OK; } int TCPSocketWin::Bind(const IPEndPoint& address) { @@ -96,36 +337,22 @@ int TCPSocketWin::Bind(const IPEndPoint& address) { return OK; } -int TCPSocketWin::GetLocalAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - - SockaddrStorage storage; - if (getsockname(socket_, storage.addr, &storage.addr_len)) - return MapSystemError(WSAGetLastError()); - if (!address->FromSockAddr(storage.addr, storage.addr_len)) - return ERR_FAILED; - - return OK; -} - int TCPSocketWin::Listen(int backlog) { DCHECK(CalledOnValidThread()); DCHECK_GT(backlog, 0); DCHECK_NE(socket_, INVALID_SOCKET); - DCHECK_EQ(socket_event_, WSA_INVALID_EVENT); + DCHECK_EQ(accept_event_, WSA_INVALID_EVENT); - socket_event_ = WSACreateEvent(); - if (socket_event_ == WSA_INVALID_EVENT) { + accept_event_ = WSACreateEvent(); + if (accept_event_ == WSA_INVALID_EVENT) { PLOG(ERROR) << "WSACreateEvent()"; - return ERR_FAILED; + return MapSystemError(WSAGetLastError()); } int result = listen(socket_, backlog); if (result < 0) { PLOG(ERROR) << "listen() returned an error"; - result = MapSystemError(WSAGetLastError()); - return result; + return MapSystemError(WSAGetLastError()); } return OK; @@ -146,8 +373,8 @@ int TCPSocketWin::Accept(scoped_ptr<TCPSocketWin>* socket, if (result == ERR_IO_PENDING) { // Start watching. - WSAEventSelect(socket_, socket_event_, FD_ACCEPT); - accept_watcher_.StartWatching(socket_event_, this); + WSAEventSelect(socket_, accept_event_, FD_ACCEPT); + accept_watcher_.StartWatching(accept_event_, this); accept_socket_ = socket; accept_address_ = address; @@ -157,10 +384,195 @@ int TCPSocketWin::Accept(scoped_ptr<TCPSocketWin>* socket, return result; } +int TCPSocketWin::Connect(const IPEndPoint& address, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(socket_, INVALID_SOCKET); + DCHECK(!waiting_connect_); + + // |peer_address_| and |core_| will be non-NULL if Connect() has been called. + // Unless Close() is called to reset the internal state, a second call to + // Connect() is not allowed. + // Please note that we enforce this even if the previous Connect() has + // completed and failed. Although it is allowed to connect the same |socket_| + // again after a connection attempt failed on Windows, it results in + // unspecified behavior according to POSIX. Therefore, we make it behave in + // the same way as TCPSocketLibevent. + DCHECK(!peer_address_ && !core_); + + if (!logging_multiple_connect_attempts_) + LogConnectBegin(AddressList(address)); + + peer_address_.reset(new IPEndPoint(address)); + + int rv = DoConnect(); + if (rv == ERR_IO_PENDING) { + // Synchronous operation not supported. + DCHECK(!callback.is_null()); + read_callback_ = callback; + waiting_connect_ = true; + } else { + DoConnectComplete(rv); + } + + return rv; +} + +bool TCPSocketWin::IsConnected() const { + DCHECK(CalledOnValidThread()); + + if (socket_ == INVALID_SOCKET || waiting_connect_) + return false; + + if (waiting_read_) + return true; + + // Check if connection is alive. + char c; + int rv = recv(socket_, &c, 1, MSG_PEEK); + if (rv == 0) + return false; + if (rv == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) + return false; + + return true; +} + +bool TCPSocketWin::IsConnectedAndIdle() const { + DCHECK(CalledOnValidThread()); + + if (socket_ == INVALID_SOCKET || waiting_connect_) + return false; + + if (waiting_read_) + return true; + + // Check if connection is alive and we haven't received any data + // unexpectedly. + char c; + int rv = recv(socket_, &c, 1, MSG_PEEK); + if (rv >= 0) + return false; + if (WSAGetLastError() != WSAEWOULDBLOCK) + return false; + + return true; +} + +int TCPSocketWin::Read(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(socket_, INVALID_SOCKET); + DCHECK(!waiting_read_); + DCHECK(read_callback_.is_null()); + DCHECK(!core_->read_iobuffer_); + + return DoRead(buf, buf_len, callback); +} + +int TCPSocketWin::Write(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(socket_, INVALID_SOCKET); + DCHECK(!waiting_write_); + DCHECK(write_callback_.is_null()); + DCHECK_GT(buf_len, 0); + DCHECK(!core_->write_iobuffer_); + + base::StatsCounter writes("tcp.writes"); + writes.Increment(); + + WSABUF write_buffer; + write_buffer.len = buf_len; + write_buffer.buf = buf->data(); + + // TODO(wtc): Remove the assertion after enough testing. + AssertEventNotSignaled(core_->write_overlapped_.hEvent); + DWORD num; + int rv = WSASend(socket_, &write_buffer, 1, &num, 0, + &core_->write_overlapped_, NULL); + if (rv == 0) { + if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) { + rv = static_cast<int>(num); + if (rv > buf_len || rv < 0) { + // It seems that some winsock interceptors report that more was written + // than was available. Treat this as an error. http://crbug.com/27870 + LOG(ERROR) << "Detected broken LSP: Asked to write " << buf_len + << " bytes, but " << rv << " bytes reported."; + return ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES; + } + base::StatsCounter write_bytes("tcp.write_bytes"); + write_bytes.Add(rv); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, rv, + buf->data()); + return rv; + } + } else { + int os_error = WSAGetLastError(); + if (os_error != WSA_IO_PENDING) { + int net_error = MapSystemError(os_error); + net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, + CreateNetLogSocketErrorCallback(net_error, os_error)); + return net_error; + } + } + waiting_write_ = true; + write_callback_ = callback; + core_->write_iobuffer_ = buf; + core_->write_buffer_length_ = buf_len; + core_->WatchForWrite(); + return ERR_IO_PENDING; +} + +int TCPSocketWin::GetLocalAddress(IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + DCHECK(address); + + SockaddrStorage storage; + if (getsockname(socket_, storage.addr, &storage.addr_len)) + return MapSystemError(WSAGetLastError()); + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + + return OK; +} + +int TCPSocketWin::GetPeerAddress(IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + DCHECK(address); + if (!IsConnected()) + return ERR_SOCKET_NOT_CONNECTED; + *address = *peer_address_; + return OK; +} + int TCPSocketWin::SetDefaultOptionsForServer() { return SetExclusiveAddrUse(); } +void TCPSocketWin::SetDefaultOptionsForClient() { + // Increase the socket buffer sizes from the default sizes for WinXP. In + // performance testing, there is substantial benefit by increasing from 8KB + // to 64KB. + // See also: + // http://support.microsoft.com/kb/823764/EN-US + // On Vista, if we manually set these sizes, Vista turns off its receive + // window auto-tuning feature. + // http://blogs.msdn.com/wndp/archive/2006/05/05/Winhec-blog-tcpip-2.aspx + // Since Vista's auto-tune is better than any static value we can could set, + // only change these on pre-vista machines. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + const int32 kSocketBufferSize = 64 * 1024; + SetSocketReceiveBufferSize(socket_, kSocketBufferSize); + SetSocketSendBufferSize(socket_, kSocketBufferSize); + } + + DisableNagle(socket_, true); + SetTCPKeepAlive(socket_, true, kTCPKeepAliveSeconds); +} + int TCPSocketWin::SetExclusiveAddrUse() { // On Windows, a bound end point can be hijacked by another process by // setting SO_REUSEADDR. Therefore a Windows-only option SO_EXCLUSIVEADDRUSE @@ -187,16 +599,99 @@ int TCPSocketWin::SetExclusiveAddrUse() { return OK; } +bool TCPSocketWin::SetReceiveBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + return SetSocketReceiveBufferSize(socket_, size); +} + +bool TCPSocketWin::SetSendBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + return SetSocketSendBufferSize(socket_, size); +} + +bool TCPSocketWin::SetKeepAlive(bool enable, int delay) { + return SetTCPKeepAlive(socket_, enable, delay); +} + +bool TCPSocketWin::SetNoDelay(bool no_delay) { + return DisableNagle(socket_, no_delay); +} + void TCPSocketWin::Close() { + DCHECK(CalledOnValidThread()); + if (socket_ != INVALID_SOCKET) { + // Note: don't use CancelIo to cancel pending IO because it doesn't work + // when there is a Winsock layered service provider. + + // In most socket implementations, closing a socket results in a graceful + // connection shutdown, but in Winsock we have to call shutdown explicitly. + // See the MSDN page "Graceful Shutdown, Linger Options, and Socket Closure" + // at http://msdn.microsoft.com/en-us/library/ms738547.aspx + shutdown(socket_, SD_SEND); + + // This cancels any pending IO. if (closesocket(socket_) < 0) PLOG(ERROR) << "closesocket"; socket_ = INVALID_SOCKET; } - if (socket_event_) { - WSACloseEvent(socket_event_); - socket_event_ = WSA_INVALID_EVENT; + if (accept_event_) { + WSACloseEvent(accept_event_); + accept_event_ = WSA_INVALID_EVENT; + } + + if (!accept_callback_.is_null()) { + accept_watcher_.StopWatching(); + accept_socket_ = NULL; + accept_address_ = NULL; + accept_callback_.Reset(); + } + + if (core_) { + if (waiting_connect_) { + // We closed the socket, so this notification will never come. + // From MSDN' WSAEventSelect documentation: + // "Closing a socket with closesocket also cancels the association and + // selection of network events specified in WSAEventSelect for the + // socket". + core_->Release(); + } + core_->Detach(); + core_ = NULL; + } + + waiting_connect_ = false; + waiting_read_ = false; + waiting_write_ = false; + + read_callback_.Reset(); + write_callback_.Reset(); + peer_address_.reset(); + connect_os_error_ = 0; +} + +bool TCPSocketWin::UsingTCPFastOpen() const { + // Not supported on windows. + return false; +} + +void TCPSocketWin::StartLoggingMultipleConnectAttempts( + const AddressList& addresses) { + if (!logging_multiple_connect_attempts_) { + logging_multiple_connect_attempts_ = true; + LogConnectBegin(addresses); + } else { + NOTREACHED(); + } +} + +void TCPSocketWin::EndLoggingMultipleConnectAttempts(int net_error) { + if (logging_multiple_connect_attempts_) { + LogConnectEnd(net_error); + logging_multiple_connect_attempts_ = false; + } else { + NOTREACHED(); } } @@ -221,7 +716,7 @@ int TCPSocketWin::AcceptInternal(scoped_ptr<TCPSocketWin>* socket, } scoped_ptr<TCPSocketWin> tcp_socket(new TCPSocketWin( net_log_.net_log(), net_log_.source())); - int adopt_result = tcp_socket->Adopt(new_socket); + int adopt_result = tcp_socket->AdoptConnectedSocket(new_socket, ip_end_point); if (adopt_result != OK) { net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, adopt_result); return adopt_result; @@ -235,7 +730,7 @@ int TCPSocketWin::AcceptInternal(scoped_ptr<TCPSocketWin>* socket, void TCPSocketWin::OnObjectSignaled(HANDLE object) { WSANETWORKEVENTS ev; - if (WSAEnumNetworkEvents(socket_, socket_event_, &ev) == SOCKET_ERROR) { + if (WSAEnumNetworkEvents(socket_, accept_event_, &ev) == SOCKET_ERROR) { PLOG(ERROR) << "WSAEnumNetworkEvents()"; return; } @@ -245,11 +740,253 @@ void TCPSocketWin::OnObjectSignaled(HANDLE object) { if (result != ERR_IO_PENDING) { accept_socket_ = NULL; accept_address_ = NULL; - CompletionCallback callback = accept_callback_; - accept_callback_.Reset(); - callback.Run(result); + base::ResetAndReturn(&accept_callback_).Run(result); + } + } +} + +int TCPSocketWin::DoConnect() { + DCHECK_EQ(connect_os_error_, 0); + DCHECK(!core_); + + net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, + CreateNetLogIPEndPointCallback(peer_address_.get())); + + core_ = new Core(this); + // WSAEventSelect sets the socket to non-blocking mode as a side effect. + // Our connect() and recv() calls require that the socket be non-blocking. + WSAEventSelect(socket_, core_->read_overlapped_.hEvent, FD_CONNECT); + + SockaddrStorage storage; + if (!peer_address_->ToSockAddr(storage.addr, &storage.addr_len)) + return ERR_INVALID_ARGUMENT; + if (!connect(socket_, storage.addr, storage.addr_len)) { + // Connected without waiting! + // + // The MSDN page for connect says: + // With a nonblocking socket, the connection attempt cannot be completed + // immediately. In this case, connect will return SOCKET_ERROR, and + // WSAGetLastError will return WSAEWOULDBLOCK. + // which implies that for a nonblocking socket, connect never returns 0. + // It's not documented whether the event object will be signaled or not + // if connect does return 0. So the code below is essentially dead code + // and we don't know if it's correct. + NOTREACHED(); + + if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) + return OK; + } else { + int os_error = WSAGetLastError(); + if (os_error != WSAEWOULDBLOCK) { + LOG(ERROR) << "connect failed: " << os_error; + connect_os_error_ = os_error; + int rv = MapConnectError(os_error); + CHECK_NE(ERR_IO_PENDING, rv); + return rv; } } + + core_->WatchForRead(); + return ERR_IO_PENDING; +} + +void TCPSocketWin::DoConnectComplete(int result) { + // Log the end of this attempt (and any OS error it threw). + int os_error = connect_os_error_; + connect_os_error_ = 0; + if (result != OK) { + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, + NetLog::IntegerCallback("os_error", os_error)); + } else { + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT); + } + + if (!logging_multiple_connect_attempts_) + LogConnectEnd(result); +} + +void TCPSocketWin::LogConnectBegin(const AddressList& addresses) { + base::StatsCounter connects("tcp.connect"); + connects.Increment(); + + net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT, + addresses.CreateNetLogCallback()); +} + +void TCPSocketWin::LogConnectEnd(int net_error) { + if (net_error == OK) + UpdateConnectionTypeHistograms(CONNECTION_ANY); + + if (net_error != OK) { + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error); + return; + } + + struct sockaddr_storage source_address; + socklen_t addrlen = sizeof(source_address); + int rv = getsockname( + socket_, reinterpret_cast<struct sockaddr*>(&source_address), &addrlen); + if (rv != 0) { + LOG(ERROR) << "getsockname() [rv: " << rv + << "] error: " << WSAGetLastError(); + NOTREACHED(); + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv); + return; + } + + net_log_.EndEvent( + NetLog::TYPE_TCP_CONNECT, + CreateNetLogSourceAddressCallback( + reinterpret_cast<const struct sockaddr*>(&source_address), + sizeof(source_address))); +} + +int TCPSocketWin::DoRead(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) { + if (!core_->non_blocking_reads_initialized_) { + WSAEventSelect(socket_, core_->read_overlapped_.hEvent, + FD_READ | FD_CLOSE); + core_->non_blocking_reads_initialized_ = true; + } + int rv = recv(socket_, buf->data(), buf_len, 0); + if (rv == SOCKET_ERROR) { + int os_error = WSAGetLastError(); + if (os_error != WSAEWOULDBLOCK) { + int net_error = MapSystemError(os_error); + net_log_.AddEvent( + NetLog::TYPE_SOCKET_READ_ERROR, + CreateNetLogSocketErrorCallback(net_error, os_error)); + return net_error; + } + } else { + base::StatsCounter read_bytes("tcp.read_bytes"); + if (rv > 0) + read_bytes.Add(rv); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, rv, + buf->data()); + return rv; + } + + waiting_read_ = true; + read_callback_ = callback; + core_->read_iobuffer_ = buf; + core_->read_buffer_length_ = buf_len; + core_->WatchForRead(); + return ERR_IO_PENDING; +} + +void TCPSocketWin::DidCompleteConnect() { + DCHECK(waiting_connect_); + DCHECK(!read_callback_.is_null()); + int result; + + WSANETWORKEVENTS events; + int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent, + &events); + int os_error = 0; + if (rv == SOCKET_ERROR) { + NOTREACHED(); + os_error = WSAGetLastError(); + result = MapSystemError(os_error); + } else if (events.lNetworkEvents & FD_CONNECT) { + os_error = events.iErrorCode[FD_CONNECT_BIT]; + result = MapConnectError(os_error); + } else { + NOTREACHED(); + result = ERR_UNEXPECTED; + } + + connect_os_error_ = os_error; + DoConnectComplete(result); + waiting_connect_ = false; + + DCHECK_NE(result, ERR_IO_PENDING); + base::ResetAndReturn(&read_callback_).Run(result); +} + +void TCPSocketWin::DidCompleteWrite() { + DCHECK(waiting_write_); + DCHECK(!write_callback_.is_null()); + + DWORD num_bytes, flags; + BOOL ok = WSAGetOverlappedResult(socket_, &core_->write_overlapped_, + &num_bytes, FALSE, &flags); + WSAResetEvent(core_->write_overlapped_.hEvent); + waiting_write_ = false; + int rv; + if (!ok) { + int os_error = WSAGetLastError(); + rv = MapSystemError(os_error); + net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, + CreateNetLogSocketErrorCallback(rv, os_error)); + } else { + rv = static_cast<int>(num_bytes); + if (rv > core_->write_buffer_length_ || rv < 0) { + // It seems that some winsock interceptors report that more was written + // than was available. Treat this as an error. http://crbug.com/27870 + LOG(ERROR) << "Detected broken LSP: Asked to write " + << core_->write_buffer_length_ << " bytes, but " << rv + << " bytes reported."; + rv = ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES; + } else { + base::StatsCounter write_bytes("tcp.write_bytes"); + write_bytes.Add(num_bytes); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, num_bytes, + core_->write_iobuffer_->data()); + } + } + + core_->write_iobuffer_ = NULL; + + DCHECK_NE(rv, ERR_IO_PENDING); + base::ResetAndReturn(&write_callback_).Run(rv); +} + +void TCPSocketWin::DidSignalRead() { + DCHECK(waiting_read_); + DCHECK(!read_callback_.is_null()); + + int os_error = 0; + WSANETWORKEVENTS network_events; + int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent, + &network_events); + if (rv == SOCKET_ERROR) { + os_error = WSAGetLastError(); + rv = MapSystemError(os_error); + } else if (network_events.lNetworkEvents) { + DCHECK_EQ(network_events.lNetworkEvents & ~(FD_READ | FD_CLOSE), 0); + // If network_events.lNetworkEvents is FD_CLOSE and + // network_events.iErrorCode[FD_CLOSE_BIT] is 0, it is a graceful + // connection closure. It is tempting to directly set rv to 0 in + // this case, but the MSDN pages for WSAEventSelect and + // WSAAsyncSelect recommend we still call DoRead(): + // FD_CLOSE should only be posted after all data is read from a + // socket, but an application should check for remaining data upon + // receipt of FD_CLOSE to avoid any possibility of losing data. + // + // If network_events.iErrorCode[FD_READ_BIT] or + // network_events.iErrorCode[FD_CLOSE_BIT] is nonzero, still call + // DoRead() because recv() reports a more accurate error code + // (WSAECONNRESET vs. WSAECONNABORTED) when the connection was + // reset. + rv = DoRead(core_->read_iobuffer_, core_->read_buffer_length_, + read_callback_); + if (rv == ERR_IO_PENDING) + return; + } else { + // This may happen because Read() may succeed synchronously and + // consume all the received data without resetting the event object. + core_->WatchForRead(); + return; + } + + waiting_read_ = false; + core_->read_iobuffer_ = NULL; + core_->read_buffer_length_ = 0; + + DCHECK_NE(rv, ERR_IO_PENDING); + base::ResetAndReturn(&read_callback_).Run(rv); } } // namespace net + diff --git a/net/socket/tcp_socket_win.h b/net/socket/tcp_socket_win.h index 044e5e0585..df5fbf09ae 100644 --- a/net/socket/tcp_socket_win.h +++ b/net/socket/tcp_socket_win.h @@ -9,8 +9,8 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" #include "base/threading/non_thread_safe.h" #include "base/win/object_watcher.h" #include "net/base/address_family.h" @@ -20,50 +20,125 @@ namespace net { +class AddressList; +class IOBuffer; class IPEndPoint; -// TODO(yzshen): This class is incomplete. TCP client operations (Connect/Read/ -// Write/etc.) will be added. And TCPClientSocket will be changed to be a -// wrapper around TCPSocket. class NET_EXPORT TCPSocketWin : NON_EXPORTED_BASE(public base::NonThreadSafe), public base::win::ObjectWatcher::Delegate { public: TCPSocketWin(NetLog* net_log, const NetLog::Source& source); virtual ~TCPSocketWin(); - int Create(AddressFamily family); + int Open(AddressFamily family); // Takes ownership of |socket|. - int Adopt(SOCKET socket); - // Returns a socket descriptor. The ownership is transferred to the caller. - SOCKET Release(); + int AdoptConnectedSocket(SOCKET socket, const IPEndPoint& peer_address); + int Bind(const IPEndPoint& address); - int GetLocalAddress(IPEndPoint* address) const; + int Listen(int backlog); int Accept(scoped_ptr<TCPSocketWin>* socket, IPEndPoint* address, const CompletionCallback& callback); + + int Connect(const IPEndPoint& address, const CompletionCallback& callback); + bool IsConnected() const; + bool IsConnectedAndIdle() const; + + // Multiple outstanding requests are not supported. + // Full duplex mode (reading and writing at the same time) is supported. + int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + + int GetLocalAddress(IPEndPoint* address) const; + int GetPeerAddress(IPEndPoint* address) const; + + // Sets various socket options. + // The commonly used options for server listening sockets: + // - SetExclusiveAddrUse(). int SetDefaultOptionsForServer(); + // The commonly used options for client sockets and accepted sockets: + // - Increase the socket buffer sizes for WinXP; + // - SetNoDelay(true); + // - SetKeepAlive(true, 45). + void SetDefaultOptionsForClient(); int SetExclusiveAddrUse(); + bool SetReceiveBufferSize(int32 size); + bool SetSendBufferSize(int32 size); + bool SetKeepAlive(bool enable, int delay); + bool SetNoDelay(bool no_delay); + void Close(); + bool UsingTCPFastOpen() const; + bool IsValid() const { return socket_ != INVALID_SOCKET; } + + // Marks the start/end of a series of connect attempts for logging purpose. + // + // TCPClientSocket may attempt to connect to multiple addresses until it + // succeeds in establishing a connection. The corresponding log will have + // multiple NetLog::TYPE_TCP_CONNECT_ATTEMPT entries nested within a + // NetLog::TYPE_TCP_CONNECT. These methods set the start/end of + // NetLog::TYPE_TCP_CONNECT. + // + // TODO(yzshen): Change logging format and let TCPClientSocket log the + // start/end of a series of connect attempts itself. + void StartLoggingMultipleConnectAttempts(const AddressList& addresses); + void EndLoggingMultipleConnectAttempts(int net_error); + const BoundNetLog& net_log() const { return net_log_; } + private: + class Core; + // base::ObjectWatcher::Delegate implementation. virtual void OnObjectSignaled(HANDLE object) OVERRIDE; - private: int AcceptInternal(scoped_ptr<TCPSocketWin>* socket, IPEndPoint* address); + int DoConnect(); + void DoConnectComplete(int result); + + void LogConnectBegin(const AddressList& addresses); + void LogConnectEnd(int net_error); + + int DoRead(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + void DidCompleteConnect(); + void DidCompleteWrite(); + void DidSignalRead(); + SOCKET socket_; - HANDLE socket_event_; + HANDLE accept_event_; base::win::ObjectWatcher accept_watcher_; scoped_ptr<TCPSocketWin>* accept_socket_; IPEndPoint* accept_address_; CompletionCallback accept_callback_; + // The various states that the socket could be in. + bool waiting_connect_; + bool waiting_read_; + bool waiting_write_; + + // The core of the socket that can live longer than the socket itself. We pass + // resources to the Windows async IO functions and we have to make sure that + // they are not destroyed while the OS still references them. + scoped_refptr<Core> core_; + + // External callback; called when connect or read is complete. + CompletionCallback read_callback_; + + // External callback; called when write is complete. + CompletionCallback write_callback_; + + scoped_ptr<IPEndPoint> peer_address_; + // The OS error that a connect attempt last completed with. + int connect_os_error_; + + bool logging_multiple_connect_attempts_; + BoundNetLog net_log_; DISALLOW_COPY_AND_ASSIGN(TCPSocketWin); @@ -72,3 +147,4 @@ class NET_EXPORT TCPSocketWin : NON_EXPORTED_BASE(public base::NonThreadSafe), } // namespace net #endif // NET_SOCKET_TCP_SOCKET_WIN_H_ + diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc index 88c7e239fa..d03e3e651a 100644 --- a/net/socket/transport_client_socket_pool.cc +++ b/net/socket/transport_client_socket_pool.cc @@ -101,10 +101,11 @@ LoadState TransportConnectJob::GetLoadState() const { case STATE_TRANSPORT_CONNECT: case STATE_TRANSPORT_CONNECT_COMPLETE: return LOAD_STATE_CONNECTING; - default: - NOTREACHED(); + case STATE_NONE: return LOAD_STATE_IDLE; } + NOTREACHED(); + return LOAD_STATE_IDLE; } // static diff --git a/net/ssl/ssl_cipher_suite_names.cc b/net/ssl/ssl_cipher_suite_names.cc index f12d017fe7..f9394dfd68 100644 --- a/net/ssl/ssl_cipher_suite_names.cc +++ b/net/ssl/ssl_cipher_suite_names.cc @@ -194,6 +194,8 @@ static const struct CipherSuite kCipherSuites[] = { {0xc08b, 0x1087}, // TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 {0xc08c, 0xf7f}, // TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 {0xc08d, 0xf87}, // TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 + {0xcc13, 0x108f}, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + {0xcc14, 0x0d8f}, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 }; static const struct { @@ -220,8 +222,8 @@ static const struct { }; static const struct { - char name[17]; -} kCipherNames[17] = { + char name[18]; +} kCipherNames[18] = { {"NULL"}, // 0 {"RC4_40"}, // 1 {"RC4_128"}, // 2 @@ -239,6 +241,7 @@ static const struct { {"AES_256_GCM"}, // 14 {"CAMELLIA_128_GCM"}, // 15 {"CAMELLIA_256_GCM"}, // 16 + {"CHACHA20_POLY1305"}, // 17 }; static const struct { diff --git a/net/test/python_utils.cc b/net/test/python_utils.cc index 30c5f679a8..a9ac98b531 100644 --- a/net/test/python_utils.cc +++ b/net/test/python_utils.cc @@ -106,18 +106,8 @@ bool GetPyProtoPath(base::FilePath* dir) { bool GetPythonCommand(CommandLine* python_cmd) { DCHECK(python_cmd); - base::FilePath dir; -#if defined(OS_WIN) - if (!PathService::Get(base::DIR_SOURCE_ROOT, &dir)) - return false; - dir = dir.Append(FILE_PATH_LITERAL("third_party")) - .Append(FILE_PATH_LITERAL("python_26")) - .Append(FILE_PATH_LITERAL("python.exe")); -#elif defined(OS_POSIX) - dir = base::FilePath("python"); -#endif - python_cmd->SetProgram(dir); + python_cmd->SetProgram(base::FilePath(FILE_PATH_LITERAL("python"))); // Launch python in unbuffered mode, so that python output doesn't mix with // gtest output in buildbot log files. See http://crbug.com/147368. diff --git a/net/test/spawned_test_server/local_test_server_win.cc b/net/test/spawned_test_server/local_test_server_win.cc index fd26483ed4..08d68f80bf 100644 --- a/net/test/spawned_test_server/local_test_server_win.cc +++ b/net/test/spawned_test_server/local_test_server_win.cc @@ -10,6 +10,7 @@ #include "base/base_paths.h" #include "base/bind.h" #include "base/command_line.h" +#include "base/environment.h" #include "base/files/file_path.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" @@ -81,6 +82,60 @@ bool ReadData(HANDLE read_fd, HANDLE write_fd, return true; } +// Class that sets up a temporary path that includes the supplied path +// at the end. +// +// TODO(bratell): By making this more generic we can possibly reuse +// it at other places such as +// chrome/common/multi_process_lock_unittest.cc. +class ScopedPath { + public: + // Constructor which sets up the environment to include the path to + // |path_to_add|. + explicit ScopedPath(const base::FilePath& path_to_add); + + // Destructor that restores the path that were active when the + // object was constructed. + ~ScopedPath(); + + private: + // The PATH environment variable before it was changed or an empty + // string if there was no PATH environment variable. + std::string old_path_; + + // The helper object that allows us to read and set environment + // variables more easily. + scoped_ptr<base::Environment> environment_; + + // A flag saying if we have actually modified the environment. + bool path_modified_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPath); +}; + +ScopedPath::ScopedPath(const base::FilePath& path_to_add) + : environment_(base::Environment::Create()), + path_modified_(false) { + environment_->GetVar("PATH", &old_path_); + + std::string new_value = old_path_; + if (!new_value.empty()) + new_value += ";"; + + new_value += WideToUTF8(path_to_add.value()); + + path_modified_ = environment_->SetVar("PATH", new_value); +} + +ScopedPath::~ScopedPath() { + if (!path_modified_) + return; + if (old_path_.empty()) + environment_->UnSetVar("PATH"); + else + environment_->SetVar("PATH", old_path_); +} + } // namespace namespace net { @@ -129,11 +184,21 @@ bool LocalTestServer::LaunchPython(const base::FilePath& testserver_path) { return false; } - if (!base::SetJobObjectAsKillOnJobClose(job_handle_.Get())) { - LOG(ERROR) << "Could not SetInformationJobObject."; + if (!base::SetJobObjectLimitFlags(job_handle_.Get(), + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE)) { + LOG(ERROR) << "Could not SetJobObjectLimitFlags."; return false; } + // Add our internal python to the path so it can be used if there is + // no system python. + base::FilePath python_dir; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_dir)) { + LOG(ERROR) << "Could not locate source root directory."; + return false; + } + python_dir = python_dir.AppendASCII("third_party").AppendASCII("python_26"); + ScopedPath python_path(python_dir); base::LaunchOptions launch_options; launch_options.inherit_handles = true; launch_options.job_handle = job_handle_.Get(); diff --git a/net/third_party/nss/README.chromium b/net/third_party/nss/README.chromium index 0bf51b0bdc..73eb259156 100644 --- a/net/third_party/nss/README.chromium +++ b/net/third_party/nss/README.chromium @@ -112,6 +112,10 @@ Patches: client private key is a 1024-bit RSA or DSA key. patches/tls12backuphash.patch + * Support ChaCha20+Poly1305 ciphersuites + http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-01 + patches/chacha20poly1305.patch + Apply the patches to NSS by running the patches/applypatches.sh script. Read the comments at the top of patches/applypatches.sh for instructions. diff --git a/net/third_party/nss/patches/applypatches.sh b/net/third_party/nss/patches/applypatches.sh index 2fba203ca0..e936182471 100755 --- a/net/third_party/nss/patches/applypatches.sh +++ b/net/third_party/nss/patches/applypatches.sh @@ -53,3 +53,5 @@ patch -p4 < $patches_dir/aesgcm.patch patch -p4 < $patches_dir/aesgcmchromium.patch patch -p4 < $patches_dir/tls12backuphash.patch + +patch -p4 < $patches_dir/chacha20poly1305.patch diff --git a/net/third_party/nss/patches/chacha20poly1305.patch b/net/third_party/nss/patches/chacha20poly1305.patch new file mode 100644 index 0000000000..c858413f3c --- /dev/null +++ b/net/third_party/nss/patches/chacha20poly1305.patch @@ -0,0 +1,280 @@ +diff --git a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c +index 8be517c..53c29f0 100644 +--- a/nss/lib/ssl/ssl3con.c ++++ b/nss/lib/ssl/ssl3con.c +@@ -40,6 +40,21 @@ + #define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24) + #endif + ++/* This is a bodge to allow this code to be compiled against older NSS ++ * headers. */ ++#ifndef CKM_NSS_CHACHA20_POLY1305 ++#define CKM_NSS_CHACHA20_POLY1305 (CKM_NSS + 25) ++ ++typedef struct CK_AEAD_PARAMS { ++ CK_BYTE_PTR pIv; /* This is the nonce. */ ++ CK_ULONG ulIvLen; ++ CK_BYTE_PTR pAAD; ++ CK_ULONG ulAADLen; ++ CK_ULONG ulTagBits; ++} CK_AEAD_PARAMS; ++ ++#endif ++ + #include <stdio.h> + #ifdef NSS_ENABLE_ZLIB + #include "zlib.h" +@@ -100,6 +115,8 @@ static SECStatus ssl3_AESGCMBypass(ssl3KeyMaterial *keys, PRBool doDecrypt, + static ssl3CipherSuiteCfg cipherSuites[ssl_V3_SUITES_IMPLEMENTED] = { + /* cipher_suite policy enabled is_present*/ + #ifdef NSS_ENABLE_ECC ++ { TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, ++ { TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, + { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, + { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, + #endif /* NSS_ENABLE_ECC */ +@@ -273,6 +290,7 @@ static const ssl3BulkCipherDef bulk_cipher_defs[] = { + {cipher_camellia_256, calg_camellia, 32,32, type_block, 16,16, 0, 0}, + {cipher_seed, calg_seed, 16,16, type_block, 16,16, 0, 0}, + {cipher_aes_128_gcm, calg_aes_gcm, 16,16, type_aead, 4, 0,16, 8}, ++ {cipher_chacha20, calg_chacha20, 32,32, type_aead, 0, 0,16, 0}, + {cipher_missing, calg_null, 0, 0, type_stream, 0, 0, 0, 0}, + }; + +@@ -399,6 +417,8 @@ static const ssl3CipherSuiteDef cipher_suite_defs[] = + {TLS_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_aead, kea_rsa}, + {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_aead, kea_ecdhe_rsa}, + {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_aead, kea_ecdhe_ecdsa}, ++ {TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, cipher_chacha20, mac_aead, kea_ecdhe_rsa}, ++ {TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, cipher_chacha20, mac_aead, kea_ecdhe_ecdsa}, + + #ifdef NSS_ENABLE_ECC + {TLS_ECDH_ECDSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdh_ecdsa}, +@@ -464,6 +484,7 @@ static const SSLCipher2Mech alg2Mech[] = { + { calg_camellia , CKM_CAMELLIA_CBC }, + { calg_seed , CKM_SEED_CBC }, + { calg_aes_gcm , CKM_AES_GCM }, ++ { calg_chacha20 , CKM_NSS_CHACHA20_POLY1305 }, + /* { calg_init , (CK_MECHANISM_TYPE)0x7fffffffL } */ + }; + +@@ -2020,6 +2041,46 @@ ssl3_AESGCMBypass(ssl3KeyMaterial *keys, + } + #endif + ++static SECStatus ++ssl3_ChaCha20Poly1305( ++ ssl3KeyMaterial *keys, ++ PRBool doDecrypt, ++ unsigned char *out, ++ int *outlen, ++ int maxout, ++ const unsigned char *in, ++ int inlen, ++ const unsigned char *additionalData, ++ int additionalDataLen) ++{ ++ SECItem param; ++ SECStatus rv = SECFailure; ++ unsigned int uOutLen; ++ CK_AEAD_PARAMS aeadParams; ++ static const int tagSize = 16; ++ ++ param.type = siBuffer; ++ param.len = sizeof(aeadParams); ++ param.data = (unsigned char *) &aeadParams; ++ memset(&aeadParams, 0, sizeof(CK_AEAD_PARAMS)); ++ aeadParams.pIv = (unsigned char *) additionalData; ++ aeadParams.ulIvLen = 8; ++ aeadParams.pAAD = (unsigned char *) additionalData; ++ aeadParams.ulAADLen = additionalDataLen; ++ aeadParams.ulTagBits = tagSize * 8; ++ ++ if (doDecrypt) { ++ rv = pk11_decrypt(keys->write_key, CKM_NSS_CHACHA20_POLY1305, ¶m, ++ out, &uOutLen, maxout, in, inlen); ++ } else { ++ rv = pk11_encrypt(keys->write_key, CKM_NSS_CHACHA20_POLY1305, ¶m, ++ out, &uOutLen, maxout, in, inlen); ++ } ++ *outlen = (int) uOutLen; ++ ++ return rv; ++} ++ + /* Initialize encryption and MAC contexts for pending spec. + * Master Secret already is derived. + * Caller holds Spec write lock. +@@ -2053,13 +2114,17 @@ ssl3_InitPendingContextsPKCS11(sslSocket *ss) + pwSpec->client.write_mac_context = NULL; + pwSpec->server.write_mac_context = NULL; + +- if (calg == calg_aes_gcm) { ++ if (calg == calg_aes_gcm || calg == calg_chacha20) { + pwSpec->encode = NULL; + pwSpec->decode = NULL; + pwSpec->destroy = NULL; + pwSpec->encodeContext = NULL; + pwSpec->decodeContext = NULL; +- pwSpec->aead = ssl3_AESGCM; ++ if (calg == calg_aes_gcm) { ++ pwSpec->aead = ssl3_AESGCM; ++ } else { ++ pwSpec->aead = ssl3_ChaCha20Poly1305; ++ } + return SECSuccess; + } + +diff --git a/nss/lib/ssl/ssl3ecc.c b/nss/lib/ssl/ssl3ecc.c +index a3638e7..21a5e05 100644 +--- a/nss/lib/ssl/ssl3ecc.c ++++ b/nss/lib/ssl/ssl3ecc.c +@@ -913,6 +913,7 @@ static const ssl3CipherSuite ecdhe_ecdsa_suites[] = { + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, ++ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_ECDSA_WITH_NULL_SHA, + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + 0 /* end of list marker */ +@@ -924,6 +925,7 @@ static const ssl3CipherSuite ecdhe_rsa_suites[] = { + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, ++ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_NULL_SHA, + TLS_ECDHE_RSA_WITH_RC4_128_SHA, + 0 /* end of list marker */ +@@ -936,6 +938,7 @@ static const ssl3CipherSuite ecSuites[] = { + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, ++ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_ECDSA_WITH_NULL_SHA, + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, +@@ -943,6 +946,7 @@ static const ssl3CipherSuite ecSuites[] = { + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, ++ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_NULL_SHA, + TLS_ECDHE_RSA_WITH_RC4_128_SHA, + TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, +diff --git a/nss/lib/ssl/sslenum.c b/nss/lib/ssl/sslenum.c +index 597ec07..fc6b854 100644 +--- a/nss/lib/ssl/sslenum.c ++++ b/nss/lib/ssl/sslenum.c +@@ -31,6 +31,8 @@ + const PRUint16 SSL_ImplementedCiphers[] = { + /* AES-GCM */ + #ifdef NSS_ENABLE_ECC ++ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, ++ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + #endif /* NSS_ENABLE_ECC */ +diff --git a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h +index 0fe12d0..e3ae9ce 100644 +--- a/nss/lib/ssl/sslimpl.h ++++ b/nss/lib/ssl/sslimpl.h +@@ -65,6 +65,7 @@ typedef SSLSignType SSL3SignType; + #define calg_camellia ssl_calg_camellia + #define calg_seed ssl_calg_seed + #define calg_aes_gcm ssl_calg_aes_gcm ++#define calg_chacha20 ssl_calg_chacha20 + + #define mac_null ssl_mac_null + #define mac_md5 ssl_mac_md5 +@@ -292,7 +293,7 @@ typedef struct { + } ssl3CipherSuiteCfg; + + #ifdef NSS_ENABLE_ECC +-#define ssl_V3_SUITES_IMPLEMENTED 61 ++#define ssl_V3_SUITES_IMPLEMENTED 63 + #else + #define ssl_V3_SUITES_IMPLEMENTED 37 + #endif /* NSS_ENABLE_ECC */ +@@ -474,6 +475,7 @@ typedef enum { + cipher_camellia_256, + cipher_seed, + cipher_aes_128_gcm, ++ cipher_chacha20, + cipher_missing /* reserved for no such supported cipher */ + /* This enum must match ssl3_cipherName[] in ssl3con.c. */ + } SSL3BulkCipher; +diff --git a/nss/lib/ssl/sslinfo.c b/nss/lib/ssl/sslinfo.c +index 9597209..bfc1676 100644 +--- a/nss/lib/ssl/sslinfo.c ++++ b/nss/lib/ssl/sslinfo.c +@@ -118,6 +118,7 @@ SSL_GetChannelInfo(PRFileDesc *fd, SSLChannelInfo *info, PRUintn len) + #define C_NULL "NULL", calg_null + #define C_SJ "SKIPJACK", calg_sj + #define C_AESGCM "AES-GCM", calg_aes_gcm ++#define C_CHACHA20 "CHACHA20POLY1305", calg_chacha20 + + #define B_256 256, 256, 256 + #define B_128 128, 128, 128 +@@ -196,12 +197,14 @@ static const SSLCipherSuiteInfo suiteInfo[] = { + {0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, 1, 0, 0, }, + {0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, 1, 0, 0, }, + {0,CS(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, 1, 0, 0, }, ++{0,CS(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305),S_ECDSA,K_ECDHE,C_CHACHA20,B_256,M_AEAD_128,0, 0, 0, }, + + {0,CS(TLS_ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, 0, 0, 0, }, + {0,CS(TLS_ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, 0, 0, 0, }, + {0,CS(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, 1, 0, 0, }, + {0,CS(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, 1, 0, 0, }, + {0,CS(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, 1, 0, 0, }, ++{0,CS(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305), S_RSA,K_ECDHE,C_CHACHA20,B_256,M_AEAD_128, 0, 0, 0, }, + + {0,CS(TLS_ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, 0, 0, 0, }, + {0,CS(TLS_ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, 0, 0, 0, }, +diff --git a/nss/lib/ssl/sslproto.h b/nss/lib/ssl/sslproto.h +index 53bba01..6b60a28 100644 +--- a/nss/lib/ssl/sslproto.h ++++ b/nss/lib/ssl/sslproto.h +@@ -213,6 +213,9 @@ + #define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F + #define TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031 + ++#define TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 0xCC13 ++#define TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 0xCC14 ++ + /* Netscape "experimental" cipher suites. */ + #define SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA 0xffe0 + #define SSL_RSA_OLDFIPS_WITH_DES_CBC_SHA 0xffe1 +diff --git a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c +index c17c7a3..ffbccc6 100644 +--- a/nss/lib/ssl/sslsock.c ++++ b/nss/lib/ssl/sslsock.c +@@ -98,6 +98,7 @@ static cipherPolicy ssl_ciphers[] = { /* Export France */ + { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, ++ { TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDH_RSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED }, + { TLS_ECDH_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, +@@ -110,6 +111,7 @@ static cipherPolicy ssl_ciphers[] = { /* Export France */ + { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, ++ { TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + #endif /* NSS_ENABLE_ECC */ + { 0, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED } + }; +diff --git a/nss/lib/ssl/sslt.h b/nss/lib/ssl/sslt.h +index b03422e..a8007d8 100644 +--- a/nss/lib/ssl/sslt.h ++++ b/nss/lib/ssl/sslt.h +@@ -94,7 +94,8 @@ typedef enum { + ssl_calg_aes = 7, + ssl_calg_camellia = 8, + ssl_calg_seed = 9, +- ssl_calg_aes_gcm = 10 ++ ssl_calg_aes_gcm = 10, ++ ssl_calg_chacha20 = 11 + } SSLCipherAlgorithm; + + typedef enum { diff --git a/net/third_party/nss/ssl/ssl3con.c b/net/third_party/nss/ssl/ssl3con.c index 8be517cde6..53c29f0e28 100644 --- a/net/third_party/nss/ssl/ssl3con.c +++ b/net/third_party/nss/ssl/ssl3con.c @@ -40,6 +40,21 @@ #define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24) #endif +/* This is a bodge to allow this code to be compiled against older NSS + * headers. */ +#ifndef CKM_NSS_CHACHA20_POLY1305 +#define CKM_NSS_CHACHA20_POLY1305 (CKM_NSS + 25) + +typedef struct CK_AEAD_PARAMS { + CK_BYTE_PTR pIv; /* This is the nonce. */ + CK_ULONG ulIvLen; + CK_BYTE_PTR pAAD; + CK_ULONG ulAADLen; + CK_ULONG ulTagBits; +} CK_AEAD_PARAMS; + +#endif + #include <stdio.h> #ifdef NSS_ENABLE_ZLIB #include "zlib.h" @@ -100,6 +115,8 @@ static SECStatus ssl3_AESGCMBypass(ssl3KeyMaterial *keys, PRBool doDecrypt, static ssl3CipherSuiteCfg cipherSuites[ssl_V3_SUITES_IMPLEMENTED] = { /* cipher_suite policy enabled is_present*/ #ifdef NSS_ENABLE_ECC + { TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, + { TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE}, #endif /* NSS_ENABLE_ECC */ @@ -273,6 +290,7 @@ static const ssl3BulkCipherDef bulk_cipher_defs[] = { {cipher_camellia_256, calg_camellia, 32,32, type_block, 16,16, 0, 0}, {cipher_seed, calg_seed, 16,16, type_block, 16,16, 0, 0}, {cipher_aes_128_gcm, calg_aes_gcm, 16,16, type_aead, 4, 0,16, 8}, + {cipher_chacha20, calg_chacha20, 32,32, type_aead, 0, 0,16, 0}, {cipher_missing, calg_null, 0, 0, type_stream, 0, 0, 0, 0}, }; @@ -399,6 +417,8 @@ static const ssl3CipherSuiteDef cipher_suite_defs[] = {TLS_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_aead, kea_rsa}, {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_aead, kea_ecdhe_rsa}, {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_aead, kea_ecdhe_ecdsa}, + {TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, cipher_chacha20, mac_aead, kea_ecdhe_rsa}, + {TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, cipher_chacha20, mac_aead, kea_ecdhe_ecdsa}, #ifdef NSS_ENABLE_ECC {TLS_ECDH_ECDSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdh_ecdsa}, @@ -464,6 +484,7 @@ static const SSLCipher2Mech alg2Mech[] = { { calg_camellia , CKM_CAMELLIA_CBC }, { calg_seed , CKM_SEED_CBC }, { calg_aes_gcm , CKM_AES_GCM }, + { calg_chacha20 , CKM_NSS_CHACHA20_POLY1305 }, /* { calg_init , (CK_MECHANISM_TYPE)0x7fffffffL } */ }; @@ -2020,6 +2041,46 @@ ssl3_AESGCMBypass(ssl3KeyMaterial *keys, } #endif +static SECStatus +ssl3_ChaCha20Poly1305( + ssl3KeyMaterial *keys, + PRBool doDecrypt, + unsigned char *out, + int *outlen, + int maxout, + const unsigned char *in, + int inlen, + const unsigned char *additionalData, + int additionalDataLen) +{ + SECItem param; + SECStatus rv = SECFailure; + unsigned int uOutLen; + CK_AEAD_PARAMS aeadParams; + static const int tagSize = 16; + + param.type = siBuffer; + param.len = sizeof(aeadParams); + param.data = (unsigned char *) &aeadParams; + memset(&aeadParams, 0, sizeof(CK_AEAD_PARAMS)); + aeadParams.pIv = (unsigned char *) additionalData; + aeadParams.ulIvLen = 8; + aeadParams.pAAD = (unsigned char *) additionalData; + aeadParams.ulAADLen = additionalDataLen; + aeadParams.ulTagBits = tagSize * 8; + + if (doDecrypt) { + rv = pk11_decrypt(keys->write_key, CKM_NSS_CHACHA20_POLY1305, ¶m, + out, &uOutLen, maxout, in, inlen); + } else { + rv = pk11_encrypt(keys->write_key, CKM_NSS_CHACHA20_POLY1305, ¶m, + out, &uOutLen, maxout, in, inlen); + } + *outlen = (int) uOutLen; + + return rv; +} + /* Initialize encryption and MAC contexts for pending spec. * Master Secret already is derived. * Caller holds Spec write lock. @@ -2053,13 +2114,17 @@ ssl3_InitPendingContextsPKCS11(sslSocket *ss) pwSpec->client.write_mac_context = NULL; pwSpec->server.write_mac_context = NULL; - if (calg == calg_aes_gcm) { + if (calg == calg_aes_gcm || calg == calg_chacha20) { pwSpec->encode = NULL; pwSpec->decode = NULL; pwSpec->destroy = NULL; pwSpec->encodeContext = NULL; pwSpec->decodeContext = NULL; - pwSpec->aead = ssl3_AESGCM; + if (calg == calg_aes_gcm) { + pwSpec->aead = ssl3_AESGCM; + } else { + pwSpec->aead = ssl3_ChaCha20Poly1305; + } return SECSuccess; } diff --git a/net/third_party/nss/ssl/ssl3ecc.c b/net/third_party/nss/ssl/ssl3ecc.c index a3638e75f1..21a5e05cf2 100644 --- a/net/third_party/nss/ssl/ssl3ecc.c +++ b/net/third_party/nss/ssl/ssl3ecc.c @@ -913,6 +913,7 @@ static const ssl3CipherSuite ecdhe_ecdsa_suites[] = { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_NULL_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 0 /* end of list marker */ @@ -924,6 +925,7 @@ static const ssl3CipherSuite ecdhe_rsa_suites[] = { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_NULL_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, 0 /* end of list marker */ @@ -936,6 +938,7 @@ static const ssl3CipherSuite ecSuites[] = { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_NULL_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, @@ -943,6 +946,7 @@ static const ssl3CipherSuite ecSuites[] = { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_NULL_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, diff --git a/net/third_party/nss/ssl/sslenum.c b/net/third_party/nss/ssl/sslenum.c index 597ec07239..fc6b85423b 100644 --- a/net/third_party/nss/ssl/sslenum.c +++ b/net/third_party/nss/ssl/sslenum.c @@ -31,6 +31,8 @@ const PRUint16 SSL_ImplementedCiphers[] = { /* AES-GCM */ #ifdef NSS_ENABLE_ECC + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, #endif /* NSS_ENABLE_ECC */ diff --git a/net/third_party/nss/ssl/sslimpl.h b/net/third_party/nss/ssl/sslimpl.h index 0fe12d0d7e..e3ae9cec5d 100644 --- a/net/third_party/nss/ssl/sslimpl.h +++ b/net/third_party/nss/ssl/sslimpl.h @@ -65,6 +65,7 @@ typedef SSLSignType SSL3SignType; #define calg_camellia ssl_calg_camellia #define calg_seed ssl_calg_seed #define calg_aes_gcm ssl_calg_aes_gcm +#define calg_chacha20 ssl_calg_chacha20 #define mac_null ssl_mac_null #define mac_md5 ssl_mac_md5 @@ -292,7 +293,7 @@ typedef struct { } ssl3CipherSuiteCfg; #ifdef NSS_ENABLE_ECC -#define ssl_V3_SUITES_IMPLEMENTED 61 +#define ssl_V3_SUITES_IMPLEMENTED 63 #else #define ssl_V3_SUITES_IMPLEMENTED 37 #endif /* NSS_ENABLE_ECC */ @@ -474,6 +475,7 @@ typedef enum { cipher_camellia_256, cipher_seed, cipher_aes_128_gcm, + cipher_chacha20, cipher_missing /* reserved for no such supported cipher */ /* This enum must match ssl3_cipherName[] in ssl3con.c. */ } SSL3BulkCipher; diff --git a/net/third_party/nss/ssl/sslinfo.c b/net/third_party/nss/ssl/sslinfo.c index 959720948d..bfc16767ff 100644 --- a/net/third_party/nss/ssl/sslinfo.c +++ b/net/third_party/nss/ssl/sslinfo.c @@ -118,6 +118,7 @@ SSL_GetChannelInfo(PRFileDesc *fd, SSLChannelInfo *info, PRUintn len) #define C_NULL "NULL", calg_null #define C_SJ "SKIPJACK", calg_sj #define C_AESGCM "AES-GCM", calg_aes_gcm +#define C_CHACHA20 "CHACHA20POLY1305", calg_chacha20 #define B_256 256, 256, 256 #define B_128 128, 128, 128 @@ -196,12 +197,14 @@ static const SSLCipherSuiteInfo suiteInfo[] = { {0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, 1, 0, 0, }, {0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, 1, 0, 0, }, {0,CS(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, 1, 0, 0, }, +{0,CS(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305),S_ECDSA,K_ECDHE,C_CHACHA20,B_256,M_AEAD_128,0, 0, 0, }, {0,CS(TLS_ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, 0, 0, 0, }, {0,CS(TLS_ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, 0, 0, 0, }, {0,CS(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, 1, 0, 0, }, {0,CS(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, 1, 0, 0, }, {0,CS(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, 1, 0, 0, }, +{0,CS(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305), S_RSA,K_ECDHE,C_CHACHA20,B_256,M_AEAD_128, 0, 0, 0, }, {0,CS(TLS_ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, 0, 0, 0, }, {0,CS(TLS_ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, 0, 0, 0, }, diff --git a/net/third_party/nss/ssl/sslproto.h b/net/third_party/nss/ssl/sslproto.h index 53bba011bb..6b60a28616 100644 --- a/net/third_party/nss/ssl/sslproto.h +++ b/net/third_party/nss/ssl/sslproto.h @@ -213,6 +213,9 @@ #define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F #define TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031 +#define TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 0xCC13 +#define TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 0xCC14 + /* Netscape "experimental" cipher suites. */ #define SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA 0xffe0 #define SSL_RSA_OLDFIPS_WITH_DES_CBC_SHA 0xffe1 diff --git a/net/third_party/nss/ssl/sslsock.c b/net/third_party/nss/ssl/sslsock.c index c17c7a3ad0..ffbccc6eb6 100644 --- a/net/third_party/nss/ssl/sslsock.c +++ b/net/third_party/nss/ssl/sslsock.c @@ -98,6 +98,7 @@ static cipherPolicy ssl_ciphers[] = { /* Export France */ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, { TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, { TLS_ECDH_RSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED }, { TLS_ECDH_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, { TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, @@ -110,6 +111,7 @@ static cipherPolicy ssl_ciphers[] = { /* Export France */ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, { TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, + { TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }, #endif /* NSS_ENABLE_ECC */ { 0, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED } }; diff --git a/net/third_party/nss/ssl/sslt.h b/net/third_party/nss/ssl/sslt.h index b03422eaa5..a8007d8b4c 100644 --- a/net/third_party/nss/ssl/sslt.h +++ b/net/third_party/nss/ssl/sslt.h @@ -94,7 +94,8 @@ typedef enum { ssl_calg_aes = 7, ssl_calg_camellia = 8, ssl_calg_seed = 9, - ssl_calg_aes_gcm = 10 + ssl_calg_aes_gcm = 10, + ssl_calg_chacha20 = 11 } SSLCipherAlgorithm; typedef enum { diff --git a/net/tools/quic/end_to_end_test.cc b/net/tools/quic/end_to_end_test.cc index 9ad6649be9..7b61be6d14 100644 --- a/net/tools/quic/end_to_end_test.cc +++ b/net/tools/quic/end_to_end_test.cc @@ -423,7 +423,29 @@ TEST_P(EndToEndTest, PostMissingBytes) { EXPECT_EQ(500u, client_->response_headers()->parsed_response_code()); } -TEST_P(EndToEndTest, LargePost) { +TEST_P(EndToEndTest, LargePostNoPacketLoss) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + client_->client()->WaitForCryptoHandshakeConfirmed(); + + // 1 Mb body. + string body; + GenerateBody(&body, 1024 * 1024); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddBody(body, true); + + EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request)); +} + +TEST_P(EndToEndTest, LargePostWithPacketLoss) { // TODO(rtenneti): Delete this when NSS is supported. if (!Aes128Gcm12Encrypter::IsSupported()) { LOG(INFO) << "AES GCM not supported. Test skipped."; @@ -439,8 +461,9 @@ TEST_P(EndToEndTest, LargePost) { client_->client()->WaitForCryptoHandshakeConfirmed(); // FLAGS_fake_packet_loss_percentage = 30; + // 10 Kb body. string body; - GenerateBody(&body, 10240); + GenerateBody(&body, 1024 * 10); HTTPMessage request(HttpConstants::HTTP_1_1, HttpConstants::POST, "/foo"); @@ -563,7 +586,6 @@ TEST_P(EndToEndTest, DISABLED_MultipleTermination) { } ASSERT_TRUE(Initialize()); - scoped_ptr<QuicTestClient> client2(CreateQuicClient()); HTTPMessage request(HttpConstants::HTTP_1_1, HttpConstants::POST, "/foo"); diff --git a/net/tools/quic/quic_epoll_connection_helper_test.cc b/net/tools/quic/quic_epoll_connection_helper_test.cc index 6d6ea13d01..636d98f7ee 100644 --- a/net/tools/quic/quic_epoll_connection_helper_test.cc +++ b/net/tools/quic/quic_epoll_connection_helper_test.cc @@ -8,6 +8,7 @@ #include "net/quic/crypto/quic_decrypter.h" #include "net/quic/crypto/quic_encrypter.h" #include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_connection.h" #include "net/quic/quic_framer.h" #include "net/quic/test_tools/quic_connection_peer.h" #include "net/quic/test_tools/quic_test_utils.h" @@ -21,6 +22,7 @@ using net::test::QuicConnectionPeer; using net::test::MockConnectionVisitor; using net::tools::test::MockEpollServer; using testing::_; +using testing::AnyNumber; using testing::Return; namespace net { @@ -91,6 +93,8 @@ class QuicEpollConnectionHelperTest : public ::testing::Test { QuicBandwidth::FromKBitsPerSecond(100))); EXPECT_CALL(*send_algorithm_, SmoothedRtt()).WillRepeatedly(Return( QuicTime::Delta::FromMilliseconds(100))); + ON_CALL(*send_algorithm_, SentPacket(_, _, _, _, _)) + .WillByDefault(Return(true)); } QuicPacket* ConstructDataPacket(QuicPacketSequenceNumber number, @@ -136,12 +140,14 @@ TEST_F(QuicEpollConnectionHelperTest, DISABLED_TestRetransmission) { arraysize(buffer) - 1; EXPECT_CALL(*send_algorithm_, - SentPacket(_, 1, packet_size, NOT_RETRANSMISSION)); + SentPacket(_, 1, packet_size, NOT_RETRANSMISSION, _)); EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, packet_size)); - connection_.SendStreamData(1, buffer, 0, false); + struct iovec iov = {const_cast<char*>(buffer), + static_cast<size_t>(3)}; + connection_.SendvStreamData(1, &iov, 1, 0, false); EXPECT_EQ(1u, helper_->header()->packet_sequence_number); EXPECT_CALL(*send_algorithm_, - SentPacket(_, 2, packet_size, IS_RETRANSMISSION)); + SentPacket(_, 2, packet_size, IS_RETRANSMISSION, _)); epoll_server_.AdvanceByAndCallCallbacks(kDefaultRetransmissionTimeMs * 1000); EXPECT_EQ(2u, helper_->header()->packet_sequence_number); @@ -150,7 +156,10 @@ TEST_F(QuicEpollConnectionHelperTest, DISABLED_TestRetransmission) { TEST_F(QuicEpollConnectionHelperTest, InitialTimeout) { EXPECT_TRUE(connection_.connected()); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA)); + EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillOnce( + Return(QuicTime::Delta::FromMicroseconds(1))); EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer)); epoll_server_.WaitForEventsAndExecuteCallbacks(); EXPECT_FALSE(connection_.connected()); @@ -167,7 +176,8 @@ TEST_F(QuicEpollConnectionHelperTest, TimeoutAfterSend) { EXPECT_EQ(5000, epoll_server_.NowInUsec()); // Send an ack so we don't set the retransmission alarm. - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, + SentPacket(_, 1, _, NOT_RETRANSMISSION, NO_RETRANSMITTABLE_DATA)); connection_.SendAck(); // The original alarm will fire. We should not time out because we had a @@ -177,7 +187,10 @@ TEST_F(QuicEpollConnectionHelperTest, TimeoutAfterSend) { // This time, we should time out. EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer)); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION, + HAS_RETRANSMITTABLE_DATA)); + EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillOnce( + Return(QuicTime::Delta::FromMicroseconds(1))); epoll_server_.WaitForEventsAndExecuteCallbacks(); EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000 + 5000, epoll_server_.NowInUsec()); @@ -191,17 +204,21 @@ TEST_F(QuicEpollConnectionHelperTest, SendSchedulerDelayThenSend) { QuicPacket* packet = ConstructDataPacket(1, 0); EXPECT_CALL( *send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( - testing::Return(QuicTime::Delta::FromMicroseconds(1))); + Return(QuicTime::Delta::FromMicroseconds(1))); connection_.SendOrQueuePacket(ENCRYPTION_NONE, 1, packet, 0, - HAS_RETRANSMITTABLE_DATA); - EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + HAS_RETRANSMITTABLE_DATA, + QuicConnection::NO_FORCE); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION, + _)); EXPECT_EQ(1u, connection_.NumQueuedPackets()); // Advance the clock to fire the alarm, and configure the scheduler // to permit the packet to be sent. - EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)). - WillRepeatedly(testing::Return(QuicTime::Delta::Zero())); - EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true)); + EXPECT_CALL(*send_algorithm_, + TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly( + Return(QuicTime::Delta::Zero())); + EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(Return(true)); + EXPECT_CALL(visitor_, HasPendingHandshake()).Times(AnyNumber()); epoll_server_.AdvanceByAndCallCallbacks(1); EXPECT_EQ(0u, connection_.NumQueuedPackets()); } diff --git a/net/tools/quic/quic_reliable_server_stream_test.cc b/net/tools/quic/quic_reliable_server_stream_test.cc index b946d94cfc..53533a35cb 100644 --- a/net/tools/quic/quic_reliable_server_stream_test.cc +++ b/net/tools/quic/quic_reliable_server_stream_test.cc @@ -58,7 +58,9 @@ class QuicReliableServerStreamTest : public ::testing::Test { stream_.reset(new QuicSpdyServerStream(3, &session_)); } - QuicConsumedData ValidateHeaders(StringPiece headers) { + QuicConsumedData ValidateHeaders(const struct iovec* iov) { + StringPiece headers = + StringPiece(static_cast<const char*>(iov[0].iov_base), iov[0].iov_len); headers_string_ = SpdyUtils::SerializeResponseHeaders( response_headers_); QuicSpdyDecompressor decompressor; @@ -119,13 +121,20 @@ class QuicReliableServerStreamTest : public ::testing::Test { string body_; }; -QuicConsumedData ConsumeAllData(QuicStreamId id, StringPiece data, - QuicStreamOffset offset, bool fin) { - return QuicConsumedData(data.size(), fin); +QuicConsumedData ConsumeAllData(QuicStreamId id, + const struct iovec* iov, + int iov_count, + QuicStreamOffset offset, + bool fin) { + ssize_t consumed_length = 0; + for (int i = 0; i < iov_count; ++i) { + consumed_length += iov[i].iov_len; + } + return QuicConsumedData(consumed_length, fin); } TEST_F(QuicReliableServerStreamTest, TestFraming) { - EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(AnyNumber()). + EXPECT_CALL(session_, WritevData(_, _, _, _, _)).Times(AnyNumber()). WillRepeatedly(Invoke(ConsumeAllData)); EXPECT_EQ(headers_string_.size(), stream_->ProcessData( @@ -138,7 +147,7 @@ TEST_F(QuicReliableServerStreamTest, TestFraming) { } TEST_F(QuicReliableServerStreamTest, TestFramingOnePacket) { - EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(AnyNumber()). + EXPECT_CALL(session_, WritevData(_, _, _, _, _)).Times(AnyNumber()). WillRepeatedly(Invoke(ConsumeAllData)); string message = headers_string_ + body_; @@ -156,7 +165,7 @@ TEST_F(QuicReliableServerStreamTest, TestFramingExtraData) { string large_body = "hello world!!!!!!"; // We'll automatically write out an error (headers + body) - EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(2). + EXPECT_CALL(session_, WritevData(_, _, _, _, _)).Times(2). WillRepeatedly(Invoke(ConsumeAllData)); EXPECT_EQ(headers_string_.size(), stream_->ProcessData( @@ -183,11 +192,11 @@ TEST_F(QuicReliableServerStreamTest, TestSendResponse) { response_headers_.ReplaceOrAppendHeader("content-length", "3"); InSequence s; - EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(1) + EXPECT_CALL(session_, WritevData(_, _, 1, _, _)).Times(1) .WillOnce(WithArgs<1>(Invoke( this, &QuicReliableServerStreamTest::ValidateHeaders))); - StringPiece kBody = "Yum"; - EXPECT_CALL(session_, WriteData(_, kBody, _, _)).Times(1). + + EXPECT_CALL(session_, WritevData(_, _, 1, _, _)).Times(1). WillOnce(Return(QuicConsumedData(3, true))); stream_->SendResponse(); @@ -201,11 +210,11 @@ TEST_F(QuicReliableServerStreamTest, TestSendErrorResponse) { response_headers_.ReplaceOrAppendHeader("content-length", "3"); InSequence s; - EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(1) + EXPECT_CALL(session_, WritevData(_, _, 1, _, _)).Times(1) .WillOnce(WithArgs<1>(Invoke( this, &QuicReliableServerStreamTest::ValidateHeaders))); - StringPiece kBody = "bad"; - EXPECT_CALL(session_, WriteData(_, kBody, _, _)).Times(1). + + EXPECT_CALL(session_, WritevData(_, _, 1, _, _)).Times(1). WillOnce(Return(QuicConsumedData(3, true))); stream_->SendErrorResponse(); diff --git a/net/tools/quic/quic_server_session.h b/net/tools/quic/quic_server_session.h index 604b9fc0c4..2f031373bd 100644 --- a/net/tools/quic/quic_server_session.h +++ b/net/tools/quic/quic_server_session.h @@ -25,6 +25,10 @@ class ReliableQuicStream; namespace tools { +namespace test { +class QuicServerSessionPeer; +} // namespace test + // An interface from the session to the entity owning the session. // This lets the session notify its owner (the Dispatcher) when the connection // is closed. @@ -66,6 +70,8 @@ class QuicServerSession : public QuicSession { const QuicCryptoServerConfig& crypto_config); private: + friend class test::QuicServerSessionPeer; + scoped_ptr<QuicCryptoServerStream> crypto_stream_; QuicSessionOwner* owner_; diff --git a/net/tools/quic/quic_server_session_test.cc b/net/tools/quic/quic_server_session_test.cc new file mode 100644 index 0000000000..a6f94abd7b --- /dev/null +++ b/net/tools/quic/quic_server_session_test.cc @@ -0,0 +1,255 @@ +// Copyright 2013 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/quic_server_session.h" + + +#include "net/quic/crypto/crypto_server_config.h" +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_connection.h" +#include "net/quic/test_tools/quic_connection_peer.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/quic/test_tools/reliable_quic_stream_peer.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_spdy_server_stream.h" +#include "net/tools/quic/test_tools/quic_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using __gnu_cxx::vector; +using net::test::MockConnection; +using net::test::QuicConnectionPeer; +using net::test::ReliableQuicStreamPeer; +using testing::_; +using testing::StrictMock; + +namespace net { +namespace tools { +namespace test { + +class QuicServerSessionPeer { + public: + static ReliableQuicStream* GetIncomingReliableStream( + QuicServerSession* s, QuicStreamId id) { + return s->GetIncomingReliableStream(id); + } + static ReliableQuicStream* GetStream(QuicServerSession* s, QuicStreamId id) { + return s->GetStream(id); + } +}; + +class CloseOnDataStream : public ReliableQuicStream { + public: + CloseOnDataStream(QuicStreamId id, QuicSession* session) + : ReliableQuicStream(id, session) { + } + + virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE { + session()->MarkDecompressionBlocked(1, id()); + session()->CloseStream(id()); + return true; + } + + virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE { + return 0; + } +}; + +class TestQuicQuicServerSession : public QuicServerSession { + public: + TestQuicQuicServerSession(const QuicConfig& config, + QuicConnection* connection, + QuicSessionOwner* owner) + : QuicServerSession(config, connection, owner), + close_stream_on_data_(false) { + } + + virtual ReliableQuicStream* CreateIncomingReliableStream( + QuicStreamId id) OVERRIDE { + if (!ShouldCreateIncomingReliableStream(id)) { + return NULL; + } + if (close_stream_on_data_) { + return new CloseOnDataStream(id, this); + } else { + return new QuicSpdyServerStream(id, this); + } + } + + void CloseStreamOnData() { + close_stream_on_data_ = true; + } + + private: + bool close_stream_on_data_; +}; + +namespace { + +class QuicServerSessionTest : public ::testing::Test { + protected: + QuicServerSessionTest() + : guid_(1), + crypto_config_(QuicCryptoServerConfig::TESTING, + QuicRandom::GetInstance()) { + config_.SetDefaults(); + config_.set_max_streams_per_connection(3, 3); + + connection_ = new MockConnection(guid_, IPEndPoint(), 0, &eps_, true); + session_.reset(new TestQuicQuicServerSession( + config_, connection_, &owner_)); + session_->InitializeSession(crypto_config_); + visitor_ = QuicConnectionPeer::GetVisitor(connection_); + } + + void MarkHeadersReadForStream(QuicStreamId id) { + ReliableQuicStream* stream = QuicServerSessionPeer::GetStream( + session_.get(), id); + ASSERT_TRUE(stream != NULL); + ReliableQuicStreamPeer::SetHeadersDecompressed(stream, true); + } + + QuicGuid guid_; + EpollServer eps_; + StrictMock<MockQuicSessionOwner> owner_; + MockConnection* connection_; + QuicConfig config_; + QuicCryptoServerConfig crypto_config_; + scoped_ptr<TestQuicQuicServerSession> session_; + QuicConnectionVisitorInterface* visitor_; +}; + +TEST_F(QuicServerSessionTest, CloseStreamDueToReset) { + // Open a stream, then reset it. + // Send two bytes of payload to open it. + QuicPacketHeader header; + header.public_header.guid = guid_; + header.public_header.reset_flag = false; + header.public_header.version_flag = false; + QuicStreamFrame data1(3, false, 0, "HT"); + vector<QuicStreamFrame> frames; + frames.push_back(data1); + EXPECT_TRUE(visitor_->OnStreamFrames(frames)); + EXPECT_EQ(1u, session_->GetNumOpenStreams()); + + // Pretend we got full headers, so we won't trigger the 'unrecoverable + // compression context' state. + MarkHeadersReadForStream(3); + + // Send a reset. + QuicRstStreamFrame rst1(3, QUIC_STREAM_NO_ERROR); + visitor_->OnRstStream(rst1); + EXPECT_EQ(0u, session_->GetNumOpenStreams()); + + // Send the same two bytes of payload in a new packet. + EXPECT_TRUE(visitor_->OnStreamFrames(frames)); + + // The stream should not be re-opened. + EXPECT_EQ(0u, session_->GetNumOpenStreams()); +} + +TEST_F(QuicServerSessionTest, NeverOpenStreamDueToReset) { + // Send a reset. + QuicRstStreamFrame rst1(3, QUIC_STREAM_NO_ERROR); + visitor_->OnRstStream(rst1); + EXPECT_EQ(0u, session_->GetNumOpenStreams()); + + // Send two bytes of payload. + QuicPacketHeader header; + header.public_header.guid = guid_; + header.public_header.reset_flag = false; + header.public_header.version_flag = false; + QuicStreamFrame data1(3, false, 0, "HT"); + vector<QuicStreamFrame> frames; + frames.push_back(data1); + + // When we get data for the closed stream, it implies the far side has + // compressed some headers. As a result we're going to bail due to + // unrecoverable compression context state. + EXPECT_CALL(*connection_, SendConnectionClose( + QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED)); + EXPECT_FALSE(visitor_->OnStreamFrames(frames)); + + // The stream should never be opened, now that the reset is received. + EXPECT_EQ(0u, session_->GetNumOpenStreams()); +} + +TEST_F(QuicServerSessionTest, GoOverPrematureClosedStreamLimit) { + QuicPacketHeader header; + header.public_header.guid = guid_; + header.public_header.reset_flag = false; + header.public_header.version_flag = false; + QuicStreamFrame data1(3, false, 0, "H"); + vector<QuicStreamFrame> frames; + frames.push_back(data1); + + // Set up the stream such that it's open in OnPacket, but closes half way + // through while on the decompression blocked list. + session_->CloseStreamOnData(); + + EXPECT_CALL(*connection_, SendConnectionClose( + QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED)); + EXPECT_FALSE(visitor_->OnStreamFrames(frames)); +} + +TEST_F(QuicServerSessionTest, AcceptClosedStream) { + QuicPacketHeader header; + header.public_header.guid = guid_; + header.public_header.reset_flag = false; + header.public_header.version_flag = false; + vector<QuicStreamFrame> frames; + // Send (empty) compressed headers followed by two bytes of data. + frames.push_back(QuicStreamFrame(3, false, 0, "\1\0\0\0\0\0\0\0HT")); + frames.push_back(QuicStreamFrame(5, false, 0, "\2\0\0\0\0\0\0\0HT")); + EXPECT_TRUE(visitor_->OnStreamFrames(frames)); + + // Pretend we got full headers, so we won't trigger the 'unercoverable + // compression context' state. + MarkHeadersReadForStream(3); + + // Send a reset. + QuicRstStreamFrame rst(3, QUIC_STREAM_NO_ERROR); + visitor_->OnRstStream(rst); + + // If we were tracking, we'd probably want to reject this because it's data + // past the reset point of stream 3. As it's a closed stream we just drop the + // data on the floor, but accept the packet because it has data for stream 5. + frames.clear(); + frames.push_back(QuicStreamFrame(3, false, 2, "TP")); + frames.push_back(QuicStreamFrame(5, false, 2, "TP")); + EXPECT_TRUE(visitor_->OnStreamFrames(frames)); +} + +TEST_F(QuicServerSessionTest, MaxNumConnections) { + EXPECT_EQ(0u, session_->GetNumOpenStreams()); + EXPECT_TRUE( + QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 3)); + EXPECT_TRUE( + QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 5)); + EXPECT_TRUE( + QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 7)); + EXPECT_FALSE( + QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 9)); +} + +TEST_F(QuicServerSessionTest, MaxNumConnectionsImplicit) { + EXPECT_EQ(0u, session_->GetNumOpenStreams()); + EXPECT_TRUE( + QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 3)); + // Implicitly opens two more streams before 9. + EXPECT_FALSE( + QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 9)); +} + +TEST_F(QuicServerSessionTest, GetEvenIncomingError) { + // Incoming streams on the server session must be odd. + EXPECT_EQ(NULL, + QuicServerSessionPeer::GetIncomingReliableStream( + session_.get(), 2)); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/net/tools/quic/quic_spdy_client_stream.h b/net/tools/quic/quic_spdy_client_stream.h index ec4d25747f..5d32b30d3c 100644 --- a/net/tools/quic/quic_spdy_client_stream.h +++ b/net/tools/quic/quic_spdy_client_stream.h @@ -34,6 +34,10 @@ class QuicSpdyClientStream : public QuicReliableClientStream { base::StringPiece body, bool fin) OVERRIDE; + // While the server's set_priority shouldn't be called externally, the creator + // of client-side streams should be able to set the priority. + using QuicReliableClientStream::set_priority; + private: int ParseResponseHeaders(); diff --git a/net/tools/quic/quic_time_wait_list_manager_test.cc b/net/tools/quic/quic_time_wait_list_manager_test.cc new file mode 100644 index 0000000000..8f59687437 --- /dev/null +++ b/net/tools/quic/quic_time_wait_list_manager_test.cc @@ -0,0 +1,397 @@ +// Copyright 2013 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/quic_time_wait_list_manager.h" + +#include <errno.h> + +#include "net/quic/crypto/crypto_protocol.h" +#include "net/quic/crypto/null_encrypter.h" +#include "net/quic/crypto/quic_decrypter.h" +#include "net/quic/crypto/quic_encrypter.h" +#include "net/quic/quic_data_reader.h" +#include "net/quic/quic_framer.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/quic/quic_packet_writer.h" +#include "net/tools/quic/test_tools/mock_epoll_server.h" +#include "net/tools/quic/test_tools/quic_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::FramerVisitorCapturingPublicReset; +using testing::_; +using testing::Args; +using testing::Matcher; +using testing::MatcherInterface; +using testing::Return; +using testing::SetArgPointee; +using testing::Truly; + +namespace net { +namespace tools { +namespace test { +namespace { + +class TestTimeWaitListManager : public QuicTimeWaitListManager { + public: + TestTimeWaitListManager(QuicPacketWriter* writer, + EpollServer* epoll_server) + : QuicTimeWaitListManager(writer, epoll_server) { + } + + using QuicTimeWaitListManager::is_write_blocked; + using QuicTimeWaitListManager::time_wait_period; + using QuicTimeWaitListManager::ShouldSendPublicReset; + using QuicTimeWaitListManager::GetQuicVersionFromGuid; +}; + +class MockFakeTimeEpollServer : public FakeTimeEpollServer { + public: + MOCK_METHOD2(RegisterAlarm, void(int64 timeout_in_us, + EpollAlarmCallbackInterface* alarm)); +}; + +class QuicTimeWaitListManagerTest : public testing::Test { + protected: + QuicTimeWaitListManagerTest() + : time_wait_list_manager_(&writer_, &epoll_server_), + framer_(QuicVersionMax(), + QuicTime::Zero(), + true), + guid_(45) { + } + + void AddGuid(QuicGuid guid) { + time_wait_list_manager_.AddGuidToTimeWait(guid, QuicVersionMax()); + } + + void AddGuid(QuicGuid guid, QuicVersion version) { + time_wait_list_manager_.AddGuidToTimeWait(guid, version); + } + + bool IsGuidInTimeWait(QuicGuid guid) { + return time_wait_list_manager_.IsGuidInTimeWait(guid); + } + + void ProcessPacket(QuicGuid guid, const QuicEncryptedPacket& packet) { + time_wait_list_manager_.ProcessPacket(server_address_, + client_address_, + guid, + packet); + } + + QuicEncryptedPacket* ConstructEncryptedPacket( + QuicGuid guid, + QuicPacketSequenceNumber sequence_number) { + QuicPacketHeader header; + header.public_header.guid = guid; + header.public_header.guid_length = PACKET_8BYTE_GUID; + header.public_header.version_flag = false; + header.public_header.reset_flag = false; + header.public_header.sequence_number_length = PACKET_6BYTE_SEQUENCE_NUMBER; + header.packet_sequence_number = sequence_number; + header.entropy_flag = false; + header.entropy_hash = 0; + header.fec_flag = false; + header.is_in_fec_group = NOT_IN_FEC_GROUP; + header.fec_group = 0; + QuicStreamFrame stream_frame(1, false, 0, "data"); + QuicFrame frame(&stream_frame); + QuicFrames frames; + frames.push_back(frame); + scoped_ptr<QuicPacket> packet( + framer_.BuildUnsizedDataPacket(header, frames).packet); + EXPECT_TRUE(packet != NULL); + QuicEncryptedPacket* encrypted = framer_.EncryptPacket(ENCRYPTION_NONE, + sequence_number, + *packet); + EXPECT_TRUE(encrypted != NULL); + return encrypted; + } + + MockFakeTimeEpollServer epoll_server_; + MockPacketWriter writer_; + TestTimeWaitListManager time_wait_list_manager_; + QuicFramer framer_; + QuicGuid guid_; + IPEndPoint server_address_; + IPEndPoint client_address_; +}; + +class ValidatePublicResetPacketPredicate + : public MatcherInterface<const std::tr1::tuple<const char*, int> > { + public: + explicit ValidatePublicResetPacketPredicate(QuicGuid guid, + QuicPacketSequenceNumber number) + : guid_(guid), sequence_number_(number) { + } + + virtual bool MatchAndExplain( + const std::tr1::tuple<const char*, int> packet_buffer, + testing::MatchResultListener* /* listener */) const { + FramerVisitorCapturingPublicReset visitor; + QuicFramer framer(QuicVersionMax(), + QuicTime::Zero(), + false); + framer.set_visitor(&visitor); + QuicEncryptedPacket encrypted(std::tr1::get<0>(packet_buffer), + std::tr1::get<1>(packet_buffer)); + framer.ProcessPacket(encrypted); + QuicPublicResetPacket packet = visitor.public_reset_packet(); + return guid_ == packet.public_header.guid && + packet.public_header.reset_flag && !packet.public_header.version_flag && + sequence_number_ == packet.rejected_sequence_number; + } + + virtual void DescribeTo(::std::ostream* os) const { } + + virtual void DescribeNegationTo(::std::ostream* os) const { } + + private: + QuicGuid guid_; + QuicPacketSequenceNumber sequence_number_; +}; + +void ValidPublicResetPacketPredicate( + QuicGuid expected_guid, + QuicPacketSequenceNumber expected_sequence_number, + const std::tr1::tuple<const char*, int>& packet_buffer) { + FramerVisitorCapturingPublicReset visitor; + QuicFramer framer(QuicVersionMax(), + QuicTime::Zero(), + false); + framer.set_visitor(&visitor); + QuicEncryptedPacket encrypted(std::tr1::get<0>(packet_buffer), + std::tr1::get<1>(packet_buffer)); + framer.ProcessPacket(encrypted); + QuicPublicResetPacket packet = visitor.public_reset_packet(); + EXPECT_EQ(expected_guid, packet.public_header.guid); + EXPECT_TRUE(packet.public_header.reset_flag); + EXPECT_FALSE(packet.public_header.version_flag); + EXPECT_EQ(expected_sequence_number, packet.rejected_sequence_number); +} + + +Matcher<const std::tr1::tuple<const char*, int> > PublicResetPacketEq( + QuicGuid guid, + QuicPacketSequenceNumber sequence_number) { + return MakeMatcher(new ValidatePublicResetPacketPredicate(guid, + sequence_number)); +} + +TEST_F(QuicTimeWaitListManagerTest, CheckGuidInTimeWait) { + EXPECT_FALSE(IsGuidInTimeWait(guid_)); + AddGuid(guid_); + EXPECT_TRUE(IsGuidInTimeWait(guid_)); +} + +TEST_F(QuicTimeWaitListManagerTest, SendPublicReset) { + AddGuid(guid_); + const int kRandomSequenceNumber = 1; + scoped_ptr<QuicEncryptedPacket> packet( + ConstructEncryptedPacket(guid_, kRandomSequenceNumber)); + EXPECT_CALL(writer_, WritePacket(_, _, + server_address_.address(), + client_address_, + &time_wait_list_manager_, + _)) + .With(Args<0, 1>(PublicResetPacketEq(guid_, + kRandomSequenceNumber))) + .WillOnce(Return(packet->length())); + + ProcessPacket(guid_, *packet); +} + +TEST_F(QuicTimeWaitListManagerTest, DropInvalidPacket) { + AddGuid(guid_); + const char buffer[] = "invalid"; + QuicEncryptedPacket packet(buffer, arraysize(buffer)); + ProcessPacket(guid_, packet); + // Will get called for a valid packet since received packet count = 1 (2 ^ 0). + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)).Times(0); +} + +TEST_F(QuicTimeWaitListManagerTest, DropPublicResetPacket) { + AddGuid(guid_); + QuicPublicResetPacket packet; + packet.public_header.guid = guid_; + packet.public_header.version_flag = false; + packet.public_header.reset_flag = true; + packet.rejected_sequence_number = 239191; + packet.nonce_proof = 1010101; + scoped_ptr<QuicEncryptedPacket> public_reset_packet( + QuicFramer::BuildPublicResetPacket(packet)); + ProcessPacket(guid_, *public_reset_packet); + // Will get called for a data packet since received packet count = 1 (2 ^ 0). + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)) + .Times(0); +} + +TEST_F(QuicTimeWaitListManagerTest, SendPublicResetWithExponentialBackOff) { + AddGuid(guid_); + for (int sequence_number = 1; sequence_number < 101; ++sequence_number) { + scoped_ptr<QuicEncryptedPacket> packet( + ConstructEncryptedPacket(guid_, sequence_number)); + if ((sequence_number & (sequence_number - 1)) == 0) { + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)) + .WillOnce(Return(1)); + } + ProcessPacket(guid_, *packet); + // Send public reset with exponential back off. + if ((sequence_number & (sequence_number - 1)) == 0) { + EXPECT_TRUE( + time_wait_list_manager_.ShouldSendPublicReset(sequence_number)); + } else { + EXPECT_FALSE( + time_wait_list_manager_.ShouldSendPublicReset(sequence_number)); + } + } +} + +TEST_F(QuicTimeWaitListManagerTest, CleanUpOldGuids) { + const int kGuidCount = 100; + const int kOldGuidCount = 31; + + // Add guids such that their expiry time is kTimeWaitPeriod_. + epoll_server_.set_now_in_usec(0); + for (int guid = 1; guid <= kOldGuidCount; ++guid) { + AddGuid(guid); + } + + // Add remaining guids such that their add time is 2 * kTimeWaitPeriod. + const QuicTime::Delta time_wait_period = + time_wait_list_manager_.time_wait_period(); + epoll_server_.set_now_in_usec(time_wait_period.ToMicroseconds()); + for (int guid = kOldGuidCount + 1; guid <= kGuidCount; ++guid) { + AddGuid(guid); + } + + QuicTime::Delta offset = QuicTime::Delta::FromMicroseconds(39); + // Now set the current time as time_wait_period + offset usecs. + epoll_server_.set_now_in_usec(time_wait_period.Add(offset).ToMicroseconds()); + // After all the old guids are cleaned up, check the next alarm interval. + int64 next_alarm_time = epoll_server_.ApproximateNowInUsec() + + time_wait_period.Subtract(offset).ToMicroseconds(); + EXPECT_CALL(epoll_server_, RegisterAlarm(next_alarm_time, _)); + + time_wait_list_manager_.CleanUpOldGuids(); + for (int guid = 1; guid <= kGuidCount; ++guid) { + EXPECT_EQ(guid > kOldGuidCount, IsGuidInTimeWait(guid)) + << "kOldGuidCount: " << kOldGuidCount + << " guid: " << guid; + } +} + +TEST_F(QuicTimeWaitListManagerTest, SendQueuedPackets) { + QuicGuid guid = 1; + AddGuid(guid); + QuicPacketSequenceNumber sequence_number = 234; + scoped_ptr<QuicEncryptedPacket> packet( + ConstructEncryptedPacket(guid, sequence_number)); + // Let first write through. + EXPECT_CALL(writer_, WritePacket(_, _, + server_address_.address(), + client_address_, + &time_wait_list_manager_, + _)) + .With(Args<0, 1>(PublicResetPacketEq(guid, + sequence_number))) + .WillOnce(Return(packet->length())); + ProcessPacket(guid, *packet); + EXPECT_FALSE(time_wait_list_manager_.is_write_blocked()); + + // write block for the next packet. + EXPECT_CALL(writer_, WritePacket(_, _, + server_address_.address(), + client_address_, + &time_wait_list_manager_, + _)) + .With(Args<0, 1>(PublicResetPacketEq(guid, + sequence_number))) + .WillOnce(DoAll(SetArgPointee<5>(EAGAIN), Return(-1))); + ProcessPacket(guid, *packet); + // 3rd packet. No public reset should be sent; + ProcessPacket(guid, *packet); + EXPECT_TRUE(time_wait_list_manager_.is_write_blocked()); + + // write packet should not be called since already write blocked but the + // should be queued. + QuicGuid other_guid = 2; + AddGuid(other_guid); + QuicPacketSequenceNumber other_sequence_number = 23423; + scoped_ptr<QuicEncryptedPacket> other_packet( + ConstructEncryptedPacket(other_guid, other_sequence_number)); + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)) + .Times(0); + ProcessPacket(other_guid, *other_packet); + + // Now expect all the write blocked public reset packets to be sent again. + EXPECT_CALL(writer_, WritePacket(_, _, + server_address_.address(), + client_address_, + &time_wait_list_manager_, + _)) + .With(Args<0, 1>(PublicResetPacketEq(guid, + sequence_number))) + .WillOnce(Return(packet->length())); + EXPECT_CALL(writer_, WritePacket(_, _, + server_address_.address(), + client_address_, + &time_wait_list_manager_, + _)) + .With(Args<0, 1>(PublicResetPacketEq(other_guid, + other_sequence_number))) + .WillOnce(Return(other_packet->length())); + time_wait_list_manager_.OnCanWrite(); + EXPECT_FALSE(time_wait_list_manager_.is_write_blocked()); +} + +TEST_F(QuicTimeWaitListManagerTest, MakeSureFramerUsesCorrectVersion) { + const int kRandomSequenceNumber = 1; + scoped_ptr<QuicEncryptedPacket> packet; + + AddGuid(guid_, QuicVersionMin()); + framer_.set_version(QuicVersionMin()); + packet.reset(ConstructEncryptedPacket(guid_, kRandomSequenceNumber)); + + // Reset packet should be written, using the minimum quic version. + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)).Times(1); + ProcessPacket(guid_, *packet); + EXPECT_EQ(time_wait_list_manager_.version(), QuicVersionMin()); + + // New guid + ++guid_; + + AddGuid(guid_, QuicVersionMax()); + framer_.set_version(QuicVersionMax()); + packet.reset(ConstructEncryptedPacket(guid_, kRandomSequenceNumber)); + + // Reset packet should be written, using the maximum quic version. + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)).Times(1); + ProcessPacket(guid_, *packet); + EXPECT_EQ(time_wait_list_manager_.version(), QuicVersionMax()); +} + +TEST_F(QuicTimeWaitListManagerTest, GetQuicVersionFromMap) { + const int kGuid1 = 123; + const int kGuid2 = 456; + const int kGuid3 = 789; + + AddGuid(kGuid1, QuicVersionMin()); + AddGuid(kGuid2, QuicVersionMax()); + AddGuid(kGuid3, QuicVersionMax()); + + EXPECT_EQ(QuicVersionMin(), + time_wait_list_manager_.GetQuicVersionFromGuid(kGuid1)); + EXPECT_EQ(QuicVersionMax(), + time_wait_list_manager_.GetQuicVersionFromGuid(kGuid2)); + EXPECT_EQ(QuicVersionMax(), + time_wait_list_manager_.GetQuicVersionFromGuid(kGuid3)); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/net/tools/quic/test_tools/quic_test_client.cc b/net/tools/quic/test_tools/quic_test_client.cc index 859d7a5604..272786ebf4 100644 --- a/net/tools/quic/test_tools/quic_test_client.cc +++ b/net/tools/quic/test_tools/quic_test_client.cc @@ -11,6 +11,7 @@ #include "net/quic/crypto/proof_verifier.h" #include "net/tools/flip_server/balsa_headers.h" #include "net/tools/quic/quic_epoll_connection_helper.h" +#include "net/tools/quic/quic_spdy_client_stream.h" #include "net/tools/quic/test_tools/http_message_test_utils.h" #include "url/gurl.h" @@ -26,7 +27,6 @@ class RecordingProofVerifier : public net::ProofVerifier { public: // ProofVerifier interface. virtual net::ProofVerifier::Status VerifyProof( - net::QuicVersion version, const string& hostname, const string& server_config, const vector<string>& certs, @@ -158,6 +158,7 @@ void QuicTestClient::Initialize(IPEndPoint address, server_address_ = address; stream_ = NULL; stream_error_ = QUIC_STREAM_NO_ERROR; + priority_ = 3; bytes_read_ = 0; bytes_written_= 0; never_connected_ = true; @@ -244,10 +245,13 @@ QuicReliableClientStream* QuicTestClient::GetOrCreateStream() { } if (!stream_) { stream_ = client_->CreateReliableClientStream(); - if (stream_ != NULL) { - stream_->set_visitor(this); + if (stream_ == NULL) { + return NULL; } + stream_->set_visitor(this); + reinterpret_cast<QuicSpdyClientStream*>(stream_)->set_priority(priority_); } + return stream_; } diff --git a/net/tools/quic/test_tools/quic_test_client.h b/net/tools/quic/test_tools/quic_test_client.h index 74bfc24646..3cd71d59f8 100644 --- a/net/tools/quic/test_tools/quic_test_client.h +++ b/net/tools/quic/test_tools/quic_test_client.h @@ -107,6 +107,8 @@ class QuicTestClient : public ReliableQuicStream::Visitor { void set_auto_reconnect(bool reconnect) { auto_reconnect_ = reconnect; } + void set_priority(QuicPriority priority) { priority_ = priority; } + private: void Initialize(IPEndPoint address, const string& hostname, bool secure); @@ -118,6 +120,8 @@ class QuicTestClient : public ReliableQuicStream::Visitor { QuicRstStreamErrorCode stream_error_; BalsaHeaders headers_; + QuicPriority priority_; + string response_; uint64 bytes_read_; uint64 bytes_written_; diff --git a/net/tools/quic/test_tools/quic_test_utils.cc b/net/tools/quic/test_tools/quic_test_utils.cc index 481a57f833..fa3627ba74 100644 --- a/net/tools/quic/test_tools/quic_test_utils.cc +++ b/net/tools/quic/test_tools/quic_test_utils.cc @@ -50,6 +50,13 @@ void MockConnection::AdvanceTime(QuicTime::Delta delta) { static_cast<MockHelper*>(helper())->AdvanceTime(delta); } + +MockQuicSessionOwner::MockQuicSessionOwner() { +} + +MockQuicSessionOwner::~MockQuicSessionOwner() { +} + bool TestDecompressorVisitor::OnDecompressedData(StringPiece data) { data.AppendToString(&data_); return true; @@ -82,6 +89,12 @@ MockAckNotifierDelegate::MockAckNotifierDelegate() { MockAckNotifierDelegate::~MockAckNotifierDelegate() { } +MockPacketWriter::MockPacketWriter() { +} + +MockPacketWriter::~MockPacketWriter() { +} + } // namespace test } // namespace tools } // namespace net diff --git a/net/tools/quic/test_tools/quic_test_utils.h b/net/tools/quic/test_tools/quic_test_utils.h index ba33c8bbfe..dffa2558af 100644 --- a/net/tools/quic/test_tools/quic_test_utils.h +++ b/net/tools/quic/test_tools/quic_test_utils.h @@ -12,6 +12,8 @@ #include "net/quic/quic_session.h" #include "net/quic/quic_spdy_decompressor.h" #include "net/spdy/spdy_framer.h" +#include "net/tools/quic/quic_packet_writer.h" +#include "net/tools/quic/quic_server_session.h" #include "testing/gmock/include/gmock/gmock.h" namespace net { @@ -69,6 +71,13 @@ class MockConnection : public QuicConnection { DISALLOW_COPY_AND_ASSIGN(MockConnection); }; +class MockQuicSessionOwner : public QuicSessionOwner { + public: + MockQuicSessionOwner(); + ~MockQuicSessionOwner(); + MOCK_METHOD2(OnConnectionClose, void(QuicGuid guid, QuicErrorCode error)); +}; + class TestDecompressorVisitor : public QuicSpdyDecompressor::Visitor { public: virtual ~TestDecompressorVisitor() {} @@ -111,6 +120,19 @@ class MockAckNotifierDelegate : public QuicAckNotifier::DelegateInterface { MOCK_METHOD0(OnAckNotification, void()); }; +class MockPacketWriter : public QuicPacketWriter { + public: + MockPacketWriter(); + virtual ~MockPacketWriter(); + + MOCK_METHOD6(WritePacket, int(const char* buffer, + size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address, + QuicBlockedWriterInterface* blocked_writer, + int* error)); +}; + } // namespace test } // namespace tools } // namespace net diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index f343f70e8f..e0317e8868 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -1348,7 +1348,7 @@ class TestPageHandler(testserver_base.BasePageHandler): if query_char < 0 or len(self.path) <= query_char + 1: self.sendRedirectHelp(test_name) return True - dest = self.path[query_char + 1:] + dest = urllib.unquote(self.path[query_char + 1:]) self.send_response(301) # moved permanently self.send_header('Location', dest) @@ -1372,7 +1372,7 @@ class TestPageHandler(testserver_base.BasePageHandler): if query_char < 0 or len(self.path) <= query_char + 1: self.sendRedirectHelp(test_name) return True - dest = self.path[query_char + 1:] + dest = urllib.unquote(self.path[query_char + 1:]) self.send_response(200) self.send_header('Content-Type', 'text/html') diff --git a/net/url_request/test_url_fetcher_factory.cc b/net/url_request/test_url_fetcher_factory.cc index e0394c44d1..30380354b2 100644 --- a/net/url_request/test_url_fetcher_factory.cc +++ b/net/url_request/test_url_fetcher_factory.cc @@ -357,11 +357,18 @@ URLFetcher* FakeURLFetcherFactory::CreateURLFetcher( return fake_fetcher.release(); } +void FakeURLFetcherFactory::SetFakeResponseForURL( + const GURL& url, + const std::string& response_data, + bool success) { + // Overwrite existing URL if it already exists. + fake_responses_[url] = std::make_pair(response_data, success); +} + void FakeURLFetcherFactory::SetFakeResponse(const std::string& url, const std::string& response_data, bool success) { - // Overwrite existing URL if it already exists. - fake_responses_[GURL(url)] = std::make_pair(response_data, success); + SetFakeResponseForURL(GURL(url), response_data, success); } void FakeURLFetcherFactory::ClearFakeResponses() { diff --git a/net/url_request/test_url_fetcher_factory.h b/net/url_request/test_url_fetcher_factory.h index 89f74c2f59..35b4607f99 100644 --- a/net/url_request/test_url_fetcher_factory.h +++ b/net/url_request/test_url_fetcher_factory.h @@ -377,6 +377,12 @@ class FakeURLFetcherFactory : public URLFetcherFactory, // Sets the fake response for a given URL. If success is true we will serve // an HTTP/200 and an HTTP/500 otherwise. The |response_data| may be empty. + void SetFakeResponseForURL(const GURL& url, + const std::string& response_data, + bool success); + + // Convenience helper that calls SetFakeResponseForURL with GURL(url). + // TODO(mnissler): Convert callers to SetFakeResponseForURL. void SetFakeResponse(const std::string& url, const std::string& response_data, bool success); diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc index 669c845de9..bf4aafcb8c 100644 --- a/net/url_request/url_request_job.cc +++ b/net/url_request/url_request_job.cc @@ -319,6 +319,10 @@ void URLRequestJob::NotifyHeadersComplete() { new_location = new_location.ReplaceComponents(replacements); } + // Redirect response bodies are not read. Notify the transaction + // so it does not treat being stopped as an error. + DoneReading(); + bool defer_redirect = false; request_->NotifyReceivedRedirect(new_location, &defer_redirect); diff --git a/net/url_request/url_request_job_unittest.cc b/net/url_request/url_request_job_unittest.cc index 354915fe12..5f63b0927d 100644 --- a/net/url_request/url_request_job_unittest.cc +++ b/net/url_request/url_request_job_unittest.cc @@ -37,6 +37,24 @@ const MockTransaction kGZip_Transaction = { net::OK }; +const MockTransaction kRedirect_Transaction = { + "http://www.google.com/redirect", + "GET", + base::Time(), + "", + net::LOAD_NORMAL, + "HTTP/1.1 302 Found", + "Cache-Control: max-age=10000\n" + "Location: http://www.google.com/destination\n" + "Content-Length: 5\n", + base::Time(), + "hello", + TEST_MODE_NORMAL, + NULL, + 0, + net::OK +}; + } // namespace TEST(URLRequestJob, TransactionNotifiedWhenDone) { @@ -78,3 +96,22 @@ TEST(URLRequestJob, SyncTransactionNotifiedWhenDone) { RemoveMockTransaction(&transaction); } + +TEST(URLRequestJob, RedirectTransactionNotifiedWhenDone) { + MockNetworkLayer network_layer; + net::TestURLRequestContext context; + context.set_http_transaction_factory(&network_layer); + + net::TestDelegate d; + net::TestURLRequest req(GURL(kRedirect_Transaction.url), &d, &context, NULL); + AddMockTransaction(&kRedirect_Transaction); + + req.set_method("GET"); + req.Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_TRUE(network_layer.done_reading_called()); + + RemoveMockTransaction(&kRedirect_Transaction); +} diff --git a/net/websockets/README b/net/websockets/README index 25b3e84315..1d1e1c3538 100644 --- a/net/websockets/README +++ b/net/websockets/README @@ -40,6 +40,11 @@ websocket_deflater.cc websocket_deflater_test.cc websocket_errors.cc websocket_errors.h +websocket_extension.cc +websocket_extension.h +websocket_extension_parser.cc +websocket_extension_parser.h +websocket_extension_parser_test.cc websocket_errors_test.cc websocket_event_interface.h websocket_frame.cc diff --git a/net/websockets/websocket_extension.cc b/net/websockets/websocket_extension.cc new file mode 100644 index 0000000000..edcd8e8657 --- /dev/null +++ b/net/websockets/websocket_extension.cc @@ -0,0 +1,43 @@ +// Copyright 2013 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/websockets/websocket_extension.h" + +#include <string> + +#include "base/logging.h" + +namespace net { + +WebSocketExtension::Parameter::Parameter(const std::string& name) + : name_(name) {} + +WebSocketExtension::Parameter::Parameter(const std::string& name, + const std::string& value) + : name_(name), value_(value) { + DCHECK(!value.empty()); +} + +bool WebSocketExtension::Parameter::Equals(const Parameter& other) const { + return name_ == other.name_ && value_ == other.value_; +} + +WebSocketExtension::WebSocketExtension() {} + +WebSocketExtension::WebSocketExtension(const std::string& name) + : name_(name) {} + +WebSocketExtension::~WebSocketExtension() {} + +bool WebSocketExtension::Equals(const WebSocketExtension& other) const { + if (name_ != other.name_) return false; + if (parameters_.size() != other.parameters_.size()) return false; + for (size_t i = 0; i < other.parameters_.size(); ++i) { + if (!parameters_[i].Equals(other.parameters_[i])) + return false; + } + return true; +} + +} // namespace net diff --git a/net/websockets/websocket_extension.h b/net/websockets/websocket_extension.h new file mode 100644 index 0000000000..5af4023869 --- /dev/null +++ b/net/websockets/websocket_extension.h @@ -0,0 +1,57 @@ +// Copyright 2013 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_WEBSOCKETS_WEBSOCKET_EXTENSION_H_ +#define NET_WEBSOCKETS_WEBSOCKET_EXTENSION_H_ + +#include <string> +#include <vector> + +#include "net/base/net_export.h" + +namespace net { + +// A WebSocketExtension instance represents a WebSocket extension specified +// in RFC6455. +class NET_EXPORT_PRIVATE WebSocketExtension { + public: + // Note that RFC6455 does not allow a parameter with an empty value. + class NET_EXPORT_PRIVATE Parameter { + public: + // Construct a parameter which does not have a value. + explicit Parameter(const std::string& name); + // Construct a parameter with a non-empty value. + Parameter(const std::string& name, const std::string& value); + + bool HasValue() const { return !value_.empty(); } + const std::string& name() const { return name_; } + const std::string& value() const { return value_; } + bool Equals(const Parameter& other) const; + + // The default copy constructor and the assignment operator are defined: + // we need them. + private: + std::string name_; + std::string value_; + }; + + WebSocketExtension(); + explicit WebSocketExtension(const std::string& name); + ~WebSocketExtension(); + + void Add(const Parameter& parameter) { parameters_.push_back(parameter); } + const std::string& name() const { return name_; } + const std::vector<Parameter>& parameters() const { return parameters_; } + bool Equals(const WebSocketExtension& other) const; + + // The default copy constructor and the assignment operator are defined: + // we need them. + private: + std::string name_; + std::vector<Parameter> parameters_; +}; + +} // namespace net + +#endif // NET_WEBSOCKETS_WEBSOCKET_EXTENSION_H_ diff --git a/net/websockets/websocket_extension_parser.cc b/net/websockets/websocket_extension_parser.cc new file mode 100644 index 0000000000..28a2db16f2 --- /dev/null +++ b/net/websockets/websocket_extension_parser.cc @@ -0,0 +1,158 @@ +// Copyright 2013 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/websockets/websocket_extension_parser.h" + +#include "base/strings/string_util.h" + +namespace net { + +WebSocketExtensionParser::WebSocketExtensionParser() {} + +WebSocketExtensionParser::~WebSocketExtensionParser() {} + +void WebSocketExtensionParser::Parse(const char* data, size_t size) { + current_ = data; + end_ = data + size; + has_error_ = false; + + ConsumeExtension(&extension_); + if (has_error_) return; + ConsumeSpaces(); + has_error_ = has_error_ || (current_ != end_); +} + +void WebSocketExtensionParser::Consume(char c) { + DCHECK(!has_error_); + ConsumeSpaces(); + DCHECK(!has_error_); + if (current_ == end_ || c != current_[0]) { + has_error_ = true; + return; + } + ++current_; +} + +void WebSocketExtensionParser::ConsumeExtension(WebSocketExtension* extension) { + DCHECK(!has_error_); + base::StringPiece name; + ConsumeToken(&name); + if (has_error_) return; + *extension = WebSocketExtension(name.as_string()); + + while (ConsumeIfMatch(';')) { + WebSocketExtension::Parameter parameter((std::string())); + ConsumeExtensionParameter(¶meter); + if (has_error_) return; + extension->Add(parameter); + } +} + +void WebSocketExtensionParser::ConsumeExtensionParameter( + WebSocketExtension::Parameter* parameter) { + DCHECK(!has_error_); + base::StringPiece name, value; + std::string value_string; + + ConsumeToken(&name); + if (has_error_) return; + if (!ConsumeIfMatch('=')) { + *parameter = WebSocketExtension::Parameter(name.as_string()); + return; + } + + if (Lookahead('\"')) { + ConsumeQuotedToken(&value_string); + } else { + ConsumeToken(&value); + value_string = value.as_string(); + } + if (has_error_) return; + *parameter = WebSocketExtension::Parameter(name.as_string(), value_string); +} + +void WebSocketExtensionParser::ConsumeToken(base::StringPiece* token) { + DCHECK(!has_error_); + ConsumeSpaces(); + DCHECK(!has_error_); + const char* head = current_; + while (current_ < end_ && + !IsControl(current_[0]) && !IsSeparator(current_[0])) + ++current_; + if (current_ == head) { + has_error_ = true; + return; + } + *token = base::StringPiece(head, current_ - head); +} + +void WebSocketExtensionParser::ConsumeQuotedToken(std::string* token) { + DCHECK(!has_error_); + Consume('"'); + if (has_error_) return; + *token = ""; + while (current_ < end_ && !IsControl(current_[0])) { + if (UnconsumedBytes() >= 2 && current_[0] == '\\') { + char next = current_[1]; + if (IsControl(next) || IsSeparator(next)) break; + *token += next; + current_ += 2; + } else if (IsSeparator(current_[0])) { + break; + } else { + *token += current_[0]; + ++current_; + } + } + // We can't use Consume here because we don't want to consume spaces. + if (current_ < end_ && current_[0] == '"') + ++current_; + else + has_error_ = true; + has_error_ = has_error_ || token->empty(); +} + +void WebSocketExtensionParser::ConsumeSpaces() { + DCHECK(!has_error_); + while (current_ < end_ && (current_[0] == ' ' || current_[0] == '\t')) + ++current_; + return; +} + +bool WebSocketExtensionParser::Lookahead(char c) { + DCHECK(!has_error_); + const char* head = current_; + + Consume(c); + bool result = !has_error_; + current_ = head; + has_error_ = false; + return result; +} + +bool WebSocketExtensionParser::ConsumeIfMatch(char c) { + DCHECK(!has_error_); + const char* head = current_; + + Consume(c); + if (has_error_) { + current_ = head; + has_error_ = false; + return false; + } + return true; +} + +// static +bool WebSocketExtensionParser::IsControl(char c) { + return (0 <= c && c <= 31) || c == 127; +} + +// static +bool WebSocketExtensionParser::IsSeparator(char c) { + const char separators[] = "()<>@,;:\\\"/[]?={} \t"; + return strchr(separators, c) != NULL; +} + +} // namespace net diff --git a/net/websockets/websocket_extension_parser.h b/net/websockets/websocket_extension_parser.h new file mode 100644 index 0000000000..ef7fe03665 --- /dev/null +++ b/net/websockets/websocket_extension_parser.h @@ -0,0 +1,59 @@ +// Copyright 2013 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_WEBSOCKETS_WEBSOCKET_EXTENSION_PARSER_H_ +#define NET_WEBSOCKETS_WEBSOCKET_EXTENSION_PARSER_H_ + +#include <string> + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/websockets/websocket_extension.h" + +namespace net { + +class NET_EXPORT_PRIVATE WebSocketExtensionParser { + public: + WebSocketExtensionParser(); + ~WebSocketExtensionParser(); + + // Parses the given string as a WebSocket extension header value. + // This parser assumes some preprocesses are made. + // - The parser parses single extension at a time. This means that + // the parser parses |extension| in RFC6455 9.1, not |extension-list|. + // - There is no newline characters in the input. LWS-concatenation must + // have already been done. + void Parse(const char* data, size_t size); + void Parse(const std::string& data) { + Parse(data.data(), data.size()); + } + + bool has_error() const { return has_error_; } + const WebSocketExtension& extension() const { return extension_; } + + private: + void Consume(char c); + void ConsumeExtension(WebSocketExtension* extension); + void ConsumeExtensionParameter(WebSocketExtension::Parameter* parameter); + void ConsumeToken(base::StringPiece* token); + void ConsumeQuotedToken(std::string* token); + void ConsumeSpaces(); + bool Lookahead(char c); + bool ConsumeIfMatch(char c); + size_t UnconsumedBytes() const { return end_ - current_; } + + static bool IsControl(char c); + static bool IsSeparator(char c); + + const char* current_; + const char* end_; + bool has_error_; + WebSocketExtension extension_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketExtensionParser); +}; + +} // namespace net + +#endif // NET_WEBSOCKETS_WEBSOCKET_EXTENSION_PARSER_H_ diff --git a/net/websockets/websocket_extension_parser_test.cc b/net/websockets/websocket_extension_parser_test.cc new file mode 100644 index 0000000000..dc7dc859d8 --- /dev/null +++ b/net/websockets/websocket_extension_parser_test.cc @@ -0,0 +1,122 @@ +// Copyright 2013 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/websockets/websocket_extension_parser.h" + +#include <string> + +#include "net/websockets/websocket_extension.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +TEST(WebSocketExtensionParserTest, ParseEmpty) { + WebSocketExtensionParser parser; + parser.Parse("", 0); + + EXPECT_TRUE(parser.has_error()); +} + +TEST(WebSocketExtensionParserTest, ParseSimple) { + WebSocketExtensionParser parser; + WebSocketExtension expected("foo"); + + parser.Parse("foo"); + + ASSERT_FALSE(parser.has_error()); + EXPECT_TRUE(expected.Equals(parser.extension())); +} + +TEST(WebSocketExtensionParserTest, ParseOneExtensionWithOneParamWithoutValue) { + WebSocketExtensionParser parser; + WebSocketExtension expected("foo"); + expected.Add(WebSocketExtension::Parameter("bar")); + + parser.Parse("\tfoo ; bar"); + + ASSERT_FALSE(parser.has_error()); + EXPECT_TRUE(expected.Equals(parser.extension())); +} + +TEST(WebSocketExtensionParserTest, ParseOneExtensionWithOneParamWithValue) { + WebSocketExtensionParser parser; + WebSocketExtension expected("foo"); + expected.Add(WebSocketExtension::Parameter("bar", "baz")); + + parser.Parse("foo ; bar= baz\t"); + + ASSERT_FALSE(parser.has_error()); + EXPECT_TRUE(expected.Equals(parser.extension())); +} + +TEST(WebSocketExtensionParserTest, ParseOneExtensionWithParams) { + WebSocketExtensionParser parser; + WebSocketExtension expected("foo"); + expected.Add(WebSocketExtension::Parameter("bar", "baz")); + expected.Add(WebSocketExtension::Parameter("hoge", "fuga")); + + parser.Parse("foo ; bar= baz;\t \thoge\t\t=fuga"); + + ASSERT_FALSE(parser.has_error()); + EXPECT_TRUE(expected.Equals(parser.extension())); +} + +TEST(WebSocketExtensionParserTest, InvalidPatterns) { + const char* patterns[] = { + "fo\ao", // control in extension name + "fo\x01o", // control in extension name + "fo<o", // separator in extension name + "foo/", // separator in extension name + ";bar", // empty extension name + "foo bar", // missing ';' + "foo;", // extension parameter without name and value + "foo; b\ar", // control in parameter name + "foo; b\x7fr", // control in parameter name + "foo; b[r", // separator in parameter name + "foo; ba:", // separator in parameter name + "foo; =baz", // empty parameter name + "foo; bar=", // empty parameter value + "foo; =", // empty parameter name and value + "foo; bar=b\x02z", // control in parameter value + "foo; bar=b@z", // separator in parameter value + "foo; bar=b\\z", // separator in parameter value + "foo; bar=b?z", // separator in parameter value + "\"foo\"", // quoted extension name + "foo; \"bar\"", // quoted parameter name + "foo; bar=\"\a2\"", // control in quoted parameter value + "foo; bar=\"b@z\"", // separator in quoted parameter value + "foo; bar=\"b\\\\z\"", // separator in quoted parameter value + "foo; bar=\"\"", // quoted empty parameter value + "foo; bar=\"baz", // unterminated quoted string + "foo; bar=\"baz \"", // space in quoted string + "foo; bar baz", // mising '=' + "foo; bar - baz", // '-' instead of '=' (note: "foo; bar-baz" is valid). + "foo; bar=\r\nbaz", // CRNL not followed by a space + "foo; bar=\r\n baz", // CRNL followed by a space + "foo, bar" // multiple extensions + }; + + for (size_t i = 0; i < arraysize(patterns); ++i) { + WebSocketExtensionParser parser; + parser.Parse(patterns[i]); + EXPECT_TRUE(parser.has_error()); + } +} + +TEST(WebSocketExtensionParserTest, QuotedParameterValue) { + WebSocketExtensionParser parser; + WebSocketExtension expected("foo"); + expected.Add(WebSocketExtension::Parameter("bar", "baz")); + + parser.Parse("foo; bar = \"ba\\z\" "); + + ASSERT_FALSE(parser.has_error()); + EXPECT_TRUE(expected.Equals(parser.extension())); +} + +} // namespace + +} // namespace net |