diff options
author | kmarshall <kmarshall@chromium.org> | 2015-01-14 19:10:03 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-15 03:10:45 +0000 |
commit | 92c461d6d93838907d197571d54375e04b185ed9 (patch) | |
tree | 96edf1ff78fee4010be8e7b401300ae2f78e7e0a /extensions | |
parent | 7c0fea86f434f4cd1706ff6cc3cbfee709083d5b (diff) | |
download | chromium_src-92c461d6d93838907d197571d54375e04b185ed9.zip chromium_src-92c461d6d93838907d197571d54375e04b185ed9.tar.gz chromium_src-92c461d6d93838907d197571d54375e04b185ed9.tar.bz2 |
Add connection keep-alive (ping) handling to CastChannelAPI.
Add browser tests to exercise new keep-alive functionality.
(Will have a companion extensions CL out for review in a jiffy.)
Review URL: https://codereview.chromium.org/756183002
Cr-Commit-Position: refs/heads/master@{#311621}
Diffstat (limited to 'extensions')
18 files changed, 837 insertions, 160 deletions
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn index 28ef9a1..0006144 100644 --- a/extensions/BUILD.gn +++ b/extensions/BUILD.gn @@ -188,6 +188,7 @@ if (false) { "browser/api/cast_channel/cast_framer_unittest.cc", "browser/api/cast_channel/cast_socket_unittest.cc", "browser/api/cast_channel/cast_transport_unittest.cc", + "browser/api/cast_channel/keep_alive_delegate_unittest.cc", "browser/api/cast_channel/logger_unittest.cc", "browser/computed_hashes_unittest.cc", "browser/content_hash_tree_unittest.cc", diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index 1b50709..e07b790 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn @@ -99,6 +99,8 @@ source_set("browser") { "api/cast_channel/cast_socket.h", "api/cast_channel/cast_transport.cc", "api/cast_channel/cast_transport.h", + "api/cast_channel/keep_alive_delegate.cc", + "api/cast_channel/keep_alive_delegate.h", "api/cast_channel/logger.cc", "api/cast_channel/logger.h", "api/cast_channel/logger_util.cc", diff --git a/extensions/browser/api/cast_channel/cast_channel_api.cc b/extensions/browser/api/cast_channel/cast_channel_api.cc index bc6782c..59db6fc 100644 --- a/extensions/browser/api/cast_channel/cast_channel_api.cc +++ b/extensions/browser/api/cast_channel/cast_channel_api.cc @@ -17,6 +17,7 @@ #include "extensions/browser/api/cast_channel/cast_auth_ica.h" #include "extensions/browser/api/cast_channel/cast_message_util.h" #include "extensions/browser/api/cast_channel/cast_socket.h" +#include "extensions/browser/api/cast_channel/keep_alive_delegate.h" #include "extensions/browser/api/cast_channel/logger.h" #include "extensions/browser/event_router.h" #include "extensions/common/api/cast_channel/cast_channel.pb.h" @@ -73,6 +74,7 @@ void FillChannelInfo(const CastSocket& socket, ChannelInfo* channel_info) { channel_info->connect_info.auth = socket.channel_auth(); channel_info->ready_state = socket.ready_state(); channel_info->error_state = socket.error_state(); + channel_info->keep_alive = socket.keep_alive(); } // Fills |error_info| from |error_state| and |last_errors|. @@ -148,6 +150,14 @@ content::BrowserContext* CastChannelAPI::GetBrowserContext() const { return browser_context_; } +void CastChannelAPI::SetPingTimeoutTimerForTest(scoped_ptr<base::Timer> timer) { + injected_timeout_timer_ = timer.Pass(); +} + +scoped_ptr<base::Timer> CastChannelAPI::GetInjectedTimeoutTimerForTest() { + return injected_timeout_timer_.Pass(); +} + CastChannelAPI::~CastChannelAPI() {} CastChannelAsyncApiFunction::CastChannelAsyncApiFunction() : manager_(NULL) { @@ -325,10 +335,33 @@ bool CastChannelOpenFunction::Prepare() { SetError("Invalid connect_info (invalid auth)"); } else if (!IsValidConnectInfoIpAddress(*connect_info_)) { SetError("Invalid connect_info (invalid IP address)"); + } else { + // Parse timeout parameters if they are set. + if (connect_info_->liveness_timeout) { + liveness_timeout_ = + base::TimeDelta::FromMilliseconds(*connect_info_->liveness_timeout); + } + if (connect_info_->ping_interval) { + ping_interval_ = + base::TimeDelta::FromMilliseconds(*connect_info_->ping_interval); + } + + // Validate timeout parameters. + if (liveness_timeout_ < base::TimeDelta() || + ping_interval_ < base::TimeDelta()) { + SetError("livenessTimeout and pingInterval must be greater than 0."); + } else if ((liveness_timeout_ > base::TimeDelta()) != + (ping_interval_ > base::TimeDelta())) { + SetError("livenessTimeout and pingInterval must be set together."); + } else if (liveness_timeout_ < ping_interval_) { + SetError("livenessTimeout must be longer than pingTimeout."); + } } + if (!GetError().empty()) { return false; } + channel_auth_ = connect_info_->auth; ip_endpoint_.reset(ParseConnectInfo(*connect_info_)); return true; @@ -348,12 +381,31 @@ void CastChannelOpenFunction::AsyncWorkStart() { base::TimeDelta::FromMilliseconds(connect_info_->timeout.get() ? *connect_info_->timeout : kDefaultConnectTimeoutMillis), - api_->GetLogger(), + liveness_timeout_ > base::TimeDelta(), api_->GetLogger(), connect_info_->capabilities ? *connect_info_->capabilities : CastDeviceCapability::NONE); } new_channel_id_ = AddSocket(socket); - scoped_ptr<CastMessageHandler> delegate(new CastMessageHandler(api_, socket)); + api_->GetLogger()->LogNewSocketEvent(*socket); + + // Construct read delegates. + scoped_ptr<core_api::cast_channel::CastTransport::Delegate> delegate( + make_scoped_ptr(new CastMessageHandler(api_, socket))); + if (socket->keep_alive()) { + // Wrap read delegate in a KeepAliveDelegate for timeout handling. + core_api::cast_channel::KeepAliveDelegate* keep_alive = + new core_api::cast_channel::KeepAliveDelegate( + socket, delegate.Pass(), ping_interval_, liveness_timeout_); + scoped_ptr<base::Timer> injected_timer = + api_->GetInjectedTimeoutTimerForTest(); + if (injected_timer) { + keep_alive->SetTimersForTest( + make_scoped_ptr(new base::Timer(false, false)), + injected_timer.Pass()); + } + delegate.reset(keep_alive); + } + api_->GetLogger()->LogNewSocketEvent(*socket); socket->Connect(delegate.Pass(), base::Bind(&CastChannelOpenFunction::OnOpen, this)); @@ -548,6 +600,9 @@ void CastChannelOpenFunction::CastMessageHandler::OnMessage( ->DispatchEventToExtension(socket->owner_extension_id(), event.Pass()); } +void CastChannelOpenFunction::CastMessageHandler::Start() { +} + CastChannelSetAuthorityKeysFunction::CastChannelSetAuthorityKeysFunction() { } diff --git a/extensions/browser/api/cast_channel/cast_channel_api.h b/extensions/browser/api/cast_channel/cast_channel_api.h index c343811..ab0eaa2 100644 --- a/extensions/browser/api/cast_channel/cast_channel_api.h +++ b/extensions/browser/api/cast_channel/cast_channel_api.h @@ -65,6 +65,13 @@ class CastChannelAPI : public BrowserContextKeyedAPI { // Returns the API browser context. content::BrowserContext* GetBrowserContext() const; + // Sets injected ping timeout timer for testing. + void SetPingTimeoutTimerForTest(scoped_ptr<base::Timer> timer); + + // Gets the injected ping timeout timer, if set. + // Returns a null scoped ptr if there is no injected timer. + scoped_ptr<base::Timer> GetInjectedTimeoutTimerForTest(); + private: friend class BrowserContextKeyedAPIFactory<CastChannelAPI>; friend class ::CastChannelAPITest; @@ -78,6 +85,7 @@ class CastChannelAPI : public BrowserContextKeyedAPI { content::BrowserContext* const browser_context_; scoped_refptr<cast_channel::Logger> logger_; scoped_ptr<cast_channel::CastSocket> socket_for_test_; + scoped_ptr<base::Timer> injected_timeout_timer_; DISALLOW_COPY_AND_ASSIGN(CastChannelAPI); }; @@ -141,16 +149,18 @@ class CastChannelOpenFunction : public CastChannelAsyncApiFunction { private: DECLARE_EXTENSION_FUNCTION("cast.channel.open", CAST_CHANNEL_OPEN) - // Processes incoming cast message events and errors, - // and provides additional API and socket context for those events. + // Receives incoming messages and errors and provides additional API and + // origin socket context. class CastMessageHandler : public cast_channel::CastTransport::Delegate { public: CastMessageHandler(CastChannelAPI* api, cast_channel::CastSocket* socket); ~CastMessageHandler() override; + // CastTransport::Delegate implementation. void OnError(cast_channel::ChannelError error_state, const cast_channel::LastErrors& last_errors) override; void OnMessage(const cast_channel::CastMessage& message) override; + void Start() override; private: CastChannelAPI* const api; @@ -179,6 +189,8 @@ class CastChannelOpenFunction : public CastChannelAsyncApiFunction { scoped_ptr<cast_channel::ConnectInfo> connect_info_; scoped_ptr<net::IPEndPoint> ip_endpoint_; cast_channel::ChannelAuthType channel_auth_; + base::TimeDelta liveness_timeout_; + base::TimeDelta ping_interval_; FRIEND_TEST_ALL_PREFIXES(CastChannelOpenFunctionTest, TestParseChannelUrl); FRIEND_TEST_ALL_PREFIXES(CastChannelOpenFunctionTest, TestParseConnectInfo); diff --git a/extensions/browser/api/cast_channel/cast_channel_apitest.cc b/extensions/browser/api/cast_channel/cast_channel_apitest.cc index 2b5fa6b..1d9d8f3 100644 --- a/extensions/browser/api/cast_channel/cast_channel_apitest.cc +++ b/extensions/browser/api/cast_channel/cast_channel_apitest.cc @@ -5,6 +5,7 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" +#include "base/timer/mock_timer.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_function_test_utils.h" #include "chrome/browser/extensions/extension_service.h" @@ -18,6 +19,7 @@ #include "extensions/common/api/cast_channel/cast_channel.pb.h" #include "extensions/common/switches.h" #include "extensions/common/test_util.h" +#include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" #include "net/base/capturing_net_log.h" #include "net/base/completion_callback.h" @@ -38,6 +40,7 @@ using cast_channel::CreateIPEndPointForTest; using cast_channel::ErrorInfo; using cast_channel::Logger; using cast_channel::MessageInfo; +using cast_channel::MockCastSocket; using cast_channel::MockCastTransport; using cast_channel::ReadyState; using extensions::Extension; @@ -57,7 +60,6 @@ using ::testing::SaveArg; namespace { -const char kTestExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf"; const char kTestCastUrl[] = "cast://192.168.1.1:8009"; static void FillCastMessage(const std::string& message, @@ -79,45 +81,6 @@ ACTION_P2(InvokeDelegateOnError, api_test, api) { api_test->CallOnError(api); } -class MockCastSocket : public CastSocket { - public: - MockCastSocket() - : CastSocket(kTestExtensionId), mock_transport_(new MockCastTransport) {} - virtual ~MockCastSocket() {} - - // Mockable version of Connect. Accepts a bare pointer to a mock object. - // (GMock won't compile with scoped_ptr method parameters.) - MOCK_METHOD2(ConnectWeakPtr, - void(CastTransport::Delegate* delegate, - base::Callback<void(ChannelError)> callback)); - - // Proxy for ConnectWeakPtr. Unpacks scoped_ptr into a GMock-friendly bare - // ptr. - virtual void Connect(scoped_ptr<CastTransport::Delegate> delegate, - base::Callback<void(ChannelError)> callback) override { - delegate_ = delegate.Pass(); - ConnectWeakPtr(delegate_.get(), callback); - } - - MOCK_METHOD1(Close, void(const net::CompletionCallback& callback)); - MOCK_CONST_METHOD0(ip_endpoint, const net::IPEndPoint&()); - MOCK_CONST_METHOD0(id, int()); - MOCK_METHOD1(set_id, void(int id)); - MOCK_CONST_METHOD0(channel_auth, ChannelAuthType()); - MOCK_CONST_METHOD0(cast_url, std::string()); - MOCK_CONST_METHOD0(ready_state, ReadyState()); - MOCK_CONST_METHOD0(error_state, ChannelError()); - MOCK_METHOD1(SetErrorState, void(ChannelError error_state)); - - CastTransport* transport() const override { return mock_transport_.get(); } - - MockCastTransport* mock_transport() const { return mock_transport_.get(); } - - private: - scoped_ptr<MockCastTransport> mock_transport_; - scoped_ptr<CastTransport::Delegate> delegate_; -}; - } // namespace class CastChannelAPITest : public ExtensionApiTest { @@ -128,11 +91,14 @@ class CastChannelAPITest : public ExtensionApiTest { ExtensionApiTest::SetUpCommandLine(command_line); command_line->AppendSwitchASCII( extensions::switches::kWhitelistedExtensionID, - kTestExtensionId); + cast_channel::kTestExtensionId); } void SetUpMockCastSocket() { extensions::CastChannelAPI* api = GetApi(); + timeout_timer_ = new base::MockTimer(true, false); + api->SetPingTimeoutTimerForTest(make_scoped_ptr(timeout_timer_)); + net::IPAddressNumber ip_number; net::ParseIPLiteralToNumber("192.168.1.1", &ip_number); net::IPEndPoint ip_endpoint(ip_number, 8009); @@ -148,6 +114,7 @@ class CastChannelAPITest : public ExtensionApiTest { .WillByDefault(ReturnRef(ip_endpoint_)); ON_CALL(*mock_cast_socket_, channel_auth()) .WillByDefault(Return(cast_channel::CHANNEL_AUTH_TYPE_SSL)); + ON_CALL(*mock_cast_socket_, keep_alive()).WillByDefault(Return(false)); ON_CALL(*mock_cast_socket_, cast_url()).WillByDefault(Return(kTestCastUrl)); } @@ -157,7 +124,7 @@ class CastChannelAPITest : public ExtensionApiTest { .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE)); { InSequence sequence; - EXPECT_CALL(*mock_cast_socket_, ConnectWeakPtr(_, _)) + EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(_, _)) .WillOnce( InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE)); EXPECT_CALL(*mock_cast_socket_, ready_state()) @@ -174,6 +141,25 @@ class CastChannelAPITest : public ExtensionApiTest { } } + void SetUpOpenPingTimeout() { + SetUpMockCastSocket(); + EXPECT_CALL(*mock_cast_socket_, error_state()) + .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE)); + EXPECT_CALL(*mock_cast_socket_, keep_alive()).WillRepeatedly(Return(true)); + { + InSequence sequence; + EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(_, _)) + .WillOnce(DoAll( + SaveArg<0>(&message_delegate_), + InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE))); + EXPECT_CALL(*mock_cast_socket_, ready_state()) + .WillOnce(Return(cast_channel::READY_STATE_OPEN)) + .RetiresOnSaturation(); + EXPECT_CALL(*mock_cast_socket_, ready_state()) + .WillOnce(Return(cast_channel::READY_STATE_CLOSED)); + } + } + extensions::CastChannelAPI* GetApi() { return extensions::CastChannelAPI::Get(profile()); } @@ -204,6 +190,22 @@ class CastChannelAPITest : public ExtensionApiTest { message_delegate_->OnMessage(cast_message); } + // Starts the read delegate on the IO thread. + void StartDelegate() { + CHECK(message_delegate_); + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&cast_channel::CastTransport::Delegate::Start, + base::Unretained(message_delegate_))); + } + + // Fires a timer on the IO thread. + void FireTimeout() { + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&base::MockTimer::Fire, base::Unretained(timeout_timer_))); + } + extensions::CastChannelOpenFunction* CreateOpenFunction( scoped_refptr<Extension> extension) { extensions::CastChannelOpenFunction* cast_channel_open_function = @@ -230,13 +232,14 @@ class CastChannelAPITest : public ExtensionApiTest { } MockCastSocket* mock_cast_socket_; + base::MockTimer* timeout_timer_; net::IPEndPoint ip_endpoint_; CastTransport::Delegate* message_delegate_; net::CapturingNetLog capturing_net_log_; int channel_id_; }; -// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest +// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest // always return true without actually running the test. Remove when fixed. #if defined(OS_WIN) && !defined(NDEBUG) #define MAYBE_TestOpenSendClose DISABLED_TestOpenSendClose @@ -252,7 +255,52 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenSendClose) { "test_open_send_close.html")); } -// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest +// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest +// always return true without actually running the test. Remove when fixed. +#if defined(OS_WIN) && !defined(NDEBUG) +#define MAYBE_TestPingTimeout DISABLED_TestPingTimeout +#else +#define MAYBE_TestPingTimeout TestPingTimeout +#endif +// Verify that timeout events are propagated through the API layer. +// (SSL, non-verified). +IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestPingTimeout) { + SetUpOpenPingTimeout(); + + ExtensionTestMessageListener channel_opened("channel_opened_ssl", false); + ExtensionTestMessageListener timeout("timeout_ssl", false); + EXPECT_TRUE( + RunExtensionSubtest("cast_channel/api", "test_open_timeout.html")); + EXPECT_TRUE(channel_opened.WaitUntilSatisfied()); + StartDelegate(); + FireTimeout(); + EXPECT_TRUE(timeout.WaitUntilSatisfied()); +} + +// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest +// always return true without actually running the test. Remove when fixed. +#if defined(OS_WIN) && !defined(NDEBUG) +#define MAYBE_TestPingTimeoutSslVerified DISABLED_TestPingTimeoutSslVerified +#else +#define MAYBE_TestPingTimeoutSslVerified TestPingTimeoutSslVerified +#endif +// Verify that timeout events are propagated through the API layer. +// (SSL, verified). +IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestPingTimeoutSslVerified) { + SetUpOpenPingTimeout(); + + ExtensionTestMessageListener channel_opened("channel_opened_ssl_verified", + false); + ExtensionTestMessageListener timeout("timeout_ssl_verified", false); + EXPECT_TRUE(RunExtensionSubtest("cast_channel/api", + "test_open_timeout_verified.html")); + EXPECT_TRUE(channel_opened.WaitUntilSatisfied()); + StartDelegate(); + FireTimeout(); + EXPECT_TRUE(timeout.WaitUntilSatisfied()); +} + +// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest // always return true without actually running the test. Remove when fixed. #if defined(OS_WIN) && !defined(NDEBUG) #define MAYBE_TestOpenSendCloseWithUrl DISABLED_TestOpenSendCloseWithUrl @@ -268,7 +316,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenSendCloseWithUrl) { "test_open_send_close_url.html")); } -// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest +// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest // always return true without actually running the test. Remove when fixed. #if defined(OS_WIN) && !defined(NDEBUG) #define MAYBE_TestOpenReceiveClose DISABLED_TestOpenReceiveClose @@ -284,7 +332,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenReceiveClose) { { InSequence sequence; - EXPECT_CALL(*mock_cast_socket_, ConnectWeakPtr(NotNull(), _)) + EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(NotNull(), _)) .WillOnce(DoAll( SaveArg<0>(&message_delegate_), InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE))); @@ -320,7 +368,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestGetLogs) { EXPECT_TRUE(RunExtensionSubtest("cast_channel/api", "test_get_logs.html")); } -// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest +// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest // always return true without actually running the test. Remove when fixed. #if defined(OS_WIN) && !defined(NDEBUG) #define MAYBE_TestOpenError DISABLED_TestOpenError @@ -331,7 +379,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestGetLogs) { IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenError) { SetUpMockCastSocket(); - EXPECT_CALL(*mock_cast_socket_, ConnectWeakPtr(NotNull(), _)) + EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(NotNull(), _)) .WillOnce(DoAll(SaveArg<0>(&message_delegate_), InvokeDelegateOnError(this, GetApi()), InvokeCompletionCallback<1>( @@ -402,6 +450,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSendInvalidMessageInfo) { std::string error(utils::RunFunctionAndReturnError( cast_channel_send_function.get(), "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", " + "\"keepAlive\": true, " "\"connectInfo\": " "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, " "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, " @@ -415,6 +464,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSendInvalidMessageInfo) { error = utils::RunFunctionAndReturnError( cast_channel_send_function.get(), "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", " + "\"keepAlive\": true, " "\"connectInfo\": " "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, " "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, " @@ -428,6 +478,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSendInvalidMessageInfo) { error = utils::RunFunctionAndReturnError( cast_channel_send_function.get(), "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", " + "\"keepAlive\": true, " "\"connectInfo\": " "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, " "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, " @@ -441,6 +492,7 @@ IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSendInvalidMessageInfo) { error = utils::RunFunctionAndReturnError( cast_channel_send_function.get(), "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", " + "\"keepAlive\": true, " "\"connectInfo\": " "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, " "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, " diff --git a/extensions/browser/api/cast_channel/cast_socket.cc b/extensions/browser/api/cast_channel/cast_socket.cc index 74f3cf1..1b4baef 100644 --- a/extensions/browser/api/cast_channel/cast_socket.cc +++ b/extensions/browser/api/cast_channel/cast_socket.cc @@ -45,21 +45,15 @@ namespace { -// The default keepalive delay. On Linux, keepalives probes will be sent after -// the socket is idle for this length of time, and the socket will be closed -// after 9 failed probes. So the total idle time before close is 10 * -// kTcpKeepAliveDelaySecs. -const int kTcpKeepAliveDelaySecs = 10; - const int kMaxSelfSignedCertLifetimeInDays = 2; std::string FormatTimeForLogging(base::Time time) { - base::Time::Exploded exploded; - time.UTCExplode(&exploded); + base::Time::Exploded exploded_time; + time.UTCExplode(&exploded_time); return base::StringPrintf( - "%04d-%02d-%02d %02d:%02d:%02d.%03d UTC", exploded.year, exploded.month, - exploded.day_of_month, exploded.hour, exploded.minute, exploded.second, - exploded.millisecond); + "%04d-%02d-%02d %02d:%02d:%02d.%03d UTC", exploded_time.year, + exploded_time.month, exploded_time.day_of_month, exploded_time.hour, + exploded_time.minute, exploded_time.second, exploded_time.millisecond); } } // namespace @@ -89,6 +83,7 @@ CastSocketImpl::CastSocketImpl(const std::string& owner_extension_id, ChannelAuthType channel_auth, net::NetLog* net_log, const base::TimeDelta& timeout, + bool keep_alive, const scoped_refptr<Logger>& logger, long device_capabilities) : CastSocket(owner_extension_id), @@ -98,6 +93,7 @@ CastSocketImpl::CastSocketImpl(const std::string& owner_extension_id, ip_endpoint_(ip_endpoint), channel_auth_(channel_auth), net_log_(net_log), + keep_alive_(keep_alive), logger_(logger), connect_timeout_(timeout), connect_timeout_timer_(new base::OneShotTimer<CastSocketImpl>), @@ -143,6 +139,10 @@ ChannelAuthType CastSocketImpl::channel_auth() const { return channel_auth_; } +bool CastSocketImpl::keep_alive() const { + return keep_alive_; +} + scoped_ptr<net::TCPClientSocket> CastSocketImpl::CreateTcpSocket() { net::AddressList addresses(ip_endpoint_); return scoped_ptr<net::TCPClientSocket>( @@ -382,13 +382,6 @@ int CastSocketImpl::DoTcpConnectComplete(int connect_result) { logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT_COMPLETE, connect_result); if (connect_result == net::OK) { - // Enable TCP-level keep-alive handling. - // TODO(kmarshall): Remove TCP keep-alive once protocol-level ping handling - // is in place. - bool keep_alive = tcp_socket_->SetKeepAlive(true, kTcpKeepAliveDelaySecs); - LOG_IF(WARNING, !keep_alive) << "Failed to SetKeepAlive."; - logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_SET_KEEP_ALIVE, - keep_alive ? 1 : 0); SetConnectState(proto::CONN_STATE_SSL_CONNECT); } else { SetErrorState(CHANNEL_ERROR_CONNECT_ERROR); @@ -421,15 +414,17 @@ int CastSocketImpl::DoSslConnectComplete(int result) { if (!transport_.get()) { // Create a channel transport if one wasn't already set (e.g. by test // code). - transport_.reset(new CastTransportImpl( - this->socket_.get(), &auth_delegate_, channel_id_, ip_endpoint_, - channel_auth_, logger_)); + transport_.reset(new CastTransportImpl(this->socket_.get(), channel_id_, + ip_endpoint_, channel_auth_, + logger_)); } + transport_->SetReadDelegate( + make_scoped_ptr(new AuthTransportDelegate(this))); if (channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED) { // Additionally verify the connection with a handshake. SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND); } else { - transport_->StartReading(); + transport_->Start(); } } else { SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR); @@ -460,7 +455,7 @@ int CastSocketImpl::DoAuthChallengeSendComplete(int result) { SetErrorState(CHANNEL_ERROR_SOCKET_ERROR); return result; } - transport_->StartReading(); + transport_->Start(); SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE); return net::ERR_IO_PENDING; } @@ -491,6 +486,9 @@ void CastSocketImpl::AuthTransportDelegate::OnMessage( } } +void CastSocketImpl::AuthTransportDelegate::Start() { +} + int CastSocketImpl::DoAuthChallengeReplyComplete(int result) { VLOG_WITH_CONNECTION(1) << "DoAuthChallengeReplyComplete: " << result; if (result < 0) { @@ -508,7 +506,7 @@ void CastSocketImpl::DoConnectCallback() { VLOG(1) << "DoConnectCallback (error_state = " << error_state_ << ")"; if (error_state_ == CHANNEL_ERROR_NONE) { SetReadyState(READY_STATE_OPEN); - transport_->SetReadDelegate(read_delegate_.get()); + transport_->SetReadDelegate(read_delegate_.Pass()); } else { SetReadyState(READY_STATE_CLOSED); CloseInternal(); diff --git a/extensions/browser/api/cast_channel/cast_socket.h b/extensions/browser/api/cast_channel/cast_socket.h index 3d89c2d..2c491d9 100644 --- a/extensions/browser/api/cast_channel/cast_socket.h +++ b/extensions/browser/api/cast_channel/cast_socket.h @@ -62,7 +62,7 @@ class CastSocket : public ApiResource { static const char* service_name() { return "CastSocketImplManager"; } // Connects the channel to the peer. If successful, the channel will be in - // READY_STATE_OPEN. DO NOT delete the CastSocketImpl object in |callback|. + // READY_STATE_OPEN. DO NOT delete the CastSocket object in |callback|. // Instead use Close(). // |callback| will be invoked with any ChannelError that occurred, or // CHANNEL_ERROR_NONE if successful. @@ -100,6 +100,9 @@ class CastSocket : public ApiResource { // CHANNEL_ERROR_NONE if no error has occurred. virtual ChannelError error_state() const = 0; + // True when keep-alive signaling is handled for this socket. + virtual bool keep_alive() const = 0; + // Marks a socket as invalid due to an error. Errors close the socket // and any further socket operations will return the error code // net::SOCKET_NOT_CORRECTED. @@ -135,6 +138,7 @@ class CastSocketImpl : public CastSocket { ChannelAuthType channel_auth, net::NetLog* net_log, const base::TimeDelta& connect_timeout, + bool keep_alive, const scoped_refptr<Logger>& logger, long device_capabilities); @@ -153,6 +157,7 @@ class CastSocketImpl : public CastSocket { std::string cast_url() const override; ReadyState ready_state() const override; ChannelError error_state() const override; + bool keep_alive() const override; // Required by ApiResourceManager. static const char* service_name() { return "CastSocketManager"; } @@ -167,6 +172,7 @@ class CastSocketImpl : public CastSocket { void OnError(ChannelError error_state, const LastErrors& last_errors) override; void OnMessage(const CastMessage& message) override; + void Start() override; private: CastSocketImpl* socket_; @@ -271,6 +277,8 @@ class CastSocketImpl : public CastSocket { net::NetLog* net_log_; // The NetLog source for this service. net::NetLog::Source net_log_source_; + // True when keep-alive signaling should be handled for this socket. + bool keep_alive_; // Shared logging object, used to log CastSocket events for diagnostics. scoped_refptr<Logger> logger_; diff --git a/extensions/browser/api/cast_channel/cast_socket_unittest.cc b/extensions/browser/api/cast_channel/cast_socket_unittest.cc index bf08e13..868c8b5 100644 --- a/extensions/browser/api/cast_channel/cast_socket_unittest.cc +++ b/extensions/browser/api/cast_channel/cast_socket_unittest.cc @@ -47,23 +47,6 @@ namespace core_api { namespace cast_channel { const char kAuthNamespace[] = "urn:x-cast:com.google.cast.tp.deviceauth"; -// Checks if two proto messages are the same. -// From -// third_party/cacheinvalidation/overrides/google/cacheinvalidation/deps/gmock.h -// TODO(kmarshall): promote to a shared testing library. -MATCHER_P(EqualsProto, message, "") { - std::string expected_serialized, actual_serialized; - message.SerializeToString(&expected_serialized); - arg.SerializeToString(&actual_serialized); - return expected_serialized == actual_serialized; -} - -ACTION_TEMPLATE(RunCompletionCallback, - HAS_1_TEMPLATE_PARAMS(int, cb_idx), - AND_1_VALUE_PARAMS(rv)) { - testing::get<cb_idx>(args).Run(rv); -} - // Returns an auth challenge message inline. CastMessage CreateAuthChallenge() { CastMessage output; @@ -157,6 +140,7 @@ class MockDelegate : public CastTransport::Delegate { MOCK_METHOD2(OnError, void(ChannelError error_state, const LastErrors& last_errors)); MOCK_METHOD1(OnMessage, void(const CastMessage& message)); + MOCK_METHOD0(Start, void()); private: DISALLOW_COPY_AND_ASSIGN(MockDelegate); @@ -202,6 +186,7 @@ class TestCastSocket : public CastSocketImpl { channel_auth, &capturing_net_log_, base::TimeDelta::FromMilliseconds(timeout_ms), + false, logger, device_capabilities), ip_(ip_endpoint), @@ -384,13 +369,14 @@ class CastSocketTest : public testing::Test { EXPECT_CALL(*socket_->GetMockTransport(), SendMessage(EqualsProto(challenge_proto), _)) .WillOnce(RunCompletionCallback<1>(net::OK)); - EXPECT_CALL(*socket_->GetMockTransport(), StartReading()); + EXPECT_CALL(*socket_->GetMockTransport(), Start()); EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE)); socket_->Connect(read_delegate_.Pass(), base::Bind(&CompleteHandler::OnConnectComplete, base::Unretained(&handler_))); RunPendingTasks(); - socket_->auth_delegate_.OnMessage(CreateAuthReply()); + socket_->GetMockTransport()->current_delegate()->OnMessage( + CreateAuthReply()); RunPendingTasks(); } @@ -576,7 +562,7 @@ TEST_F(CastSocketTest, TestConnectAuthMessageCorrupted) { EXPECT_CALL(*socket_->GetMockTransport(), SendMessage(EqualsProto(challenge_proto), _)) .WillOnce(RunCompletionCallback<1>(net::OK)); - EXPECT_CALL(*socket_->GetMockTransport(), StartReading()); + EXPECT_CALL(*socket_->GetMockTransport(), Start()); EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_TRANSPORT_ERROR)); socket_->Connect(read_delegate_.Pass(), base::Bind(&CompleteHandler::OnConnectComplete, @@ -585,7 +571,8 @@ TEST_F(CastSocketTest, TestConnectAuthMessageCorrupted) { CastMessage mangled_auth_reply = CreateAuthReply(); mangled_auth_reply.set_namespace_("BOGUS_NAMESPACE"); - socket_->auth_delegate_.OnMessage(mangled_auth_reply); + socket_->GetMockTransport()->current_delegate()->OnMessage( + mangled_auth_reply); RunPendingTasks(); EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state()); @@ -753,10 +740,12 @@ TEST_F(CastSocketTest, TestConnectChallengeReplyReceiveError) { socket_->AddReadResult(net::SYNCHRONOUS, net::ERR_FAILED); EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_SOCKET_ERROR)); + EXPECT_CALL(*socket_->GetMockTransport(), Start()); socket_->Connect(read_delegate_.Pass(), base::Bind(&CompleteHandler::OnConnectComplete, base::Unretained(&handler_))); - socket_->auth_delegate_.OnError(CHANNEL_ERROR_SOCKET_ERROR, LastErrors()); + socket_->GetMockTransport()->current_delegate()->OnError( + CHANNEL_ERROR_SOCKET_ERROR, LastErrors()); RunPendingTasks(); EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state()); @@ -777,11 +766,12 @@ TEST_F(CastSocketTest, TestConnectChallengeVerificationFails) { SendMessage(EqualsProto(challenge_proto), _)) .WillOnce(RunCompletionCallback<1>(net::OK)); EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR)); + EXPECT_CALL(*socket_->GetMockTransport(), Start()); socket_->Connect(read_delegate_.Pass(), base::Bind(&CompleteHandler::OnConnectComplete, base::Unretained(&handler_))); RunPendingTasks(); - socket_->auth_delegate_.OnMessage(CreateAuthReply()); + socket_->GetMockTransport()->current_delegate()->OnMessage(CreateAuthReply()); RunPendingTasks(); EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state()); diff --git a/extensions/browser/api/cast_channel/cast_transport.cc b/extensions/browser/api/cast_channel/cast_transport.cc index 2acf2d91..6724662 100644 --- a/extensions/browser/api/cast_channel/cast_transport.cc +++ b/extensions/browser/api/cast_channel/cast_transport.cc @@ -27,13 +27,12 @@ namespace core_api { namespace cast_channel { CastTransportImpl::CastTransportImpl(net::Socket* socket, - Delegate* read_delegate, int channel_id, const net::IPEndPoint& ip_endpoint, ChannelAuthType channel_auth, scoped_refptr<Logger> logger) - : socket_(socket), - read_delegate_(read_delegate), + : started_(false), + socket_(socket), write_state_(WRITE_STATE_NONE), read_state_(READ_STATE_NONE), error_state_(CHANNEL_ERROR_NONE), @@ -42,7 +41,6 @@ CastTransportImpl::CastTransportImpl(net::Socket* socket, channel_auth_(channel_auth), logger_(logger) { DCHECK(socket); - DCHECK(read_delegate); // Buffer is reused across messages to minimize unnecessary buffer // [re]allocations. @@ -125,9 +123,13 @@ proto::ErrorState CastTransportImpl::ErrorStateToProto(ChannelError state) { } } -void CastTransportImpl::SetReadDelegate(Delegate* read_delegate) { +void CastTransportImpl::SetReadDelegate(scoped_ptr<Delegate> read_delegate) { + DCHECK(CalledOnValidThread()); DCHECK(read_delegate); - read_delegate_ = read_delegate; + read_delegate_ = read_delegate.Pass(); + if (started_) { + read_delegate_->Start(); + } } void CastTransportImpl::FlushWriteQueue() { @@ -315,13 +317,18 @@ int CastTransportImpl::DoWriteError(int result) { return net::ERR_FAILED; } -void CastTransportImpl::StartReading() { +void CastTransportImpl::Start() { DCHECK(CalledOnValidThread()); + DCHECK(!started_); + DCHECK(read_delegate_) + << "Read delegate must be set prior to calling Start()"; + read_delegate_->Start(); if (read_state_ == READ_STATE_NONE) { // Initialize and run the read state machine. SetReadState(READ_STATE_READ); OnReadResult(net::OK); } + started_ = true; } void CastTransportImpl::OnReadResult(int result) { diff --git a/extensions/browser/api/cast_channel/cast_transport.h b/extensions/browser/api/cast_channel/cast_transport.h index 7b8cdaf..8a84a6d 100644 --- a/extensions/browser/api/cast_channel/cast_transport.h +++ b/extensions/browser/api/cast_channel/cast_transport.h @@ -41,11 +41,16 @@ class CastTransport { public: virtual ~Delegate() {} + // Called once Transport is successfully initialized and started. + // Owned read delegates are Start()ed automatically. + virtual void Start() = 0; + // An error occurred on the channel. |last_errors| contains the last errors // logged for the channel from the implementation. // The caller is responsible for closing |socket| if an error occurred. virtual void OnError(ChannelError error_state, const LastErrors& last_errors) = 0; + // A message was received on the channel. virtual void OnMessage(const CastMessage& message) = 0; }; @@ -60,24 +65,25 @@ class CastTransport { // Initializes the reading state machine and starts reading from the // underlying socket. // Virtual for testing. - virtual void StartReading() = 0; + virtual void Start() = 0; // Changes the delegate for processing read events. Pending reads remain // in-flight. - virtual void SetReadDelegate(Delegate* delegate) = 0; + // Ownership of the pointee of |delegate| is assumed by the transport. + // Prior delegates are deleted automatically. + virtual void SetReadDelegate(scoped_ptr<Delegate> delegate) = 0; }; // Manager class for reading and writing messages to/from a socket. -// TODO(kmarshall): Handle heartbeat messages in this layer. class CastTransportImpl : public CastTransport, public base::NonThreadSafe { public: // Adds a CastMessage read/write layer to a socket. // Message read events are propagated to the owner via |read_delegate|. - // |socket|, |read_delegate| and |logger| must all out-live the - // CastTransportImpl instance. // |vlog_prefix| sets the prefix used for all VLOGged output. + // |socket| and |logger| must all out-live the + // CastTransportImpl instance. + // |read_delegate| is owned by this CastTransportImpl object. CastTransportImpl(net::Socket* socket, - Delegate* read_delegate, int channel_id, const net::IPEndPoint& ip_endpoint_, ChannelAuthType channel_auth_, @@ -88,8 +94,8 @@ class CastTransportImpl : public CastTransport, public base::NonThreadSafe { // CastTransport interface. void SendMessage(const CastMessage& message, const net::CompletionCallback& callback) override; - void StartReading() override; - void SetReadDelegate(Delegate* delegate) override; + void Start() override; + void SetReadDelegate(scoped_ptr<Delegate> delegate) override; private: // Internal write states. @@ -161,6 +167,10 @@ class CastTransportImpl : public CastTransport, public base::NonThreadSafe { int DoReadCallback(); int DoReadError(int result); + // Indicates that the transport object is started and may receive and send + // messages. + bool started_; + // Queue of pending writes. The message at the front of the queue is the one // being written. std::queue<WriteRequest> write_queue_; @@ -178,7 +188,7 @@ class CastTransportImpl : public CastTransport, public base::NonThreadSafe { net::Socket* const socket_; // Methods for communicating message receipt and error status to client code. - Delegate* read_delegate_; + scoped_ptr<Delegate> read_delegate_; // Write flow state machine state. WriteState write_state_; diff --git a/extensions/browser/api/cast_channel/cast_transport_unittest.cc b/extensions/browser/api/cast_channel/cast_transport_unittest.cc index de5af62..f160b5a 100644 --- a/extensions/browser/api/cast_channel/cast_transport_unittest.cc +++ b/extensions/browser/api/cast_channel/cast_transport_unittest.cc @@ -111,24 +111,8 @@ ACTION_TEMPLATE(EnqueueCallback, completion_queue->Push(testing::get<cb_idx>(args)); } -// Checks if two proto messages are the same. -// From -// third_party/cacheinvalidation/overrides/google/cacheinvalidation/deps/gmock.h -MATCHER_P(EqualsProto, message, "") { - std::string expected_serialized, actual_serialized; - message.SerializeToString(&expected_serialized); - arg.SerializeToString(&actual_serialized); - return expected_serialized == actual_serialized; -} } // namespace -class MockCastTransportDelegate : public CastTransport::Delegate { - public: - MOCK_METHOD2(OnError, - void(ChannelError error, const LastErrors& last_errors)); - MOCK_METHOD1(OnMessage, void(const CastMessage& message)); -}; - class MockSocket : public net::Socket { public: MOCK_METHOD3(Read, @@ -158,14 +142,15 @@ class CastTransportTest : public testing::Test { : logger_(new Logger( scoped_ptr<base::TickClock>(new base::SimpleTestTickClock), base::TimeTicks())) { - transport_.reset(new CastTransportImpl(&mock_socket_, &delegate_, 0, - CreateIPEndPointForTest(), - auth_type_, logger_)); + delegate_ = new MockCastTransportDelegate; + transport_.reset(new CastTransportImpl( + &mock_socket_, 0, CreateIPEndPointForTest(), auth_type_, logger_)); + transport_->SetReadDelegate(make_scoped_ptr(delegate_)); } ~CastTransportTest() override {} protected: - MockCastTransportDelegate delegate_; + MockCastTransportDelegate* delegate_; MockSocket mock_socket_; ChannelAuthType auth_type_; Logger* logger_; @@ -329,11 +314,11 @@ TEST_F(CastTransportTest, TestFullReadAsync) { Return(net::ERR_IO_PENDING))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnMessage(EqualsProto(message))); + EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message))); EXPECT_CALL(mock_socket_, Read(NotNull(), MessageFramer::MessageHeader::header_size(), _)) .WillOnce(Return(net::ERR_IO_PENDING)); - transport_->StartReading(); + transport_->Start(); socket_cbs.Pop(MessageFramer::MessageHeader::header_size()); socket_cbs.Pop(serialized_message.size() - MessageFramer::MessageHeader::header_size()); @@ -374,8 +359,8 @@ TEST_F(CastTransportTest, TestPartialReadAsync) { EnqueueCallback<2>(&socket_cbs), Return(net::ERR_IO_PENDING))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnMessage(EqualsProto(message))); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message))); + transport_->Start(); socket_cbs.Pop(MessageFramer::MessageHeader::header_size()); socket_cbs.Pop(serialized_message.size() - MessageFramer::MessageHeader::header_size() - 1); @@ -400,8 +385,8 @@ TEST_F(CastTransportTest, TestReadErrorInHeaderAsync) { Return(net::ERR_IO_PENDING))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); + transport_->Start(); // Header read failure. socket_cbs.Pop(net::ERR_CONNECTION_RESET); } @@ -433,8 +418,8 @@ TEST_F(CastTransportTest, TestReadErrorInBodyAsync) { EnqueueCallback<2>(&socket_cbs), Return(net::ERR_IO_PENDING))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); + transport_->Start(); // Header read is OK. socket_cbs.Pop(MessageFramer::MessageHeader::header_size()); // Body read fails. @@ -476,8 +461,8 @@ TEST_F(CastTransportTest, TestReadCorruptedMessageAsync) { Return(net::ERR_IO_PENDING))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE, _)); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE, _)); + transport_->Start(); socket_cbs.Pop(MessageFramer::MessageHeader::header_size()); socket_cbs.Pop(serialized_message.size() - MessageFramer::MessageHeader::header_size()); @@ -510,12 +495,12 @@ TEST_F(CastTransportTest, TestFullReadSync) { Return(serialized_message.size() - MessageFramer::MessageHeader::header_size()))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnMessage(EqualsProto(message))); + EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message))); // Async result in order to discontinue the read loop. EXPECT_CALL(mock_socket_, Read(NotNull(), MessageFramer::MessageHeader::header_size(), _)) .WillOnce(Return(net::ERR_IO_PENDING)); - transport_->StartReading(); + transport_->Start(); } TEST_F(CastTransportTest, TestPartialReadSync) { @@ -550,11 +535,11 @@ TEST_F(CastTransportTest, TestPartialReadSync) { serialized_message.size() - 1, 1)), Return(1))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnMessage(EqualsProto(message))); + EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message))); EXPECT_CALL(mock_socket_, Read(NotNull(), MessageFramer::MessageHeader::header_size(), _)) .WillOnce(Return(net::ERR_IO_PENDING)); - transport_->StartReading(); + transport_->Start(); } TEST_F(CastTransportTest, TestReadErrorInHeaderSync) { @@ -569,8 +554,8 @@ TEST_F(CastTransportTest, TestReadErrorInHeaderSync) { .WillOnce(DoAll(FillBufferFromString<0>(serialized_message), Return(net::ERR_CONNECTION_RESET))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); + transport_->Start(); } TEST_F(CastTransportTest, TestReadErrorInBodySync) { @@ -596,8 +581,8 @@ TEST_F(CastTransportTest, TestReadErrorInBodySync) { MessageFramer::MessageHeader::header_size() - 1)), Return(net::ERR_CONNECTION_RESET))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); + transport_->Start(); } TEST_F(CastTransportTest, TestReadCorruptedMessageSync) { @@ -632,8 +617,8 @@ TEST_F(CastTransportTest, TestReadCorruptedMessageSync) { Return(serialized_message.size() - MessageFramer::MessageHeader::header_size()))) .RetiresOnSaturation(); - EXPECT_CALL(delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE, _)); - transport_->StartReading(); + EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE, _)); + transport_->Start(); } } // namespace cast_channel } // namespace core_api diff --git a/extensions/browser/api/cast_channel/keep_alive_delegate.cc b/extensions/browser/api/cast_channel/keep_alive_delegate.cc new file mode 100644 index 0000000..d8fd716 --- /dev/null +++ b/extensions/browser/api/cast_channel/keep_alive_delegate.cc @@ -0,0 +1,194 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "extensions/browser/api/cast_channel/cast_message_util.h" +#include "extensions/browser/api/cast_channel/cast_socket.h" +#include "extensions/browser/api/cast_channel/keep_alive_delegate.h" +#include "extensions/common/api/cast_channel/cast_channel.pb.h" +#include "extensions/common/api/cast_channel/logging.pb.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace core_api { +namespace cast_channel { +namespace { + +const char kHeartbeatNamespace[] = "urn:x-cast:com.google.cast.tp.heartbeat"; +const char kPingSenderId[] = "chrome"; +const char kPingReceiverId[] = "receiver-0"; +const char kTypeNodeId[] = "type"; + +// Determines if the JSON-encoded payload is equivalent to +// { "type": |chk_type| } +bool NestedPayloadTypeEquals(const std::string& chk_type, + const CastMessage& message) { + MessageInfo message_info; + CastMessageToMessageInfo(message, &message_info); + std::string type_json; + if (!message_info.data->GetAsString(&type_json)) { + return false; + } + scoped_ptr<base::Value> type_value(base::JSONReader::Read(type_json)); + if (!type_value.get()) { + return false; + } + + base::DictionaryValue* type_dict; + if (!type_value->GetAsDictionary(&type_dict)) { + return false; + } + + std::string type_string; + return (type_dict->HasKey(kTypeNodeId) && + type_dict->GetString(kTypeNodeId, &type_string) && + type_string == chk_type); +} + +} // namespace + +// static +const char KeepAliveDelegate::kHeartbeatPingType[] = "PING"; + +// static +const char KeepAliveDelegate::kHeartbeatPongType[] = "PONG"; + +// static +CastMessage KeepAliveDelegate::CreateKeepAliveMessage( + const char* message_type) { + CastMessage output; + output.set_protocol_version(CastMessage::CASTV2_1_0); + output.set_source_id(kPingSenderId); + output.set_destination_id(kPingReceiverId); + output.set_namespace_(kHeartbeatNamespace); + base::DictionaryValue type_dict; + type_dict.SetString(kTypeNodeId, message_type); + if (!base::JSONWriter::Write(&type_dict, output.mutable_payload_utf8())) { + LOG(ERROR) << "Failed to serialize dictionary."; + return output; + } + output.set_payload_type( + CastMessage::PayloadType::CastMessage_PayloadType_STRING); + return output; +} + +KeepAliveDelegate::KeepAliveDelegate( + CastSocket* socket, + scoped_ptr<CastTransport::Delegate> inner_delegate, + base::TimeDelta ping_interval, + base::TimeDelta liveness_timeout) + : started_(false), + socket_(socket), + inner_delegate_(inner_delegate.Pass()), + liveness_timeout_(liveness_timeout), + ping_interval_(ping_interval) { + DCHECK(ping_interval_ < liveness_timeout_); + DCHECK(inner_delegate_); + DCHECK(socket_); + ping_message_ = CreateKeepAliveMessage(kHeartbeatPingType); + pong_message_ = CreateKeepAliveMessage(kHeartbeatPongType); +} + +KeepAliveDelegate::~KeepAliveDelegate() { +} + +void KeepAliveDelegate::SetTimersForTest( + scoped_ptr<base::Timer> injected_ping_timer, + scoped_ptr<base::Timer> injected_liveness_timer) { + ping_timer_ = injected_ping_timer.Pass(); + liveness_timer_ = injected_liveness_timer.Pass(); +} + +void KeepAliveDelegate::Start() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!started_); + + VLOG(1) << "Starting keep-alive timers."; + VLOG(1) << "Ping timeout: " << ping_interval_; + VLOG(1) << "Liveness timeout: " << liveness_timeout_; + + // Use injected mock timers, if provided. + if (!ping_timer_) { + ping_timer_.reset(new base::Timer(true, false)); + } + if (!liveness_timer_) { + liveness_timer_.reset(new base::Timer(true, false)); + } + + ping_timer_->Start( + FROM_HERE, ping_interval_, + base::Bind(&KeepAliveDelegate::SendKeepAliveMessage, + base::Unretained(this), ping_message_, kHeartbeatPingType)); + liveness_timer_->Start( + FROM_HERE, liveness_timeout_, + base::Bind(&KeepAliveDelegate::LivenessTimeout, base::Unretained(this))); + + started_ = true; + inner_delegate_->Start(); +} + +void KeepAliveDelegate::ResetTimers() { + DCHECK(started_); + ping_timer_->Reset(); + liveness_timer_->Reset(); +} + +void KeepAliveDelegate::SendKeepAliveMessage(const CastMessage& message, + const char* message_type) const { + DCHECK(thread_checker_.CalledOnValidThread()); + VLOG(1) << "Sending " << message_type; + socket_->transport()->SendMessage( + message, base::Bind(&KeepAliveDelegate::SendKeepAliveMessageComplete, + base::Unretained(this), message_type)); +} + +void KeepAliveDelegate::SendKeepAliveMessageComplete(const char* message_type, + int rv) const { + VLOG(2) << "Sending " << message_type << " complete, rv=" << rv; + if (rv != net::OK) { + // An error occurred while sending the ping response. + // Close the connection. + VLOG(1) << "Error sending " << message_type; + inner_delegate_->OnError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR, + LastErrors()); + } +} + +void KeepAliveDelegate::LivenessTimeout() const { + VLOG(1) << "Ping timeout"; + inner_delegate_->OnError(cast_channel::CHANNEL_ERROR_PING_TIMEOUT, + LastErrors()); +} + +// CastTransport::Delegate interface. +void KeepAliveDelegate::OnError(ChannelError error_state, + const LastErrors& last_errors) { + DCHECK(started_); + DCHECK(thread_checker_.CalledOnValidThread()); + VLOG(2) << "KeepAlive::OnError"; + inner_delegate_->OnError(error_state, last_errors); +} + +void KeepAliveDelegate::OnMessage(const CastMessage& message) { + DCHECK(started_); + DCHECK(thread_checker_.CalledOnValidThread()); + VLOG(2) << "KeepAlive::OnMessage : " << message.payload_utf8(); + + ResetTimers(); + + if (NestedPayloadTypeEquals(kHeartbeatPingType, message)) { + VLOG(1) << "Received PING."; + SendKeepAliveMessage(pong_message_, kHeartbeatPongType); + } else if (NestedPayloadTypeEquals(kHeartbeatPongType, message)) { + VLOG(1) << "Received PONG."; + } else { + // PING and PONG messages are intentionally suppressed from layers above. + inner_delegate_->OnMessage(message); + } +} + +} // namespace cast_channel +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/cast_channel/keep_alive_delegate.h b/extensions/browser/api/cast_channel/keep_alive_delegate.h new file mode 100644 index 0000000..baf6a8c --- /dev/null +++ b/extensions/browser/api/cast_channel/keep_alive_delegate.h @@ -0,0 +1,106 @@ +// Copyright 2015 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 EXTENSIONS_BROWSER_API_CAST_CHANNEL_KEEP_ALIVE_DELEGATE_H_ +#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_KEEP_ALIVE_DELEGATE_H_ + +#include "base/threading/thread_checker.h" +#include "base/timer/timer.h" +#include "extensions/browser/api/cast_channel/cast_transport.h" +#include "extensions/common/api/cast_channel/cast_channel.pb.h" + +namespace extensions { +namespace core_api { +namespace cast_channel { + +class CastSocket; + +// Decorator delegate which provides keep-alive functionality. +// Keep-alive messages are handled by this object; all other messages and +// errors are passed to |inner_delegate_|. +class KeepAliveDelegate : public CastTransport::Delegate { + public: + // |socket|: The socket to be kept alive. + // |inner_delegate|: The delegate which processes all non-keep-alive + // messages. This object assumes ownership of + // |inner_delegate|. + // |ping_interval|: The amount of idle time to wait before sending a PING to + // the remote end. + // |liveness_timeout|: The amount of idle time to wait before terminating the + // connection. + KeepAliveDelegate(CastSocket* socket, + scoped_ptr<CastTransport::Delegate> inner_delegate, + base::TimeDelta ping_interval, + base::TimeDelta liveness_timeout); + + ~KeepAliveDelegate() override; + + // Creates a keep-alive message (e.g. PING or PONG). + static CastMessage CreateKeepAliveMessage(const char* message_type); + + void SetTimersForTest(scoped_ptr<base::Timer> injected_ping_timer, + scoped_ptr<base::Timer> injected_liveness_timer); + + // CastTransport::Delegate implementation. + void Start() override; + void OnError(ChannelError error_state, + const LastErrors& last_errors) override; + void OnMessage(const CastMessage& message) override; + + static const char kHeartbeatPingType[]; + static const char kHeartbeatPongType[]; + + private: + // Restarts the ping/liveness timeout timers. Called when a message + // is received from the remote end. + void ResetTimers(); + + // Sends a formatted PING or PONG message to the remote side. + void SendKeepAliveMessage(const CastMessage& message, + const char* message_type) const; + + // Callback for SendKeepAliveMessage. + void SendKeepAliveMessageComplete(const char* message_type, int rv) const; + + // Called when the liveness timer expires, indicating that the remote + // end has not responded within the |liveness_timeout_| interval. + void LivenessTimeout() const; + + // Indicates that Start() was called. + bool started_; + + // Socket that is managed by the keep-alive object. + CastSocket* socket_; + + // Delegate object which receives all non-keep alive messages. + scoped_ptr<CastTransport::Delegate> inner_delegate_; + + // Amount of idle time to wait before disconnecting. + base::TimeDelta liveness_timeout_; + + // Amount of idle time to wait before pinging the receiver. + base::TimeDelta ping_interval_; + + // Fired when |ping_interval_| is exceeded or when triggered by test code. + scoped_ptr<base::Timer> ping_timer_; + + // Fired when |liveness_timer_| is exceeded. + scoped_ptr<base::Timer> liveness_timer_; + + // The PING message to send over the wire. + CastMessage ping_message_; + + // The PONG message to send over the wire. + CastMessage pong_message_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(KeepAliveDelegate); +}; + +} // namespace cast_channel +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_ diff --git a/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc b/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc new file mode 100644 index 0000000..b287719 --- /dev/null +++ b/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc @@ -0,0 +1,130 @@ +// Copyright 2015 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 "extensions/browser/api/cast_channel/keep_alive_delegate.h" + +#include "base/timer/mock_timer.h" +#include "extensions/browser/api/cast_channel/test_util.h" +#include "net/base/net_errors.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; + +namespace extensions { +namespace core_api { +namespace cast_channel { +namespace { + +const int64 kTestPingTimeoutMillis = 1000; +const int64 kTestLivenessTimeoutMillis = 10000; + +// Extends MockTimer with a mockable method ResetTriggered() which permits +// test code to set GMock expectations for Timer::Reset(). +class MockTimerWithMonitoredReset : public base::MockTimer { + public: + MockTimerWithMonitoredReset(bool retain_user_task, bool is_repeating) + : base::MockTimer(retain_user_task, is_repeating) {} + ~MockTimerWithMonitoredReset() override {} + + // Instrumentation point for determining how many times Reset() was called. + MOCK_METHOD0(ResetTriggered, void(void)); + + // Passes through the Reset call to the base MockTimer and visits the mock + // ResetTriggered method. + void Reset() override { + base::MockTimer::Reset(); + ResetTriggered(); + } +}; + +class KeepAliveDelegateTest : public testing::Test { + public: + KeepAliveDelegateTest() {} + ~KeepAliveDelegateTest() override {} + + protected: + void SetUp() override { + inner_delegate_ = new MockCastTransportDelegate; + keep_alive_.reset(new KeepAliveDelegate( + &socket_, make_scoped_ptr(inner_delegate_), + base::TimeDelta::FromMilliseconds(kTestPingTimeoutMillis), + base::TimeDelta::FromMilliseconds(kTestLivenessTimeoutMillis))); + liveness_timer_ = new MockTimerWithMonitoredReset(true, false); + ping_timer_ = new MockTimerWithMonitoredReset(true, false); + keep_alive_->SetTimersForTest(make_scoped_ptr(ping_timer_), + make_scoped_ptr(liveness_timer_)); + } + + MockCastSocket socket_; + scoped_ptr<KeepAliveDelegate> keep_alive_; + MockCastTransportDelegate* inner_delegate_; + MockTimerWithMonitoredReset* liveness_timer_; + MockTimerWithMonitoredReset* ping_timer_; + + DISALLOW_COPY_AND_ASSIGN(KeepAliveDelegateTest); +}; + +TEST_F(KeepAliveDelegateTest, TestPing) { + EXPECT_CALL(*socket_.mock_transport(), + SendMessage(EqualsProto(KeepAliveDelegate::CreateKeepAliveMessage( + KeepAliveDelegate::kHeartbeatPingType)), + _)).WillOnce(RunCompletionCallback<1>(net::OK)); + EXPECT_CALL(*inner_delegate_, Start()); + EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2); + EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2); + + keep_alive_->Start(); + ping_timer_->Fire(); + keep_alive_->OnMessage(KeepAliveDelegate::CreateKeepAliveMessage( + KeepAliveDelegate::kHeartbeatPongType)); +} + +TEST_F(KeepAliveDelegateTest, TestPingFailed) { + EXPECT_CALL(*socket_.mock_transport(), + SendMessage(EqualsProto(KeepAliveDelegate::CreateKeepAliveMessage( + KeepAliveDelegate::kHeartbeatPingType)), + _)) + .WillOnce(RunCompletionCallback<1>(net::ERR_CONNECTION_RESET)); + EXPECT_CALL(*inner_delegate_, Start()); + EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR, _)); + EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1); + EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1); + + keep_alive_->Start(); + ping_timer_->Fire(); +} + +TEST_F(KeepAliveDelegateTest, TestPingAndLivenessTimeout) { + EXPECT_CALL(*socket_.mock_transport(), + SendMessage(EqualsProto(KeepAliveDelegate::CreateKeepAliveMessage( + KeepAliveDelegate::kHeartbeatPingType)), + _)).WillOnce(RunCompletionCallback<1>(net::OK)); + EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_PING_TIMEOUT, _)); + EXPECT_CALL(*inner_delegate_, Start()); + EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1); + EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1); + + keep_alive_->Start(); + ping_timer_->Fire(); + liveness_timer_->Fire(); +} + +TEST_F(KeepAliveDelegateTest, TestResetTimersAndPassthroughAllOtherTraffic) { + CastMessage other_message = + KeepAliveDelegate::CreateKeepAliveMessage("NEITHER_PING_NOR_PONG"); + + EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(other_message))); + EXPECT_CALL(*inner_delegate_, Start()); + EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2); + EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2); + + keep_alive_->Start(); + keep_alive_->OnMessage(other_message); +} + +} // namespace +} // namespace cast_channel +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/cast_channel/test_util.cc b/extensions/browser/api/cast_channel/test_util.cc index 83b0bb5..4d4a4fc 100644 --- a/extensions/browser/api/cast_channel/test_util.cc +++ b/extensions/browser/api/cast_channel/test_util.cc @@ -8,11 +8,34 @@ namespace extensions { namespace core_api { namespace cast_channel { +const char kTestExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf"; + MockCastTransport::MockCastTransport() { } MockCastTransport::~MockCastTransport() { } +CastTransport::Delegate* MockCastTransport::current_delegate() const { + CHECK(delegate_); + return delegate_.get(); +} + +void MockCastTransport::SetReadDelegate( + scoped_ptr<CastTransport::Delegate> delegate) { + delegate_ = delegate.Pass(); +} + +MockCastTransportDelegate::MockCastTransportDelegate() { +} +MockCastTransportDelegate::~MockCastTransportDelegate() { +} + +MockCastSocket::MockCastSocket() + : CastSocket(kTestExtensionId), mock_transport_(new MockCastTransport) { +} +MockCastSocket::~MockCastSocket() { +} + net::IPEndPoint CreateIPEndPointForTest() { net::IPAddressNumber number; number.push_back(192); diff --git a/extensions/browser/api/cast_channel/test_util.h b/extensions/browser/api/cast_channel/test_util.h index 60276d1..defa627 100644 --- a/extensions/browser/api/cast_channel/test_util.h +++ b/extensions/browser/api/cast_channel/test_util.h @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_TEST_UTIL_H +#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_TEST_UTIL_H + +#include "extensions/browser/api/cast_channel/cast_socket.h" #include "extensions/browser/api/cast_channel/cast_transport.h" #include "extensions/common/api/cast_channel/cast_channel.pb.h" #include "net/base/ip_endpoint.h" @@ -11,27 +15,109 @@ namespace extensions { namespace core_api { namespace cast_channel { +extern const char kTestExtensionId[]; + class MockCastTransport : public extensions::core_api::cast_channel::CastTransport { public: MockCastTransport(); + ~MockCastTransport() override; - virtual ~MockCastTransport() override; + void SetReadDelegate(scoped_ptr<CastTransport::Delegate> delegate) override; MOCK_METHOD2( SendMessage, void(const extensions::core_api::cast_channel::CastMessage& message, const net::CompletionCallback& callback)); - MOCK_METHOD0(StartReading, void(void)); - MOCK_METHOD1(SetReadDelegate, void(CastTransport::Delegate* delegate)); + + MOCK_METHOD0(Start, void(void)); + + // Gets the read delegate that is currently active for this transport. + CastTransport::Delegate* current_delegate() const; private: + scoped_ptr<CastTransport::Delegate> delegate_; + DISALLOW_COPY_AND_ASSIGN(MockCastTransport); }; +class MockCastTransportDelegate : public CastTransport::Delegate { + public: + MockCastTransportDelegate(); + ~MockCastTransportDelegate() override; + + MOCK_METHOD2(OnError, + void(ChannelError error, const LastErrors& last_errors)); + MOCK_METHOD1(OnMessage, void(const CastMessage& message)); + MOCK_METHOD0(Start, void(void)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockCastTransportDelegate); +}; + +class MockCastSocket : public CastSocket { + public: + MockCastSocket(); + ~MockCastSocket() override; + + // Mockable version of Connect. Accepts a bare pointer to a mock object. + // (GMock won't compile with scoped_ptr method parameters.) + MOCK_METHOD2(ConnectRawPtr, + void(CastTransport::Delegate* delegate, + base::Callback<void(ChannelError)> callback)); + + // Proxy for ConnectRawPtr. Unpacks scoped_ptr into a GMock-friendly bare + // ptr. + virtual void Connect(scoped_ptr<CastTransport::Delegate> delegate, + base::Callback<void(ChannelError)> callback) override { + delegate_ = delegate.Pass(); + ConnectRawPtr(delegate_.get(), callback); + } + + MOCK_METHOD1(Close, void(const net::CompletionCallback& callback)); + MOCK_CONST_METHOD0(ip_endpoint, const net::IPEndPoint&()); + MOCK_CONST_METHOD0(id, int()); + MOCK_METHOD1(set_id, void(int id)); + MOCK_CONST_METHOD0(channel_auth, ChannelAuthType()); + MOCK_CONST_METHOD0(cast_url, std::string()); + MOCK_CONST_METHOD0(ready_state, ReadyState()); + MOCK_CONST_METHOD0(error_state, ChannelError()); + MOCK_CONST_METHOD0(keep_alive, bool(void)); + MOCK_METHOD1(SetErrorState, void(ChannelError error_state)); + + CastTransport* transport() const override { return mock_transport_.get(); } + + MockCastTransport* mock_transport() const { return mock_transport_.get(); } + + private: + scoped_ptr<MockCastTransport> mock_transport_; + scoped_ptr<CastTransport::Delegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(MockCastSocket); +}; + // Creates the IPEndpoint 192.168.1.1. net::IPEndPoint CreateIPEndPointForTest(); +// Checks if two proto messages are the same. +// From +// third_party/cacheinvalidation/overrides/google/cacheinvalidation/deps/gmock.h +// TODO(kmarshall): promote to a shared testing library. +MATCHER_P(EqualsProto, message, "") { + std::string expected_serialized, actual_serialized; + message.SerializeToString(&expected_serialized); + arg.SerializeToString(&actual_serialized); + return expected_serialized == actual_serialized; +} + +ACTION_TEMPLATE(RunCompletionCallback, + HAS_1_TEMPLATE_PARAMS(int, cb_idx), + AND_1_VALUE_PARAMS(rv)) { + testing::get<cb_idx>(args).Run(rv); +} + } // namespace cast_channel } // namespace core_api } // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_TEST_UTIL_H diff --git a/extensions/common/api/cast_channel.idl b/extensions/common/api/cast_channel.idl index f74ebf2..003e388 100644 --- a/extensions/common/api/cast_channel.idl +++ b/extensions/common/api/cast_channel.idl @@ -5,7 +5,6 @@ // API for communicating with a Google Cast device over an authenticated // channel. namespace cast.channel { - // The state of the channel. enum ReadyState { // The channel is connecting. @@ -44,6 +43,8 @@ namespace cast.channel { invalid_channel_id, // The connection could not be established before timing out. connect_timeout, + // The receiving end became unresponsive. + ping_timeout, // Unspecified error. unknown }; @@ -74,6 +75,17 @@ namespace cast.channel { // The authentication method required for the channel. ChannelAuthType auth; + // ------------------------------------------------------------------------ + // Both pingInterval and livenessTimeout must be set to enable keep-alive + // handling. + + // The amount of time to wait in milliseconds before sending pings + // to idle channels. + long? pingInterval; + + // The maximum amount of idle time allowed before a channel is closed. + long? livenessTimeout; + // If set, CastDeviceCapability bitmask values describing capability of the // cast device. long? capabilities; @@ -97,6 +109,9 @@ namespace cast.channel { // If set, the last error condition encountered by the channel. ChannelError? errorState; + + // If true, keep-alive messages are handled automatically by the channel. + boolean keepAlive; }; // Describes a message sent or received over the channel. Currently only diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index 4306d35..c8338df 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -389,6 +389,8 @@ 'browser/api/cast_channel/cast_framer.h', 'browser/api/cast_channel/cast_transport.h', 'browser/api/cast_channel/cast_transport.cc', + 'browser/api/cast_channel/keep_alive_delegate.cc', + 'browser/api/cast_channel/keep_alive_delegate.h', 'browser/api/cast_channel/logger.cc', 'browser/api/cast_channel/logger.h', 'browser/api/cast_channel/logger_util.cc', @@ -1203,6 +1205,7 @@ 'browser/api/cast_channel/cast_framer_unittest.cc', 'browser/api/cast_channel/cast_socket_unittest.cc', 'browser/api/cast_channel/cast_transport_unittest.cc', + 'browser/api/cast_channel/keep_alive_delegate_unittest.cc', 'browser/api/cast_channel/logger_unittest.cc', 'browser/api/cast_channel/test_util.cc', 'browser/api/cast_channel/test_util.h', |