Add ICU message format support
[chromium-blink-merge.git] / extensions / browser / api / cast_channel / cast_socket.cc
blobd3dba00745026b830675aca68f19942b4a7050c0
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<api::cast_channel::CastSocket>>> g_factory =
64 LAZY_INSTANCE_INITIALIZER;
66 // static
67 template <>
68 BrowserContextKeyedAPIFactory<
69 ApiResourceManager<api::cast_channel::CastSocket>>*
70 ApiResourceManager<api::cast_channel::CastSocket>::GetFactoryInstance() {
71 return g_factory.Pointer();
74 namespace 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 return transport_.get();
285 void CastSocketImpl::OnConnectTimeout() {
286 DCHECK(CalledOnValidThread());
287 // Stop all pending connection setup tasks and report back to the client.
288 is_canceled_ = true;
289 logger_->LogSocketEvent(channel_id_, proto::CONNECT_TIMED_OUT);
290 VLOG_WITH_CONNECTION(1) << "Timeout while establishing a connection.";
291 SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
292 DoConnectCallback();
295 void CastSocketImpl::PostTaskToStartConnectLoop(int result) {
296 DCHECK(CalledOnValidThread());
297 DCHECK(connect_loop_callback_.IsCancelled());
298 connect_loop_callback_.Reset(base::Bind(&CastSocketImpl::DoConnectLoop,
299 base::Unretained(this), result));
300 base::MessageLoop::current()->PostTask(FROM_HERE,
301 connect_loop_callback_.callback());
304 // This method performs the state machine transitions for connection flow.
305 // There are two entry points to this method:
306 // 1. Connect method: this starts the flow
307 // 2. Callback from network operations that finish asynchronously.
308 void CastSocketImpl::DoConnectLoop(int result) {
309 connect_loop_callback_.Cancel();
310 if (is_canceled_) {
311 LOG(ERROR) << "CANCELLED - Aborting DoConnectLoop.";
312 return;
315 // Network operations can either finish synchronously or asynchronously.
316 // This method executes the state machine transitions in a loop so that
317 // correct state transitions happen even when network operations finish
318 // synchronously.
319 int rv = result;
320 do {
321 proto::ConnectionState state = connect_state_;
322 // Default to CONN_STATE_NONE, which breaks the processing loop if any
323 // handler fails to transition to another state to continue processing.
324 connect_state_ = proto::CONN_STATE_NONE;
325 switch (state) {
326 case proto::CONN_STATE_TCP_CONNECT:
327 rv = DoTcpConnect();
328 break;
329 case proto::CONN_STATE_TCP_CONNECT_COMPLETE:
330 rv = DoTcpConnectComplete(rv);
331 break;
332 case proto::CONN_STATE_SSL_CONNECT:
333 DCHECK_EQ(net::OK, rv);
334 rv = DoSslConnect();
335 break;
336 case proto::CONN_STATE_SSL_CONNECT_COMPLETE:
337 rv = DoSslConnectComplete(rv);
338 break;
339 case proto::CONN_STATE_AUTH_CHALLENGE_SEND:
340 rv = DoAuthChallengeSend();
341 break;
342 case proto::CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE:
343 rv = DoAuthChallengeSendComplete(rv);
344 break;
345 case proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE:
346 rv = DoAuthChallengeReplyComplete(rv);
347 break;
348 default:
349 NOTREACHED() << "BUG in connect flow. Unknown state: " << state;
350 break;
352 } while (rv != net::ERR_IO_PENDING &&
353 connect_state_ != proto::CONN_STATE_NONE);
354 // Get out of the loop either when: // a. A network operation is pending, OR
355 // b. The Do* method called did not change state
357 // No state change occurred above. This means state has transitioned to NONE.
358 if (connect_state_ == proto::CONN_STATE_NONE) {
359 logger_->LogSocketConnectState(channel_id_, connect_state_);
362 if (rv != net::ERR_IO_PENDING) {
363 GetTimer()->Stop();
364 DoConnectCallback();
368 int CastSocketImpl::DoTcpConnect() {
369 DCHECK(connect_loop_callback_.IsCancelled());
370 VLOG_WITH_CONNECTION(1) << "DoTcpConnect";
371 SetConnectState(proto::CONN_STATE_TCP_CONNECT_COMPLETE);
372 tcp_socket_ = CreateTcpSocket();
374 int rv = tcp_socket_->Connect(
375 base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
376 logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT, rv);
377 return rv;
380 int CastSocketImpl::DoTcpConnectComplete(int connect_result) {
381 VLOG_WITH_CONNECTION(1) << "DoTcpConnectComplete: " << connect_result;
382 logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT_COMPLETE,
383 connect_result);
384 if (connect_result == net::OK) {
385 SetConnectState(proto::CONN_STATE_SSL_CONNECT);
386 } else if (connect_result == net::ERR_CONNECTION_TIMED_OUT) {
387 SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
388 } else {
389 SetErrorState(CHANNEL_ERROR_CONNECT_ERROR);
391 return connect_result;
394 int CastSocketImpl::DoSslConnect() {
395 DCHECK(connect_loop_callback_.IsCancelled());
396 VLOG_WITH_CONNECTION(1) << "DoSslConnect";
397 SetConnectState(proto::CONN_STATE_SSL_CONNECT_COMPLETE);
398 socket_ = CreateSslSocket(tcp_socket_.Pass());
400 int rv = socket_->Connect(
401 base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
402 logger_->LogSocketEventWithRv(channel_id_, proto::SSL_SOCKET_CONNECT, rv);
403 return rv;
406 int CastSocketImpl::DoSslConnectComplete(int result) {
407 logger_->LogSocketEventWithRv(channel_id_, proto::SSL_SOCKET_CONNECT_COMPLETE,
408 result);
409 VLOG_WITH_CONNECTION(1) << "DoSslConnectComplete: " << result;
410 if (result == net::ERR_CERT_AUTHORITY_INVALID &&
411 peer_cert_.empty() && ExtractPeerCert(&peer_cert_)) {
412 // Peer certificate fallback - try the connection again, from the top.
413 SetConnectState(proto::CONN_STATE_TCP_CONNECT);
414 } else if (result == net::OK) {
415 // SSL connection succeeded.
416 if (!transport_.get()) {
417 // Create a channel transport if one wasn't already set (e.g. by test
418 // code).
419 transport_.reset(new CastTransportImpl(this->socket_.get(), channel_id_,
420 ip_endpoint_, channel_auth_,
421 logger_));
423 auth_delegate_ = new AuthTransportDelegate(this);
424 transport_->SetReadDelegate(make_scoped_ptr(auth_delegate_));
425 if (channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED) {
426 // Additionally verify the connection with a handshake.
427 SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND);
428 } else {
429 transport_->Start();
431 } else if (result == net::ERR_CONNECTION_TIMED_OUT) {
432 SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
433 } else {
434 SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
436 return result;
439 int CastSocketImpl::DoAuthChallengeSend() {
440 VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSend";
441 SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE);
443 CastMessage challenge_message;
444 CreateAuthChallengeMessage(&challenge_message);
445 VLOG_WITH_CONNECTION(1) << "Sending challenge: "
446 << CastMessageToString(challenge_message);
448 transport_->SendMessage(
449 challenge_message,
450 base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
452 // Always return IO_PENDING since the result is always asynchronous.
453 return net::ERR_IO_PENDING;
456 int CastSocketImpl::DoAuthChallengeSendComplete(int result) {
457 VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSendComplete: " << result;
458 if (result < 0) {
459 logger_->LogSocketEventWithRv(channel_id_,
460 proto::SEND_AUTH_CHALLENGE_FAILED, result);
461 SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
462 return result;
464 transport_->Start();
465 SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE);
466 return net::ERR_IO_PENDING;
469 CastSocketImpl::AuthTransportDelegate::AuthTransportDelegate(
470 CastSocketImpl* socket)
471 : socket_(socket), error_state_(CHANNEL_ERROR_NONE) {
472 DCHECK(socket);
475 ChannelError CastSocketImpl::AuthTransportDelegate::error_state() const {
476 return error_state_;
479 LastErrors CastSocketImpl::AuthTransportDelegate::last_errors() const {
480 return last_errors_;
483 void CastSocketImpl::AuthTransportDelegate::OnError(ChannelError error_state) {
484 error_state_ = error_state;
485 socket_->PostTaskToStartConnectLoop(net::ERR_CONNECTION_FAILED);
488 void CastSocketImpl::AuthTransportDelegate::OnMessage(
489 const CastMessage& message) {
490 if (!IsAuthMessage(message)) {
491 error_state_ = CHANNEL_ERROR_TRANSPORT_ERROR;
492 socket_->logger_->LogSocketEvent(socket_->channel_id_,
493 proto::AUTH_CHALLENGE_REPLY_INVALID);
494 socket_->PostTaskToStartConnectLoop(net::ERR_INVALID_RESPONSE);
495 } else {
496 socket_->challenge_reply_.reset(new CastMessage(message));
497 socket_->logger_->LogSocketEvent(socket_->channel_id_,
498 proto::RECEIVED_CHALLENGE_REPLY);
499 socket_->PostTaskToStartConnectLoop(net::OK);
503 void CastSocketImpl::AuthTransportDelegate::Start() {
506 int CastSocketImpl::DoAuthChallengeReplyComplete(int result) {
507 VLOG_WITH_CONNECTION(1) << "DoAuthChallengeReplyComplete: " << result;
508 if (auth_delegate_->error_state() != CHANNEL_ERROR_NONE) {
509 SetErrorState(auth_delegate_->error_state());
510 return net::ERR_CONNECTION_FAILED;
512 auth_delegate_ = nullptr;
513 if (result < 0) {
514 return result;
516 if (!VerifyChallengeReply()) {
517 SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
518 return net::ERR_CONNECTION_FAILED;
520 VLOG_WITH_CONNECTION(1) << "Auth challenge verification succeeded";
521 return net::OK;
524 void CastSocketImpl::DoConnectCallback() {
525 VLOG(1) << "DoConnectCallback (error_state = " << error_state_ << ")";
526 if (error_state_ == CHANNEL_ERROR_NONE) {
527 SetReadyState(READY_STATE_OPEN);
528 transport_->SetReadDelegate(delegate_.Pass());
529 } else {
530 CloseInternal();
532 base::ResetAndReturn(&connect_callback_).Run(error_state_);
535 void CastSocketImpl::Close(const net::CompletionCallback& callback) {
536 DCHECK(CalledOnValidThread());
537 CloseInternal();
538 // Run this callback last. It may delete the socket.
539 callback.Run(net::OK);
542 void CastSocketImpl::CloseInternal() {
543 // TODO(mfoltz): Enforce this when CastChannelAPITest is rewritten to create
544 // and free sockets on the same thread. crbug.com/398242
545 DCHECK(CalledOnValidThread());
546 if (ready_state_ == READY_STATE_CLOSED) {
547 return;
550 VLOG_WITH_CONNECTION(1) << "Close ReadyState = " << ready_state_;
551 transport_.reset();
552 tcp_socket_.reset();
553 socket_.reset();
554 transport_security_state_.reset();
555 if (GetTimer()) {
556 GetTimer()->Stop();
559 // Cancel callbacks that we queued ourselves to re-enter the connect or read
560 // loops.
561 connect_loop_callback_.Cancel();
562 send_auth_challenge_callback_.Cancel();
563 connect_timeout_callback_.Cancel();
564 SetReadyState(READY_STATE_CLOSED);
565 logger_->LogSocketEvent(channel_id_, proto::SOCKET_CLOSED);
568 bool CastSocketImpl::CalledOnValidThread() const {
569 return thread_checker_.CalledOnValidThread();
572 base::Timer* CastSocketImpl::GetTimer() {
573 return connect_timeout_timer_.get();
576 void CastSocketImpl::SetConnectState(proto::ConnectionState connect_state) {
577 if (connect_state_ != connect_state) {
578 connect_state_ = connect_state;
579 logger_->LogSocketConnectState(channel_id_, connect_state_);
583 void CastSocketImpl::SetReadyState(ReadyState ready_state) {
584 if (ready_state_ != ready_state) {
585 ready_state_ = ready_state;
586 logger_->LogSocketReadyState(channel_id_, ReadyStateToProto(ready_state_));
590 void CastSocketImpl::SetErrorState(ChannelError error_state) {
591 VLOG_WITH_CONNECTION(1) << "SetErrorState " << error_state;
592 DCHECK_EQ(CHANNEL_ERROR_NONE, error_state_);
593 error_state_ = error_state;
594 logger_->LogSocketErrorState(channel_id_, ErrorStateToProto(error_state_));
595 delegate_->OnError(error_state_);
597 } // namespace cast_channel
598 } // namespace api
599 } // namespace extensions
600 #undef VLOG_WITH_CONNECTION