summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorkmarshall <kmarshall@chromium.org>2015-01-14 19:10:03 -0800
committerCommit bot <commit-bot@chromium.org>2015-01-15 03:10:45 +0000
commit92c461d6d93838907d197571d54375e04b185ed9 (patch)
tree96edf1ff78fee4010be8e7b401300ae2f78e7e0a /extensions
parent7c0fea86f434f4cd1706ff6cc3cbfee709083d5b (diff)
downloadchromium_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')
-rw-r--r--extensions/BUILD.gn1
-rw-r--r--extensions/browser/BUILD.gn2
-rw-r--r--extensions/browser/api/cast_channel/cast_channel_api.cc59
-rw-r--r--extensions/browser/api/cast_channel/cast_channel_api.h16
-rw-r--r--extensions/browser/api/cast_channel/cast_channel_apitest.cc148
-rw-r--r--extensions/browser/api/cast_channel/cast_socket.cc46
-rw-r--r--extensions/browser/api/cast_channel/cast_socket.h10
-rw-r--r--extensions/browser/api/cast_channel/cast_socket_unittest.cc36
-rw-r--r--extensions/browser/api/cast_channel/cast_transport.cc21
-rw-r--r--extensions/browser/api/cast_channel/cast_transport.h28
-rw-r--r--extensions/browser/api/cast_channel/cast_transport_unittest.cc65
-rw-r--r--extensions/browser/api/cast_channel/keep_alive_delegate.cc194
-rw-r--r--extensions/browser/api/cast_channel/keep_alive_delegate.h106
-rw-r--r--extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc130
-rw-r--r--extensions/browser/api/cast_channel/test_util.cc23
-rw-r--r--extensions/browser/api/cast_channel/test_util.h92
-rw-r--r--extensions/common/api/cast_channel.idl17
-rw-r--r--extensions/extensions.gyp3
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',