summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/api/dial/dial_service.h
blob: 9869f2929e8cd05545e5c9a202bd3df587ab4b4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// 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 <vector>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.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/log/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.
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(const Observer* observer) const = 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);
  ~DialServiceImpl() override;

  // DialService implementation
  bool Discover() override;
  void AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer) override;
  bool HasObserver(const Observer* observer) const 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.
  std::vector<scoped_ptr<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 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 request_timer_;

  // The delay for |request_timer_|; how long to wait between successive
  // requests.
  base::TimeDelta request_interval_;

  // List of observers.
  base::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_