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/time/time.h"
25 #include "device/usb/usb_device.h"
26 #include "device/usb/usb_device_handle.h"
27 #include "device/usb/usb_service.h"
28 #include "net/proxy/proxy_service.h"
29 #include "net/url_request/url_fetcher.h"
30 #include "net/url_request/url_fetcher_delegate.h"
31 #include "net/url_request/url_request_context.h"
32 #include "net/url_request/url_request_context_builder.h"
33 #include "net/url_request/url_request_context_getter.h"
36 using ::base::PlatformThread
;
37 using ::base::TimeDelta
;
43 static const char kCommandLineSwitch
[] = "enable-gadget-tests";
44 static const int kClaimRetries
= 100; // 5 seconds
45 static const int kDisconnectRetries
= 100; // 5 seconds
46 static const int kRetryPeriod
= 50; // 0.05 seconds
47 static const int kReconnectRetries
= 100; // 5 seconds
48 static const int kUpdateRetries
= 100; // 5 seconds
50 // Wait for the given time delta while still running the main loop. This is
51 // necessary so that device add/remove events are processed by the UsbService.
52 void SleepWithRunLoop(base::TimeDelta delta
) {
53 base::RunLoop run_loop
;
54 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
55 run_loop
.QuitClosure(), delta
);
59 struct UsbTestGadgetConfiguration
{
60 UsbTestGadget::Type type
;
61 const char* http_resource
;
65 static const struct UsbTestGadgetConfiguration kConfigurations
[] = {
66 {UsbTestGadget::DEFAULT
, "/unconfigure", 0x58F0},
67 {UsbTestGadget::KEYBOARD
, "/keyboard/configure", 0x58F1},
68 {UsbTestGadget::MOUSE
, "/mouse/configure", 0x58F2},
69 {UsbTestGadget::HID_ECHO
, "/hid_echo/configure", 0x58F3},
70 {UsbTestGadget::ECHO
, "/echo/configure", 0x58F4},
73 class UsbTestGadgetImpl
: public UsbTestGadget
{
75 ~UsbTestGadgetImpl() override
;
77 bool Unclaim() override
;
78 bool Disconnect() override
;
79 bool Reconnect() override
;
80 bool SetType(Type type
) override
;
81 UsbDevice
* GetDevice() const override
;
82 const std::string
& GetSerialNumber() const override
;
88 scoped_ptr
<net::URLFetcher
> CreateURLFetcher(
90 net::URLFetcher::RequestType request_type
,
91 net::URLFetcherDelegate
* delegate
);
92 int SimplePOSTRequest(const GURL
& url
, const std::string
& form_data
);
94 bool GetVersion(std::string
* version
);
97 bool ReadLocalVersion(std::string
* version
);
98 bool ReadLocalPackage(std::string
* package
);
99 bool ReadFile(const base::FilePath
& file_path
, std::string
* content
);
101 class Delegate
: public net::URLFetcherDelegate
{
104 ~Delegate() override
{}
106 void WaitForCompletion() {
110 void OnURLFetchComplete(const net::URLFetcher
* source
) override
{
115 base::RunLoop run_loop_
;
117 DISALLOW_COPY_AND_ASSIGN(Delegate
);
120 scoped_refptr
<UsbDevice
> device_
;
121 std::string device_address_
;
122 scoped_ptr
<net::URLRequestContext
> request_context_
;
123 std::string session_id_
;
124 UsbService
* usb_service_
;
126 friend class UsbTestGadget
;
128 DISALLOW_COPY_AND_ASSIGN(UsbTestGadgetImpl
);
133 bool UsbTestGadget::IsTestEnabled() {
134 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
135 return command_line
->HasSwitch(kCommandLineSwitch
);
138 scoped_ptr
<UsbTestGadget
> UsbTestGadget::Claim() {
139 scoped_ptr
<UsbTestGadgetImpl
> gadget(new UsbTestGadgetImpl
);
141 int retries
= kClaimRetries
;
142 while (!gadget
->FindUnclaimed()) {
143 if (--retries
== 0) {
144 LOG(ERROR
) << "Failed to find an unclaimed device.";
145 return scoped_ptr
<UsbTestGadget
>();
147 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod
));
149 VLOG(1) << "It took " << (kClaimRetries
- retries
)
150 << " retries to find an unclaimed device.";
152 return gadget
.Pass();
155 UsbTestGadgetImpl::UsbTestGadgetImpl() {
156 net::URLRequestContextBuilder context_builder
;
157 context_builder
.set_proxy_service(net::ProxyService::CreateDirect());
158 request_context_
.reset(context_builder
.Build());
160 base::ProcessId process_id
= base::GetCurrentProcId();
161 session_id_
= base::StringPrintf(
162 "%s:%p", base::HexEncode(&process_id
, sizeof(process_id
)).c_str(), this);
164 usb_service_
= UsbService::GetInstance(NULL
);
167 UsbTestGadgetImpl::~UsbTestGadgetImpl() {
168 if (!device_address_
.empty()) {
173 UsbDevice
* UsbTestGadgetImpl::GetDevice() const {
174 return device_
.get();
177 const std::string
& UsbTestGadgetImpl::GetSerialNumber() const {
178 return device_address_
;
181 scoped_ptr
<net::URLFetcher
> UsbTestGadgetImpl::CreateURLFetcher(
182 const GURL
& url
, net::URLFetcher::RequestType request_type
,
183 net::URLFetcherDelegate
* delegate
) {
184 scoped_ptr
<net::URLFetcher
> url_fetcher(
185 net::URLFetcher::Create(url
, request_type
, delegate
));
187 url_fetcher
->SetRequestContext(new net::TrivialURLRequestContextGetter(
188 request_context_
.get(), base::MessageLoop::current()->task_runner()));
193 int UsbTestGadgetImpl::SimplePOSTRequest(const GURL
& url
,
194 const std::string
& form_data
) {
196 scoped_ptr
<net::URLFetcher
> url_fetcher
=
197 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
199 url_fetcher
->SetUploadData("application/x-www-form-urlencoded", form_data
);
200 url_fetcher
->Start();
201 delegate
.WaitForCompletion();
203 return url_fetcher
->GetResponseCode();
206 bool UsbTestGadgetImpl::FindUnclaimed() {
207 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
208 usb_service_
->GetDevices(&devices
);
210 for (std::vector
<scoped_refptr
<UsbDevice
> >::const_iterator iter
=
211 devices
.begin(); iter
!= devices
.end(); ++iter
) {
212 const scoped_refptr
<UsbDevice
> &device
= *iter
;
213 if (device
->vendor_id() == 0x18D1 && device
->product_id() == 0x58F0) {
214 base::string16 serial_utf16
;
215 if (!device
->GetSerialNumber(&serial_utf16
)) {
219 const std::string serial
= base::UTF16ToUTF8(serial_utf16
);
220 const GURL
url("http://" + serial
+ "/claim");
221 const std::string form_data
= base::StringPrintf(
223 net::EscapeUrlEncodedData(session_id_
, true).c_str());
224 const int response_code
= SimplePOSTRequest(url
, form_data
);
226 if (response_code
== 200) {
227 device_address_
= serial
;
232 // The device is probably claimed by another process.
233 if (response_code
!= 403) {
234 LOG(WARNING
) << "Unexpected HTTP " << response_code
<< " from /claim.";
239 std::string local_version
;
241 if (!ReadLocalVersion(&local_version
) ||
242 !GetVersion(&version
)) {
246 if (version
== local_version
) {
253 bool UsbTestGadgetImpl::GetVersion(std::string
* version
) {
255 const GURL
url("http://" + device_address_
+ "/version");
256 scoped_ptr
<net::URLFetcher
> url_fetcher
=
257 CreateURLFetcher(url
, net::URLFetcher::GET
, &delegate
);
259 url_fetcher
->Start();
260 delegate
.WaitForCompletion();
262 const int response_code
= url_fetcher
->GetResponseCode();
263 if (response_code
!= 200) {
264 VLOG(2) << "Unexpected HTTP " << response_code
<< " from /version.";
268 STLClearObject(version
);
269 if (!url_fetcher
->GetResponseAsString(version
)) {
270 VLOG(2) << "Failed to read body from /version.";
276 bool UsbTestGadgetImpl::Update() {
278 if (!ReadLocalVersion(&version
)) {
281 LOG(INFO
) << "Updating " << device_address_
<< " to " << version
<< "...";
284 const GURL
url("http://" + device_address_
+ "/update");
285 scoped_ptr
<net::URLFetcher
> url_fetcher
=
286 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
288 const std::string mime_header
=
291 "Content-Disposition: form-data; name=\"file\"; "
292 "filename=\"usb_gadget-%s.zip\"\r\n"
293 "Content-Type: application/octet-stream\r\n"
294 "\r\n", version
.c_str());
295 const std::string
mime_footer("\r\n--foo--\r\n");
298 if (!ReadLocalPackage(&package
)) {
302 url_fetcher
->SetUploadData("multipart/form-data; boundary=foo",
303 mime_header
+ package
+ mime_footer
);
304 url_fetcher
->Start();
305 delegate
.WaitForCompletion();
307 const int response_code
= url_fetcher
->GetResponseCode();
308 if (response_code
!= 200) {
309 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /update.";
313 int retries
= kUpdateRetries
;
314 std::string new_version
;
315 while (!GetVersion(&new_version
) || new_version
!= version
) {
316 if (--retries
== 0) {
317 LOG(ERROR
) << "Device not responding with new version.";
320 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod
));
322 VLOG(1) << "It took " << (kUpdateRetries
- retries
)
323 << " retries to see the new version.";
325 // Release the old reference to the device and try to open a new one.
327 retries
= kReconnectRetries
;
328 while (!FindClaimed()) {
329 if (--retries
== 0) {
330 LOG(ERROR
) << "Failed to find updated device.";
333 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod
));
335 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
336 << " retries to find the updated device.";
341 bool UsbTestGadgetImpl::FindClaimed() {
342 CHECK(!device_
.get());
344 std::string expected_serial
= GetSerialNumber();
346 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
347 usb_service_
->GetDevices(&devices
);
349 for (std::vector
<scoped_refptr
<UsbDevice
> >::iterator iter
=
350 devices
.begin(); iter
!= devices
.end(); ++iter
) {
351 scoped_refptr
<UsbDevice
> &device
= *iter
;
353 if (device
->vendor_id() == 0x18D1) {
354 const uint16 product_id
= device
->product_id();
356 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
357 if (product_id
== kConfigurations
[i
].product_id
) {
366 base::string16 serial_utf16
;
367 if (!device
->GetSerialNumber(&serial_utf16
)) {
371 std::string serial
= base::UTF16ToUTF8(serial_utf16
);
372 if (serial
!= expected_serial
) {
384 bool UsbTestGadgetImpl::ReadLocalVersion(std::string
* version
) {
385 base::FilePath file_path
;
386 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
387 file_path
= file_path
.AppendASCII("usb_gadget.zip.md5");
389 return ReadFile(file_path
, version
);
392 bool UsbTestGadgetImpl::ReadLocalPackage(std::string
* package
) {
393 base::FilePath file_path
;
394 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
395 file_path
= file_path
.AppendASCII("usb_gadget.zip");
397 return ReadFile(file_path
, package
);
400 bool UsbTestGadgetImpl::ReadFile(const base::FilePath
& file_path
,
401 std::string
* content
) {
402 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
403 if (!file
.IsValid()) {
404 LOG(ERROR
) << "Cannot open " << file_path
.MaybeAsASCII() << ": "
405 << base::File::ErrorToString(file
.error_details());
409 STLClearObject(content
);
413 rv
= file
.ReadAtCurrentPos(buf
, sizeof buf
);
415 LOG(ERROR
) << "Cannot read " << file_path
.MaybeAsASCII() << ": "
416 << base::File::ErrorToString(file
.error_details());
419 content
->append(buf
, rv
);
425 bool UsbTestGadgetImpl::Unclaim() {
426 VLOG(1) << "Releasing the device at " << device_address_
<< ".";
428 const GURL
url("http://" + device_address_
+ "/unclaim");
429 const int response_code
= SimplePOSTRequest(url
, "");
431 if (response_code
!= 200) {
432 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /unclaim.";
436 device_address_
.clear();
440 bool UsbTestGadgetImpl::SetType(Type type
) {
441 const struct UsbTestGadgetConfiguration
* config
= NULL
;
442 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
443 if (kConfigurations
[i
].type
== type
) {
444 config
= &kConfigurations
[i
];
449 const GURL
url("http://" + device_address_
+ config
->http_resource
);
450 const int response_code
= SimplePOSTRequest(url
, "");
452 if (response_code
!= 200) {
453 LOG(ERROR
) << "Unexpected HTTP " << response_code
454 << " from " << config
->http_resource
<< ".";
458 // Release the old reference to the device and try to open a new one.
459 int retries
= kReconnectRetries
;
462 if (FindClaimed() && device_
->product_id() == config
->product_id
) {
465 if (--retries
== 0) {
466 LOG(ERROR
) << "Failed to find updated device.";
469 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod
));
471 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
472 << " retries to find the updated device.";
477 bool UsbTestGadgetImpl::Disconnect() {
478 const GURL
url("http://" + device_address_
+ "/disconnect");
479 const int response_code
= SimplePOSTRequest(url
, "");
481 if (response_code
!= 200) {
482 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /disconnect.";
486 // Release the old reference to the device and wait until it can't be found.
487 int retries
= kDisconnectRetries
;
490 if (!FindClaimed()) {
493 if (--retries
== 0) {
494 LOG(ERROR
) << "Device did not disconnect.";
497 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod
));
499 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
500 << " retries for the device to disconnect.";
505 bool UsbTestGadgetImpl::Reconnect() {
506 const GURL
url("http://" + device_address_
+ "/reconnect");
507 const int response_code
= SimplePOSTRequest(url
, "");
509 if (response_code
!= 200) {
510 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /reconnect.";
514 int retries
= kDisconnectRetries
;
519 if (--retries
== 0) {
520 LOG(ERROR
) << "Device did not reconnect.";
523 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod
));
525 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
526 << " retries for the device to reconnect.";
531 } // namespace device