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.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 "components/usb_service/usb_device.h"
27 #include "components/usb_service/usb_device_handle.h"
28 #include "components/usb_service/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
;
39 using ::usb_service::UsbDevice
;
40 using ::usb_service::UsbDeviceHandle
;
41 using ::usb_service::UsbService
;
47 static const char kCommandLineSwitch
[] = "enable-gadget-tests";
48 static const int kClaimRetries
= 100; // 5 seconds
49 static const int kDisconnectRetries
= 100; // 5 seconds
50 static const int kRetryPeriod
= 50; // 0.05 seconds
51 static const int kReconnectRetries
= 100; // 5 seconds
52 static const int kUpdateRetries
= 100; // 5 seconds
54 struct UsbTestGadgetConfiguration
{
55 UsbTestGadget::Type type
;
56 const char* http_resource
;
60 static const struct UsbTestGadgetConfiguration kConfigurations
[] = {
61 { UsbTestGadget::DEFAULT
, "/unconfigure", 0x58F0 },
62 { UsbTestGadget::KEYBOARD
, "/keyboard/configure", 0x58F1 },
63 { UsbTestGadget::MOUSE
, "/mouse/configure", 0x58F2 },
64 { UsbTestGadget::HID_ECHO
, "/hid_echo/configure", 0x58F3 },
67 class UsbTestGadgetImpl
: public UsbTestGadget
{
69 virtual ~UsbTestGadgetImpl();
71 virtual bool Unclaim() OVERRIDE
;
72 virtual bool Disconnect() OVERRIDE
;
73 virtual bool Reconnect() OVERRIDE
;
74 virtual bool SetType(Type type
) OVERRIDE
;
75 virtual UsbDevice
* GetDevice() const OVERRIDE
;
76 virtual std::string
GetSerial() const OVERRIDE
;
82 scoped_ptr
<net::URLFetcher
> CreateURLFetcher(
84 net::URLFetcher::RequestType request_type
,
85 net::URLFetcherDelegate
* delegate
);
86 int SimplePOSTRequest(const GURL
& url
, const std::string
& form_data
);
88 bool GetVersion(std::string
* version
);
91 bool ReadLocalVersion(std::string
* version
);
92 bool ReadLocalPackage(std::string
* package
);
93 bool ReadFile(const base::FilePath
& file_path
, std::string
* content
);
95 class Delegate
: public net::URLFetcherDelegate
{
98 virtual ~Delegate() {}
100 void WaitForCompletion() {
104 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
{
109 base::RunLoop run_loop_
;
111 DISALLOW_COPY_AND_ASSIGN(Delegate
);
114 scoped_refptr
<UsbDevice
> device_
;
115 std::string device_address_
;
116 scoped_ptr
<net::URLRequestContext
> request_context_
;
117 std::string session_id_
;
118 UsbService
* usb_service_
;
120 friend class UsbTestGadget
;
122 DISALLOW_COPY_AND_ASSIGN(UsbTestGadgetImpl
);
127 bool UsbTestGadget::IsTestEnabled() {
128 base::CommandLine
* command_line
= CommandLine::ForCurrentProcess();
129 return command_line
->HasSwitch(kCommandLineSwitch
);
132 scoped_ptr
<UsbTestGadget
> UsbTestGadget::Claim() {
133 scoped_ptr
<UsbTestGadgetImpl
> gadget(new UsbTestGadgetImpl
);
135 int retries
= kClaimRetries
;
136 while (!gadget
->FindUnclaimed()) {
137 if (--retries
== 0) {
138 LOG(ERROR
) << "Failed to find an unclaimed device.";
139 return scoped_ptr
<UsbTestGadget
>();
141 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
143 VLOG(1) << "It took " << (kClaimRetries
- retries
)
144 << " retries to find an unclaimed device.";
146 return gadget
.PassAs
<UsbTestGadget
>();
149 UsbTestGadgetImpl::UsbTestGadgetImpl() {
150 net::URLRequestContextBuilder context_builder
;
151 context_builder
.set_proxy_service(net::ProxyService::CreateDirect());
152 request_context_
.reset(context_builder
.Build());
154 base::ProcessId process_id
= base::Process::Current().pid();
155 session_id_
= base::StringPrintf(
156 "%s:%p", base::HexEncode(&process_id
, sizeof(process_id
)).c_str(), this);
158 usb_service_
= UsbService::GetInstance();
161 UsbTestGadgetImpl::~UsbTestGadgetImpl() {
162 if (!device_address_
.empty()) {
167 UsbDevice
* UsbTestGadgetImpl::GetDevice() const {
168 return device_
.get();
171 std::string
UsbTestGadgetImpl::GetSerial() const {
172 return device_address_
;
175 scoped_ptr
<net::URLFetcher
> UsbTestGadgetImpl::CreateURLFetcher(
176 const GURL
& url
, net::URLFetcher::RequestType request_type
,
177 net::URLFetcherDelegate
* delegate
) {
178 scoped_ptr
<net::URLFetcher
> url_fetcher(
179 net::URLFetcher::Create(url
, request_type
, delegate
));
181 url_fetcher
->SetRequestContext(
182 new net::TrivialURLRequestContextGetter(
183 request_context_
.get(),
184 base::MessageLoop::current()->message_loop_proxy()));
186 return url_fetcher
.PassAs
<net::URLFetcher
>();
189 int UsbTestGadgetImpl::SimplePOSTRequest(const GURL
& url
,
190 const std::string
& form_data
) {
192 scoped_ptr
<net::URLFetcher
> url_fetcher
=
193 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
195 url_fetcher
->SetUploadData("application/x-www-form-urlencoded", form_data
);
196 url_fetcher
->Start();
197 delegate
.WaitForCompletion();
199 return url_fetcher
->GetResponseCode();
202 bool UsbTestGadgetImpl::FindUnclaimed() {
203 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
204 usb_service_
->GetDevices(&devices
);
206 for (std::vector
<scoped_refptr
<UsbDevice
> >::const_iterator iter
=
207 devices
.begin(); iter
!= devices
.end(); ++iter
) {
208 const scoped_refptr
<UsbDevice
> &device
= *iter
;
209 if (device
->vendor_id() == 0x18D1 && device
->product_id() == 0x58F0) {
210 scoped_refptr
<UsbDeviceHandle
> handle
= device
->Open();
211 if (handle
.get() == NULL
) {
215 base::string16 serial_utf16
;
216 if (!handle
->GetSerial(&serial_utf16
)) {
220 const std::string serial
= base::UTF16ToUTF8(serial_utf16
);
221 const GURL
url("http://" + serial
+ "/claim");
222 const std::string form_data
= base::StringPrintf(
224 net::EscapeUrlEncodedData(session_id_
, true).c_str());
225 const int response_code
= SimplePOSTRequest(url
, form_data
);
227 if (response_code
== 200) {
228 device_address_
= serial
;
233 // The device is probably claimed by another process.
234 if (response_code
!= 403) {
235 LOG(WARNING
) << "Unexpected HTTP " << response_code
<< " from /claim.";
240 std::string local_version
;
242 if (!ReadLocalVersion(&local_version
) ||
243 !GetVersion(&version
)) {
247 if (version
== local_version
) {
254 bool UsbTestGadgetImpl::GetVersion(std::string
* version
) {
256 const GURL
url("http://" + device_address_
+ "/version");
257 scoped_ptr
<net::URLFetcher
> url_fetcher
=
258 CreateURLFetcher(url
, net::URLFetcher::GET
, &delegate
);
260 url_fetcher
->Start();
261 delegate
.WaitForCompletion();
263 const int response_code
= url_fetcher
->GetResponseCode();
264 if (response_code
!= 200) {
265 VLOG(2) << "Unexpected HTTP " << response_code
<< " from /version.";
269 STLClearObject(version
);
270 if (!url_fetcher
->GetResponseAsString(version
)) {
271 VLOG(2) << "Failed to read body from /version.";
277 bool UsbTestGadgetImpl::Update() {
279 if (!ReadLocalVersion(&version
)) {
282 LOG(INFO
) << "Updating " << device_address_
<< " to " << version
<< "...";
285 const GURL
url("http://" + device_address_
+ "/update");
286 scoped_ptr
<net::URLFetcher
> url_fetcher
=
287 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
289 const std::string mime_header
=
292 "Content-Disposition: form-data; name=\"file\"; "
293 "filename=\"usb_gadget-%s.zip\"\r\n"
294 "Content-Type: application/octet-stream\r\n"
295 "\r\n", version
.c_str());
296 const std::string
mime_footer("\r\n--foo--\r\n");
299 if (!ReadLocalPackage(&package
)) {
303 url_fetcher
->SetUploadData("multipart/form-data; boundary=foo",
304 mime_header
+ package
+ mime_footer
);
305 url_fetcher
->Start();
306 delegate
.WaitForCompletion();
308 const int response_code
= url_fetcher
->GetResponseCode();
309 if (response_code
!= 200) {
310 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /update.";
314 int retries
= kUpdateRetries
;
315 std::string new_version
;
316 while (!GetVersion(&new_version
) || new_version
!= version
) {
317 if (--retries
== 0) {
318 LOG(ERROR
) << "Device not responding with new version.";
321 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
323 VLOG(1) << "It took " << (kUpdateRetries
- retries
)
324 << " retries to see the new version.";
326 // Release the old reference to the device and try to open a new one.
328 retries
= kReconnectRetries
;
329 while (!FindClaimed()) {
330 if (--retries
== 0) {
331 LOG(ERROR
) << "Failed to find updated device.";
334 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
336 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
337 << " retries to find the updated device.";
342 bool UsbTestGadgetImpl::FindClaimed() {
343 CHECK(!device_
.get());
345 std::string expected_serial
= GetSerial();
347 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
348 usb_service_
->GetDevices(&devices
);
350 for (std::vector
<scoped_refptr
<UsbDevice
> >::iterator iter
=
351 devices
.begin(); iter
!= devices
.end(); ++iter
) {
352 scoped_refptr
<UsbDevice
> &device
= *iter
;
354 if (device
->vendor_id() == 0x18D1) {
355 const uint16 product_id
= device
->product_id();
357 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
358 if (product_id
== kConfigurations
[i
].product_id
) {
367 scoped_refptr
<UsbDeviceHandle
> handle(device
->Open());
368 if (handle
.get() == NULL
) {
372 base::string16 serial_utf16
;
373 if (!handle
->GetSerial(&serial_utf16
)) {
377 std::string serial
= base::UTF16ToUTF8(serial_utf16
);
378 if (serial
!= expected_serial
) {
390 bool UsbTestGadgetImpl::ReadLocalVersion(std::string
* version
) {
391 base::FilePath file_path
;
392 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
393 file_path
= file_path
.AppendASCII("usb_gadget.zip.md5");
395 return ReadFile(file_path
, version
);
398 bool UsbTestGadgetImpl::ReadLocalPackage(std::string
* package
) {
399 base::FilePath file_path
;
400 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
401 file_path
= file_path
.AppendASCII("usb_gadget.zip");
403 return ReadFile(file_path
, package
);
406 bool UsbTestGadgetImpl::ReadFile(const base::FilePath
& file_path
,
407 std::string
* content
) {
408 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
409 if (!file
.IsValid()) {
410 LOG(ERROR
) << "Cannot open " << file_path
.MaybeAsASCII() << ": "
411 << base::File::ErrorToString(file
.error_details());
415 STLClearObject(content
);
419 rv
= file
.ReadAtCurrentPos(buf
, sizeof buf
);
421 LOG(ERROR
) << "Cannot read " << file_path
.MaybeAsASCII() << ": "
422 << base::File::ErrorToString(file
.error_details());
425 content
->append(buf
, rv
);
431 bool UsbTestGadgetImpl::Unclaim() {
432 VLOG(1) << "Releasing the device at " << device_address_
<< ".";
434 const GURL
url("http://" + device_address_
+ "/unclaim");
435 const int response_code
= SimplePOSTRequest(url
, "");
437 if (response_code
!= 200) {
438 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /unclaim.";
444 bool UsbTestGadgetImpl::SetType(Type type
) {
445 const struct UsbTestGadgetConfiguration
* config
= NULL
;
446 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
447 if (kConfigurations
[i
].type
== type
) {
448 config
= &kConfigurations
[i
];
453 const GURL
url("http://" + device_address_
+ config
->http_resource
);
454 const int response_code
= SimplePOSTRequest(url
, "");
456 if (response_code
!= 200) {
457 LOG(ERROR
) << "Unexpected HTTP " << response_code
458 << " from " << config
->http_resource
<< ".";
462 // Release the old reference to the device and try to open a new one.
463 int retries
= kReconnectRetries
;
466 if (FindClaimed() && device_
->product_id() == config
->product_id
) {
469 if (--retries
== 0) {
470 LOG(ERROR
) << "Failed to find updated device.";
473 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
475 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
476 << " retries to find the updated device.";
481 bool UsbTestGadgetImpl::Disconnect() {
482 const GURL
url("http://" + device_address_
+ "/disconnect");
483 const int response_code
= SimplePOSTRequest(url
, "");
485 if (response_code
!= 200) {
486 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /disconnect.";
490 // Release the old reference to the device and wait until it can't be found.
491 int retries
= kDisconnectRetries
;
494 if (!FindClaimed()) {
497 if (--retries
== 0) {
498 LOG(ERROR
) << "Device did not disconnect.";
501 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
503 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
504 << " retries for the device to disconnect.";
509 bool UsbTestGadgetImpl::Reconnect() {
510 const GURL
url("http://" + device_address_
+ "/reconnect");
511 const int response_code
= SimplePOSTRequest(url
, "");
513 if (response_code
!= 200) {
514 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /reconnect.";
518 int retries
= kDisconnectRetries
;
523 if (--retries
== 0) {
524 LOG(ERROR
) << "Device did not reconnect.";
527 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
529 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
530 << " retries for the device to reconnect.";
535 } // namespace device