root/chrome/browser/extensions/api/dial/dial_service.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetHeader
  2. BuildRequest
  3. GetNetworkListOnFileThread
  4. InsertBestBindAddressChromeOS
  5. is_reading_
  6. CreateAndBindSocket
  7. SendOneRequest
  8. IsClosed
  9. CheckResult
  10. Close
  11. OnSocketWrite
  12. ReadSocket
  13. OnSocketRead
  14. HandleResponse
  15. ParseResponse
  16. request_interval_
  17. AddObserver
  18. RemoveObserver
  19. HasObserver
  20. Discover
  21. StartDiscovery
  22. SendNetworkList
  23. DiscoverOnAddresses
  24. BindAndAddSocket
  25. CreateDialSocket
  26. SendOneRequest
  27. NotifyOnDiscoveryRequest
  28. NotifyOnDeviceDiscovered
  29. NotifyOnError
  30. FinishDiscovery
  31. HasOpenSockets

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/dial/dial_service.h"

#include <algorithm>
#include <set>
#include <utility>

#include "base/basictypes.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/api/dial/dial_device_data.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "url/gurl.h"
#if defined(OS_CHROMEOS)
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/shill_property_util.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#endif

using base::Time;
using base::TimeDelta;
using content::BrowserThread;
using net::HttpResponseHeaders;
using net::HttpUtil;
using net::IOBufferWithSize;
using net::IPAddressNumber;
using net::IPEndPoint;
using net::NetworkInterface;
using net::NetworkInterfaceList;
using net::StringIOBuffer;
using net::UDPSocket;

namespace extensions {

namespace {

// The total number of requests to make per discovery cycle.
const int kDialMaxRequests = 4;

// The interval to wait between successive requests.
const int kDialRequestIntervalMillis = 1000;

// The maximum delay a device may wait before responding (MX).
const int kDialMaxResponseDelaySecs = 1;

// The maximum time a response is expected after a M-SEARCH request.
const int kDialResponseTimeoutSecs = 2;

// The multicast IP address for discovery.
const char kDialRequestAddress[] = "239.255.255.250";

// The UDP port number for discovery.
const int kDialRequestPort = 1900;

// The DIAL service type as part of the search request.
const char kDialSearchType[] = "urn:dial-multiscreen-org:service:dial:1";

// SSDP headers parsed from the response.
const char kSsdpLocationHeader[] = "LOCATION";
const char kSsdpCacheControlHeader[] = "CACHE-CONTROL";
const char kSsdpConfigIdHeader[] = "CONFIGID.UPNP.ORG";
const char kSsdpUsnHeader[] = "USN";

// The receive buffer size, in bytes.
const int kDialRecvBufferSize = 1500;

// Gets a specific header from |headers| and puts it in |value|.
bool GetHeader(HttpResponseHeaders* headers, const char* name,
               std::string* value) {
  return headers->EnumerateHeader(NULL, std::string(name), value);
}

// Returns the request string.
std::string BuildRequest() {
  // Extra line at the end to make UPnP lib happy.
  chrome::VersionInfo version;
  std::string request(base::StringPrintf(
      "M-SEARCH * HTTP/1.1\r\n"
      "HOST: %s:%i\r\n"
      "MAN: \"ssdp:discover\"\r\n"
      "MX: %d\r\n"
      "ST: %s\r\n"
      "USER-AGENT: %s/%s %s\r\n"
      "\r\n",
      kDialRequestAddress,
      kDialRequestPort,
      kDialMaxResponseDelaySecs,
      kDialSearchType,
      version.Name().c_str(),
      version.Version().c_str(),
      version.OSType().c_str()));
  // 1500 is a good MTU value for most Ethernet LANs.
  DCHECK(request.size() <= 1500);
  return request;
}

#if !defined(OS_CHROMEOS)
void GetNetworkListOnFileThread(
    const scoped_refptr<base::MessageLoopProxy>& loop,
    const base::Callback<void(const NetworkInterfaceList& networks)>& cb) {
  NetworkInterfaceList list;
  bool success = net::GetNetworkList(
      &list, net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES);
  if (!success)
    DVLOG(1) << "Could not retrieve network list!";

  loop->PostTask(FROM_HERE, base::Bind(cb, list));
}

#else

// Finds the IP address of the preferred interface of network type |type|
// to bind the socket and inserts the address into |bind_address_list|. This
// ChromeOS version can prioritize wifi and ethernet interfaces.
void InsertBestBindAddressChromeOS(
    const chromeos::NetworkTypePattern& type,
    std::vector<IPAddressNumber>* bind_address_list) {
  const chromeos::NetworkState* state = chromeos::NetworkHandler::Get()
      ->network_state_handler()->ConnectedNetworkByType(type);
  IPAddressNumber bind_ip_address;
  if (state
      && net::ParseIPLiteralToNumber(state->ip_address(), &bind_ip_address)
      && bind_ip_address.size() == net::kIPv4AddressSize) {
    DVLOG(1) << "Found " << state->type() << ", " << state->name() << ": "
             << state->ip_address();
    bind_address_list->push_back(bind_ip_address);
  }
}
#endif  // !defined(OS_CHROMEOS)

}  // namespace

DialServiceImpl::DialSocket::DialSocket(
    const base::Closure& discovery_request_cb,
    const base::Callback<void(const DialDeviceData&)>& device_discovered_cb,
    const base::Closure& on_error_cb)
    : discovery_request_cb_(discovery_request_cb),
      device_discovered_cb_(device_discovered_cb),
      on_error_cb_(on_error_cb),
      is_writing_(false),
      is_reading_(false) {
}

DialServiceImpl::DialSocket::~DialSocket() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

bool DialServiceImpl::DialSocket::CreateAndBindSocket(
    const IPAddressNumber& bind_ip_address,
    net::NetLog* net_log,
    net::NetLog::Source net_log_source) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!socket_.get());
  DCHECK(bind_ip_address.size() == net::kIPv4AddressSize);

  net::RandIntCallback rand_cb = base::Bind(&base::RandInt);
  socket_.reset(new UDPSocket(net::DatagramSocket::RANDOM_BIND,
                              rand_cb,
                              net_log,
                              net_log_source));
  socket_->AllowBroadcast();

  // 0 means bind a random port
  IPEndPoint address(bind_ip_address, 0);

  if (!CheckResult("Bind", socket_->Bind(address)))
    return false;

  DCHECK(socket_.get());

  recv_buffer_ = new IOBufferWithSize(kDialRecvBufferSize);
  return ReadSocket();
}

void DialServiceImpl::DialSocket::SendOneRequest(
    const net::IPEndPoint& send_address,
    const scoped_refptr<net::StringIOBuffer>& send_buffer) {
  if (!socket_.get()) {
    DLOG(WARNING) << "Socket not connected.";
    return;
  }

  if (is_writing_) {
    VLOG(2) << "Already writing.";
    return;
  }

  is_writing_ = true;
  int result = socket_->SendTo(
      send_buffer.get(), send_buffer->size(), send_address,
      base::Bind(&DialServiceImpl::DialSocket::OnSocketWrite,
                 base::Unretained(this),
                 send_buffer->size()));
  bool result_ok = CheckResult("SendTo", result);
  if (result_ok && result > 0) {
    // Synchronous write.
    OnSocketWrite(send_buffer->size(), result);
  }
}

bool DialServiceImpl::DialSocket::IsClosed() {
  DCHECK(thread_checker_.CalledOnValidThread());
  return !socket_.get();
}

bool DialServiceImpl::DialSocket::CheckResult(const char* operation,
                                              int result) {
  DCHECK(thread_checker_.CalledOnValidThread());
  VLOG(2) << "Operation " << operation << " result " << result;
  if (result < net::OK && result != net::ERR_IO_PENDING) {
    Close();
    std::string error_str(net::ErrorToString(result));
    DVLOG(0) << "dial socket error: " << error_str;
    on_error_cb_.Run();
    return false;
  }
  return true;
}

void DialServiceImpl::DialSocket::Close() {
  DCHECK(thread_checker_.CalledOnValidThread());
  is_reading_ = false;
  is_writing_ = false;
  socket_.reset();
}

void DialServiceImpl::DialSocket::OnSocketWrite(int send_buffer_size,
                                                int result) {
  DCHECK(thread_checker_.CalledOnValidThread());
  is_writing_ = false;
  if (!CheckResult("OnSocketWrite", result))
    return;
  if (result != send_buffer_size) {
    DLOG(ERROR) << "Sent " << result << " chars, expected "
                << send_buffer_size << " chars";
  }
  discovery_request_cb_.Run();
}

bool DialServiceImpl::DialSocket::ReadSocket() {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!socket_.get()) {
    DLOG(WARNING) << "Socket not connected.";
    return false;
  }

  if (is_reading_) {
    VLOG(2) << "Already reading.";
    return false;
  }

  int result = net::OK;
  bool result_ok = true;
  do {
    is_reading_ = true;
    result = socket_->RecvFrom(
        recv_buffer_.get(),
        kDialRecvBufferSize, &recv_address_,
        base::Bind(&DialServiceImpl::DialSocket::OnSocketRead,
                   base::Unretained(this)));
    result_ok = CheckResult("RecvFrom", result);
    if (result != net::ERR_IO_PENDING)
      is_reading_ = false;
    if (result_ok && result > 0) {
      // Synchronous read.
      HandleResponse(result);
    }
  } while (result_ok && result != net::OK && result != net::ERR_IO_PENDING);
  return result_ok;
}

void DialServiceImpl::DialSocket::OnSocketRead(int result) {
  DCHECK(thread_checker_.CalledOnValidThread());
  is_reading_ = false;
  if (!CheckResult("OnSocketRead", result))
    return;
  if (result > 0)
    HandleResponse(result);

  // Await next response.
  ReadSocket();
}

void DialServiceImpl::DialSocket::HandleResponse(int bytes_read) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_GT(bytes_read, 0);
  if (bytes_read > kDialRecvBufferSize) {
    DLOG(ERROR) << bytes_read << " > " << kDialRecvBufferSize << "!?";
    return;
  }
  VLOG(2) << "Read " << bytes_read << " bytes from "
          << recv_address_.ToString();

  std::string response(recv_buffer_->data(), bytes_read);
  Time response_time = Time::Now();

  // Attempt to parse response, notify observers if successful.
  DialDeviceData parsed_device;
  if (ParseResponse(response, response_time, &parsed_device))
    device_discovered_cb_.Run(parsed_device);
}

// static
bool DialServiceImpl::DialSocket::ParseResponse(
    const std::string& response,
    const base::Time& response_time,
    DialDeviceData* device) {
  int headers_end = HttpUtil::LocateEndOfHeaders(response.c_str(),
                                                 response.size());
  if (headers_end < 1) {
    VLOG(2) << "Headers invalid or empty, ignoring: " << response;
    return false;
  }
  std::string raw_headers =
      HttpUtil::AssembleRawHeaders(response.c_str(), headers_end);
  VLOG(2) << "raw_headers: " << raw_headers << "\n";
  scoped_refptr<HttpResponseHeaders> headers =
      new HttpResponseHeaders(raw_headers);

  std::string device_url_str;
  if (!GetHeader(headers.get(), kSsdpLocationHeader, &device_url_str) ||
      device_url_str.empty()) {
    VLOG(2) << "No LOCATION header found.";
    return false;
  }

  GURL device_url(device_url_str);
  if (!DialDeviceData::IsDeviceDescriptionUrl(device_url)) {
    VLOG(2) << "URL " << device_url_str << " not valid.";
    return false;
  }

  std::string device_id;
  if (!GetHeader(headers.get(), kSsdpUsnHeader, &device_id) ||
      device_id.empty()) {
    VLOG(2) << "No USN header found.";
    return false;
  }

  device->set_device_id(device_id);
  device->set_device_description_url(device_url);
  device->set_response_time(response_time);

  // TODO(mfoltz): Parse the max-age value from the cache control header.
  // http://crbug.com/165289
  std::string cache_control;
  GetHeader(headers.get(), kSsdpCacheControlHeader, &cache_control);

  std::string config_id;
  int config_id_int;
  if (GetHeader(headers.get(), kSsdpConfigIdHeader, &config_id) &&
      base::StringToInt(config_id, &config_id_int)) {
    device->set_config_id(config_id_int);
  } else {
    VLOG(2) << "Malformed or missing " << kSsdpConfigIdHeader << ": "
            << config_id;
  }

  return true;
}

DialServiceImpl::DialServiceImpl(net::NetLog* net_log)
    : discovery_active_(false),
      num_requests_sent_(0),
      max_requests_(kDialMaxRequests),
      finish_delay_(TimeDelta::FromMilliseconds((kDialMaxRequests - 1) *
                                                kDialRequestIntervalMillis) +
                    TimeDelta::FromSeconds(kDialResponseTimeoutSecs)),
      request_interval_(
          TimeDelta::FromMilliseconds(kDialRequestIntervalMillis)) {
  IPAddressNumber address;
  bool success = net::ParseIPLiteralToNumber(kDialRequestAddress, &address);
  DCHECK(success);
  send_address_ = IPEndPoint(address, kDialRequestPort);
  send_buffer_ = new StringIOBuffer(BuildRequest());
  net_log_ = net_log;
  net_log_source_.type = net::NetLog::SOURCE_UDP_SOCKET;
  net_log_source_.id = net_log_->NextID();
}

DialServiceImpl::~DialServiceImpl() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void DialServiceImpl::AddObserver(Observer* observer) {
  DCHECK(thread_checker_.CalledOnValidThread());
  observer_list_.AddObserver(observer);
}

void DialServiceImpl::RemoveObserver(Observer* observer) {
  DCHECK(thread_checker_.CalledOnValidThread());
  observer_list_.RemoveObserver(observer);
}

bool DialServiceImpl::HasObserver(Observer* observer) {
  DCHECK(thread_checker_.CalledOnValidThread());
  return observer_list_.HasObserver(observer);
}

bool DialServiceImpl::Discover() {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (discovery_active_) {
    VLOG(2) << "Discovery is already active - returning.";
    return false;
  }
  discovery_active_ = true;

  VLOG(2) << "Discovery started.";

  StartDiscovery();
  return true;
}

void DialServiceImpl::StartDiscovery() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(discovery_active_);
  if (HasOpenSockets()) {
    VLOG(2) << "Calling StartDiscovery() with open sockets. Returning.";
    return;
  }

#if defined(OS_CHROMEOS)
  // The ChromeOS specific version of getting network interfaces does not
  // require trampolining to another thread, and contains additional interface
  // information such as interface types (i.e. wifi vs cellular).
  std::vector<IPAddressNumber> chrome_os_address_list;
  InsertBestBindAddressChromeOS(chromeos::NetworkTypePattern::Ethernet(),
                                &chrome_os_address_list);
  InsertBestBindAddressChromeOS(chromeos::NetworkTypePattern::WiFi(),
                                &chrome_os_address_list);
  DiscoverOnAddresses(chrome_os_address_list);

#else
  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
      &GetNetworkListOnFileThread,
      base::MessageLoopProxy::current(), base::Bind(
          &DialServiceImpl::SendNetworkList, AsWeakPtr())));
#endif
}

void DialServiceImpl::SendNetworkList(const NetworkInterfaceList& networks) {
  DCHECK(thread_checker_.CalledOnValidThread());
  typedef std::pair<uint32, net::AddressFamily> InterfaceIndexAddressFamily;
  std::set<InterfaceIndexAddressFamily> interface_index_addr_family_seen;
  std::vector<IPAddressNumber> ip_addresses;

  // Binds a socket to each IPv4 network interface found. Note that
  // there may be duplicates in |networks|, so address family + interface index
  // is used to identify unique interfaces.
  // TODO(mfoltz): Support IPV6 multicast.  http://crbug.com/165286
  for (NetworkInterfaceList::const_iterator iter = networks.begin();
       iter != networks.end(); ++iter) {
    net::AddressFamily addr_family = net::GetAddressFamily(iter->address);
    DVLOG(1) << "Found " << iter->name << ", "
             << net::IPAddressToString(iter->address)
             << ", address family: " << addr_family;
    if (addr_family == net::ADDRESS_FAMILY_IPV4) {
      InterfaceIndexAddressFamily interface_index_addr_family =
          std::make_pair(iter->interface_index, addr_family);
      bool inserted = interface_index_addr_family_seen
          .insert(interface_index_addr_family)
          .second;
      // We have not seen this interface before, so add its IP address to the
      // discovery list.
      if (inserted) {
        VLOG(2) << "Encountered "
                << "interface index: " << iter->interface_index << ", "
                << "address family: " << addr_family << " for the first time, "
                << "adding IP address " << net::IPAddressToString(iter->address)
                << " to list.";
        ip_addresses.push_back(iter->address);
      } else {
        VLOG(2) << "Already encountered "
                << "interface index: " << iter->interface_index << ", "
                << "address family: " << addr_family << " before, not adding.";
      }
    }
  }

  DiscoverOnAddresses(ip_addresses);
}

void DialServiceImpl::DiscoverOnAddresses(
    const std::vector<IPAddressNumber>& ip_addresses) {
  if (ip_addresses.empty()) {
    DVLOG(1) << "Could not find a valid interface to bind. Finishing discovery";
    FinishDiscovery();
    return;
  }

  // Schedule a timer to finish the discovery process (and close the sockets).
  if (finish_delay_ > TimeDelta::FromSeconds(0)) {
    VLOG(2) << "Starting timer to finish discovery.";
    finish_timer_.Start(FROM_HERE,
                        finish_delay_,
                        this,
                        &DialServiceImpl::FinishDiscovery);
  }

  for (std::vector<IPAddressNumber>::const_iterator iter = ip_addresses.begin();
       iter != ip_addresses.end();
       ++iter)
    BindAndAddSocket(*iter);

  SendOneRequest();
}

void DialServiceImpl::BindAndAddSocket(const IPAddressNumber& bind_ip_address) {
  scoped_ptr<DialServiceImpl::DialSocket> dial_socket(CreateDialSocket());
  if (dial_socket->CreateAndBindSocket(bind_ip_address, net_log_,
                                       net_log_source_))
    dial_sockets_.push_back(dial_socket.release());
}

scoped_ptr<DialServiceImpl::DialSocket> DialServiceImpl::CreateDialSocket() {
  scoped_ptr<DialServiceImpl::DialSocket> dial_socket(
      new DialServiceImpl::DialSocket(
          base::Bind(&DialServiceImpl::NotifyOnDiscoveryRequest, AsWeakPtr()),
          base::Bind(&DialServiceImpl::NotifyOnDeviceDiscovered, AsWeakPtr()),
          base::Bind(&DialServiceImpl::NotifyOnError, AsWeakPtr())));
  return dial_socket.Pass();
}

void DialServiceImpl::SendOneRequest() {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (num_requests_sent_ == max_requests_) {
    VLOG(2) << "Reached max requests; stopping request timer.";
    request_timer_.Stop();
    return;
  }
  num_requests_sent_++;
  VLOG(2) << "Sending request " << num_requests_sent_ << "/"
          << max_requests_;
  for (ScopedVector<DialServiceImpl::DialSocket>::iterator iter =
           dial_sockets_.begin();
       iter != dial_sockets_.end();
       ++iter) {
    if (!((*iter)->IsClosed()))
      (*iter)->SendOneRequest(send_address_, send_buffer_);
  }
}

void DialServiceImpl::NotifyOnDiscoveryRequest() {
  DCHECK(thread_checker_.CalledOnValidThread());
  // If discovery is inactive, no reason to notify observers.
  if (!discovery_active_) {
    VLOG(2) << "Request sent after discovery finished.  Ignoring.";
    return;
  }

  VLOG(2) << "Notifying observers of discovery request";
  FOR_EACH_OBSERVER(Observer, observer_list_, OnDiscoveryRequest(this));
  // If we need to send additional requests, schedule a timer to do so.
  if (num_requests_sent_ < max_requests_ && num_requests_sent_ == 1) {
    VLOG(2) << "Scheduling timer to send additional requests";
    // TODO(imcheng): Move this to SendOneRequest() once the implications are
    // understood.
    request_timer_.Start(FROM_HERE,
                         request_interval_,
                         this,
                         &DialServiceImpl::SendOneRequest);
  }
}

void DialServiceImpl::NotifyOnDeviceDiscovered(
    const DialDeviceData& device_data) {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!discovery_active_) {
    VLOG(2) << "Got response after discovery finished.  Ignoring.";
    return;
  }
  FOR_EACH_OBSERVER(Observer, observer_list_,
                    OnDeviceDiscovered(this, device_data));
}

void DialServiceImpl::NotifyOnError() {
  DCHECK(thread_checker_.CalledOnValidThread());
  // TODO(imcheng): Modify upstream so that the device list is not cleared
  // when it could still potentially discover devices on other sockets.
  FOR_EACH_OBSERVER(Observer, observer_list_,
                    OnError(this,
                            HasOpenSockets() ? DIAL_SERVICE_SOCKET_ERROR
                                             : DIAL_SERVICE_NO_INTERFACES));
}

void DialServiceImpl::FinishDiscovery() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(discovery_active_);
  VLOG(2) << "Discovery finished.";
  // Close all open sockets.
  dial_sockets_.clear();
  finish_timer_.Stop();
  request_timer_.Stop();
  discovery_active_ = false;
  num_requests_sent_ = 0;
  FOR_EACH_OBSERVER(Observer, observer_list_, OnDiscoveryFinished(this));
}

bool DialServiceImpl::HasOpenSockets() {
  for (ScopedVector<DialSocket>::const_iterator iter = dial_sockets_.begin();
       iter != dial_sockets_.end();
       ++iter) {
    if (!((*iter)->IsClosed()))
      return true;
  }
  return false;
}

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */