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 "base/memory/ref_counted.h"
6#include "base/memory/scoped_ptr.h"
7#include "base/message_loop/message_loop.h"
8#include "chrome/browser/extensions/api/dial/dial_device_data.h"
9#include "chrome/browser/extensions/api/dial/dial_service.h"
10#include "net/base/capturing_net_log.h"
11#include "net/base/ip_endpoint.h"
12#include "testing/gmock/include/gmock/gmock.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15using base::Time;
16using base::TimeDelta;
17using ::testing::A;
18using ::testing::AtLeast;
19using ::testing::Return;
20
21namespace {
22
23const char kValidResponse[] =
24  "HTTP/1.1 OK\r\n"
25  "LOCATION: http://127.0.0.1/dd.xml\r\n"
26  "USN: some_id\r\n"
27  "CACHE-CONTROL: max-age=1800\r\n"
28  "CONFIGID.UPNP.ORG: 1\r\n\r\n";
29
30}  // namespace
31
32namespace extensions {
33
34class MockObserver : public DialService::Observer {
35 public:
36  MOCK_METHOD1(OnDiscoveryRequest, void(DialService*));
37  MOCK_METHOD2(OnDeviceDiscovered, void(DialService*, const DialDeviceData&));
38  MOCK_METHOD1(OnDiscoveryFinished, void(DialService*));
39  MOCK_METHOD2(OnError, void(DialService*,
40                             const DialService::DialServiceErrorCode&));
41};
42
43class DialServiceTest : public testing::Test {
44 public:
45  DialServiceTest()
46    : dial_service_(&capturing_net_log_) {
47    CHECK(net::ParseIPLiteralToNumber("0.0.0.0", &mock_ip_));
48    dial_service_.AddObserver(&mock_observer_);
49    dial_socket_ = dial_service_.CreateDialSocket();
50  }
51 protected:
52  net::CapturingNetLog capturing_net_log_;
53  net::IPAddressNumber mock_ip_;
54  DialServiceImpl dial_service_;
55  scoped_ptr<DialServiceImpl::DialSocket> dial_socket_;
56  MockObserver mock_observer_;
57};
58
59TEST_F(DialServiceTest, TestSendMultipleRequests) {
60  base::MessageLoopForIO loop;
61  // Setting the finish delay to zero disables the timer that invokes
62  // FinishDiscovery().
63  dial_service_.finish_delay_ = TimeDelta::FromSeconds(0);
64  dial_service_.request_interval_ = TimeDelta::FromSeconds(0);
65  dial_service_.max_requests_ = 4;
66  dial_service_.discovery_active_ = true;
67  EXPECT_CALL(mock_observer_, OnDiscoveryRequest(A<DialService*>())).Times(4);
68  EXPECT_CALL(mock_observer_, OnDiscoveryFinished(A<DialService*>())).Times(1);
69  dial_service_.BindAndAddSocket(mock_ip_);
70  EXPECT_EQ(1u, dial_service_.dial_sockets_.size());
71  dial_service_.SendOneRequest();
72  loop.RunUntilIdle();
73  dial_service_.FinishDiscovery();
74}
75
76TEST_F(DialServiceTest, TestMultipleNetworkInterfaces) {
77  base::MessageLoopForIO loop;
78  // Setting the finish delay to zero disables the timer that invokes
79  // FinishDiscovery().
80  dial_service_.finish_delay_ = TimeDelta::FromSeconds(0);
81  dial_service_.request_interval_ = TimeDelta::FromSeconds(0);
82  dial_service_.max_requests_ = 4;
83  dial_service_.discovery_active_ = true;
84  net::NetworkInterfaceList interface_list;
85  interface_list.push_back(
86      net::NetworkInterface("network1",
87                            "network1",
88                            0,
89                            net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
90                            mock_ip_,
91                            0,
92                            net::IP_ADDRESS_ATTRIBUTE_NONE));
93  interface_list.push_back(
94      net::NetworkInterface("network2",
95                            "network2",
96                            1,
97                            net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
98                            mock_ip_,
99                            0,
100                            net::IP_ADDRESS_ATTRIBUTE_NONE));
101  interface_list.push_back(
102      net::NetworkInterface("network3",
103                            "network3",
104                            2,
105                            net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
106                            mock_ip_,
107                            0,
108                            net::IP_ADDRESS_ATTRIBUTE_NONE));
109
110  // "network4" is equivalent to "network2" because both the address family
111  // and interface index are the same.
112  interface_list.push_back(
113      net::NetworkInterface("network4",
114                            "network4",
115                            1,
116                            net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
117                            mock_ip_,
118                            0,
119                            net::IP_ADDRESS_ATTRIBUTE_NONE));
120
121  // 3 sockets * 4 requests per socket = 12 requests
122  EXPECT_CALL(mock_observer_, OnDiscoveryRequest(A<DialService*>())).Times(12);
123  EXPECT_CALL(mock_observer_, OnDiscoveryFinished(A<DialService*>())).Times(1);
124
125  dial_service_.SendNetworkList(interface_list);
126  EXPECT_EQ(3u, dial_service_.dial_sockets_.size());
127
128  loop.RunUntilIdle();
129  dial_service_.FinishDiscovery();
130}
131
132TEST_F(DialServiceTest, TestOnDiscoveryRequest) {
133  dial_service_.discovery_active_ = true;
134  dial_service_.num_requests_sent_ = 1;
135  dial_service_.max_requests_ = 1;
136  size_t num_bytes = dial_service_.send_buffer_->size();
137  EXPECT_CALL(mock_observer_, OnDiscoveryRequest(A<DialService*>())).Times(1);
138  dial_socket_->OnSocketWrite(num_bytes, num_bytes);
139}
140
141TEST_F(DialServiceTest, TestOnDeviceDiscovered) {
142  dial_service_.discovery_active_ = true;
143  int response_size = arraysize(kValidResponse) - 1;
144  dial_socket_->recv_buffer_ =
145      new net::IOBufferWithSize(response_size);
146  strncpy(dial_socket_->recv_buffer_->data(),
147          kValidResponse,
148          response_size);
149  dial_socket_->recv_address_ = net::IPEndPoint(mock_ip_, 12345);
150
151  DialDeviceData expected_device;
152  expected_device.set_device_id("some_id");
153
154  EXPECT_CALL(mock_observer_,
155              OnDeviceDiscovered(A<DialService*>(), expected_device))
156      .Times(1);
157  dial_socket_->OnSocketRead(response_size);
158};
159
160TEST_F(DialServiceTest, TestOnDiscoveryFinished) {
161  dial_service_.discovery_active_ = true;
162
163  EXPECT_CALL(mock_observer_, OnDiscoveryFinished(A<DialService*>())).Times(1);
164  dial_service_.FinishDiscovery();
165  EXPECT_FALSE(dial_service_.discovery_active_);
166}
167
168TEST_F(DialServiceTest, TestResponseParsing) {
169  Time now = Time::Now();
170
171  // Successful case
172  DialDeviceData parsed;
173  EXPECT_TRUE(DialServiceImpl::DialSocket::ParseResponse(
174      kValidResponse, now, &parsed));
175  EXPECT_EQ("some_id", parsed.device_id());
176  EXPECT_EQ("http://127.0.0.1/dd.xml", parsed.device_description_url().spec());
177  EXPECT_EQ(1, parsed.config_id());
178  EXPECT_EQ(now, parsed.response_time());
179
180  // Failure cases
181  DialDeviceData not_parsed;
182
183  // Empty, garbage
184  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
185      std::string(), now, &not_parsed));
186  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
187      "\r\n\r\n",
188    now, &not_parsed));
189  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
190      "xyzzy",
191      now, &not_parsed));
192
193  // No headers
194  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
195      "HTTP/1.1 OK\r\n\r\n",
196      now, &not_parsed));
197
198  // Missing LOCATION
199  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
200      "HTTP/1.1 OK\r\n"
201      "USN: some_id\r\n\r\n",
202      now, &not_parsed));
203
204  // Empty LOCATION
205  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
206      "HTTP/1.1 OK\r\n"
207      "LOCATION:\r\n"
208      "USN: some_id\r\n\r\n",
209      now, &not_parsed));
210
211  // Missing USN
212  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
213      "HTTP/1.1 OK\r\n"
214      "LOCATION: http://127.0.0.1/dd.xml\r\n\r\n",
215      now, &not_parsed));
216
217  // Empty USN
218  EXPECT_FALSE(DialServiceImpl::DialSocket::ParseResponse(
219      "HTTP/1.1 OK\r\n"
220      "LOCATION: http://127.0.0.1/dd.xml\r\n"
221      "USN:\r\n\r\n",
222      now, &not_parsed));
223}
224
225}  // namespace extensions
226