Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / device / test / usb_test_gadget_impl.cc
blob59a5faa686dfcad13929d1c499aa50304c945947
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.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"
35 #include "url/gurl.h"
37 using ::base::PlatformThread;
38 using ::base::TimeDelta;
39 using ::usb_service::UsbDevice;
40 using ::usb_service::UsbDeviceHandle;
41 using ::usb_service::UsbService;
43 namespace device {
45 namespace {
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;
57 uint16 product_id;
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 {
68 public:
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;
78 protected:
79 UsbTestGadgetImpl();
81 private:
82 scoped_ptr<net::URLFetcher> CreateURLFetcher(
83 const GURL& url,
84 net::URLFetcher::RequestType request_type,
85 net::URLFetcherDelegate* delegate);
86 int SimplePOSTRequest(const GURL& url, const std::string& form_data);
87 bool FindUnclaimed();
88 bool GetVersion(std::string* version);
89 bool Update();
90 bool FindClaimed();
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 {
96 public:
97 Delegate() {}
98 virtual ~Delegate() {}
100 void WaitForCompletion() {
101 run_loop_.Run();
104 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
105 run_loop_.Quit();
108 private:
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);
125 } // namespace
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()) {
163 Unclaim();
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) {
191 Delegate delegate;
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) {
212 continue;
215 base::string16 serial_utf16;
216 if (!handle->GetSerial(&serial_utf16)) {
217 continue;
220 const std::string serial = base::UTF16ToUTF8(serial_utf16);
221 const GURL url("http://" + serial + "/claim");
222 const std::string form_data = base::StringPrintf(
223 "session_id=%s",
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;
229 device_ = device;
230 break;
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;
241 std::string version;
242 if (!ReadLocalVersion(&local_version) ||
243 !GetVersion(&version)) {
244 return false;
247 if (version == local_version) {
248 return true;
251 return Update();
254 bool UsbTestGadgetImpl::GetVersion(std::string* version) {
255 Delegate delegate;
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.";
266 return false;
269 STLClearObject(version);
270 if (!url_fetcher->GetResponseAsString(version)) {
271 VLOG(2) << "Failed to read body from /version.";
272 return false;
274 return true;
277 bool UsbTestGadgetImpl::Update() {
278 std::string version;
279 if (!ReadLocalVersion(&version)) {
280 return false;
282 LOG(INFO) << "Updating " << device_address_ << " to " << version << "...";
284 Delegate delegate;
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 =
290 base::StringPrintf(
291 "--foo\r\n"
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");
298 std::string package;
299 if (!ReadLocalPackage(&package)) {
300 return false;
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.";
311 return false;
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.";
319 return false;
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.
327 device_ = NULL;
328 retries = kReconnectRetries;
329 while (!FindClaimed()) {
330 if (--retries == 0) {
331 LOG(ERROR) << "Failed to find updated device.";
332 return false;
334 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
336 VLOG(1) << "It took " << (kReconnectRetries - retries)
337 << " retries to find the updated device.";
339 return true;
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();
356 bool found = false;
357 for (size_t i = 0; i < arraysize(kConfigurations); ++i) {
358 if (product_id == kConfigurations[i].product_id) {
359 found = true;
360 break;
363 if (!found) {
364 continue;
367 scoped_refptr<UsbDeviceHandle> handle(device->Open());
368 if (handle.get() == NULL) {
369 continue;
372 base::string16 serial_utf16;
373 if (!handle->GetSerial(&serial_utf16)) {
374 continue;
377 std::string serial = base::UTF16ToUTF8(serial_utf16);
378 if (serial != expected_serial) {
379 continue;
382 device_ = device;
383 return true;
387 return false;
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());
412 return false;
415 STLClearObject(content);
416 int rv;
417 do {
418 char buf[4096];
419 rv = file.ReadAtCurrentPos(buf, sizeof buf);
420 if (rv == -1) {
421 LOG(ERROR) << "Cannot read " << file_path.MaybeAsASCII() << ": "
422 << base::File::ErrorToString(file.error_details());
423 return false;
425 content->append(buf, rv);
426 } while (rv > 0);
428 return true;
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.";
439 return false;
441 return true;
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];
451 CHECK(config);
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 << ".";
459 return false;
462 // Release the old reference to the device and try to open a new one.
463 int retries = kReconnectRetries;
464 while (true) {
465 device_ = NULL;
466 if (FindClaimed() && device_->product_id() == config->product_id) {
467 break;
469 if (--retries == 0) {
470 LOG(ERROR) << "Failed to find updated device.";
471 return false;
473 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
475 VLOG(1) << "It took " << (kReconnectRetries - retries)
476 << " retries to find the updated device.";
478 return true;
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.";
487 return false;
490 // Release the old reference to the device and wait until it can't be found.
491 int retries = kDisconnectRetries;
492 while (true) {
493 device_ = NULL;
494 if (!FindClaimed()) {
495 break;
497 if (--retries == 0) {
498 LOG(ERROR) << "Device did not disconnect.";
499 return false;
501 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
503 VLOG(1) << "It took " << (kDisconnectRetries - retries)
504 << " retries for the device to disconnect.";
506 return true;
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.";
515 return false;
518 int retries = kDisconnectRetries;
519 while (true) {
520 if (FindClaimed()) {
521 break;
523 if (--retries == 0) {
524 LOG(ERROR) << "Device did not reconnect.";
525 return false;
527 PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod));
529 VLOG(1) << "It took " << (kDisconnectRetries - retries)
530 << " retries for the device to reconnect.";
532 return true;
535 } // namespace device