1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 #include "base/json/json_reader.h"
8 #include "base/json/json_writer.h"
9 #include "extensions/browser/api/cast_channel/cast_message_util.h"
10 #include "extensions/browser/api/cast_channel/cast_socket.h"
11 #include "extensions/browser/api/cast_channel/keep_alive_delegate.h"
12 #include "extensions/browser/api/cast_channel/logger.h"
13 #include "extensions/common/api/cast_channel/cast_channel.pb.h"
14 #include "extensions/common/api/cast_channel/logging.pb.h"
15 #include "net/base/net_errors.h"
17 namespace extensions
{
19 namespace cast_channel
{
22 const char kHeartbeatNamespace
[] = "urn:x-cast:com.google.cast.tp.heartbeat";
23 const char kPingSenderId
[] = "chrome";
24 const char kPingReceiverId
[] = "receiver-0";
25 const char kTypeNodeId
[] = "type";
27 // Determines if the JSON-encoded payload is equivalent to
28 // { "type": |chk_type| }
29 bool NestedPayloadTypeEquals(const std::string
& chk_type
,
30 const CastMessage
& message
) {
31 MessageInfo message_info
;
32 CastMessageToMessageInfo(message
, &message_info
);
33 std::string type_json
;
34 if (!message_info
.data
->GetAsString(&type_json
)) {
37 scoped_ptr
<base::Value
> type_value(base::JSONReader::Read(type_json
));
38 if (!type_value
.get()) {
42 base::DictionaryValue
* type_dict
;
43 if (!type_value
->GetAsDictionary(&type_dict
)) {
47 std::string type_string
;
48 return (type_dict
->HasKey(kTypeNodeId
) &&
49 type_dict
->GetString(kTypeNodeId
, &type_string
) &&
50 type_string
== chk_type
);
56 const char KeepAliveDelegate::kHeartbeatPingType
[] = "PING";
59 const char KeepAliveDelegate::kHeartbeatPongType
[] = "PONG";
62 CastMessage
KeepAliveDelegate::CreateKeepAliveMessage(
63 const char* message_type
) {
65 output
.set_protocol_version(CastMessage::CASTV2_1_0
);
66 output
.set_source_id(kPingSenderId
);
67 output
.set_destination_id(kPingReceiverId
);
68 output
.set_namespace_(kHeartbeatNamespace
);
69 base::DictionaryValue type_dict
;
70 type_dict
.SetString(kTypeNodeId
, message_type
);
71 if (!base::JSONWriter::Write(&type_dict
, output
.mutable_payload_utf8())) {
72 LOG(ERROR
) << "Failed to serialize dictionary.";
75 output
.set_payload_type(
76 CastMessage::PayloadType::CastMessage_PayloadType_STRING
);
80 KeepAliveDelegate::KeepAliveDelegate(
82 scoped_refptr
<Logger
> logger
,
83 scoped_ptr
<CastTransport::Delegate
> inner_delegate
,
84 base::TimeDelta ping_interval
,
85 base::TimeDelta liveness_timeout
)
89 inner_delegate_(inner_delegate
.Pass()),
90 liveness_timeout_(liveness_timeout
),
91 ping_interval_(ping_interval
) {
92 DCHECK(ping_interval_
< liveness_timeout_
);
93 DCHECK(inner_delegate_
);
95 ping_message_
= CreateKeepAliveMessage(kHeartbeatPingType
);
96 pong_message_
= CreateKeepAliveMessage(kHeartbeatPongType
);
99 KeepAliveDelegate::~KeepAliveDelegate() {
102 void KeepAliveDelegate::SetTimersForTest(
103 scoped_ptr
<base::Timer
> injected_ping_timer
,
104 scoped_ptr
<base::Timer
> injected_liveness_timer
) {
105 ping_timer_
= injected_ping_timer
.Pass();
106 liveness_timer_
= injected_liveness_timer
.Pass();
109 void KeepAliveDelegate::Start() {
110 DCHECK(thread_checker_
.CalledOnValidThread());
113 VLOG(1) << "Starting keep-alive timers.";
114 VLOG(1) << "Ping timeout: " << ping_interval_
;
115 VLOG(1) << "Liveness timeout: " << liveness_timeout_
;
117 // Use injected mock timers, if provided.
119 ping_timer_
.reset(new base::Timer(true, false));
121 if (!liveness_timer_
) {
122 liveness_timer_
.reset(new base::Timer(true, false));
126 FROM_HERE
, ping_interval_
,
127 base::Bind(&KeepAliveDelegate::SendKeepAliveMessage
,
128 base::Unretained(this), ping_message_
, kHeartbeatPingType
));
129 liveness_timer_
->Start(
130 FROM_HERE
, liveness_timeout_
,
131 base::Bind(&KeepAliveDelegate::LivenessTimeout
, base::Unretained(this)));
134 inner_delegate_
->Start();
137 void KeepAliveDelegate::ResetTimers() {
139 ping_timer_
->Reset();
140 liveness_timer_
->Reset();
143 void KeepAliveDelegate::SendKeepAliveMessage(const CastMessage
& message
,
144 const char* message_type
) const {
145 DCHECK(thread_checker_
.CalledOnValidThread());
146 VLOG(1) << "Sending " << message_type
;
147 socket_
->transport()->SendMessage(
148 message
, base::Bind(&KeepAliveDelegate::SendKeepAliveMessageComplete
,
149 base::Unretained(this), message_type
));
152 void KeepAliveDelegate::SendKeepAliveMessageComplete(const char* message_type
,
154 VLOG(2) << "Sending " << message_type
<< " complete, rv=" << rv
;
156 // An error occurred while sending the ping response.
157 VLOG(1) << "Error sending " << message_type
;
158 logger_
->LogSocketEventWithRv(socket_
->id(), proto::PING_WRITE_ERROR
, rv
);
159 inner_delegate_
->OnError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR
);
163 void KeepAliveDelegate::LivenessTimeout() const {
164 VLOG(1) << "Ping timeout";
165 inner_delegate_
->OnError(cast_channel::CHANNEL_ERROR_PING_TIMEOUT
);
168 // CastTransport::Delegate interface.
169 void KeepAliveDelegate::OnError(ChannelError error_state
) {
171 DCHECK(thread_checker_
.CalledOnValidThread());
172 VLOG(2) << "KeepAlive::OnError";
173 inner_delegate_
->OnError(error_state
);
176 void KeepAliveDelegate::OnMessage(const CastMessage
& message
) {
178 DCHECK(thread_checker_
.CalledOnValidThread());
179 VLOG(2) << "KeepAlive::OnMessage : " << message
.payload_utf8();
183 if (NestedPayloadTypeEquals(kHeartbeatPingType
, message
)) {
184 VLOG(1) << "Received PING.";
185 SendKeepAliveMessage(pong_message_
, kHeartbeatPongType
);
186 } else if (NestedPayloadTypeEquals(kHeartbeatPongType
, message
)) {
187 VLOG(1) << "Received PONG.";
189 // PING and PONG messages are intentionally suppressed from layers above.
190 inner_delegate_
->OnMessage(message
);
194 } // namespace cast_channel
195 } // namespace core_api
196 } // namespace extensions