diff options
author | rtenneti@google.com <rtenneti@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-12 18:42:12 +0000 |
---|---|---|
committer | rtenneti@google.com <rtenneti@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-12 18:42:12 +0000 |
commit | 39c48fcca9e6dfcd163778ed17d3eff865cf204b (patch) | |
tree | eb28e6daabb23f5d290466102dc729da83708271 | |
parent | a1b9abbd91e5cfa56fb2649ce86709fac623f4ed (diff) | |
download | chromium_src-39c48fcca9e6dfcd163778ed17d3eff865cf204b.zip chromium_src-39c48fcca9e6dfcd163778ed17d3eff865cf204b.tar.gz chromium_src-39c48fcca9e6dfcd163778ed17d3eff865cf204b.tar.bz2 |
SPDY - integration of spdy/3 code.
TEST=network unit tests and browser unit_tests
R=willchan
Review URL: https://chromiumcodereview.appspot.com/9618002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@126175 0039d316-1c4b-4281-b951-d872f2087c98
55 files changed, 6062 insertions, 1430 deletions
diff --git a/chrome/browser/net/http_server_properties_manager.cc b/chrome/browser/net/http_server_properties_manager.cc index c39704a..3dabe59 100644 --- a/chrome/browser/net/http_server_properties_manager.cc +++ b/chrome/browser/net/http_server_properties_manager.cc @@ -159,6 +159,17 @@ bool HttpServerPropertiesManager::SetSpdySettings( return persist; } +bool HttpServerPropertiesManager::SetSpdySetting( + const net::HostPortPair& host_port_pair, + const spdy::SpdySetting& setting) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + bool persist = http_server_properties_impl_->SetSpdySetting( + host_port_pair, setting); + if (persist) + ScheduleUpdatePrefsOnIO(); + return persist; +} + void HttpServerPropertiesManager::ClearSpdySettings() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); http_server_properties_impl_->ClearSpdySettings(); @@ -302,10 +313,8 @@ void HttpServerPropertiesManager::UpdateCacheFromPrefsOnUI() { continue; } - spdy::SettingsFlagsAndId flags_and_id(0); - flags_and_id.set_id(id); - flags_and_id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); - + spdy::SettingsFlagsAndId flags_and_id( + spdy::SETTINGS_FLAG_PERSISTED, id); spdy_settings.push_back(spdy::SpdySetting(flags_and_id, value)); } diff --git a/chrome/browser/net/http_server_properties_manager.h b/chrome/browser/net/http_server_properties_manager.h index 2196f96..8d5d85e 100644 --- a/chrome/browser/net/http_server_properties_manager.h +++ b/chrome/browser/net/http_server_properties_manager.h @@ -117,6 +117,12 @@ class HttpServerPropertiesManager const net::HostPortPair& host_port_pair, const spdy::SpdySettings& settings) OVERRIDE; + // Saves an individual setting for a host. Returns true if SpdySetting is + // persisted. + virtual bool SetSpdySetting( + const net::HostPortPair& host_port_pair, + const spdy::SpdySetting& setting) OVERRIDE; + // Clears all spdy_settings. virtual void ClearSpdySettings() OVERRIDE; diff --git a/chrome/browser/net/http_server_properties_manager_unittest.cc b/chrome/browser/net/http_server_properties_manager_unittest.cc index 72fe555..35ca76c 100644 --- a/chrome/browser/net/http_server_properties_manager_unittest.cc +++ b/chrome/browser/net/http_server_properties_manager_unittest.cc @@ -281,9 +281,7 @@ TEST_F(HttpServerPropertiesManagerTest, SetSpdySettings) { // Add SpdySettings for mail.google.com:443. net::HostPortPair spdy_server_mail("mail.google.com", 443); spdy::SpdySettings spdy_settings; - spdy::SettingsFlagsAndId id1(0); - id1.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - id1.set_id(1234); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 1234); spdy_settings.push_back(std::make_pair(id1, 31337)); http_server_props_manager_->SetSpdySettings(spdy_server_mail, spdy_settings); @@ -302,6 +300,30 @@ TEST_F(HttpServerPropertiesManagerTest, SetSpdySettings) { Mock::VerifyAndClearExpectations(http_server_props_manager_.get()); } +TEST_F(HttpServerPropertiesManagerTest, SetSpdySetting) { + ExpectPrefsUpdate(); + + // Add SpdySetting for mail.google.com:443. + net::HostPortPair spdy_server_mail("mail.google.com", 443); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 1234); + http_server_props_manager_->SetSpdySetting( + spdy_server_mail, std::make_pair(id1, 31337)); + + // Run the task. + loop_.RunAllPending(); + + spdy::SpdySettings spdy_settings_ret = + http_server_props_manager_->GetSpdySettings(spdy_server_mail); + ASSERT_EQ(1U, spdy_settings_ret.size()); + spdy::SpdySetting spdy_setting1_ret = spdy_settings_ret.front(); + spdy::SettingsFlagsAndId id1_ret(spdy_setting1_ret.first); + EXPECT_EQ(1234U, id1_ret.id()); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, id1_ret.flags()); + EXPECT_EQ(31337U, spdy_setting1_ret.second); + + Mock::VerifyAndClearExpectations(http_server_props_manager_.get()); +} + TEST_F(HttpServerPropertiesManagerTest, HasAlternateProtocol) { ExpectPrefsUpdate(); @@ -359,9 +381,7 @@ TEST_F(HttpServerPropertiesManagerTest, Clear) { spdy_server_mail, 443, net::NPN_SPDY_2); spdy::SpdySettings spdy_settings; - spdy::SettingsFlagsAndId id1(0); - id1.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - id1.set_id(1234); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 1234); spdy_settings.push_back(std::make_pair(id1, 31337)); http_server_props_manager_->SetSpdySettings(spdy_server_mail, spdy_settings); diff --git a/net/base/net_test_suite.cc b/net/base/net_test_suite.cc index 3dc91b7..2d2b411 100644 --- a/net/base/net_test_suite.cc +++ b/net/base/net_test_suite.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -7,6 +7,7 @@ #include "base/message_loop.h" #include "net/base/network_change_notifier.h" #include "net/http/http_stream_factory.h" +#include "net/spdy/spdy_session.h" #if defined(USE_NSS) #include "net/ocsp/nss_ocsp.h" #endif @@ -15,6 +16,7 @@ class StaticReset : public ::testing::EmptyTestEventListener { virtual void OnTestStart(const ::testing::TestInfo& test_info) OVERRIDE { net::HttpStreamFactory::ResetStaticSettingsToInit(); + net::SpdySession::ResetStaticSettingsToInit(); } }; diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc index 0894af7..39d24f4 100644 --- a/net/http/http_network_layer.cc +++ b/net/http/http_network_layer.cc @@ -46,7 +46,7 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) { static const char kExclude[] = "exclude"; // Hosts to exclude static const char kDisableCompression[] = "no-compress"; static const char kDisableAltProtocols[] = "no-alt-protocols"; - static const char kEnableVersionOne[] = "v1"; + static const char kEnableVersionThree[] = "v3"; static const char kForceAltProtocols[] = "force-alt-protocols"; static const char kSingleDomain[] = "single-domain"; @@ -84,10 +84,12 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) { if (option == kOff) { HttpStreamFactory::set_spdy_enabled(false); } else if (option == kDisableSSL) { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); SpdySession::SetSSLMode(false); // Disable SSL HttpStreamFactory::set_force_spdy_over_ssl(false); HttpStreamFactory::set_force_spdy_always(true); } else if (option == kSSL) { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); HttpStreamFactory::set_force_spdy_over_ssl(true); HttpStreamFactory::set_force_spdy_always(true); } else if (option == kDisablePing) { @@ -102,6 +104,13 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) { next_protos.push_back("http/1.1"); next_protos.push_back("spdy/2"); HttpStreamFactory::SetNextProtos(next_protos); + } else if (option == kEnableVersionThree) { + std::vector<std::string> next_protos; + next_protos.push_back("http/1.1"); + next_protos.push_back("spdy/2"); + next_protos.push_back("spdy/2.1"); + next_protos.push_back("spdy/3"); + HttpStreamFactory::SetNextProtos(next_protos); } else if (option == kEnableNpnHttpOnly) { // Avoid alternate protocol in this case. Otherwise, browser will try SSL // and then fallback to http. This introduces extra load. @@ -110,13 +119,6 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) { next_protos.push_back("http/1.1"); next_protos.push_back("http1.1"); HttpStreamFactory::SetNextProtos(next_protos); - } else if (option == kEnableVersionOne) { - spdy::SpdyFramer::set_protocol_version(1); - std::vector<std::string> next_protos; - // This is a temporary hack to pretend we support version 1. - next_protos.push_back("http/1.1"); - next_protos.push_back("spdy/1"); - HttpStreamFactory::SetNextProtos(next_protos); } else if (option == kDisableAltProtocols) { use_alt_protocols = false; HttpStreamFactory::set_use_alternate_protocols(false); diff --git a/net/http/http_network_transaction_spdy21_unittest.cc b/net/http/http_network_transaction_spdy21_unittest.cc index 3873c84..a4c8834 100644 --- a/net/http/http_network_transaction_spdy21_unittest.cc +++ b/net/http/http_network_transaction_spdy21_unittest.cc @@ -164,6 +164,7 @@ class HttpNetworkTransactionSpdy21Test : public PlatformTest { }; virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY21); NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); MessageLoop::current()->RunAllPending(); spdy::SpdyFramer::set_enable_compression_default(false); diff --git a/net/http/http_network_transaction_spdy2_unittest.cc b/net/http/http_network_transaction_spdy2_unittest.cc index 68ae655..4d19b32 100644 --- a/net/http/http_network_transaction_spdy2_unittest.cc +++ b/net/http/http_network_transaction_spdy2_unittest.cc @@ -164,6 +164,7 @@ class HttpNetworkTransactionSpdy2Test : public PlatformTest { }; virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); MessageLoop::current()->RunAllPending(); spdy::SpdyFramer::set_enable_compression_default(false); diff --git a/net/http/http_network_transaction_spdy3_unittest.cc b/net/http/http_network_transaction_spdy3_unittest.cc index 77954c2..59b45ea 100644 --- a/net/http/http_network_transaction_spdy3_unittest.cc +++ b/net/http/http_network_transaction_spdy3_unittest.cc @@ -97,7 +97,7 @@ std::vector<std::string> MakeNextProtos(const char* a, ...) { // SpdyNextProtos returns a vector of NPN protocol strings for negotiating // SPDY. std::vector<std::string> SpdyNextProtos() { - return MakeNextProtos("http/1.1", "spdy/2", "spdy/2.1", NULL); + return MakeNextProtos("http/1.1", "spdy/2", "spdy/2.1", "spdy/3", NULL); } } // namespace @@ -164,6 +164,7 @@ class HttpNetworkTransactionSpdy3Test : public PlatformTest { }; virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); MessageLoop::current()->RunAllPending(); spdy::SpdyFramer::set_enable_compression_default(false); @@ -385,7 +386,7 @@ CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool( // This is the expected return from a current server advertising SPDY. static const char kAlternateProtocolHttpHeader[] = - "Alternate-Protocol: 443:npn-spdy/2.1\r\n\r\n"; + "Alternate-Protocol: 443:npn-spdy/3\r\n\r\n"; // Helper functions for validating that AuthChallengeInfo's are correctly // configured for common cases. @@ -2169,7 +2170,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyGet) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); TestCompletionCallback callback1; @@ -2253,7 +2254,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyGetWithProxyAuth) { session_deps.socket_factory.AddSocketDataProvider(data.get()); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); TestCompletionCallback callback1; @@ -2348,7 +2349,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectHttps) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); SSLSocketDataProvider ssl2(ASYNC, OK); ssl2.was_npn_negotiated = false; @@ -2427,11 +2428,11 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectSpdy) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); SSLSocketDataProvider ssl2(ASYNC, OK); - ssl2.SetNextProto(SSLClientSocket::kProtoSPDY21); - ssl2.protocol_negotiated = SSLClientSocket::kProtoSPDY21; + ssl2.SetNextProto(SSLClientSocket::kProtoSPDY3); + ssl2.protocol_negotiated = SSLClientSocket::kProtoSPDY3; session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); TestCompletionCallback callback1; @@ -2491,10 +2492,10 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectFailure) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); SSLSocketDataProvider ssl2(ASYNC, OK); - ssl2.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl2.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); TestCompletionCallback callback1; @@ -4596,7 +4597,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, RedirectOfHttpsConnectViaSpdyProxy) { data_reads, arraysize(data_reads), data_writes, arraysize(data_writes))); SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy - proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSocketDataProvider(data.get()); session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); @@ -4715,7 +4716,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, data_reads, arraysize(data_reads), data_writes, arraysize(data_writes))); SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy - proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSocketDataProvider(data.get()); session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); @@ -4825,7 +4826,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthSpdyProxy) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); // Negotiate SPDY to the proxy SSLSocketDataProvider proxy(ASYNC, OK); - proxy.SetNextProto(SSLClientSocket::kProtoSPDY21); + proxy.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&proxy); // Vanilla SSL to the server SSLSocketDataProvider server(ASYNC, OK); @@ -5625,7 +5626,7 @@ scoped_refptr<HttpNetworkSession> SetupSessionForGroupNameTests( session->http_server_properties(); http_server_properties->SetAlternateProtocol( HostPortPair("host.with.alternate", 80), 443, - NPN_SPDY_21); + NPN_SPDY_3); return session; } @@ -6572,7 +6573,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, HonorAlternateProtocolHeader) { http_server_properties.GetAlternateProtocol(http_host_port_pair); PortAlternateProtocolPair expected_alternate; expected_alternate.port = 443; - expected_alternate.protocol = NPN_SPDY_21; + expected_alternate.protocol = NPN_SPDY_3; EXPECT_TRUE(expected_alternate.Equals(alternate)); HttpStreamFactory::set_use_alternate_protocols(false); @@ -6612,7 +6613,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, http_server_properties->SetAlternateProtocol( HostPortPair::FromURL(request.url), 666 /* port is ignored by MockConnect anyway */, - NPN_SPDY_21); + NPN_SPDY_3); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); TestCompletionCallback callback; @@ -6675,7 +6676,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, http_server_properties->SetAlternateProtocol( HostPortPair::FromURL(restricted_port_request.url), kUnrestrictedAlternatePort, - NPN_SPDY_21); + NPN_SPDY_3); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); TestCompletionCallback callback; @@ -6725,7 +6726,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, http_server_properties->SetAlternateProtocol( HostPortPair::FromURL(restricted_port_request.url), kRestrictedAlternatePort, - NPN_SPDY_21); + NPN_SPDY_3); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); TestCompletionCallback callback; @@ -6775,7 +6776,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, http_server_properties->SetAlternateProtocol( HostPortPair::FromURL(unrestricted_port_request.url), kRestrictedAlternatePort, - NPN_SPDY_21); + NPN_SPDY_3); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); TestCompletionCallback callback; @@ -6825,7 +6826,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, http_server_properties->SetAlternateProtocol( HostPortPair::FromURL(unrestricted_port_request.url), kUnrestrictedAlternatePort, - NPN_SPDY_21); + NPN_SPDY_3); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); TestCompletionCallback callback; @@ -6870,7 +6871,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, AlternateProtocolUnsafeBlocked) { http_server_properties->SetAlternateProtocol( HostPortPair::FromURL(request.url), kUnsafePort, - NPN_SPDY_2); + NPN_SPDY_3); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); TestCompletionCallback callback; @@ -6915,7 +6916,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, UseAlternateProtocolForNpnSpdy) { session_deps.socket_factory.AddSocketDataProvider(&first_transaction); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -7014,7 +7015,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, AlternateProtocolWithSpdyLateBinding) { session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_ptr<spdy::SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -7120,7 +7121,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, StallAlternateProtocolForNpnSpdy) { session_deps.socket_factory.AddSocketDataProvider(&first_transaction); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); @@ -7253,7 +7254,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, session_deps.socket_factory.AddSocketDataProvider(&first_transaction); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -7357,7 +7358,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, session_deps.socket_factory.AddSocketDataProvider(&first_transaction); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); // Make sure we use ssl for spdy here. SpdySession::SetSSLMode(true); @@ -8241,7 +8242,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, SpdyPostNPNServerHangup) { request.load_flags = 0; SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -8277,7 +8278,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, SpdyAlternateProtocolThroughProxy) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos( MakeNextProtos( - "http/1.1", "http1.1", "spdy/2.1", "spdy/2", "spdy", NULL)); + "http/1.1", "http1.1", "spdy/2", "spdy/2.1", "spdy/3", "spdy", NULL)); SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); HttpAuthHandlerMock::Factory* auth_factory = @@ -8302,7 +8303,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, SpdyAlternateProtocolThroughProxy) { MockRead data_reads_1[] = { MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), MockRead("HTTP/1.1 200 OK\r\n" - "Alternate-Protocol: 443:npn-spdy/2.1\r\n" + "Alternate-Protocol: 443:npn-spdy/3\r\n" "Proxy-Connection: close\r\n" "\r\n"), }; @@ -8372,7 +8373,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, SpdyAlternateProtocolThroughProxy) { data_writes_2, arraysize(data_writes_2))); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); StaticSocketDataProvider hanging_non_alternate_protocol_socket( @@ -8653,7 +8654,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, PreconnectWithExistingSpdySession) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); @@ -9085,7 +9086,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, UseIPConnectionPooling) { pool_peer.DisableDomainAuthenticationVerification(); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_ptr<spdy::SpdyFrame> host1_req(ConstructSpdyGet( @@ -9236,7 +9237,7 @@ TEST_F(HttpNetworkTransactionSpdy3Test, pool_peer.DisableDomainAuthenticationVerification(); SSLSocketDataProvider ssl(ASYNC, OK); - ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY3); session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); scoped_ptr<spdy::SpdyFrame> host1_req(ConstructSpdyGet( diff --git a/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc index 6aa2779..c5c3b1a 100644 --- a/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc @@ -175,7 +175,7 @@ class HttpProxyClientSocketPoolSpdy3Test : public TestWithHttpParam { void InitializeSpdySsl() { spdy::SpdyFramer::set_enable_compression_default(false); - ssl_data_->SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl_data_->SetNextProto(SSLClientSocket::kProtoSPDY3); } HttpNetworkSession* CreateNetworkSession() { diff --git a/net/http/http_server_properties.cc b/net/http/http_server_properties.cc index b685900..5dfcebc 100644 --- a/net/http/http_server_properties.cc +++ b/net/http/http_server_properties.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -14,6 +14,7 @@ const char* const kAlternateProtocolStrings[] = { "npn-spdy/1", "npn-spdy/2", "npn-spdy/2.1", + "npn-spdy/3", }; static const char* AlternateProtocolToString(AlternateProtocol protocol) { @@ -21,6 +22,7 @@ static const char* AlternateProtocolToString(AlternateProtocol protocol) { case NPN_SPDY_1: case NPN_SPDY_2: case NPN_SPDY_21: + case NPN_SPDY_3: return kAlternateProtocolStrings[protocol]; case ALTERNATE_PROTOCOL_BROKEN: return "Broken"; diff --git a/net/http/http_server_properties.h b/net/http/http_server_properties.h index d9d60fd..3878768 100644 --- a/net/http/http_server_properties.h +++ b/net/http/http_server_properties.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -19,6 +19,7 @@ enum AlternateProtocol { NPN_SPDY_1 = 0, NPN_SPDY_2, NPN_SPDY_21, + NPN_SPDY_3, NUM_ALTERNATE_PROTOCOLS, ALTERNATE_PROTOCOL_BROKEN, // The alternate protocol is known to be broken. UNINITIALIZED_ALTERNATE_PROTOCOL, @@ -89,10 +90,16 @@ class NET_EXPORT HttpServerProperties { const HostPortPair& host_port_pair) const = 0; // Saves settings for a host. Returns true if SpdySettings are to be - // persisted. + // persisted. Used by unittests only. + // TODO(rtenneti): Move this method to test utility file. virtual bool SetSpdySettings(const HostPortPair& host_port_pair, const spdy::SpdySettings& settings) = 0; + // Saves an individual setting for a host. Returns true if SpdySetting is to + // be persisted. + virtual bool SetSpdySetting(const HostPortPair& host_port_pair, + const spdy::SpdySetting& setting) = 0; + // Clears all spdy_settings. virtual void ClearSpdySettings() = 0; diff --git a/net/http/http_server_properties_impl.cc b/net/http/http_server_properties_impl.cc index b8a7ed1..6601fe5 100644 --- a/net/http/http_server_properties_impl.cc +++ b/net/http/http_server_properties_impl.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -241,8 +241,8 @@ bool HttpServerPropertiesImpl::SetSpdySettings( for (it = settings.begin(); it != settings.end(); ++it) { spdy::SettingsFlagsAndId id = it->first; if (id.flags() & spdy::SETTINGS_FLAG_PLEASE_PERSIST) { - id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); - persistent_settings.push_back(std::make_pair(id, it->second)); + spdy::SettingsFlagsAndId new_id(spdy::SETTINGS_FLAG_PERSISTED, id.id()); + persistent_settings.push_back(std::make_pair(new_id, it->second)); } } @@ -254,6 +254,27 @@ bool HttpServerPropertiesImpl::SetSpdySettings( return true; } +bool HttpServerPropertiesImpl::SetSpdySetting( + const HostPortPair& host_port_pair, + const spdy::SpdySetting& setting) { + + spdy::SettingsFlagsAndId id = setting.first; + if (!(id.flags() & spdy::SETTINGS_FLAG_PLEASE_PERSIST)) + return false; + + SpdySettingsMap::const_iterator it = spdy_settings_map_.find(host_port_pair); + spdy::SpdySettings persistent_settings; + if (it != spdy_settings_map_.end()) { + persistent_settings = it->second; + } + + spdy::SettingsFlagsAndId new_id(spdy::SETTINGS_FLAG_PERSISTED, id.id()); + persistent_settings.push_back(std::make_pair(new_id, setting.second)); + spdy_settings_map_[host_port_pair] = persistent_settings; + + return true; +} + void HttpServerPropertiesImpl::ClearSpdySettings() { spdy_settings_map_.clear(); } diff --git a/net/http/http_server_properties_impl.h b/net/http/http_server_properties_impl.h index 487e0fd..b4de808 100644 --- a/net/http/http_server_properties_impl.h +++ b/net/http/http_server_properties_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -113,6 +113,11 @@ class NET_EXPORT HttpServerPropertiesImpl virtual bool SetSpdySettings(const HostPortPair& host_port_pair, const spdy::SpdySettings& settings) OVERRIDE; + // Saves an individual setting for a host. Returns true if SpdySetting is to + // be persisted because |spdy_settings_map_| has been updated. + virtual bool SetSpdySetting(const HostPortPair& host_port_pair, + const spdy::SpdySetting& setting) OVERRIDE; + // Clears all spdy_settings. virtual void ClearSpdySettings() OVERRIDE; diff --git a/net/http/http_server_properties_impl_unittest.cc b/net/http/http_server_properties_impl_unittest.cc index 724dea0..d58ba31 100644 --- a/net/http/http_server_properties_impl_unittest.cc +++ b/net/http/http_server_properties_impl_unittest.cc @@ -291,9 +291,7 @@ TEST_F(SpdySettingsServerPropertiesTest, Initialize) { // Check by initializing with www.google.com:443 spdy server settings. spdy::SpdySettings spdy_settings; - spdy::SettingsFlagsAndId spdy_setting(0); - spdy_setting.set_flags(spdy::SETTINGS_FLAG_PERSISTED); - spdy_setting.set_id(1234); + spdy::SettingsFlagsAndId spdy_setting(spdy::SETTINGS_FLAG_PERSISTED, 1234); spdy_settings.push_back(std::make_pair(spdy_setting, 31337)); spdy_settings_map[spdy_server_google] = spdy_settings; impl_.InitializeSpdySettingsServers(&spdy_settings_map); @@ -306,7 +304,7 @@ TEST_F(SpdySettingsServerPropertiesTest, Initialize) { EXPECT_EQ(31337U, spdy_settings2.front().second); } -TEST_F(SpdySettingsServerPropertiesTest, SpdySettingsTest) { +TEST_F(SpdySettingsServerPropertiesTest, SetSpdySettings) { HostPortPair spdy_server_empty("", 443); spdy::SpdySettings spdy_settings0 = impl_.GetSpdySettings(spdy_server_empty); EXPECT_EQ(0U, spdy_settings0.size()); // Returns kEmptySpdySettings @@ -314,9 +312,7 @@ TEST_F(SpdySettingsServerPropertiesTest, SpdySettingsTest) { // Add www.google.com:443 as persisting. HostPortPair spdy_server_google("www.google.com", 443); spdy::SpdySettings spdy_settings1; - spdy::SettingsFlagsAndId id1(0); - id1.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - id1.set_id(1234); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 1234); spdy_settings1.push_back(std::make_pair(id1, 31337)); EXPECT_TRUE(impl_.SetSpdySettings(spdy_server_google, spdy_settings1)); spdy::SpdySettings spdy_settings1_ret = @@ -331,8 +327,7 @@ TEST_F(SpdySettingsServerPropertiesTest, SpdySettingsTest) { // Add mail.google.com:443 as not persisting. HostPortPair spdy_server_mail("mail.google.com", 443); spdy::SpdySettings spdy_settings2; - spdy::SettingsFlagsAndId id2(0); - id2.set_id(5678); + spdy::SettingsFlagsAndId id2(0, 5678); spdy_settings2.push_back(std::make_pair(id2, 62667)); EXPECT_FALSE(impl_.SetSpdySettings(spdy_server_mail, spdy_settings2)); spdy::SpdySettings spdy_settings2_ret = @@ -342,9 +337,7 @@ TEST_F(SpdySettingsServerPropertiesTest, SpdySettingsTest) { // Add docs.google.com:443 as persisting HostPortPair spdy_server_docs("docs.google.com", 443); spdy::SpdySettings spdy_settings3; - spdy::SettingsFlagsAndId id3(0); - id3.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - id3.set_id(9012); + spdy::SettingsFlagsAndId id3(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 9012); spdy_settings3.push_back(std::make_pair(id3, 93997)); EXPECT_TRUE(impl_.SetSpdySettings(spdy_server_docs, spdy_settings3)); spdy::SpdySettings spdy_settings3_ret = @@ -357,13 +350,63 @@ TEST_F(SpdySettingsServerPropertiesTest, SpdySettingsTest) { EXPECT_EQ(93997U, spdy_setting3_ret.second); } +TEST_F(SpdySettingsServerPropertiesTest, SetSpdySetting) { + HostPortPair spdy_server_empty("", 443); + spdy::SpdySettings spdy_settings0 = impl_.GetSpdySettings(spdy_server_empty); + EXPECT_EQ(0U, spdy_settings0.size()); // Returns kEmptySpdySettings + + // Add www.google.com:443 as persisting. + HostPortPair spdy_server_google("www.google.com", 443); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 1234); + EXPECT_TRUE(impl_.SetSpdySetting( + spdy_server_google, std::make_pair(id1, 31337))); + spdy::SpdySettings spdy_settings1_ret = + impl_.GetSpdySettings(spdy_server_google); + ASSERT_EQ(1U, spdy_settings1_ret.size()); + spdy::SpdySetting spdy_setting1_ret = spdy_settings1_ret.front(); + spdy::SettingsFlagsAndId id1_ret(spdy_setting1_ret.first); + EXPECT_EQ(1234U, id1_ret.id()); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, id1_ret.flags()); + EXPECT_EQ(31337U, spdy_setting1_ret.second); + + // Add mail.google.com:443 as not persisting. + HostPortPair spdy_server_mail("mail.google.com", 443); + spdy::SettingsFlagsAndId id2(0, 5678); + EXPECT_FALSE(impl_.SetSpdySetting( + spdy_server_mail, std::make_pair(id2, 62667))); + spdy::SpdySettings spdy_settings2_ret = + impl_.GetSpdySettings(spdy_server_mail); + EXPECT_EQ(0U, spdy_settings2_ret.size()); // Returns kEmptySpdySettings + + // Add docs.google.com:443 as persisting + HostPortPair spdy_server_docs("docs.google.com", 443); + spdy::SettingsFlagsAndId id3(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 9012); + EXPECT_TRUE(impl_.SetSpdySetting( + spdy_server_docs, std::make_pair(id3, 93997))); + spdy::SpdySettings spdy_settings3_ret = + impl_.GetSpdySettings(spdy_server_docs); + ASSERT_EQ(1U, spdy_settings3_ret.size()); + spdy::SpdySetting spdy_setting3_ret = spdy_settings3_ret.front(); + spdy::SettingsFlagsAndId id3_ret(spdy_setting3_ret.first); + EXPECT_EQ(9012U, id3_ret.id()); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, id3_ret.flags()); + EXPECT_EQ(93997U, spdy_setting3_ret.second); + // Check data for www.google.com:443. + spdy::SpdySettings spdy_settings4_ret = + impl_.GetSpdySettings(spdy_server_google); + ASSERT_EQ(1U, spdy_settings4_ret.size()); + spdy::SpdySetting spdy_setting4_ret = spdy_settings4_ret.front(); + spdy::SettingsFlagsAndId id4_ret(spdy_setting4_ret.first); + EXPECT_EQ(1234U, id4_ret.id()); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, id4_ret.flags()); + EXPECT_EQ(31337U, spdy_setting4_ret.second); +} + TEST_F(SpdySettingsServerPropertiesTest, Clear) { // Add www.google.com:443 as persisting. HostPortPair spdy_server_google("www.google.com", 443); spdy::SpdySettings spdy_settings1; - spdy::SettingsFlagsAndId id1(0); - id1.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - id1.set_id(1234); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 1234); spdy_settings1.push_back(std::make_pair(id1, 31337)); EXPECT_TRUE(impl_.SetSpdySettings(spdy_server_google, spdy_settings1)); spdy::SpdySettings spdy_settings1_ret = @@ -378,9 +421,7 @@ TEST_F(SpdySettingsServerPropertiesTest, Clear) { // Add docs.google.com:443 as persisting HostPortPair spdy_server_docs("docs.google.com", 443); spdy::SpdySettings spdy_settings3; - spdy::SettingsFlagsAndId id3(0); - id3.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - id3.set_id(9012); + spdy::SettingsFlagsAndId id3(spdy::SETTINGS_FLAG_PLEASE_PERSIST, 9012); spdy_settings3.push_back(std::make_pair(id3, 93997)); EXPECT_TRUE(impl_.SetSpdySettings(spdy_server_docs, spdy_settings3)); spdy::SpdySettings spdy_settings3_ret = diff --git a/net/http/http_stream_factory.cc b/net/http/http_stream_factory.cc index 6d0fc31..dda9951 100644 --- a/net/http/http_stream_factory.cc +++ b/net/http/http_stream_factory.cc @@ -167,6 +167,8 @@ void HttpStreamFactory::SetNextProtos(const std::vector<std::string>& value) { enabled_protocols_[NPN_SPDY_2] = true; } else if (value[i] == "spdy/2.1") { enabled_protocols_[NPN_SPDY_21] = true; + } else if (value[i] == "spdy/3") { + enabled_protocols_[NPN_SPDY_3] = true; } } enabled_protocols_[NPN_SPDY_1] = false; diff --git a/net/net.gyp b/net/net.gyp index 2260ae8..a850041 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -646,6 +646,8 @@ 'spdy/spdy_credential_state.h', 'spdy/spdy_frame_builder.cc', 'spdy/spdy_frame_builder.h', + 'spdy/spdy_frame_reader.cc', + 'spdy/spdy_frame_reader.h', 'spdy/spdy_framer.cc', 'spdy/spdy_framer.h', 'spdy/spdy_http_stream.cc', @@ -1193,13 +1195,16 @@ 'spdy/buffered_spdy_framer_spdy3_unittest.cc', 'spdy/buffered_spdy_framer_spdy2_unittest.cc', 'spdy/spdy_credential_state_unittest.cc', - 'spdy/spdy_framer_test.cc', + 'spdy/spdy_frame_reader_test.cc', + 'spdy/spdy_framer_spdy3_test.cc', + 'spdy/spdy_framer_spdy2_test.cc', 'spdy/spdy_http_stream_spdy3_unittest.cc', 'spdy/spdy_http_stream_spdy2_unittest.cc', 'spdy/spdy_network_transaction_spdy3_unittest.cc', 'spdy/spdy_network_transaction_spdy21_unittest.cc', 'spdy/spdy_network_transaction_spdy2_unittest.cc', - 'spdy/spdy_protocol_test.cc', + 'spdy/spdy_protocol_spdy3_test.cc', + 'spdy/spdy_protocol_spdy2_test.cc', 'spdy/spdy_proxy_client_socket_spdy3_unittest.cc', 'spdy/spdy_proxy_client_socket_spdy2_unittest.cc', 'spdy/spdy_session_spdy3_unittest.cc', diff --git a/net/socket/ssl_client_socket.cc b/net/socket/ssl_client_socket.cc index 3d881fb..ecee79b 100644 --- a/net/socket/ssl_client_socket.cc +++ b/net/socket/ssl_client_socket.cc @@ -25,6 +25,8 @@ SSLClientSocket::NextProto SSLClientSocket::NextProtoFromString( return kProtoSPDY2; } else if (proto_string == "spdy/2.1") { return kProtoSPDY21; + } else if (proto_string == "spdy/3") { + return kProtoSPDY3; } else { return kProtoUnknown; } @@ -41,6 +43,8 @@ const char* SSLClientSocket::NextProtoToString( return "spdy/2"; case kProtoSPDY21: return "spdy/2.1"; + case kProtoSPDY3: + return "spdy/3"; default: break; } diff --git a/net/socket/ssl_client_socket_pool.cc b/net/socket/ssl_client_socket_pool.cc index 53f7f97..0c96546 100644 --- a/net/socket/ssl_client_socket_pool.cc +++ b/net/socket/ssl_client_socket_pool.cc @@ -303,7 +303,8 @@ int SSLConnectJob::DoSSLConnectComplete(int result) { // TODO(mbelshe): verify it was a protocol we advertised? if (protocol_negotiated == SSLClientSocket::kProtoSPDY1 || protocol_negotiated == SSLClientSocket::kProtoSPDY2 || - protocol_negotiated == SSLClientSocket::kProtoSPDY21) { + protocol_negotiated == SSLClientSocket::kProtoSPDY21 || + protocol_negotiated == SSLClientSocket::kProtoSPDY3) { ssl_socket_->set_was_spdy_negotiated(true); } } diff --git a/net/spdy/buffered_spdy_framer.cc b/net/spdy/buffered_spdy_framer.cc index a07cb61..909c27f 100644 --- a/net/spdy/buffered_spdy_framer.cc +++ b/net/spdy/buffered_spdy_framer.cc @@ -9,7 +9,7 @@ namespace spdy { BufferedSpdyFramer::BufferedSpdyFramer(int version) - : spdy_framer_(), + : spdy_framer_(version), visitor_(NULL), header_buffer_used_(0), header_buffer_valid_(false), @@ -49,8 +49,6 @@ void BufferedSpdyFramer::OnControl(const SpdyControlFrame* frame) { *reinterpret_cast<const spdy::SpdyPingControlFrame*>(frame)); break; case SETTINGS: - visitor_->OnSettings( - *reinterpret_cast<const spdy::SpdySettingsControlFrame*>(frame)); break; case RST_STREAM: visitor_->OnRstStream( @@ -136,6 +134,12 @@ void BufferedSpdyFramer::OnStreamFrameData(SpdyStreamId stream_id, visitor_->OnStreamFrameData(stream_id, data, len); } +void BufferedSpdyFramer::OnSetting(SpdySettingsIds id, + uint8 flags, + uint32 value) { + visitor_->OnSetting(id, flags, value); +} + int BufferedSpdyFramer::protocol_version() { return spdy_framer_.protocol_version(); } @@ -164,11 +168,6 @@ bool BufferedSpdyFramer::HasError() { return spdy_framer_.HasError(); } -bool BufferedSpdyFramer::ParseHeaderBlock(const SpdyFrame* frame, - SpdyHeaderBlock* block) { - return spdy_framer_.ParseHeaderBlock(frame, block); -} - SpdySynStreamControlFrame* BufferedSpdyFramer::CreateSynStream( SpdyStreamId stream_id, SpdyStreamId associated_stream_id, @@ -235,6 +234,10 @@ SpdyDataFrame* BufferedSpdyFramer::CreateDataFrame(SpdyStreamId stream_id, return spdy_framer_.CreateDataFrame(stream_id, data, len, flags); } +SpdyPriority BufferedSpdyFramer::GetHighestPriority() const { + return spdy_framer_.GetHighestPriority(); +} + SpdyFrame* BufferedSpdyFramer::CompressFrame(const SpdyFrame& frame) { return spdy_framer_.CompressFrame(frame); } diff --git a/net/spdy/buffered_spdy_framer.h b/net/spdy/buffered_spdy_framer.h index c784764..70859bf 100644 --- a/net/spdy/buffered_spdy_framer.h +++ b/net/spdy/buffered_spdy_framer.h @@ -51,9 +51,6 @@ class NET_EXPORT_PRIVATE BufferedSpdyFramerVisitorInterface { // Called after a PING frame is received. virtual void OnPing(const spdy::SpdyPingControlFrame& frame) = 0; - // Called after a SETTINGS frame is received. - virtual void OnSettings(const spdy::SpdySettingsControlFrame& frame) = 0; - // Called after a WINDOW_UPDATE frame is received. virtual void OnWindowUpdate( const spdy::SpdyWindowUpdateControlFrame& frame) = 0; @@ -68,6 +65,10 @@ class NET_EXPORT_PRIVATE BufferedSpdyFramerVisitorInterface { const char* data, size_t len) = 0; + // Called when an individual setting within a SETTINGS frame has been parsed + // and validated. + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0; + private: DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramerVisitorInterface); }; @@ -95,6 +96,8 @@ class NET_EXPORT_PRIVATE BufferedSpdyFramer virtual void OnStreamFrameData(SpdyStreamId stream_id, const char* data, size_t len) OVERRIDE; + virtual void OnSetting( + SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE; virtual void OnDataFrameHeader(const SpdyDataFrame* frame) OVERRIDE; // SpdyFramer methods. @@ -105,7 +108,6 @@ class NET_EXPORT_PRIVATE BufferedSpdyFramer SpdyFramer::SpdyState state() const; bool MessageFullyRead(); bool HasError(); - bool ParseHeaderBlock(const SpdyFrame* frame, SpdyHeaderBlock* block); SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id, SpdyStreamId associated_stream_id, int priority, @@ -135,6 +137,7 @@ class NET_EXPORT_PRIVATE BufferedSpdyFramer const char* data, uint32 len, SpdyDataFlags flags); + SpdyPriority GetHighestPriority() const; SpdyFrame* CompressFrame(const SpdyFrame& frame); bool IsCompressible(const SpdyFrame& frame) const; diff --git a/net/spdy/buffered_spdy_framer_spdy2_unittest.cc b/net/spdy/buffered_spdy_framer_spdy2_unittest.cc index e267603..b001fba 100644 --- a/net/spdy/buffered_spdy_framer_spdy2_unittest.cc +++ b/net/spdy/buffered_spdy_framer_spdy2_unittest.cc @@ -18,6 +18,7 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { TestBufferedSpdyVisitor() : buffered_spdy_framer_(2), error_count_(0), + setting_count_(0), syn_frame_count_(0), syn_reply_frame_count_(0), headers_frame_count_(0), @@ -66,6 +67,10 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { LOG(FATAL) << "Unexpected OnStreamFrameData call."; } + void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) { + setting_count_++; + } + bool OnCredentialFrameData(const char*, size_t) { LOG(FATAL) << "Unexpected OnCredentialFrameData call."; return false; @@ -93,7 +98,6 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { void OnRstStream(const spdy::SpdyRstStreamControlFrame& frame) {} void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) {} void OnPing(const spdy::SpdyPingControlFrame& frame) {} - void OnSettings(const spdy::SpdySettingsControlFrame& frame) {} void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame) {} void OnCredential(const spdy::SpdyCredentialControlFrame& frame) {} @@ -123,6 +127,7 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { // Counters from the visitor callbacks. int error_count_; + int setting_count_; int syn_frame_count_; int syn_reply_frame_count_; int headers_frame_count_; @@ -170,6 +175,24 @@ class BufferedSpdyFramerSpdy2Test : public PlatformTest { } }; +TEST_F(BufferedSpdyFramerSpdy2Test, OnSetting) { + EnableCompression(false); + + SpdyFramer framer(2); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000003)); + + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestBufferedSpdyVisitor visitor; + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); +} + TEST_F(BufferedSpdyFramerSpdy2Test, ReadSynStreamHeaderBlock) { EnableCompression(false); diff --git a/net/spdy/buffered_spdy_framer_spdy3_unittest.cc b/net/spdy/buffered_spdy_framer_spdy3_unittest.cc index 19f07c3..e18c688 100644 --- a/net/spdy/buffered_spdy_framer_spdy3_unittest.cc +++ b/net/spdy/buffered_spdy_framer_spdy3_unittest.cc @@ -18,6 +18,7 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { TestBufferedSpdyVisitor() : buffered_spdy_framer_(3), error_count_(0), + setting_count_(0), syn_frame_count_(0), syn_reply_frame_count_(0), headers_frame_count_(0), @@ -66,6 +67,10 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { LOG(FATAL) << "Unexpected OnStreamFrameData call."; } + void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) { + setting_count_++; + } + bool OnCredentialFrameData(const char*, size_t) { LOG(FATAL) << "Unexpected OnCredentialFrameData call."; return false; @@ -93,7 +98,6 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { void OnRstStream(const spdy::SpdyRstStreamControlFrame& frame) {} void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) {} void OnPing(const spdy::SpdyPingControlFrame& frame) {} - void OnSettings(const spdy::SpdySettingsControlFrame& frame) {} void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame) {} void OnCredential(const spdy::SpdyCredentialControlFrame& frame) {} @@ -123,6 +127,7 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { // Counters from the visitor callbacks. int error_count_; + int setting_count_; int syn_frame_count_; int syn_reply_frame_count_; int headers_frame_count_; @@ -170,6 +175,24 @@ class BufferedSpdyFramerSpdy3Test : public PlatformTest { } }; +TEST_F(BufferedSpdyFramerSpdy3Test, OnSetting) { + EnableCompression(false); + + SpdyFramer framer(3); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000003)); + + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestBufferedSpdyVisitor visitor; + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); +} + TEST_F(BufferedSpdyFramerSpdy3Test, ReadSynStreamHeaderBlock) { EnableCompression(false); diff --git a/net/spdy/spdy_bitmasks.h b/net/spdy/spdy_bitmasks.h index eb34ce6..1d1b42c 100644 --- a/net/spdy/spdy_bitmasks.h +++ b/net/spdy/spdy_bitmasks.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// 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. @@ -15,14 +15,12 @@ const unsigned int kStreamIdMask = 0x7fffffff; const unsigned int kControlFlagMask = 0x8000; // Priority mask from the SYN_FRAME -const unsigned int kPriorityMask = 0xc0; +const unsigned int kSpdy3PriorityMask = 0xe0; +const unsigned int kSpdy2PriorityMask = 0xc0; // Mask the lower 24 bits. const unsigned int kLengthMask = 0xffffff; -// Mask the Id from a SETTINGS id. -const unsigned int kSettingsIdMask = 0xffffff; - // Legal flags on data packets. const int kDataFlagsMask = 0x03; diff --git a/net/spdy/spdy_frame_builder.cc b/net/spdy/spdy_frame_builder.cc index fdb7686..e857d90 100644 --- a/net/spdy/spdy_frame_builder.cc +++ b/net/spdy/spdy_frame_builder.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -12,14 +12,6 @@ namespace spdy { // We mark a read only SpdyFrameBuilder with a special capacity_. static const size_t kCapacityReadOnly = std::numeric_limits<size_t>::max(); -SpdyFrameBuilder::SpdyFrameBuilder() - : buffer_(NULL), - capacity_(0), - length_(0), - variable_buffer_offset_(0) { - Resize(kInitialPayload); -} - SpdyFrameBuilder::SpdyFrameBuilder(size_t size) : buffer_(NULL), capacity_(0), @@ -28,121 +20,11 @@ SpdyFrameBuilder::SpdyFrameBuilder(size_t size) Resize(size); } -SpdyFrameBuilder::SpdyFrameBuilder(const char* data, int data_len) - : buffer_(const_cast<char*>(data)), - capacity_(kCapacityReadOnly), - length_(data_len), - variable_buffer_offset_(0) { -} - SpdyFrameBuilder::~SpdyFrameBuilder() { - if (capacity_ != kCapacityReadOnly) + if (buffer_) delete[] buffer_; } -bool SpdyFrameBuilder::ReadUInt16(void** iter, uint16* result) const { - DCHECK(iter); - if (!*iter) - *iter = const_cast<char*>(buffer_); - - if (!IteratorHasRoomFor(*iter, sizeof(*result))) - return false; - - *result = ntohs(*(reinterpret_cast<uint16*>(*iter))); - - UpdateIter(iter, sizeof(*result)); - return true; -} - -bool SpdyFrameBuilder::ReadUInt32(void** iter, uint32* result) const { - DCHECK(iter); - if (!*iter) - *iter = const_cast<char*>(buffer_); - - if (!IteratorHasRoomFor(*iter, sizeof(*result))) - return false; - - *result = ntohl(*(reinterpret_cast<uint32*>(*iter))); - - UpdateIter(iter, sizeof(*result)); - return true; -} - -bool SpdyFrameBuilder::ReadString(void** iter, std::string* result) const { - DCHECK(iter); - - uint16 len; - if (!ReadUInt16(iter, &len)) - return false; - - if (!IteratorHasRoomFor(*iter, len)) - return false; - - char* chars = reinterpret_cast<char*>(*iter); - result->assign(chars, len); - - UpdateIter(iter, len); - return true; -} - -bool SpdyFrameBuilder::ReadBytes(void** iter, const char** data, - uint32 length) const { - DCHECK(iter); - DCHECK(data); - - if (!IteratorHasRoomFor(*iter, length)) - return false; - - *data = reinterpret_cast<const char*>(*iter); - - UpdateIter(iter, length); - return true; -} - -bool SpdyFrameBuilder::ReadData(void** iter, const char** data, - uint16* length) const { - DCHECK(iter); - DCHECK(data); - DCHECK(length); - - if (!ReadUInt16(iter, length)) - return false; - - return ReadBytes(iter, data, *length); -} - -bool SpdyFrameBuilder::ReadReadLen32PrefixedData(void** iter, - const char** data, - uint32* length) const { - DCHECK(iter); - DCHECK(data); - DCHECK(length); - - if (!ReadUInt32(iter, length)) - return false; - - return ReadBytes(iter, data, *length); -} - -char* SpdyFrameBuilder::BeginWriteData(uint16 length) { - DCHECK_EQ(variable_buffer_offset_, 0U) << - "There can only be one variable buffer in a SpdyFrameBuilder"; - - if (!WriteUInt16(length)) - return NULL; - - char *data_ptr = BeginWrite(length); - if (!data_ptr) - return NULL; - - variable_buffer_offset_ = data_ptr - buffer_ - sizeof(int); - - // EndWrite doesn't necessarily have to be called after the write operation, - // so we call it here to pad out what the caller will eventually write. - EndWrite(data_ptr, length); - return data_ptr; -} - char* SpdyFrameBuilder::BeginWrite(size_t length) { size_t offset = length_; size_t needed_size = length_ + length; @@ -160,8 +42,6 @@ void SpdyFrameBuilder::EndWrite(char* dest, int length) { } bool SpdyFrameBuilder::WriteBytes(const void* data, uint32 data_len) { - DCHECK(capacity_ != kCapacityReadOnly); - if (data_len > kLengthMask) { return false; } @@ -187,10 +67,18 @@ bool SpdyFrameBuilder::WriteString(const std::string& value) { return WriteBytes(value.data(), static_cast<uint16>(value.size())); } +bool SpdyFrameBuilder::WriteStringPiece32(const base::StringPiece& value) { + if (!WriteUInt32(value.size())) { + return false; + } + + return WriteBytes(value.data(), value.size()); +} + // TODO(hkhalil) Remove Resize() entirely. bool SpdyFrameBuilder::Resize(size_t new_capacity) { DCHECK(new_capacity > 0); - if (new_capacity <= capacity_) + if (new_capacity < capacity_) return true; char* p = new char[new_capacity]; @@ -198,6 +86,8 @@ bool SpdyFrameBuilder::Resize(size_t new_capacity) { memcpy(p, buffer_, capacity_); delete[] buffer_; } + if (!p && new_capacity > 0) + return false; buffer_ = p; capacity_ = new_capacity; return true; diff --git a/net/spdy/spdy_frame_builder.h b/net/spdy/spdy_frame_builder.h index 403e819..db5926d6 100644 --- a/net/spdy/spdy_frame_builder.h +++ b/net/spdy/spdy_frame_builder.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -9,6 +9,7 @@ #include <string> #include "base/basictypes.h" +#include "base/string_piece.h" #include "base/sys_byteorder.h" #include "net/base/net_export.h" #include "net/spdy/spdy_protocol.h" @@ -22,10 +23,6 @@ namespace spdy { // to a frame instance. The SpdyFrameBuilder grows its internal memory buffer // dynamically to hold the sequence of primitive values. The internal memory // buffer is exposed as the "data" of the SpdyFrameBuilder. -// -// When reading from a SpdyFrameBuilder the consumer must know what value types -// to read and in what order to read them as the SpdyFrameBuilder does not keep -// track of the type of data written to it. class NET_EXPORT_PRIVATE SpdyFrameBuilder { public: ~SpdyFrameBuilder(); @@ -36,12 +33,6 @@ class NET_EXPORT_PRIVATE SpdyFrameBuilder { // The buffer will still be resized as necessary. explicit SpdyFrameBuilder(size_t size); - // Initializes a SpdyFrameBuilder from a const block of data. The data is - // not copied; instead the data is merely referenced by this - // SpdyFrameBuilder. Only const methods should be used when initialized - // this way. - SpdyFrameBuilder(const char* data, int data_len); - // Returns the size of the SpdyFrameBuilder's data. int length() const { return length_; } @@ -54,22 +45,8 @@ class NET_EXPORT_PRIVATE SpdyFrameBuilder { return rv; } - // Methods for reading the payload of the SpdyFrameBuilder. To read from the - // start of the SpdyFrameBuilder, initialize *iter to NULL. If successful, - // these methods return true. Otherwise, false is returned to indicate that - // the result could not be extracted. - bool ReadUInt16(void** iter, uint16* result) const; - bool ReadUInt32(void** iter, uint32* result) const; - bool ReadString(void** iter, std::string* result) const; - bool ReadBytes(void** iter, const char** data, uint32 length) const; - bool ReadData(void** iter, const char** data, uint16* length) const; - bool ReadReadLen32PrefixedData(void** iter, - const char** data, - uint32* length) const; - // Methods for adding to the payload. These values are appended to the end - // of the SpdyFrameBuilder payload. When reading values, you must read them - // in the order they were added. Note - binary integers are converted from + // of the SpdyFrameBuilder payload. Note - binary integers are converted from // host to network form. bool WriteUInt16(uint16 value) { value = htons(value); @@ -79,7 +56,9 @@ class NET_EXPORT_PRIVATE SpdyFrameBuilder { value = htonl(value); return WriteBytes(&value, sizeof(value)); } + // TODO(hkhalil) Rename to WriteStringPiece16(). bool WriteString(const std::string& value); + bool WriteStringPiece32(const base::StringPiece& value); bool WriteBytes(const void* data, uint32 data_len); // Write an integer to a particular offset in the data buffer. @@ -97,16 +76,6 @@ class NET_EXPORT_PRIVATE SpdyFrameBuilder { return true; } - // Allows the caller to write data directly into the SpdyFrameBuilder. - // This saves a copy when the data is not already available in a buffer. - // The caller must not write more than the length it declares it will. - // Use ReadData to get the data. - // Returns NULL on failure. - // - // The returned pointer will only be valid until the next write operation - // on this SpdyFrameBuilder. - char* BeginWriteData(uint16 length); - // Returns true if the given iterator could point to data with the given // length. If there is no room for the given data before the end of the // payload, returns false. @@ -152,9 +121,6 @@ class NET_EXPORT_PRIVATE SpdyFrameBuilder { *iter = static_cast<char*>(*iter) + bytes; } - // Initial size of the payload. - static const int kInitialPayload = 1024; - private: char* buffer_; size_t capacity_; // Allocation size of payload (or -1 if buffer is const). diff --git a/net/spdy/spdy_frame_reader.cc b/net/spdy/spdy_frame_reader.cc new file mode 100644 index 0000000..183bae9 --- /dev/null +++ b/net/spdy/spdy_frame_reader.cc @@ -0,0 +1,126 @@ +// 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 <limits> + +#include "base/sys_byteorder.h" +#include "net/spdy/spdy_frame_reader.h" + +namespace spdy { + +SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len) + : data_(data), + len_(len), + ofs_(0) { +} + +bool SpdyFrameReader::ReadUInt16(uint16* result) { + // Make sure that we have the whole uint16. + if (!CanRead(2)) { + OnFailure(); + return false; + } + + // Read into result. + *result = ntohs(*(reinterpret_cast<const uint16*>(data_ + ofs_))); + + // Iterate. + ofs_ += 2; + + return true; +} + +bool SpdyFrameReader::ReadUInt32(uint32* result) { + // Make sure that we have the whole uint32. + if (!CanRead(4)) { + OnFailure(); + return false; + } + + // Read into result. + *result = ntohl(*(reinterpret_cast<const uint32*>(data_ + ofs_))); + + // Iterate. + ofs_ += 4; + + return true; +} + +bool SpdyFrameReader::ReadStringPiece16(base::StringPiece* result) { + // Read resultant length. + uint16 result_len; + if (!ReadUInt16(&result_len)) { + // OnFailure() already called. + return false; + } + + // Make sure that we have the whole string. + if (!CanRead(result_len)) { + OnFailure(); + return false; + } + + // Set result. + result->set(data_ + ofs_, result_len); + + // Iterate. + ofs_ += result_len; + + return true; +} + +bool SpdyFrameReader::ReadStringPiece32(base::StringPiece* result) { + // Read resultant length. + uint32 result_len; + if (!ReadUInt32(&result_len)) { + // OnFailure() already called. + return false; + } + + // Make sure that we have the whole string. + if (!CanRead(result_len)) { + OnFailure(); + return false; + } + + // Set result. + result->set(data_ + ofs_, result_len); + + // Iterate. + ofs_ += result_len; + + return true; +} + +bool SpdyFrameReader::ReadBytes(void* result, size_t size) { + // Make sure that we have enough data to read. + if (!CanRead(size)) { + OnFailure(); + return false; + } + + // Read into result. + memcpy(result, data_ + ofs_, size); + + // Iterate. + ofs_ += size; + + return true; +} + +bool SpdyFrameReader::IsDoneReading() const { + return len_ == ofs_; +} + +bool SpdyFrameReader::CanRead(size_t bytes) const { + return bytes <= (len_ - ofs_); +} + +void SpdyFrameReader::OnFailure() { + // Set our iterator to the end of the buffer so that further reads fail + // immediately. + ofs_ = len_; +} + +} // namespace spdy diff --git a/net/spdy/spdy_frame_reader.h b/net/spdy/spdy_frame_reader.h new file mode 100644 index 0000000..72805a4 --- /dev/null +++ b/net/spdy/spdy_frame_reader.h @@ -0,0 +1,96 @@ +// 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_SPDY_FRAME_READER_H_ +#define NET_SPDY_FRAME_READER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "net/base/net_export.h" + +namespace spdy { + +// Used for reading SPDY frames. Though there isn't really anything terribly +// SPDY-specific here, it's a helper class that's useful when doing SPDY +// framing. +// +// To use, simply construct a SpdyFramerReader using the underlying buffer that +// you'd like to read fields from, then call one of the Read*() methods to +// actually do some reading. +// +// This class keeps an internal iterator to keep track of what's already been +// read and each successive Read*() call automatically increments said iterator +// on success. On failure, internal state of the SpdyFrameReader should not be +// trusted and it is up to the caller to throw away the failed instance and +// handle the error as appropriate. None of the Read*() methods should ever be +// called after failure, as they will also fail immediately. +class NET_EXPORT_PRIVATE SpdyFrameReader { + public: + // Caller must provide an underlying buffer to work on. + SpdyFrameReader(const char* data, const size_t len); + + // Empty destructor. + ~SpdyFrameReader() {} + + // Reads a 16-bit unsigned integer into the given output parameter. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadUInt16(uint16* result); + + // Reads a 32-bit unsigned integer into the given output parameter. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadUInt32(uint32* result); + + // Reads a string prefixed with 16-bit length into the given output parameter. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadStringPiece16(base::StringPiece* result); + + // Reads a string prefixed with 32-bit length into the given output parameter. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadStringPiece32(base::StringPiece* result); + + // Reads a given number of bytes into the given buffer. The buffer + // must be of adequate size. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadBytes(void* result, size_t size); + + // Returns true if the entirety of the underlying buffer has been read via + // Read*() calls. + bool IsDoneReading() const; + + private: + // Returns true if the underlying buffer has enough room to read the given + // amount of bytes. + bool CanRead(size_t bytes) const; + + // To be called when a read fails for any reason. + void OnFailure(); + + // The data buffer that we're reading from. + const char* data_; + + // The length of the data buffer that we're reading from. + const size_t len_; + + // The location of the next read from our data buffer. + size_t ofs_; +}; + +} // namespace spdy + +#endif // NET_SPDY_FRAME_READER_H_ + diff --git a/net/spdy/spdy_frame_reader_test.cc b/net/spdy/spdy_frame_reader_test.cc new file mode 100644 index 0000000..aeaca94 --- /dev/null +++ b/net/spdy/spdy_frame_reader_test.cc @@ -0,0 +1,249 @@ +// 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 <algorithm> +#include <iostream> + +#include "base/memory/scoped_ptr.h" +#include "base/sys_byteorder.h" +#include "net/spdy/spdy_frame_reader.h" +#include "testing/platform_test.h" + +namespace spdy { + +TEST(SpdyFrameReaderTest, ReadUInt16) { + // Frame data in network byte order. + const uint16 kFrameData[] = { + htons(1), htons(1<<15), + }; + + SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData), + arraysize(kFrameData) * sizeof(uint16)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint16 uint16_val; + EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(1u, uint16_val); + + EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(1<<15, uint16_val); +} + +TEST(SpdyFrameReaderTest, ReadUInt32) { + // Frame data in network byte order. + const uint32 kFrameData[] = { + htonl(1), htonl(1<<31), + }; + + SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData), + arraysize(kFrameData) * sizeof(uint32)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint32 uint32_val; + EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(1u, uint32_val); + + EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(static_cast<uint32>(1<<31), uint32_val); +} + +TEST(SpdyFrameReaderTest, ReadStringPiece16) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x02, // uint16(2) + 0x48, 0x69, // "Hi" + 0x00, 0x10, // uint16(16) + 0x54, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x31, 0x2c, 0x20, + 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Hi")); + + EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3")); +} + +TEST(SpdyFrameReaderTest, ReadStringPiece32) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, 0x03, // uint32(3) + 0x66, 0x6f, 0x6f, // "foo" + 0x00, 0x00, 0x00, 0x10, // uint32(16) + 0x54, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x34, 0x2c, 0x20, + 0x35, 0x2c, 0x20, 0x36, // "Testing, 4, 5, 6" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("foo")); + + EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6")); +} + +TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, // part of a uint16 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, // part of a uint32 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint32 uint32_val; + EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small to fit the entire string. +TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x03, // uint16(3) + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small even to fit the length. +TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, // part of a uint16 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece32() with a buffer too small to fit the entire string. +TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, 0x03, // uint32(3) + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece32() with a buffer too small even to fit the length. +TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, // part of a uint32 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +TEST(SpdyFrameReaderTest, ReadBytes) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x66, 0x6f, 0x6f, // "foo" + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + char dest1[3] = {}; + EXPECT_TRUE(frame_reader.ReadBytes(&dest1, arraysize(dest1))); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ("foo", base::StringPiece(dest1, arraysize(dest1))); + + char dest2[2] = {}; + EXPECT_TRUE(frame_reader.ReadBytes(&dest2, arraysize(dest2))); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ("Hi", base::StringPiece(dest2, arraysize(dest2))); +} + +TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x01, + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + char dest[arraysize(kFrameData) + 2] = {}; + EXPECT_FALSE(frame_reader.ReadBytes(&dest, arraysize(kFrameData) + 1)); + EXPECT_STREQ("", dest); +} + +} // namespace diff --git a/net/spdy/spdy_framer.cc b/net/spdy/spdy_framer.cc index ff6268a..ccf7d26 100644 --- a/net/spdy/spdy_framer.cc +++ b/net/spdy/spdy_framer.cc @@ -12,6 +12,7 @@ #include "base/metrics/stats_counters.h" #include "base/third_party/valgrind/memcheck.h" #include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_frame_reader.h" #include "net/spdy/spdy_bitmasks.h" #if defined(USE_SYSTEM_ZLIB) @@ -24,62 +25,21 @@ using std::vector; namespace spdy { -SpdyCredential::SpdyCredential() : slot(0) { } -SpdyCredential::~SpdyCredential() { } - // Compute the id of our dictionary so that we know we're using the // right one when asked for it. -uLong CalculateDictionaryId() { +uLong CalculateDictionaryId(const char* dictionary, + const size_t dictionary_size) { uLong initial_value = adler32(0L, Z_NULL, 0); return adler32(initial_value, - reinterpret_cast<const Bytef*>(SpdyFramer::kDictionary), - SpdyFramer::kDictionarySize); + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); } -// Adler ID for the SPDY header compressor dictionary. -const uLong kDictionaryId = CalculateDictionaryId(); - -int DecompressHeaderBlockInZStream(z_stream* decompressor) { - int rv = inflate(decompressor, Z_SYNC_FLUSH); - if (rv == Z_NEED_DICT) { - // Need to try again with the right dictionary. - if (decompressor->adler == kDictionaryId) { - rv = inflateSetDictionary(decompressor, - (const Bytef*)SpdyFramer::kDictionary, - SpdyFramer::kDictionarySize); - if (rv == Z_OK) - rv = inflate(decompressor, Z_SYNC_FLUSH); - } - } - return rv; -} - -// Retrieve serialized length of SpdyHeaderBlock. -size_t GetSerializedLength(const SpdyHeaderBlock* headers) { - size_t total_length = SpdyControlFrame::kNumNameValuePairsSize; - SpdyHeaderBlock::const_iterator it; - for (it = headers->begin(); it != headers->end(); ++it) { - // We add space for the length of the name and the length of the value as - // well as the length of the name and the length of the value. - total_length += SpdyControlFrame::kLengthOfNameSize + - it->first.size() + - SpdyControlFrame::kLengthOfValueSize + - it->second.size(); - } - return total_length; -} - -// Serializes a SpdyHeaderBlock. -void WriteHeaderBlock(SpdyFrameBuilder* frame, const SpdyHeaderBlock* headers) { - frame->WriteUInt16(headers->size()); // Number of headers. - SpdyHeaderBlock::const_iterator it; - for (it = headers->begin(); it != headers->end(); ++it) { - bool wrote_header; - wrote_header = frame->WriteString(it->first); - wrote_header &= frame->WriteString(it->second); - DCHECK(wrote_header); - } -} +// Adler ID for the SPDY header compressor dictionaries. +const uLong kV2DictionaryId = CalculateDictionaryId(kV2Dictionary, + kV2DictionarySize); +const uLong kV3DictionaryId = CalculateDictionaryId(kV3Dictionary, + kV3DictionarySize); // Creates a FlagsAndLength. FlagsAndLength CreateFlagsAndLength(SpdyControlFlags flags, size_t length) { @@ -103,11 +63,19 @@ size_t SpdyFramer::kControlFrameBufferInitialSize = 32 * 1024; // TODO(mbelshe): We should make this stream-based so there are no limits. size_t SpdyFramer::kControlFrameBufferMaxSize = 64 * 1024; -int SpdyFramer::spdy_version_ = kSpdyProtocolVersion; +// The initial size of the control frame buffer when compression is disabled. +// This exists because we don't do stream (de)compressed control frame data to +// our visitor; we instead buffer the entirety of the control frame and then +// decompress in one fell swoop. +// Since this is only used for control frame headers, the maximum control +// frame header size (18B) is sufficient; all remaining control frame data is +// streamed to the visitor. +size_t SpdyFramer::kUncompressedControlFrameBufferInitialSize = 18; const SpdyStreamId SpdyFramer::kInvalidStream = -1; const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024; + #ifdef DEBUG_SPDY_STATE_CHANGES #define CHANGE_STATE(newstate) \ { \ @@ -122,7 +90,42 @@ const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024; #define CHANGE_STATE(newstate) (state_ = newstate) #endif -SpdyFramer::SpdyFramer() +SettingsFlagsAndId SettingsFlagsAndId::FromWireFormat(int version, + uint32 wire) { + if (version < 3) { + ConvertFlagsAndIdForSpdy2(&wire); + } + return SettingsFlagsAndId(ntohl(wire) >> 24, ntohl(wire) & 0x00ffffff); +} + +SettingsFlagsAndId::SettingsFlagsAndId(uint8 flags, uint32 id) + : flags_(flags), id_(id & 0x00ffffff) { + DCHECK_GT(static_cast<uint32>(1 << 24), id); +} + +uint32 SettingsFlagsAndId::GetWireFormat(int version) const { + uint32 wire = htonl(id_ & 0x00ffffff) | htonl(flags_ << 24); + if (version < 3) { + ConvertFlagsAndIdForSpdy2(&wire); + } + return wire; +} + +// SPDY 2 had a bug in it with respect to byte ordering of id/flags field. +// This method is used to preserve buggy behavior and works on both +// little-endian and big-endian hosts. +// This method is also bidirectional (can be used to translate SPDY 2 to SPDY 3 +// as well as vice versa). +void SettingsFlagsAndId::ConvertFlagsAndIdForSpdy2(uint32* val) { + uint8* wire_array = reinterpret_cast<uint8*>(val); + std::swap(wire_array[0], wire_array[3]); + std::swap(wire_array[1], wire_array[2]); +} + +SpdyCredential::SpdyCredential() : slot(0) { } +SpdyCredential::~SpdyCredential() { } + +SpdyFramer::SpdyFramer(int version) : state_(SPDY_RESET), error_code_(SPDY_NO_ERROR), remaining_data_(0), @@ -134,7 +137,12 @@ SpdyFramer::SpdyFramer() validate_control_frame_sizes_(true), enable_compression_(compression_default_), visitor_(NULL), - display_protocol_("SPDY") { + display_protocol_("SPDY"), + spdy_version_(version), + syn_frame_processed_(false), + probable_http_response_(false) { + DCHECK_GE(3, version); + DCHECK_LE(2, version); } SpdyFramer::~SpdyFramer() { @@ -155,6 +163,7 @@ void SpdyFramer::Reset() { remaining_control_payload_ = 0; remaining_control_header_ = 0; current_frame_len_ = 0; + settings_scratch_.Reset(); // TODO(hkhalil): Remove once initial_size == kControlFrameBufferInitialSize. size_t initial_size = kControlFrameBufferInitialSize; if (!enable_compression_) { @@ -192,6 +201,8 @@ const char* SpdyFramer::StateToString(int state) { return "SPDY_CONTROL_FRAME_HEADER_BLOCK"; case SPDY_CREDENTIAL_FRAME_PAYLOAD: return "SPDY_CREDENTIAL_FRAME_PAYLOAD"; + case SPDY_SETTINGS_FRAME_PAYLOAD: + return "SPDY_SETTINGS_FRAME_PAYLOAD"; } return "UNKNOWN_STATE"; } @@ -304,6 +315,10 @@ size_t SpdyFramer::ProcessInput(const char* data, size_t len) { // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK // 2. SPDY_CONTROL_FRAME_HEADER_BLOCK // + // SETTINGS frames take a slightly modified route: + // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK + // 2. SPDY_SETTINGS_FRAME_PAYLOAD + // // All other control frames will use the alternate route directly to // SPDY_CONTROL_FRAME_PAYLOAD int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len); @@ -312,6 +327,13 @@ size_t SpdyFramer::ProcessInput(const char* data, size_t len) { continue; } + case SPDY_SETTINGS_FRAME_PAYLOAD: { + int bytes_read = ProcessSettingsFramePayload(data, len); + len -= bytes_read; + data += bytes_read; + continue; + } + case SPDY_CONTROL_FRAME_HEADER_BLOCK: { int bytes_read = ProcessControlFrameHeaderBlock(data, len); len -= bytes_read; @@ -323,7 +345,6 @@ size_t SpdyFramer::ProcessInput(const char* data, size_t len) { size_t bytes_read = ProcessCredentialFramePayload(data, len); len -= bytes_read; data += bytes_read; - continue; } case SPDY_CONTROL_FRAME_PAYLOAD: { @@ -342,6 +363,8 @@ size_t SpdyFramer::ProcessInput(const char* data, size_t len) { continue; } default: + LOG(ERROR) << "Invalid value for " << display_protocol_ + << " framer state: " << state_; // This ensures that we don't infinite-loop if state_ gets an // invalid value somehow, such as due to a SpdyFramer getting deleted // from a callback it calls. @@ -383,8 +406,16 @@ size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) { // This is just a sanity check for help debugging early frame errors. if (remaining_data_ > 1000000u) { - LOG(WARNING) << "Unexpectedly large frame. " << display_protocol_ - << " session is likely corrupt."; + // The strncmp for 5 is safe because we only hit this point if we + // have SpdyFrame::kHeaderSize (8) bytes + if (!syn_frame_processed_ && + strncmp(current_frame_buffer_, "HTTP/", 5) == 0) { + LOG(WARNING) << "Unexpected HTTP response to spdy request"; + probable_http_response_ = true; + } else { + LOG(WARNING) << "Unexpectedly large frame. " << display_protocol_ + << " session is likely corrupt."; + } } // if we're here, then we have the common header all received. @@ -407,6 +438,8 @@ void SpdyFramer::ProcessControlFrameHeader() { // We check version before we check validity: version can never be 'invalid', // it can only be unsupported. if (current_control_frame.version() != spdy_version_) { + DLOG(INFO) << "Unsupported SPDY version " << current_control_frame.version() + << " (expected " << spdy_version_ << ")"; set_error(SPDY_UNSUPPORTED_VERSION); return; } @@ -445,9 +478,15 @@ void SpdyFramer::ProcessControlFrameHeader() { set_error(SPDY_INVALID_CONTROL_FRAME); break; case SETTINGS: + // Make sure that we have an integral number of 8-byte key/value pairs, + // plus a 4-byte length field. if (current_control_frame.length() < - SpdySettingsControlFrame::size() - SpdyControlFrame::kHeaderSize) + SpdySettingsControlFrame::size() - SpdyControlFrame::kHeaderSize || + (current_control_frame.length() % 8 != 4)) { + DLOG(WARNING) << "Invalid length for SETTINGS frame: " + << current_control_frame.length(); set_error(SPDY_INVALID_CONTROL_FRAME); + } break; case GOAWAY: if (current_control_frame.length() != @@ -485,12 +524,6 @@ void SpdyFramer::ProcessControlFrameHeader() { } } - // We only support version 1 of this protocol. - if (current_control_frame.version() != spdy_version_) { - set_error(SPDY_UNSUPPORTED_VERSION); - return; - } - remaining_control_payload_ = current_control_frame.length(); if (remaining_control_payload_ > kControlFrameBufferMaxSize - SpdyFrame::kHeaderSize) { @@ -504,37 +537,54 @@ void SpdyFramer::ProcessControlFrameHeader() { return; } - int32 frame_size_without_header_block; + // Determine the frame size without variable-length data. + int32 frame_size_without_variable_data; switch (current_control_frame.type()) { case SYN_STREAM: - frame_size_without_header_block = SpdySynStreamControlFrame::size(); + syn_frame_processed_ = true; + frame_size_without_variable_data = SpdySynStreamControlFrame::size(); break; case SYN_REPLY: - frame_size_without_header_block = SpdySynReplyControlFrame::size(); + syn_frame_processed_ = true; + frame_size_without_variable_data = SpdySynReplyControlFrame::size(); + // SPDY 2 had two bytes of unused space preceeding payload. + if (spdy_version_ < 3) { + frame_size_without_variable_data += 2; + } break; case HEADERS: - frame_size_without_header_block = SpdyHeadersControlFrame::size(); + frame_size_without_variable_data = SpdyHeadersControlFrame::size(); + // SPDY 2 had two bytes of unused space preceeding payload. + if (spdy_version_ < 3) { + frame_size_without_variable_data += 2; + } + break; + case SETTINGS: + frame_size_without_variable_data = SpdySettingsControlFrame::size(); break; default: - frame_size_without_header_block = -1; - LOG_IF(DFATAL, remaining_control_payload_ + SpdyFrame::kHeaderSize > - current_frame_capacity_) - << display_protocol_ - << " control frame buffer too small for fixed-length frame."; - ExpandControlFrameBuffer(remaining_control_payload_); + frame_size_without_variable_data = -1; break; } - if (frame_size_without_header_block > 0) { + if (frame_size_without_variable_data == -1) { + LOG_IF(ERROR, remaining_control_payload_ + SpdyFrame::kHeaderSize > + current_frame_capacity_) + << display_protocol_ + << " control frame buffer too small for fixed-length frame."; + // TODO(hkhalil): Remove ExpandControlFrameBuffer(). + ExpandControlFrameBuffer(remaining_control_payload_); + } + if (frame_size_without_variable_data > 0) { // We have a control frame with a header block. We need to parse the // remainder of the control frame's header before we can parse the header // block. The start of the header block varies with the control type. - DCHECK_GE(static_cast<uint32>(frame_size_without_header_block), - current_frame_len_); - remaining_control_header_ = frame_size_without_header_block - + DCHECK_GE(frame_size_without_variable_data, + static_cast<int32>(current_frame_len_)); + remaining_control_header_ = frame_size_without_variable_data - current_frame_len_; remaining_control_payload_ += SpdyFrame::kHeaderSize - - frame_size_without_header_block; + frame_size_without_variable_data; CHANGE_STATE(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK); return; } @@ -553,6 +603,46 @@ size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len, return bytes_to_read; } +size_t SpdyFramer::GetSerializedLength(const SpdyHeaderBlock* headers) const { + const size_t num_name_value_pairs_size + = (spdy_version_ < 3) ? sizeof(uint16) : sizeof(uint32); + const size_t length_of_name_size = num_name_value_pairs_size; + const size_t length_of_value_size = num_name_value_pairs_size; + + size_t total_length = num_name_value_pairs_size; + for (SpdyHeaderBlock::const_iterator it = headers->begin(); + it != headers->end(); + ++it) { + // We add space for the length of the name and the length of the value as + // well as the length of the name and the length of the value. + total_length += length_of_name_size + it->first.size() + + length_of_value_size + it->second.size(); + } + return total_length; +} + +void SpdyFramer::WriteHeaderBlock(SpdyFrameBuilder* frame, + const SpdyHeaderBlock* headers) const { + if (spdy_version_ < 3) { + frame->WriteUInt16(headers->size()); // Number of headers. + } else { + frame->WriteUInt32(headers->size()); // Number of headers. + } + SpdyHeaderBlock::const_iterator it; + for (it = headers->begin(); it != headers->end(); ++it) { + bool wrote_header; + if (spdy_version_ < 3) { + wrote_header = frame->WriteString(it->first); + wrote_header &= frame->WriteString(it->second); + } else { + wrote_header = frame->WriteStringPiece32(it->first); + wrote_header &= frame->WriteStringPiece32(it->second); + } + DCHECK(wrote_header); + } +} + + size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data, size_t len) { DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_); @@ -567,10 +657,15 @@ size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data, SpdyControlFrame control_frame(current_frame_buffer_, false); DCHECK(control_frame.type() == SYN_STREAM || control_frame.type() == SYN_REPLY || - control_frame.type() == HEADERS); + control_frame.type() == HEADERS || + control_frame.type() == SETTINGS); visitor_->OnControl(&control_frame); - CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + if (control_frame.type() == SETTINGS) { + CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD); + } else { + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + } } } return original_len - len; @@ -625,6 +720,110 @@ size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data, return process_bytes; } +size_t SpdyFramer::ProcessSettingsFramePayload(const char* data, + size_t data_len) { + DCHECK_EQ(SPDY_SETTINGS_FRAME_PAYLOAD, state_); + SpdyControlFrame control_frame(current_frame_buffer_, false); + DCHECK_EQ(control_frame.type(), SETTINGS); + size_t unprocessed_bytes = std::min(data_len, remaining_control_payload_); + size_t processed_bytes = 0; + DCHECK_GT(unprocessed_bytes, 0u); + + // Loop over our incoming data. + while (unprocessed_bytes > 0) { + // Process up to one setting at a time. + size_t processing = std::min( + unprocessed_bytes, + static_cast<size_t>(8 - settings_scratch_.setting_buf_len)); + + // Check if we have a complete setting in our input. + if (processing == 8) { + // Parse the setting directly out of the input without buffering. + if (!ProcessSetting(data + processed_bytes)) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return processed_bytes; + } + } else { + // Continue updating settings_scratch_.setting_buf. + memcpy(settings_scratch_.setting_buf + settings_scratch_.setting_buf_len, + data + processed_bytes, + processing); + settings_scratch_.setting_buf_len += processing; + + // Check if we have a complete setting buffered. + if (settings_scratch_.setting_buf_len == 8) { + if (!ProcessSetting(settings_scratch_.setting_buf)) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return processed_bytes; + } + // Reset settings_scratch_.setting_buf for our next setting. + settings_scratch_.setting_buf_len = 0; + } + } + + // Iterate. + unprocessed_bytes -= processing; + processed_bytes += processing; + } + + // Check if we're done handling this SETTINGS frame. + remaining_control_payload_ -= processed_bytes; + if (remaining_control_payload_ == 0) { + CHANGE_STATE(SPDY_AUTO_RESET); + } + + return processed_bytes; +} + +bool SpdyFramer::ProcessSetting(const char* data) { + // Extract fields. + // Maintain behavior of old SPDY 2 bug with byte ordering of flags/id. + const uint32 id_and_flags_wire = *(reinterpret_cast<const uint32*>(data)); + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(spdy_version_, id_and_flags_wire); + uint8 flags = id_and_flags.flags(); + uint32 value = ntohl(*(reinterpret_cast<const uint32*>(data + 4))); + + // Validate id. + switch (id_and_flags.id()) { + case SETTINGS_UPLOAD_BANDWIDTH: + case SETTINGS_DOWNLOAD_BANDWIDTH: + case SETTINGS_ROUND_TRIP_TIME: + case SETTINGS_MAX_CONCURRENT_STREAMS: + case SETTINGS_CURRENT_CWND: + case SETTINGS_DOWNLOAD_RETRANS_RATE: + case SETTINGS_INITIAL_WINDOW_SIZE: + // Valid values. + break; + default: + DLOG(WARNING) << "Unknown SETTINGS ID: " << id_and_flags.id(); + return false; + } + SpdySettingsIds id = static_cast<SpdySettingsIds>(id_and_flags.id()); + + // Detect duplciates. + if (static_cast<uint32>(id) <= settings_scratch_.last_setting_id) { + DLOG(WARNING) << "Duplicate entry or invalid ordering for id " << id + << " in " << display_protocol_ << " SETTINGS frame " + << "(last settikng id was " + << settings_scratch_.last_setting_id << ")."; + return false; + } + settings_scratch_.last_setting_id = id; + + // Validate flags. + uint8 kFlagsMask = SETTINGS_FLAG_PLEASE_PERSIST | SETTINGS_FLAG_PERSISTED; + if ((flags & ~(kFlagsMask)) != 0) { + DLOG(WARNING) << "Unknown SETTINGS flags provided for id " << id << ": " + << flags; + return false; + } + + // Validation succeeded. Pass on to visitor. + visitor_->OnSetting(id, flags, value); + return true; +} + size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) { size_t original_len = len; if (remaining_control_payload_) { @@ -729,95 +928,62 @@ void SpdyFramer::ExpandControlFrameBuffer(size_t size) { current_frame_buffer_ = new_buffer; } -/* static */ bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data, size_t header_length, SpdyHeaderBlock* block) { - SpdyFrameBuilder builder(header_data, header_length); - void* iter = NULL; - uint16 num_headers; - if (builder.ReadUInt16(&iter, &num_headers)) { - for (int index = 0; index < num_headers; ++index) { - std::string name; - std::string value; - if (!builder.ReadString(&iter, &name)) - return false; - if (!builder.ReadString(&iter, &value)) - return false; - if (block->find(name) == block->end()) { - (*block)[name] = value; - } else { - return false; - } + SpdyFrameReader reader(header_data, header_length); + + // Read number of headers. + uint32 num_headers; + if (spdy_version_ < 3) { + uint16 temp; + if (!reader.ReadUInt16(&temp)) { + DLOG(INFO) << "Unable to read number of headers."; + return false; + } + num_headers = temp; + } else { + if (!reader.ReadUInt32(&num_headers)) { + DLOG(INFO) << "Unable to read number of headers."; + return false; } - return true; } - return false; -} - -bool SpdyFramer::ParseHeaderBlock(const SpdyFrame* frame, - SpdyHeaderBlock* block) { - SpdyControlFrame control_frame(frame->data(), false); - uint32 type = control_frame.type(); - if (type != SYN_STREAM && type != SYN_REPLY && type != HEADERS) - return false; - - // Find the header data within the control frame. - scoped_ptr<SpdyFrame> decompressed_frame(DecompressFrame(*frame)); - if (!decompressed_frame.get()) - return false; - const char *header_data = NULL; - int header_length = 0; + // Read each header. + for (uint32 index = 0; index < num_headers; ++index) { + base::StringPiece temp; - switch (type) { - case SYN_STREAM: - { - SpdySynStreamControlFrame syn_frame(decompressed_frame->data(), false); - header_data = syn_frame.header_block(); - header_length = syn_frame.header_block_len(); - } - break; - case SYN_REPLY: - { - SpdySynReplyControlFrame syn_frame(decompressed_frame->data(), false); - header_data = syn_frame.header_block(); - header_length = syn_frame.header_block_len(); - } - break; - case HEADERS: - { - SpdyHeadersControlFrame header_frame(decompressed_frame->data(), false); - header_data = header_frame.header_block(); - header_length = header_frame.header_block_len(); - } - break; - } + // Read header name. + if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp) + : !reader.ReadStringPiece32(&temp)) { + DLOG(INFO) << "Unable to read header name (" << index + 1 << " of " + << num_headers << ")."; + return false; + } + std::string name; + temp.CopyToString(&name); + + // Read header value. + if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp) + : !reader.ReadStringPiece32(&temp)) { + DLOG(INFO) << "Unable to read header value (" << index + 1 << " of " + << num_headers << ")."; + return false; + } + std::string value; + temp.CopyToString(&value); - SpdyFrameBuilder builder(header_data, header_length); - void* iter = NULL; - uint16 num_headers; - if (builder.ReadUInt16(&iter, &num_headers)) { - int index; - for (index = 0; index < num_headers; ++index) { - std::string name; - std::string value; - if (!builder.ReadString(&iter, &name)) - break; - if (!builder.ReadString(&iter, &value)) - break; - if (!name.size() || !value.size()) - return false; - if (block->find(name) == block->end()) { - (*block)[name] = value; - } else { - return false; - } + // Ensure no duplicates. + if (block->find(name) != block->end()) { + DLOG(INFO) << "Duplicate header '" << name << "' (" << index + 1 << " of " + << num_headers << ")."; + return false; } - return index == num_headers && - iter == header_data + header_length; + + // Store header. + (*block)[name] = value; } - return false; + return true; } /* static */ @@ -826,16 +992,20 @@ bool SpdyFramer::ParseSettings(const SpdySettingsControlFrame* frame, DCHECK_EQ(frame->type(), SETTINGS); DCHECK(settings); - SpdyFrameBuilder parser(frame->header_block(), frame->header_block_len()); - void* iter = NULL; + SpdyFrameReader parser(frame->header_block(), frame->header_block_len()); for (size_t index = 0; index < frame->num_entries(); ++index) { - uint32 id; + uint32 id_and_flags_wire; uint32 value; - if (!parser.ReadUInt32(&iter, &id)) + // SettingsFlagsAndId accepts off-the-wire (network byte order) data, so we + // use ReadBytes() instead of ReadUInt32() as the latter calls ntohl(). + if (!parser.ReadBytes(&id_and_flags_wire, 4)) { return false; - if (!parser.ReadUInt32(&iter, &value)) + } + if (!parser.ReadUInt32(&value)) return false; - settings->insert(settings->end(), std::make_pair(id, value)); + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(frame->version(), id_and_flags_wire); + settings->insert(settings->end(), std::make_pair(id_and_flags, value)); } return true; } @@ -845,32 +1015,35 @@ bool SpdyFramer::ParseCredentialData(const char* data, size_t len, SpdyCredential* credential) { DCHECK(credential); - void* iter = NULL; - SpdyFrameBuilder parser(data, len); - if (!parser.ReadUInt16(&iter, &credential->slot)) + SpdyFrameReader parser(data, len); + base::StringPiece temp; + if (!parser.ReadUInt16(&credential->slot)) { return false; + } - uint32 proof_len; - const char* proof_data; - if (!parser.ReadReadLen32PrefixedData(&iter, &proof_data, &proof_len)) + if (!parser.ReadStringPiece32(&temp)) { return false; - credential->proof.assign(proof_data, proof_len); + } + temp.CopyToString(&credential->proof); - while (parser.IteratorHasRoomFor(iter, 1)) { - uint32 cert_len; - const char* cert_data; - if (!parser.ReadReadLen32PrefixedData(&iter, &cert_data, &cert_len)) + while (!parser.IsDoneReading()) { + if (!parser.ReadStringPiece32(&temp)) { return false; - credential->certs.push_back(""); - credential->certs.back().assign(cert_data, cert_len); + } + std::string cert; + temp.CopyToString(&cert); + credential->certs.push_back(cert); } return true; } SpdySynStreamControlFrame* SpdyFramer::CreateSynStream( - SpdyStreamId stream_id, SpdyStreamId associated_stream_id, int priority, - SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) { - DCHECK_GT(stream_id, static_cast<SpdyStreamId>(0)); + SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { DCHECK_EQ(0u, stream_id & ~kStreamIdMask); DCHECK_EQ(0u, associated_stream_id & ~kStreamIdMask); @@ -889,7 +1062,13 @@ SpdySynStreamControlFrame* SpdyFramer::CreateSynStream( frame.WriteBytes(&flags_length, sizeof(flags_length)); frame.WriteUInt32(stream_id); frame.WriteUInt32(associated_stream_id); - frame.WriteUInt16(ntohs(priority) << 6); // Priority. + // Cap as appropriate. + if (priority > GetLowestPriority()) { + DLOG(ERROR) << "Priority out-of-bounds."; + priority = GetLowestPriority(); + } + // Priority is 2 bits for <spdy3, 3 bits otherwise. + frame.WriteUInt16(ntohs(priority) << (spdy_version_ < 3 ? 6 : 5)); WriteHeaderBlock(&frame, headers); scoped_ptr<SpdySynStreamControlFrame> syn_frame( @@ -901,14 +1080,21 @@ SpdySynStreamControlFrame* SpdyFramer::CreateSynStream( return syn_frame.release(); } -SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id, - SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) { +SpdySynReplyControlFrame* SpdyFramer::CreateSynReply( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); // Find our length. size_t expected_frame_size = SpdySynReplyControlFrame::size() + GetSerializedLength(headers); + // In SPDY 2, there were 2 unused bytes before payload. + if (spdy_version_ < 3) { + expected_frame_size += 2; + } // Create our FlagsAndLength. FlagsAndLength flags_length = CreateFlagsAndLength( @@ -920,7 +1106,9 @@ SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id, frame.WriteUInt16(SYN_REPLY); frame.WriteBytes(&flags_length, sizeof(flags_length)); frame.WriteUInt32(stream_id); - frame.WriteUInt16(0); // Unused + if (spdy_version_ < 3) { + frame.WriteUInt16(0); // Unused + } WriteHeaderBlock(&frame, headers); scoped_ptr<SpdySynReplyControlFrame> reply_frame( @@ -932,9 +1120,9 @@ SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id, return reply_frame.release(); } -/* static */ -SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(SpdyStreamId stream_id, - SpdyStatusCodes status) { +SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream( + SpdyStreamId stream_id, + SpdyStatusCodes status) const { DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); DCHECK_NE(status, INVALID); @@ -949,9 +1137,8 @@ SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(SpdyStreamId stream_id, return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take()); } -/* static */ SpdySettingsControlFrame* SpdyFramer::CreateSettings( - const SpdySettings& values) { + const SpdySettings& values) const { SpdyFrameBuilder frame(SpdySettingsControlFrame::size() + 8 * values.size()); frame.WriteUInt16(kControlFlagMask | spdy_version_); frame.WriteUInt16(SETTINGS); @@ -962,24 +1149,15 @@ SpdySettingsControlFrame* SpdyFramer::CreateSettings( frame.WriteUInt32(values.size()); SpdySettings::const_iterator it = values.begin(); while (it != values.end()) { - frame.WriteUInt32(it->first.id_); + uint32 id_and_flags_wire = it->first.GetWireFormat(spdy_version_); + frame.WriteBytes(&id_and_flags_wire, 4); frame.WriteUInt32(it->second); ++it; } return reinterpret_cast<SpdySettingsControlFrame*>(frame.take()); } -/* static */ -SpdyNoOpControlFrame* SpdyFramer::CreateNopFrame() { - SpdyFrameBuilder frame(SpdyNoOpControlFrame::size()); - frame.WriteUInt16(kControlFlagMask | spdy_version_); - frame.WriteUInt16(NOOP); - frame.WriteUInt32(0); - return reinterpret_cast<SpdyNoOpControlFrame*>(frame.take()); -} - -/* static */ -SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) { +SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const { SpdyFrameBuilder frame(SpdyPingControlFrame::size()); frame.WriteUInt16(kControlFlagMask | spdy_version_); frame.WriteUInt16(PING); @@ -989,9 +1167,8 @@ SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) { return reinterpret_cast<SpdyPingControlFrame*>(frame.take()); } -/* static */ SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway( - SpdyStreamId last_accepted_stream_id) { + SpdyStreamId last_accepted_stream_id) const { DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask); SpdyFrameBuilder frame(SpdyGoAwayControlFrame::size()); @@ -1003,8 +1180,11 @@ SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway( return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take()); } -SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id, - SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) { +SpdyHeadersControlFrame* SpdyFramer::CreateHeaders( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { // Basically the same as CreateSynReply(). DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); @@ -1012,6 +1192,10 @@ SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id, // Find our length. size_t expected_frame_size = SpdyHeadersControlFrame::size() + GetSerializedLength(headers); + // In SPDY 2, there were 2 unused bytes before payload. + if (spdy_version_ < 3) { + expected_frame_size += 2; + } // Create our FlagsAndLength. FlagsAndLength flags_length = CreateFlagsAndLength( @@ -1023,7 +1207,9 @@ SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id, frame.WriteUInt16(HEADERS); frame.WriteBytes(&flags_length, sizeof(flags_length)); frame.WriteUInt32(stream_id); - frame.WriteUInt16(0); // Unused + if (spdy_version_ < 3) { + frame.WriteUInt16(0); // Unused + } WriteHeaderBlock(&frame, headers); DCHECK_EQ(static_cast<size_t>(frame.length()), expected_frame_size); @@ -1036,14 +1222,14 @@ SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id, return headers_frame.release(); } -/* static */ SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate( SpdyStreamId stream_id, - uint32 delta_window_size) { + uint32 delta_window_size) const { DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); DCHECK_GT(delta_window_size, 0u); - DCHECK_LE(delta_window_size, spdy::kSpdyStreamMaximumWindowSize); + DCHECK_LE(delta_window_size, + static_cast<uint32>(spdy::kSpdyStreamMaximumWindowSize)); SpdyFrameBuilder frame(SpdyWindowUpdateControlFrame::size()); frame.WriteUInt16(kControlFlagMask | spdy_version_); @@ -1056,25 +1242,24 @@ SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate( return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take()); } -/* static */ SpdyCredentialControlFrame* SpdyFramer::CreateCredentialFrame( - const SpdyCredential& credential) { + const SpdyCredential& credential) const { // Calculate the size of the frame by adding the size of the // variable length data to the size of the fixed length data. size_t frame_size = SpdyCredentialControlFrame::size() + credential.proof.length(); DCHECK_EQ(SpdyCredentialControlFrame::size(), 14u); - for (vector<std::string>::const_iterator cert = credential.certs.begin(); + for (std::vector<std::string>::const_iterator cert = credential.certs.begin(); cert != credential.certs.end(); - cert++) { + ++cert) { frame_size += sizeof(uint32); // size of the cert_length field - frame_size += cert->length(); // size of the cert_data field + frame_size += cert->length(); // size of the cert_data field } size_t payload_size = frame_size - SpdyFrame::kHeaderSize; SpdyFrameBuilder frame(frame_size); // Create our FlagsAndLength. - SpdyControlFlags flags = spdy::CONTROL_FLAG_NONE; + SpdyControlFlags flags = CONTROL_FLAG_NONE; FlagsAndLength flags_length = CreateFlagsAndLength(flags, payload_size); frame.WriteUInt16(kControlFlagMask | spdy_version_); @@ -1083,9 +1268,9 @@ SpdyCredentialControlFrame* SpdyFramer::CreateCredentialFrame( frame.WriteUInt16(credential.slot); frame.WriteUInt32(credential.proof.size()); frame.WriteBytes(credential.proof.c_str(), credential.proof.size()); - for (vector<std::string>::const_iterator cert = credential.certs.begin(); + for (std::vector<std::string>::const_iterator cert = credential.certs.begin(); cert != credential.certs.end(); - cert++) { + ++cert) { frame.WriteUInt32(cert->length()); frame.WriteBytes(cert->c_str(), cert->length()); } @@ -1095,7 +1280,6 @@ SpdyCredentialControlFrame* SpdyFramer::CreateCredentialFrame( SpdyDataFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id, const char* data, uint32 len, SpdyDataFlags flags) { - DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); SpdyFrameBuilder frame(SpdyDataFrame::size() + len); @@ -1112,10 +1296,10 @@ SpdyDataFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id, scoped_ptr<SpdyFrame> data_frame(frame.take()); SpdyDataFrame* rv; if (flags & DATA_FLAG_COMPRESSED) { - rv = reinterpret_cast<SpdyDataFrame*>(CompressFrame(*data_frame.get())); - } else { - rv = reinterpret_cast<SpdyDataFrame*>(data_frame.release()); + LOG(DFATAL) << "DATA_FLAG_COMPRESSED invalid for " << display_protocol_ + << "."; } + rv = reinterpret_cast<SpdyDataFrame*>(data_frame.release()); if (flags & DATA_FLAG_FIN) { CleanupCompressorForStream(stream_id); @@ -1131,46 +1315,18 @@ static const int kCompressorLevel = 9; static const int kCompressorWindowSizeInBits = 11; static const int kCompressorMemLevel = 1; -// This is just a hacked dictionary to use for shrinking HTTP-like headers. -// TODO(mbelshe): Use a scientific methodology for computing the dictionary. -const char SpdyFramer::kDictionary[] = - "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" - "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" - "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" - "-agent10010120020120220320420520630030130230330430530630740040140240340440" - "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" - "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" - "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" - "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" - "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" - "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" - "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" - "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" - ".1statusversionurl"; -const int SpdyFramer::kDictionarySize = arraysize(kDictionary); - SpdyFrame* SpdyFramer::CompressFrame(const SpdyFrame& frame) { if (frame.is_control_frame()) { return CompressControlFrame( reinterpret_cast<const SpdyControlFrame&>(frame)); } - return CompressDataFrame(reinterpret_cast<const SpdyDataFrame&>(frame)); -} - -SpdyFrame* SpdyFramer::DecompressFrame(const SpdyFrame& frame) { - if (frame.is_control_frame()) { - return DecompressControlFrame( - reinterpret_cast<const SpdyControlFrame&>(frame)); - } - return DecompressDataFrame(reinterpret_cast<const SpdyDataFrame&>(frame)); + return NULL; } bool SpdyFramer::IsCompressible(const SpdyFrame& frame) const { // The important frames to compress are those which contain large // amounts of compressible data - namely the headers in the SYN_STREAM // and SYN_REPLY. - // TODO(mbelshe): Reconcile this with the spec when the spec is - // explicit about which frames compress and which do not. if (frame.is_control_frame()) { const SpdyControlFrame& control_frame = reinterpret_cast<const SpdyControlFrame&>(frame); @@ -1178,9 +1334,8 @@ bool SpdyFramer::IsCompressible(const SpdyFrame& frame) const { control_frame.type() == SYN_REPLY; } - const SpdyDataFrame& data_frame = - reinterpret_cast<const SpdyDataFrame&>(frame); - return (data_frame.flags() & DATA_FLAG_COMPRESSED) != 0; + // We don't compress Data frames. + return false; } z_stream* SpdyFramer::GetHeaderCompressor() { @@ -1196,10 +1351,15 @@ z_stream* SpdyFramer::GetHeaderCompressor() { kCompressorWindowSizeInBits, kCompressorMemLevel, Z_DEFAULT_STRATEGY); - if (success == Z_OK) + if (success == Z_OK) { + const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary + : kV3Dictionary; + const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize + : kV3DictionarySize; success = deflateSetDictionary(header_compressor_.get(), - reinterpret_cast<const Bytef*>(kDictionary), - kDictionarySize); + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); + } if (success != Z_OK) { LOG(WARNING) << "deflateSetDictionary failure: " << success; header_compressor_.reset(NULL); @@ -1224,27 +1384,6 @@ z_stream* SpdyFramer::GetHeaderDecompressor() { return header_decompressor_.get(); } -z_stream* SpdyFramer::GetStreamCompressor(SpdyStreamId stream_id) { - CompressorMap::iterator it = stream_compressors_.find(stream_id); - if (it != stream_compressors_.end()) - return it->second; // Already initialized. - - scoped_ptr<z_stream> compressor(new z_stream); - memset(compressor.get(), 0, sizeof(z_stream)); - - int success = deflateInit2(compressor.get(), - kCompressorLevel, - Z_DEFLATED, - kCompressorWindowSizeInBits, - kCompressorMemLevel, - Z_DEFAULT_STRATEGY); - if (success != Z_OK) { - LOG(WARNING) << "deflateInit failure: " << success; - return NULL; - } - return stream_compressors_[stream_id] = compressor.release(); -} - z_stream* SpdyFramer::GetStreamDecompressor(SpdyStreamId stream_id) { CompressorMap::iterator it = stream_decompressors_.find(stream_id); if (it != stream_decompressors_.end()) @@ -1288,6 +1427,11 @@ bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame, *payload_length = syn_frame.header_block_len(); *header_length = frame_size; *payload = frame.data() + *header_length; + // SPDY 2 had two bytes of unused space preceeding payload. + if (spdy_version_ < 3) { + *header_length += 2; + *payload += 2; + } } break; case HEADERS: @@ -1298,6 +1442,11 @@ bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame, *payload_length = headers_frame.header_block_len(); *header_length = frame_size; *payload = frame.data() + *header_length; + // SPDY 2 had two bytes of unused space preceeding payload. + if (spdy_version_ < 3) { + *header_length += 2; + *payload += 2; + } } break; default: @@ -1318,40 +1467,7 @@ SpdyControlFrame* SpdyFramer::CompressControlFrame( z_stream* compressor = GetHeaderCompressor(); if (!compressor) return NULL; - return reinterpret_cast<SpdyControlFrame*>( - CompressFrameWithZStream(frame, compressor)); -} -SpdyDataFrame* SpdyFramer::CompressDataFrame(const SpdyDataFrame& frame) { - z_stream* compressor = GetStreamCompressor(frame.stream_id()); - if (!compressor) - return NULL; - return reinterpret_cast<SpdyDataFrame*>( - CompressFrameWithZStream(frame, compressor)); -} - -SpdyControlFrame* SpdyFramer::DecompressControlFrame( - const SpdyControlFrame& frame) { - z_stream* decompressor = GetHeaderDecompressor(); - if (!decompressor) { - LOG(DFATAL) << "Couldn't get decompressor for handling control frame."; - set_error(SPDY_DECOMPRESS_FAILURE); - return NULL; - } - return reinterpret_cast<SpdyControlFrame*>( - DecompressFrameWithZStream(frame, decompressor)); -} - -SpdyDataFrame* SpdyFramer::DecompressDataFrame(const SpdyDataFrame& frame) { - z_stream* decompressor = GetStreamDecompressor(frame.stream_id()); - if (!decompressor) - return NULL; - return reinterpret_cast<SpdyDataFrame*>( - DecompressFrameWithZStream(frame, decompressor)); -} - -SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame, - z_stream* compressor) { int payload_length; int header_length; const char* payload; @@ -1361,7 +1477,7 @@ SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame, base::StatsCounter post_compress_bytes("spdy.PostCompressSize"); if (!enable_compression_) - return DuplicateFrame(frame); + return reinterpret_cast<SpdyControlFrame*>(DuplicateFrame(frame)); if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload)) return NULL; @@ -1369,7 +1485,13 @@ SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame, // Create an output frame. int compressed_max_size = deflateBound(compressor, payload_length); int new_frame_size = header_length + compressed_max_size; - scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size)); + if ((frame.type() == SYN_REPLY || frame.type() == HEADERS) && + spdy_version_ < 3) { + new_frame_size += 2; + } + DCHECK_GE(new_frame_size, + static_cast<int>(frame.length() + SpdyFrame::kHeaderSize)); + scoped_ptr<SpdyControlFrame> new_frame(new SpdyControlFrame(new_frame_size)); memcpy(new_frame->data(), frame.data(), frame.length() + SpdyFrame::kHeaderSize); @@ -1380,6 +1502,9 @@ SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame, compressor->avail_out = compressed_max_size; // Data packets have a 'compressed' flag. + // TODO(hkhalil): Remove post code-yellow. It's impossible to execute this + // branch given that SpdyControlFrame::is_control_frame always returns true. + DCHECK(new_frame->is_control_frame()); if (!new_frame->is_control_frame()) { SpdyDataFrame* data_frame = reinterpret_cast<SpdyDataFrame*>(new_frame.get()); @@ -1416,85 +1541,6 @@ SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame, return new_frame.release(); } -SpdyFrame* SpdyFramer::DecompressFrameWithZStream(const SpdyFrame& frame, - z_stream* decompressor) { - int payload_length; - int header_length; - const char* payload; - - base::StatsCounter decompressed_frames("spdy.DecompressedFrames"); - base::StatsCounter pre_decompress_bytes("spdy.PreDeCompressSize"); - base::StatsCounter post_decompress_bytes("spdy.PostDeCompressSize"); - - if (!enable_compression_) - return DuplicateFrame(frame); - - if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload)) - return NULL; - - if (!frame.is_control_frame()) { - const SpdyDataFrame& data_frame = - reinterpret_cast<const SpdyDataFrame&>(frame); - if ((data_frame.flags() & DATA_FLAG_COMPRESSED) == 0) - return DuplicateFrame(frame); - } - - // Create an output frame. Assume it does not need to be longer than - // the input data. - size_t decompressed_max_size = kControlFrameBufferInitialSize; - int new_frame_size = header_length + decompressed_max_size; - if (frame.length() > decompressed_max_size) - return NULL; - scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size)); - memcpy(new_frame->data(), frame.data(), - frame.length() + SpdyFrame::kHeaderSize); - - decompressor->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload)); - decompressor->avail_in = payload_length; - decompressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) + - header_length; - decompressor->avail_out = decompressed_max_size; - - int rv = inflate(decompressor, Z_SYNC_FLUSH); - if (rv == Z_NEED_DICT) { - // Need to try again with the right dictionary. - if (decompressor->adler == kDictionaryId) { - rv = inflateSetDictionary(decompressor, - (const Bytef*)SpdyFramer::kDictionary, - SpdyFramer::kDictionarySize); - if (rv == Z_OK) - rv = inflate(decompressor, Z_SYNC_FLUSH); - } - } - if (rv != Z_OK) { // How can we know that it decompressed everything? - LOG(WARNING) << "inflate failure: " << rv; - return NULL; - } - - // Unset the compressed flag for data frames. - if (!new_frame->is_control_frame()) { - SpdyDataFrame* data_frame = - reinterpret_cast<SpdyDataFrame*>(new_frame.get()); - data_frame->set_flags(data_frame->flags() & ~DATA_FLAG_COMPRESSED); - } - - int decompressed_size = decompressed_max_size - decompressor->avail_out; - new_frame->set_length( - header_length + decompressed_size - SpdyFrame::kHeaderSize); - - // If there is data left, then the frame didn't fully decompress. This - // means that there is stranded data at the end of this frame buffer which - // will be ignored. - DCHECK_EQ(decompressor->avail_in, 0u); - - pre_decompress_bytes.Add(frame.length()); - post_decompress_bytes.Add(new_frame->length()); - - decompressed_frames.Increment(); - - return new_frame.release(); -} - // Incrementally decompress the control frame's header block, feeding the // result to the visitor in chunks. Continue this until the visitor // indicates that it cannot process any more data, or (more commonly) we @@ -1521,12 +1567,31 @@ bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData( while (decomp->avail_in > 0 && processed_successfully) { decomp->next_out = reinterpret_cast<Bytef*>(buffer); decomp->avail_out = arraysize(buffer); - int rv = DecompressHeaderBlockInZStream(decomp); - if (rv != Z_OK && rv != Z_BUF_ERROR) { - set_error(SPDY_DECOMPRESS_FAILURE); - DLOG(WARNING) << "inflate failure: " << rv; - processed_successfully = false; - } else { + + int rv = inflate(decomp, Z_SYNC_FLUSH); + if (rv == Z_NEED_DICT) { + const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary + : kV3Dictionary; + const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize + : kV3DictionarySize; + const uLong dictionary_id = (spdy_version_ < 3) ? kV2DictionaryId + : kV3DictionaryId; + // Need to try again with the right dictionary. + if (decomp->adler == dictionary_id) { + rv = inflateSetDictionary(decomp, + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); + if (rv == Z_OK) + rv = inflate(decomp, Z_SYNC_FLUSH); + } + } + + // Inflate will generate a Z_BUF_ERROR if it runs out of input + // without producing any output. The input is consumed and + // buffered internally by zlib so we can detect this condition by + // checking if avail_in is 0 after the call to inflate. + bool input_exhausted = ((rv == Z_BUF_ERROR) && (decomp->avail_in == 0)); + if ((rv == Z_OK) || input_exhausted) { size_t decompressed_len = arraysize(buffer) - decomp->avail_out; if (decompressed_len > 0) { processed_successfully = visitor_->OnControlFrameHeaderData( @@ -1537,6 +1602,10 @@ bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData( // visitor. set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); } + } else { + DLOG(WARNING) << "inflate failure: " << rv << " " << len; + set_error(SPDY_DECOMPRESS_FAILURE); + processed_successfully = false; } } return processed_successfully; @@ -1546,7 +1615,6 @@ bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData( const SpdyControlFrame* control_frame, const char* data, size_t len) { bool read_successfully = true; const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame); - DCHECK_LT(0u, stream_id); while (read_successfully && len > 0) { size_t bytes_to_deliver = std::min(len, kHeaderDataChunkMaxSize); read_successfully = visitor_->OnControlFrameHeaderData(stream_id, data, @@ -1622,7 +1690,10 @@ size_t SpdyFramer::GetMinimumControlFrameSize(SpdyControlType type) { case SETTINGS: return SpdySettingsControlFrame::size(); case NOOP: - return SpdyNoOpControlFrame::size(); + // Even though NOOP is no longer supported, we still correctly report its + // size so that it can be handled correctly as incoming data if + // implementations so desire. + return SpdyFrame::kHeaderSize; case PING: return SpdyPingControlFrame::size(); case GOAWAY: @@ -1682,42 +1753,16 @@ SpdyStreamId SpdyFramer::GetControlFrameStreamId( return stream_id; } -size_t SpdyFramer::BytesSafeToRead() const { - switch (state_) { - case SPDY_ERROR: - case SPDY_DONE: - case SPDY_AUTO_RESET: - case SPDY_RESET: - return 0; - case SPDY_READING_COMMON_HEADER: - DCHECK_LT(current_frame_len_, - static_cast<size_t>(SpdyFrame::kHeaderSize)); - return SpdyFrame::kHeaderSize - current_frame_len_; - // TODO(rtenneti): Add support for SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK - // and SPDY_CONTROL_FRAME_HEADER_BLOCK. - case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: - case SPDY_CONTROL_FRAME_HEADER_BLOCK: - return 0; - case SPDY_CONTROL_FRAME_PAYLOAD: - case SPDY_CREDENTIAL_FRAME_PAYLOAD: - case SPDY_IGNORE_REMAINING_PAYLOAD: - case SPDY_FORWARD_STREAM_FRAME: - return remaining_data_; - } - // We should never get to here. - return 0; -} - void SpdyFramer::set_enable_compression(bool value) { enable_compression_ = value; } -void SpdyFramer::set_enable_compression_default(bool value) { - compression_default_ = value; -} - void SpdyFramer::set_validate_control_frame_sizes(bool value) { validate_control_frame_sizes_ = value; } +void SpdyFramer::set_enable_compression_default(bool value) { + compression_default_ = value; +} + } // namespace spdy diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h index f4a3a3a61..1b0ce62 100644 --- a/net/spdy/spdy_framer.h +++ b/net/spdy/spdy_framer.h @@ -37,19 +37,51 @@ class WebSocketJobTest; namespace spdy { class SpdyFramer; -class SpdyFramerTest; +class SpdyFrameBuilder; +class SpdyFramerSpdy2Test; +class SpdyFramerSpdy3Test; + +namespace test_spdy2 { + +class TestSpdyVisitor; + +} // namespace test_spdy2 + +namespace test_spdy3 { -namespace test { class TestSpdyVisitor; -void FramerSetEnableCompressionHelper(SpdyFramer* framer, bool compress); -} // namespace test + +} // namespace test_spdy3 // A datastructure for holding a set of headers from either a // SYN_STREAM or SYN_REPLY frame. typedef std::map<std::string, std::string> SpdyHeaderBlock; +// A datastructure for holding the ID and flag fields for SETTINGS. +// Conveniently handles converstion to/from wire format. +class NET_EXPORT_PRIVATE SettingsFlagsAndId { + public: + static SettingsFlagsAndId FromWireFormat(int version, uint32 wire); + + SettingsFlagsAndId() : flags_(0), id_(0) {} + + // TODO(hkhalil): restrict to enums instead of free-form ints. + SettingsFlagsAndId(uint8 flags, uint32 id); + + uint32 GetWireFormat(int version) const; + + uint32 id() const { return id_; } + uint8 flags() const { return flags_; } + + private: + static void ConvertFlagsAndIdForSpdy2(uint32* val); + + uint8 flags_; + uint32 id_; +}; + // A datastructure for holding a set of ID/value pairs for a SETTINGS frame. -typedef std::pair<spdy::SettingsFlagsAndId, uint32> SpdySetting; +typedef std::pair<SettingsFlagsAndId, uint32> SpdySetting; typedef std::list<SpdySetting> SpdySettings; // A datastrcture for holding the contents of a CREDENTIAL frame. @@ -62,6 +94,27 @@ struct NET_EXPORT_PRIVATE SpdyCredential { std::string proof; }; +// Scratch space necessary for processing SETTINGS frames. +struct NET_EXPORT_PRIVATE SpdySettingsScratch { + SpdySettingsScratch() { Reset(); } + + void Reset() { + setting_buf_len = 0; + last_setting_id = 0; + } + + // Buffer contains up to one complete key/value pair. + char setting_buf[8]; + + // The amount of the buffer that is filled with valid data. + size_t setting_buf_len; + + // The ID of the last setting that was processed in the current SETTINGS + // frame. Used for detecting out-of-order or duplicate keys within a settings + // frame. Set to 0 before first key/value pair is processed. + uint32 last_setting_id; +}; + // SpdyFramerVisitorInterface is a set of callbacks for the SpdyFramer. // Implement this interface to receive event callbacks as frames are // decoded from the framer. @@ -113,13 +166,13 @@ class NET_EXPORT_PRIVATE SpdyFramerVisitorInterface { // Called when a chunk of payload data for a credential frame is available. // This is called after OnControl() is called with the credential frame // associated with the payload being delivered here. - // |frame_data| A buffer containing the header data chunk received. + // |header_data| A buffer containing the header data chunk received. // |len| The length of the header data buffer. A length of zero indicates // that the header data block has been completely sent. // When this function returns true the visitor indicates that it accepted // all of the data. Returning false indicates that that an unrecoverable // error has occurred, such as bad header data or resource exhaustion. - virtual bool OnCredentialFrameData(const char* frame_data, + virtual bool OnCredentialFrameData(const char* header_data, size_t len) = 0; // Called when a data frame header is received. The frame's data @@ -136,6 +189,10 @@ class NET_EXPORT_PRIVATE SpdyFramerVisitorInterface { virtual void OnStreamFrameData(SpdyStreamId stream_id, const char* data, size_t len) = 0; + + // Called when a complete setting within a SETTINGS frame has been parsed and + // validated. + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0; }; class NET_EXPORT_PRIVATE SpdyFramer { @@ -155,6 +212,7 @@ class NET_EXPORT_PRIVATE SpdyFramer { SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, SPDY_CONTROL_FRAME_HEADER_BLOCK, SPDY_CREDENTIAL_FRAME_PAYLOAD, + SPDY_SETTINGS_FRAME_PAYLOAD, }; // SPDY error codes. @@ -179,8 +237,8 @@ class NET_EXPORT_PRIVATE SpdyFramer { // purposes.) static const size_t kHeaderDataChunkMaxSize; - // Create a new Framer. - SpdyFramer(); + // Create a new Framer, provided a SPDY version. + explicit SpdyFramer(int version); virtual ~SpdyFramer(); // Set callbacks to be called from the framer. A visitor must be set, or @@ -209,31 +267,26 @@ class NET_EXPORT_PRIVATE SpdyFramer { } bool HasError() { return state_ == SPDY_ERROR; } - // Further parsing utilities. - // Given a control frame, parse out a SpdyHeaderBlock. Only - // valid for SYN_STREAM and SYN_REPLY frames. - // Returns true if successfully parsed, false otherwise. - bool ParseHeaderBlock(const SpdyFrame* frame, SpdyHeaderBlock* block); - // Given a buffer containing a decompressed header block in SPDY // serialized format, parse out a SpdyHeaderBlock, putting the results // in the given header block. // Returns true if successfully parsed, false otherwise. - static bool ParseHeaderBlockInBuffer(const char* header_data, - size_t header_length, - SpdyHeaderBlock* block); + bool ParseHeaderBlockInBuffer(const char* header_data, + size_t header_length, + SpdyHeaderBlock* block); // Create a SpdySynStreamControlFrame. // |stream_id| is the id for this stream. // |associated_stream_id| is the associated stream id for this stream. - // |priority| is the priority (0-3) for this stream. + // |priority| is the priority (GetHighestPriority()-GetLowestPriority) for + // this stream. // |flags| is the flags to use with the data. // To mark this frame as the last frame, enable CONTROL_FLAG_FIN. // |compressed| specifies whether the frame should be compressed. // |headers| is the header block to include in the frame. SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id, SpdyStreamId associated_stream_id, - int priority, + SpdyPriority priority, SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers); @@ -249,26 +302,23 @@ class NET_EXPORT_PRIVATE SpdyFramer { bool compressed, const SpdyHeaderBlock* headers); - static SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id, - SpdyStatusCodes status); + SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id, + SpdyStatusCodes status) const; // Creates an instance of SpdySettingsControlFrame. The SETTINGS frame is // used to communicate name/value pairs relevant to the communication channel. - // TODO(mbelshe): add the name/value pairs!! - static SpdySettingsControlFrame* CreateSettings(const SpdySettings& values); - - static SpdyNoOpControlFrame* CreateNopFrame(); + SpdySettingsControlFrame* CreateSettings(const SpdySettings& values) const; // Creates an instance of SpdyPingControlFrame. The unique_id is used to // identify the ping request/response. - static SpdyPingControlFrame* CreatePingFrame(uint32 unique_id); + SpdyPingControlFrame* CreatePingFrame(uint32 unique_id) const; // Creates an instance of SpdyGoAwayControlFrame. The GOAWAY frame is used // prior to the shutting down of the TCP connection, and includes the // stream_id of the last stream the sender of the frame is willing to process // to completion. - static SpdyGoAwayControlFrame* CreateGoAway( - SpdyStreamId last_accepted_stream_id); + SpdyGoAwayControlFrame* CreateGoAway( + SpdyStreamId last_accepted_stream_id) const; // Creates an instance of SpdyHeadersControlFrame. The HEADERS frame is used // for sending additional headers outside of a SYN_STREAM/SYN_REPLY. The @@ -280,15 +330,15 @@ class NET_EXPORT_PRIVATE SpdyFramer { // Creates an instance of SpdyWindowUpdateControlFrame. The WINDOW_UPDATE // frame is used to implement per stream flow control in SPDY. - static SpdyWindowUpdateControlFrame* CreateWindowUpdate( + SpdyWindowUpdateControlFrame* CreateWindowUpdate( SpdyStreamId stream_id, - uint32 delta_window_size); + uint32 delta_window_size) const; // Creates an instance of SpdyCredentialControlFrame. The CREDENTIAL // frame is used to send a client certificate to the server when // request more than one origin are sent over the same SPDY session. - static SpdyCredentialControlFrame* CreateCredentialFrame( - const SpdyCredential& credential); + SpdyCredentialControlFrame* CreateCredentialFrame( + const SpdyCredential& credential) const; // Given a SpdySettingsControlFrame, extract the settings. // Returns true on successful parse, false otherwise. @@ -330,13 +380,6 @@ class NET_EXPORT_PRIVATE SpdyFramer { // On failure, returns NULL. SpdyFrame* CompressFrame(const SpdyFrame& frame); - // Decompresses a SpdyFrame. - // On success, returns a new SpdyFrame with the payload decompressed. - // Compression state is maintained as part of the SpdyFramer. - // Returned frame must be freed with "delete". - // On failure, returns NULL. - SpdyFrame* DecompressFrame(const SpdyFrame& frame); - // Create a copy of a frame. // Returned frame must be freed with "delete". SpdyFrame* DuplicateFrame(const SpdyFrame& frame); @@ -373,18 +416,44 @@ class NET_EXPORT_PRIVATE SpdyFramer { static const char* StatusCodeToString(int status_code); static const char* ControlTypeToString(SpdyControlType type); - static void set_protocol_version(int version) { spdy_version_= version; } - static int protocol_version() { return spdy_version_; } + // TODO(hkhalil): Remove SpdyFramer::set_protocol_version() + void set_protocol_version(int version) { spdy_version_= version; } + int protocol_version() const { return spdy_version_; } + + bool probable_http_response() { return probable_http_response_; } - // Export the compression dictionary - static const char kDictionary[]; - static const int kDictionarySize; + SpdyPriority GetLowestPriority() const { return spdy_version_ < 3 ? 3 : 7; } + SpdyPriority GetHighestPriority() const { return 0; } protected: - FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HeaderCompression); - FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ExpandBuffer_HeapSmash); - FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HugeHeaderBlock); - FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, BasicCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, ControlFrameSizesAreValidated); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, HeaderCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, DecompressUncompressedFrame); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, ExpandBuffer_HeapSmash); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, HugeHeaderBlock); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, UnclosedStreamDataCompressors); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, + UnclosedStreamDataCompressorsOneByteAtATime); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, + UncompressLargerThanFrameBufferInitialSize); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, ReadLargeSettingsFrame); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy2Test, + ReadLargeSettingsFrameInSmallChunks); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, BasicCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, ControlFrameSizesAreValidated); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, HeaderCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, DecompressUncompressedFrame); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, ExpandBuffer_HeapSmash); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, HugeHeaderBlock); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, UnclosedStreamDataCompressors); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, + UnclosedStreamDataCompressorsOneByteAtATime); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, + UncompressLargerThanFrameBufferInitialSize); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, ReadLargeSettingsFrame); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerSpdy3Test, + ReadLargeSettingsFrameInSmallChunks); friend class net::HttpNetworkLayer; // This is temporary for the server. friend class net::HttpNetworkTransactionTest; friend class net::HttpProxyClientSocketPoolTest; @@ -395,45 +464,37 @@ class NET_EXPORT_PRIVATE SpdyFramer { friend class net::SpdyStreamTest; friend class net::SpdyWebSocketStreamTest; friend class net::WebSocketJobTest; - friend class test::TestSpdyVisitor; - friend void test::FramerSetEnableCompressionHelper(SpdyFramer* framer, - bool compress); + friend class test_spdy2::TestSpdyVisitor; + friend class test_spdy3::TestSpdyVisitor; private: typedef std::map<SpdyStreamId, z_stream*> CompressorMap; - // Internal breakout from ProcessInput. Returns the number of bytes + // Internal breakouts from ProcessInput. Each returns the number of bytes // consumed from the data. size_t ProcessCommonHeader(const char* data, size_t len); - void ProcessControlFrameHeader(); size_t ProcessControlFramePayload(const char* data, size_t len); size_t ProcessCredentialFramePayload(const char* data, size_t len); size_t ProcessControlFrameBeforeHeaderBlock(const char* data, size_t len); size_t ProcessControlFrameHeaderBlock(const char* data, size_t len); + size_t ProcessSettingsFramePayload(const char* data, size_t len); size_t ProcessDataFramePayload(const char* data, size_t len); + // Helpers for above internal breakouts from ProcessInput. + void ProcessControlFrameHeader(); + bool ProcessSetting(const char* data); // Always passed exactly 8 bytes. + // Get (and lazily initialize) the ZLib state. z_stream* GetHeaderCompressor(); z_stream* GetHeaderDecompressor(); - z_stream* GetStreamCompressor(SpdyStreamId id); z_stream* GetStreamDecompressor(SpdyStreamId id); // Compression helpers SpdyControlFrame* CompressControlFrame(const SpdyControlFrame& frame); - SpdyDataFrame* CompressDataFrame(const SpdyDataFrame& frame); - SpdyControlFrame* DecompressControlFrame(const SpdyControlFrame& frame); - SpdyDataFrame* DecompressDataFrame(const SpdyDataFrame& frame); - SpdyFrame* CompressFrameWithZStream(const SpdyFrame& frame, - z_stream* compressor); - SpdyFrame* DecompressFrameWithZStream(const SpdyFrame& frame, - z_stream* decompressor); void CleanupCompressorForStream(SpdyStreamId id); void CleanupDecompressorForStream(SpdyStreamId id); void CleanupStreamCompressorsAndDecompressors(); - // Not used (yet) - size_t BytesSafeToRead() const; - // Deliver the given control frame's compressed headers block to the visitor // in decompressed form, in chunks. Returns true if the visitor has // accepted all of the chunks. @@ -458,6 +519,13 @@ class NET_EXPORT_PRIVATE SpdyFramer { size_t UpdateCurrentFrameBuffer(const char** data, size_t* len, size_t max_bytes); + // Retrieve serialized length of SpdyHeaderBlock. + size_t GetSerializedLength(const SpdyHeaderBlock* headers) const; + + // Serializes a SpdyHeaderBlock. + void WriteHeaderBlock(SpdyFrameBuilder* frame, + const SpdyHeaderBlock* headers) const; + // Set the error code and moves the framer into the error state. void set_error(SpdyError error); @@ -486,19 +554,14 @@ class NET_EXPORT_PRIVATE SpdyFramer { // Since this is only used for control frame headers, the maximum control // frame header size (18B) is sufficient; all remaining control frame data is // streamed to the visitor. - // In addition to the maximum control frame header size, we account for any - // LOAS checksumming (16B) that may occur in the VTL case. // TODO(hkhalil): Remove post code-yellow once streamed inflate is properly // implemented. - static const size_t kUncompressedControlFrameBufferInitialSize = 18 + 16; + static size_t kUncompressedControlFrameBufferInitialSize; // The maximum size of the control frame buffer that we support. // TODO(mbelshe): We should make this stream-based so there are no limits. static size_t kControlFrameBufferMaxSize; - // The size of the buffer into which compressed frames are inflated. - static const size_t kDecompressionBufferSize = 8 * 1024; - SpdyState state_; SpdyError error_code_; size_t remaining_data_; @@ -516,6 +579,11 @@ class NET_EXPORT_PRIVATE SpdyFramer { size_t current_frame_len_; // Number of bytes read into the current_frame_. size_t current_frame_capacity_; + // Scratch space for handling SETTINGS frames. + // TODO(hkhalil): Unify memory for this scratch space with + // current_frame_buffer_. + SpdySettingsScratch settings_scratch_; + bool validate_control_frame_sizes_; bool enable_compression_; // Controls all compression // SPDY header compressors. @@ -530,8 +598,24 @@ class NET_EXPORT_PRIVATE SpdyFramer { std::string display_protocol_; + int spdy_version_; + + // Tracks if we've ever gotten far enough in framing to see a control frame of + // type SYN_STREAM or SYN_REPLY. + // + // If we ever get something which looks like a data frame before we've had a + // SYN, we explicitly check to see if it looks like we got an HTTP response to + // a SPDY request. This boolean lets us do that. + bool syn_frame_processed_; + + // If we ever get a data frame before a SYN frame, we check to see if it + // starts with HTTP. If it does, we likely have an HTTP response. This + // isn't guaranteed though: we could have gotten a settings frame and then + // corrupt data that just looks like HTTP, but deterministic checking requires + // a lot more state. + bool probable_http_response_; + static bool compression_default_; - static int spdy_version_; }; } // namespace spdy diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_spdy2_test.cc index e2590e0..f6bd35d 100644 --- a/net/spdy/spdy_framer_test.cc +++ b/net/spdy/spdy_framer_spdy2_test.cc @@ -11,13 +11,152 @@ #include "net/spdy/spdy_frame_builder.h" #include "testing/platform_test.h" +namespace { + +// Default SPDY version for unit tests. +const int SPDY_VERSION_FOR_TESTS = 2; + +// The current default spdy version as a byte to be included in const +// byte arrays below. Name choice is unfortunate, but better to fit to four +// bytes than not. +unsigned char kVer = SPDY_VERSION_FOR_TESTS; + +spdy::SpdySetting SpdySettingFromWireFormat(uint32 key, uint32 value) { + return spdy::SpdySetting( + spdy::SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, key), + value); +} + +} // namespace + namespace spdy { -namespace test { +namespace test_spdy2 { + +static const size_t kMaxDecompressedSize = 1024; + +class SpdyFramerTestUtil { + public: + // Decompress a single frame using the decompression context held by + // the SpdyFramer. The implemention will CHECK fail if the input is anything + // other than a single, well-formed compressed frame. + // + // Returns a new decompressed SpdyFrame. + template<class SpdyFrameType> static SpdyFrame* DecompressFrame( + SpdyFramer* framer, const SpdyFrameType& frame) { + DecompressionVisitor visitor; + framer->set_visitor(&visitor); + size_t input_size = frame.length() + SpdyFrame::kHeaderSize; + CHECK_EQ(input_size, framer->ProcessInput(frame.data(), input_size)); + CHECK_EQ(SpdyFramer::SPDY_RESET, framer->state()); + framer->set_visitor(NULL); + + char* buffer = visitor.ReleaseBuffer(); + CHECK(buffer); + SpdyFrame* decompressed_frame = new SpdyFrame(buffer, true); + decompressed_frame->set_length(visitor.size() - SpdyFrame::kHeaderSize); + return decompressed_frame; + } + + class DecompressionVisitor : public SpdyFramerVisitorInterface { + public: + DecompressionVisitor() + : buffer_(NULL), size_(0), finished_(false), allow_data_frames_(false) { + } + + virtual void OnControl(const SpdyControlFrame* frame) { + CHECK(frame->has_header_block()); + CHECK(!buffer_.get()); + CHECK_EQ(size_, 0u); + CHECK(!finished_); + + int32 control_frame_header_size = 0; + switch (frame->type()) { + case SYN_STREAM: + control_frame_header_size = SpdySynStreamControlFrame::size(); + break; + case SYN_REPLY: + control_frame_header_size = SpdySynReplyControlFrame::size(); + break; + case HEADERS: + control_frame_header_size = SpdyHeadersControlFrame::size(); + break; + default: + LOG(FATAL); + return; + } + + // Allocate space for the frame, and the copy header over. + buffer_.reset(new char[kMaxDecompressedSize]); + memcpy(buffer_.get(), frame->data(), control_frame_header_size); + size_ += control_frame_header_size; + } + + virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) { + CHECK(buffer_.get() != NULL); + CHECK_GE(kMaxDecompressedSize, size_ + len); + CHECK(!finished_); + if (len != 0) { + memcpy(buffer_.get() + size_, header_data, len); + size_ += len; + } else { + // Done. + finished_ = true; + } + return true; + } + + virtual bool OnCredentialFrameData(const char* header_data, + size_t len) { + LOG(FATAL) << "Unexpected CREDENTIAL Frame"; + return false; + } + + virtual void OnError(SpdyFramer* framer) { LOG(FATAL); } + virtual void OnDataFrameHeader(const SpdyDataFrame* frame) { + // For most tests, this class does not expect to see OnDataFrameHeader + // calls. Individual tests can override this if they need to. + if (!allow_data_frames_) { + LOG(FATAL) << "Unexpected data frame header"; + } + } + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) { + LOG(FATAL); + } + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) { + LOG(FATAL); + } + + char* ReleaseBuffer() { + CHECK(finished_); + return buffer_.release(); + } + + size_t size() const { + CHECK(finished_); + return size_; + } + void set_allow_data_frames(bool allow) { allow_data_frames_ = allow; } + + private: + scoped_array<char> buffer_; + size_t size_; + bool finished_; + bool allow_data_frames_; + + DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor); + }; + + DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil); +}; std::string HexDumpWithMarks(const unsigned char* data, int length, const bool* marks, int mark_length) { - static const char kHexChars[] = "0123456789ABCDEF"; + static const char kHexChars[] = "0123456789abcdef"; static const int kColumns = 4; const int kSizeLimit = 1024; @@ -58,8 +197,8 @@ void CompareCharArraysWithHexError( const int actual_len, const unsigned char* expected, const int expected_len) { - const int min_len = actual_len > expected_len ? expected_len : actual_len; - const int max_len = actual_len > expected_len ? actual_len : expected_len; + const int min_len = std::min(actual_len, expected_len); + const int max_len = std::max(actual_len, expected_len); scoped_array<bool> marks(new bool[max_len]); bool identical = (actual_len == expected_len); for (int i = 0; i < min_len; ++i) { @@ -89,13 +228,16 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { static const size_t kDefaultCredentialBufferSize = 16 * 1024; TestSpdyVisitor() - : use_compression_(false), + : framer_(kVer), + use_compression_(false), error_count_(0), syn_frame_count_(0), syn_reply_frame_count_(0), headers_frame_count_(0), goaway_count_(0), credential_count_(0), + settings_frame_count_(0), + setting_count_(0), data_bytes_(0), fin_frame_count_(0), fin_flag_count_(0), @@ -166,6 +308,9 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { case CREDENTIAL: credential_count_++; break; + case SETTINGS: + settings_frame_count_++; + break; default: DLOG(FATAL); // Error! } @@ -173,6 +318,10 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { ++fin_flag_count_; } + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) { + setting_count_++; + } + bool OnControlFrameHeaderData(SpdyStreamId stream_id, const char* header_data, size_t len) { @@ -182,7 +331,7 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { ++zero_length_control_frame_header_data_count_; // Indicates end-of-header-block. CHECK(header_buffer_valid_); - bool parsed_headers = SpdyFramer::ParseHeaderBlockInBuffer( + bool parsed_headers = framer_.ParseHeaderBlockInBuffer( header_buffer_.get(), header_buffer_length_, &headers_); DCHECK(parsed_headers); return true; @@ -197,7 +346,8 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { return true; } - bool OnCredentialFrameData(const char* credential_data, size_t len) { + bool OnCredentialFrameData(const char* credential_data, + size_t len) { if (len == 0) { if (!framer_.ParseCredentialData(credential_buffer_.get(), credential_buffer_length_, @@ -272,6 +422,8 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { int headers_frame_count_; int goaway_count_; int credential_count_; + int settings_frame_count_; + int setting_count_; int data_bytes_; int fin_frame_count_; // The count of RST_STREAM type frames received. int fin_flag_count_; // The count of frames with the FIN flag set. @@ -297,7 +449,7 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { SpdyCredential credential_; }; -} // namespace test +} // namespace test_spdy2 } // namespace spdy @@ -315,12 +467,13 @@ using spdy::CONTROL_FLAG_NONE; using spdy::DATA_FLAG_COMPRESSED; using spdy::DATA_FLAG_FIN; using spdy::SYN_STREAM; -using spdy::test::CompareCharArraysWithHexError; -using spdy::test::TestSpdyVisitor; +using spdy::test_spdy2::CompareCharArraysWithHexError; +using spdy::test_spdy2::SpdyFramerTestUtil; +using spdy::test_spdy2::TestSpdyVisitor; namespace spdy { -TEST(SpdyFrameBuilderTest, WriteLimits) { +TEST(SpdyFrameBuilderSpdy2Test, WriteLimits) { SpdyFrameBuilder builder(kLengthMask + 4); // length field should fail. EXPECT_FALSE(builder.WriteBytes(reinterpret_cast<const void*>(0x1), @@ -335,7 +488,7 @@ TEST(SpdyFrameBuilderTest, WriteLimits) { EXPECT_EQ(4 + kLengthMask, static_cast<unsigned>(builder.length())); } -class SpdyFramerTest : public PlatformTest { +class SpdyFramerSpdy2Test : public PlatformTest { public: virtual void TearDown() {} @@ -382,11 +535,11 @@ class SpdyFramerTest : public PlatformTest { // Test that we can encode and decode a SpdyHeaderBlock in serialized form. -TEST_F(SpdyFramerTest, HeaderBlockInBuffer) { +TEST_F(SpdyFramerSpdy2Test, HeaderBlockInBuffer) { SpdyHeaderBlock headers; headers["alpha"] = "beta"; headers["gamma"] = "charlie"; - SpdyFramer framer; + SpdyFramer framer(kVer); // Encode the header block into a SynStream frame. scoped_ptr<SpdySynStreamControlFrame> frame( @@ -405,11 +558,11 @@ TEST_F(SpdyFramerTest, HeaderBlockInBuffer) { } // Test that if there's not a full frame, we fail to parse it. -TEST_F(SpdyFramerTest, UndersizedHeaderBlockInBuffer) { +TEST_F(SpdyFramerSpdy2Test, UndersizedHeaderBlockInBuffer) { SpdyHeaderBlock headers; headers["alpha"] = "beta"; headers["gamma"] = "charlie"; - SpdyFramer framer; + SpdyFramer framer(kVer); // Encode the header block into a SynStream frame. scoped_ptr<SpdySynStreamControlFrame> frame( @@ -424,7 +577,7 @@ TEST_F(SpdyFramerTest, UndersizedHeaderBlockInBuffer) { &new_headers)); } -TEST_F(SpdyFramerTest, OutOfOrderHeaders) { +TEST_F(SpdyFramerSpdy2Test, OutOfOrderHeaders) { // Frame builder with plentiful buffer size. SpdyFrameBuilder frame(1024); @@ -435,12 +588,19 @@ TEST_F(SpdyFramerTest, OutOfOrderHeaders) { frame.WriteUInt32(0); // Associated stream id frame.WriteUInt16(0); // Priority. - frame.WriteUInt16(2); // Number of headers. - SpdyHeaderBlock::iterator it; - frame.WriteString("gamma"); - frame.WriteString("gamma"); - frame.WriteString("alpha"); - frame.WriteString("alpha"); + if (SPDY_VERSION_FOR_TESTS < 3) { + frame.WriteUInt16(2); // Number of headers. + frame.WriteString("gamma"); + frame.WriteString("gamma"); + frame.WriteString("alpha"); + frame.WriteString("alpha"); + } else { + frame.WriteUInt32(2); // Number of headers. + frame.WriteStringPiece32("gamma"); + frame.WriteStringPiece32("gamma"); + frame.WriteStringPiece32("alpha"); + frame.WriteStringPiece32("alpha"); + } // write the length frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize); @@ -449,14 +609,85 @@ TEST_F(SpdyFramerTest, OutOfOrderHeaders) { SpdySynStreamControlFrame syn_frame(control_frame->data(), false); std::string serialized_headers(syn_frame.header_block(), syn_frame.header_block_len()); - SpdyFramer framer; + SpdyFramer framer(kVer); framer.set_enable_compression(false); EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), serialized_headers.size(), &new_headers)); } -TEST_F(SpdyFramerTest, DuplicateHeader) { +TEST_F(SpdyFramerSpdy2Test, CreateCredential) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "CREDENTIAL frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x0A, + 0x00, 0x00, 0x00, 0x33, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerSpdy2Test, ParseCredentialFrameData) { + SpdyFramer framer(kVer); + + { + unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x0A, + 0x00, 0x00, 0x00, 0x33, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + SpdyCredentialControlFrame frame(reinterpret_cast<char*>(kFrameData), + false); + SpdyCredential credential; + EXPECT_TRUE(SpdyFramer::ParseCredentialData(frame.payload(), frame.length(), + &credential)); + EXPECT_EQ(3u, credential.slot); + EXPECT_EQ("proof", credential.proof); + EXPECT_EQ("a cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_EQ("another cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_EQ("final cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_TRUE(credential.certs.empty()); + } +} + +TEST_F(SpdyFramerSpdy2Test, DuplicateHeader) { // Frame builder with plentiful buffer size. SpdyFrameBuilder frame(1024); @@ -467,12 +698,19 @@ TEST_F(SpdyFramerTest, DuplicateHeader) { frame.WriteUInt32(0); // associated stream id frame.WriteUInt16(0); // Priority. - frame.WriteUInt16(2); // Number of headers. - SpdyHeaderBlock::iterator it; - frame.WriteString("name"); - frame.WriteString("value1"); - frame.WriteString("name"); - frame.WriteString("value2"); + if (SPDY_VERSION_FOR_TESTS < 3) { + frame.WriteUInt16(2); // Number of headers. + frame.WriteString("name"); + frame.WriteString("value1"); + frame.WriteString("name"); + frame.WriteString("value2"); + } else { + frame.WriteUInt32(2); // Number of headers. + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32("value1"); + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32("value2"); + } // write the length frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize); @@ -481,7 +719,7 @@ TEST_F(SpdyFramerTest, DuplicateHeader) { SpdySynStreamControlFrame syn_frame(control_frame->data(), false); std::string serialized_headers(syn_frame.header_block(), syn_frame.header_block_len()); - SpdyFramer framer; + SpdyFramer framer(kVer); framer.set_enable_compression(false); // This should fail because duplicate headers are verboten by the spec. EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), @@ -489,7 +727,7 @@ TEST_F(SpdyFramerTest, DuplicateHeader) { &new_headers)); } -TEST_F(SpdyFramerTest, MultiValueHeader) { +TEST_F(SpdyFramerSpdy2Test, MultiValueHeader) { // Frame builder with plentiful buffer size. SpdyFrameBuilder frame(1024); @@ -500,11 +738,16 @@ TEST_F(SpdyFramerTest, MultiValueHeader) { frame.WriteUInt32(0); // associated stream id frame.WriteUInt16(0); // Priority. - frame.WriteUInt16(1); // Number of headers. - SpdyHeaderBlock::iterator it; - frame.WriteString("name"); std::string value("value1\0value2"); - frame.WriteString(value); + if (SPDY_VERSION_FOR_TESTS < 3) { + frame.WriteUInt16(1); // Number of headers. + frame.WriteString("name"); + frame.WriteString(value); + } else { + frame.WriteUInt32(1); // Number of headers. + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32(value); + } // write the length frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize); @@ -513,7 +756,7 @@ TEST_F(SpdyFramerTest, MultiValueHeader) { SpdySynStreamControlFrame syn_frame(control_frame->data(), false); std::string serialized_headers(syn_frame.header_block(), syn_frame.header_block_len()); - SpdyFramer framer; + SpdyFramer framer(kVer); framer.set_enable_compression(false); EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), serialized_headers.size(), @@ -522,7 +765,7 @@ TEST_F(SpdyFramerTest, MultiValueHeader) { EXPECT_EQ(value, new_headers.find("name")->second); } -TEST_F(SpdyFramerTest, BasicCompression) { +TEST_F(SpdyFramerSpdy2Test, BasicCompression) { SpdyHeaderBlock headers; headers["server"] = "SpdyServer 1.0"; headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; @@ -531,7 +774,7 @@ TEST_F(SpdyFramerTest, BasicCompression) { headers["content-type"] = "text/html"; headers["content-length"] = "12"; - SpdyFramer framer; + SpdyFramer framer(kVer); framer.set_enable_compression(true); scoped_ptr<SpdySynStreamControlFrame> frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, @@ -544,10 +787,12 @@ TEST_F(SpdyFramerTest, BasicCompression) { EXPECT_LE(frame2->length(), frame1->length()); // Decompress the first frame - scoped_ptr<SpdyFrame> frame3(framer.DecompressFrame(*frame1.get())); + scoped_ptr<SpdyFrame> frame3(SpdyFramerTestUtil::DecompressFrame( + &framer, *frame1.get())); // Decompress the second frame - scoped_ptr<SpdyFrame> frame4(framer.DecompressFrame(*frame2.get())); + scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame( + &framer, *frame2.get())); // Expect frames 3 & 4 to be the same. EXPECT_EQ(0, @@ -566,30 +811,9 @@ TEST_F(SpdyFramerTest, BasicCompression) { SpdyFrame::kHeaderSize + uncompressed_frame->length())); } -TEST_F(SpdyFramerTest, DecompressUncompressedFrame) { - SpdyHeaderBlock headers; - headers["server"] = "SpdyServer 1.0"; - headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; - headers["status"] = "200"; - headers["version"] = "HTTP/1.1"; - headers["content-type"] = "text/html"; - headers["content-length"] = "12"; - - SpdyFramer framer; - framer.set_enable_compression(true); - scoped_ptr<SpdySynStreamControlFrame> - frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, false, - &headers)); - - // Decompress the frame - scoped_ptr<SpdyFrame> frame2(framer.DecompressFrame(*frame1.get())); - - EXPECT_EQ(NULL, frame2.get()); -} - -TEST_F(SpdyFramerTest, Basic) { - const unsigned char input[] = { - 0x80, 0x02, 0x00, 0x01, // SYN Stream #1 +TEST_F(SpdyFramerSpdy2Test, Basic) { + const unsigned char kV2Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, @@ -597,7 +821,7 @@ TEST_F(SpdyFramerTest, Basic) { 0x00, 0x02, 'h', 'h', 0x00, 0x02, 'v', 'v', - 0x80, 0x02, 0x00, 0x08, // HEADERS on Stream #1 + 0x80, kVer, 0x00, 0x08, // HEADERS on Stream #1 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, @@ -608,11 +832,11 @@ TEST_F(SpdyFramerTest, Basic) { 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 0x00, 0x00, 0x00, 0x0c, - 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, - 0x80, 0x02, 0x00, 0x01, // SYN Stream #3 + 0x80, kVer, 0x00, 0x01, // SYN Stream #3 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, @@ -620,14 +844,14 @@ TEST_F(SpdyFramerTest, Basic) { 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 0x00, 0x00, 0x00, 0x08, - 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 0x00, 0x00, 0x00, 0x04, - 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, - 0x80, 0x02, 0x00, 0x03, // RST_STREAM on Stream #1 + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #1 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, @@ -635,14 +859,77 @@ TEST_F(SpdyFramerTest, Basic) { 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 0x00, 0x00, 0x00, 0x00, - 0x80, 0x02, 0x00, 0x03, // RST_STREAM on Stream #3 + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + }; + + const unsigned char kV3Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, kVer, 0x00, 0x08, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x22, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x02, 'h', '2', + 0x00, 0x00, 0x00, 0x02, + 'v', '2', 0x00, 0x00, + 0x00, 0x02, 'h', '3', + 0x00, 0x00, 0x00, 0x02, + 'v', '3', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, kVer, 0x00, 0x01, // SYN Stream #3 + 0x00, 0x00, 0x00, 0x0e, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #1 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x00, + + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #3 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, }; TestSpdyVisitor visitor; - visitor.SimulateInFramer(input, sizeof(input)); + if (SPDY_VERSION_FOR_TESTS < 3) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(2, visitor.syn_frame_count_); @@ -656,9 +943,9 @@ TEST_F(SpdyFramerTest, Basic) { } // Test that the FIN flag on a data frame signifies EOF. -TEST_F(SpdyFramerTest, FinOnDataFrame) { - const unsigned char input[] = { - 0x80, 0x02, 0x00, 0x01, // SYN Stream #1 +TEST_F(SpdyFramerSpdy2Test, FinOnDataFrame) { + const unsigned char kV2Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, @@ -666,7 +953,7 @@ TEST_F(SpdyFramerTest, FinOnDataFrame) { 0x00, 0x02, 'h', 'h', 0x00, 0x02, 'v', 'v', - 0x80, 0x02, 0x00, 0x02, // SYN REPLY Stream #1 + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, @@ -683,9 +970,43 @@ TEST_F(SpdyFramerTest, FinOnDataFrame) { 0x01, 0x00, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef, }; + const unsigned char kV3Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 + 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'a', 'a', + 0x00, 0x00, 0x00, 0x02, + 'b', 'b', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF + 0x01, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + }; TestSpdyVisitor visitor; - visitor.SimulateInFramer(input, sizeof(input)); + if (SPDY_VERSION_FOR_TESTS < 3) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(1, visitor.syn_frame_count_); @@ -699,9 +1020,9 @@ TEST_F(SpdyFramerTest, FinOnDataFrame) { } // Test that the FIN flag on a SYN reply frame signifies EOF. -TEST_F(SpdyFramerTest, FinOnSynReplyFrame) { - const unsigned char input[] = { - 0x80, 0x02, 0x00, 0x01, // SYN Stream #1 +TEST_F(SpdyFramerSpdy2Test, FinOnSynReplyFrame) { + const unsigned char kV2Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, @@ -709,7 +1030,7 @@ TEST_F(SpdyFramerTest, FinOnSynReplyFrame) { 0x00, 0x02, 'h', 'h', 0x00, 0x02, 'v', 'v', - 0x80, 0x02, 0x00, 0x02, // SYN REPLY Stream #1 + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, @@ -717,9 +1038,34 @@ TEST_F(SpdyFramerTest, FinOnSynReplyFrame) { 0x00, 0x02, 'a', 'a', 0x00, 0x02, 'b', 'b', }; + const unsigned char kV3Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 + 0x01, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'a', 'a', + 0x00, 0x00, 0x00, 0x02, + 'b', 'b', + }; TestSpdyVisitor visitor; - visitor.SimulateInFramer(input, sizeof(input)); + if (SPDY_VERSION_FOR_TESTS < 3) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(1, visitor.syn_frame_count_); @@ -732,9 +1078,9 @@ TEST_F(SpdyFramerTest, FinOnSynReplyFrame) { EXPECT_EQ(0, visitor.data_frame_count_); } -TEST_F(SpdyFramerTest, HeaderCompression) { - SpdyFramer send_framer; - SpdyFramer recv_framer; +TEST_F(SpdyFramerSpdy2Test, HeaderCompression) { + SpdyFramer send_framer(kVer); + SpdyFramer recv_framer(kVer); send_framer.set_enable_compression(true); recv_framer.set_enable_compression(true); @@ -751,33 +1097,30 @@ TEST_F(SpdyFramerTest, HeaderCompression) { block[kHeader1] = kValue1; block[kHeader2] = kValue2; SpdyControlFlags flags(CONTROL_FLAG_NONE); - scoped_ptr<spdy::SpdyFrame> syn_frame_1( + scoped_ptr<SpdySynStreamControlFrame> syn_frame_1( send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); EXPECT_TRUE(syn_frame_1.get() != NULL); // SYN_STREAM #2 block[kHeader3] = kValue3; - scoped_ptr<spdy::SpdyFrame> syn_frame_2( + scoped_ptr<SpdySynStreamControlFrame> syn_frame_2( send_framer.CreateSynStream(3, 0, 0, flags, true, &block)); EXPECT_TRUE(syn_frame_2.get() != NULL); // Now start decompressing scoped_ptr<SpdyFrame> decompressed; - scoped_ptr<SpdyFrame> decompressed_syn_frame; - SpdySynStreamControlFrame* syn_frame; + scoped_ptr<SpdySynStreamControlFrame> syn_frame; scoped_ptr<std::string> serialized_headers; SpdyHeaderBlock decompressed_headers; // Decompress SYN_STREAM #1 - decompressed.reset(recv_framer.DecompressFrame(*syn_frame_1.get())); + decompressed.reset(SpdyFramerTestUtil::DecompressFrame( + &recv_framer, *syn_frame_1.get())); EXPECT_TRUE(decompressed.get() != NULL); EXPECT_TRUE(decompressed->is_control_frame()); EXPECT_EQ(SYN_STREAM, reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type()); - decompressed_syn_frame.reset( - new SpdySynStreamControlFrame(decompressed->data(), false)); - syn_frame = reinterpret_cast<SpdySynStreamControlFrame*>( - decompressed_syn_frame.get()); + syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false)); serialized_headers.reset(new std::string(syn_frame->header_block(), syn_frame->header_block_len())); EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(), @@ -788,15 +1131,13 @@ TEST_F(SpdyFramerTest, HeaderCompression) { EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); // Decompress SYN_STREAM #2 - decompressed.reset(recv_framer.DecompressFrame(*syn_frame_2.get())); + decompressed.reset(SpdyFramerTestUtil::DecompressFrame( + &recv_framer, *syn_frame_2.get())); EXPECT_TRUE(decompressed.get() != NULL); EXPECT_TRUE(decompressed->is_control_frame()); EXPECT_EQ(SYN_STREAM, reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type()); - decompressed_syn_frame.reset( - new SpdySynStreamControlFrame(decompressed->data(), false)); - syn_frame = reinterpret_cast<SpdySynStreamControlFrame*>( - decompressed_syn_frame.get()); + syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false)); serialized_headers.reset(new std::string(syn_frame->header_block(), syn_frame->header_block_len())); decompressed_headers.clear(); @@ -816,8 +1157,8 @@ TEST_F(SpdyFramerTest, HeaderCompression) { } // Verify we don't leak when we leave streams unclosed -TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { - SpdyFramer send_framer; +TEST_F(SpdyFramerSpdy2Test, UnclosedStreamDataCompressors) { + SpdyFramer send_framer(kVer); send_framer.set_enable_compression(true); @@ -830,7 +1171,7 @@ TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { block[kHeader1] = kValue1; block[kHeader2] = kValue2; SpdyControlFlags flags(CONTROL_FLAG_NONE); - scoped_ptr<spdy::SpdyFrame> syn_frame( + scoped_ptr<SpdyFrame> syn_frame( send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); EXPECT_TRUE(syn_frame.get() != NULL); @@ -838,7 +1179,7 @@ TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { scoped_ptr<SpdyFrame> send_frame( send_framer.CreateDataFrame( 1, bytes, arraysize(bytes), - DATA_FLAG_FIN)); + static_cast<SpdyDataFlags>(DATA_FLAG_FIN))); EXPECT_TRUE(send_frame.get() != NULL); // Run the inputs through the framer. @@ -867,12 +1208,76 @@ TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { EXPECT_EQ(0, send_framer.num_stream_decompressors()); } -TEST_F(SpdyFramerTest, WindowUpdateFrame) { +// Verify we can decompress the stream even if handed over to the +// framer 1 byte at a time. +TEST_F(SpdyFramerSpdy2Test, UnclosedStreamDataCompressorsOneByteAtATime) { + SpdyFramer send_framer(kVer); + + send_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + scoped_ptr<SpdyFrame> syn_frame( + send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); + EXPECT_TRUE(syn_frame.get() != NULL); + + const char bytes[] = "this is a test test test test test!"; + scoped_ptr<SpdyFrame> send_frame( + send_framer.CreateDataFrame( + 1, bytes, arraysize(bytes), + static_cast<SpdyDataFlags>(DATA_FLAG_FIN))); + EXPECT_TRUE(send_frame.get() != NULL); + + // Run the inputs through the framer. + TestSpdyVisitor visitor; + visitor.use_compression_ = true; + const unsigned char* data; + data = reinterpret_cast<const unsigned char*>(syn_frame->data()); + for (size_t idx = 0; + idx < syn_frame->length() + SpdyFrame::kHeaderSize; + ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + data = reinterpret_cast<const unsigned char*>(send_frame->data()); + for (size_t idx = 0; + idx < send_frame->length() + SpdyFrame::kHeaderSize; + ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_)); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.zero_length_data_frame_count_); + EXPECT_EQ(1, visitor.data_frame_count_); + + // We closed the streams, so all compressors should be down. + EXPECT_EQ(0, visitor.framer_.num_stream_compressors()); + EXPECT_EQ(0, visitor.framer_.num_stream_decompressors()); + EXPECT_EQ(0, send_framer.num_stream_compressors()); + EXPECT_EQ(0, send_framer.num_stream_decompressors()); +} + +TEST_F(SpdyFramerSpdy2Test, WindowUpdateFrame) { + SpdyFramer framer(kVer); scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame( - SpdyFramer::CreateWindowUpdate(1, 0x12345678)); + framer.CreateWindowUpdate(1, 0x12345678)); const unsigned char expected_data_frame[] = { - 0x80, 0x02, 0x00, 0x09, + 0x80, kVer, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x12, 0x34, 0x56, 0x78 @@ -883,8 +1288,8 @@ TEST_F(SpdyFramerTest, WindowUpdateFrame) { memcmp(window_update_frame->data(), expected_data_frame, 16)); } -TEST_F(SpdyFramerTest, CreateDataFrame) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateDataFrame) { + SpdyFramer framer(kVer); { const char kDescription[] = "'hello' data frame, no FIN"; @@ -969,8 +1374,8 @@ TEST_F(SpdyFramerTest, CreateDataFrame) { } } -TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateSynStreamUncompressed) { + SpdyFramer framer(kVer); framer.set_enable_compression(false); { @@ -980,24 +1385,43 @@ TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) { headers["bar"] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x01, + const unsigned char kPri = + (SPDY_VERSION_FOR_TESTS != 2) ? 0xE0 : 0xC0; + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0xC0, 0x00, 0x00, 0x02, + kPri, 0x00, 0x00, 0x02, 0x00, 0x03, 'b', 'a', 'r', 0x00, 0x03, 'f', 'o', 'o', 0x00, 0x03, 'f', 'o', 'o', 0x00, 0x03, 'b', 'a', 'r' }; - scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( - 1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE, - false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); - EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x2a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + kPri, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'b', + 'a', 'r' + }; + scoped_ptr<SpdySynStreamControlFrame> frame(framer.CreateSynStream( + 1, 0, framer.GetLowestPriority(), CONTROL_FLAG_NONE, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get())); } { @@ -1009,8 +1433,8 @@ TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) { headers[""] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x01, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, 0x01, 0x00, 0x00, 0x1D, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, @@ -1021,58 +1445,97 @@ TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) { 0x00, 0x03, 'b', 'a', 'r' }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x27, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r' + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( - 0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN, + 0x7fffffff, 0x7fffffff, framer.GetHighestPriority(), CONTROL_FLAG_FIN, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } { const char kDescription[] = - "SYN_STREAM frame with a 0-length header val, highest pri, FIN, " + "SYN_STREAM frame with a 0-length header val, high pri, FIN, " "max stream ID"; SpdyHeaderBlock headers; headers["bar"] = "foo"; headers["foo"] = ""; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x01, + const unsigned char kPri = + (SPDY_VERSION_FOR_TESTS != 2) ? 0x20 : 0x40; + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, 0x01, 0x00, 0x00, 0x1D, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x02, + kPri, 0x00, 0x00, 0x02, 0x00, 0x03, 'b', 'a', 'r', 0x00, 0x03, 'f', 'o', 'o', 0x00, 0x03, 'f', 'o', 'o', 0x00, 0x00 }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x27, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + kPri, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x00 + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( - 0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN, - false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + 0x7fffffff, 0x7fffffff, 1, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } } -TEST_F(SpdyFramerTest, CreateSynStreamCompressed) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateSynStreamCompressed) { + SpdyFramer framer(kVer); framer.set_enable_compression(true); { const char kDescription[] = - "SYN_STREAM frame, lowest pri, no FIN"; + "SYN_STREAM frame, low pri, no FIN"; SpdyHeaderBlock headers; headers["bar"] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x01, + const SpdyPriority priority = + (SPDY_VERSION_FOR_TESTS != 2) ? 4 : 2; + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0xC0, 0x00, 0x38, 0xea, + 0x80, 0x00, 0x38, 0xea, 0xdf, 0xa2, 0x51, 0xb2, 0x62, 0x60, 0x62, 0x60, 0x4e, 0x4a, 0x2c, 0x62, @@ -1081,15 +1544,32 @@ TEST_F(SpdyFramerTest, CreateSynStreamCompressed) { 0x00, 0x00, 0x00, 0xff, 0xff }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x38, 0xEA, + 0xE3, 0xC6, 0xA7, 0xC2, + 0x02, 0xE5, 0x0E, 0x50, + 0xC2, 0x4B, 0x4A, 0x04, + 0xE5, 0x0B, 0xE6, 0xB4, + 0xFC, 0x7C, 0x24, 0x0A, + 0x28, 0x08, 0x00, 0x00, + 0x00, 0xFF, 0xFF + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( - 1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE, - true, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + 1, 0, priority, CONTROL_FLAG_NONE, true, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } } -TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateSynReplyUncompressed) { + SpdyFramer framer(kVer); framer.set_enable_compression(false); { @@ -1099,8 +1579,8 @@ TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { headers["bar"] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x02, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, @@ -1110,9 +1590,26 @@ TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { 'f', 'o', 'o', 0x00, 0x03, 'b', 'a', 'r' }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( 1, CONTROL_FLAG_NONE, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } { @@ -1123,8 +1620,8 @@ TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { headers[""] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x02, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, 0x01, 0x00, 0x00, 0x19, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, @@ -1134,9 +1631,26 @@ TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { 0x00, 0x03, 'b', 'a', 'r' }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } { @@ -1147,8 +1661,8 @@ TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { headers["bar"] = "foo"; headers["foo"] = ""; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x02, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, 0x01, 0x00, 0x00, 0x19, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, @@ -1158,14 +1672,31 @@ TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { 'f', 'o', 'o', 0x00, 0x00 }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } } -TEST_F(SpdyFramerTest, CreateSynReplyCompressed) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateSynReplyCompressed) { + SpdyFramer framer(kVer); framer.set_enable_compression(true); { @@ -1175,8 +1706,8 @@ TEST_F(SpdyFramerTest, CreateSynReplyCompressed) { headers["bar"] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x02, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x38, 0xea, @@ -1188,33 +1719,50 @@ TEST_F(SpdyFramerTest, CreateSynReplyCompressed) { 0x00, 0x00, 0x00, 0xff, 0xff }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0xe6, 0xb4, 0xfc, 0x7c, + 0x24, 0x0a, 0x28, 0x08, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( 1, CONTROL_FLAG_NONE, true, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } } -TEST_F(SpdyFramerTest, CreateRstStream) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateRstStream) { + SpdyFramer framer(kVer); { const char kDescription[] = "RST_STREAM frame"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x03, + 0x80, kVer, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, }; - scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(1, PROTOCOL_ERROR)); + scoped_ptr<SpdyRstStreamControlFrame> frame( + framer.CreateRstStream(1, PROTOCOL_ERROR)); CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); - EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); + EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get())); } { const char kDescription[] = "RST_STREAM frame with max stream ID"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x03, + 0x80, kVer, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, @@ -1227,7 +1775,7 @@ TEST_F(SpdyFramerTest, CreateRstStream) { { const char kDescription[] = "RST_STREAM frame with max status code"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x03, + 0x80, kVer, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x06, @@ -1238,51 +1786,108 @@ TEST_F(SpdyFramerTest, CreateRstStream) { } } -TEST_F(SpdyFramerTest, CreateSettings) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateSettings) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "Network byte order SETTINGS frame"; + + uint32 kValue = 0x0a0b0c0d; + uint8 kFlags = 0x04; + uint32 kId = 0x030201; + SettingsFlagsAndId idAndFlags(kFlags, kId); + + SpdySettings settings; + settings.push_back(SpdySetting(idAndFlags, kValue)); + + EXPECT_EQ(kValue, settings.back().second); + EXPECT_EQ(kFlags, settings.back().first.flags()); + EXPECT_EQ(kId, settings.back().first.id()); + + const unsigned char kFrameDatav2[] = { + 0x80, kVer, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x02, 0x03, 0x04, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + + const unsigned char kFrameDatav3[] = { + 0x80, kVer, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, + 0x04, 0x03, 0x02, 0x01, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + + scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kFrameDatav2 : kFrameDatav3, + arraysize(kFrameDatav3)); // Size is unchanged among versions. + EXPECT_EQ(SpdyFramer::kInvalidStream, + SpdyFramer::GetControlFrameStreamId(frame.get())); + + // Make sure that ParseSettings also works as advertised. + SpdySettings parsed_settings; + EXPECT_TRUE(framer.ParseSettings(frame.get(), &parsed_settings)); + EXPECT_EQ(settings.size(), parsed_settings.size()); + EXPECT_EQ(kFlags, parsed_settings.back().first.flags()); + EXPECT_EQ(kId, parsed_settings.back().first.id()); + } { const char kDescription[] = "Basic SETTINGS frame"; SpdySettings settings; - settings.push_back(SpdySetting(0x00000000, 0x00000000)); - settings.push_back(SpdySetting(0xffffffff, 0x00000001)); - settings.push_back(SpdySetting(0xff000001, 0x00000002)); + settings.push_back( + SpdySettingFromWireFormat(0x00000000, 0x00000000)); // 1st Setting + settings.push_back( + SpdySettingFromWireFormat(0xffffffff, 0x00000001)); // 2nd Setting + settings.push_back( + SpdySettingFromWireFormat(0xff000001, 0x00000002)); // 3rd Setting // Duplicates allowed - settings.push_back(SpdySetting(0x01000002, 0x00000003)); - settings.push_back(SpdySetting(0x01000002, 0x00000003)); - - settings.push_back(SpdySetting(0x01000003, 0x000000ff)); - settings.push_back(SpdySetting(0x01000004, 0xff000001)); - settings.push_back(SpdySetting(0x01000004, 0xffffffff)); + settings.push_back( + SpdySettingFromWireFormat(0x01000002, 0x00000003)); // 4th Setting + settings.push_back( + SpdySettingFromWireFormat(0x01000002, 0x00000003)); // 5th Setting + + settings.push_back( + SpdySettingFromWireFormat(0x01000003, 0x000000ff)); // 6th Setting + settings.push_back( + SpdySettingFromWireFormat(0x01000004, 0xff000001)); // 7th Setting + settings.push_back( + SpdySettingFromWireFormat(0x01000004, 0xffffffff)); // 8th Setting const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x04, + 0x80, kVer, 0x00, 0x04, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, // 1st Setting 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, // 2nd Setting 0x00, 0x00, 0x00, 0x01, - 0xff, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0xff, // 3rd Setting 0x00, 0x00, 0x00, 0x02, - 0x01, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x01, // 4th Setting 0x00, 0x00, 0x00, 0x03, - 0x01, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x01, // 5th Setting 0x00, 0x00, 0x00, 0x03, - 0x01, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x01, // 6th Setting 0x00, 0x00, 0x00, 0xff, - 0x01, 0x00, 0x00, 0x04, + 0x04, 0x00, 0x00, 0x01, // 7th Setting 0xff, 0x00, 0x00, 0x01, - 0x01, 0x00, 0x00, 0x04, + 0x04, 0x00, 0x00, 0x01, // 8th Setting 0xff, 0xff, 0xff, 0xff, }; - scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings)); + CompareFrame(kDescription, + *frame, + kFrameData, + arraysize(kFrameData)); EXPECT_EQ(SpdyFramer::kInvalidStream, - SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); + SpdyFramer::GetControlFrameStreamId(frame.get())); } { @@ -1291,7 +1896,7 @@ TEST_F(SpdyFramerTest, CreateSettings) { SpdySettings settings; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x04, + 0x80, kVer, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, }; @@ -1300,62 +1905,43 @@ TEST_F(SpdyFramerTest, CreateSettings) { } } -TEST_F(SpdyFramerTest, CreateNopFrame) { - SpdyFramer framer; - - { - const char kDescription[] = "NOOP frame"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x00, - }; - scoped_ptr<SpdyFrame> frame(framer.CreateNopFrame()); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); - EXPECT_EQ(SpdyFramer::kInvalidStream, - SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); - } -} - -TEST_F(SpdyFramerTest, CreatePingFrame) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreatePingFrame) { + SpdyFramer framer(kVer); { const char kDescription[] = "PING frame"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x06, + 0x80, kVer, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, }; - scoped_ptr<SpdyFrame> frame(framer.CreatePingFrame(0x12345678u)); + scoped_ptr<SpdyPingControlFrame> frame(framer.CreatePingFrame(0x12345678u)); CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); EXPECT_EQ(SpdyFramer::kInvalidStream, - SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); + SpdyFramer::GetControlFrameStreamId(frame.get())); } } -TEST_F(SpdyFramerTest, CreateGoAway) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateGoAway) { + SpdyFramer framer(kVer); { const char kDescription[] = "GOAWAY frame"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x07, + 0x80, kVer, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, }; - scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0)); + scoped_ptr<SpdyGoAwayControlFrame> frame(framer.CreateGoAway(0)); CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); EXPECT_EQ(SpdyFramer::kInvalidStream, - SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); + SpdyFramer::GetControlFrameStreamId(frame.get())); } { const char kDescription[] = "GOAWAY frame with max stream ID"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x07, + 0x80, kVer, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x7f, 0xff, 0xff, 0xff, }; @@ -1364,8 +1950,8 @@ TEST_F(SpdyFramerTest, CreateGoAway) { } } -TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateHeadersUncompressed) { + SpdyFramer framer(kVer); framer.set_enable_compression(false); { @@ -1375,8 +1961,8 @@ TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { headers["bar"] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x08, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, @@ -1386,9 +1972,26 @@ TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { 'f', 'o', 'o', 0x00, 0x03, 'b', 'a', 'r' }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( 1, CONTROL_FLAG_NONE, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } { @@ -1399,8 +2002,8 @@ TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { headers[""] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x08, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, 0x01, 0x00, 0x00, 0x19, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, @@ -1410,9 +2013,26 @@ TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { 0x00, 0x03, 'b', 'a', 'r' }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } { @@ -1423,8 +2043,8 @@ TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { headers["bar"] = "foo"; headers["foo"] = ""; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x08, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, 0x01, 0x00, 0x00, 0x19, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, @@ -1434,14 +2054,31 @@ TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { 'f', 'o', 'o', 0x00, 0x00 }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } } -TEST_F(SpdyFramerTest, CreateHeadersCompressed) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateHeadersCompressed) { + SpdyFramer framer(kVer); framer.set_enable_compression(true); { @@ -1451,8 +2088,8 @@ TEST_F(SpdyFramerTest, CreateHeadersCompressed) { headers["bar"] = "foo"; headers["foo"] = "bar"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x08, + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x38, 0xea, @@ -1464,33 +2101,50 @@ TEST_F(SpdyFramerTest, CreateHeadersCompressed) { 0x00, 0x00, 0x00, 0xff, 0xff }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0xe6, 0xb4, 0xfc, 0x7c, + 0x24, 0x0a, 0x28, 0x08, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( 1, CONTROL_FLAG_NONE, true, &headers)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); } } -TEST_F(SpdyFramerTest, CreateWindowUpdate) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, CreateWindowUpdate) { + SpdyFramer framer(kVer); { const char kDescription[] = "WINDOW_UPDATE frame"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x09, + 0x80, kVer, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, }; - scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 1)); + scoped_ptr<SpdyWindowUpdateControlFrame> frame( + framer.CreateWindowUpdate(1, 1)); CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); - EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId( - reinterpret_cast<const SpdyControlFrame*>(frame.get()))); + EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get())); } { const char kDescription[] = "WINDOW_UPDATE frame with max stream ID"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x09, + 0x80, kVer, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, @@ -1502,7 +2156,7 @@ TEST_F(SpdyFramerTest, CreateWindowUpdate) { { const char kDescription[] = "WINDOW_UPDATE frame with max window delta"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x09, + 0x80, kVer, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff, 0xff, 0xff, @@ -1512,85 +2166,13 @@ TEST_F(SpdyFramerTest, CreateWindowUpdate) { } } -TEST_F(SpdyFramerTest, CreateCredential) { - SpdyFramer framer; - - { - const char kDescription[] = "CREDENTIAL frame"; - const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x0A, - 0x00, 0x00, 0x00, 0x33, - 0x00, 0x03, 0x00, 0x00, - 0x00, 0x05, 'p', 'r', - 'o', 'o', 'f', 0x00, - 0x00, 0x00, 0x06, 'a', - ' ', 'c', 'e', 'r', - 't', 0x00, 0x00, 0x00, - 0x0C, 'a', 'n', 'o', - 't', 'h', 'e', 'r', - ' ', 'c', 'e', 'r', - 't', 0x00, 0x00, 0x00, - 0x0A, 'f', 'i', 'n', - 'a', 'l', ' ', 'c', - 'e', 'r', 't', - }; - SpdyCredential credential; - credential.slot = 3; - credential.proof = "proof"; - credential.certs.push_back("a cert"); - credential.certs.push_back("another cert"); - credential.certs.push_back("final cert"); - scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential)); - CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); - } -} - -TEST_F(SpdyFramerTest, ParseCredentialFrame) { - SpdyFramer framer; - - { - unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x0A, - 0x00, 0x00, 0x00, 0x33, - 0x00, 0x03, 0x00, 0x00, - 0x00, 0x05, 'p', 'r', - 'o', 'o', 'f', 0x00, - 0x00, 0x00, 0x06, 'a', - ' ', 'c', 'e', 'r', - 't', 0x00, 0x00, 0x00, - 0x0C, 'a', 'n', 'o', - 't', 'h', 'e', 'r', - ' ', 'c', 'e', 'r', - 't', 0x00, 0x00, 0x00, - 0x0A, 'f', 'i', 'n', - 'a', 'l', ' ', 'c', - 'e', 'r', 't', - }; - SpdyCredentialControlFrame frame(reinterpret_cast<char*>(kFrameData), - false); - SpdyCredential credential; - EXPECT_TRUE(SpdyFramer::ParseCredentialData(frame.payload(), - frame.length(), - &credential)); - EXPECT_EQ(3u, credential.slot); - EXPECT_EQ("proof", credential.proof); - EXPECT_EQ("a cert", credential.certs.front()); - credential.certs.erase(credential.certs.begin()); - EXPECT_EQ("another cert", credential.certs.front()); - credential.certs.erase(credential.certs.begin()); - EXPECT_EQ("final cert", credential.certs.front()); - credential.certs.erase(credential.certs.begin()); - EXPECT_TRUE(credential.certs.empty()); - } -} - -TEST_F(SpdyFramerTest, DuplicateFrame) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, DuplicateFrame) { + SpdyFramer framer(kVer); { const char kDescription[] = "PING frame"; const unsigned char kFrameData[] = { - 0x80, 0x02, 0x00, 0x06, + 0x80, kVer, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, }; @@ -1607,7 +2189,7 @@ TEST_F(SpdyFramerTest, DuplicateFrame) { // the framer to overrun the buffer, and smash other heap contents. This test // relies on the debug version of the heap manager, which checks for buffer // overrun errors during delete processing. Regression test for b/2974814. -TEST_F(SpdyFramerTest, ExpandBuffer_HeapSmash) { +TEST_F(SpdyFramerSpdy2Test, ExpandBuffer_HeapSmash) { // Sweep through the area of problematic values, to make sure we always cover // the danger zone, even if it moves around at bit due to SPDY changes. for (uint16 val2_len = SpdyFramer::kControlFrameBufferInitialSize - 50; @@ -1618,7 +2200,7 @@ TEST_F(SpdyFramerTest, ExpandBuffer_HeapSmash) { headers["bar"] = "foo"; headers["foo"] = "baz"; headers["grue"] = val2.c_str(); - SpdyFramer framer; + SpdyFramer framer(kVer); scoped_ptr<SpdySynStreamControlFrame> template_frame( framer.CreateSynStream(1, // stream_id 0, // associated_stream_id @@ -1635,14 +2217,158 @@ TEST_F(SpdyFramerTest, ExpandBuffer_HeapSmash) { } } -TEST_F(SpdyFramerTest, ReadCredentialFrame) { +TEST_F(SpdyFramerSpdy2Test, ControlFrameSizesAreValidated) { + // Create a GoAway frame that has a few extra bytes at the end. + // We create enough overhead to require the framer to expand its frame buffer. + size_t overhead = SpdyFramer::kUncompressedControlFrameBufferInitialSize; + SpdyFramer framer(kVer); + scoped_ptr<SpdyGoAwayControlFrame> goaway(framer.CreateGoAway(1)); + goaway->set_length(goaway->length() + overhead); + std::string pad('A', overhead); + TestSpdyVisitor visitor; + + // First attempt without validation on. + visitor.framer_.set_validate_control_frame_sizes(false); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(goaway->data()), + goaway->length() - overhead + SpdyControlFrame::kHeaderSize); + visitor.SimulateInFramer( + reinterpret_cast<const unsigned char*>(pad.c_str()), + overhead); + EXPECT_EQ(0, visitor.error_count_); // Not an error. + EXPECT_EQ(1, visitor.goaway_count_); // The goaway was parsed. + + // Attempt with validation on. + visitor.framer_.set_validate_control_frame_sizes(true); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(goaway->data()), + goaway->length() - overhead + SpdyControlFrame::kHeaderSize); + visitor.SimulateInFramer( + reinterpret_cast<const unsigned char*>(pad.c_str()), + overhead); + EXPECT_EQ(1, visitor.error_count_); // This generated an error. + EXPECT_EQ(1, visitor.goaway_count_); // Unchanged from before. +} + +TEST_F(SpdyFramerSpdy2Test, ReadZeroLenSettingsFrame) { + SpdyFramer framer(kVer); + SpdySettings settings; + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + control_frame->set_length(0); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + // Should generate an error, since zero-len settings frames are unsupported. + EXPECT_EQ(1, visitor.error_count_); +} + +// Tests handling of SETTINGS frames with invalid length. +TEST_F(SpdyFramerSpdy2Test, ReadBogusLenSettingsFrame) { + SpdyFramer framer(kVer); + SpdySettings settings; + // Add a setting to pad the frame so that we don't get a buffer overflow when + // calling SimulateInFramer() below. + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + control_frame->set_length(5); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + // Should generate an error, since zero-len settings frames are unsupported. + EXPECT_EQ(1, visitor.error_count_); +} + +// Tests handling of SETTINGS frames larger than the frame buffer size. +TEST_F(SpdyFramerSpdy2Test, ReadLargeSettingsFrame) { + SpdyFramer framer(kVer); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 2), 0x00000003)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 3), 0x00000004)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + EXPECT_LT(SpdyFramer::kUncompressedControlFrameBufferInitialSize, + control_frame->length() + SpdyControlFrame::kHeaderSize); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + + // Read all at once. + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(settings.size(), static_cast<unsigned>(visitor.setting_count_)); + EXPECT_EQ(1, visitor.settings_frame_count_); + + // Read data in small chunks. + size_t framed_data = 0; + size_t unframed_data = control_frame->length() + + SpdyControlFrame::kHeaderSize; + size_t kReadChunkSize = 5; // Read five bytes at a time. + while (unframed_data > 0) { + size_t to_read = std::min(kReadChunkSize, unframed_data); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data() + framed_data), + to_read); + unframed_data -= to_read; + framed_data += to_read; + } + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(settings.size() * 2, static_cast<unsigned>(visitor.setting_count_)); + EXPECT_EQ(2, visitor.settings_frame_count_); +} + +// Tests handling of SETTINGS frame with duplicate entries. +TEST_F(SpdyFramerSpdy2Test, ReadDuplicateSettings) { + SpdyFramer framer(kVer); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000003)); + // This last setting should not be processed due to error above. + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 3), 0x00000003)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_frame_count_); +} + +// Tests handling of SETTINGS frame with entries out of order. +TEST_F(SpdyFramerSpdy2Test, ReadOutOfOrderSettings) { + SpdyFramer framer(kVer); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 2), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000003)); + // This last setting should not be processed due to error above. + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 3), 0x00000003)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_frame_count_); +} + +TEST_F(SpdyFramerSpdy2Test, ReadCredentialFrame) { SpdyCredential credential; credential.slot = 3; credential.proof = "proof"; credential.certs.push_back("a cert"); credential.certs.push_back("another cert"); credential.certs.push_back("final cert"); - SpdyFramer framer; + SpdyFramer framer(kVer); scoped_ptr<SpdyFrame> control_frame( framer.CreateCredentialFrame(credential)); EXPECT_TRUE(control_frame.get() != NULL); @@ -1662,14 +2388,14 @@ TEST_F(SpdyFramerTest, ReadCredentialFrame) { } } -TEST_F(SpdyFramerTest, ReadCredentialFrameWithCorruptProof) { +TEST_F(SpdyFramerSpdy2Test, ReadCredentialFrameWithCorruptProof) { SpdyCredential credential; credential.slot = 3; credential.proof = "proof"; credential.certs.push_back("a cert"); credential.certs.push_back("another cert"); credential.certs.push_back("final cert"); - SpdyFramer framer; + SpdyFramer framer(kVer); scoped_ptr<SpdyFrame> control_frame( framer.CreateCredentialFrame(credential)); EXPECT_TRUE(control_frame.get() != NULL); @@ -1684,14 +2410,14 @@ TEST_F(SpdyFramerTest, ReadCredentialFrameWithCorruptProof) { EXPECT_EQ(1, visitor.error_count_); } -TEST_F(SpdyFramerTest, ReadCredentialFrameWithCorruptCertificate) { +TEST_F(SpdyFramerSpdy2Test, ReadCredentialFrameWithCorruptCertificate) { SpdyCredential credential; credential.slot = 3; credential.proof = "proof"; credential.certs.push_back("a cert"); credential.certs.push_back("another cert"); credential.certs.push_back("final cert"); - SpdyFramer framer; + SpdyFramer framer(kVer); scoped_ptr<SpdyFrame> control_frame( framer.CreateCredentialFrame(credential)); EXPECT_TRUE(control_frame.get() != NULL); @@ -1706,8 +2432,8 @@ TEST_F(SpdyFramerTest, ReadCredentialFrameWithCorruptCertificate) { EXPECT_EQ(1, visitor.error_count_); } -TEST_F(SpdyFramerTest, ReadGarbage) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, ReadGarbage) { + SpdyFramer framer(kVer); unsigned char garbage_frame[256]; memset(garbage_frame, ~0, sizeof(garbage_frame)); TestSpdyVisitor visitor; @@ -1716,12 +2442,12 @@ TEST_F(SpdyFramerTest, ReadGarbage) { EXPECT_EQ(1, visitor.error_count_); } -TEST_F(SpdyFramerTest, ReadGarbageWithValidVersion) { - SpdyFramer framer; +TEST_F(SpdyFramerSpdy2Test, ReadGarbageWithValidVersion) { + SpdyFramer framer(kVer); char garbage_frame[256]; memset(garbage_frame, ~0, sizeof(garbage_frame)); SpdyControlFrame control_frame(&garbage_frame[0], false); - control_frame.set_version(kSpdyProtocolVersion); + control_frame.set_version(SPDY_VERSION_FOR_TESTS); TestSpdyVisitor visitor; visitor.use_compression_ = false; visitor.SimulateInFramer( @@ -1730,7 +2456,7 @@ TEST_F(SpdyFramerTest, ReadGarbageWithValidVersion) { EXPECT_EQ(1, visitor.error_count_); } -TEST(SpdyFramer, StateToStringTest) { +TEST_F(SpdyFramerSpdy2Test, StateToStringTest) { EXPECT_STREQ("ERROR", SpdyFramer::StateToString(SpdyFramer::SPDY_ERROR)); EXPECT_STREQ("DONE", @@ -1760,12 +2486,15 @@ TEST(SpdyFramer, StateToStringTest) { EXPECT_STREQ("SPDY_CREDENTIAL_FRAME_PAYLOAD", SpdyFramer::StateToString( SpdyFramer::SPDY_CREDENTIAL_FRAME_PAYLOAD)); + EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD)); EXPECT_STREQ("UNKNOWN_STATE", SpdyFramer::StateToString( - SpdyFramer::SPDY_CREDENTIAL_FRAME_PAYLOAD + 1)); + SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD + 1)); } -TEST(SpdyFramer, ErrorCodeToStringTest) { +TEST_F(SpdyFramerSpdy2Test, ErrorCodeToStringTest) { EXPECT_STREQ("NO_ERROR", SpdyFramer::ErrorCodeToString(SpdyFramer::SPDY_NO_ERROR)); EXPECT_STREQ("INVALID_CONTROL_FRAME", @@ -1790,7 +2519,7 @@ TEST(SpdyFramer, ErrorCodeToStringTest) { SpdyFramer::ErrorCodeToString(SpdyFramer::LAST_ERROR)); } -TEST(SpdyFramer, StatusCodeToStringTest) { +TEST_F(SpdyFramerSpdy2Test, StatusCodeToStringTest) { EXPECT_STREQ("INVALID", SpdyFramer::StatusCodeToString(INVALID)); EXPECT_STREQ("PROTOCOL_ERROR", @@ -1811,7 +2540,7 @@ TEST(SpdyFramer, StatusCodeToStringTest) { SpdyFramer::StatusCodeToString(NUM_STATUS_CODES)); } -TEST(SpdyFramer, ControlTypeToStringTest) { +TEST_F(SpdyFramerSpdy2Test, ControlTypeToStringTest) { EXPECT_STREQ("SYN_STREAM", SpdyFramer::ControlTypeToString(SYN_STREAM)); EXPECT_STREQ("SYN_REPLY", @@ -1830,13 +2559,13 @@ TEST(SpdyFramer, ControlTypeToStringTest) { SpdyFramer::ControlTypeToString(HEADERS)); EXPECT_STREQ("WINDOW_UPDATE", SpdyFramer::ControlTypeToString(WINDOW_UPDATE)); - EXPECT_STREQ("SETTINGS", - SpdyFramer::ControlTypeToString(SETTINGS)); + EXPECT_STREQ("CREDENTIAL", + SpdyFramer::ControlTypeToString(CREDENTIAL)); EXPECT_STREQ("UNKNOWN_CONTROL_TYPE", SpdyFramer::ControlTypeToString(NUM_CONTROL_FRAME_TYPES)); } -TEST(SpdyFramer, GetMinimumControlFrameSizeTest) { +TEST_F(SpdyFramerSpdy2Test, GetMinimumControlFrameSizeTest) { EXPECT_EQ(SpdySynStreamControlFrame::size(), SpdyFramer::GetMinimumControlFrameSize(SYN_STREAM)); EXPECT_EQ(SpdySynReplyControlFrame::size(), @@ -1845,7 +2574,7 @@ TEST(SpdyFramer, GetMinimumControlFrameSizeTest) { SpdyFramer::GetMinimumControlFrameSize(RST_STREAM)); EXPECT_EQ(SpdySettingsControlFrame::size(), SpdyFramer::GetMinimumControlFrameSize(SETTINGS)); - EXPECT_EQ(SpdyNoOpControlFrame::size(), + EXPECT_EQ(SpdyFrame::kHeaderSize, SpdyFramer::GetMinimumControlFrameSize(NOOP)); EXPECT_EQ(SpdyPingControlFrame::size(), SpdyFramer::GetMinimumControlFrameSize(PING)); @@ -1861,11 +2590,34 @@ TEST(SpdyFramer, GetMinimumControlFrameSizeTest) { SpdyFramer::GetMinimumControlFrameSize(NUM_CONTROL_FRAME_TYPES)); } -std::string RandomString(int length) { - std::string rv; - for (int index = 0; index < length; index++) - rv += static_cast<char>('a' + (rand() % 26)); - return rv; +TEST_F(SpdyFramerSpdy2Test, CatchProbableHttpResponse) { + SpdyFramerTestUtil::DecompressionVisitor visitor; + visitor.set_allow_data_frames(true); + { + SpdyFramer framer(kVer); + framer.set_visitor(&visitor); + framer.ProcessInput("HTTP/1.1", 8); + EXPECT_TRUE(framer.probable_http_response()); + } + { + SpdyFramer framer(kVer); + framer.set_visitor(&visitor); + framer.ProcessInput("HTTP/1.0", 8); + EXPECT_TRUE(framer.probable_http_response()); + } +} + +TEST_F(SpdyFramerSpdy2Test, SettingsFlagsAndId) { + const uint32 kId = 0x020304; + const uint32 kFlags = 0x01; + const uint32 kWireFormat = + htonl((SPDY_VERSION_FOR_TESTS < 3) ? 0x04030201 : 0x01020304); + + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, kWireFormat); + EXPECT_EQ(kId, id_and_flags.id()); + EXPECT_EQ(kFlags, id_and_flags.flags()); + EXPECT_EQ(kWireFormat, id_and_flags.GetWireFormat(SPDY_VERSION_FOR_TESTS)); } } // namespace diff --git a/net/spdy/spdy_framer_spdy3_test.cc b/net/spdy/spdy_framer_spdy3_test.cc new file mode 100644 index 0000000..2cc0de9 --- /dev/null +++ b/net/spdy/spdy_framer_spdy3_test.cc @@ -0,0 +1,2623 @@ +// 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 <algorithm> +#include <iostream> + +#include "base/memory/scoped_ptr.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_frame_builder.h" +#include "testing/platform_test.h" + +namespace { + +// Default SPDY version for unit tests. +const int SPDY_VERSION_FOR_TESTS = 3; + +// The current default spdy version as a byte to be included in const +// byte arrays below. Name choice is unfortunate, but better to fit to four +// bytes than not. +unsigned char kVer = SPDY_VERSION_FOR_TESTS; + +spdy::SpdySetting SpdySettingFromWireFormat(uint32 key, uint32 value) { + return spdy::SpdySetting( + spdy::SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, key), + value); +} + +} // namespace + +namespace spdy { + +namespace test_spdy3 { + +static const size_t kMaxDecompressedSize = 1024; + +class SpdyFramerTestUtil { + public: + // Decompress a single frame using the decompression context held by + // the SpdyFramer. The implemention will CHECK fail if the input is anything + // other than a single, well-formed compressed frame. + // + // Returns a new decompressed SpdyFrame. + template<class SpdyFrameType> static SpdyFrame* DecompressFrame( + SpdyFramer* framer, const SpdyFrameType& frame) { + DecompressionVisitor visitor; + framer->set_visitor(&visitor); + size_t input_size = frame.length() + SpdyFrame::kHeaderSize; + CHECK_EQ(input_size, framer->ProcessInput(frame.data(), input_size)); + CHECK_EQ(SpdyFramer::SPDY_RESET, framer->state()); + framer->set_visitor(NULL); + + char* buffer = visitor.ReleaseBuffer(); + CHECK(buffer); + SpdyFrame* decompressed_frame = new SpdyFrame(buffer, true); + decompressed_frame->set_length(visitor.size() - SpdyFrame::kHeaderSize); + return decompressed_frame; + } + + class DecompressionVisitor : public SpdyFramerVisitorInterface { + public: + DecompressionVisitor() + : buffer_(NULL), size_(0), finished_(false), allow_data_frames_(false) { + } + + virtual void OnControl(const SpdyControlFrame* frame) { + CHECK(frame->has_header_block()); + CHECK(!buffer_.get()); + CHECK_EQ(size_, 0u); + CHECK(!finished_); + + int32 control_frame_header_size = 0; + switch (frame->type()) { + case SYN_STREAM: + control_frame_header_size = SpdySynStreamControlFrame::size(); + break; + case SYN_REPLY: + control_frame_header_size = SpdySynReplyControlFrame::size(); + break; + case HEADERS: + control_frame_header_size = SpdyHeadersControlFrame::size(); + break; + default: + LOG(FATAL); + return; + } + + // Allocate space for the frame, and the copy header over. + buffer_.reset(new char[kMaxDecompressedSize]); + memcpy(buffer_.get(), frame->data(), control_frame_header_size); + size_ += control_frame_header_size; + } + + virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) { + CHECK(buffer_.get() != NULL); + CHECK_GE(kMaxDecompressedSize, size_ + len); + CHECK(!finished_); + if (len != 0) { + memcpy(buffer_.get() + size_, header_data, len); + size_ += len; + } else { + // Done. + finished_ = true; + } + return true; + } + + virtual bool OnCredentialFrameData(const char* header_data, + size_t len) { + LOG(FATAL) << "Unexpected CREDENTIAL Frame"; + return false; + } + + virtual void OnError(SpdyFramer* framer) { LOG(FATAL); } + virtual void OnDataFrameHeader(const SpdyDataFrame* frame) { + // For most tests, this class does not expect to see OnDataFrameHeader + // calls. Individual tests can override this if they need to. + if (!allow_data_frames_) { + LOG(FATAL) << "Unexpected data frame header"; + } + } + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) { + LOG(FATAL); + } + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) { + LOG(FATAL); + } + + char* ReleaseBuffer() { + CHECK(finished_); + return buffer_.release(); + } + + size_t size() const { + CHECK(finished_); + return size_; + } + void set_allow_data_frames(bool allow) { allow_data_frames_ = allow; } + + private: + scoped_array<char> buffer_; + size_t size_; + bool finished_; + bool allow_data_frames_; + + DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor); + }; + + DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil); +}; + +std::string HexDumpWithMarks(const unsigned char* data, int length, + const bool* marks, int mark_length) { + static const char kHexChars[] = "0123456789abcdef"; + static const int kColumns = 4; + + const int kSizeLimit = 1024; + if (length > kSizeLimit || mark_length > kSizeLimit) { + LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes."; + length = std::min(length, kSizeLimit); + mark_length = std::min(mark_length, kSizeLimit); + } + + std::string hex; + for (const unsigned char* row = data; length > 0; + row += kColumns, length -= kColumns) { + for (const unsigned char *p = row; p < row + 4; ++p) { + if (p < row + length) { + const bool mark = + (marks && (p - data) < mark_length && marks[p - data]); + hex += mark ? '*' : ' '; + hex += kHexChars[(*p & 0xf0) >> 4]; + hex += kHexChars[*p & 0x0f]; + hex += mark ? '*' : ' '; + } else { + hex += " "; + } + } + hex = hex + " "; + + for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p) + hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.'; + + hex = hex + '\n'; + } + return hex; +} + +void CompareCharArraysWithHexError( + const std::string& description, + const unsigned char* actual, + const int actual_len, + const unsigned char* expected, + const int expected_len) { + const int min_len = std::min(actual_len, expected_len); + const int max_len = std::max(actual_len, expected_len); + scoped_array<bool> marks(new bool[max_len]); + bool identical = (actual_len == expected_len); + for (int i = 0; i < min_len; ++i) { + if (actual[i] != expected[i]) { + marks[i] = true; + identical = false; + } else { + marks[i] = false; + } + } + for (int i = min_len; i < max_len; ++i) { + marks[i] = true; + } + if (identical) return; + ADD_FAILURE() + << "Description:\n" + << description + << "\n\nExpected:\n" + << HexDumpWithMarks(expected, expected_len, marks.get(), max_len) + << "\nActual:\n" + << HexDumpWithMarks(actual, actual_len, marks.get(), max_len); +} + +class TestSpdyVisitor : public SpdyFramerVisitorInterface { + public: + static const size_t kDefaultHeaderBufferSize = 64 * 1024; + static const size_t kDefaultCredentialBufferSize = 16 * 1024; + + TestSpdyVisitor() + : framer_(kVer), + use_compression_(false), + error_count_(0), + syn_frame_count_(0), + syn_reply_frame_count_(0), + headers_frame_count_(0), + goaway_count_(0), + credential_count_(0), + settings_frame_count_(0), + setting_count_(0), + data_bytes_(0), + fin_frame_count_(0), + fin_flag_count_(0), + zero_length_data_frame_count_(0), + header_blocks_count_(0), + control_frame_header_data_count_(0), + zero_length_control_frame_header_data_count_(0), + data_frame_count_(0), + header_buffer_(new char[kDefaultHeaderBufferSize]), + header_buffer_length_(0), + header_buffer_size_(kDefaultHeaderBufferSize), + header_stream_id_(-1), + header_control_type_(NUM_CONTROL_FRAME_TYPES), + header_buffer_valid_(false), + credential_buffer_(new char[kDefaultCredentialBufferSize]), + credential_buffer_length_(0), + credential_buffer_size_(kDefaultCredentialBufferSize) { + } + + void OnError(SpdyFramer* f) { + LOG(INFO) << "SpdyFramer Error: " + << SpdyFramer::ErrorCodeToString(f->error_code()); + error_count_++; + } + + void OnDataFrameHeader(const SpdyDataFrame* frame) { + data_frame_count_++; + header_stream_id_ = frame->stream_id(); + } + + void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) { + EXPECT_EQ(header_stream_id_, stream_id); + if (len == 0) + ++zero_length_data_frame_count_; + + data_bytes_ += len; + std::cerr << "OnStreamFrameData(" << stream_id << ", \""; + if (len > 0) { + for (size_t i = 0 ; i < len; ++i) { + std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec; + } + } + std::cerr << "\", " << len << ")\n"; + } + + void OnControl(const SpdyControlFrame* frame) { + switch (frame->type()) { + case SYN_STREAM: + syn_frame_count_++; + InitHeaderStreaming(frame); + break; + case SYN_REPLY: + syn_reply_frame_count_++; + InitHeaderStreaming(frame); + break; + case RST_STREAM: + fin_frame_count_++; + break; + case HEADERS: + headers_frame_count_++; + InitHeaderStreaming(frame); + break; + case GOAWAY: + goaway_count_++; + break; + case CREDENTIAL: + credential_count_++; + break; + case SETTINGS: + settings_frame_count_++; + break; + default: + DLOG(FATAL); // Error! + } + if (frame->flags() & CONTROL_FLAG_FIN) + ++fin_flag_count_; + } + + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) { + setting_count_++; + } + + bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) { + ++control_frame_header_data_count_; + CHECK_EQ(header_stream_id_, stream_id); + if (len == 0) { + ++zero_length_control_frame_header_data_count_; + // Indicates end-of-header-block. + CHECK(header_buffer_valid_); + bool parsed_headers = framer_.ParseHeaderBlockInBuffer( + header_buffer_.get(), header_buffer_length_, &headers_); + DCHECK(parsed_headers); + return true; + } + const size_t available = header_buffer_size_ - header_buffer_length_; + if (len > available) { + header_buffer_valid_ = false; + return false; + } + memcpy(header_buffer_.get() + header_buffer_length_, header_data, len); + header_buffer_length_ += len; + return true; + } + + bool OnCredentialFrameData(const char* credential_data, + size_t len) { + if (len == 0) { + if (!framer_.ParseCredentialData(credential_buffer_.get(), + credential_buffer_length_, + &credential_)) { + ++error_count_; + } + return true; + } + const size_t available = + credential_buffer_size_ - credential_buffer_length_; + if (len > available) { + return false; + } + memcpy(credential_buffer_.get() + credential_buffer_length_, + credential_data, len); + credential_buffer_length_ += len; + return true; + } + + // Convenience function which runs a framer simulation with particular input. + void SimulateInFramer(const unsigned char* input, size_t size) { + framer_.set_enable_compression(use_compression_); + framer_.set_visitor(this); + size_t input_remaining = size; + const char* input_ptr = reinterpret_cast<const char*>(input); + while (input_remaining > 0 && + framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) { + // To make the tests more interesting, we feed random (amd small) chunks + // into the framer. This simulates getting strange-sized reads from + // the socket. + const size_t kMaxReadSize = 32; + size_t bytes_read = + (rand() % std::min(input_remaining, kMaxReadSize)) + 1; + size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read); + input_remaining -= bytes_processed; + input_ptr += bytes_processed; + if (framer_.state() == SpdyFramer::SPDY_DONE) + framer_.Reset(); + } + } + + void InitHeaderStreaming(const SpdyControlFrame* frame) { + memset(header_buffer_.get(), 0, header_buffer_size_); + header_buffer_length_ = 0; + header_stream_id_ = SpdyFramer::GetControlFrameStreamId(frame); + header_control_type_ = frame->type(); + header_buffer_valid_ = true; + DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream); + } + + // Override the default buffer size (16K). Call before using the framer! + void set_header_buffer_size(size_t header_buffer_size) { + header_buffer_size_ = header_buffer_size; + header_buffer_.reset(new char[header_buffer_size]); + } + + static size_t control_frame_buffer_max_size() { + return SpdyFramer::kControlFrameBufferMaxSize; + } + + static size_t header_data_chunk_max_size() { + return SpdyFramer::kHeaderDataChunkMaxSize; + } + + SpdyFramer framer_; + bool use_compression_; + + // Counters from the visitor callbacks. + int error_count_; + int syn_frame_count_; + int syn_reply_frame_count_; + int headers_frame_count_; + int goaway_count_; + int credential_count_; + int settings_frame_count_; + int setting_count_; + int data_bytes_; + int fin_frame_count_; // The count of RST_STREAM type frames received. + int fin_flag_count_; // The count of frames with the FIN flag set. + int zero_length_data_frame_count_; // The count of zero-length data frames. + int header_blocks_count_; + int control_frame_header_data_count_; // The count of chunks received. + // The count of zero-length control frame header data chunks received. + int zero_length_control_frame_header_data_count_; + int data_frame_count_; + + // Header block streaming state: + scoped_array<char> header_buffer_; + size_t header_buffer_length_; + size_t header_buffer_size_; + SpdyStreamId header_stream_id_; + SpdyControlType header_control_type_; + bool header_buffer_valid_; + SpdyHeaderBlock headers_; + + scoped_array<char> credential_buffer_; + size_t credential_buffer_length_; + size_t credential_buffer_size_; + SpdyCredential credential_; +}; + +} // namespace test_spdy3 + +} // namespace spdy + +using spdy::SpdyControlFlags; +using spdy::SpdyControlFrame; +using spdy::SpdyDataFrame; +using spdy::SpdyFrame; +using spdy::SpdyFrameBuilder; +using spdy::SpdyFramer; +using spdy::SpdyHeaderBlock; +using spdy::SpdySynStreamControlFrame; +using spdy::kControlFlagMask; +using spdy::kLengthMask; +using spdy::CONTROL_FLAG_NONE; +using spdy::DATA_FLAG_COMPRESSED; +using spdy::DATA_FLAG_FIN; +using spdy::SYN_STREAM; +using spdy::test_spdy3::CompareCharArraysWithHexError; +using spdy::test_spdy3::SpdyFramerTestUtil; +using spdy::test_spdy3::TestSpdyVisitor; + +namespace spdy { + +TEST(SpdyFrameBuilderSpdy3Test, WriteLimits) { + SpdyFrameBuilder builder(kLengthMask + 4); + // length field should fail. + EXPECT_FALSE(builder.WriteBytes(reinterpret_cast<const void*>(0x1), + kLengthMask + 1)); + EXPECT_EQ(0, builder.length()); + + // Writing a block of the maximum allowed size should succeed. + const std::string kLargeData(kLengthMask, 'A'); + builder.WriteUInt32(kLengthMask); + EXPECT_EQ(4, builder.length()); + EXPECT_TRUE(builder.WriteBytes(kLargeData.data(), kLengthMask)); + EXPECT_EQ(4 + kLengthMask, static_cast<unsigned>(builder.length())); +} + +class SpdyFramerSpdy3Test : public PlatformTest { + public: + virtual void TearDown() {} + + protected: + void CompareFrame(const std::string& description, + const SpdyFrame& actual_frame, + const unsigned char* expected, + const int expected_len) { + const unsigned char* actual = + reinterpret_cast<const unsigned char*>(actual_frame.data()); + int actual_len = actual_frame.length() + SpdyFrame::kHeaderSize; + CompareCharArraysWithHexError( + description, actual, actual_len, expected, expected_len); + } + + // Returns true if the two header blocks have equivalent content. + bool CompareHeaderBlocks(const SpdyHeaderBlock* expected, + const SpdyHeaderBlock* actual) { + if (expected->size() != actual->size()) { + LOG(ERROR) << "Expected " << expected->size() << " headers; actually got " + << actual->size() << "." << std::endl; + return false; + } + for (SpdyHeaderBlock::const_iterator it = expected->begin(); + it != expected->end(); + ++it) { + SpdyHeaderBlock::const_iterator it2 = actual->find(it->first); + if (it2 == actual->end()) { + LOG(ERROR) << "Expected header name '" << it->first << "'." + << std::endl; + return false; + } + if (it->second.compare(it2->second) != 0) { + LOG(ERROR) << "Expected header named '" << it->first + << "' to have a value of '" << it->second + << "'. The actual value received was '" << it2->second + << "'." << std::endl; + return false; + } + } + return true; + } +}; + + +// Test that we can encode and decode a SpdyHeaderBlock in serialized form. +TEST_F(SpdyFramerSpdy3Test, HeaderBlockInBuffer) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "charlie"; + SpdyFramer framer(kVer); + + // Encode the header block into a SynStream frame. + scoped_ptr<SpdySynStreamControlFrame> frame( + framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, false, &headers)); + EXPECT_TRUE(frame.get() != NULL); + std::string serialized_headers(frame->header_block(), + frame->header_block_len()); + SpdyHeaderBlock new_headers; + EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), + serialized_headers.size(), + &new_headers)); + + EXPECT_EQ(headers.size(), new_headers.size()); + EXPECT_EQ(headers["alpha"], new_headers["alpha"]); + EXPECT_EQ(headers["gamma"], new_headers["gamma"]); +} + +// Test that if there's not a full frame, we fail to parse it. +TEST_F(SpdyFramerSpdy3Test, UndersizedHeaderBlockInBuffer) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "charlie"; + SpdyFramer framer(kVer); + + // Encode the header block into a SynStream frame. + scoped_ptr<SpdySynStreamControlFrame> frame( + framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, false, &headers)); + EXPECT_TRUE(frame.get() != NULL); + + std::string serialized_headers(frame->header_block(), + frame->header_block_len()); + SpdyHeaderBlock new_headers; + EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), + serialized_headers.size() - 2, + &new_headers)); +} + +TEST_F(SpdyFramerSpdy3Test, OutOfOrderHeaders) { + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + + frame.WriteUInt16(kControlFlagMask | 1); + frame.WriteUInt16(SYN_STREAM); + frame.WriteUInt32(0); // Placeholder for the length. + frame.WriteUInt32(3); // stream_id + frame.WriteUInt32(0); // Associated stream id + frame.WriteUInt16(0); // Priority. + + if (SPDY_VERSION_FOR_TESTS < 3) { + frame.WriteUInt16(2); // Number of headers. + frame.WriteString("gamma"); + frame.WriteString("gamma"); + frame.WriteString("alpha"); + frame.WriteString("alpha"); + } else { + frame.WriteUInt32(2); // Number of headers. + frame.WriteStringPiece32("gamma"); + frame.WriteStringPiece32("gamma"); + frame.WriteStringPiece32("alpha"); + frame.WriteStringPiece32("alpha"); + } + // write the length + frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize); + + SpdyHeaderBlock new_headers; + scoped_ptr<SpdyFrame> control_frame(frame.take()); + SpdySynStreamControlFrame syn_frame(control_frame->data(), false); + std::string serialized_headers(syn_frame.header_block(), + syn_frame.header_block_len()); + SpdyFramer framer(kVer); + framer.set_enable_compression(false); + EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), + serialized_headers.size(), + &new_headers)); +} + +TEST_F(SpdyFramerSpdy3Test, CreateCredential) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "CREDENTIAL frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x0A, + 0x00, 0x00, 0x00, 0x33, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, ParseCredentialFrameData) { + SpdyFramer framer(kVer); + + { + unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x0A, + 0x00, 0x00, 0x00, 0x33, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + SpdyCredentialControlFrame frame(reinterpret_cast<char*>(kFrameData), + false); + SpdyCredential credential; + EXPECT_TRUE(SpdyFramer::ParseCredentialData(frame.payload(), frame.length(), + &credential)); + EXPECT_EQ(3u, credential.slot); + EXPECT_EQ("proof", credential.proof); + EXPECT_EQ("a cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_EQ("another cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_EQ("final cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_TRUE(credential.certs.empty()); + } +} + +TEST_F(SpdyFramerSpdy3Test, DuplicateHeader) { + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + + frame.WriteUInt16(kControlFlagMask | 1); + frame.WriteUInt16(SYN_STREAM); + frame.WriteUInt32(0); // Placeholder for the length. + frame.WriteUInt32(3); // stream_id + frame.WriteUInt32(0); // associated stream id + frame.WriteUInt16(0); // Priority. + + if (SPDY_VERSION_FOR_TESTS < 3) { + frame.WriteUInt16(2); // Number of headers. + frame.WriteString("name"); + frame.WriteString("value1"); + frame.WriteString("name"); + frame.WriteString("value2"); + } else { + frame.WriteUInt32(2); // Number of headers. + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32("value1"); + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32("value2"); + } + // write the length + frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize); + + SpdyHeaderBlock new_headers; + scoped_ptr<SpdyFrame> control_frame(frame.take()); + SpdySynStreamControlFrame syn_frame(control_frame->data(), false); + std::string serialized_headers(syn_frame.header_block(), + syn_frame.header_block_len()); + SpdyFramer framer(kVer); + framer.set_enable_compression(false); + // This should fail because duplicate headers are verboten by the spec. + EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), + serialized_headers.size(), + &new_headers)); +} + +TEST_F(SpdyFramerSpdy3Test, MultiValueHeader) { + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + + frame.WriteUInt16(kControlFlagMask | 1); + frame.WriteUInt16(SYN_STREAM); + frame.WriteUInt32(0); // Placeholder for the length. + frame.WriteUInt32(3); // stream_id + frame.WriteUInt32(0); // associated stream id + frame.WriteUInt16(0); // Priority. + + std::string value("value1\0value2"); + if (SPDY_VERSION_FOR_TESTS < 3) { + frame.WriteUInt16(1); // Number of headers. + frame.WriteString("name"); + frame.WriteString(value); + } else { + frame.WriteUInt32(1); // Number of headers. + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32(value); + } + // write the length + frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize); + + SpdyHeaderBlock new_headers; + scoped_ptr<SpdyFrame> control_frame(frame.take()); + SpdySynStreamControlFrame syn_frame(control_frame->data(), false); + std::string serialized_headers(syn_frame.header_block(), + syn_frame.header_block_len()); + SpdyFramer framer(kVer); + framer.set_enable_compression(false); + EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(), + serialized_headers.size(), + &new_headers)); + EXPECT_TRUE(new_headers.find("name") != new_headers.end()); + EXPECT_EQ(value, new_headers.find("name")->second); +} + +TEST_F(SpdyFramerSpdy3Test, BasicCompression) { + SpdyHeaderBlock headers; + headers["server"] = "SpdyServer 1.0"; + headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; + headers["status"] = "200"; + headers["version"] = "HTTP/1.1"; + headers["content-type"] = "text/html"; + headers["content-length"] = "12"; + + SpdyFramer framer(kVer); + framer.set_enable_compression(true); + scoped_ptr<SpdySynStreamControlFrame> + frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, + &headers)); + scoped_ptr<SpdySynStreamControlFrame> + frame2(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, + &headers)); + + // Expect the second frame to be more compact than the first. + EXPECT_LE(frame2->length(), frame1->length()); + + // Decompress the first frame + scoped_ptr<SpdyFrame> frame3(SpdyFramerTestUtil::DecompressFrame( + &framer, *frame1.get())); + + // Decompress the second frame + scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame( + &framer, *frame2.get())); + + // Expect frames 3 & 4 to be the same. + EXPECT_EQ(0, + memcmp(frame3->data(), frame4->data(), + SpdyFrame::kHeaderSize + frame3->length())); + + + // Expect frames 3 to be the same as a uncompressed frame created + // from scratch. + scoped_ptr<SpdySynStreamControlFrame> + uncompressed_frame(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, + false, &headers)); + EXPECT_EQ(frame3->length(), uncompressed_frame->length()); + EXPECT_EQ(0, + memcmp(frame3->data(), uncompressed_frame->data(), + SpdyFrame::kHeaderSize + uncompressed_frame->length())); +} + +TEST_F(SpdyFramerSpdy3Test, Basic) { + const unsigned char kV2Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'h', 'h', + 0x00, 0x02, 'v', 'v', + + 0x80, kVer, 0x00, 0x08, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 'h', '2', + 0x00, 0x02, 'v', '2', + 0x00, 0x02, 'h', '3', + 0x00, 0x02, 'v', '3', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, kVer, 0x00, 0x01, // SYN Stream #3 + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #1 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x00, + + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + }; + + const unsigned char kV3Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, kVer, 0x00, 0x08, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x22, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x02, 'h', '2', + 0x00, 0x00, 0x00, 0x02, + 'v', '2', 0x00, 0x00, + 0x00, 0x02, 'h', '3', + 0x00, 0x00, 0x00, 0x02, + 'v', '3', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, kVer, 0x00, 0x01, // SYN Stream #3 + 0x00, 0x00, 0x00, 0x0e, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #1 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x00, + + 0x80, kVer, 0x00, 0x03, // RST_STREAM on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + }; + + TestSpdyVisitor visitor; + if (SPDY_VERSION_FOR_TESTS < 3) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(2, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(24, visitor.data_bytes_); + EXPECT_EQ(2, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(0, visitor.zero_length_data_frame_count_); + EXPECT_EQ(4, visitor.data_frame_count_); +} + +// Test that the FIN flag on a data frame signifies EOF. +TEST_F(SpdyFramerSpdy3Test, FinOnDataFrame) { + const unsigned char kV2Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'h', 'h', + 0x00, 0x02, 'v', 'v', + + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 + 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'a', 'a', + 0x00, 0x02, 'b', 'b', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF + 0x01, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + }; + const unsigned char kV3Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 + 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'a', 'a', + 0x00, 0x00, 0x00, 0x02, + 'b', 'b', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF + 0x01, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + }; + + TestSpdyVisitor visitor; + if (SPDY_VERSION_FOR_TESTS < 3) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(16, visitor.data_bytes_); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.zero_length_data_frame_count_); + EXPECT_EQ(2, visitor.data_frame_count_); +} + +// Test that the FIN flag on a SYN reply frame signifies EOF. +TEST_F(SpdyFramerSpdy3Test, FinOnSynReplyFrame) { + const unsigned char kV2Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'h', 'h', + 0x00, 0x02, 'v', 'v', + + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 + 0x01, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'a', 'a', + 0x00, 0x02, 'b', 'b', + }; + const unsigned char kV3Input[] = { + 0x80, kVer, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, kVer, 0x00, 0x02, // SYN REPLY Stream #1 + 0x01, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'a', 'a', + 0x00, 0x00, 0x00, 0x02, + 'b', 'b', + }; + + TestSpdyVisitor visitor; + if (SPDY_VERSION_FOR_TESTS < 3) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.data_bytes_); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(1, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.zero_length_data_frame_count_); + EXPECT_EQ(0, visitor.data_frame_count_); +} + +TEST_F(SpdyFramerSpdy3Test, HeaderCompression) { + SpdyFramer send_framer(kVer); + SpdyFramer recv_framer(kVer); + + send_framer.set_enable_compression(true); + recv_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kHeader3[] = "header3"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + const char kValue3[] = "value3"; + + // SYN_STREAM #1 + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + scoped_ptr<SpdySynStreamControlFrame> syn_frame_1( + send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); + EXPECT_TRUE(syn_frame_1.get() != NULL); + + // SYN_STREAM #2 + block[kHeader3] = kValue3; + scoped_ptr<SpdySynStreamControlFrame> syn_frame_2( + send_framer.CreateSynStream(3, 0, 0, flags, true, &block)); + EXPECT_TRUE(syn_frame_2.get() != NULL); + + // Now start decompressing + scoped_ptr<SpdyFrame> decompressed; + scoped_ptr<SpdySynStreamControlFrame> syn_frame; + scoped_ptr<std::string> serialized_headers; + SpdyHeaderBlock decompressed_headers; + + // Decompress SYN_STREAM #1 + decompressed.reset(SpdyFramerTestUtil::DecompressFrame( + &recv_framer, *syn_frame_1.get())); + EXPECT_TRUE(decompressed.get() != NULL); + EXPECT_TRUE(decompressed->is_control_frame()); + EXPECT_EQ(SYN_STREAM, + reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type()); + syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false)); + serialized_headers.reset(new std::string(syn_frame->header_block(), + syn_frame->header_block_len())); + EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(), + serialized_headers->size(), + &decompressed_headers)); + EXPECT_EQ(2u, decompressed_headers.size()); + EXPECT_EQ(kValue1, decompressed_headers[kHeader1]); + EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); + + // Decompress SYN_STREAM #2 + decompressed.reset(SpdyFramerTestUtil::DecompressFrame( + &recv_framer, *syn_frame_2.get())); + EXPECT_TRUE(decompressed.get() != NULL); + EXPECT_TRUE(decompressed->is_control_frame()); + EXPECT_EQ(SYN_STREAM, + reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type()); + syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false)); + serialized_headers.reset(new std::string(syn_frame->header_block(), + syn_frame->header_block_len())); + decompressed_headers.clear(); + EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(), + serialized_headers->size(), + &decompressed_headers)); + EXPECT_EQ(3u, decompressed_headers.size()); + EXPECT_EQ(kValue1, decompressed_headers[kHeader1]); + EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); + EXPECT_EQ(kValue3, decompressed_headers[kHeader3]); + + // We didn't have data streams, so we shouldn't have (de)compressors. + EXPECT_EQ(0, send_framer.num_stream_compressors()); + EXPECT_EQ(0, send_framer.num_stream_decompressors()); + EXPECT_EQ(0, recv_framer.num_stream_compressors()); + EXPECT_EQ(0, recv_framer.num_stream_decompressors()); +} + +// Verify we don't leak when we leave streams unclosed +TEST_F(SpdyFramerSpdy3Test, UnclosedStreamDataCompressors) { + SpdyFramer send_framer(kVer); + + send_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + scoped_ptr<SpdyFrame> syn_frame( + send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); + EXPECT_TRUE(syn_frame.get() != NULL); + + const char bytes[] = "this is a test test test test test!"; + scoped_ptr<SpdyFrame> send_frame( + send_framer.CreateDataFrame( + 1, bytes, arraysize(bytes), + static_cast<SpdyDataFlags>(DATA_FLAG_FIN))); + EXPECT_TRUE(send_frame.get() != NULL); + + // Run the inputs through the framer. + TestSpdyVisitor visitor; + visitor.use_compression_ = true; + const unsigned char* data; + data = reinterpret_cast<const unsigned char*>(syn_frame->data()); + visitor.SimulateInFramer(data, syn_frame->length() + SpdyFrame::kHeaderSize); + data = reinterpret_cast<const unsigned char*>(send_frame->data()); + visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::kHeaderSize); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_)); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.zero_length_data_frame_count_); + EXPECT_EQ(1, visitor.data_frame_count_); + + // We closed the streams, so all compressors should be down. + EXPECT_EQ(0, visitor.framer_.num_stream_compressors()); + EXPECT_EQ(0, visitor.framer_.num_stream_decompressors()); + EXPECT_EQ(0, send_framer.num_stream_compressors()); + EXPECT_EQ(0, send_framer.num_stream_decompressors()); +} + +// Verify we can decompress the stream even if handed over to the +// framer 1 byte at a time. +TEST_F(SpdyFramerSpdy3Test, UnclosedStreamDataCompressorsOneByteAtATime) { + SpdyFramer send_framer(kVer); + + send_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + scoped_ptr<SpdyFrame> syn_frame( + send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); + EXPECT_TRUE(syn_frame.get() != NULL); + + const char bytes[] = "this is a test test test test test!"; + scoped_ptr<SpdyFrame> send_frame( + send_framer.CreateDataFrame( + 1, bytes, arraysize(bytes), + static_cast<SpdyDataFlags>(DATA_FLAG_FIN))); + EXPECT_TRUE(send_frame.get() != NULL); + + // Run the inputs through the framer. + TestSpdyVisitor visitor; + visitor.use_compression_ = true; + const unsigned char* data; + data = reinterpret_cast<const unsigned char*>(syn_frame->data()); + for (size_t idx = 0; + idx < syn_frame->length() + SpdyFrame::kHeaderSize; + ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + data = reinterpret_cast<const unsigned char*>(send_frame->data()); + for (size_t idx = 0; + idx < send_frame->length() + SpdyFrame::kHeaderSize; + ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_)); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.zero_length_data_frame_count_); + EXPECT_EQ(1, visitor.data_frame_count_); + + // We closed the streams, so all compressors should be down. + EXPECT_EQ(0, visitor.framer_.num_stream_compressors()); + EXPECT_EQ(0, visitor.framer_.num_stream_decompressors()); + EXPECT_EQ(0, send_framer.num_stream_compressors()); + EXPECT_EQ(0, send_framer.num_stream_decompressors()); +} + +TEST_F(SpdyFramerSpdy3Test, WindowUpdateFrame) { + SpdyFramer framer(kVer); + scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame( + framer.CreateWindowUpdate(1, 0x12345678)); + + const unsigned char expected_data_frame[] = { + 0x80, kVer, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78 + }; + + EXPECT_EQ(16u, window_update_frame->size()); + EXPECT_EQ(0, + memcmp(window_update_frame->data(), expected_data_frame, 16)); +} + +TEST_F(SpdyFramerSpdy3Test, CreateDataFrame) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "'hello' data frame, no FIN"; + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x05, + 'h', 'e', 'l', 'l', + 'o' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "hello", 5, DATA_FLAG_NONE)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "Data frame with negative data byte, no FIN"; + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0xff + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "\xff", 1, DATA_FLAG_NONE)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "'hello' data frame, with FIN"; + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x05, + 'h', 'e', 'l', 'l', + 'o' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "hello", 5, DATA_FLAG_FIN)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "Empty data frame"; + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "", 0, DATA_FLAG_NONE)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "Data frame with max stream ID"; + const unsigned char kFrameData[] = { + 0x7f, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x05, + 'h', 'e', 'l', 'l', + 'o' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 0x7fffffff, "hello", 5, DATA_FLAG_FIN)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "Large data frame"; + const int kDataSize = 4 * 1024 * 1024; // 4 MB + const std::string kData(kDataSize, 'A'); + const unsigned char kFrameHeader[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x40, 0x00, 0x00, + }; + + const int kFrameSize = arraysize(kFrameHeader) + kDataSize; + scoped_array<unsigned char> expected_frame_data( + new unsigned char[kFrameSize]); + memcpy(expected_frame_data.get(), kFrameHeader, arraysize(kFrameHeader)); + memset(expected_frame_data.get() + arraysize(kFrameHeader), 'A', kDataSize); + + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, kData.data(), kData.size(), DATA_FLAG_FIN)); + CompareFrame(kDescription, *frame, expected_frame_data.get(), kFrameSize); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateSynStreamUncompressed) { + SpdyFramer framer(kVer); + framer.set_enable_compression(false); + + { + const char kDescription[] = "SYN_STREAM frame, lowest pri, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kPri = + (SPDY_VERSION_FOR_TESTS != 2) ? 0xE0 : 0xC0; + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + kPri, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x2a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + kPri, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'b', + 'a', 'r' + }; + scoped_ptr<SpdySynStreamControlFrame> frame(framer.CreateSynStream( + 1, 0, framer.GetLowestPriority(), CONTROL_FLAG_NONE, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get())); + } + + { + const char kDescription[] = + "SYN_STREAM frame with a 0-length header name, highest pri, FIN, " + "max stream ID"; + + SpdyHeaderBlock headers; + headers[""] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x1D, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x27, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( + 0x7fffffff, 0x7fffffff, framer.GetHighestPriority(), CONTROL_FLAG_FIN, + false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } + + { + const char kDescription[] = + "SYN_STREAM frame with a 0-length header val, high pri, FIN, " + "max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kPri = + (SPDY_VERSION_FOR_TESTS != 2) ? 0x20 : 0x40; + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x1D, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + kPri, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x27, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + kPri, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x00 + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( + 0x7fffffff, 0x7fffffff, 1, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateSynStreamCompressed) { + SpdyFramer framer(kVer); + framer.set_enable_compression(true); + + { + const char kDescription[] = + "SYN_STREAM frame, low pri, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const SpdyPriority priority = + (SPDY_VERSION_FOR_TESTS != 2) ? 4 : 2; + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x25, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x4e, 0xcb, 0xcf, + 0x87, 0x12, 0x40, 0x2e, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x38, 0xEA, + 0xE3, 0xC6, 0xA7, 0xC2, + 0x02, 0xE5, 0x0E, 0x50, + 0xC2, 0x4B, 0x4A, 0x04, + 0xE5, 0x0B, 0xE6, 0xB4, + 0xFC, 0x7C, 0x24, 0x0A, + 0x28, 0x08, 0x00, 0x00, + 0x00, 0xFF, 0xFF + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( + 1, 0, priority, CONTROL_FLAG_NONE, true, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateSynReplyUncompressed) { + SpdyFramer framer(kVer); + framer.set_enable_compression(false); + + { + const char kDescription[] = "SYN_REPLY frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 1, CONTROL_FLAG_NONE, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } + + { + const char kDescription[] = + "SYN_REPLY frame with a 0-length header name, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers[""] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } + + { + const char kDescription[] = + "SYN_REPLY frame with a 0-length header val, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateSynReplyCompressed) { + SpdyFramer framer(kVer); + framer.set_enable_compression(true); + + { + const char kDescription[] = "SYN_REPLY frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x4e, 0xcb, 0xcf, + 0x87, 0x12, 0x40, 0x2e, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0xe6, 0xb4, 0xfc, 0x7c, + 0x24, 0x0a, 0x28, 0x08, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 1, CONTROL_FLAG_NONE, true, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateRstStream) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "RST_STREAM frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyRstStreamControlFrame> frame( + framer.CreateRstStream(1, PROTOCOL_ERROR)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get())); + } + + { + const char kDescription[] = "RST_STREAM frame with max stream ID"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF, + PROTOCOL_ERROR)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "RST_STREAM frame with max status code"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x06, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF, + INTERNAL_ERROR)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateSettings) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "Network byte order SETTINGS frame"; + + uint32 kValue = 0x0a0b0c0d; + uint8 kFlags = 0x04; + uint32 kId = 0x030201; + SettingsFlagsAndId idAndFlags(kFlags, kId); + + SpdySettings settings; + settings.push_back(SpdySetting(idAndFlags, kValue)); + + EXPECT_EQ(kValue, settings.back().second); + EXPECT_EQ(kFlags, settings.back().first.flags()); + EXPECT_EQ(kId, settings.back().first.id()); + + const unsigned char kFrameDatav2[] = { + 0x80, kVer, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x02, 0x03, 0x04, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + + const unsigned char kFrameDatav3[] = { + 0x80, kVer, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, + 0x04, 0x03, 0x02, 0x01, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + + scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kFrameDatav2 : kFrameDatav3, + arraysize(kFrameDatav3)); // Size is unchanged among versions. + EXPECT_EQ(SpdyFramer::kInvalidStream, + SpdyFramer::GetControlFrameStreamId(frame.get())); + + // Make sure that ParseSettings also works as advertised. + SpdySettings parsed_settings; + EXPECT_TRUE(framer.ParseSettings(frame.get(), &parsed_settings)); + EXPECT_EQ(settings.size(), parsed_settings.size()); + EXPECT_EQ(kFlags, parsed_settings.back().first.flags()); + EXPECT_EQ(kId, parsed_settings.back().first.id()); + } + + { + const char kDescription[] = "Basic SETTINGS frame"; + + SpdySettings settings; + settings.push_back( + SpdySettingFromWireFormat(0x00000000, 0x00000000)); // 1st Setting + settings.push_back( + SpdySettingFromWireFormat(0xffffffff, 0x00000001)); // 2nd Setting + settings.push_back( + SpdySettingFromWireFormat(0xff000001, 0x00000002)); // 3rd Setting + + // Duplicates allowed + settings.push_back( + SpdySettingFromWireFormat(0x01000002, 0x00000003)); // 4th Setting + settings.push_back( + SpdySettingFromWireFormat(0x01000002, 0x00000003)); // 5th Setting + + settings.push_back( + SpdySettingFromWireFormat(0x01000003, 0x000000ff)); // 6th Setting + settings.push_back( + SpdySettingFromWireFormat(0x01000004, 0xff000001)); // 7th Setting + settings.push_back( + SpdySettingFromWireFormat(0x01000004, 0xffffffff)); // 8th Setting + + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x44, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, // 1st Setting + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, // 2nd Setting + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0xff, // 3rd Setting + 0x00, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x01, // 4th Setting + 0x00, 0x00, 0x00, 0x03, + 0x02, 0x00, 0x00, 0x01, // 5th Setting + 0x00, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x01, // 6th Setting + 0x00, 0x00, 0x00, 0xff, + 0x04, 0x00, 0x00, 0x01, // 7th Setting + 0xff, 0x00, 0x00, 0x01, + 0x04, 0x00, 0x00, 0x01, // 8th Setting + 0xff, 0xff, 0xff, 0xff, + }; + scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings)); + CompareFrame(kDescription, + *frame, + kFrameData, + arraysize(kFrameData)); + EXPECT_EQ(SpdyFramer::kInvalidStream, + SpdyFramer::GetControlFrameStreamId(frame.get())); + } + + { + const char kDescription[] = "Empty SETTINGS frame"; + + SpdySettings settings; + + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreatePingFrame) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "PING frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x04, + 0x12, 0x34, 0x56, 0x78, + }; + scoped_ptr<SpdyPingControlFrame> frame(framer.CreatePingFrame(0x12345678u)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + EXPECT_EQ(SpdyFramer::kInvalidStream, + SpdyFramer::GetControlFrameStreamId(frame.get())); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateGoAway) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "GOAWAY frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, + }; + scoped_ptr<SpdyGoAwayControlFrame> frame(framer.CreateGoAway(0)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + EXPECT_EQ(SpdyFramer::kInvalidStream, + SpdyFramer::GetControlFrameStreamId(frame.get())); + } + + { + const char kDescription[] = "GOAWAY frame with max stream ID"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x04, + 0x7f, 0xff, 0xff, 0xff, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateHeadersUncompressed) { + SpdyFramer framer(kVer); + framer.set_enable_compression(false); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 1, CONTROL_FLAG_NONE, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header name, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers[""] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateHeadersCompressed) { + SpdyFramer framer(kVer); + framer.set_enable_compression(true); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x4e, 0xcb, 0xcf, + 0x87, 0x12, 0x40, 0x2e, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + const unsigned char kV3FrameData[] = { + 0x80, kVer, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0xe6, 0xb4, 0xfc, 0x7c, + 0x24, 0x0a, 0x28, 0x08, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 1, CONTROL_FLAG_NONE, true, &headers)); + CompareFrame(kDescription, + *frame, + (SPDY_VERSION_FOR_TESTS < 3) ? kV2FrameData : kV3FrameData, + (SPDY_VERSION_FOR_TESTS < 3) ? arraysize(kV2FrameData) + : arraysize(kV3FrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, CreateWindowUpdate) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "WINDOW_UPDATE frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyWindowUpdateControlFrame> frame( + framer.CreateWindowUpdate(1, 1)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get())); + } + + { + const char kDescription[] = "WINDOW_UPDATE frame with max stream ID"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = "WINDOW_UPDATE frame with max window delta"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x7f, 0xff, 0xff, 0xff, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerSpdy3Test, DuplicateFrame) { + SpdyFramer framer(kVer); + + { + const char kDescription[] = "PING frame"; + const unsigned char kFrameData[] = { + 0x80, kVer, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x04, + 0x12, 0x34, 0x56, 0x78, + }; + scoped_ptr<SpdyFrame> frame1(framer.CreatePingFrame(0x12345678u)); + CompareFrame(kDescription, *frame1, kFrameData, arraysize(kFrameData)); + + scoped_ptr<SpdyFrame> frame2(framer.DuplicateFrame(*frame1)); + CompareFrame(kDescription, *frame2, kFrameData, arraysize(kFrameData)); + } +} + +// This test case reproduces conditions that caused ExpandControlFrameBuffer to +// fail to expand the buffer control frame buffer when it should have, allowing +// the framer to overrun the buffer, and smash other heap contents. This test +// relies on the debug version of the heap manager, which checks for buffer +// overrun errors during delete processing. Regression test for b/2974814. +TEST_F(SpdyFramerSpdy3Test, ExpandBuffer_HeapSmash) { + // Sweep through the area of problematic values, to make sure we always cover + // the danger zone, even if it moves around at bit due to SPDY changes. + for (uint16 val2_len = SpdyFramer::kControlFrameBufferInitialSize - 50; + val2_len < SpdyFramer::kControlFrameBufferInitialSize; + val2_len++) { + std::string val2 = std::string(val2_len, 'a'); + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "baz"; + headers["grue"] = val2.c_str(); + SpdyFramer framer(kVer); + scoped_ptr<SpdySynStreamControlFrame> template_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + CONTROL_FLAG_NONE, + false, // compress + &headers)); + EXPECT_TRUE(template_frame.get() != NULL); + TestSpdyVisitor visitor; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(template_frame.get()->data()), + template_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.syn_frame_count_); + } +} + +TEST_F(SpdyFramerSpdy3Test, ControlFrameSizesAreValidated) { + // Create a GoAway frame that has a few extra bytes at the end. + // We create enough overhead to require the framer to expand its frame buffer. + size_t overhead = SpdyFramer::kUncompressedControlFrameBufferInitialSize; + SpdyFramer framer(kVer); + scoped_ptr<SpdyGoAwayControlFrame> goaway(framer.CreateGoAway(1)); + goaway->set_length(goaway->length() + overhead); + std::string pad('A', overhead); + TestSpdyVisitor visitor; + + // First attempt without validation on. + visitor.framer_.set_validate_control_frame_sizes(false); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(goaway->data()), + goaway->length() - overhead + SpdyControlFrame::kHeaderSize); + visitor.SimulateInFramer( + reinterpret_cast<const unsigned char*>(pad.c_str()), + overhead); + EXPECT_EQ(0, visitor.error_count_); // Not an error. + EXPECT_EQ(1, visitor.goaway_count_); // The goaway was parsed. + + // Attempt with validation on. + visitor.framer_.set_validate_control_frame_sizes(true); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(goaway->data()), + goaway->length() - overhead + SpdyControlFrame::kHeaderSize); + visitor.SimulateInFramer( + reinterpret_cast<const unsigned char*>(pad.c_str()), + overhead); + EXPECT_EQ(1, visitor.error_count_); // This generated an error. + EXPECT_EQ(1, visitor.goaway_count_); // Unchanged from before. +} + +TEST_F(SpdyFramerSpdy3Test, ReadZeroLenSettingsFrame) { + SpdyFramer framer(kVer); + SpdySettings settings; + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + control_frame->set_length(0); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + // Should generate an error, since zero-len settings frames are unsupported. + EXPECT_EQ(1, visitor.error_count_); +} + +// Tests handling of SETTINGS frames with invalid length. +TEST_F(SpdyFramerSpdy3Test, ReadBogusLenSettingsFrame) { + SpdyFramer framer(kVer); + SpdySettings settings; + // Add a setting to pad the frame so that we don't get a buffer overflow when + // calling SimulateInFramer() below. + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + control_frame->set_length(5); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + // Should generate an error, since zero-len settings frames are unsupported. + EXPECT_EQ(1, visitor.error_count_); +} + +// Tests handling of SETTINGS frames larger than the frame buffer size. +TEST_F(SpdyFramerSpdy3Test, ReadLargeSettingsFrame) { + SpdyFramer framer(kVer); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 2), 0x00000003)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 3), 0x00000004)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + EXPECT_LT(SpdyFramer::kUncompressedControlFrameBufferInitialSize, + control_frame->length() + SpdyControlFrame::kHeaderSize); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + + // Read all at once. + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(settings.size(), static_cast<unsigned>(visitor.setting_count_)); + EXPECT_EQ(1, visitor.settings_frame_count_); + + // Read data in small chunks. + size_t framed_data = 0; + size_t unframed_data = control_frame->length() + + SpdyControlFrame::kHeaderSize; + size_t kReadChunkSize = 5; // Read five bytes at a time. + while (unframed_data > 0) { + size_t to_read = std::min(kReadChunkSize, unframed_data); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data() + framed_data), + to_read); + unframed_data -= to_read; + framed_data += to_read; + } + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(settings.size() * 2, static_cast<unsigned>(visitor.setting_count_)); + EXPECT_EQ(2, visitor.settings_frame_count_); +} + +// Tests handling of SETTINGS frame with duplicate entries. +TEST_F(SpdyFramerSpdy3Test, ReadDuplicateSettings) { + SpdyFramer framer(kVer); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000003)); + // This last setting should not be processed due to error above. + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 3), 0x00000003)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_frame_count_); +} + +// Tests handling of SETTINGS frame with entries out of order. +TEST_F(SpdyFramerSpdy3Test, ReadOutOfOrderSettings) { + SpdyFramer framer(kVer); + SpdySettings settings; + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 2), 0x00000002)); + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 1), 0x00000003)); + // This last setting should not be processed due to error above. + settings.push_back(SpdySetting(SettingsFlagsAndId(0, 3), 0x00000003)); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_frame_count_); +} + +TEST_F(SpdyFramerSpdy3Test, ReadCredentialFrame) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(kVer); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.credential_count_); + EXPECT_EQ(control_frame->length(), visitor.credential_buffer_length_); + EXPECT_EQ(credential.slot, visitor.credential_.slot); + EXPECT_EQ(credential.proof, visitor.credential_.proof); + EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size()); + for (size_t i = 0; i < credential.certs.size(); i++) { + EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]); + } +} + +TEST_F(SpdyFramerSpdy3Test, ReadCredentialFrameWithCorruptProof) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(kVer); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + unsigned char* data = + reinterpret_cast<unsigned char*>(control_frame.get()->data()); + size_t offset = SpdyControlFrame::kHeaderSize + 4; + data[offset] = 0xFF; // Proof length is past the end of the frame + visitor.SimulateInFramer( + data, control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_F(SpdyFramerSpdy3Test, ReadCredentialFrameWithCorruptCertificate) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(kVer); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + unsigned char* data = + reinterpret_cast<unsigned char*>(control_frame.get()->data()); + size_t offset = SpdyControlFrame::kHeaderSize + credential.proof.length(); + data[offset] = 0xFF; // Certificate length is past the end of the frame + visitor.SimulateInFramer( + data, control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_F(SpdyFramerSpdy3Test, ReadGarbage) { + SpdyFramer framer(kVer); + unsigned char garbage_frame[256]; + memset(garbage_frame, ~0, sizeof(garbage_frame)); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_F(SpdyFramerSpdy3Test, ReadGarbageWithValidVersion) { + SpdyFramer framer(kVer); + char garbage_frame[256]; + memset(garbage_frame, ~0, sizeof(garbage_frame)); + SpdyControlFrame control_frame(&garbage_frame[0], false); + control_frame.set_version(SPDY_VERSION_FOR_TESTS); + TestSpdyVisitor visitor; + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + sizeof(garbage_frame)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_F(SpdyFramerSpdy3Test, StateToStringTest) { + EXPECT_STREQ("ERROR", + SpdyFramer::StateToString(SpdyFramer::SPDY_ERROR)); + EXPECT_STREQ("DONE", + SpdyFramer::StateToString(SpdyFramer::SPDY_DONE)); + EXPECT_STREQ("AUTO_RESET", + SpdyFramer::StateToString(SpdyFramer::SPDY_AUTO_RESET)); + EXPECT_STREQ("RESET", + SpdyFramer::StateToString(SpdyFramer::SPDY_RESET)); + EXPECT_STREQ("READING_COMMON_HEADER", + SpdyFramer::StateToString( + SpdyFramer::SPDY_READING_COMMON_HEADER)); + EXPECT_STREQ("CONTROL_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CONTROL_FRAME_PAYLOAD)); + EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_IGNORE_REMAINING_PAYLOAD)); + EXPECT_STREQ("FORWARD_STREAM_FRAME", + SpdyFramer::StateToString( + SpdyFramer::SPDY_FORWARD_STREAM_FRAME)); + EXPECT_STREQ("SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK)); + EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CONTROL_FRAME_HEADER_BLOCK)); + EXPECT_STREQ("SPDY_CREDENTIAL_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CREDENTIAL_FRAME_PAYLOAD)); + EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD)); + EXPECT_STREQ("UNKNOWN_STATE", + SpdyFramer::StateToString( + SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD + 1)); +} + +TEST_F(SpdyFramerSpdy3Test, ErrorCodeToStringTest) { + EXPECT_STREQ("NO_ERROR", + SpdyFramer::ErrorCodeToString(SpdyFramer::SPDY_NO_ERROR)); + EXPECT_STREQ("INVALID_CONTROL_FRAME", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_INVALID_CONTROL_FRAME)); + EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE)); + EXPECT_STREQ("ZLIB_INIT_FAILURE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_ZLIB_INIT_FAILURE)); + EXPECT_STREQ("UNSUPPORTED_VERSION", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_UNSUPPORTED_VERSION)); + EXPECT_STREQ("DECOMPRESS_FAILURE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_DECOMPRESS_FAILURE)); + EXPECT_STREQ("COMPRESS_FAILURE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_COMPRESS_FAILURE)); + EXPECT_STREQ("UNKNOWN_ERROR", + SpdyFramer::ErrorCodeToString(SpdyFramer::LAST_ERROR)); +} + +TEST_F(SpdyFramerSpdy3Test, StatusCodeToStringTest) { + EXPECT_STREQ("INVALID", + SpdyFramer::StatusCodeToString(INVALID)); + EXPECT_STREQ("PROTOCOL_ERROR", + SpdyFramer::StatusCodeToString(PROTOCOL_ERROR)); + EXPECT_STREQ("INVALID_STREAM", + SpdyFramer::StatusCodeToString(INVALID_STREAM)); + EXPECT_STREQ("REFUSED_STREAM", + SpdyFramer::StatusCodeToString(REFUSED_STREAM)); + EXPECT_STREQ("UNSUPPORTED_VERSION", + SpdyFramer::StatusCodeToString(UNSUPPORTED_VERSION)); + EXPECT_STREQ("CANCEL", + SpdyFramer::StatusCodeToString(CANCEL)); + EXPECT_STREQ("INTERNAL_ERROR", + SpdyFramer::StatusCodeToString(INTERNAL_ERROR)); + EXPECT_STREQ("FLOW_CONTROL_ERROR", + SpdyFramer::StatusCodeToString(FLOW_CONTROL_ERROR)); + EXPECT_STREQ("UNKNOWN_STATUS", + SpdyFramer::StatusCodeToString(NUM_STATUS_CODES)); +} + +TEST_F(SpdyFramerSpdy3Test, ControlTypeToStringTest) { + EXPECT_STREQ("SYN_STREAM", + SpdyFramer::ControlTypeToString(SYN_STREAM)); + EXPECT_STREQ("SYN_REPLY", + SpdyFramer::ControlTypeToString(SYN_REPLY)); + EXPECT_STREQ("RST_STREAM", + SpdyFramer::ControlTypeToString(RST_STREAM)); + EXPECT_STREQ("SETTINGS", + SpdyFramer::ControlTypeToString(SETTINGS)); + EXPECT_STREQ("NOOP", + SpdyFramer::ControlTypeToString(NOOP)); + EXPECT_STREQ("PING", + SpdyFramer::ControlTypeToString(PING)); + EXPECT_STREQ("GOAWAY", + SpdyFramer::ControlTypeToString(GOAWAY)); + EXPECT_STREQ("HEADERS", + SpdyFramer::ControlTypeToString(HEADERS)); + EXPECT_STREQ("WINDOW_UPDATE", + SpdyFramer::ControlTypeToString(WINDOW_UPDATE)); + EXPECT_STREQ("CREDENTIAL", + SpdyFramer::ControlTypeToString(CREDENTIAL)); + EXPECT_STREQ("UNKNOWN_CONTROL_TYPE", + SpdyFramer::ControlTypeToString(NUM_CONTROL_FRAME_TYPES)); +} + +TEST_F(SpdyFramerSpdy3Test, GetMinimumControlFrameSizeTest) { + EXPECT_EQ(SpdySynStreamControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(SYN_STREAM)); + EXPECT_EQ(SpdySynReplyControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(SYN_REPLY)); + EXPECT_EQ(SpdyRstStreamControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(RST_STREAM)); + EXPECT_EQ(SpdySettingsControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(SETTINGS)); + EXPECT_EQ(SpdyFrame::kHeaderSize, + SpdyFramer::GetMinimumControlFrameSize(NOOP)); + EXPECT_EQ(SpdyPingControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(PING)); + EXPECT_EQ(SpdyGoAwayControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(GOAWAY)); + EXPECT_EQ(SpdyHeadersControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(HEADERS)); + EXPECT_EQ(SpdyWindowUpdateControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(WINDOW_UPDATE)); + EXPECT_EQ(SpdyCredentialControlFrame::size(), + SpdyFramer::GetMinimumControlFrameSize(CREDENTIAL)); + EXPECT_EQ(static_cast<size_t>(0x7FFFFFFF), + SpdyFramer::GetMinimumControlFrameSize(NUM_CONTROL_FRAME_TYPES)); +} + +TEST_F(SpdyFramerSpdy3Test, CatchProbableHttpResponse) { + SpdyFramerTestUtil::DecompressionVisitor visitor; + visitor.set_allow_data_frames(true); + { + SpdyFramer framer(kVer); + framer.set_visitor(&visitor); + framer.ProcessInput("HTTP/1.1", 8); + EXPECT_TRUE(framer.probable_http_response()); + } + { + SpdyFramer framer(kVer); + framer.set_visitor(&visitor); + framer.ProcessInput("HTTP/1.0", 8); + EXPECT_TRUE(framer.probable_http_response()); + } +} + +TEST_F(SpdyFramerSpdy3Test, SettingsFlagsAndId) { + const uint32 kId = 0x020304; + const uint32 kFlags = 0x01; + const uint32 kWireFormat = + htonl((SPDY_VERSION_FOR_TESTS < 3) ? 0x04030201 : 0x01020304); + + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, kWireFormat); + EXPECT_EQ(kId, id_and_flags.id()); + EXPECT_EQ(kFlags, id_and_flags.flags()); + EXPECT_EQ(kWireFormat, id_and_flags.GetWireFormat(SPDY_VERSION_FOR_TESTS)); +} + +} // namespace diff --git a/net/spdy/spdy_http_stream_spdy2_unittest.cc b/net/spdy/spdy_http_stream_spdy2_unittest.cc index 244f84e..836d50dc 100644 --- a/net/spdy/spdy_http_stream_spdy2_unittest.cc +++ b/net/spdy/spdy_http_stream_spdy2_unittest.cc @@ -29,10 +29,15 @@ class SpdyHttpStreamSpdy2Test : public testing::Test { spdy::SpdyFramer::set_enable_compression_default(enabled); } + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); + } + virtual void TearDown() { crypto::ECSignatureCreator::SetFactoryForTesting(NULL); MessageLoop::current()->RunAllPending(); } + int InitSession(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count, HostPortPair& host_port_pair) { @@ -447,22 +452,6 @@ class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory { DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory); }; -TEST_F(SpdyHttpStreamSpdy2Test, SendCredentialsEC) { - scoped_ptr<crypto::ECSignatureCreatorFactory> ec_signature_creator_factory( - new MockECSignatureCreatorFactory()); - crypto::ECSignatureCreator::SetFactoryForTesting( - ec_signature_creator_factory.get()); - - scoped_ptr<OriginBoundCertService> obc_service( - new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL))); - std::string cert; - std::string proof; - GetECOriginBoundCertAndProof("http://www.gmail.com/", obc_service.get(), - &cert, &proof); - - TestSendCredentials(obc_service.get(), cert, proof, CLIENT_CERT_ECDSA_SIGN); -} - #endif // !defined(USE_OPENSSL) // TODO(willchan): Write a longer test for SpdyStream that exercises all diff --git a/net/spdy/spdy_http_stream_spdy3_unittest.cc b/net/spdy/spdy_http_stream_spdy3_unittest.cc index 79390df..49f61a6 100644 --- a/net/spdy/spdy_http_stream_spdy3_unittest.cc +++ b/net/spdy/spdy_http_stream_spdy3_unittest.cc @@ -29,10 +29,15 @@ class SpdyHttpStreamSpdy3Test : public testing::Test { spdy::SpdyFramer::set_enable_compression_default(enabled); } + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); + } + virtual void TearDown() { crypto::ECSignatureCreator::SetFactoryForTesting(NULL); MessageLoop::current()->RunAllPending(); } + int InitSession(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count, HostPortPair& host_port_pair) { diff --git a/net/spdy/spdy_network_transaction_spdy21_unittest.cc b/net/spdy/spdy_network_transaction_spdy21_unittest.cc index e4a5f22..39a7473 100644 --- a/net/spdy/spdy_network_transaction_spdy21_unittest.cc +++ b/net/spdy/spdy_network_transaction_spdy21_unittest.cc @@ -41,6 +41,7 @@ class SpdyNetworkTransactionSpdy21Test virtual void SetUp() { // By default, all tests turn off compression. EnableCompression(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY21); google_get_request_initialized_ = false; google_post_request_initialized_ = false; google_chunked_post_request_initialized_ = false; @@ -939,8 +940,7 @@ TEST_P(SpdyNetworkTransactionSpdy21Test, ThreeGetsWithMaxConcurrent) { scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1078,8 +1078,7 @@ TEST_P(SpdyNetworkTransactionSpdy21Test, FourGetsWithMaxConcurrentPriority) { spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1223,8 +1222,7 @@ TEST_P(SpdyNetworkTransactionSpdy21Test, ThreeGetsWithMaxConcurrentDelete) { scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1360,8 +1358,7 @@ TEST_P(SpdyNetworkTransactionSpdy21Test, scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -4445,25 +4442,23 @@ TEST_P(SpdyNetworkTransactionSpdy21Test, SettingsSaved) { unsigned int kSampleValue1 = 0x0a0a0a0a; unsigned int kSampleId2 = 0x2; unsigned int kSampleValue2 = 0x0b0b0b0b; - unsigned int kSampleId3 = 0xababab; + unsigned int kSampleId3 = 0x3; unsigned int kSampleValue3 = 0x0c0c0c0c; scoped_ptr<spdy::SpdyFrame> settings_frame; { // Construct the SETTINGS frame. spdy::SpdySettings settings; - spdy::SettingsFlagsAndId setting(0); // First add a persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId1); - settings.push_back(std::make_pair(setting, kSampleValue1)); + spdy::SettingsFlagsAndId setting1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId1); + settings.push_back(std::make_pair(setting1, kSampleValue1)); // Next add a non-persisted setting - setting.set_flags(0); - setting.set_id(kSampleId2); - settings.push_back(std::make_pair(setting, kSampleValue2)); + spdy::SettingsFlagsAndId setting2(0, kSampleId2); + settings.push_back(std::make_pair(setting2, kSampleValue2)); // Next add another persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId3); - settings.push_back(std::make_pair(setting, kSampleValue3)); + spdy::SettingsFlagsAndId setting3(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId3); + settings.push_back(std::make_pair(setting3, kSampleValue3)); settings_frame.reset(ConstructSpdySettings(settings)); } @@ -4547,15 +4542,14 @@ TEST_P(SpdyNetworkTransactionSpdy21Test, SettingsPlayback) { // Manually insert settings into the SpdySettingsStorage here. { spdy::SpdySettings settings; - spdy::SettingsFlagsAndId setting(0); // First add a persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId1); - settings.push_back(std::make_pair(setting, kSampleValue1)); + spdy::SettingsFlagsAndId setting1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId1); + settings.push_back(std::make_pair(setting1, kSampleValue1)); // Next add another persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId2); - settings.push_back(std::make_pair(setting, kSampleValue2)); + spdy::SettingsFlagsAndId setting2(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId2); + settings.push_back(std::make_pair(setting2, kSampleValue2)); spdy_session_pool->http_server_properties()->SetSpdySettings( host_port_pair, settings); diff --git a/net/spdy/spdy_network_transaction_spdy2_unittest.cc b/net/spdy/spdy_network_transaction_spdy2_unittest.cc index a083b53..1e9d335 100644 --- a/net/spdy/spdy_network_transaction_spdy2_unittest.cc +++ b/net/spdy/spdy_network_transaction_spdy2_unittest.cc @@ -41,6 +41,7 @@ class SpdyNetworkTransactionSpdy2Test virtual void SetUp() { // By default, all tests turn off compression. EnableCompression(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); google_get_request_initialized_ = false; google_post_request_initialized_ = false; google_chunked_post_request_initialized_ = false; @@ -939,8 +940,7 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrent) { scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1078,8 +1078,7 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, FourGetsWithMaxConcurrentPriority) { spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1223,8 +1222,7 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentDelete) { scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1359,8 +1357,7 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentSocketClose) { scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -2789,8 +2786,6 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushMultipleDataFrame) { TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushMultipleDataFrameInterrupted) { - SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); - static const unsigned char kPushBodyFrame1[] = { 0x00, 0x00, 0x00, 0x02, // header, ID 0x01, 0x00, 0x00, 0x1F, // FIN, length @@ -2852,8 +2847,6 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, // Verify the pushed stream. EXPECT_TRUE(response2.headers != NULL); EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); - - SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); } TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID0) { @@ -3568,8 +3561,6 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, NetLog) { // on the network, but issued a Read for only 5 of those bytes) that the data // flow still works correctly. TEST_P(SpdyNetworkTransactionSpdy2Test, BufferFull) { - SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); - spdy::BufferedSpdyFramer framer(2); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -3659,16 +3650,12 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, BufferFull) { EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("goodbye world", out.response_data); - - SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); } // Verify that basic buffering works; when multiple data frames arrive // at the same time, ensure that we don't notify a read completion for // each data frame individually. TEST_P(SpdyNetworkTransactionSpdy2Test, Buffering) { - SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); - spdy::BufferedSpdyFramer framer(2); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -3760,8 +3747,6 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, Buffering) { EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("messagemessagemessagemessage", out.response_data); - - SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); } // Verify the case where we buffer data but read it after it has been buffered. @@ -4065,25 +4050,23 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, SettingsSaved) { unsigned int kSampleValue1 = 0x0a0a0a0a; unsigned int kSampleId2 = 0x2; unsigned int kSampleValue2 = 0x0b0b0b0b; - unsigned int kSampleId3 = 0xababab; + unsigned int kSampleId3 = 0x3; unsigned int kSampleValue3 = 0x0c0c0c0c; scoped_ptr<spdy::SpdyFrame> settings_frame; { // Construct the SETTINGS frame. spdy::SpdySettings settings; - spdy::SettingsFlagsAndId setting(0); // First add a persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId1); - settings.push_back(std::make_pair(setting, kSampleValue1)); + spdy::SettingsFlagsAndId setting1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId1); + settings.push_back(std::make_pair(setting1, kSampleValue1)); // Next add a non-persisted setting - setting.set_flags(0); - setting.set_id(kSampleId2); - settings.push_back(std::make_pair(setting, kSampleValue2)); + spdy::SettingsFlagsAndId setting2(0, kSampleId2); + settings.push_back(std::make_pair(setting2, kSampleValue2)); // Next add another persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId3); - settings.push_back(std::make_pair(setting, kSampleValue3)); + spdy::SettingsFlagsAndId setting3(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId3); + settings.push_back(std::make_pair(setting3, kSampleValue3)); settings_frame.reset(ConstructSpdySettings(settings)); } @@ -4167,15 +4150,14 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, SettingsPlayback) { // Manually insert settings into the SpdySettingsStorage here. { spdy::SpdySettings settings; - spdy::SettingsFlagsAndId setting(0); // First add a persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId1); - settings.push_back(std::make_pair(setting, kSampleValue1)); + spdy::SettingsFlagsAndId setting1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId1); + settings.push_back(std::make_pair(setting1, kSampleValue1)); // Next add another persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId2); - settings.push_back(std::make_pair(setting, kSampleValue2)); + spdy::SettingsFlagsAndId setting2(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId2); + settings.push_back(std::make_pair(setting2, kSampleValue2)); spdy_session_pool->http_server_properties()->SetSpdySettings( host_port_pair, settings); diff --git a/net/spdy/spdy_network_transaction_spdy3_unittest.cc b/net/spdy/spdy_network_transaction_spdy3_unittest.cc index c156345..be8eb99 100644 --- a/net/spdy/spdy_network_transaction_spdy3_unittest.cc +++ b/net/spdy/spdy_network_transaction_spdy3_unittest.cc @@ -41,6 +41,7 @@ class SpdyNetworkTransactionSpdy3Test virtual void SetUp() { // By default, all tests turn off compression. EnableCompression(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); google_get_request_initialized_ = false; google_post_request_initialized_ = false; google_chunked_post_request_initialized_ = false; @@ -125,12 +126,13 @@ class SpdyNetworkTransactionSpdy3Test next_protos.push_back("http/1.1"); next_protos.push_back("spdy/2"); next_protos.push_back("spdy/2.1"); + next_protos.push_back("spdy/3"); switch (test_type_) { case SPDYNPN: session_->http_server_properties()->SetAlternateProtocol( HostPortPair("www.google.com", 80), 443, - NPN_SPDY_21); + NPN_SPDY_3); HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(next_protos); break; @@ -236,7 +238,7 @@ class SpdyNetworkTransactionSpdy3Test linked_ptr<SSLSocketDataProvider> ssl_( new SSLSocketDataProvider(ASYNC, OK)); if (test_type_ == SPDYNPN) { - ssl_->SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl_->SetNextProto(SSLClientSocket::kProtoSPDY3); } ssl_vector_.push_back(ssl_); if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) @@ -261,7 +263,7 @@ class SpdyNetworkTransactionSpdy3Test linked_ptr<SSLSocketDataProvider> ssl_( new SSLSocketDataProvider(ASYNC, OK)); if (test_type_ == SPDYNPN) { - ssl_->SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl_->SetNextProto(SSLClientSocket::kProtoSPDY3); } ssl_vector_.push_back(ssl_); if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) { @@ -939,8 +941,7 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrent) { scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1078,8 +1079,7 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, FourGetsWithMaxConcurrentPriority) { spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1223,8 +1223,7 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentDelete) { scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -1359,8 +1358,7 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentSocketClose) { scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 1; settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -4444,25 +4442,23 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, SettingsSaved) { unsigned int kSampleValue1 = 0x0a0a0a0a; unsigned int kSampleId2 = 0x2; unsigned int kSampleValue2 = 0x0b0b0b0b; - unsigned int kSampleId3 = 0xababab; + unsigned int kSampleId3 = 0x3; unsigned int kSampleValue3 = 0x0c0c0c0c; scoped_ptr<spdy::SpdyFrame> settings_frame; { // Construct the SETTINGS frame. spdy::SpdySettings settings; - spdy::SettingsFlagsAndId setting(0); // First add a persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId1); - settings.push_back(std::make_pair(setting, kSampleValue1)); + spdy::SettingsFlagsAndId setting1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId1); + settings.push_back(std::make_pair(setting1, kSampleValue1)); // Next add a non-persisted setting - setting.set_flags(0); - setting.set_id(kSampleId2); - settings.push_back(std::make_pair(setting, kSampleValue2)); + spdy::SettingsFlagsAndId setting2(0, kSampleId2); + settings.push_back(std::make_pair(setting2, kSampleValue2)); // Next add another persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId3); - settings.push_back(std::make_pair(setting, kSampleValue3)); + spdy::SettingsFlagsAndId setting3(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId3); + settings.push_back(std::make_pair(setting3, kSampleValue3)); settings_frame.reset(ConstructSpdySettings(settings)); } @@ -4546,15 +4542,14 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, SettingsPlayback) { // Manually insert settings into the SpdySettingsStorage here. { spdy::SpdySettings settings; - spdy::SettingsFlagsAndId setting(0); // First add a persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId1); - settings.push_back(std::make_pair(setting, kSampleValue1)); + spdy::SettingsFlagsAndId setting1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId1); + settings.push_back(std::make_pair(setting1, kSampleValue1)); // Next add another persisted setting - setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - setting.set_id(kSampleId2); - settings.push_back(std::make_pair(setting, kSampleValue2)); + spdy::SettingsFlagsAndId setting2(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + kSampleId2); + settings.push_back(std::make_pair(setting2, kSampleValue2)); spdy_session_pool->http_server_properties()->SetSpdySettings( host_port_pair, settings); diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h index fcfe8b5..cb09456 100644 --- a/net/spdy/spdy_protocol.h +++ b/net/spdy/spdy_protocol.h @@ -141,17 +141,14 @@ // | Certificate | | // +----------------------------------+ <+ // -namespace spdy { -// This implementation of Spdy is version 2; It's like version 1, with some -// minor tweaks. -const int kSpdyProtocolVersion = 2; +namespace spdy { // Initial window size for a Spdy stream -const size_t kSpdyStreamInitialWindowSize = 64 * 1024; // 64 KBytes +const int32 kSpdyStreamInitialWindowSize = 64 * 1024; // 64 KBytes // Maximum window size for a Spdy stream -const size_t kSpdyStreamMaximumWindowSize = 0x7FFFFFFF; // Max signed 32bit int +const int32 kSpdyStreamMaximumWindowSize = 0x7FFFFFFF; // Max signed 32bit int // HTTP-over-SPDY header constants const char kMethod[] = "method"; @@ -162,6 +159,207 @@ const char kVersion[] = "version"; // push headers so that the client will know what url the data corresponds to. const char kPath[] = "path"; +// SPDY 2 dictionary. +// This is just a hacked dictionary to use for shrinking HTTP-like headers. +const char kV2Dictionary[] = + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + "-agent10010120020120220320420520630030130230330430530630740040140240340440" + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + ".1statusversionurl"; +const int kV2DictionarySize = arraysize(kV2Dictionary); + +// SPDY 3 dictionary. +const char kV3Dictionary[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete.... + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace... + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept. + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding.... + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage. + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age. + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding.... + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content- + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation.. + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5... + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range.. + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type.. + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date.. + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag.. + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match. + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince.... + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none- + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match... + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if- + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation... + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards... + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma. + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate... + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion.... + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range... + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after. + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te. + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding. + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via. + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate.. + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get. + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion.. + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive. + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204. + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503. + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service. + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept. + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov. + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu. + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun.. + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text. + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859- + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-.. + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0. +}; +const int kV3DictionarySize = arraysize(kV3Dictionary); + // Note: all protocol data structures are on-the-wire format. That means that // data is stored in network-normalized order. Readers must use the // accessors provided or call ntohX() functions. @@ -172,7 +370,7 @@ enum SpdyControlType { SYN_REPLY, RST_STREAM, SETTINGS, - NOOP, + NOOP, // Because it is valid in SPDY/2, kept for identifiability/enum order. PING, GOAWAY, HEADERS, @@ -239,7 +437,9 @@ enum SpdyStatusCodes { // A SPDY stream id is a 31 bit entity. typedef uint32 SpdyStreamId; -// A SPDY priority is a number between 0 and 3 (inclusive). +// A SPDY priority is a number between 0 and 7 (inclusive). +// SPDY priority range is version-dependant. For SPDY 2 and below, priority is a +// number between 0 and 3. typedef uint8 SpdyPriority; // SPDY Priorities. (there are only 2 bits) @@ -285,7 +485,6 @@ struct SpdySynStreamControlFrameBlock : SpdyFrameBlock { // A SYN_REPLY Control Frame structure. struct SpdySynReplyControlFrameBlock : SpdyFrameBlock { SpdyStreamId stream_id_; - uint16 unused_; }; // A RST_STREAM Control Frame structure. @@ -300,15 +499,12 @@ struct SpdySettingsControlFrameBlock : SpdyFrameBlock { // Variable data here. }; -// A NOOP Control Frame structure. -struct SpdyNoopControlFrameBlock : SpdyFrameBlock { -}; - // A PING Control Frame structure. struct SpdyPingControlFrameBlock : SpdyFrameBlock { uint32 unique_id_; }; +// TODO(avd): remove this struct // A CREDENTIAL Control Frame structure. struct SpdyCredentialControlFrameBlock : SpdyFrameBlock { uint16 slot_; @@ -326,7 +522,6 @@ struct SpdyGoAwayControlFrameBlock : SpdyFrameBlock { // A HEADERS Control Frame structure. struct SpdyHeadersControlFrameBlock : SpdyFrameBlock { SpdyStreamId stream_id_; - uint16 unused_; }; // A WINDOW_UPDATE Control Frame structure @@ -335,22 +530,6 @@ struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock { uint32 delta_window_size_; }; -// A structure for the 8 bit flags and 24 bit ID fields. -union SettingsFlagsAndId { - // Sets both flags and id to the value for flags-and-id as sent over the wire - SettingsFlagsAndId(uint32 val) : id_(val) {} - uint8 flags() const { return flags_[0]; } - void set_flags(uint8 flags) { flags_[0] = flags; } - uint32 id() const { return (ntohl(id_) & kSettingsIdMask); } - void set_id(uint32 id) { - DCHECK_EQ(0u, (id & ~kSettingsIdMask)); - id_ = htonl((flags() << 24) | (id & kSettingsIdMask)); - } - - uint8 flags_[4]; // 8 bits - uint32 id_; // 24 bits -}; - #pragma pack(pop) // ------------------------------------------------------------------------- @@ -467,7 +646,10 @@ class SpdyControlFrame : public SpdyFrame { bool AppearsToBeAValidControlFrame() const { // Right now we only check if the frame has an out-of-bounds type. uint16 type = ntohs(block()->control_.type_); - return (type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES); + // NOOP is not a 'valid' control frame in SPDY/3 and beyond. + return type >= SYN_STREAM && + type < NUM_CONTROL_FRAME_TYPES && + (version() == 2 || type != NOOP); } uint16 version() const { @@ -476,13 +658,15 @@ class SpdyControlFrame : public SpdyFrame { } void set_version(uint16 version) { - DCHECK_EQ(0u, version & kControlFlagMask); - mutable_block()->control_.version_ = htons(kControlFlagMask | version); + const uint16 kControlBit = 0x80; + DCHECK_EQ(0, version & kControlBit); + mutable_block()->control_.version_ = kControlBit | htons(version); } SpdyControlType type() const { uint16 type = ntohs(block()->control_.type_); - DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES); + LOG_IF(DFATAL, type < SYN_STREAM || type >= NUM_CONTROL_FRAME_TYPES) + << "Invalid control frame type " << type; return static_cast<SpdyControlType>(type); } @@ -497,15 +681,6 @@ class SpdyControlFrame : public SpdyFrame { return type() == SYN_STREAM || type() == SYN_REPLY || type() == HEADERS; } - // The size of the 'Number of Name/Value pairs' field in a Name/Value block. - static const size_t kNumNameValuePairsSize = 2; - - // The size of the 'Length of a name' field in a Name/Value block. - static const size_t kLengthOfNameSize = 2; - - // The size of the 'Length of a value' field in a Name/Value block. - static const size_t kLengthOfValueSize = 2; - private: const struct SpdyFrameBlock* block() const { return frame_; @@ -540,7 +715,11 @@ class SpdySynStreamControlFrame : public SpdyControlFrame { } SpdyPriority priority() const { - return (block()->priority_ & kPriorityMask) >> 6; + if (version() < 3) { + return (block()->priority_ & kSpdy2PriorityMask) >> 6; + } else { + return (block()->priority_ & kSpdy3PriorityMask) >> 5; + } } // The number of bytes in the header block beyond the frame header length. @@ -582,11 +761,21 @@ class SpdySynReplyControlFrame : public SpdyControlFrame { } int header_block_len() const { - return length() - (size() - SpdyFrame::kHeaderSize); + size_t header_block_len = length() - (size() - SpdyFrame::kHeaderSize); + // SPDY 2 had 2 bytes of unused space preceeding the header block. + if (version() < 3) { + header_block_len -= 2; + } + return header_block_len; } const char* header_block() const { - return reinterpret_cast<const char*>(block()) + size(); + const char* header_block = reinterpret_cast<const char*>(block()) + size(); + // SPDY 2 had 2 bytes of unused space preceeding the header block. + if (version() < 3) { + header_block += 2; + } + return header_block; } // Returns the size of the SpdySynReplyControlFrameBlock structure. @@ -680,15 +869,6 @@ class SpdySettingsControlFrame : public SpdyControlFrame { DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame); }; -class SpdyNoOpControlFrame : public SpdyControlFrame { - public: - SpdyNoOpControlFrame() : SpdyControlFrame(size()) {} - SpdyNoOpControlFrame(char* data, bool owns_buffer) - : SpdyControlFrame(data, owns_buffer) {} - - static size_t size() { return sizeof(SpdyNoopControlFrameBlock); } -}; - class SpdyPingControlFrame : public SpdyControlFrame { public: SpdyPingControlFrame() : SpdyControlFrame(size()) {} @@ -776,11 +956,21 @@ class SpdyHeadersControlFrame : public SpdyControlFrame { // The number of bytes in the header block beyond the frame header length. int header_block_len() const { - return length() - (size() - SpdyFrame::kHeaderSize); + size_t header_block_len = length() - (size() - SpdyFrame::kHeaderSize); + // SPDY 2 had 2 bytes of unused space preceeding the header block. + if (version() < 3) { + header_block_len -= 2; + } + return header_block_len; } const char* header_block() const { - return reinterpret_cast<const char*>(block()) + size(); + const char* header_block = reinterpret_cast<const char*>(block()) + size(); + // SPDY 2 had 2 bytes of unused space preceeding the header block. + if (version() < 3) { + header_block += 2; + } + return header_block; } // Returns the size of the SpdyHeadersControlFrameBlock structure. diff --git a/net/spdy/spdy_protocol_test.cc b/net/spdy/spdy_protocol_spdy2_test.cc index 46dd5f0..cfa7951 100644 --- a/net/spdy/spdy_protocol_test.cc +++ b/net/spdy/spdy_protocol_spdy2_test.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -11,6 +11,7 @@ using spdy::CONTROL_FLAG_FIN; using spdy::CONTROL_FLAG_NONE; +using spdy::FlagsAndLength; using spdy::GOAWAY; using spdy::HEADERS; using spdy::NOOP; @@ -20,17 +21,15 @@ using spdy::RST_STREAM; using spdy::SETTINGS; using spdy::SYN_REPLY; using spdy::SYN_STREAM; -using spdy::WINDOW_UPDATE; -using spdy::FlagsAndLength; +using spdy::SettingsFlagsAndId; using spdy::SpdyControlFrame; using spdy::SpdyControlType; using spdy::SpdyDataFrame; using spdy::SpdyFrame; using spdy::SpdyFramer; +using spdy::SpdyGoAwayControlFrame; using spdy::SpdyHeaderBlock; using spdy::SpdyHeadersControlFrame; -using spdy::SpdyGoAwayControlFrame; -using spdy::SpdyNoOpControlFrame; using spdy::SpdyPingControlFrame; using spdy::SpdyRstStreamControlFrame; using spdy::SpdySettings; @@ -39,26 +38,27 @@ using spdy::SpdyStatusCodes; using spdy::SpdySynReplyControlFrame; using spdy::SpdySynStreamControlFrame; using spdy::SpdyWindowUpdateControlFrame; -using spdy::SettingsFlagsAndId; +using spdy::WINDOW_UPDATE; using spdy::kLengthMask; -using spdy::kSpdyProtocolVersion; using spdy::kStreamIdMask; namespace { +// Default SPDY version for unit tests. +const int SPDY_VERSION_FOR_TESTS = 2; + // Test our protocol constants -TEST(SpdyProtocolTest, ProtocolConstants) { +TEST(SpdyProtocolSpdy2Test, ProtocolConstants) { EXPECT_EQ(8u, SpdyFrame::kHeaderSize); EXPECT_EQ(8u, SpdyDataFrame::size()); EXPECT_EQ(8u, SpdyControlFrame::kHeaderSize); EXPECT_EQ(18u, SpdySynStreamControlFrame::size()); - EXPECT_EQ(14u, SpdySynReplyControlFrame::size()); + EXPECT_EQ(12u, SpdySynReplyControlFrame::size()); EXPECT_EQ(16u, SpdyRstStreamControlFrame::size()); EXPECT_EQ(12u, SpdySettingsControlFrame::size()); - EXPECT_EQ(8u, SpdyNoOpControlFrame::size()); EXPECT_EQ(12u, SpdyPingControlFrame::size()); EXPECT_EQ(12u, SpdyGoAwayControlFrame::size()); - EXPECT_EQ(14u, SpdyHeadersControlFrame::size()); + EXPECT_EQ(12u, SpdyHeadersControlFrame::size()); EXPECT_EQ(16u, SpdyWindowUpdateControlFrame::size()); EXPECT_EQ(4u, sizeof(FlagsAndLength)); EXPECT_EQ(1, SYN_STREAM); @@ -73,7 +73,7 @@ TEST(SpdyProtocolTest, ProtocolConstants) { } // Test some of the protocol helper functions -TEST(SpdyProtocolTest, FrameStructs) { +TEST(SpdyProtocolSpdy2Test, FrameStructs) { SpdyFrame frame(SpdyFrame::kHeaderSize); frame.set_length(12345); frame.set_flags(10); @@ -88,25 +88,26 @@ TEST(SpdyProtocolTest, FrameStructs) { EXPECT_FALSE(frame.is_control_frame()); } -TEST(SpdyProtocolTest, DataFrameStructs) { +TEST(SpdyProtocolSpdy2Test, DataFrameStructs) { SpdyDataFrame data_frame; data_frame.set_stream_id(12345); EXPECT_EQ(12345u, data_frame.stream_id()); } -TEST(SpdyProtocolTest, ControlFrameStructs) { - SpdyFramer framer; +TEST(SpdyProtocolSpdy2Test, ControlFrameStructs) { + SpdyFramer framer(SPDY_VERSION_FOR_TESTS); SpdyHeaderBlock headers; scoped_ptr<SpdySynStreamControlFrame> syn_frame( framer.CreateSynStream(123, 456, 2, CONTROL_FLAG_FIN, false, &headers)); - EXPECT_EQ(kSpdyProtocolVersion, syn_frame->version()); + EXPECT_EQ(framer.protocol_version(), syn_frame->version()); EXPECT_TRUE(syn_frame->is_control_frame()); EXPECT_EQ(SYN_STREAM, syn_frame->type()); EXPECT_EQ(123u, syn_frame->stream_id()); EXPECT_EQ(456u, syn_frame->associated_stream_id()); EXPECT_EQ(2u, syn_frame->priority()); - EXPECT_EQ(2, syn_frame->header_block_len()); + EXPECT_EQ((SPDY_VERSION_FOR_TESTS < 3) ? 2 : 4, + syn_frame->header_block_len()); EXPECT_EQ(1u, syn_frame->flags()); syn_frame->set_associated_stream_id(999u); EXPECT_EQ(123u, syn_frame->stream_id()); @@ -114,16 +115,17 @@ TEST(SpdyProtocolTest, ControlFrameStructs) { scoped_ptr<SpdySynReplyControlFrame> syn_reply( framer.CreateSynReply(123, CONTROL_FLAG_NONE, false, &headers)); - EXPECT_EQ(kSpdyProtocolVersion, syn_reply->version()); + EXPECT_EQ(framer.protocol_version(), syn_reply->version()); EXPECT_TRUE(syn_reply->is_control_frame()); EXPECT_EQ(SYN_REPLY, syn_reply->type()); EXPECT_EQ(123u, syn_reply->stream_id()); - EXPECT_EQ(2, syn_reply->header_block_len()); + EXPECT_EQ((SPDY_VERSION_FOR_TESTS < 3) ? 2 : 4, + syn_reply->header_block_len()); EXPECT_EQ(0, syn_reply->flags()); scoped_ptr<SpdyRstStreamControlFrame> rst_frame( framer.CreateRstStream(123, spdy::PROTOCOL_ERROR)); - EXPECT_EQ(kSpdyProtocolVersion, rst_frame->version()); + EXPECT_EQ(framer.protocol_version(), rst_frame->version()); EXPECT_TRUE(rst_frame->is_control_frame()); EXPECT_EQ(RST_STREAM, rst_frame->type()); EXPECT_EQ(123u, rst_frame->stream_id()); @@ -132,18 +134,11 @@ TEST(SpdyProtocolTest, ControlFrameStructs) { EXPECT_EQ(spdy::INVALID_STREAM, rst_frame->status()); EXPECT_EQ(0, rst_frame->flags()); - scoped_ptr<SpdyNoOpControlFrame> noop_frame( - framer.CreateNopFrame()); - EXPECT_EQ(kSpdyProtocolVersion, noop_frame->version()); - EXPECT_TRUE(noop_frame->is_control_frame()); - EXPECT_EQ(NOOP, noop_frame->type()); - EXPECT_EQ(0, noop_frame->flags()); - const uint32 kUniqueId = 1234567u; const uint32 kUniqueId2 = 31415926u; scoped_ptr<SpdyPingControlFrame> ping_frame( framer.CreatePingFrame(kUniqueId)); - EXPECT_EQ(kSpdyProtocolVersion, ping_frame->version()); + EXPECT_EQ(framer.protocol_version(), ping_frame->version()); EXPECT_TRUE(ping_frame->is_control_frame()); EXPECT_EQ(PING, ping_frame->type()); EXPECT_EQ(kUniqueId, ping_frame->unique_id()); @@ -152,30 +147,31 @@ TEST(SpdyProtocolTest, ControlFrameStructs) { scoped_ptr<SpdyGoAwayControlFrame> goaway_frame( framer.CreateGoAway(123)); - EXPECT_EQ(kSpdyProtocolVersion, goaway_frame->version()); + EXPECT_EQ(framer.protocol_version(), goaway_frame->version()); EXPECT_TRUE(goaway_frame->is_control_frame()); EXPECT_EQ(GOAWAY, goaway_frame->type()); EXPECT_EQ(123u, goaway_frame->last_accepted_stream_id()); scoped_ptr<SpdyHeadersControlFrame> headers_frame( framer.CreateHeaders(123, CONTROL_FLAG_NONE, false, &headers)); - EXPECT_EQ(kSpdyProtocolVersion, headers_frame->version()); + EXPECT_EQ(framer.protocol_version(), headers_frame->version()); EXPECT_TRUE(headers_frame->is_control_frame()); EXPECT_EQ(HEADERS, headers_frame->type()); EXPECT_EQ(123u, headers_frame->stream_id()); - EXPECT_EQ(2, headers_frame->header_block_len()); + EXPECT_EQ((SPDY_VERSION_FOR_TESTS < 3) ? 2 : 4, + headers_frame->header_block_len()); EXPECT_EQ(0, headers_frame->flags()); scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame( framer.CreateWindowUpdate(123, 456)); - EXPECT_EQ(kSpdyProtocolVersion, window_update_frame->version()); + EXPECT_EQ(framer.protocol_version(), window_update_frame->version()); EXPECT_TRUE(window_update_frame->is_control_frame()); EXPECT_EQ(WINDOW_UPDATE, window_update_frame->type()); EXPECT_EQ(123u, window_update_frame->stream_id()); EXPECT_EQ(456u, window_update_frame->delta_window_size()); } -TEST(SpdyProtocolTest, TestDataFrame) { +TEST(SpdyProtocolSpdy2Test, TestDataFrame) { SpdyDataFrame frame; // Set the stream ID to various values. @@ -218,14 +214,14 @@ TEST(SpdyProtocolTest, TestDataFrame) { } // Test various types of SETTINGS frames. -TEST(SpdyProtocolTest, TestSpdySettingsFrame) { - SpdyFramer framer; +TEST(SpdyProtocolSpdy2Test, TestSpdySettingsFrame) { + SpdyFramer framer(SPDY_VERSION_FOR_TESTS); // Create a settings frame with no settings. SpdySettings settings; scoped_ptr<SpdySettingsControlFrame> settings_frame( framer.CreateSettings(settings)); - EXPECT_EQ(kSpdyProtocolVersion, settings_frame->version()); + EXPECT_EQ(framer.protocol_version(), settings_frame->version()); EXPECT_TRUE(settings_frame->is_control_frame()); EXPECT_EQ(SETTINGS, settings_frame->type()); EXPECT_EQ(0u, settings_frame->num_entries()); @@ -233,38 +229,40 @@ TEST(SpdyProtocolTest, TestSpdySettingsFrame) { // We'll add several different ID/Flag combinations and then verify // that they encode and decode properly. SettingsFlagsAndId ids[] = { - 0x00000000, - 0xffffffff, - 0xff000001, - 0x01000002, + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0x00000000), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0xffffffff), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0xff000001), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0xffffffff), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0x01000002), + SettingsFlagsAndId(3, 1) }; for (size_t index = 0; index < arraysize(ids); ++index) { settings.insert(settings.end(), std::make_pair(ids[index], index)); settings_frame.reset(framer.CreateSettings(settings)); - EXPECT_EQ(kSpdyProtocolVersion, settings_frame->version()); + EXPECT_EQ(framer.protocol_version(), settings_frame->version()); EXPECT_TRUE(settings_frame->is_control_frame()); EXPECT_EQ(SETTINGS, settings_frame->type()); EXPECT_EQ(index + 1, settings_frame->num_entries()); SpdySettings parsed_settings; EXPECT_TRUE(framer.ParseSettings(settings_frame.get(), &parsed_settings)); - EXPECT_EQ(parsed_settings.size(), settings.size()); + EXPECT_EQ(settings.size(), parsed_settings.size()); SpdySettings::const_iterator it = parsed_settings.begin(); int pos = 0; while (it != parsed_settings.end()) { SettingsFlagsAndId parsed = it->first; uint32 value = it->second; - EXPECT_EQ(parsed.flags(), ids[pos].flags()); - EXPECT_EQ(parsed.id(), ids[pos].id()); - EXPECT_EQ(value, static_cast<uint32>(pos)); + EXPECT_EQ(ids[pos].flags(), parsed.flags()); + EXPECT_EQ(ids[pos].id(), parsed.id()); + EXPECT_EQ(static_cast<uint32>(pos), value); ++it; ++pos; } } } -TEST(SpdyProtocolTest, HasHeaderBlock) { +TEST(SpdyProtocolSpdy2Test, HasHeaderBlock) { SpdyControlFrame frame(SpdyControlFrame::kHeaderSize); for (SpdyControlType type = SYN_STREAM; type < NUM_CONTROL_FRAME_TYPES; @@ -281,7 +279,7 @@ TEST(SpdyProtocolTest, HasHeaderBlock) { // Make sure that overflows both die in debug mode, and do not cause problems // in opt mode. Note: The EXPECT_DEBUG_DEATH call does not work on Win32 yet, // so we comment it out. -TEST(SpdyProtocolDeathTest, TestDataFrame) { +TEST(SpdyProtocolDeathSpdy2Test, TestDataFrame) { SpdyDataFrame frame; frame.set_stream_id(0); @@ -306,7 +304,7 @@ TEST(SpdyProtocolDeathTest, TestDataFrame) { EXPECT_EQ(0, frame.flags()); } -TEST(SpdyProtocolDeathTest, TestSpdyControlFrameStreamId) { +TEST(SpdyProtocolDeathSpdy2Test, TestSpdyControlFrameStreamId) { SpdyControlFrame frame_store(SpdySynStreamControlFrame::size()); memset(frame_store.data(), '1', SpdyControlFrame::kHeaderSize); SpdySynStreamControlFrame* frame = @@ -321,7 +319,7 @@ TEST(SpdyProtocolDeathTest, TestSpdyControlFrameStreamId) { EXPECT_FALSE(frame->is_control_frame()); } -TEST(SpdyProtocolDeathTest, TestSpdyControlFrameVersion) { +TEST(SpdyProtocolDeathSpdy2Test, TestSpdyControlFrameVersion) { const unsigned int kVersionMask = 0x7fff; SpdyControlFrame frame(SpdySynStreamControlFrame::size()); memset(frame.data(), '1', SpdyControlFrame::kHeaderSize); @@ -342,16 +340,17 @@ TEST(SpdyProtocolDeathTest, TestSpdyControlFrameVersion) { EXPECT_EQ(SYN_STREAM, frame.type()); } -TEST(SpdyProtocolDeathTest, TestSpdyControlFrameType) { +TEST(SpdyProtocolDeathSpdy2Test, TestSpdyControlFrameType) { SpdyControlFrame frame(SpdyControlFrame::kHeaderSize); memset(frame.data(), 255, SpdyControlFrame::kHeaderSize); // type() should be out of bounds. EXPECT_FALSE(frame.AppearsToBeAValidControlFrame()); + frame.set_version(SPDY_VERSION_FOR_TESTS); uint16 version = frame.version(); - for (int i = SYN_STREAM; i <= spdy::NOOP; ++i) { + for (int i = SYN_STREAM; i <= spdy::WINDOW_UPDATE; ++i) { frame.set_type(static_cast<SpdyControlType>(i)); EXPECT_EQ(i, static_cast<int>(frame.type())); EXPECT_TRUE(frame.AppearsToBeAValidControlFrame()); @@ -361,8 +360,8 @@ TEST(SpdyProtocolDeathTest, TestSpdyControlFrameType) { } } -TEST(SpdyProtocolDeathTest, TestRstStreamStatusBounds) { - SpdyFramer framer; +TEST(SpdyProtocolDeathSpdy2Test, TestRstStreamStatusBounds) { + SpdyFramer framer(SPDY_VERSION_FOR_TESTS); scoped_ptr<SpdyRstStreamControlFrame> rst_frame; rst_frame.reset(framer.CreateRstStream(123, spdy::PROTOCOL_ERROR)); diff --git a/net/spdy/spdy_protocol_spdy3_test.cc b/net/spdy/spdy_protocol_spdy3_test.cc new file mode 100644 index 0000000..918436b --- /dev/null +++ b/net/spdy/spdy_protocol_spdy3_test.cc @@ -0,0 +1,386 @@ +// 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/spdy/spdy_protocol.h" + +#include "base/memory/scoped_ptr.h" +#include "net/spdy/spdy_bitmasks.h" +#include "net/spdy/spdy_framer.h" +#include "testing/platform_test.h" + +using spdy::CONTROL_FLAG_FIN; +using spdy::CONTROL_FLAG_NONE; +using spdy::FlagsAndLength; +using spdy::GOAWAY; +using spdy::HEADERS; +using spdy::NOOP; +using spdy::NUM_CONTROL_FRAME_TYPES; +using spdy::PING; +using spdy::RST_STREAM; +using spdy::SETTINGS; +using spdy::SYN_REPLY; +using spdy::SYN_STREAM; +using spdy::SettingsFlagsAndId; +using spdy::SpdyControlFrame; +using spdy::SpdyControlType; +using spdy::SpdyDataFrame; +using spdy::SpdyFrame; +using spdy::SpdyFramer; +using spdy::SpdyGoAwayControlFrame; +using spdy::SpdyHeaderBlock; +using spdy::SpdyHeadersControlFrame; +using spdy::SpdyPingControlFrame; +using spdy::SpdyRstStreamControlFrame; +using spdy::SpdySettings; +using spdy::SpdySettingsControlFrame; +using spdy::SpdyStatusCodes; +using spdy::SpdySynReplyControlFrame; +using spdy::SpdySynStreamControlFrame; +using spdy::SpdyWindowUpdateControlFrame; +using spdy::WINDOW_UPDATE; +using spdy::kLengthMask; +using spdy::kStreamIdMask; + +namespace { + +// Default SPDY version for unit tests. +const int SPDY_VERSION_FOR_TESTS = 3; + +// Test our protocol constants +TEST(SpdyProtocolSpdy3Test, ProtocolConstants) { + EXPECT_EQ(8u, SpdyFrame::kHeaderSize); + EXPECT_EQ(8u, SpdyDataFrame::size()); + EXPECT_EQ(8u, SpdyControlFrame::kHeaderSize); + EXPECT_EQ(18u, SpdySynStreamControlFrame::size()); + EXPECT_EQ(12u, SpdySynReplyControlFrame::size()); + EXPECT_EQ(16u, SpdyRstStreamControlFrame::size()); + EXPECT_EQ(12u, SpdySettingsControlFrame::size()); + EXPECT_EQ(12u, SpdyPingControlFrame::size()); + EXPECT_EQ(12u, SpdyGoAwayControlFrame::size()); + EXPECT_EQ(12u, SpdyHeadersControlFrame::size()); + EXPECT_EQ(16u, SpdyWindowUpdateControlFrame::size()); + EXPECT_EQ(4u, sizeof(FlagsAndLength)); + EXPECT_EQ(1, SYN_STREAM); + EXPECT_EQ(2, SYN_REPLY); + EXPECT_EQ(3, RST_STREAM); + EXPECT_EQ(4, SETTINGS); + EXPECT_EQ(5, NOOP); + EXPECT_EQ(6, PING); + EXPECT_EQ(7, GOAWAY); + EXPECT_EQ(8, HEADERS); + EXPECT_EQ(9, WINDOW_UPDATE); +} + +// Test some of the protocol helper functions +TEST(SpdyProtocolSpdy3Test, FrameStructs) { + SpdyFrame frame(SpdyFrame::kHeaderSize); + frame.set_length(12345); + frame.set_flags(10); + EXPECT_EQ(12345u, frame.length()); + EXPECT_EQ(10u, frame.flags()); + EXPECT_FALSE(frame.is_control_frame()); + + frame.set_length(0); + frame.set_flags(10); + EXPECT_EQ(0u, frame.length()); + EXPECT_EQ(10u, frame.flags()); + EXPECT_FALSE(frame.is_control_frame()); +} + +TEST(SpdyProtocolSpdy3Test, DataFrameStructs) { + SpdyDataFrame data_frame; + data_frame.set_stream_id(12345); + EXPECT_EQ(12345u, data_frame.stream_id()); +} + +TEST(SpdyProtocolSpdy3Test, ControlFrameStructs) { + SpdyFramer framer(SPDY_VERSION_FOR_TESTS); + SpdyHeaderBlock headers; + + scoped_ptr<SpdySynStreamControlFrame> syn_frame( + framer.CreateSynStream(123, 456, 2, CONTROL_FLAG_FIN, false, &headers)); + EXPECT_EQ(framer.protocol_version(), syn_frame->version()); + EXPECT_TRUE(syn_frame->is_control_frame()); + EXPECT_EQ(SYN_STREAM, syn_frame->type()); + EXPECT_EQ(123u, syn_frame->stream_id()); + EXPECT_EQ(456u, syn_frame->associated_stream_id()); + EXPECT_EQ(2u, syn_frame->priority()); + EXPECT_EQ((SPDY_VERSION_FOR_TESTS < 3) ? 2 : 4, + syn_frame->header_block_len()); + EXPECT_EQ(1u, syn_frame->flags()); + syn_frame->set_associated_stream_id(999u); + EXPECT_EQ(123u, syn_frame->stream_id()); + EXPECT_EQ(999u, syn_frame->associated_stream_id()); + + scoped_ptr<SpdySynReplyControlFrame> syn_reply( + framer.CreateSynReply(123, CONTROL_FLAG_NONE, false, &headers)); + EXPECT_EQ(framer.protocol_version(), syn_reply->version()); + EXPECT_TRUE(syn_reply->is_control_frame()); + EXPECT_EQ(SYN_REPLY, syn_reply->type()); + EXPECT_EQ(123u, syn_reply->stream_id()); + EXPECT_EQ((SPDY_VERSION_FOR_TESTS < 3) ? 2 : 4, + syn_reply->header_block_len()); + EXPECT_EQ(0, syn_reply->flags()); + + scoped_ptr<SpdyRstStreamControlFrame> rst_frame( + framer.CreateRstStream(123, spdy::PROTOCOL_ERROR)); + EXPECT_EQ(framer.protocol_version(), rst_frame->version()); + EXPECT_TRUE(rst_frame->is_control_frame()); + EXPECT_EQ(RST_STREAM, rst_frame->type()); + EXPECT_EQ(123u, rst_frame->stream_id()); + EXPECT_EQ(spdy::PROTOCOL_ERROR, rst_frame->status()); + rst_frame->set_status(spdy::INVALID_STREAM); + EXPECT_EQ(spdy::INVALID_STREAM, rst_frame->status()); + EXPECT_EQ(0, rst_frame->flags()); + + const uint32 kUniqueId = 1234567u; + const uint32 kUniqueId2 = 31415926u; + scoped_ptr<SpdyPingControlFrame> ping_frame( + framer.CreatePingFrame(kUniqueId)); + EXPECT_EQ(framer.protocol_version(), ping_frame->version()); + EXPECT_TRUE(ping_frame->is_control_frame()); + EXPECT_EQ(PING, ping_frame->type()); + EXPECT_EQ(kUniqueId, ping_frame->unique_id()); + ping_frame->set_unique_id(kUniqueId2); + EXPECT_EQ(kUniqueId2, ping_frame->unique_id()); + + scoped_ptr<SpdyGoAwayControlFrame> goaway_frame( + framer.CreateGoAway(123)); + EXPECT_EQ(framer.protocol_version(), goaway_frame->version()); + EXPECT_TRUE(goaway_frame->is_control_frame()); + EXPECT_EQ(GOAWAY, goaway_frame->type()); + EXPECT_EQ(123u, goaway_frame->last_accepted_stream_id()); + + scoped_ptr<SpdyHeadersControlFrame> headers_frame( + framer.CreateHeaders(123, CONTROL_FLAG_NONE, false, &headers)); + EXPECT_EQ(framer.protocol_version(), headers_frame->version()); + EXPECT_TRUE(headers_frame->is_control_frame()); + EXPECT_EQ(HEADERS, headers_frame->type()); + EXPECT_EQ(123u, headers_frame->stream_id()); + EXPECT_EQ((SPDY_VERSION_FOR_TESTS < 3) ? 2 : 4, + headers_frame->header_block_len()); + EXPECT_EQ(0, headers_frame->flags()); + + scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame( + framer.CreateWindowUpdate(123, 456)); + EXPECT_EQ(framer.protocol_version(), window_update_frame->version()); + EXPECT_TRUE(window_update_frame->is_control_frame()); + EXPECT_EQ(WINDOW_UPDATE, window_update_frame->type()); + EXPECT_EQ(123u, window_update_frame->stream_id()); + EXPECT_EQ(456u, window_update_frame->delta_window_size()); +} + +TEST(SpdyProtocolSpdy3Test, TestDataFrame) { + SpdyDataFrame frame; + + // Set the stream ID to various values. + frame.set_stream_id(0); + EXPECT_EQ(0u, frame.stream_id()); + EXPECT_FALSE(frame.is_control_frame()); + frame.set_stream_id(~0 & kStreamIdMask); + EXPECT_EQ(~0 & kStreamIdMask, frame.stream_id()); + EXPECT_FALSE(frame.is_control_frame()); + + // Set length to various values. Make sure that when you set_length(x), + // length() == x. Also make sure the flags are unaltered. + memset(frame.data(), '1', SpdyDataFrame::size()); + int8 flags = frame.flags(); + frame.set_length(0); + EXPECT_EQ(0u, frame.length()); + EXPECT_EQ(flags, frame.flags()); + frame.set_length(kLengthMask); + EXPECT_EQ(kLengthMask, frame.length()); + EXPECT_EQ(flags, frame.flags()); + frame.set_length(5u); + EXPECT_EQ(5u, frame.length()); + EXPECT_EQ(flags, frame.flags()); + + // Set flags to various values. Make sure that when you set_flags(x), + // flags() == x. Also make sure the length is unaltered. + memset(frame.data(), '1', SpdyDataFrame::size()); + uint32 length = frame.length(); + frame.set_flags(0u); + EXPECT_EQ(0u, frame.flags()); + EXPECT_EQ(length, frame.length()); + int8 all_flags = ~0; + frame.set_flags(all_flags); + flags = frame.flags(); + EXPECT_EQ(all_flags, flags); + EXPECT_EQ(length, frame.length()); + frame.set_flags(5u); + EXPECT_EQ(5u, frame.flags()); + EXPECT_EQ(length, frame.length()); +} + +// Test various types of SETTINGS frames. +TEST(SpdyProtocolSpdy3Test, TestSpdySettingsFrame) { + SpdyFramer framer(SPDY_VERSION_FOR_TESTS); + + // Create a settings frame with no settings. + SpdySettings settings; + scoped_ptr<SpdySettingsControlFrame> settings_frame( + framer.CreateSettings(settings)); + EXPECT_EQ(framer.protocol_version(), settings_frame->version()); + EXPECT_TRUE(settings_frame->is_control_frame()); + EXPECT_EQ(SETTINGS, settings_frame->type()); + EXPECT_EQ(0u, settings_frame->num_entries()); + + // We'll add several different ID/Flag combinations and then verify + // that they encode and decode properly. + SettingsFlagsAndId ids[] = { + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0x00000000), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0xffffffff), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0xff000001), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0xffffffff), + SettingsFlagsAndId::FromWireFormat(SPDY_VERSION_FOR_TESTS, 0x01000002), + SettingsFlagsAndId(3, 1) + }; + + for (size_t index = 0; index < arraysize(ids); ++index) { + settings.insert(settings.end(), std::make_pair(ids[index], index)); + settings_frame.reset(framer.CreateSettings(settings)); + EXPECT_EQ(framer.protocol_version(), settings_frame->version()); + EXPECT_TRUE(settings_frame->is_control_frame()); + EXPECT_EQ(SETTINGS, settings_frame->type()); + EXPECT_EQ(index + 1, settings_frame->num_entries()); + + SpdySettings parsed_settings; + EXPECT_TRUE(framer.ParseSettings(settings_frame.get(), &parsed_settings)); + EXPECT_EQ(settings.size(), parsed_settings.size()); + SpdySettings::const_iterator it = parsed_settings.begin(); + int pos = 0; + while (it != parsed_settings.end()) { + SettingsFlagsAndId parsed = it->first; + uint32 value = it->second; + EXPECT_EQ(ids[pos].flags(), parsed.flags()); + EXPECT_EQ(ids[pos].id(), parsed.id()); + EXPECT_EQ(static_cast<uint32>(pos), value); + ++it; + ++pos; + } + } +} + +TEST(SpdyProtocolSpdy3Test, HasHeaderBlock) { + SpdyControlFrame frame(SpdyControlFrame::kHeaderSize); + for (SpdyControlType type = SYN_STREAM; + type < NUM_CONTROL_FRAME_TYPES; + type = static_cast<SpdyControlType>(type + 1)) { + frame.set_type(type); + if (type == SYN_STREAM || type == SYN_REPLY || type == HEADERS) { + EXPECT_TRUE(frame.has_header_block()); + } else { + EXPECT_FALSE(frame.has_header_block()); + } + } +} + +// Make sure that overflows both die in debug mode, and do not cause problems +// in opt mode. Note: The EXPECT_DEBUG_DEATH call does not work on Win32 yet, +// so we comment it out. +TEST(SpdyProtocolDeathSpdy3Test, TestDataFrame) { + SpdyDataFrame frame; + + frame.set_stream_id(0); + // TODO(mbelshe): implement EXPECT_DEBUG_DEATH on windows. +#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST) +#if !defined(DCHECK_ALWAYS_ON) + EXPECT_DEBUG_DEATH(frame.set_stream_id(~0), ""); +#else + EXPECT_DEATH(frame.set_stream_id(~0), ""); +#endif +#endif + EXPECT_FALSE(frame.is_control_frame()); + + frame.set_flags(0); +#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST) +#if !defined(DCHECK_ALWAYS_ON) + EXPECT_DEBUG_DEATH(frame.set_length(~0), ""); +#else + EXPECT_DEATH(frame.set_length(~0), ""); +#endif +#endif + EXPECT_EQ(0, frame.flags()); +} + +TEST(SpdyProtocolDeathSpdy3Test, TestSpdyControlFrameStreamId) { + SpdyControlFrame frame_store(SpdySynStreamControlFrame::size()); + memset(frame_store.data(), '1', SpdyControlFrame::kHeaderSize); + SpdySynStreamControlFrame* frame = + reinterpret_cast<SpdySynStreamControlFrame*>(&frame_store); + + // Set the stream ID to various values. + frame->set_stream_id(0); + EXPECT_EQ(0u, frame->stream_id()); + EXPECT_FALSE(frame->is_control_frame()); + frame->set_stream_id(kStreamIdMask); + EXPECT_EQ(kStreamIdMask, frame->stream_id()); + EXPECT_FALSE(frame->is_control_frame()); +} + +TEST(SpdyProtocolDeathSpdy3Test, TestSpdyControlFrameVersion) { + const unsigned int kVersionMask = 0x7fff; + SpdyControlFrame frame(SpdySynStreamControlFrame::size()); + memset(frame.data(), '1', SpdyControlFrame::kHeaderSize); + + // Set the version to various values, and make sure it does not affect the + // type. + frame.set_type(SYN_STREAM); + frame.set_version(0); + EXPECT_EQ(0, frame.version()); + EXPECT_TRUE(frame.is_control_frame()); + EXPECT_EQ(SYN_STREAM, frame.type()); + + SpdySynStreamControlFrame* syn_stream = + reinterpret_cast<SpdySynStreamControlFrame*>(&frame); + syn_stream->set_stream_id(~0 & kVersionMask); + EXPECT_EQ(~0 & kVersionMask, syn_stream->stream_id()); + EXPECT_TRUE(frame.is_control_frame()); + EXPECT_EQ(SYN_STREAM, frame.type()); +} + +TEST(SpdyProtocolDeathSpdy3Test, TestSpdyControlFrameType) { + SpdyControlFrame frame(SpdyControlFrame::kHeaderSize); + memset(frame.data(), 255, SpdyControlFrame::kHeaderSize); + + // type() should be out of bounds. + EXPECT_FALSE(frame.AppearsToBeAValidControlFrame()); + + frame.set_version(SPDY_VERSION_FOR_TESTS); + uint16 version = frame.version(); + + for (int i = SYN_STREAM; i <= spdy::WINDOW_UPDATE; ++i) { + frame.set_type(static_cast<SpdyControlType>(i)); + EXPECT_EQ(i, static_cast<int>(frame.type())); + if (i == spdy::NOOP) { + // NOOP frames aren't 'valid'. + EXPECT_FALSE(frame.AppearsToBeAValidControlFrame()); + } else { + EXPECT_TRUE(frame.AppearsToBeAValidControlFrame()); + } + // Make sure setting type does not alter the version block. + EXPECT_EQ(version, frame.version()); + EXPECT_TRUE(frame.is_control_frame()); + } +} + +TEST(SpdyProtocolDeathSpdy3Test, TestRstStreamStatusBounds) { + SpdyFramer framer(SPDY_VERSION_FOR_TESTS); + scoped_ptr<SpdyRstStreamControlFrame> rst_frame; + + rst_frame.reset(framer.CreateRstStream(123, spdy::PROTOCOL_ERROR)); + EXPECT_EQ(spdy::PROTOCOL_ERROR, rst_frame->status()); + + rst_frame->set_status(spdy::INVALID); + EXPECT_EQ(spdy::INVALID, rst_frame->status()); + + rst_frame->set_status( + static_cast<spdy::SpdyStatusCodes>(spdy::INVALID - 1)); + EXPECT_EQ(spdy::INVALID, rst_frame->status()); + + rst_frame->set_status(spdy::NUM_STATUS_CODES); + EXPECT_EQ(spdy::INVALID, rst_frame->status()); +} + +} // namespace diff --git a/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc b/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc index eaf4fbe..5963e96 100644 --- a/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc +++ b/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc @@ -65,6 +65,10 @@ class SpdyProxyClientSocketSpdy2Test : public PlatformTest { virtual void TearDown(); protected: + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); + } + void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count); spdy::SpdyFrame* ConstructConnectRequestFrame(); diff --git a/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc b/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc index da3456b..0073243 100644 --- a/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc +++ b/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc @@ -65,6 +65,10 @@ class SpdyProxyClientSocketSpdy3Test : public PlatformTest { virtual void TearDown(); protected: + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); + } + void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count); spdy::SpdyFrame* ConstructConnectRequestFrame(); diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index 78b5993..7281a47 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -4,6 +4,8 @@ #include "net/spdy/spdy_session.h" +#include <map> + #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/linked_ptr.h" @@ -27,7 +29,6 @@ #include "net/base/origin_bound_cert_service.h" #include "net/http/http_network_session.h" #include "net/http/http_server_properties.h" -#include "net/socket/ssl_client_socket.h" #include "net/spdy/spdy_frame_builder.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" @@ -270,6 +271,9 @@ bool SpdySession::use_ssl_ = true; SpdySession::FlowControl SpdySession::use_flow_control_ = SpdySession::kFlowControlBasedOnNPN; +SSLClientSocket::NextProto SpdySession::default_protocol_ = + SSLClientSocket::kProtoUnknown; + // static size_t SpdySession::init_max_concurrent_streams_ = 10; @@ -288,6 +292,13 @@ int SpdySession::trailing_ping_delay_time_ms_ = 1000; // 1 second // static int SpdySession::hung_interval_ms_ = 10000; // 10 seconds +// static +void SpdySession::ResetStaticSettingsToInit() { + // WARNING: These must match the initializers above. + use_flow_control_ = SpdySession::kFlowControlBasedOnNPN; + default_protocol_ = SSLClientSocket::kProtoUnknown; +} + SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair, SpdySessionPool* spdy_session_pool, HttpServerProperties* http_server_properties, @@ -381,13 +392,16 @@ net::Error SpdySession::InitializeWithSocket( is_secure_ = is_secure; certificate_error_code_ = certificate_error_code; + SSLClientSocket::NextProto protocol = default_protocol_; if (is_secure_) { SSLClientSocket* ssl_socket = GetSSLClientSocket(); SSLClientSocket::NextProto protocol_negotiated = ssl_socket->protocol_negotiated(); - if (protocol_negotiated != SSLClientSocket::kProtoUnknown) + if (protocol_negotiated != SSLClientSocket::kProtoUnknown) { + protocol = protocol_negotiated; flow_control_ = (protocol_negotiated >= SSLClientSocket::kProtoSPDY21); + } if (ssl_socket->WasOriginBoundCertSent()) { // According to the SPDY spec, the credential associated with the TLS @@ -396,7 +410,21 @@ net::Error SpdySession::InitializeWithSocket( } } - buffered_spdy_framer_.reset(new spdy::BufferedSpdyFramer(2)); + int version = 2; + switch (protocol) { + case SSLClientSocket::kProtoSPDY2: + case SSLClientSocket::kProtoSPDY21: + version = 2; + break; + case SSLClientSocket::kProtoSPDY3: + version = 3; + break; + default: + NOTREACHED(); + break; + } + + buffered_spdy_framer_.reset(new spdy::BufferedSpdyFramer(version)); buffered_spdy_framer_->set_visitor(this); SendSettings(); @@ -1274,6 +1302,24 @@ void SpdySession::OnStreamFrameData(spdy::SpdyStreamId stream_id, stream->OnDataReceived(data, len); } +void SpdySession::OnSetting(spdy::SpdySettingsIds id, + uint8 flags, + uint32 value) { + HandleSetting(id, value); + spdy::SettingsFlagsAndId flags_and_id(flags, id); + http_server_properties_->SetSpdySetting( + host_port_pair(), std::make_pair(flags_and_id, value)); + + received_settings_ = true; + + // Log the settings. + spdy::SpdySettings settings; + settings.insert(settings.end(), std::make_pair(flags_and_id, value)); + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_RECV_SETTINGS, + make_scoped_refptr(new NetLogSpdySettingsParameter(settings))); +} + bool SpdySession::Respond(const spdy::SpdyHeaderBlock& headers, const scoped_refptr<SpdyStream> stream) { int rv = OK; @@ -1535,20 +1581,6 @@ void SpdySession::OnPing(const spdy::SpdyPingControlFrame& frame) { PlanToSendTrailingPing(); } -void SpdySession::OnSettings(const spdy::SpdySettingsControlFrame& frame) { - spdy::SpdySettings settings; - if (spdy::SpdyFramer::ParseSettings(&frame, &settings)) { - HandleSettings(settings); - http_server_properties_->SetSpdySettings(host_port_pair(), settings); - } - - received_settings_ = true; - - net_log_.AddEvent( - NetLog::TYPE_SPDY_SESSION_RECV_SETTINGS, - make_scoped_refptr(new NetLogSpdySettingsParameter(settings))); -} - void SpdySession::OnWindowUpdate( const spdy::SpdyWindowUpdateControlFrame& frame) { spdy::SpdyStreamId stream_id = frame.stream_id(); @@ -1627,6 +1659,9 @@ void SpdySession::SendSettings() { if (settings.empty()) return; + typedef std::map<uint32, spdy::SpdySetting> SpdySettingsMap; + SpdySettingsMap unique_settings; + // Record Histogram Data and Apply the SpdyCwnd FieldTrial if applicable. for (spdy::SpdySettings::iterator i = settings.begin(), end = settings.end(); i != end; ++i) { @@ -1640,12 +1675,17 @@ void SpdySession::SendSettings() { cwnd, 1, 200, 100); if (cwnd != val) { + spdy::SettingsFlagsAndId new_id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + id); i->second = cwnd; - i->first.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - http_server_properties_->SetSpdySettings(host_port_pair(), settings); + i->first = new_id; + spdy::SpdySetting setting(new_id, val); + http_server_properties_->SetSpdySetting(host_port_pair(), setting); + unique_settings[id] = setting; + continue; } - break; } + unique_settings[id] = *i; } HandleSettings(settings); @@ -1654,10 +1694,17 @@ void SpdySession::SendSettings() { NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS, make_scoped_refptr(new NetLogSpdySettingsParameter(settings))); + spdy::SpdySettings sorted_settings; + for (SpdySettingsMap::iterator it = unique_settings.begin(); + unique_settings.end() != it; + ++it) { + sorted_settings.push_back(it->second); + } + // Create the SETTINGS frame and send it. DCHECK(buffered_spdy_framer_.get()); scoped_ptr<spdy::SpdySettingsControlFrame> settings_frame( - buffered_spdy_framer_->CreateSettings(settings)); + buffered_spdy_framer_->CreateSettings(sorted_settings)); sent_settings_ = true; QueueFrame(settings_frame.get(), 0, NULL); } @@ -1665,25 +1712,27 @@ void SpdySession::SendSettings() { void SpdySession::HandleSettings(const spdy::SpdySettings& settings) { for (spdy::SpdySettings::const_iterator i = settings.begin(), end = settings.end(); i != end; ++i) { - const uint32 id = i->first.id(); - const uint32 val = i->second; - switch (id) { - case spdy::SETTINGS_MAX_CONCURRENT_STREAMS: - max_concurrent_streams_ = std::min(static_cast<size_t>(val), - max_concurrent_stream_limit_); - ProcessPendingCreateStreams(); - break; - case spdy::SETTINGS_INITIAL_WINDOW_SIZE: - // INITIAL_WINDOW_SIZE updates initial_send_window_size_ only. - // TODO(rtenneti): discuss with the server team about - // initial_recv_window_size_. - int32 prev_initial_send_window_size = initial_send_window_size_; - initial_send_window_size_ = val; - int32 delta_window_size = - initial_send_window_size_ - prev_initial_send_window_size; - UpdateStreamsSendWindowSize(delta_window_size); - break; - } + HandleSetting(i->first.id(), i->second); + } +} + +void SpdySession::HandleSetting(uint32 id, uint32 value) { + switch (id) { + case spdy::SETTINGS_MAX_CONCURRENT_STREAMS: + max_concurrent_streams_ = std::min(static_cast<size_t>(value), + max_concurrent_stream_limit_); + ProcessPendingCreateStreams(); + break; + case spdy::SETTINGS_INITIAL_WINDOW_SIZE: + // INITIAL_WINDOW_SIZE updates initial_send_window_size_ only. + // TODO(rtenneti): discuss with the server team about + // initial_recv_window_size_. + int32 prev_initial_send_window_size = initial_send_window_size_; + initial_send_window_size_ = value; + int32 delta_window_size = + initial_send_window_size_ - prev_initial_send_window_size; + UpdateStreamsSendWindowSize(delta_window_size); + break; } } @@ -1736,8 +1785,9 @@ void SpdySession::SendTrailingPing() { void SpdySession::WritePingFrame(uint32 unique_id) { DCHECK(buffered_spdy_framer_.get()); scoped_ptr<spdy::SpdyPingControlFrame> ping_frame( - spdy::SpdyFramer::CreatePingFrame(next_ping_id_)); - QueueFrame(ping_frame.get(), SPDY_PRIORITY_HIGHEST, NULL); + buffered_spdy_framer_->CreatePingFrame(next_ping_id_)); + QueueFrame( + ping_frame.get(), buffered_spdy_framer_->GetHighestPriority(), NULL); if (net_log().IsLoggingAllEvents()) { net_log().AddEvent( diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 6f33dfc..6114056 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -174,6 +174,9 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, // CLIENT_CERT_INVALID_TYPE if none was sent. SSLClientCertType GetOriginBoundCertType() const; + // Reset all static settings to initialized values. Used to init test suite. + static void ResetStaticSettingsToInit(); + // Enable or disable SSL. static void SetSSLMode(bool enable) { use_ssl_ = enable; } static bool SSLMode() { return use_ssl_; } @@ -184,6 +187,13 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, use_flow_control_ = flow_control; } + // Specify the SPDY protocol to be used for SPDY session which do not use NPN + // to negotiate a particular protocol. + static void set_default_protocol( + SSLClientSocket::NextProto default_protocol) { + default_protocol_ = default_protocol; + } + // Sets the max concurrent streams per session, as a ceiling on any server // specific SETTINGS value. static void set_max_concurrent_streams(size_t value) { @@ -354,6 +364,7 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, // Handle SETTINGS. Either when we send settings, or when we receive a // SETTINGS control frame, update our SpdySession accordingly. void HandleSettings(const spdy::SpdySettings& settings); + void HandleSetting(uint32 id, uint32 value); // Adjust the send window size of all ActiveStreams and PendingCreateStreams. void UpdateStreamsSendWindowSize(int32 delta_window_size); @@ -437,12 +448,13 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, const spdy::SpdyRstStreamControlFrame& frame) OVERRIDE; virtual void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) OVERRIDE; virtual void OnPing(const spdy::SpdyPingControlFrame& frame) OVERRIDE; - virtual void OnSettings(const spdy::SpdySettingsControlFrame& frame) OVERRIDE; virtual void OnWindowUpdate( const spdy::SpdyWindowUpdateControlFrame& frame) OVERRIDE; virtual void OnStreamFrameData(spdy::SpdyStreamId stream_id, const char* data, size_t len) OVERRIDE; + virtual void OnSetting( + spdy::SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE; virtual void OnSynStream( const spdy::SpdySynStreamControlFrame& frame, const linked_ptr<spdy::SpdyHeaderBlock>& headers) OVERRIDE; @@ -623,6 +635,7 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, static bool use_ssl_; static FlowControl use_flow_control_; + static SSLClientSocket::NextProto default_protocol_; static size_t init_max_concurrent_streams_; static size_t max_concurrent_stream_limit_; diff --git a/net/spdy/spdy_session_spdy2_unittest.cc b/net/spdy/spdy_session_spdy2_unittest.cc index f5f33fc..a5f8af7 100644 --- a/net/spdy/spdy_session_spdy2_unittest.cc +++ b/net/spdy/spdy_session_spdy2_unittest.cc @@ -24,6 +24,10 @@ class SpdySessionSpdy2Test : public PlatformTest { spdy::SpdyFramer::set_enable_compression_default(false); } protected: + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); + } + virtual void TearDown() { // Wanted to be 100% sure PING is disabled. SpdySession::set_enable_ping_based_connection_checking(false); @@ -497,8 +501,7 @@ TEST_F(SpdySessionSpdy2Test, OnSettings) { session_deps.host_resolver->set_synchronous_mode(true); spdy::SpdySettings new_settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; new_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -530,8 +533,8 @@ TEST_F(SpdySessionSpdy2Test, OnSettings) { // Initialize the SpdySettingsStorage with 1 max concurrent streams. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy::SpdySettings old_settings; - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - old_settings.push_back(spdy::SpdySetting(id, 1)); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, id.id()); + old_settings.push_back(spdy::SpdySetting(id1, 1)); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, old_settings); @@ -612,9 +615,8 @@ TEST_F(SpdySessionSpdy2Test, CancelPendingCreateStream) { // Initialize the SpdySettingsStorage with 1 max concurrent streams. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); settings.push_back(spdy::SpdySetting(id, 1)); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, settings); @@ -685,9 +687,7 @@ TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) { spdy::SpdySettings settings; const uint32 kBogusSettingId = 0xABAB; const uint32 kBogusSettingValue = 0xCDCD; - spdy::SettingsFlagsAndId id(0); - id.set_id(kBogusSettingId); - id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PERSISTED, kBogusSettingId); settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr<spdy::SpdyFrame> settings_frame( @@ -712,9 +712,9 @@ TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) { HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, id.id()); settings.clear(); - settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); + settings.push_back(spdy::SpdySetting(id1, kBogusSettingValue)); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, settings); @@ -873,9 +873,8 @@ TEST_F(SpdySessionSpdy2Test, ClearSettingsStorage) { const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); spdy::SpdySettings test_settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -897,9 +896,8 @@ TEST_F(SpdySessionSpdy2Test, ClearSettingsStorageOnIPAddressChanged) { HttpServerProperties* test_http_server_properties = spdy_session_pool->http_server_properties(); - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; spdy::SpdySettings test_settings; test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); diff --git a/net/spdy/spdy_session_spdy3_unittest.cc b/net/spdy/spdy_session_spdy3_unittest.cc index 7a8b086..d850c6a 100644 --- a/net/spdy/spdy_session_spdy3_unittest.cc +++ b/net/spdy/spdy_session_spdy3_unittest.cc @@ -24,6 +24,10 @@ class SpdySessionSpdy3Test : public PlatformTest { spdy::SpdyFramer::set_enable_compression_default(false); } protected: + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); + } + virtual void TearDown() { // Wanted to be 100% sure PING is disabled. SpdySession::set_enable_ping_based_connection_checking(false); @@ -497,8 +501,7 @@ TEST_F(SpdySessionSpdy3Test, OnSettings) { session_deps.host_resolver->set_synchronous_mode(true); spdy::SpdySettings new_settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; new_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -530,8 +533,8 @@ TEST_F(SpdySessionSpdy3Test, OnSettings) { // Initialize the SpdySettingsStorage with 1 max concurrent streams. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy::SpdySettings old_settings; - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); - old_settings.push_back(spdy::SpdySetting(id, 1)); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, id.id()); + old_settings.push_back(spdy::SpdySetting(id1, 1)); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, old_settings); @@ -612,9 +615,8 @@ TEST_F(SpdySessionSpdy3Test, CancelPendingCreateStream) { // Initialize the SpdySettingsStorage with 1 max concurrent streams. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy::SpdySettings settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); settings.push_back(spdy::SpdySetting(id, 1)); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, settings); @@ -685,9 +687,7 @@ TEST_F(SpdySessionSpdy3Test, SendSettingsOnNewSession) { spdy::SpdySettings settings; const uint32 kBogusSettingId = 0xABAB; const uint32 kBogusSettingValue = 0xCDCD; - spdy::SettingsFlagsAndId id(0); - id.set_id(kBogusSettingId); - id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PERSISTED, kBogusSettingId); settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr<spdy::SpdyFrame> settings_frame( @@ -712,9 +712,9 @@ TEST_F(SpdySessionSpdy3Test, SendSettingsOnNewSession) { HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, id.id()); settings.clear(); - settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); + settings.push_back(spdy::SpdySetting(id1, kBogusSettingValue)); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, settings); @@ -873,9 +873,8 @@ TEST_F(SpdySessionSpdy3Test, ClearSettingsStorage) { const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); spdy::SpdySettings test_settings; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); @@ -897,9 +896,8 @@ TEST_F(SpdySessionSpdy3Test, ClearSettingsStorageOnIPAddressChanged) { HttpServerProperties* test_http_server_properties = spdy_session_pool->http_server_properties(); - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; spdy::SpdySettings test_settings; test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); diff --git a/net/spdy/spdy_settings_storage.cc b/net/spdy/spdy_settings_storage.cc index f714249..19df996 100644 --- a/net/spdy/spdy_settings_storage.cc +++ b/net/spdy/spdy_settings_storage.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -34,8 +34,8 @@ void SpdySettingsStorage::Set(const HostPortPair& host_port_pair, for (it = settings.begin(); it != settings.end(); ++it) { spdy::SettingsFlagsAndId id = it->first; if (id.flags() & spdy::SETTINGS_FLAG_PLEASE_PERSIST) { - id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); - persistent_settings.push_back(std::make_pair(id, it->second)); + spdy::SettingsFlagsAndId new_id(spdy::SETTINGS_FLAG_PERSISTED, id.id()); + persistent_settings.push_back(std::make_pair(new_id, it->second)); } } diff --git a/net/spdy/spdy_stream_spdy2_unittest.cc b/net/spdy/spdy_stream_spdy2_unittest.cc index e57105c..b04b42b 100644 --- a/net/spdy/spdy_stream_spdy2_unittest.cc +++ b/net/spdy/spdy_stream_spdy2_unittest.cc @@ -113,6 +113,10 @@ class SpdyStreamSpdy2Test : public testing::Test { return session; } + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); + } + virtual void TearDown() { MessageLoop::current()->RunAllPending(); } diff --git a/net/spdy/spdy_stream_spdy3_unittest.cc b/net/spdy/spdy_stream_spdy3_unittest.cc index 38f9787..e51b8a5 100644 --- a/net/spdy/spdy_stream_spdy3_unittest.cc +++ b/net/spdy/spdy_stream_spdy3_unittest.cc @@ -113,6 +113,10 @@ class SpdyStreamSpdy3Test : public testing::Test { return session; } + virtual void SetUp() { + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); + } + virtual void TearDown() { MessageLoop::current()->RunAllPending(); } diff --git a/net/spdy/spdy_websocket_stream_spdy2_unittest.cc b/net/spdy/spdy_websocket_stream_spdy2_unittest.cc index aae61b8..311e300 100644 --- a/net/spdy/spdy_websocket_stream_spdy2_unittest.cc +++ b/net/spdy/spdy_websocket_stream_spdy2_unittest.cc @@ -11,6 +11,7 @@ #include "base/bind_helpers.h" #include "net/base/completion_callback.h" #include "net/proxy/proxy_server.h" +#include "net/socket/ssl_client_socket.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" #include "net/spdy/spdy_session.h" @@ -189,6 +190,7 @@ class SpdyWebSocketStreamSpdy2Test : public testing::Test { virtual void SetUp() { EnableCompression(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); SpdySession::SetSSLMode(false); host_port_pair_.set_host("example.com"); @@ -197,17 +199,17 @@ class SpdyWebSocketStreamSpdy2Test : public testing::Test { host_port_proxy_pair_.second = ProxyServer::Direct(); const size_t max_concurrent_streams = 1; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); spdy_settings_to_set_.push_back( spdy::SpdySetting(id, max_concurrent_streams)); - id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PERSISTED, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); spdy_settings_to_send_.push_back( - spdy::SpdySetting(id, max_concurrent_streams)); + spdy::SpdySetting(id1, max_concurrent_streams)); } + virtual void TearDown() { MessageLoop::current()->RunAllPending(); } diff --git a/net/spdy/spdy_websocket_stream_spdy3_unittest.cc b/net/spdy/spdy_websocket_stream_spdy3_unittest.cc index 0e90553..b78b9f5 100644 --- a/net/spdy/spdy_websocket_stream_spdy3_unittest.cc +++ b/net/spdy/spdy_websocket_stream_spdy3_unittest.cc @@ -11,6 +11,7 @@ #include "base/bind_helpers.h" #include "net/base/completion_callback.h" #include "net/proxy/proxy_server.h" +#include "net/socket/ssl_client_socket.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" #include "net/spdy/spdy_session.h" @@ -189,6 +190,7 @@ class SpdyWebSocketStreamSpdy3Test : public testing::Test { virtual void SetUp() { EnableCompression(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); SpdySession::SetSSLMode(false); host_port_pair_.set_host("example.com"); @@ -197,17 +199,17 @@ class SpdyWebSocketStreamSpdy3Test : public testing::Test { host_port_proxy_pair_.second = ProxyServer::Direct(); const size_t max_concurrent_streams = 1; - spdy::SettingsFlagsAndId id(0); - id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); - - id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); spdy_settings_to_set_.push_back( spdy::SpdySetting(id, max_concurrent_streams)); - id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PERSISTED, + spdy::SETTINGS_MAX_CONCURRENT_STREAMS); spdy_settings_to_send_.push_back( - spdy::SpdySetting(id, max_concurrent_streams)); + spdy::SpdySetting(id1, max_concurrent_streams)); } + virtual void TearDown() { MessageLoop::current()->RunAllPending(); } diff --git a/net/tools/flip_server/spdy_interface.cc b/net/tools/flip_server/spdy_interface.cc index 3ccc672..4638b1f 100644 --- a/net/tools/flip_server/spdy_interface.cc +++ b/net/tools/flip_server/spdy_interface.cc @@ -319,10 +319,10 @@ void SpdySM::ResetForNewConnection() { // Send a settings frame int SpdySM::PostAcceptHook() { SpdySettings settings; - SettingsFlagsAndId settings_id(SETTINGS_MAX_CONCURRENT_STREAMS); + SettingsFlagsAndId settings_id(0, SETTINGS_MAX_CONCURRENT_STREAMS); settings.push_back(SpdySetting(settings_id, 100)); SpdySettingsControlFrame* settings_frame = - SpdyFramer::CreateSettings(settings); + buffered_spdy_framer_->CreateSettings(settings); VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame"; EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame)); diff --git a/net/tools/flip_server/spdy_interface.h b/net/tools/flip_server/spdy_interface.h index d0e30ee..621a4ee 100644 --- a/net/tools/flip_server/spdy_interface.h +++ b/net/tools/flip_server/spdy_interface.h @@ -68,13 +68,13 @@ class SpdySM : public spdy::BufferedSpdyFramerVisitorInterface, const spdy::SpdyRstStreamControlFrame& frame) OVERRIDE; virtual void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) OVERRIDE {} virtual void OnPing(const spdy::SpdyPingControlFrame& frame) OVERRIDE {} - virtual void OnSettings( - const spdy::SpdySettingsControlFrame& frame) OVERRIDE {} virtual void OnWindowUpdate( const spdy::SpdyWindowUpdateControlFrame& frame) OVERRIDE {} virtual void OnStreamFrameData(spdy::SpdyStreamId stream_id, const char* data, size_t len) OVERRIDE; + virtual void OnSetting( + spdy::SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE {} virtual void OnSynStream( const spdy::SpdySynStreamControlFrame& frame, const linked_ptr<spdy::SpdyHeaderBlock>& headers) OVERRIDE; diff --git a/net/websockets/websocket_job_spdy2_unittest.cc b/net/websockets/websocket_job_spdy2_unittest.cc index 36e7802..ad00fde 100644 --- a/net/websockets/websocket_job_spdy2_unittest.cc +++ b/net/websockets/websocket_job_spdy2_unittest.cc @@ -319,6 +319,7 @@ class WebSocketJobSpdy2Test : public PlatformTest { public: virtual void SetUp() { spdy::SpdyFramer::set_enable_compression_default(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); stream_type_ = STREAM_INVALID; cookie_store_ = new MockCookieStore; context_ = new MockURLRequestContext(cookie_store_.get()); diff --git a/net/websockets/websocket_job_spdy3_unittest.cc b/net/websockets/websocket_job_spdy3_unittest.cc index 19a089d..235760b 100644 --- a/net/websockets/websocket_job_spdy3_unittest.cc +++ b/net/websockets/websocket_job_spdy3_unittest.cc @@ -319,6 +319,7 @@ class WebSocketJobSpdy3Test : public PlatformTest { public: virtual void SetUp() { spdy::SpdyFramer::set_enable_compression_default(false); + SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY3); stream_type_ = STREAM_INVALID; cookie_store_ = new MockCookieStore; context_ = new MockURLRequestContext(cookie_store_.get()); |