blob: 82efb3e50f4f1bc43a4b3d2c0c09a0fad8c315c7 [file] [log] [blame]
// 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.
#ifndef CHROME_BROWSER_EXTENSIONS_API_DIAL_DIAL_SERVICE_H_
#define CHROME_BROWSER_EXTENSIONS_API_DIAL_DIAL_SERVICE_H_
#include <string>
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "net/base/net_log.h"
#include "net/udp/udp_socket.h"
namespace net {
class IPEndPoint;
class IPAddress;
class IOBuffer;
class StringIOBuffer;
struct NetworkInterface;
}
namespace extensions {
class DialDeviceData;
// DialService accepts requests to discover devices, sends multiple M-SEARCH
// requests via UDP multicast, and notifies observers when a DIAL-compliant
// device responds.
//
// Each time Discover() is called, kDialNumRequests M-SEARCH requests are sent
// (with a delay of kDialRequestIntervalMillis in between):
//
// Time Action
// ---- ------
// T1 Request 1 sent, OnDiscoveryReqest() called
// ...
// Tk Request kDialNumRequests sent, OnDiscoveryReqest() called
// Tf OnDiscoveryFinished() called
//
// Any time a valid response is received between T1 and Tf, it is parsed and
// OnDeviceDiscovered() is called with the result. Tf is set to Tk +
// kDialResponseTimeoutSecs (the response timeout passed in each request).
//
// Calling Discover() again between T1 and Tf has no effect.
//
// All relevant constants are defined in dial_service.cc.
//
// TODO(mfoltz): Port this into net/.
// See https://code.google.com/p/chromium/issues/detail?id=164473
class DialService {
public:
enum DialServiceErrorCode {
DIAL_SERVICE_NO_INTERFACES = 0,
DIAL_SERVICE_SOCKET_ERROR
};
class Observer {
public:
// Called when a single discovery request was sent.
virtual void OnDiscoveryRequest(DialService* service) = 0;
// Called when a device responds to a request.
virtual void OnDeviceDiscovered(DialService* service,
const DialDeviceData& device) = 0;
// Called when we have all responses from the last discovery request.
virtual void OnDiscoveryFinished(DialService* service) = 0;
// Called when an error occurs.
virtual void OnError(DialService* service,
const DialServiceErrorCode& code) = 0;
protected:
virtual ~Observer() {}
};
virtual ~DialService() {}
// Starts a new round of discovery. Returns |true| if discovery was started
// successfully or there is already one active. Returns |false| on error.
virtual bool Discover() = 0;
// Called by listeners to this service to add/remove themselves as observers.
virtual void AddObserver(Observer* observer) = 0;
virtual void RemoveObserver(Observer* observer) = 0;
virtual bool HasObserver(Observer* observer) = 0;
};
// Implements DialService.
//
// NOTE(mfoltz): It would make this class cleaner to refactor most of the state
// associated with a single discovery cycle into its own |DiscoveryOperation|
// object. This would also simplify lifetime of the object w.r.t. DialRegistry;
// the Registry would not need to create/destroy the Service on demand.
class DialServiceImpl : public DialService,
public base::SupportsWeakPtr<DialServiceImpl> {
public:
explicit DialServiceImpl(net::NetLog* net_log);
virtual ~DialServiceImpl();
// DialService implementation
virtual bool Discover() OVERRIDE;
virtual void AddObserver(Observer* observer) OVERRIDE;
virtual void RemoveObserver(Observer* observer) OVERRIDE;
virtual bool HasObserver(Observer* observer) OVERRIDE;
private:
// Represents a socket binding to a single network interface.
class DialSocket {
public:
// TODO(imcheng): Consider writing a DialSocket::Delegate interface that
// declares methods for these callbacks, and taking a ptr to the delegate
// here.
DialSocket(
const base::Closure& discovery_request_cb,
const base::Callback<void(const DialDeviceData&)>& device_discovered_cb,
const base::Closure& on_error_cb);
~DialSocket();
// Creates a socket using |net_log| and |net_log_source| and binds it to
// |bind_ip_address|.
bool CreateAndBindSocket(const net::IPAddressNumber& bind_ip_address,
net::NetLog* net_log,
net::NetLog::Source net_log_source);
// Sends a single discovery request |send_buffer| to |send_address|
// over the socket.
void SendOneRequest(const net::IPEndPoint& send_address,
const scoped_refptr<net::StringIOBuffer>& send_buffer);
// Returns true if the socket is closed.
bool IsClosed();
private:
// Checks the result of a socket operation. The name of the socket
// operation is given by |operation| and the result of the operation is
// given by |result|. If the result is an error, closes the socket,
// calls |on_error_cb_|, and returns |false|. Returns
// |true| otherwise. |operation| and |result| are logged.
bool CheckResult(const char* operation, int result);
// Closes the socket.
void Close();
// Callback invoked for socket writes.
void OnSocketWrite(int buffer_size, int result);
// Establishes the callback to read from the socket. Returns true if
// successful.
bool ReadSocket();
// Callback invoked for socket reads.
void OnSocketRead(int result);
// Callback invoked for socket reads.
void HandleResponse(int bytes_read);
// Parses a response into a DialDeviceData object. If the DIAL response is
// invalid or does not contain enough information, then the return
// value will be false and |device| is not changed.
static bool ParseResponse(const std::string& response,
const base::Time& response_time,
DialDeviceData* device);
// The UDP socket.
scoped_ptr<net::UDPSocket> socket_;
// Buffer for socket reads.
scoped_refptr<net::IOBufferWithSize> recv_buffer_;
// The source of of the last socket read.
net::IPEndPoint recv_address_;
// Thread checker.
base::ThreadChecker thread_checker_;
// The callback to be invoked when a discovery request was made.
base::Closure discovery_request_cb_;
// The callback to be invoked when a device has been discovered.
base::Callback<void(const DialDeviceData&)> device_discovered_cb_;
// The callback to be invoked when there is an error with socket operations.
base::Closure on_error_cb_;
// Marks whether there is an active write callback.
bool is_writing_;
// Marks whether there is an active read callback.
bool is_reading_;
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestNotifyOnError);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestOnDeviceDiscovered);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestOnDiscoveryRequest);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestResponseParsing);
DISALLOW_COPY_AND_ASSIGN(DialSocket);
};
// Starts the control flow for one discovery cycle.
void StartDiscovery();
// For each network interface in |list|, finds all unqiue IPv4 network
// interfaces and call |DiscoverOnAddresses()| with their IP addresses.
void SendNetworkList(const net::NetworkInterfaceList& list);
// Calls |BindAndAddSocket()| for each address in |ip_addresses|, calls
// |SendOneRequest()|, and start the timer to finish discovery if needed.
// The (Address family, interface index) of each address in |ip_addresses|
// must be unique. If |ip_address| is empty, calls |FinishDiscovery()|.
void DiscoverOnAddresses(
const std::vector<net::IPAddressNumber>& ip_addresses);
// Creates a DialSocket, binds it to |bind_ip_address| and if
// successful, add the DialSocket to |dial_sockets_|.
void BindAndAddSocket(const net::IPAddressNumber& bind_ip_address);
// Creates a DialSocket with callbacks to this object.
scoped_ptr<DialSocket> CreateDialSocket();
// Sends a single discovery request to every socket that are currently open.
void SendOneRequest();
// Notify observers that a discovery request was made.
void NotifyOnDiscoveryRequest();
// Notify observers a device has been discovered.
void NotifyOnDeviceDiscovered(const DialDeviceData& device_data);
// Notify observers that there has been an error with one of the DialSockets.
void NotifyOnError();
// Called from finish_timer_ when we are done with the current round of
// discovery.
void FinishDiscovery();
// Returns |true| if there are open sockets.
bool HasOpenSockets();
// DialSockets for each network interface whose ip address was
// successfully bound.
ScopedVector<DialSocket> dial_sockets_;
// The NetLog for this service.
net::NetLog* net_log_;
// The NetLog source for this service.
net::NetLog::Source net_log_source_;
// The multicast address:port for search requests.
net::IPEndPoint send_address_;
// Buffer for socket writes.
scoped_refptr<net::StringIOBuffer> send_buffer_;
// True when we are currently doing discovery.
bool discovery_active_;
// The number of requests that have been sent in the current discovery.
int num_requests_sent_;
// The maximum number of requests to send per discovery cycle.
int max_requests_;
// Timer for finishing discovery.
base::OneShotTimer<DialServiceImpl> finish_timer_;
// The delay for |finish_timer_|; how long to wait for discovery to finish.
// Setting this to zero disables the timer.
base::TimeDelta finish_delay_;
// Timer for sending multiple requests at fixed intervals.
base::RepeatingTimer<DialServiceImpl> request_timer_;
// The delay for |request_timer_|; how long to wait between successive
// requests.
base::TimeDelta request_interval_;
// List of observers.
ObserverList<Observer> observer_list_;
// Thread checker.
base::ThreadChecker thread_checker_;
friend class DialServiceTest;
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestSendMultipleRequests);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestMultipleNetworkInterfaces);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestNotifyOnError);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestOnDeviceDiscovered);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestOnDiscoveryFinished);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestOnDiscoveryRequest);
FRIEND_TEST_ALL_PREFIXES(DialServiceTest, TestResponseParsing);
DISALLOW_COPY_AND_ASSIGN(DialServiceImpl);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_DIAL_DIAL_SERVICE_H_