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 "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 virtual ~UsbTestGadgetImpl();
69 virtual bool Unclaim() OVERRIDE
;
70 virtual bool Disconnect() OVERRIDE
;
71 virtual bool Reconnect() OVERRIDE
;
72 virtual bool SetType(Type type
) OVERRIDE
;
73 virtual UsbDevice
* GetDevice() const OVERRIDE
;
74 virtual std::string
GetSerial() 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 virtual ~Delegate() {}
98 void WaitForCompletion() {
102 virtual 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
.PassAs
<UsbTestGadget
>();
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::Process::Current().pid();
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::GetSerial() 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()));
184 return url_fetcher
.PassAs
<net::URLFetcher
>();
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 scoped_refptr
<UsbDeviceHandle
> handle
= device
->Open();
209 if (handle
.get() == NULL
) {
213 base::string16 serial_utf16
;
214 if (!handle
->GetSerial(&serial_utf16
)) {
218 const std::string serial
= base::UTF16ToUTF8(serial_utf16
);
219 const GURL
url("http://" + serial
+ "/claim");
220 const std::string form_data
= base::StringPrintf(
222 net::EscapeUrlEncodedData(session_id_
, true).c_str());
223 const int response_code
= SimplePOSTRequest(url
, form_data
);
225 if (response_code
== 200) {
226 device_address_
= serial
;
231 // The device is probably claimed by another process.
232 if (response_code
!= 403) {
233 LOG(WARNING
) << "Unexpected HTTP " << response_code
<< " from /claim.";
238 std::string local_version
;
240 if (!ReadLocalVersion(&local_version
) ||
241 !GetVersion(&version
)) {
245 if (version
== local_version
) {
252 bool UsbTestGadgetImpl::GetVersion(std::string
* version
) {
254 const GURL
url("http://" + device_address_
+ "/version");
255 scoped_ptr
<net::URLFetcher
> url_fetcher
=
256 CreateURLFetcher(url
, net::URLFetcher::GET
, &delegate
);
258 url_fetcher
->Start();
259 delegate
.WaitForCompletion();
261 const int response_code
= url_fetcher
->GetResponseCode();
262 if (response_code
!= 200) {
263 VLOG(2) << "Unexpected HTTP " << response_code
<< " from /version.";
267 STLClearObject(version
);
268 if (!url_fetcher
->GetResponseAsString(version
)) {
269 VLOG(2) << "Failed to read body from /version.";
275 bool UsbTestGadgetImpl::Update() {
277 if (!ReadLocalVersion(&version
)) {
280 LOG(INFO
) << "Updating " << device_address_
<< " to " << version
<< "...";
283 const GURL
url("http://" + device_address_
+ "/update");
284 scoped_ptr
<net::URLFetcher
> url_fetcher
=
285 CreateURLFetcher(url
, net::URLFetcher::POST
, &delegate
);
287 const std::string mime_header
=
290 "Content-Disposition: form-data; name=\"file\"; "
291 "filename=\"usb_gadget-%s.zip\"\r\n"
292 "Content-Type: application/octet-stream\r\n"
293 "\r\n", version
.c_str());
294 const std::string
mime_footer("\r\n--foo--\r\n");
297 if (!ReadLocalPackage(&package
)) {
301 url_fetcher
->SetUploadData("multipart/form-data; boundary=foo",
302 mime_header
+ package
+ mime_footer
);
303 url_fetcher
->Start();
304 delegate
.WaitForCompletion();
306 const int response_code
= url_fetcher
->GetResponseCode();
307 if (response_code
!= 200) {
308 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /update.";
312 int retries
= kUpdateRetries
;
313 std::string new_version
;
314 while (!GetVersion(&new_version
) || new_version
!= version
) {
315 if (--retries
== 0) {
316 LOG(ERROR
) << "Device not responding with new version.";
319 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
321 VLOG(1) << "It took " << (kUpdateRetries
- retries
)
322 << " retries to see the new version.";
324 // Release the old reference to the device and try to open a new one.
326 retries
= kReconnectRetries
;
327 while (!FindClaimed()) {
328 if (--retries
== 0) {
329 LOG(ERROR
) << "Failed to find updated device.";
332 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
334 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
335 << " retries to find the updated device.";
340 bool UsbTestGadgetImpl::FindClaimed() {
341 CHECK(!device_
.get());
343 std::string expected_serial
= GetSerial();
345 std::vector
<scoped_refptr
<UsbDevice
> > devices
;
346 usb_service_
->GetDevices(&devices
);
348 for (std::vector
<scoped_refptr
<UsbDevice
> >::iterator iter
=
349 devices
.begin(); iter
!= devices
.end(); ++iter
) {
350 scoped_refptr
<UsbDevice
> &device
= *iter
;
352 if (device
->vendor_id() == 0x18D1) {
353 const uint16 product_id
= device
->product_id();
355 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
356 if (product_id
== kConfigurations
[i
].product_id
) {
365 scoped_refptr
<UsbDeviceHandle
> handle(device
->Open());
366 if (handle
.get() == NULL
) {
370 base::string16 serial_utf16
;
371 if (!handle
->GetSerial(&serial_utf16
)) {
375 std::string serial
= base::UTF16ToUTF8(serial_utf16
);
376 if (serial
!= expected_serial
) {
388 bool UsbTestGadgetImpl::ReadLocalVersion(std::string
* version
) {
389 base::FilePath file_path
;
390 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
391 file_path
= file_path
.AppendASCII("usb_gadget.zip.md5");
393 return ReadFile(file_path
, version
);
396 bool UsbTestGadgetImpl::ReadLocalPackage(std::string
* package
) {
397 base::FilePath file_path
;
398 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
399 file_path
= file_path
.AppendASCII("usb_gadget.zip");
401 return ReadFile(file_path
, package
);
404 bool UsbTestGadgetImpl::ReadFile(const base::FilePath
& file_path
,
405 std::string
* content
) {
406 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
407 if (!file
.IsValid()) {
408 LOG(ERROR
) << "Cannot open " << file_path
.MaybeAsASCII() << ": "
409 << base::File::ErrorToString(file
.error_details());
413 STLClearObject(content
);
417 rv
= file
.ReadAtCurrentPos(buf
, sizeof buf
);
419 LOG(ERROR
) << "Cannot read " << file_path
.MaybeAsASCII() << ": "
420 << base::File::ErrorToString(file
.error_details());
423 content
->append(buf
, rv
);
429 bool UsbTestGadgetImpl::Unclaim() {
430 VLOG(1) << "Releasing the device at " << device_address_
<< ".";
432 const GURL
url("http://" + device_address_
+ "/unclaim");
433 const int response_code
= SimplePOSTRequest(url
, "");
435 if (response_code
!= 200) {
436 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /unclaim.";
442 bool UsbTestGadgetImpl::SetType(Type type
) {
443 const struct UsbTestGadgetConfiguration
* config
= NULL
;
444 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
445 if (kConfigurations
[i
].type
== type
) {
446 config
= &kConfigurations
[i
];
451 const GURL
url("http://" + device_address_
+ config
->http_resource
);
452 const int response_code
= SimplePOSTRequest(url
, "");
454 if (response_code
!= 200) {
455 LOG(ERROR
) << "Unexpected HTTP " << response_code
456 << " from " << config
->http_resource
<< ".";
460 // Release the old reference to the device and try to open a new one.
461 int retries
= kReconnectRetries
;
464 if (FindClaimed() && device_
->product_id() == config
->product_id
) {
467 if (--retries
== 0) {
468 LOG(ERROR
) << "Failed to find updated device.";
471 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
473 VLOG(1) << "It took " << (kReconnectRetries
- retries
)
474 << " retries to find the updated device.";
479 bool UsbTestGadgetImpl::Disconnect() {
480 const GURL
url("http://" + device_address_
+ "/disconnect");
481 const int response_code
= SimplePOSTRequest(url
, "");
483 if (response_code
!= 200) {
484 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /disconnect.";
488 // Release the old reference to the device and wait until it can't be found.
489 int retries
= kDisconnectRetries
;
492 if (!FindClaimed()) {
495 if (--retries
== 0) {
496 LOG(ERROR
) << "Device did not disconnect.";
499 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
501 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
502 << " retries for the device to disconnect.";
507 bool UsbTestGadgetImpl::Reconnect() {
508 const GURL
url("http://" + device_address_
+ "/reconnect");
509 const int response_code
= SimplePOSTRequest(url
, "");
511 if (response_code
!= 200) {
512 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /reconnect.";
516 int retries
= kDisconnectRetries
;
521 if (--retries
== 0) {
522 LOG(ERROR
) << "Device did not reconnect.";
525 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod
));
527 VLOG(1) << "It took " << (kDisconnectRetries
- retries
)
528 << " retries for the device to reconnect.";
533 } // namespace device