Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / extensions / browser / api / cast_channel / cast_channel_api.cc
blobe00646e5858f289f30bfe872a8e6a60b49ce973a
1 // Copyright 2014 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.
5 #include "extensions/browser/api/cast_channel/cast_channel_api.h"
7 #include <limits>
8 #include <string>
10 #include "base/json/json_writer.h"
11 #include "base/lazy_instance.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/time/default_tick_clock.h"
15 #include "base/values.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "extensions/browser/api/cast_channel/cast_auth_ica.h"
18 #include "extensions/browser/api/cast_channel/cast_message_util.h"
19 #include "extensions/browser/api/cast_channel/cast_socket.h"
20 #include "extensions/browser/api/cast_channel/keep_alive_delegate.h"
21 #include "extensions/browser/api/cast_channel/logger.h"
22 #include "extensions/browser/event_router.h"
23 #include "extensions/common/api/cast_channel/cast_channel.pb.h"
24 #include "extensions/common/api/cast_channel/logging.pb.h"
25 #include "net/base/ip_endpoint.h"
26 #include "net/base/net_errors.h"
27 #include "net/base/net_util.h"
28 #include "url/gurl.h"
30 // Default timeout interval for connection setup.
31 // Used if not otherwise specified at ConnectInfo::timeout.
32 const int kDefaultConnectTimeoutMillis = 5000; // 5 seconds.
34 namespace extensions {
36 namespace Close = cast_channel::Close;
37 namespace OnError = cast_channel::OnError;
38 namespace OnMessage = cast_channel::OnMessage;
39 namespace Open = cast_channel::Open;
40 namespace Send = cast_channel::Send;
41 using cast_channel::CastDeviceCapability;
42 using cast_channel::CastMessage;
43 using cast_channel::CastSocket;
44 using cast_channel::ChannelAuthType;
45 using cast_channel::ChannelError;
46 using cast_channel::ChannelInfo;
47 using cast_channel::ConnectInfo;
48 using cast_channel::ErrorInfo;
49 using cast_channel::LastErrors;
50 using cast_channel::Logger;
51 using cast_channel::MessageInfo;
52 using cast_channel::ReadyState;
53 using content::BrowserThread;
55 namespace {
57 // T is an extension dictionary (MessageInfo or ChannelInfo)
58 template <class T>
59 std::string ParamToString(const T& info) {
60 scoped_ptr<base::DictionaryValue> dict = info.ToValue();
61 std::string out;
62 base::JSONWriter::Write(dict.get(), &out);
63 return out;
66 // Fills |channel_info| from the destination and state of |socket|.
67 void FillChannelInfo(const CastSocket& socket, ChannelInfo* channel_info) {
68 DCHECK(channel_info);
69 channel_info->channel_id = socket.id();
70 channel_info->url = socket.cast_url();
71 const net::IPEndPoint& ip_endpoint = socket.ip_endpoint();
72 channel_info->connect_info.ip_address = ip_endpoint.ToStringWithoutPort();
73 channel_info->connect_info.port = ip_endpoint.port();
74 channel_info->connect_info.auth = socket.channel_auth();
75 channel_info->ready_state = socket.ready_state();
76 channel_info->error_state = socket.error_state();
77 channel_info->keep_alive = socket.keep_alive();
80 // Fills |error_info| from |error_state| and |last_errors|.
81 void FillErrorInfo(ChannelError error_state,
82 const LastErrors& last_errors,
83 ErrorInfo* error_info) {
84 error_info->error_state = error_state;
85 if (last_errors.event_type != cast_channel::proto::EVENT_TYPE_UNKNOWN)
86 error_info->event_type.reset(new int(last_errors.event_type));
87 if (last_errors.challenge_reply_error_type !=
88 cast_channel::proto::CHALLENGE_REPLY_ERROR_NONE) {
89 error_info->challenge_reply_error_type.reset(
90 new int(last_errors.challenge_reply_error_type));
92 if (last_errors.net_return_value <= 0)
93 error_info->net_return_value.reset(new int(last_errors.net_return_value));
94 if (last_errors.nss_error_code < 0)
95 error_info->nss_error_code.reset(new int(last_errors.nss_error_code));
98 bool IsValidConnectInfoPort(const ConnectInfo& connect_info) {
99 return connect_info.port > 0 && connect_info.port <
100 std::numeric_limits<uint16_t>::max();
103 bool IsValidConnectInfoAuth(const ConnectInfo& connect_info) {
104 return connect_info.auth == cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED ||
105 connect_info.auth == cast_channel::CHANNEL_AUTH_TYPE_SSL;
108 bool IsValidConnectInfoIpAddress(const ConnectInfo& connect_info) {
109 net::IPAddressNumber ip_address;
110 return net::ParseIPLiteralToNumber(connect_info.ip_address, &ip_address);
113 } // namespace
115 CastChannelAPI::CastChannelAPI(content::BrowserContext* context)
116 : browser_context_(context),
117 logger_(
118 new Logger(scoped_ptr<base::TickClock>(new base::DefaultTickClock),
119 base::TimeTicks::UnixEpoch())) {
120 DCHECK(browser_context_);
123 // static
124 CastChannelAPI* CastChannelAPI::Get(content::BrowserContext* context) {
125 return BrowserContextKeyedAPIFactory<CastChannelAPI>::Get(context);
128 scoped_refptr<Logger> CastChannelAPI::GetLogger() {
129 return logger_;
132 void CastChannelAPI::SendEvent(const std::string& extension_id,
133 scoped_ptr<Event> event) {
134 DCHECK_CURRENTLY_ON(BrowserThread::UI);
135 EventRouter* event_router = EventRouter::Get(GetBrowserContext());
136 if (event_router) {
137 event_router->DispatchEventToExtension(extension_id, event.Pass());
141 static base::LazyInstance<BrowserContextKeyedAPIFactory<CastChannelAPI> >
142 g_factory = LAZY_INSTANCE_INITIALIZER;
144 // static
145 BrowserContextKeyedAPIFactory<CastChannelAPI>*
146 CastChannelAPI::GetFactoryInstance() {
147 return g_factory.Pointer();
150 void CastChannelAPI::SetSocketForTest(scoped_ptr<CastSocket> socket_for_test) {
151 socket_for_test_ = socket_for_test.Pass();
154 scoped_ptr<CastSocket> CastChannelAPI::GetSocketForTest() {
155 return socket_for_test_.Pass();
158 content::BrowserContext* CastChannelAPI::GetBrowserContext() const {
159 return browser_context_;
162 void CastChannelAPI::SetPingTimeoutTimerForTest(scoped_ptr<base::Timer> timer) {
163 injected_timeout_timer_ = timer.Pass();
166 scoped_ptr<base::Timer> CastChannelAPI::GetInjectedTimeoutTimerForTest() {
167 return injected_timeout_timer_.Pass();
170 CastChannelAPI::~CastChannelAPI() {}
172 CastChannelAsyncApiFunction::CastChannelAsyncApiFunction() : manager_(nullptr) {
175 CastChannelAsyncApiFunction::~CastChannelAsyncApiFunction() { }
177 bool CastChannelAsyncApiFunction::PrePrepare() {
178 manager_ = ApiResourceManager<CastSocket>::Get(browser_context());
179 return true;
182 bool CastChannelAsyncApiFunction::Respond() {
183 return GetError().empty();
186 CastSocket* CastChannelAsyncApiFunction::GetSocketOrCompleteWithError(
187 int channel_id) {
188 CastSocket* socket = GetSocket(channel_id);
189 if (!socket) {
190 SetResultFromError(channel_id,
191 cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
192 AsyncWorkCompleted();
194 return socket;
197 int CastChannelAsyncApiFunction::AddSocket(CastSocket* socket) {
198 DCHECK_CURRENTLY_ON(BrowserThread::IO);
199 DCHECK(socket);
200 DCHECK(manager_);
201 const int id = manager_->Add(socket);
202 socket->set_id(id);
203 return id;
206 void CastChannelAsyncApiFunction::RemoveSocket(int channel_id) {
207 DCHECK_CURRENTLY_ON(BrowserThread::IO);
208 DCHECK(manager_);
209 manager_->Remove(extension_->id(), channel_id);
212 void CastChannelAsyncApiFunction::SetResultFromSocket(
213 const CastSocket& socket) {
214 ChannelInfo channel_info;
215 FillChannelInfo(socket, &channel_info);
216 ChannelError error = socket.error_state();
217 if (error != cast_channel::CHANNEL_ERROR_NONE) {
218 SetError("Channel socket error = " + base::IntToString(error));
220 SetResultFromChannelInfo(channel_info);
223 void CastChannelAsyncApiFunction::SetResultFromError(int channel_id,
224 ChannelError error) {
225 ChannelInfo channel_info;
226 channel_info.channel_id = channel_id;
227 channel_info.url = "";
228 channel_info.ready_state = cast_channel::READY_STATE_CLOSED;
229 channel_info.error_state = error;
230 channel_info.connect_info.ip_address = "";
231 channel_info.connect_info.port = 0;
232 channel_info.connect_info.auth = cast_channel::CHANNEL_AUTH_TYPE_SSL;
233 SetResultFromChannelInfo(channel_info);
234 SetError("Channel error = " + base::IntToString(error));
237 CastSocket* CastChannelAsyncApiFunction::GetSocket(int channel_id) const {
238 DCHECK_CURRENTLY_ON(BrowserThread::IO);
239 DCHECK(manager_);
240 return manager_->Get(extension_->id(), channel_id);
243 void CastChannelAsyncApiFunction::SetResultFromChannelInfo(
244 const ChannelInfo& channel_info) {
245 DCHECK_CURRENTLY_ON(BrowserThread::IO);
246 SetResult(channel_info.ToValue().release());
249 CastChannelOpenFunction::CastChannelOpenFunction()
250 : new_channel_id_(0) { }
252 CastChannelOpenFunction::~CastChannelOpenFunction() { }
254 // TODO(mfoltz): Remove URL parsing when clients have converted to use
255 // ConnectInfo.
257 // Allowed schemes for Cast device URLs.
258 const char kCastInsecureScheme[] = "cast";
259 const char kCastSecureScheme[] = "casts";
261 bool CastChannelOpenFunction::ParseChannelUrl(const GURL& url,
262 ConnectInfo* connect_info) {
263 DCHECK(connect_info);
264 VLOG(2) << "ParseChannelUrl";
265 bool auth_required = false;
266 if (url.SchemeIs(kCastSecureScheme)) {
267 auth_required = true;
268 } else if (!url.SchemeIs(kCastInsecureScheme)) {
269 return false;
271 // TODO(mfoltz): Test for IPv6 addresses. Brackets or no brackets?
272 // TODO(mfoltz): Maybe enforce restriction to IPv4 private and IPv6
273 // link-local networks
274 const std::string& path = url.path();
275 // Shortest possible: //A:B
276 if (path.size() < 5) {
277 return false;
279 if (path.find("//") != 0) {
280 return false;
282 size_t colon = path.find_last_of(':');
283 if (colon == std::string::npos || colon < 3 || colon > path.size() - 2) {
284 return false;
286 const std::string& ip_address_str = path.substr(2, colon - 2);
287 const std::string& port_str = path.substr(colon + 1);
288 VLOG(2) << "IP: " << ip_address_str << " Port: " << port_str;
289 int port;
290 if (!base::StringToInt(port_str, &port))
291 return false;
292 connect_info->ip_address = ip_address_str;
293 connect_info->port = port;
294 connect_info->auth = auth_required ?
295 cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED :
296 cast_channel::CHANNEL_AUTH_TYPE_SSL;
297 return true;
300 net::IPEndPoint* CastChannelOpenFunction::ParseConnectInfo(
301 const ConnectInfo& connect_info) {
302 net::IPAddressNumber ip_address;
303 CHECK(net::ParseIPLiteralToNumber(connect_info.ip_address, &ip_address));
304 return new net::IPEndPoint(ip_address,
305 static_cast<uint16>(connect_info.port));
308 bool CastChannelOpenFunction::PrePrepare() {
309 api_ = CastChannelAPI::Get(browser_context());
310 return CastChannelAsyncApiFunction::PrePrepare();
313 bool CastChannelOpenFunction::Prepare() {
314 params_ = Open::Params::Create(*args_);
315 EXTENSION_FUNCTION_VALIDATE(params_.get());
316 // The connect_info parameter may be a string URL like cast:// or casts:// or
317 // a ConnectInfo object.
318 std::string cast_url;
319 switch (params_->connect_info->GetType()) {
320 case base::Value::TYPE_STRING:
321 CHECK(params_->connect_info->GetAsString(&cast_url));
322 connect_info_.reset(new ConnectInfo);
323 if (!ParseChannelUrl(GURL(cast_url), connect_info_.get())) {
324 connect_info_.reset();
325 SetError("Invalid connect_info (invalid Cast URL " + cast_url + ")");
327 break;
328 case base::Value::TYPE_DICTIONARY:
329 connect_info_ = ConnectInfo::FromValue(*(params_->connect_info));
330 if (!connect_info_.get()) {
331 SetError("connect_info.auth is required");
333 break;
334 default:
335 SetError("Invalid connect_info (unknown type)");
336 break;
338 if (!connect_info_.get()) {
339 return false;
341 if (!IsValidConnectInfoPort(*connect_info_)) {
342 SetError("Invalid connect_info (invalid port)");
343 } else if (!IsValidConnectInfoAuth(*connect_info_)) {
344 SetError("Invalid connect_info (invalid auth)");
345 } else if (!IsValidConnectInfoIpAddress(*connect_info_)) {
346 SetError("Invalid connect_info (invalid IP address)");
347 } else {
348 // Parse timeout parameters if they are set.
349 if (connect_info_->liveness_timeout) {
350 liveness_timeout_ =
351 base::TimeDelta::FromMilliseconds(*connect_info_->liveness_timeout);
353 if (connect_info_->ping_interval) {
354 ping_interval_ =
355 base::TimeDelta::FromMilliseconds(*connect_info_->ping_interval);
358 // Validate timeout parameters.
359 if (liveness_timeout_ < base::TimeDelta() ||
360 ping_interval_ < base::TimeDelta()) {
361 SetError("livenessTimeout and pingInterval must be greater than 0.");
362 } else if ((liveness_timeout_ > base::TimeDelta()) !=
363 (ping_interval_ > base::TimeDelta())) {
364 SetError("livenessTimeout and pingInterval must be set together.");
365 } else if (liveness_timeout_ < ping_interval_) {
366 SetError("livenessTimeout must be longer than pingTimeout.");
370 if (!GetError().empty()) {
371 return false;
374 channel_auth_ = connect_info_->auth;
375 ip_endpoint_.reset(ParseConnectInfo(*connect_info_));
376 return true;
379 void CastChannelOpenFunction::AsyncWorkStart() {
380 DCHECK(api_);
381 DCHECK(ip_endpoint_.get());
382 CastSocket* socket;
383 scoped_ptr<CastSocket> test_socket = api_->GetSocketForTest();
384 if (test_socket.get()) {
385 socket = test_socket.release();
386 } else {
387 socket = new cast_channel::CastSocketImpl(
388 extension_->id(), *ip_endpoint_, channel_auth_,
389 ExtensionsBrowserClient::Get()->GetNetLog(),
390 base::TimeDelta::FromMilliseconds(connect_info_->timeout.get()
391 ? *connect_info_->timeout
392 : kDefaultConnectTimeoutMillis),
393 liveness_timeout_ > base::TimeDelta(), api_->GetLogger(),
394 connect_info_->capabilities ? *connect_info_->capabilities
395 : CastDeviceCapability::NONE);
397 new_channel_id_ = AddSocket(socket);
398 api_->GetLogger()->LogNewSocketEvent(*socket);
400 // Construct read delegates.
401 scoped_ptr<core_api::cast_channel::CastTransport::Delegate> delegate(
402 make_scoped_ptr(new CastMessageHandler(
403 base::Bind(&CastChannelAPI::SendEvent, api_->AsWeakPtr()), socket,
404 api_->GetLogger())));
405 if (socket->keep_alive()) {
406 // Wrap read delegate in a KeepAliveDelegate for timeout handling.
407 core_api::cast_channel::KeepAliveDelegate* keep_alive =
408 new core_api::cast_channel::KeepAliveDelegate(
409 socket, api_->GetLogger(), delegate.Pass(), ping_interval_,
410 liveness_timeout_);
411 scoped_ptr<base::Timer> injected_timer =
412 api_->GetInjectedTimeoutTimerForTest();
413 if (injected_timer) {
414 keep_alive->SetTimersForTest(
415 make_scoped_ptr(new base::Timer(false, false)),
416 injected_timer.Pass());
418 delegate.reset(keep_alive);
421 api_->GetLogger()->LogNewSocketEvent(*socket);
422 socket->Connect(delegate.Pass(),
423 base::Bind(&CastChannelOpenFunction::OnOpen, this));
426 void CastChannelOpenFunction::OnOpen(cast_channel::ChannelError result) {
427 DCHECK_CURRENTLY_ON(BrowserThread::IO);
428 VLOG(1) << "Connect finished, OnOpen invoked.";
429 CastSocket* socket = GetSocket(new_channel_id_);
430 if (!socket) {
431 SetResultFromError(new_channel_id_, result);
432 } else {
433 SetResultFromSocket(*socket);
435 AsyncWorkCompleted();
438 CastChannelSendFunction::CastChannelSendFunction() { }
440 CastChannelSendFunction::~CastChannelSendFunction() { }
442 bool CastChannelSendFunction::Prepare() {
443 params_ = Send::Params::Create(*args_);
444 EXTENSION_FUNCTION_VALIDATE(params_.get());
445 if (params_->message.namespace_.empty()) {
446 SetError("message_info.namespace_ is required");
447 return false;
449 if (params_->message.source_id.empty()) {
450 SetError("message_info.source_id is required");
451 return false;
453 if (params_->message.destination_id.empty()) {
454 SetError("message_info.destination_id is required");
455 return false;
457 switch (params_->message.data->GetType()) {
458 case base::Value::TYPE_STRING:
459 case base::Value::TYPE_BINARY:
460 break;
461 default:
462 SetError("Invalid type of message_info.data");
463 return false;
465 return true;
468 void CastChannelSendFunction::AsyncWorkStart() {
469 CastSocket* socket = GetSocket(params_->channel.channel_id);
470 if (!socket) {
471 SetResultFromError(params_->channel.channel_id,
472 cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
473 AsyncWorkCompleted();
474 return;
476 CastMessage message_to_send;
477 if (!MessageInfoToCastMessage(params_->message, &message_to_send)) {
478 SetResultFromError(params_->channel.channel_id,
479 cast_channel::CHANNEL_ERROR_INVALID_MESSAGE);
480 AsyncWorkCompleted();
481 return;
483 socket->transport()->SendMessage(
484 message_to_send, base::Bind(&CastChannelSendFunction::OnSend, this));
487 void CastChannelSendFunction::OnSend(int result) {
488 DCHECK_CURRENTLY_ON(BrowserThread::IO);
489 int channel_id = params_->channel.channel_id;
490 CastSocket* socket = GetSocket(channel_id);
491 if (result < 0 || !socket) {
492 SetResultFromError(channel_id,
493 cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
494 } else {
495 SetResultFromSocket(*socket);
497 AsyncWorkCompleted();
500 CastChannelCloseFunction::CastChannelCloseFunction() { }
502 CastChannelCloseFunction::~CastChannelCloseFunction() { }
504 bool CastChannelCloseFunction::Prepare() {
505 params_ = Close::Params::Create(*args_);
506 EXTENSION_FUNCTION_VALIDATE(params_.get());
507 return true;
510 void CastChannelCloseFunction::AsyncWorkStart() {
511 CastSocket* socket = GetSocket(params_->channel.channel_id);
512 if (!socket) {
513 SetResultFromError(params_->channel.channel_id,
514 cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
515 AsyncWorkCompleted();
516 } else {
517 socket->Close(base::Bind(&CastChannelCloseFunction::OnClose, this));
521 void CastChannelCloseFunction::OnClose(int result) {
522 DCHECK_CURRENTLY_ON(BrowserThread::IO);
523 VLOG(1) << "CastChannelCloseFunction::OnClose result = " << result;
524 int channel_id = params_->channel.channel_id;
525 CastSocket* socket = GetSocket(channel_id);
526 if (result < 0 || !socket) {
527 SetResultFromError(channel_id,
528 cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
529 } else {
530 SetResultFromSocket(*socket);
531 // This will delete |socket|.
532 RemoveSocket(channel_id);
534 AsyncWorkCompleted();
537 CastChannelGetLogsFunction::CastChannelGetLogsFunction() {
540 CastChannelGetLogsFunction::~CastChannelGetLogsFunction() {
543 bool CastChannelGetLogsFunction::PrePrepare() {
544 api_ = CastChannelAPI::Get(browser_context());
545 return CastChannelAsyncApiFunction::PrePrepare();
548 bool CastChannelGetLogsFunction::Prepare() {
549 return true;
552 void CastChannelGetLogsFunction::AsyncWorkStart() {
553 DCHECK(api_);
555 size_t length = 0;
556 scoped_ptr<char[]> out = api_->GetLogger()->GetLogs(&length);
557 if (out.get()) {
558 SetResult(new base::BinaryValue(out.Pass(), length));
559 } else {
560 SetError("Unable to get logs.");
563 api_->GetLogger()->Reset();
565 AsyncWorkCompleted();
568 CastChannelOpenFunction::CastMessageHandler::CastMessageHandler(
569 const EventDispatchCallback& ui_dispatch_cb,
570 cast_channel::CastSocket* socket,
571 scoped_refptr<Logger> logger)
572 : ui_dispatch_cb_(ui_dispatch_cb), socket_(socket), logger_(logger) {
573 DCHECK(socket_);
574 DCHECK(logger_);
577 CastChannelOpenFunction::CastMessageHandler::~CastMessageHandler() {
580 void CastChannelOpenFunction::CastMessageHandler::OnError(
581 cast_channel::ChannelError error_state) {
582 DCHECK_CURRENTLY_ON(BrowserThread::IO);
584 ChannelInfo channel_info;
585 FillChannelInfo(*socket_, &channel_info);
586 channel_info.error_state = error_state;
587 ErrorInfo error_info;
588 FillErrorInfo(error_state, logger_->GetLastErrors(socket_->id()),
589 &error_info);
591 scoped_ptr<base::ListValue> results =
592 OnError::Create(channel_info, error_info);
593 scoped_ptr<Event> event(new Event(OnError::kEventName, results.Pass()));
594 BrowserThread::PostTask(
595 BrowserThread::UI, FROM_HERE,
596 base::Bind(ui_dispatch_cb_, socket_->owner_extension_id(),
597 base::Passed(event.Pass())));
600 void CastChannelOpenFunction::CastMessageHandler::OnMessage(
601 const CastMessage& message) {
602 DCHECK_CURRENTLY_ON(BrowserThread::IO);
604 MessageInfo message_info;
605 cast_channel::CastMessageToMessageInfo(message, &message_info);
606 ChannelInfo channel_info;
607 FillChannelInfo(*socket_, &channel_info);
608 VLOG(1) << "Received message " << ParamToString(message_info)
609 << " on channel " << ParamToString(channel_info);
611 scoped_ptr<base::ListValue> results =
612 OnMessage::Create(channel_info, message_info);
613 scoped_ptr<Event> event(new Event(OnMessage::kEventName, results.Pass()));
614 BrowserThread::PostTask(
615 BrowserThread::UI, FROM_HERE,
616 base::Bind(ui_dispatch_cb_, socket_->owner_extension_id(),
617 base::Passed(event.Pass())));
620 void CastChannelOpenFunction::CastMessageHandler::Start() {
623 CastChannelSetAuthorityKeysFunction::CastChannelSetAuthorityKeysFunction() {
626 CastChannelSetAuthorityKeysFunction::~CastChannelSetAuthorityKeysFunction() {
629 bool CastChannelSetAuthorityKeysFunction::Prepare() {
630 params_ = cast_channel::SetAuthorityKeys::Params::Create(*args_);
631 EXTENSION_FUNCTION_VALIDATE(params_.get());
632 return true;
635 void CastChannelSetAuthorityKeysFunction::AsyncWorkStart() {
636 std::string& keys = params_->keys;
637 std::string& signature = params_->signature;
638 if (signature.empty() || keys.empty() ||
639 !cast_channel::SetTrustedCertificateAuthorities(keys, signature)) {
640 SetError("Unable to set authority keys.");
643 AsyncWorkCompleted();
646 } // namespace extensions