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/scoped_observer.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/thread_task_runner_handle.h"
25 #include "base/time/time.h"
26 #include "device/core/device_client.h"
27 #include "device/usb/usb_device.h"
28 #include "device/usb/usb_device_handle.h"
29 #include "device/usb/usb_service.h"
30 #include "net/proxy/proxy_service.h"
31 #include "net/url_request/url_fetcher.h"
32 #include "net/url_request/url_fetcher_delegate.h"
33 #include "net/url_request/url_request_context.h"
34 #include "net/url_request/url_request_context_builder.h"
35 #include "net/url_request/url_request_context_getter.h"
40 class UsbTestGadgetImpl
: public UsbTestGadget
{
43 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter
,
44 UsbService
* usb_service
,
45 scoped_refptr
<UsbDevice
> device
);
46 ~UsbTestGadgetImpl() override
;
48 bool Unclaim() override
;
49 bool Disconnect() override
;
50 bool Reconnect() override
;
51 bool SetType(Type type
) override
;
52 UsbDevice
* GetDevice() const override
;
55 std::string device_address_
;
56 scoped_refptr
<UsbDevice
> device_
;
57 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter_
;
58 UsbService
* usb_service_
;
60 DISALLOW_COPY_AND_ASSIGN(UsbTestGadgetImpl
);
65 static const char kCommandLineSwitch
[] = "enable-gadget-tests";
66 static const int kReenumeratePeriod
= 100; // 0.1 seconds
68 struct UsbTestGadgetConfiguration
{
69 UsbTestGadget::Type type
;
70 const char* http_resource
;
74 static const struct UsbTestGadgetConfiguration kConfigurations
[] = {
75 {UsbTestGadget::DEFAULT
, "/unconfigure", 0x58F0},
76 {UsbTestGadget::KEYBOARD
, "/keyboard/configure", 0x58F1},
77 {UsbTestGadget::MOUSE
, "/mouse/configure", 0x58F2},
78 {UsbTestGadget::HID_ECHO
, "/hid_echo/configure", 0x58F3},
79 {UsbTestGadget::ECHO
, "/echo/configure", 0x58F4},
82 bool ReadFile(const base::FilePath
& file_path
, std::string
* content
) {
83 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
84 if (!file
.IsValid()) {
85 LOG(ERROR
) << "Cannot open " << file_path
.MaybeAsASCII() << ": "
86 << base::File::ErrorToString(file
.error_details());
90 STLClearObject(content
);
94 rv
= file
.ReadAtCurrentPos(buf
, sizeof buf
);
96 LOG(ERROR
) << "Cannot read " << file_path
.MaybeAsASCII() << ": "
97 << base::File::ErrorToString(file
.error_details());
100 content
->append(buf
, rv
);
106 bool ReadLocalVersion(std::string
* version
) {
107 base::FilePath file_path
;
108 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
109 file_path
= file_path
.AppendASCII("usb_gadget.zip.md5");
111 return ReadFile(file_path
, version
);
114 bool ReadLocalPackage(std::string
* package
) {
115 base::FilePath file_path
;
116 CHECK(PathService::Get(base::DIR_EXE
, &file_path
));
117 file_path
= file_path
.AppendASCII("usb_gadget.zip");
119 return ReadFile(file_path
, package
);
122 scoped_ptr
<net::URLFetcher
> CreateURLFetcher(
123 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter
,
125 net::URLFetcher::RequestType request_type
,
126 net::URLFetcherDelegate
* delegate
) {
127 scoped_ptr
<net::URLFetcher
> url_fetcher
=
128 net::URLFetcher::Create(url
, request_type
, delegate
);
130 url_fetcher
->SetRequestContext(request_context_getter
.get());
135 class URLRequestContextGetter
: public net::URLRequestContextGetter
{
137 URLRequestContextGetter(
138 scoped_refptr
<base::SingleThreadTaskRunner
> network_task_runner
)
139 : network_task_runner_(network_task_runner
) {}
142 ~URLRequestContextGetter() override
{}
144 // net::URLRequestContextGetter implementation
145 net::URLRequestContext
* GetURLRequestContext() override
{
147 net::URLRequestContextBuilder context_builder
;
148 context_builder
.set_proxy_service(
149 make_scoped_ptr(net::ProxyService::CreateDirect()));
150 context_
= context_builder
.Build().Pass();
152 return context_
.get();
155 scoped_refptr
<base::SingleThreadTaskRunner
> GetNetworkTaskRunner()
157 return network_task_runner_
;
160 scoped_ptr
<net::URLRequestContext
> context_
;
161 scoped_refptr
<base::SingleThreadTaskRunner
> network_task_runner_
;
164 class URLFetcherDelegate
: public net::URLFetcherDelegate
{
166 URLFetcherDelegate() {}
167 ~URLFetcherDelegate() override
{}
169 void WaitForCompletion() { run_loop_
.Run(); }
171 void OnURLFetchComplete(const net::URLFetcher
* source
) override
{
176 base::RunLoop run_loop_
;
178 DISALLOW_COPY_AND_ASSIGN(URLFetcherDelegate
);
181 int SimplePOSTRequest(
182 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter
,
184 const std::string
& form_data
) {
185 URLFetcherDelegate delegate
;
186 scoped_ptr
<net::URLFetcher
> url_fetcher
= CreateURLFetcher(
187 request_context_getter
, url
, net::URLFetcher::POST
, &delegate
);
189 url_fetcher
->SetUploadData("application/x-www-form-urlencoded", form_data
);
190 url_fetcher
->Start();
191 delegate
.WaitForCompletion();
193 return url_fetcher
->GetResponseCode();
196 class UsbGadgetFactory
: public UsbService::Observer
,
197 public net::URLFetcherDelegate
{
199 UsbGadgetFactory(scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
)
200 : observer_(this), weak_factory_(this) {
201 usb_service_
= DeviceClient::Get()->GetUsbService();
202 request_context_getter_
= new URLRequestContextGetter(io_task_runner
);
204 static uint32 next_session_id
;
205 base::ProcessId process_id
= base::GetCurrentProcId();
206 session_id_
= base::StringPrintf("%d-%d", process_id
, next_session_id
++);
208 observer_
.Add(usb_service_
);
211 ~UsbGadgetFactory() override
{}
213 scoped_ptr
<UsbTestGadget
> WaitForDevice() {
216 return make_scoped_ptr(
217 new UsbTestGadgetImpl(request_context_getter_
, usb_service_
, device_
));
221 void EnumerateDevices() {
223 usb_service_
->GetDevices(base::Bind(
224 &UsbGadgetFactory::OnDevicesEnumerated
, weak_factory_
.GetWeakPtr()));
228 void OnDevicesEnumerated(
229 const std::vector
<scoped_refptr
<UsbDevice
>>& devices
) {
230 for (const scoped_refptr
<UsbDevice
>& device
: devices
) {
231 OnDeviceAdded(device
);
235 // TODO(reillyg): This timer could be replaced by a way to use long-
236 // polling to wait for claimed devices to become unclaimed.
237 base::MessageLoop::current()->PostDelayedTask(
238 FROM_HERE
, base::Bind(&UsbGadgetFactory::EnumerateDevices
,
239 weak_factory_
.GetWeakPtr()),
240 base::TimeDelta::FromMilliseconds(kReenumeratePeriod
));
244 void OnDeviceAdded(scoped_refptr
<UsbDevice
> device
) override
{
246 // Already trying to claim a device.
250 if (device
->vendor_id() != 0x18D1 || device
->product_id() != 0x58F0 ||
251 device
->serial_number().empty()) {
255 std::string serial_number
= base::UTF16ToUTF8(device
->serial_number());
256 if (serial_number
== serial_number_
) {
257 // We were waiting for the device to reappear after upgrade.
264 serial_number_
= serial_number
;
269 VLOG(1) << "Trying to claim " << serial_number_
<< ".";
271 GURL
url("http://" + serial_number_
+ "/claim");
272 std::string form_data
= base::StringPrintf(
273 "session_id=%s", net::EscapeUrlEncodedData(session_id_
, true).c_str());
274 url_fetcher_
= CreateURLFetcher(request_context_getter_
, url
,
275 net::URLFetcher::POST
, this);
276 url_fetcher_
->SetUploadData("application/x-www-form-urlencoded", form_data
);
277 url_fetcher_
->Start();
281 GURL
url("http://" + serial_number_
+ "/version");
282 url_fetcher_
= CreateURLFetcher(request_context_getter_
, url
,
283 net::URLFetcher::GET
, this);
284 url_fetcher_
->Start();
287 bool Update(const std::string
& version
) {
288 LOG(INFO
) << "Updating " << serial_number_
<< " to " << version
<< "...";
290 GURL
url("http://" + serial_number_
+ "/update");
291 url_fetcher_
= CreateURLFetcher(request_context_getter_
, url
,
292 net::URLFetcher::POST
, this);
293 std::string mime_header
= base::StringPrintf(
295 "Content-Disposition: form-data; name=\"file\"; "
296 "filename=\"usb_gadget-%s.zip\"\r\n"
297 "Content-Type: application/octet-stream\r\n"
300 std::string
mime_footer("\r\n--foo--\r\n");
303 if (!ReadLocalPackage(&package
)) {
307 url_fetcher_
->SetUploadData("multipart/form-data; boundary=foo",
308 mime_header
+ package
+ mime_footer
);
309 url_fetcher_
->Start();
314 void OnURLFetchComplete(const net::URLFetcher
* source
) override
{
315 DCHECK(!serial_number_
.empty());
317 int response_code
= source
->GetResponseCode();
319 // Just completed a /claim request.
320 if (response_code
== 200) {
324 if (response_code
!= 403) {
325 LOG(WARNING
) << "Unexpected HTTP " << response_code
330 } else if (version_
.empty()) {
331 // Just completed a /version request.
332 if (response_code
!= 200) {
333 LOG(WARNING
) << "Unexpected HTTP " << response_code
334 << " from /version.";
339 if (!source
->GetResponseAsString(&version_
)) {
340 LOG(WARNING
) << "Failed to read body from /version.";
345 std::string local_version
;
346 if (!ReadLocalVersion(&local_version
)) {
351 if (version_
== local_version
) {
354 if (!Update(local_version
)) {
359 // Just completed an /update request.
360 if (response_code
!= 200) {
361 LOG(WARNING
) << "Unexpected HTTP " << response_code
<< " from /update.";
366 // Must wait for the device to reconnect.
372 serial_number_
.clear();
376 // Wait a bit and then try again to find an available device.
377 base::MessageLoop::current()->PostDelayedTask(
378 FROM_HERE
, base::Bind(&UsbGadgetFactory::EnumerateDevices
,
379 weak_factory_
.GetWeakPtr()),
380 base::TimeDelta::FromMilliseconds(kReenumeratePeriod
));
383 UsbService
* usb_service_
= nullptr;
384 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter_
;
385 std::string session_id_
;
386 scoped_ptr
<net::URLFetcher
> url_fetcher_
;
387 scoped_refptr
<UsbDevice
> device_
;
388 std::string serial_number_
;
389 bool claimed_
= false;
390 std::string version_
;
391 base::RunLoop run_loop_
;
392 ScopedObserver
<UsbService
, UsbService::Observer
> observer_
;
393 base::WeakPtrFactory
<UsbGadgetFactory
> weak_factory_
;
396 class DeviceAddListener
: public UsbService::Observer
{
398 DeviceAddListener(UsbService
* usb_service
,
399 const std::string
& serial_number
,
401 : usb_service_(usb_service
),
402 serial_number_(serial_number
),
403 product_id_(product_id
),
405 weak_factory_(this) {
406 observer_
.Add(usb_service_
);
408 ~DeviceAddListener() override
{}
410 scoped_refptr
<UsbDevice
> WaitForAdd() {
411 usb_service_
->GetDevices(base::Bind(&DeviceAddListener::OnDevicesEnumerated
,
412 weak_factory_
.GetWeakPtr()));
418 void OnDevicesEnumerated(
419 const std::vector
<scoped_refptr
<UsbDevice
>>& devices
) {
420 for (const scoped_refptr
<UsbDevice
>& device
: devices
) {
421 OnDeviceAdded(device
);
425 void OnDeviceAdded(scoped_refptr
<UsbDevice
> device
) override
{
426 if (device
->vendor_id() == 0x18D1 && !device
->serial_number().empty()) {
427 const uint16 product_id
= device
->product_id();
428 if (product_id_
== -1) {
430 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
431 if (product_id
== kConfigurations
[i
].product_id
) {
440 if (product_id_
!= product_id
) {
445 if (serial_number_
!= base::UTF16ToUTF8(device
->serial_number())) {
454 UsbService
* usb_service_
;
455 const std::string serial_number_
;
456 const int product_id_
;
457 base::RunLoop run_loop_
;
458 scoped_refptr
<UsbDevice
> device_
;
459 ScopedObserver
<UsbService
, UsbService::Observer
> observer_
;
460 base::WeakPtrFactory
<DeviceAddListener
> weak_factory_
;
462 DISALLOW_COPY_AND_ASSIGN(DeviceAddListener
);
465 class DeviceRemoveListener
: public UsbService::Observer
{
467 DeviceRemoveListener(UsbService
* usb_service
, scoped_refptr
<UsbDevice
> device
)
468 : usb_service_(usb_service
),
471 weak_factory_(this) {
472 observer_
.Add(usb_service_
);
474 ~DeviceRemoveListener() override
{}
476 void WaitForRemove() {
477 usb_service_
->GetDevices(
478 base::Bind(&DeviceRemoveListener::OnDevicesEnumerated
,
479 weak_factory_
.GetWeakPtr()));
484 void OnDevicesEnumerated(
485 const std::vector
<scoped_refptr
<UsbDevice
>>& devices
) {
487 for (const scoped_refptr
<UsbDevice
>& device
: devices
) {
488 if (device_
== device
) {
497 void OnDeviceRemoved(scoped_refptr
<UsbDevice
> device
) override
{
498 if (device_
== device
) {
503 UsbService
* usb_service_
;
504 base::RunLoop run_loop_
;
505 scoped_refptr
<UsbDevice
> device_
;
506 ScopedObserver
<UsbService
, UsbService::Observer
> observer_
;
507 base::WeakPtrFactory
<DeviceRemoveListener
> weak_factory_
;
509 DISALLOW_COPY_AND_ASSIGN(DeviceRemoveListener
);
514 bool UsbTestGadget::IsTestEnabled() {
515 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
516 return command_line
->HasSwitch(kCommandLineSwitch
);
519 scoped_ptr
<UsbTestGadget
> UsbTestGadget::Claim(
520 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
) {
521 UsbGadgetFactory
gadget_factory(io_task_runner
);
522 return gadget_factory
.WaitForDevice().Pass();
525 UsbTestGadgetImpl::UsbTestGadgetImpl(
526 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter_
,
527 UsbService
* usb_service
,
528 scoped_refptr
<UsbDevice
> device
)
529 : device_address_(base::UTF16ToUTF8(device
->serial_number())),
531 request_context_getter_(request_context_getter_
),
532 usb_service_(usb_service
) {
535 UsbTestGadgetImpl::~UsbTestGadgetImpl() {
536 if (!device_address_
.empty()) {
541 UsbDevice
* UsbTestGadgetImpl::GetDevice() const {
542 return device_
.get();
545 bool UsbTestGadgetImpl::Unclaim() {
546 VLOG(1) << "Releasing the device at " << device_address_
<< ".";
548 GURL
url("http://" + device_address_
+ "/unclaim");
549 int response_code
= SimplePOSTRequest(request_context_getter_
, url
, "");
551 if (response_code
!= 200) {
552 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from /unclaim.";
556 device_address_
.clear();
560 bool UsbTestGadgetImpl::SetType(Type type
) {
561 const struct UsbTestGadgetConfiguration
* config
= NULL
;
562 for (size_t i
= 0; i
< arraysize(kConfigurations
); ++i
) {
563 if (kConfigurations
[i
].type
== type
) {
564 config
= &kConfigurations
[i
];
569 GURL
url("http://" + device_address_
+ config
->http_resource
);
570 int response_code
= SimplePOSTRequest(request_context_getter_
, url
, "");
572 if (response_code
!= 200) {
573 LOG(ERROR
) << "Unexpected HTTP " << response_code
574 << " from " << config
->http_resource
<< ".";
578 // Release the old reference to the device and try to open a new one.
579 DeviceAddListener
add_listener(usb_service_
, device_address_
,
581 device_
= add_listener
.WaitForAdd();
582 DCHECK(device_
.get());
586 bool UsbTestGadgetImpl::Disconnect() {
587 GURL
url("http://" + device_address_
+ "/disconnect");
588 int response_code
= SimplePOSTRequest(request_context_getter_
, url
, "");
590 if (response_code
!= 200) {
591 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from " << url
<< ".";
595 // Release the old reference to the device and wait until it can't be found.
596 DeviceRemoveListener
remove_listener(usb_service_
, device_
);
597 remove_listener
.WaitForRemove();
602 bool UsbTestGadgetImpl::Reconnect() {
603 GURL
url("http://" + device_address_
+ "/reconnect");
604 int response_code
= SimplePOSTRequest(request_context_getter_
, url
, "");
606 if (response_code
!= 200) {
607 LOG(ERROR
) << "Unexpected HTTP " << response_code
<< " from " << url
<< ".";
611 DeviceAddListener
add_listener(usb_service_
, device_address_
, -1);
612 device_
= add_listener
.WaitForAdd();
613 DCHECK(device_
.get());
617 } // namespace device