1 // Copyright 2014 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.
5 #include "device/test/usb_test_gadget.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/files/file.h"
13 #include "base/files/file_path.h"
14 #include "base/logging.h"
15 #include "base/macros.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/path_service.h"
19 #include "base/process/process_handle.h"
20 #include "base/run_loop.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/platform_thread.h"
25 #include "base/time/time.h"
26 #include "device/usb/usb_device.h"
27 #include "device/usb/usb_device_handle.h"
28 #include "device/usb/usb_service.h"
29 #include "net/proxy/proxy_service.h"
30 #include "net/url_request/url_fetcher.h"
31 #include "net/url_request/url_fetcher_delegate.h"
32 #include "net/url_request/url_request_context.h"
33 #include "net/url_request/url_request_context_builder.h"
34 #include "net/url_request/url_request_context_getter.h"
37 using ::base::PlatformThread
;
38 using ::base::TimeDelta
;
44 static const char kCommandLineSwitch
[] = "enable-gadget-tests";
45 static const int kClaimRetries
= 100; // 5 seconds
46 static const int kDisconnectRetries
= 100; // 5 seconds
47 static const int kRetryPeriod
= 50; // 0.05 seconds
48 static const int kReconnectRetries
= 100; // 5 seconds
49 static const int kUpdateRetries
= 100; // 5 seconds
51 struct UsbTestGadgetConfiguration
{
52 UsbTestGadget::Type type
;
53 const char* http_resource
;
57 static const struct UsbTestGadgetConfiguration kConfigurations
[] = {
58 {UsbTestGadget::DEFAULT
, "/unconfigure", 0x58F0},
59 {UsbTestGadget::KEYBOARD
, "/keyboard/configure", 0x58F1},
60 {UsbTestGadget::MOUSE
, "/mouse/configure", 0x58F2},
61 {UsbTestGadget::HID_ECHO
, "/hid_echo/configure", 0x58F3},
62 {UsbTestGadget::ECHO
, "/echo/configure", 0x58F4},
65 class UsbTestGadgetImpl
: public UsbTestGadget
{
67 ~UsbTestGadgetImpl() override
;
69 bool Unclaim() override
;
70 bool Disconnect() override
;
71 bool Reconnect() override
;
72 bool SetType(Type type
) override
;
73 UsbDevice
* GetDevice() const override
;
74 std::string
GetSerialNumber() const override
;
80 scoped_ptr
<net::URLFetcher
> CreateURLFetcher(
82 net::URLFetcher::RequestType request_type
,
83 net::URLFetcherDelegate
* delegate
);
84 int SimplePOSTRequest(const GURL
& url
, const std::string
& form_data
);
86 bool GetVersion(std::string
* version
);
89 bool ReadLocalVersion(std::string
* version
);
90 bool ReadLocalPackage(std::string
* package
);
91 bool ReadFile(const base::FilePath
& file_path
, std::string
* content
);
93 class Delegate
: public net::URLFetcherDelegate
{
96 ~Delegate() override
{}
98 void WaitForCompletion() {
102 void OnURLFetchComplete(const net::URLFetcher
* source
) override
{
107 base::RunLoop run_loop_
;
109 DISALLOW_COPY_AND_ASSIGN(Delegate
);
112 scoped_refptr
<UsbDevice
> device_
;
113 std::string device_address_
;
114 scoped_ptr
<net::URLRequestContext
> request_context_
;
115 std::string session_id_
;
116 UsbService
* usb_service_
;
118 friend class UsbTestGadget
;
120 DISALLOW_COPY_AND_ASSIGN(UsbTestGadgetImpl
);
125 bool UsbTestGadget::IsTestEnabled() {
126 base::CommandLine
* command_line
= CommandLine::ForCurrentProcess();
127 return command_line
->HasSwitch(kCommandLineSwitch
);
130 scoped_ptr
<UsbTestGadget
> UsbTestGadget::Claim() {
131 scoped_ptr
<UsbTestGadgetImpl
> gadget(new UsbTestGadgetImpl
);
133 int retries
= kClaimRetries
;
134 while (!gadget
->FindUnclaimed()) {
135 if (--retries
== 0) {
136 LOG(ERROR
) << "Failed to find an unclaimed device.";
137 return scoped_ptr
<UsbTestGadget
>();
139 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
141 VLOG(1) << "It took " << (kClaimRetries
- retries
)
142 << " retries to find an unclaimed device.";
144 return gadget
.Pass();
147 UsbTestGadgetImpl::UsbTestGadgetImpl() {
148 net::URLRequestContextBuilder context_builder
;
149 context_builder
.set_proxy_service(net::ProxyService::CreateDirect());
150 request_context_
.reset(context_builder
.Build());
152 base::ProcessId process_id
= base::GetCurrentProcId();
153 session_id_
= base::StringPrintf(
154 "%s:%p", base::HexEncode(&process_id
, sizeof(process_id
)).c_str(), this);
156 usb_service_
= UsbService::GetInstance(NULL
);
159 UsbTestGadgetImpl::~UsbTestGadgetImpl() {
160 if (!device_address_
.empty()) {
165 UsbDevice
* UsbTestGadgetImpl::GetDevice() const {
166 return device_
.get();
169 std::string
UsbTestGadgetImpl::GetSerialNumber() const {
170 return device_address_
;
173 scoped_ptr
<net::URLFetcher
> UsbTestGadgetImpl::CreateURLFetcher(
174 const GURL
& url
, net::URLFetcher::RequestType request_type
,
175 net::URLFetcherDelegate
* delegate
) {
176 scoped_ptr
<net::URLFetcher
> url_fetcher(
177 net::URLFetcher::Create(url
, request_type
, delegate
));
179 url_fetcher
->SetRequestContext(
180 new net::TrivialURLRequestContextGetter(
181 request_context_
.get(),
182 base::MessageLoop::current()->message_loop_proxy()));
187 int UsbTestGadgetImpl::SimplePOSTRequest(const GURL
& url
,
188 const std::string
& form_data
) {
190 scoped_ptr
<net::URLFetcher
> url_fetcher
=
191 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
193 url_fetcher
->SetUploadData("application/x-www-form-urlencoded", form_data
);
194 url_fetcher
->Start();
195 delegate
.WaitForCompletion();
197 return url_fetcher
->GetResponseCode();
200 bool UsbTestGadgetImpl::FindUnclaimed() {
201 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
202 usb_service_
->GetDevices(&devices
);
204 for (std::vector
<scoped_refptr
<UsbDevice
> >::const_iterator iter
=
205 devices
.begin(); iter
!= devices
.end(); ++iter
) {
206 const scoped_refptr
<UsbDevice
> &device
= *iter
;
207 if (device
->vendor_id() == 0x18D1 && device
->product_id() == 0x58F0) {
208 base::string16 serial_utf16
;
209 if (!device
->GetSerialNumber(&serial_utf16
)) {
213 const std::string serial
= base::UTF16ToUTF8(serial_utf16
);
214 const GURL
url("http://" + serial
+ "/claim");
215 const std::string form_data
= base::StringPrintf(
217 net::EscapeUrlEncodedData(session_id_
, true).c_str());
218 const int response_code
= SimplePOSTRequest(url
, form_data
);
220 if (response_code
== 200) {
221 device_address_
= serial
;
226 // The device is probably claimed by another process.
227 if (response_code
!= 403) {
228 LOG(WARNING
) << "Unexpected HTTP " << response_code
<< " from /claim.";
233 std::string local_version
;
235 if (!ReadLocalVersion(&local_version
) ||
236 !GetVersion(&version
)) {
240 if (version
== local_version
) {
247 bool UsbTestGadgetImpl::GetVersion(std::string
* version
) {
249 const GURL
url("http://" + device_address_
+ "/version");
250 scoped_ptr
<net::URLFetcher
> url_fetcher
=
251 CreateURLFetcher(url
, net::URLFetcher::GET
, &delegate
);
253 url_fetcher
->Start();
254 delegate
.WaitForCompletion();
256 const int response_code
= url_fetcher
->GetResponseCode();
257 if (response_code
!= 200) {
258 VLOG(2) << "Unexpected HTTP " << response_code
<< " from /version.";
262 STLClearObject(version
);
263 if (!url_fetcher
->GetResponseAsString(version
)) {
264 VLOG(2) << "Failed to read body from /version.";
270 bool UsbTestGadgetImpl::Update() {
272 if (!ReadLocalVersion(&version
)) {
275 LOG(INFO
) << "Updating " << device_address_
<< " to " << version
<< "...";
278 const GURL
url("http://" + device_address_
+ "/update");
279 scoped_ptr
<net::URLFetcher
> url_fetcher
=
280 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
282 const std::string mime_header
=
285 "Content-Disposition: form-data; name=\"file\"; "
286 "filename=\"usb_gadget-%s.zip\"\r\n"
287 "Content-Type: application/octet-stream\r\n"
288 "\r\n", version
.c_str());
289 const std::string
mime_footer("\r\n--foo--\r\n");
292 if (!ReadLocalPackage(&package
)) {
296 url_fetcher
->SetUploadData("multipart/form-data; boundary=foo",
297 mime_header
+ package
+ mime_footer
);
298 url_fetcher
->Start();
299 delegate
.WaitForCompletion();
301 const int response_code
= url_fetcher
->GetResponseCode();
302 if (response_code
!= 200) {
303 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /update.";
307 int retries
= kUpdateRetries
;
308 std::string new_version
;
309 while (!GetVersion(&new_version
) || new_version
!= version
) {
310 if (--retries
== 0) {
311 LOG(ERROR
) << "Device not responding with new version.";
314 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
316 VLOG(1) << "It took " << (kUpdateRetries
- retries
)
317 << " retries to see the new version.";
319 // Release the old reference to the device and try to open a new one.
321 retries
= kReconnectRetries
;
322 while (!FindClaimed()) {
323 if (--retries
== 0) {
324 LOG(ERROR
) << "Failed to find updated device.";
327 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
329 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
330 << " retries to find the updated device.";
335 bool UsbTestGadgetImpl::FindClaimed() {
336 CHECK(!device_
.get());
338 std::string expected_serial
= GetSerialNumber();
340 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
341 usb_service_
->GetDevices(&devices
);
343 for (std::vector
<scoped_refptr
<UsbDevice
> >::iterator iter
=
344 devices
.begin(); iter
!= devices
.end(); ++iter
) {
345 scoped_refptr
<UsbDevice
> &device
= *iter
;
347 if (device
->vendor_id() == 0x18D1) {
348 const uint16 product_id
= device
->product_id();
350 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
351 if (product_id
== kConfigurations
[i
].product_id
) {
360 base::string16 serial_utf16
;
361 if (!device
->GetSerialNumber(&serial_utf16
)) {
365 std::string serial
= base::UTF16ToUTF8(serial_utf16
);
366 if (serial
!= expected_serial
) {
378 bool UsbTestGadgetImpl::ReadLocalVersion(std::string
* version
) {
379 base::FilePath file_path
;
380 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
381 file_path
= file_path
.AppendASCII("usb_gadget.zip.md5");
383 return ReadFile(file_path
, version
);
386 bool UsbTestGadgetImpl::ReadLocalPackage(std::string
* package
) {
387 base::FilePath file_path
;
388 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
389 file_path
= file_path
.AppendASCII("usb_gadget.zip");
391 return ReadFile(file_path
, package
);
394 bool UsbTestGadgetImpl::ReadFile(const base::FilePath
& file_path
,
395 std::string
* content
) {
396 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
397 if (!file
.IsValid()) {
398 LOG(ERROR
) << "Cannot open " << file_path
.MaybeAsASCII() << ": "
399 << base::File::ErrorToString(file
.error_details());
403 STLClearObject(content
);
407 rv
= file
.ReadAtCurrentPos(buf
, sizeof buf
);
409 LOG(ERROR
) << "Cannot read " << file_path
.MaybeAsASCII() << ": "
410 << base::File::ErrorToString(file
.error_details());
413 content
->append(buf
, rv
);
419 bool UsbTestGadgetImpl::Unclaim() {
420 VLOG(1) << "Releasing the device at " << device_address_
<< ".";
422 const GURL
url("http://" + device_address_
+ "/unclaim");
423 const int response_code
= SimplePOSTRequest(url
, "");
425 if (response_code
!= 200) {
426 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /unclaim.";
432 bool UsbTestGadgetImpl::SetType(Type type
) {
433 const struct UsbTestGadgetConfiguration
* config
= NULL
;
434 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
435 if (kConfigurations
[i
].type
== type
) {
436 config
= &kConfigurations
[i
];
441 const GURL
url("http://" + device_address_
+ config
->http_resource
);
442 const int response_code
= SimplePOSTRequest(url
, "");
444 if (response_code
!= 200) {
445 LOG(ERROR
) << "Unexpected HTTP " << response_code
446 << " from " << config
->http_resource
<< ".";
450 // Release the old reference to the device and try to open a new one.
451 int retries
= kReconnectRetries
;
454 if (FindClaimed() && device_
->product_id() == config
->product_id
) {
457 if (--retries
== 0) {
458 LOG(ERROR
) << "Failed to find updated device.";
461 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
463 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
464 << " retries to find the updated device.";
469 bool UsbTestGadgetImpl::Disconnect() {
470 const GURL
url("http://" + device_address_
+ "/disconnect");
471 const int response_code
= SimplePOSTRequest(url
, "");
473 if (response_code
!= 200) {
474 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /disconnect.";
478 // Release the old reference to the device and wait until it can't be found.
479 int retries
= kDisconnectRetries
;
482 if (!FindClaimed()) {
485 if (--retries
== 0) {
486 LOG(ERROR
) << "Device did not disconnect.";
489 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
491 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
492 << " retries for the device to disconnect.";
497 bool UsbTestGadgetImpl::Reconnect() {
498 const GURL
url("http://" + device_address_
+ "/reconnect");
499 const int response_code
= SimplePOSTRequest(url
, "");
501 if (response_code
!= 200) {
502 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /reconnect.";
506 int retries
= kDisconnectRetries
;
511 if (--retries
== 0) {
512 LOG(ERROR
) << "Device did not reconnect.";
515 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
517 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
518 << " retries for the device to reconnect.";
523 } // namespace device