Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(175)

Side by Side Diff: chrome/browser/extensions/api/dial/dial_service.cc

Issue 2756483007: [Device Discovery] Move files from browser/extensions/api/dial to browser/media/router/discovery/di… (Closed)
Patch Set: resolve code review comments from Devlin Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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.
4
5 #include "chrome/browser/extensions/api/dial/dial_service.h"
6
7 #include <stdint.h>
8
9 #include <algorithm>
10 #include <set>
11 #include <utility>
12
13 #include "base/callback.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/rand_util.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "base/time/time.h"
23 #include "build/build_config.h"
24 #include "chrome/browser/extensions/api/dial/dial_device_data.h"
25 #include "components/version_info/version_info.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "net/base/address_family.h"
28 #include "net/base/completion_callback.h"
29 #include "net/base/io_buffer.h"
30 #include "net/base/ip_endpoint.h"
31 #include "net/base/net_errors.h"
32 #include "net/base/network_interfaces.h"
33 #include "net/http/http_response_headers.h"
34 #include "net/http/http_util.h"
35 #include "net/log/net_log.h"
36 #include "net/log/net_log_source_type.h"
37 #include "url/gurl.h"
38
39 #if defined(OS_CHROMEOS)
40 #include "chromeos/network/network_state.h"
41 #include "chromeos/network/network_state_handler.h"
42 #include "third_party/cros_system_api/dbus/service_constants.h"
43 #endif
44
45 using base::Time;
46 using base::TimeDelta;
47 using content::BrowserThread;
48 using net::HttpResponseHeaders;
49 using net::HttpUtil;
50 using net::IOBufferWithSize;
51 using net::IPAddress;
52 using net::NetworkInterface;
53 using net::NetworkInterfaceList;
54 using net::StringIOBuffer;
55 using net::UDPSocket;
56
57 namespace extensions {
58 namespace api {
59 namespace dial {
60
61 namespace {
62
63 // The total number of requests to make per discovery cycle.
64 const int kDialMaxRequests = 4;
65
66 // The interval to wait between successive requests.
67 const int kDialRequestIntervalMillis = 1000;
68
69 // The maximum delay a device may wait before responding (MX).
70 const int kDialMaxResponseDelaySecs = 1;
71
72 // The maximum time a response is expected after a M-SEARCH request.
73 const int kDialResponseTimeoutSecs = 2;
74
75 // The multicast IP address for discovery.
76 const char kDialRequestAddress[] = "239.255.255.250";
77
78 // The UDP port number for discovery.
79 const uint16_t kDialRequestPort = 1900;
80
81 // The DIAL service type as part of the search request.
82 const char kDialSearchType[] = "urn:dial-multiscreen-org:service:dial:1";
83
84 // SSDP headers parsed from the response.
85 const char kSsdpLocationHeader[] = "LOCATION";
86 const char kSsdpCacheControlHeader[] = "CACHE-CONTROL";
87 const char kSsdpConfigIdHeader[] = "CONFIGID.UPNP.ORG";
88 const char kSsdpUsnHeader[] = "USN";
89
90 // The receive buffer size, in bytes.
91 const int kDialRecvBufferSize = 1500;
92
93 // Gets a specific header from |headers| and puts it in |value|.
94 bool GetHeader(HttpResponseHeaders* headers, const char* name,
95 std::string* value) {
96 return headers->EnumerateHeader(nullptr, std::string(name), value);
97 }
98
99 // Returns the request string.
100 std::string BuildRequest() {
101 // Extra line at the end to make UPnP lib happy.
102 std::string request(base::StringPrintf(
103 "M-SEARCH * HTTP/1.1\r\n"
104 "HOST: %s:%u\r\n"
105 "MAN: \"ssdp:discover\"\r\n"
106 "MX: %d\r\n"
107 "ST: %s\r\n"
108 "USER-AGENT: %s/%s %s\r\n"
109 "\r\n",
110 kDialRequestAddress,
111 kDialRequestPort,
112 kDialMaxResponseDelaySecs,
113 kDialSearchType,
114 version_info::GetProductName().c_str(),
115 version_info::GetVersionNumber().c_str(),
116 version_info::GetOSType().c_str()));
117 // 1500 is a good MTU value for most Ethernet LANs.
118 DCHECK_LE(request.size(), 1500U);
119 return request;
120 }
121
122 #if defined(OS_CHROMEOS)
123 // Finds the IP address of the preferred interface of network type |type|
124 // to bind the socket and inserts the address into |bind_address_list|. This
125 // ChromeOS version can prioritize wifi and ethernet interfaces.
126 void InsertBestBindAddressChromeOS(const chromeos::NetworkTypePattern& type,
127 net::IPAddressList* bind_address_list) {
128 const chromeos::NetworkState* state = chromeos::NetworkHandler::Get()
129 ->network_state_handler()->ConnectedNetworkByType(type);
130 IPAddress bind_ip_address;
131 if (state && bind_ip_address.AssignFromIPLiteral(state->ip_address()) &&
132 bind_ip_address.IsIPv4()) {
133 VLOG(2) << "Found " << state->type() << ", " << state->name() << ": "
134 << state->ip_address();
135 bind_address_list->push_back(bind_ip_address);
136 }
137 }
138 #else
139 NetworkInterfaceList GetNetworkListOnFileThread() {
140 NetworkInterfaceList list;
141 bool success =
142 net::GetNetworkList(&list, net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES);
143 if (!success)
144 VLOG(1) << "Could not retrieve network list!";
145 return list;
146 }
147 #endif // defined(OS_CHROMEOS)
148
149 } // namespace
150
151 DialServiceImpl::DialSocket::DialSocket(
152 const base::Closure& discovery_request_cb,
153 const base::Callback<void(const DialDeviceData&)>& device_discovered_cb,
154 const base::Closure& on_error_cb)
155 : discovery_request_cb_(discovery_request_cb),
156 device_discovered_cb_(device_discovered_cb),
157 on_error_cb_(on_error_cb),
158 is_writing_(false),
159 is_reading_(false) {
160 DCHECK_CURRENTLY_ON(BrowserThread::IO);
161 }
162
163 DialServiceImpl::DialSocket::~DialSocket() {
164 DCHECK_CURRENTLY_ON(BrowserThread::IO);
165 }
166
167 bool DialServiceImpl::DialSocket::CreateAndBindSocket(
168 const IPAddress& bind_ip_address,
169 net::NetLog* net_log,
170 net::NetLogSource net_log_source) {
171 DCHECK_CURRENTLY_ON(BrowserThread::IO);
172 DCHECK(!socket_);
173 DCHECK(bind_ip_address.IsIPv4());
174
175 net::RandIntCallback rand_cb = base::Bind(&base::RandInt);
176 socket_ = base::MakeUnique<UDPSocket>(net::DatagramSocket::RANDOM_BIND,
177 rand_cb, net_log, net_log_source);
178
179 // 0 means bind a random port
180 net::IPEndPoint address(bind_ip_address, 0);
181
182 if (socket_->Open(address.GetFamily()) != net::OK ||
183 socket_->SetBroadcast(true) != net::OK ||
184 !CheckResult("Bind", socket_->Bind(address))) {
185 socket_.reset();
186 return false;
187 }
188
189 recv_buffer_ = new IOBufferWithSize(kDialRecvBufferSize);
190 return ReadSocket();
191 }
192
193 void DialServiceImpl::DialSocket::SendOneRequest(
194 const net::IPEndPoint& send_address,
195 const scoped_refptr<net::StringIOBuffer>& send_buffer) {
196 if (!socket_) {
197 VLOG(1) << "Socket not connected.";
198 return;
199 }
200
201 if (is_writing_) {
202 VLOG(1) << "Already writing.";
203 return;
204 }
205
206 is_writing_ = true;
207 int result = socket_->SendTo(
208 send_buffer.get(), send_buffer->size(), send_address,
209 base::Bind(&DialServiceImpl::DialSocket::OnSocketWrite,
210 base::Unretained(this),
211 send_buffer->size()));
212 bool result_ok = CheckResult("SendTo", result);
213 if (result_ok && result > 0) {
214 // Synchronous write.
215 OnSocketWrite(send_buffer->size(), result);
216 }
217 }
218
219 bool DialServiceImpl::DialSocket::IsClosed() {
220 DCHECK_CURRENTLY_ON(BrowserThread::IO);
221 return !socket_;
222 }
223
224 bool DialServiceImpl::DialSocket::CheckResult(const char* operation,
225 int result) {
226 DCHECK_CURRENTLY_ON(BrowserThread::IO);
227 VLOG(2) << "Operation " << operation << " result " << result;
228 if (result < net::OK && result != net::ERR_IO_PENDING) {
229 Close();
230 std::string error_str(net::ErrorToString(result));
231 VLOG(1) << "dial socket error: " << error_str;
232 on_error_cb_.Run();
233 return false;
234 }
235 return true;
236 }
237
238 void DialServiceImpl::DialSocket::Close() {
239 DCHECK_CURRENTLY_ON(BrowserThread::IO);
240 is_reading_ = false;
241 is_writing_ = false;
242 socket_.reset();
243 }
244
245 void DialServiceImpl::DialSocket::OnSocketWrite(int send_buffer_size,
246 int result) {
247 DCHECK_CURRENTLY_ON(BrowserThread::IO);
248 is_writing_ = false;
249 if (!CheckResult("OnSocketWrite", result))
250 return;
251 if (result != send_buffer_size) {
252 VLOG(1) << "Sent " << result << " chars, expected "
253 << send_buffer_size << " chars";
254 }
255 discovery_request_cb_.Run();
256 }
257
258 bool DialServiceImpl::DialSocket::ReadSocket() {
259 DCHECK_CURRENTLY_ON(BrowserThread::IO);
260 if (!socket_) {
261 VLOG(1) << "Socket not connected.";
262 return false;
263 }
264
265 if (is_reading_) {
266 VLOG(1) << "Already reading.";
267 return false;
268 }
269
270 int result = net::OK;
271 bool result_ok = true;
272 do {
273 is_reading_ = true;
274 result = socket_->RecvFrom(
275 recv_buffer_.get(),
276 kDialRecvBufferSize, &recv_address_,
277 base::Bind(&DialServiceImpl::DialSocket::OnSocketRead,
278 base::Unretained(this)));
279 result_ok = CheckResult("RecvFrom", result);
280 if (result != net::ERR_IO_PENDING)
281 is_reading_ = false;
282 if (result_ok && result > 0) {
283 // Synchronous read.
284 HandleResponse(result);
285 }
286 } while (result_ok && result != net::OK && result != net::ERR_IO_PENDING);
287 return result_ok;
288 }
289
290 void DialServiceImpl::DialSocket::OnSocketRead(int result) {
291 DCHECK_CURRENTLY_ON(BrowserThread::IO);
292 is_reading_ = false;
293 if (!CheckResult("OnSocketRead", result))
294 return;
295 if (result > 0)
296 HandleResponse(result);
297
298 // Await next response.
299 ReadSocket();
300 }
301
302 void DialServiceImpl::DialSocket::HandleResponse(int bytes_read) {
303 DCHECK_CURRENTLY_ON(BrowserThread::IO);
304 DCHECK_GT(bytes_read, 0);
305 if (bytes_read > kDialRecvBufferSize) {
306 VLOG(1) << bytes_read << " > " << kDialRecvBufferSize << "!?";
307 return;
308 }
309 VLOG(2) << "Read " << bytes_read << " bytes from "
310 << recv_address_.ToString();
311
312 std::string response(recv_buffer_->data(), bytes_read);
313 Time response_time = Time::Now();
314
315 // Attempt to parse response, notify observers if successful.
316 DialDeviceData parsed_device;
317 if (ParseResponse(response, response_time, &parsed_device))
318 device_discovered_cb_.Run(parsed_device);
319 }
320
321 // static
322 bool DialServiceImpl::DialSocket::ParseResponse(
323 const std::string& response,
324 const base::Time& response_time,
325 DialDeviceData* device) {
326 int headers_end = HttpUtil::LocateEndOfHeaders(response.c_str(),
327 response.size());
328 if (headers_end < 1) {
329 VLOG(1) << "Headers invalid or empty, ignoring: " << response;
330 return false;
331 }
332 std::string raw_headers =
333 HttpUtil::AssembleRawHeaders(response.c_str(), headers_end);
334 VLOG(3) << "raw_headers: " << raw_headers << "\n";
335 scoped_refptr<HttpResponseHeaders> headers =
336 new HttpResponseHeaders(raw_headers);
337
338 std::string device_url_str;
339 if (!GetHeader(headers.get(), kSsdpLocationHeader, &device_url_str) ||
340 device_url_str.empty()) {
341 VLOG(1) << "No LOCATION header found.";
342 return false;
343 }
344
345 GURL device_url(device_url_str);
346 if (!DialDeviceData::IsDeviceDescriptionUrl(device_url)) {
347 VLOG(1) << "URL " << device_url_str << " not valid.";
348 return false;
349 }
350
351 std::string device_id;
352 if (!GetHeader(headers.get(), kSsdpUsnHeader, &device_id) ||
353 device_id.empty()) {
354 VLOG(1) << "No USN header found.";
355 return false;
356 }
357
358 device->set_device_id(device_id);
359 device->set_device_description_url(device_url);
360 device->set_response_time(response_time);
361
362 // TODO(mfoltz): Parse the max-age value from the cache control header.
363 // http://crbug.com/165289
364 std::string cache_control;
365 GetHeader(headers.get(), kSsdpCacheControlHeader, &cache_control);
366
367 std::string config_id;
368 int config_id_int;
369 if (GetHeader(headers.get(), kSsdpConfigIdHeader, &config_id) &&
370 base::StringToInt(config_id, &config_id_int)) {
371 device->set_config_id(config_id_int);
372 } else {
373 VLOG(1) << "Malformed or missing " << kSsdpConfigIdHeader << ": "
374 << config_id;
375 }
376
377 return true;
378 }
379
380 DialServiceImpl::DialServiceImpl(net::NetLog* net_log)
381 : net_log_(net_log),
382 discovery_active_(false),
383 num_requests_sent_(0),
384 max_requests_(kDialMaxRequests),
385 finish_delay_(TimeDelta::FromMilliseconds((kDialMaxRequests - 1) *
386 kDialRequestIntervalMillis) +
387 TimeDelta::FromSeconds(kDialResponseTimeoutSecs)),
388 request_interval_(
389 TimeDelta::FromMilliseconds(kDialRequestIntervalMillis)),
390 weak_factory_(this) {
391 IPAddress address;
392 bool success = address.AssignFromIPLiteral(kDialRequestAddress);
393 DCHECK(success);
394 send_address_ = net::IPEndPoint(address, kDialRequestPort);
395 send_buffer_ = new StringIOBuffer(BuildRequest());
396 net_log_source_.type = net::NetLogSourceType::UDP_SOCKET;
397 net_log_source_.id = net_log_->NextID();
398 }
399
400 DialServiceImpl::~DialServiceImpl() {
401 DCHECK_CURRENTLY_ON(BrowserThread::IO);
402 }
403
404 void DialServiceImpl::AddObserver(Observer* observer) {
405 DCHECK_CURRENTLY_ON(BrowserThread::IO);
406 observer_list_.AddObserver(observer);
407 }
408
409 void DialServiceImpl::RemoveObserver(Observer* observer) {
410 DCHECK_CURRENTLY_ON(BrowserThread::IO);
411 observer_list_.RemoveObserver(observer);
412 }
413
414 bool DialServiceImpl::HasObserver(const Observer* observer) const {
415 DCHECK_CURRENTLY_ON(BrowserThread::IO);
416 return observer_list_.HasObserver(observer);
417 }
418
419 bool DialServiceImpl::Discover() {
420 DCHECK_CURRENTLY_ON(BrowserThread::IO);
421 if (discovery_active_) {
422 VLOG(2) << "Discovery is already active - returning.";
423 return false;
424 }
425 discovery_active_ = true;
426
427 VLOG(2) << "Discovery started.";
428
429 StartDiscovery();
430 return true;
431 }
432
433 void DialServiceImpl::StartDiscovery() {
434 DCHECK_CURRENTLY_ON(BrowserThread::IO);
435 DCHECK(discovery_active_);
436 if (HasOpenSockets()) {
437 VLOG(2) << "Calling StartDiscovery() with open sockets. Returning.";
438 return;
439 }
440
441 #if defined(OS_CHROMEOS)
442 // The ChromeOS specific version of getting network interfaces does not
443 // require trampolining to another thread, and contains additional interface
444 // information such as interface types (i.e. wifi vs cellular).
445 net::IPAddressList chrome_os_address_list;
446 InsertBestBindAddressChromeOS(chromeos::NetworkTypePattern::Ethernet(),
447 &chrome_os_address_list);
448 InsertBestBindAddressChromeOS(chromeos::NetworkTypePattern::WiFi(),
449 &chrome_os_address_list);
450 DiscoverOnAddresses(chrome_os_address_list);
451
452 #else
453 BrowserThread::PostTaskAndReplyWithResult(
454 BrowserThread::FILE, FROM_HERE, base::Bind(&GetNetworkListOnFileThread),
455 base::Bind(&DialServiceImpl::SendNetworkList,
456 weak_factory_.GetWeakPtr()));
457 #endif
458 }
459
460 void DialServiceImpl::SendNetworkList(const NetworkInterfaceList& networks) {
461 DCHECK_CURRENTLY_ON(BrowserThread::IO);
462 using InterfaceIndexAddressFamily = std::pair<uint32_t, net::AddressFamily>;
463 std::set<InterfaceIndexAddressFamily> interface_index_addr_family_seen;
464 net::IPAddressList ip_addresses;
465
466 // Binds a socket to each IPv4 network interface found. Note that
467 // there may be duplicates in |networks|, so address family + interface index
468 // is used to identify unique interfaces.
469 // TODO(mfoltz): Support IPV6 multicast. http://crbug.com/165286
470 for (NetworkInterfaceList::const_iterator iter = networks.begin();
471 iter != networks.end(); ++iter) {
472 net::AddressFamily addr_family = net::GetAddressFamily(iter->address);
473 VLOG(2) << "Found " << iter->name << ", " << iter->address.ToString()
474 << ", address family: " << addr_family;
475 if (addr_family == net::ADDRESS_FAMILY_IPV4) {
476 InterfaceIndexAddressFamily interface_index_addr_family =
477 std::make_pair(iter->interface_index, addr_family);
478 bool inserted = interface_index_addr_family_seen
479 .insert(interface_index_addr_family)
480 .second;
481 // We have not seen this interface before, so add its IP address to the
482 // discovery list.
483 if (inserted) {
484 VLOG(2) << "Encountered "
485 << "interface index: " << iter->interface_index << ", "
486 << "address family: " << addr_family << " for the first time, "
487 << "adding IP address " << iter->address.ToString()
488 << " to list.";
489 ip_addresses.push_back(iter->address);
490 } else {
491 VLOG(2) << "Already encountered "
492 << "interface index: " << iter->interface_index << ", "
493 << "address family: " << addr_family << " before, not adding.";
494 }
495 }
496 }
497
498 DiscoverOnAddresses(ip_addresses);
499 }
500
501 void DialServiceImpl::DiscoverOnAddresses(
502 const net::IPAddressList& ip_addresses) {
503 if (ip_addresses.empty()) {
504 VLOG(1) << "Could not find a valid interface to bind. Finishing discovery";
505 FinishDiscovery();
506 return;
507 }
508
509 // Schedule a timer to finish the discovery process (and close the sockets).
510 if (finish_delay_ > TimeDelta::FromSeconds(0)) {
511 VLOG(2) << "Starting timer to finish discovery.";
512 finish_timer_.Start(FROM_HERE,
513 finish_delay_,
514 this,
515 &DialServiceImpl::FinishDiscovery);
516 }
517
518 for (const auto& address : ip_addresses) {
519 BindAndAddSocket(address);
520 }
521
522 SendOneRequest();
523 }
524
525 void DialServiceImpl::BindAndAddSocket(const IPAddress& bind_ip_address) {
526 std::unique_ptr<DialServiceImpl::DialSocket> dial_socket(CreateDialSocket());
527 if (dial_socket->CreateAndBindSocket(bind_ip_address, net_log_,
528 net_log_source_))
529 dial_sockets_.push_back(std::move(dial_socket));
530 }
531
532 std::unique_ptr<DialServiceImpl::DialSocket>
533 DialServiceImpl::CreateDialSocket() {
534 return base::MakeUnique<DialServiceImpl::DialSocket>(
535 base::Bind(&DialServiceImpl::NotifyOnDiscoveryRequest,
536 weak_factory_.GetWeakPtr()),
537 base::Bind(&DialServiceImpl::NotifyOnDeviceDiscovered,
538 weak_factory_.GetWeakPtr()),
539 base::Bind(&DialServiceImpl::NotifyOnError, weak_factory_.GetWeakPtr()));
540 }
541
542 void DialServiceImpl::SendOneRequest() {
543 DCHECK_CURRENTLY_ON(BrowserThread::IO);
544 if (num_requests_sent_ == max_requests_) {
545 VLOG(2) << "Reached max requests; stopping request timer.";
546 request_timer_.Stop();
547 return;
548 }
549 num_requests_sent_++;
550 VLOG(2) << "Sending request " << num_requests_sent_ << "/"
551 << max_requests_;
552 for (const auto& socket : dial_sockets_) {
553 if (!socket->IsClosed())
554 socket->SendOneRequest(send_address_, send_buffer_);
555 }
556 }
557
558 void DialServiceImpl::NotifyOnDiscoveryRequest() {
559 DCHECK_CURRENTLY_ON(BrowserThread::IO);
560 // If discovery is inactive, no reason to notify observers.
561 if (!discovery_active_) {
562 VLOG(2) << "Request sent after discovery finished. Ignoring.";
563 return;
564 }
565
566 VLOG(2) << "Notifying observers of discovery request";
567 for (auto& observer : observer_list_)
568 observer.OnDiscoveryRequest(this);
569 // If we need to send additional requests, schedule a timer to do so.
570 if (num_requests_sent_ < max_requests_ && num_requests_sent_ == 1) {
571 VLOG(2) << "Scheduling timer to send additional requests";
572 // TODO(imcheng): Move this to SendOneRequest() once the implications are
573 // understood.
574 request_timer_.Start(FROM_HERE,
575 request_interval_,
576 this,
577 &DialServiceImpl::SendOneRequest);
578 }
579 }
580
581 void DialServiceImpl::NotifyOnDeviceDiscovered(
582 const DialDeviceData& device_data) {
583 DCHECK_CURRENTLY_ON(BrowserThread::IO);
584 if (!discovery_active_) {
585 VLOG(2) << "Got response after discovery finished. Ignoring.";
586 return;
587 }
588 for (auto& observer : observer_list_)
589 observer.OnDeviceDiscovered(this, device_data);
590 }
591
592 void DialServiceImpl::NotifyOnError() {
593 DCHECK_CURRENTLY_ON(BrowserThread::IO);
594 // TODO(imcheng): Modify upstream so that the device list is not cleared
595 // when it could still potentially discover devices on other sockets.
596 for (auto& observer : observer_list_) {
597 observer.OnError(this, HasOpenSockets() ? DIAL_SERVICE_SOCKET_ERROR
598 : DIAL_SERVICE_NO_INTERFACES);
599 }
600 }
601
602 void DialServiceImpl::FinishDiscovery() {
603 DCHECK_CURRENTLY_ON(BrowserThread::IO);
604 DCHECK(discovery_active_);
605 VLOG(2) << "Discovery finished.";
606 // Close all open sockets.
607 dial_sockets_.clear();
608 finish_timer_.Stop();
609 request_timer_.Stop();
610 discovery_active_ = false;
611 num_requests_sent_ = 0;
612 for (auto& observer : observer_list_)
613 observer.OnDiscoveryFinished(this);
614 }
615
616 bool DialServiceImpl::HasOpenSockets() {
617 for (const auto& socket : dial_sockets_) {
618 if (!socket->IsClosed())
619 return true;
620 }
621 return false;
622 }
623
624 } // namespace dial
625 } // namespace api
626 } // namespace extensions
OLDNEW
« no previous file with comments | « chrome/browser/extensions/api/dial/dial_service.h ('k') | chrome/browser/extensions/api/dial/dial_service_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698