Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / extensions / browser / api / cast_channel / cast_socket.cc
blobbfb31d093ac9592611577e48aa6185a0182673d9
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_socket.h"
7 #include <stdlib.h>
8 #include <string.h>
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/format_macros.h"
13 #include "base/lazy_instance.h"
14 #include "base/numerics/safe_conversions.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/sys_byteorder.h"
18 #include "base/time/time.h"
19 #include "extensions/browser/api/cast_channel/cast_auth_util.h"
20 #include "extensions/browser/api/cast_channel/cast_framer.h"
21 #include "extensions/browser/api/cast_channel/cast_message_util.h"
22 #include "extensions/browser/api/cast_channel/cast_transport.h"
23 #include "extensions/browser/api/cast_channel/logger.h"
24 #include "extensions/browser/api/cast_channel/logger_util.h"
25 #include "extensions/common/api/cast_channel/cast_channel.pb.h"
26 #include "net/base/address_list.h"
27 #include "net/base/host_port_pair.h"
28 #include "net/base/net_errors.h"
29 #include "net/base/net_util.h"
30 #include "net/cert/cert_verifier.h"
31 #include "net/cert/x509_certificate.h"
32 #include "net/http/transport_security_state.h"
33 #include "net/socket/client_socket_factory.h"
34 #include "net/socket/client_socket_handle.h"
35 #include "net/socket/ssl_client_socket.h"
36 #include "net/socket/stream_socket.h"
37 #include "net/socket/tcp_client_socket.h"
38 #include "net/ssl/ssl_config_service.h"
39 #include "net/ssl/ssl_info.h"
41 // Assumes |ip_endpoint_| of type net::IPEndPoint and |channel_auth_| of enum
42 // type ChannelAuthType are available in the current scope.
43 #define VLOG_WITH_CONNECTION(level) VLOG(level) << "[" << \
44 ip_endpoint_.ToString() << ", auth=" << channel_auth_ << "] "
46 namespace {
48 const int kMaxSelfSignedCertLifetimeInDays = 4;
50 std::string FormatTimeForLogging(base::Time time) {
51 base::Time::Exploded exploded_time;
52 time.UTCExplode(&exploded_time);
53 return base::StringPrintf(
54 "%04d-%02d-%02d %02d:%02d:%02d.%03d UTC", exploded_time.year,
55 exploded_time.month, exploded_time.day_of_month, exploded_time.hour,
56 exploded_time.minute, exploded_time.second, exploded_time.millisecond);
59 } // namespace
61 namespace extensions {
62 static base::LazyInstance<BrowserContextKeyedAPIFactory<
63 ApiResourceManager<core_api::cast_channel::CastSocket> > > g_factory =
64 LAZY_INSTANCE_INITIALIZER;
66 // static
67 template <>
68 BrowserContextKeyedAPIFactory<
69 ApiResourceManager<core_api::cast_channel::CastSocket> >*
70 ApiResourceManager<core_api::cast_channel::CastSocket>::GetFactoryInstance() {
71 return g_factory.Pointer();
74 namespace core_api {
75 namespace cast_channel {
76 CastSocket::CastSocket(const std::string& owner_extension_id)
77 : ApiResource(owner_extension_id) {
80 bool CastSocket::IsPersistent() const {
81 return true;
84 CastSocketImpl::CastSocketImpl(const std::string& owner_extension_id,
85 const net::IPEndPoint& ip_endpoint,
86 ChannelAuthType channel_auth,
87 net::NetLog* net_log,
88 const base::TimeDelta& timeout,
89 bool keep_alive,
90 const scoped_refptr<Logger>& logger,
91 uint64 device_capabilities)
92 : CastSocket(owner_extension_id),
93 owner_extension_id_(owner_extension_id),
94 channel_id_(0),
95 ip_endpoint_(ip_endpoint),
96 channel_auth_(channel_auth),
97 net_log_(net_log),
98 keep_alive_(keep_alive),
99 logger_(logger),
100 connect_timeout_(timeout),
101 connect_timeout_timer_(new base::OneShotTimer<CastSocketImpl>),
102 is_canceled_(false),
103 device_capabilities_(device_capabilities),
104 connect_state_(proto::CONN_STATE_NONE),
105 error_state_(CHANNEL_ERROR_NONE),
106 ready_state_(READY_STATE_NONE),
107 auth_delegate_(nullptr) {
108 DCHECK(net_log_);
109 DCHECK(channel_auth_ == CHANNEL_AUTH_TYPE_SSL ||
110 channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED);
111 net_log_source_.type = net::NetLog::SOURCE_SOCKET;
112 net_log_source_.id = net_log_->NextID();
115 CastSocketImpl::~CastSocketImpl() {
116 // Ensure that resources are freed but do not run pending callbacks to avoid
117 // any re-entrancy.
118 CloseInternal();
121 ReadyState CastSocketImpl::ready_state() const {
122 return ready_state_;
125 ChannelError CastSocketImpl::error_state() const {
126 return error_state_;
129 const net::IPEndPoint& CastSocketImpl::ip_endpoint() const {
130 return ip_endpoint_;
133 int CastSocketImpl::id() const {
134 return channel_id_;
137 void CastSocketImpl::set_id(int id) {
138 channel_id_ = id;
141 ChannelAuthType CastSocketImpl::channel_auth() const {
142 return channel_auth_;
145 bool CastSocketImpl::keep_alive() const {
146 return keep_alive_;
149 scoped_ptr<net::TCPClientSocket> CastSocketImpl::CreateTcpSocket() {
150 net::AddressList addresses(ip_endpoint_);
151 return scoped_ptr<net::TCPClientSocket>(
152 new net::TCPClientSocket(addresses, net_log_, net_log_source_));
153 // Options cannot be set on the TCPClientSocket yet, because the
154 // underlying platform socket will not be created until Bind()
155 // or Connect() is called.
158 scoped_ptr<net::SSLClientSocket> CastSocketImpl::CreateSslSocket(
159 scoped_ptr<net::StreamSocket> socket) {
160 net::SSLConfig ssl_config;
161 // If a peer cert was extracted in a previous attempt to connect, then
162 // whitelist that cert.
163 if (!peer_cert_.empty()) {
164 net::SSLConfig::CertAndStatus cert_and_status;
165 cert_and_status.cert_status = net::CERT_STATUS_AUTHORITY_INVALID;
166 cert_and_status.der_cert = peer_cert_;
167 ssl_config.allowed_bad_certs.push_back(cert_and_status);
168 logger_->LogSocketEvent(channel_id_, proto::SSL_CERT_WHITELISTED);
171 cert_verifier_.reset(net::CertVerifier::CreateDefault());
172 transport_security_state_.reset(new net::TransportSecurityState);
173 net::SSLClientSocketContext context;
174 // CertVerifier and TransportSecurityState are owned by us, not the
175 // context object.
176 context.cert_verifier = cert_verifier_.get();
177 context.transport_security_state = transport_security_state_.get();
179 scoped_ptr<net::ClientSocketHandle> connection(new net::ClientSocketHandle);
180 connection->SetSocket(socket.Pass());
181 net::HostPortPair host_and_port = net::HostPortPair::FromIPEndPoint(
182 ip_endpoint_);
184 return net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket(
185 connection.Pass(), host_and_port, ssl_config, context);
188 bool CastSocketImpl::ExtractPeerCert(std::string* cert) {
189 DCHECK(cert);
190 DCHECK(peer_cert_.empty());
191 net::SSLInfo ssl_info;
192 if (!socket_->GetSSLInfo(&ssl_info) || !ssl_info.cert.get()) {
193 return false;
196 logger_->LogSocketEvent(channel_id_, proto::SSL_INFO_OBTAINED);
198 // Ensure that the peer cert (which is self-signed) doesn't have an excessive
199 // remaining life-time.
200 base::Time expiry = ssl_info.cert->valid_expiry();
201 base::Time lifetimeLimit =
202 base::Time::Now() +
203 base::TimeDelta::FromDays(kMaxSelfSignedCertLifetimeInDays);
204 if (expiry.is_null() || expiry > lifetimeLimit) {
205 logger_->LogSocketEventWithDetails(channel_id_,
206 proto::SSL_CERT_EXCESSIVE_LIFETIME,
207 FormatTimeForLogging(expiry));
208 return false;
211 bool result = net::X509Certificate::GetDEREncoded(
212 ssl_info.cert->os_cert_handle(), cert);
213 if (result) {
214 VLOG_WITH_CONNECTION(1) << "Successfully extracted peer certificate";
217 logger_->LogSocketEventWithRv(
218 channel_id_, proto::DER_ENCODED_CERT_OBTAIN, result ? 1 : 0);
219 return result;
222 bool CastSocketImpl::VerifyChannelPolicy(const AuthResult& result) {
223 if ((device_capabilities_ & CastDeviceCapability::VIDEO_OUT) != 0 &&
224 (result.channel_policies & AuthResult::POLICY_AUDIO_ONLY) != 0) {
225 LOG(ERROR) << "Audio only policy enforced";
226 logger_->LogSocketEventWithDetails(
227 channel_id_, proto::CHANNEL_POLICY_ENFORCED, std::string());
228 return false;
230 return true;
233 bool CastSocketImpl::VerifyChallengeReply() {
234 AuthResult result = AuthenticateChallengeReply(*challenge_reply_, peer_cert_);
235 logger_->LogSocketChallengeReplyEvent(channel_id_, result);
236 if (result.success()) {
237 VLOG(1) << result.error_message;
238 if (!VerifyChannelPolicy(result)) {
239 return false;
242 return result.success();
245 void CastSocketImpl::SetTransportForTesting(
246 scoped_ptr<CastTransport> transport) {
247 transport_ = transport.Pass();
250 void CastSocketImpl::Connect(scoped_ptr<CastTransport::Delegate> delegate,
251 base::Callback<void(ChannelError)> callback) {
252 DCHECK(CalledOnValidThread());
253 VLOG_WITH_CONNECTION(1) << "Connect readyState = " << ready_state_;
255 delegate_ = delegate.Pass();
257 if (ready_state_ != READY_STATE_NONE) {
258 logger_->LogSocketEventWithDetails(
259 channel_id_, proto::CONNECT_FAILED, "ReadyState not NONE");
260 callback.Run(CHANNEL_ERROR_CONNECT_ERROR);
261 return;
264 connect_callback_ = callback;
265 SetReadyState(READY_STATE_CONNECTING);
266 SetConnectState(proto::CONN_STATE_TCP_CONNECT);
268 // Set up connection timeout.
269 if (connect_timeout_.InMicroseconds() > 0) {
270 DCHECK(connect_timeout_callback_.IsCancelled());
271 connect_timeout_callback_.Reset(
272 base::Bind(&CastSocketImpl::OnConnectTimeout, base::Unretained(this)));
273 GetTimer()->Start(FROM_HERE,
274 connect_timeout_,
275 connect_timeout_callback_.callback());
278 DoConnectLoop(net::OK);
281 CastTransport* CastSocketImpl::transport() const {
282 DCHECK_EQ(ready_state_, READY_STATE_OPEN);
283 return transport_.get();
286 void CastSocketImpl::OnConnectTimeout() {
287 DCHECK(CalledOnValidThread());
288 // Stop all pending connection setup tasks and report back to the client.
289 is_canceled_ = true;
290 logger_->LogSocketEvent(channel_id_, proto::CONNECT_TIMED_OUT);
291 VLOG_WITH_CONNECTION(1) << "Timeout while establishing a connection.";
292 SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
293 DoConnectCallback();
296 void CastSocketImpl::PostTaskToStartConnectLoop(int result) {
297 DCHECK(CalledOnValidThread());
298 DCHECK(connect_loop_callback_.IsCancelled());
299 connect_loop_callback_.Reset(base::Bind(&CastSocketImpl::DoConnectLoop,
300 base::Unretained(this), result));
301 base::MessageLoop::current()->PostTask(FROM_HERE,
302 connect_loop_callback_.callback());
305 // This method performs the state machine transitions for connection flow.
306 // There are two entry points to this method:
307 // 1. Connect method: this starts the flow
308 // 2. Callback from network operations that finish asynchronously.
309 void CastSocketImpl::DoConnectLoop(int result) {
310 connect_loop_callback_.Cancel();
311 if (is_canceled_) {
312 LOG(ERROR) << "CANCELLED - Aborting DoConnectLoop.";
313 return;
316 // Network operations can either finish synchronously or asynchronously.
317 // This method executes the state machine transitions in a loop so that
318 // correct state transitions happen even when network operations finish
319 // synchronously.
320 int rv = result;
321 do {
322 proto::ConnectionState state = connect_state_;
323 // Default to CONN_STATE_NONE, which breaks the processing loop if any
324 // handler fails to transition to another state to continue processing.
325 connect_state_ = proto::CONN_STATE_NONE;
326 switch (state) {
327 case proto::CONN_STATE_TCP_CONNECT:
328 rv = DoTcpConnect();
329 break;
330 case proto::CONN_STATE_TCP_CONNECT_COMPLETE:
331 rv = DoTcpConnectComplete(rv);
332 break;
333 case proto::CONN_STATE_SSL_CONNECT:
334 DCHECK_EQ(net::OK, rv);
335 rv = DoSslConnect();
336 break;
337 case proto::CONN_STATE_SSL_CONNECT_COMPLETE:
338 rv = DoSslConnectComplete(rv);
339 break;
340 case proto::CONN_STATE_AUTH_CHALLENGE_SEND:
341 rv = DoAuthChallengeSend();
342 break;
343 case proto::CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE:
344 rv = DoAuthChallengeSendComplete(rv);
345 break;
346 case proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE:
347 rv = DoAuthChallengeReplyComplete(rv);
348 break;
349 default:
350 NOTREACHED() << "BUG in connect flow. Unknown state: " << state;
351 break;
353 } while (rv != net::ERR_IO_PENDING &&
354 connect_state_ != proto::CONN_STATE_NONE);
355 // Get out of the loop either when: // a. A network operation is pending, OR
356 // b. The Do* method called did not change state
358 // No state change occurred above. This means state has transitioned to NONE.
359 if (connect_state_ == proto::CONN_STATE_NONE) {
360 logger_->LogSocketConnectState(channel_id_, connect_state_);
363 if (rv != net::ERR_IO_PENDING) {
364 GetTimer()->Stop();
365 DoConnectCallback();
369 int CastSocketImpl::DoTcpConnect() {
370 DCHECK(connect_loop_callback_.IsCancelled());
371 VLOG_WITH_CONNECTION(1) << "DoTcpConnect";
372 SetConnectState(proto::CONN_STATE_TCP_CONNECT_COMPLETE);
373 tcp_socket_ = CreateTcpSocket();
375 int rv = tcp_socket_->Connect(
376 base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
377 logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT, rv);
378 return rv;
381 int CastSocketImpl::DoTcpConnectComplete(int connect_result) {
382 VLOG_WITH_CONNECTION(1) << "DoTcpConnectComplete: " << connect_result;
383 logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT_COMPLETE,
384 connect_result);
385 if (connect_result == net::OK) {
386 SetConnectState(proto::CONN_STATE_SSL_CONNECT);
387 } else {
388 SetErrorState(CHANNEL_ERROR_CONNECT_ERROR);
390 return connect_result;
393 int CastSocketImpl::DoSslConnect() {
394 DCHECK(connect_loop_callback_.IsCancelled());
395 VLOG_WITH_CONNECTION(1) << "DoSslConnect";
396 SetConnectState(proto::CONN_STATE_SSL_CONNECT_COMPLETE);
397 socket_ = CreateSslSocket(tcp_socket_.Pass());
399 int rv = socket_->Connect(
400 base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
401 logger_->LogSocketEventWithRv(channel_id_, proto::SSL_SOCKET_CONNECT, rv);
402 return rv;
405 int CastSocketImpl::DoSslConnectComplete(int result) {
406 logger_->LogSocketEventWithRv(channel_id_, proto::SSL_SOCKET_CONNECT_COMPLETE,
407 result);
408 VLOG_WITH_CONNECTION(1) << "DoSslConnectComplete: " << result;
409 if (result == net::ERR_CERT_AUTHORITY_INVALID &&
410 peer_cert_.empty() && ExtractPeerCert(&peer_cert_)) {
411 // Peer certificate fallback - try the connection again, from the top.
412 SetConnectState(proto::CONN_STATE_TCP_CONNECT);
413 } else if (result == net::OK) {
414 // SSL connection succeeded.
415 if (!transport_.get()) {
416 // Create a channel transport if one wasn't already set (e.g. by test
417 // code).
418 transport_.reset(new CastTransportImpl(this->socket_.get(), channel_id_,
419 ip_endpoint_, channel_auth_,
420 logger_));
422 auth_delegate_ = new AuthTransportDelegate(this);
423 transport_->SetReadDelegate(make_scoped_ptr(auth_delegate_));
424 if (channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED) {
425 // Additionally verify the connection with a handshake.
426 SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND);
427 } else {
428 transport_->Start();
430 } else {
431 SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
433 return result;
436 int CastSocketImpl::DoAuthChallengeSend() {
437 VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSend";
438 SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE);
440 CastMessage challenge_message;
441 CreateAuthChallengeMessage(&challenge_message);
442 VLOG_WITH_CONNECTION(1) << "Sending challenge: "
443 << CastMessageToString(challenge_message);
445 transport_->SendMessage(
446 challenge_message,
447 base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
449 // Always return IO_PENDING since the result is always asynchronous.
450 return net::ERR_IO_PENDING;
453 int CastSocketImpl::DoAuthChallengeSendComplete(int result) {
454 VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSendComplete: " << result;
455 if (result < 0) {
456 logger_->LogSocketEventWithRv(channel_id_,
457 proto::SEND_AUTH_CHALLENGE_FAILED, result);
458 SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
459 return result;
461 transport_->Start();
462 SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE);
463 return net::ERR_IO_PENDING;
466 CastSocketImpl::AuthTransportDelegate::AuthTransportDelegate(
467 CastSocketImpl* socket)
468 : socket_(socket), error_state_(CHANNEL_ERROR_NONE) {
469 DCHECK(socket);
472 ChannelError CastSocketImpl::AuthTransportDelegate::error_state() const {
473 return error_state_;
476 LastErrors CastSocketImpl::AuthTransportDelegate::last_errors() const {
477 return last_errors_;
480 void CastSocketImpl::AuthTransportDelegate::OnError(ChannelError error_state) {
481 error_state_ = error_state;
482 socket_->PostTaskToStartConnectLoop(net::ERR_CONNECTION_FAILED);
485 void CastSocketImpl::AuthTransportDelegate::OnMessage(
486 const CastMessage& message) {
487 if (!IsAuthMessage(message)) {
488 error_state_ = CHANNEL_ERROR_TRANSPORT_ERROR;
489 socket_->logger_->LogSocketEvent(socket_->channel_id_,
490 proto::AUTH_CHALLENGE_REPLY_INVALID);
491 socket_->PostTaskToStartConnectLoop(net::ERR_INVALID_RESPONSE);
492 } else {
493 socket_->challenge_reply_.reset(new CastMessage(message));
494 socket_->logger_->LogSocketEvent(socket_->channel_id_,
495 proto::RECEIVED_CHALLENGE_REPLY);
496 socket_->PostTaskToStartConnectLoop(net::OK);
500 void CastSocketImpl::AuthTransportDelegate::Start() {
503 int CastSocketImpl::DoAuthChallengeReplyComplete(int result) {
504 VLOG_WITH_CONNECTION(1) << "DoAuthChallengeReplyComplete: " << result;
505 if (auth_delegate_->error_state() != CHANNEL_ERROR_NONE) {
506 SetErrorState(auth_delegate_->error_state());
507 return net::ERR_CONNECTION_FAILED;
509 auth_delegate_ = nullptr;
510 if (result < 0) {
511 return result;
513 if (!VerifyChallengeReply()) {
514 SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
515 return net::ERR_CONNECTION_FAILED;
517 VLOG_WITH_CONNECTION(1) << "Auth challenge verification succeeded";
518 return net::OK;
521 void CastSocketImpl::DoConnectCallback() {
522 VLOG(1) << "DoConnectCallback (error_state = " << error_state_ << ")";
523 if (error_state_ == CHANNEL_ERROR_NONE) {
524 SetReadyState(READY_STATE_OPEN);
525 transport_->SetReadDelegate(delegate_.Pass());
526 } else {
527 SetReadyState(READY_STATE_CLOSED);
528 CloseInternal();
530 base::ResetAndReturn(&connect_callback_).Run(error_state_);
533 void CastSocketImpl::Close(const net::CompletionCallback& callback) {
534 DCHECK(CalledOnValidThread());
535 CloseInternal();
536 // Run this callback last. It may delete the socket.
537 callback.Run(net::OK);
540 void CastSocketImpl::CloseInternal() {
541 // TODO(mfoltz): Enforce this when CastChannelAPITest is rewritten to create
542 // and free sockets on the same thread. crbug.com/398242
543 DCHECK(CalledOnValidThread());
544 if (ready_state_ == READY_STATE_CLOSED) {
545 return;
548 VLOG_WITH_CONNECTION(1) << "Close ReadyState = " << ready_state_;
549 transport_.reset();
550 tcp_socket_.reset();
551 socket_.reset();
552 transport_security_state_.reset();
553 if (GetTimer()) {
554 GetTimer()->Stop();
557 // Cancel callbacks that we queued ourselves to re-enter the connect or read
558 // loops.
559 connect_loop_callback_.Cancel();
560 send_auth_challenge_callback_.Cancel();
561 connect_timeout_callback_.Cancel();
562 SetReadyState(READY_STATE_CLOSED);
563 logger_->LogSocketEvent(channel_id_, proto::SOCKET_CLOSED);
566 std::string CastSocketImpl::cast_url() const {
567 return ((channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED) ? "casts://"
568 : "cast://") +
569 ip_endpoint_.ToString();
572 bool CastSocketImpl::CalledOnValidThread() const {
573 return thread_checker_.CalledOnValidThread();
576 base::Timer* CastSocketImpl::GetTimer() {
577 return connect_timeout_timer_.get();
580 void CastSocketImpl::SetConnectState(proto::ConnectionState connect_state) {
581 if (connect_state_ != connect_state) {
582 connect_state_ = connect_state;
583 logger_->LogSocketConnectState(channel_id_, connect_state_);
587 void CastSocketImpl::SetReadyState(ReadyState ready_state) {
588 if (ready_state_ != ready_state) {
589 ready_state_ = ready_state;
590 logger_->LogSocketReadyState(channel_id_, ReadyStateToProto(ready_state_));
594 void CastSocketImpl::SetErrorState(ChannelError error_state) {
595 VLOG_WITH_CONNECTION(1) << "SetErrorState " << error_state;
596 DCHECK_EQ(CHANNEL_ERROR_NONE, error_state_);
597 error_state_ = error_state;
598 logger_->LogSocketErrorState(channel_id_, ErrorStateToProto(error_state_));
599 delegate_->OnError(error_state_);
601 } // namespace cast_channel
602 } // namespace core_api
603 } // namespace extensions
604 #undef VLOG_WITH_CONNECTION