Roll src/third_party/skia 57a48a7:de7665a
[chromium-blink-merge.git] / device / test / usb_test_gadget_impl.cc
blobd9371b4767b56b744c9d33f902aa44e8f10ea4b7
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"
7 #include <string>
8 #include <vector>
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"
34 #include "url/gurl.h"
36 using ::base::PlatformThread;
37 using ::base::TimeDelta;
39 namespace device {
41 namespace {
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);
56 run_loop.Run();
59 struct UsbTestGadgetConfiguration {
60 UsbTestGadget::Type type;
61 const char* http_resource;
62 uint16 product_id;
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 {
74 public:
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;
84 protected:
85 UsbTestGadgetImpl();
87 private:
88 scoped_ptr<net::URLFetcher> CreateURLFetcher(
89 const GURL& url,
90 net::URLFetcher::RequestType request_type,
91 net::URLFetcherDelegate* delegate);
92 int SimplePOSTRequest(const GURL& url, const std::string& form_data);
93 bool FindUnclaimed();
94 bool GetVersion(std::string* version);
95 bool Update();
96 bool FindClaimed();
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 {
102 public:
103 Delegate() {}
104 ~Delegate() override {}
106 void WaitForCompletion() {
107 run_loop_.Run();
110 void OnURLFetchComplete(const net::URLFetcher* source) override {
111 run_loop_.Quit();
114 private:
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);
131 } // namespace
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()) {
169 Unclaim();
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()));
190 return url_fetcher;
193 int UsbTestGadgetImpl::SimplePOSTRequest(const GURL& url,
194 const std::string& form_data) {
195 Delegate delegate;
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)) {
216 continue;
219 const std::string serial = base::UTF16ToUTF8(serial_utf16);
220 const GURL url("http://" + serial + "/claim");
221 const std::string form_data = base::StringPrintf(
222 "session_id=%s",
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;
228 device_ = device;
229 break;
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;
240 std::string version;
241 if (!ReadLocalVersion(&local_version) ||
242 !GetVersion(&version)) {
243 return false;
246 if (version == local_version) {
247 return true;
250 return Update();
253 bool UsbTestGadgetImpl::GetVersion(std::string* version) {
254 Delegate delegate;
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.";
265 return false;
268 STLClearObject(version);
269 if (!url_fetcher->GetResponseAsString(version)) {
270 VLOG(2) << "Failed to read body from /version.";
271 return false;
273 return true;
276 bool UsbTestGadgetImpl::Update() {
277 std::string version;
278 if (!ReadLocalVersion(&version)) {
279 return false;
281 LOG(INFO) << "Updating " << device_address_ << " to " << version << "...";
283 Delegate delegate;
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 =
289 base::StringPrintf(
290 "--foo\r\n"
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");
297 std::string package;
298 if (!ReadLocalPackage(&package)) {
299 return false;
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.";
310 return false;
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.";
318 return false;
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.
326 device_ = NULL;
327 retries = kReconnectRetries;
328 while (!FindClaimed()) {
329 if (--retries == 0) {
330 LOG(ERROR) << "Failed to find updated device.";
331 return false;
333 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod));
335 VLOG(1) << "It took " << (kReconnectRetries - retries)
336 << " retries to find the updated device.";
338 return true;
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();
355 bool found = false;
356 for (size_t i = 0; i < arraysize(kConfigurations); ++i) {
357 if (product_id == kConfigurations[i].product_id) {
358 found = true;
359 break;
362 if (!found) {
363 continue;
366 base::string16 serial_utf16;
367 if (!device->GetSerialNumber(&serial_utf16)) {
368 continue;
371 std::string serial = base::UTF16ToUTF8(serial_utf16);
372 if (serial != expected_serial) {
373 continue;
376 device_ = device;
377 return true;
381 return false;
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());
406 return false;
409 STLClearObject(content);
410 int rv;
411 do {
412 char buf[4096];
413 rv = file.ReadAtCurrentPos(buf, sizeof buf);
414 if (rv == -1) {
415 LOG(ERROR) << "Cannot read " << file_path.MaybeAsASCII() << ": "
416 << base::File::ErrorToString(file.error_details());
417 return false;
419 content->append(buf, rv);
420 } while (rv > 0);
422 return true;
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.";
433 return false;
436 device_address_.clear();
437 return true;
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];
447 CHECK(config);
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 << ".";
455 return false;
458 // Release the old reference to the device and try to open a new one.
459 int retries = kReconnectRetries;
460 while (true) {
461 device_ = NULL;
462 if (FindClaimed() && device_->product_id() == config->product_id) {
463 break;
465 if (--retries == 0) {
466 LOG(ERROR) << "Failed to find updated device.";
467 return false;
469 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod));
471 VLOG(1) << "It took " << (kReconnectRetries - retries)
472 << " retries to find the updated device.";
474 return true;
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.";
483 return false;
486 // Release the old reference to the device and wait until it can't be found.
487 int retries = kDisconnectRetries;
488 while (true) {
489 device_ = NULL;
490 if (!FindClaimed()) {
491 break;
493 if (--retries == 0) {
494 LOG(ERROR) << "Device did not disconnect.";
495 return false;
497 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod));
499 VLOG(1) << "It took " << (kDisconnectRetries - retries)
500 << " retries for the device to disconnect.";
502 return true;
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.";
511 return false;
514 int retries = kDisconnectRetries;
515 while (true) {
516 if (FindClaimed()) {
517 break;
519 if (--retries == 0) {
520 LOG(ERROR) << "Device did not reconnect.";
521 return false;
523 SleepWithRunLoop(TimeDelta::FromMilliseconds(kRetryPeriod));
525 VLOG(1) << "It took " << (kDisconnectRetries - retries)
526 << " retries for the device to reconnect.";
528 return true;
531 } // namespace device