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.
4
5 #include "device/test/usb_test_gadget.h"
6
7 #include <string>
8 #include <vector>
9
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"
35 #include "url/gurl.h"
36
37 using ::base::PlatformThread;
38 using ::base::TimeDelta;
39
40 namespace device {
41
42 namespace {
43
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
50
51 struct UsbTestGadgetConfiguration {
52 UsbTestGadget::Type type;
53 const char* http_resource;
54 uint16 product_id;
55 };
56
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},
63 };
64
65 class UsbTestGadgetImpl : public UsbTestGadget {
66 public:
67 virtual ~UsbTestGadgetImpl();
68
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 GetSerialNumber() const OVERRIDE;
75
76 protected:
77 UsbTestGadgetImpl();
78
79 private:
80 scoped_ptr<net::URLFetcher> CreateURLFetcher(
81 const GURL& url,
82 net::URLFetcher::RequestType request_type,
83 net::URLFetcherDelegate* delegate);
84 int SimplePOSTRequest(const GURL& url, const std::string& form_data);
85 bool FindUnclaimed();
86 bool GetVersion(std::string* version);
87 bool Update();
88 bool FindClaimed();
89 bool ReadLocalVersion(std::string* version);
90 bool ReadLocalPackage(std::string* package);
91 bool ReadFile(const base::FilePath& file_path, std::string* content);
92
93 class Delegate : public net::URLFetcherDelegate {
94 public:
Delegate()95 Delegate() {}
~Delegate()96 virtual ~Delegate() {}
97
WaitForCompletion()98 void WaitForCompletion() {
99 run_loop_.Run();
100 }
101
OnURLFetchComplete(const net::URLFetcher * source)102 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
103 run_loop_.Quit();
104 }
105
106 private:
107 base::RunLoop run_loop_;
108
109 DISALLOW_COPY_AND_ASSIGN(Delegate);
110 };
111
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_;
117
118 friend class UsbTestGadget;
119
120 DISALLOW_COPY_AND_ASSIGN(UsbTestGadgetImpl);
121 };
122
123 } // namespace
124
IsTestEnabled()125 bool UsbTestGadget::IsTestEnabled() {
126 base::CommandLine* command_line = CommandLine::ForCurrentProcess();
127 return command_line->HasSwitch(kCommandLineSwitch);
128 }
129
Claim()130 scoped_ptr<UsbTestGadget> UsbTestGadget::Claim() {
131 scoped_ptr<UsbTestGadgetImpl> gadget(new UsbTestGadgetImpl);
132
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>();
138 }
139 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
140 }
141 VLOG(1) << "It took " << (kClaimRetries - retries)
142 << " retries to find an unclaimed device.";
143
144 return gadget.PassAs<UsbTestGadget>();
145 }
146
UsbTestGadgetImpl()147 UsbTestGadgetImpl::UsbTestGadgetImpl() {
148 net::URLRequestContextBuilder context_builder;
149 context_builder.set_proxy_service(net::ProxyService::CreateDirect());
150 request_context_.reset(context_builder.Build());
151
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);
155
156 usb_service_ = UsbService::GetInstance(NULL);
157 }
158
~UsbTestGadgetImpl()159 UsbTestGadgetImpl::~UsbTestGadgetImpl() {
160 if (!device_address_.empty()) {
161 Unclaim();
162 }
163 }
164
GetDevice() const165 UsbDevice* UsbTestGadgetImpl::GetDevice() const {
166 return device_.get();
167 }
168
GetSerialNumber() const169 std::string UsbTestGadgetImpl::GetSerialNumber() const {
170 return device_address_;
171 }
172
CreateURLFetcher(const GURL & url,net::URLFetcher::RequestType request_type,net::URLFetcherDelegate * delegate)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));
178
179 url_fetcher->SetRequestContext(
180 new net::TrivialURLRequestContextGetter(
181 request_context_.get(),
182 base::MessageLoop::current()->message_loop_proxy()));
183
184 return url_fetcher.PassAs<net::URLFetcher>();
185 }
186
SimplePOSTRequest(const GURL & url,const std::string & form_data)187 int UsbTestGadgetImpl::SimplePOSTRequest(const GURL& url,
188 const std::string& form_data) {
189 Delegate delegate;
190 scoped_ptr<net::URLFetcher> url_fetcher =
191 CreateURLFetcher(url, net::URLFetcher::POST, &delegate);
192
193 url_fetcher->SetUploadData("application/x-www-form-urlencoded", form_data);
194 url_fetcher->Start();
195 delegate.WaitForCompletion();
196
197 return url_fetcher->GetResponseCode();
198 }
199
FindUnclaimed()200 bool UsbTestGadgetImpl::FindUnclaimed() {
201 std::vector<scoped_refptr<UsbDevice> > devices;
202 usb_service_->GetDevices(&devices);
203
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)) {
210 continue;
211 }
212
213 const std::string serial = base::UTF16ToUTF8(serial_utf16);
214 const GURL url("http://" + serial + "/claim");
215 const std::string form_data = base::StringPrintf(
216 "session_id=%s",
217 net::EscapeUrlEncodedData(session_id_, true).c_str());
218 const int response_code = SimplePOSTRequest(url, form_data);
219
220 if (response_code == 200) {
221 device_address_ = serial;
222 device_ = device;
223 break;
224 }
225
226 // The device is probably claimed by another process.
227 if (response_code != 403) {
228 LOG(WARNING) << "Unexpected HTTP " << response_code << " from /claim.";
229 }
230 }
231 }
232
233 std::string local_version;
234 std::string version;
235 if (!ReadLocalVersion(&local_version) ||
236 !GetVersion(&version)) {
237 return false;
238 }
239
240 if (version == local_version) {
241 return true;
242 }
243
244 return Update();
245 }
246
GetVersion(std::string * version)247 bool UsbTestGadgetImpl::GetVersion(std::string* version) {
248 Delegate delegate;
249 const GURL url("http://" + device_address_ + "/version");
250 scoped_ptr<net::URLFetcher> url_fetcher =
251 CreateURLFetcher(url, net::URLFetcher::GET, &delegate);
252
253 url_fetcher->Start();
254 delegate.WaitForCompletion();
255
256 const int response_code = url_fetcher->GetResponseCode();
257 if (response_code != 200) {
258 VLOG(2) << "Unexpected HTTP " << response_code << " from /version.";
259 return false;
260 }
261
262 STLClearObject(version);
263 if (!url_fetcher->GetResponseAsString(version)) {
264 VLOG(2) << "Failed to read body from /version.";
265 return false;
266 }
267 return true;
268 }
269
Update()270 bool UsbTestGadgetImpl::Update() {
271 std::string version;
272 if (!ReadLocalVersion(&version)) {
273 return false;
274 }
275 LOG(INFO) << "Updating " << device_address_ << " to " << version << "...";
276
277 Delegate delegate;
278 const GURL url("http://" + device_address_ + "/update");
279 scoped_ptr<net::URLFetcher> url_fetcher =
280 CreateURLFetcher(url, net::URLFetcher::POST, &delegate);
281
282 const std::string mime_header =
283 base::StringPrintf(
284 "--foo\r\n"
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");
290
291 std::string package;
292 if (!ReadLocalPackage(&package)) {
293 return false;
294 }
295
296 url_fetcher->SetUploadData("multipart/form-data; boundary=foo",
297 mime_header + package + mime_footer);
298 url_fetcher->Start();
299 delegate.WaitForCompletion();
300
301 const int response_code = url_fetcher->GetResponseCode();
302 if (response_code != 200) {
303 LOG(ERROR) << "Unexpected HTTP " << response_code << " from /update.";
304 return false;
305 }
306
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.";
312 return false;
313 }
314 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
315 }
316 VLOG(1) << "It took " << (kUpdateRetries - retries)
317 << " retries to see the new version.";
318
319 // Release the old reference to the device and try to open a new one.
320 device_ = NULL;
321 retries = kReconnectRetries;
322 while (!FindClaimed()) {
323 if (--retries == 0) {
324 LOG(ERROR) << "Failed to find updated device.";
325 return false;
326 }
327 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
328 }
329 VLOG(1) << "It took " << (kReconnectRetries - retries)
330 << " retries to find the updated device.";
331
332 return true;
333 }
334
FindClaimed()335 bool UsbTestGadgetImpl::FindClaimed() {
336 CHECK(!device_.get());
337
338 std::string expected_serial = GetSerialNumber();
339
340 std::vector<scoped_refptr<UsbDevice> > devices;
341 usb_service_->GetDevices(&devices);
342
343 for (std::vector<scoped_refptr<UsbDevice> >::iterator iter =
344 devices.begin(); iter != devices.end(); ++iter) {
345 scoped_refptr<UsbDevice> &device = *iter;
346
347 if (device->vendor_id() == 0x18D1) {
348 const uint16 product_id = device->product_id();
349 bool found = false;
350 for (size_t i = 0; i < arraysize(kConfigurations); ++i) {
351 if (product_id == kConfigurations[i].product_id) {
352 found = true;
353 break;
354 }
355 }
356 if (!found) {
357 continue;
358 }
359
360 base::string16 serial_utf16;
361 if (!device->GetSerialNumber(&serial_utf16)) {
362 continue;
363 }
364
365 std::string serial = base::UTF16ToUTF8(serial_utf16);
366 if (serial != expected_serial) {
367 continue;
368 }
369
370 device_ = device;
371 return true;
372 }
373 }
374
375 return false;
376 }
377
ReadLocalVersion(std::string * version)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");
382
383 return ReadFile(file_path, version);
384 }
385
ReadLocalPackage(std::string * package)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");
390
391 return ReadFile(file_path, package);
392 }
393
ReadFile(const base::FilePath & file_path,std::string * content)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());
400 return false;
401 }
402
403 STLClearObject(content);
404 int rv;
405 do {
406 char buf[4096];
407 rv = file.ReadAtCurrentPos(buf, sizeof buf);
408 if (rv == -1) {
409 LOG(ERROR) << "Cannot read " << file_path.MaybeAsASCII() << ": "
410 << base::File::ErrorToString(file.error_details());
411 return false;
412 }
413 content->append(buf, rv);
414 } while (rv > 0);
415
416 return true;
417 }
418
Unclaim()419 bool UsbTestGadgetImpl::Unclaim() {
420 VLOG(1) << "Releasing the device at " << device_address_ << ".";
421
422 const GURL url("http://" + device_address_ + "/unclaim");
423 const int response_code = SimplePOSTRequest(url, "");
424
425 if (response_code != 200) {
426 LOG(ERROR) << "Unexpected HTTP " << response_code << " from /unclaim.";
427 return false;
428 }
429 return true;
430 }
431
SetType(Type type)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];
437 }
438 }
439 CHECK(config);
440
441 const GURL url("http://" + device_address_ + config->http_resource);
442 const int response_code = SimplePOSTRequest(url, "");
443
444 if (response_code != 200) {
445 LOG(ERROR) << "Unexpected HTTP " << response_code
446 << " from " << config->http_resource << ".";
447 return false;
448 }
449
450 // Release the old reference to the device and try to open a new one.
451 int retries = kReconnectRetries;
452 while (true) {
453 device_ = NULL;
454 if (FindClaimed() && device_->product_id() == config->product_id) {
455 break;
456 }
457 if (--retries == 0) {
458 LOG(ERROR) << "Failed to find updated device.";
459 return false;
460 }
461 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
462 }
463 VLOG(1) << "It took " << (kReconnectRetries - retries)
464 << " retries to find the updated device.";
465
466 return true;
467 }
468
Disconnect()469 bool UsbTestGadgetImpl::Disconnect() {
470 const GURL url("http://" + device_address_ + "/disconnect");
471 const int response_code = SimplePOSTRequest(url, "");
472
473 if (response_code != 200) {
474 LOG(ERROR) << "Unexpected HTTP " << response_code << " from /disconnect.";
475 return false;
476 }
477
478 // Release the old reference to the device and wait until it can't be found.
479 int retries = kDisconnectRetries;
480 while (true) {
481 device_ = NULL;
482 if (!FindClaimed()) {
483 break;
484 }
485 if (--retries == 0) {
486 LOG(ERROR) << "Device did not disconnect.";
487 return false;
488 }
489 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
490 }
491 VLOG(1) << "It took " << (kDisconnectRetries - retries)
492 << " retries for the device to disconnect.";
493
494 return true;
495 }
496
Reconnect()497 bool UsbTestGadgetImpl::Reconnect() {
498 const GURL url("http://" + device_address_ + "/reconnect");
499 const int response_code = SimplePOSTRequest(url, "");
500
501 if (response_code != 200) {
502 LOG(ERROR) << "Unexpected HTTP " << response_code << " from /reconnect.";
503 return false;
504 }
505
506 int retries = kDisconnectRetries;
507 while (true) {
508 if (FindClaimed()) {
509 break;
510 }
511 if (--retries == 0) {
512 LOG(ERROR) << "Device did not reconnect.";
513 return false;
514 }
515 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
516 }
517 VLOG(1) << "It took " << (kDisconnectRetries - retries)
518 << " retries for the device to reconnect.";
519
520 return true;
521 }
522
523 } // namespace device
524