diff options
author | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-30 03:20:54 +0000 |
---|---|---|
committer | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-30 03:20:54 +0000 |
commit | 18d3f7bcf292d7e7a4c5924254102faf3de5effc (patch) | |
tree | 9308aa7c36d86694c46b5bd9cede17db0b7ef519 /device/test | |
parent | 1e66770e8a5d12de9c3772204866a0675be6e065 (diff) | |
download | chromium_src-18d3f7bcf292d7e7a4c5924254102faf3de5effc.zip chromium_src-18d3f7bcf292d7e7a4c5924254102faf3de5effc.tar.gz chromium_src-18d3f7bcf292d7e7a4c5924254102faf3de5effc.tar.bz2 |
[usb_gadget p11] C++ test gadget bindings.
The device::UsbTestGadget class manages ownership of a USB test device
for the duration of a test. If necessary it will update the software on
the device to the bundle that the executable was built with.
BUG=396682
Review URL: https://codereview.chromium.org/420563007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286388 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'device/test')
-rw-r--r-- | device/test/DEPS | 3 | ||||
-rw-r--r-- | device/test/usb_test_gadget.h | 49 | ||||
-rw-r--r-- | device/test/usb_test_gadget_impl.cc | 534 |
3 files changed, 586 insertions, 0 deletions
diff --git a/device/test/DEPS b/device/test/DEPS index 26b3ad9b..9b323d8 100644 --- a/device/test/DEPS +++ b/device/test/DEPS @@ -1,3 +1,6 @@ include_rules = [ + "+components/usb_service", "+mojo/embedder", + "+net/proxy", + "+net/url_request", ] diff --git a/device/test/usb_test_gadget.h b/device/test/usb_test_gadget.h new file mode 100644 index 0000000..a959146 --- /dev/null +++ b/device/test/usb_test_gadget.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_TEST_USB_TEST_GADGET_H_ +#define DEVICE_TEST_USB_TEST_GADGET_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace usb_service { +class UsbDevice; +} // namespace usb_service + +namespace device { + +class UsbTestGadget { + public: + enum Type { + DEFAULT = 0, + KEYBOARD, + MOUSE, + }; + + virtual ~UsbTestGadget() {} + + static bool IsTestEnabled(); + static scoped_ptr<UsbTestGadget> Claim(); + + virtual bool Unclaim() = 0; + virtual bool Disconnect() = 0; + virtual bool Reconnect() = 0; + virtual bool SetType(Type type) = 0; + + virtual usb_service::UsbDevice* GetDevice() const = 0; + virtual std::string GetSerial() const = 0; + + protected: + UsbTestGadget() {} + + private: + DISALLOW_COPY_AND_ASSIGN(UsbTestGadget); +}; + +} // namespace device + +#endif // DEVICE_TEST_USB_TEST_GADGET_H_ diff --git a/device/test/usb_test_gadget_impl.cc b/device/test/usb_test_gadget_impl.cc new file mode 100644 index 0000000..65d37aa --- /dev/null +++ b/device/test/usb_test_gadget_impl.cc @@ -0,0 +1,534 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/test/usb_test_gadget.h" + +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/process/process.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "components/usb_service/usb_device.h" +#include "components/usb_service/usb_device_handle.h" +#include "components/usb_service/usb_service.h" +#include "net/proxy/proxy_service.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_builder.h" +#include "net/url_request/url_request_context_getter.h" +#include "url/gurl.h" + +using ::base::PlatformThread; +using ::base::TimeDelta; +using ::usb_service::UsbDevice; +using ::usb_service::UsbDeviceHandle; +using ::usb_service::UsbService; + +namespace device { + +namespace { + +static const char kCommandLineSwitch[] = "enable-gadget-tests"; +static const int kClaimRetries = 100; // 5 seconds +static const int kDisconnectRetries = 100; // 5 seconds +static const int kRetryPeriod = 50; // 0.05 seconds +static const int kReconnectRetries = 100; // 5 seconds +static const int kUpdateRetries = 100; // 5 seconds + +struct UsbTestGadgetConfiguration { + UsbTestGadget::Type type; + const char* http_resource; + uint16 product_id; +}; + +static const struct UsbTestGadgetConfiguration kConfigurations[] = { + { UsbTestGadget::DEFAULT, "/unconfigure", 0x2000 }, + { UsbTestGadget::KEYBOARD, "/keyboard/configure", 0x2001 }, + { UsbTestGadget::MOUSE, "/mouse/configure", 0x2002 }, +}; + +class UsbTestGadgetImpl : public UsbTestGadget { + public: + virtual ~UsbTestGadgetImpl(); + + virtual bool Unclaim() OVERRIDE; + virtual bool Disconnect() OVERRIDE; + virtual bool Reconnect() OVERRIDE; + virtual bool SetType(Type type) OVERRIDE; + virtual UsbDevice* GetDevice() const OVERRIDE; + virtual std::string GetSerial() const OVERRIDE; + + protected: + UsbTestGadgetImpl(); + + private: + scoped_ptr<net::URLFetcher> CreateURLFetcher( + const GURL& url, + net::URLFetcher::RequestType request_type, + net::URLFetcherDelegate* delegate); + int SimplePOSTRequest(const GURL& url, const std::string& form_data); + bool FindUnclaimed(); + bool GetVersion(std::string* version); + bool Update(); + bool FindClaimed(); + bool ReadLocalVersion(std::string* version); + bool ReadLocalPackage(std::string* package); + bool ReadFile(const base::FilePath& file_path, std::string* content); + + class Delegate : public net::URLFetcherDelegate { + public: + Delegate() {} + virtual ~Delegate() {} + + void WaitForCompletion() { + run_loop_.Run(); + } + + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE { + run_loop_.Quit(); + } + + private: + base::RunLoop run_loop_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + scoped_refptr<UsbDevice> device_; + std::string device_address_; + scoped_ptr<net::URLRequestContext> request_context_; + std::string session_id_; + UsbService* usb_service_; + + friend class UsbTestGadget; + + DISALLOW_COPY_AND_ASSIGN(UsbTestGadgetImpl); +}; + +} // namespace + +bool UsbTestGadget::IsTestEnabled() { + base::CommandLine* command_line = CommandLine::ForCurrentProcess(); + return command_line->HasSwitch(kCommandLineSwitch); +} + +scoped_ptr<UsbTestGadget> UsbTestGadget::Claim() { + scoped_ptr<UsbTestGadgetImpl> gadget(new UsbTestGadgetImpl); + + int retries = kClaimRetries; + while (!gadget->FindUnclaimed()) { + if (--retries == 0) { + LOG(ERROR) << "Failed to find an unclaimed device."; + return scoped_ptr<UsbTestGadget>(); + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod)); + } + VLOG(1) << "It took " << (kClaimRetries - retries) + << " retries to find an unclaimed device."; + + return gadget.PassAs<UsbTestGadget>(); +} + +UsbTestGadgetImpl::UsbTestGadgetImpl() { + net::URLRequestContextBuilder context_builder; + context_builder.set_proxy_service(net::ProxyService::CreateDirect()); + request_context_.reset(context_builder.Build()); + + base::ProcessId process_id = base::Process::Current().pid(); + session_id_ = base::StringPrintf( + "%s:%p", base::HexEncode(&process_id, sizeof(process_id)).c_str(), this); + + usb_service_ = UsbService::GetInstance(); +} + +UsbTestGadgetImpl::~UsbTestGadgetImpl() { + if (!device_address_.empty()) { + Unclaim(); + } +} + +UsbDevice* UsbTestGadgetImpl::GetDevice() const { + return device_.get(); +} + +std::string UsbTestGadgetImpl::GetSerial() const { + return device_address_; +} + +scoped_ptr<net::URLFetcher> UsbTestGadgetImpl::CreateURLFetcher( + const GURL& url, net::URLFetcher::RequestType request_type, + net::URLFetcherDelegate* delegate) { + scoped_ptr<net::URLFetcher> url_fetcher( + net::URLFetcher::Create(url, request_type, delegate)); + + url_fetcher->SetRequestContext( + new net::TrivialURLRequestContextGetter( + request_context_.get(), + base::MessageLoop::current()->message_loop_proxy())); + + return url_fetcher.PassAs<net::URLFetcher>(); +} + +int UsbTestGadgetImpl::SimplePOSTRequest(const GURL& url, + const std::string& form_data) { + Delegate delegate; + scoped_ptr<net::URLFetcher> url_fetcher = + CreateURLFetcher(url, net::URLFetcher::POST, &delegate); + + url_fetcher->SetUploadData("application/x-www-form-urlencoded", form_data); + url_fetcher->Start(); + delegate.WaitForCompletion(); + + return url_fetcher->GetResponseCode(); +} + +bool UsbTestGadgetImpl::FindUnclaimed() { + std::vector<scoped_refptr<UsbDevice> > devices; + usb_service_->GetDevices(&devices); + + for (std::vector<scoped_refptr<UsbDevice> >::const_iterator iter = + devices.begin(); iter != devices.end(); ++iter) { + const scoped_refptr<UsbDevice> &device = *iter; + if (device->vendor_id() == 0x18D1 && device->product_id() == 0x2000) { + scoped_refptr<UsbDeviceHandle> handle = device->Open(); + if (handle.get() == NULL) { + continue; + } + + base::string16 serial_utf16; + if (!handle->GetSerial(&serial_utf16)) { + continue; + } + + const std::string serial = base::UTF16ToUTF8(serial_utf16); + const GURL url("http://" + serial + "/claim"); + const std::string form_data = base::StringPrintf( + "session_id=%s", + net::EscapeUrlEncodedData(session_id_, true).c_str()); + const int response_code = SimplePOSTRequest(url, form_data); + + if (response_code == 200) { + device_address_ = serial; + device_ = device; + break; + } + + // The device is probably claimed by another process. + if (response_code != 403) { + LOG(WARNING) << "Unexpected HTTP " << response_code << " from /claim."; + } + } + } + + std::string local_version; + std::string version; + if (!ReadLocalVersion(&local_version) || + !GetVersion(&version)) { + return false; + } + + if (version == local_version) { + return true; + } + + return Update(); +} + +bool UsbTestGadgetImpl::GetVersion(std::string* version) { + Delegate delegate; + const GURL url("http://" + device_address_ + "/version"); + scoped_ptr<net::URLFetcher> url_fetcher = + CreateURLFetcher(url, net::URLFetcher::GET, &delegate); + + url_fetcher->Start(); + delegate.WaitForCompletion(); + + const int response_code = url_fetcher->GetResponseCode(); + if (response_code != 200) { + VLOG(2) << "Unexpected HTTP " << response_code << " from /version."; + return false; + } + + STLClearObject(version); + if (!url_fetcher->GetResponseAsString(version)) { + VLOG(2) << "Failed to read body from /version."; + return false; + } + return true; +} + +bool UsbTestGadgetImpl::Update() { + std::string version; + if (!ReadLocalVersion(&version)) { + return false; + } + LOG(INFO) << "Updating " << device_address_ << " to " << version << "..."; + + Delegate delegate; + const GURL url("http://" + device_address_ + "/update"); + scoped_ptr<net::URLFetcher> url_fetcher = + CreateURLFetcher(url, net::URLFetcher::POST, &delegate); + + const std::string mime_header = + base::StringPrintf( + "--foo\r\n" + "Content-Disposition: form-data; name=\"file\"; " + "filename=\"usb_gadget-%s.zip\"\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n", version.c_str()); + const std::string mime_footer("\r\n--foo--\r\n"); + + std::string package; + if (!ReadLocalPackage(&package)) { + return false; + } + + url_fetcher->SetUploadData("multipart/form-data; boundary=foo", + mime_header + package + mime_footer); + url_fetcher->Start(); + delegate.WaitForCompletion(); + + const int response_code = url_fetcher->GetResponseCode(); + if (response_code != 200) { + LOG(ERROR) << "Unexpected HTTP " << response_code << " from /update."; + return false; + } + + int retries = kUpdateRetries; + std::string new_version; + while (!GetVersion(&new_version) || new_version != version) { + if (--retries == 0) { + LOG(ERROR) << "Device not responding with new version."; + return false; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod)); + } + VLOG(1) << "It took " << (kUpdateRetries - retries) + << " retries to see the new version."; + + // Release the old reference to the device and try to open a new one. + device_ = NULL; + retries = kReconnectRetries; + while (!FindClaimed()) { + if (--retries == 0) { + LOG(ERROR) << "Failed to find updated device."; + return false; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod)); + } + VLOG(1) << "It took " << (kReconnectRetries - retries) + << " retries to find the updated device."; + + return true; +} + +bool UsbTestGadgetImpl::FindClaimed() { + CHECK(!device_.get()); + + std::string expected_serial = GetSerial(); + + std::vector<scoped_refptr<UsbDevice> > devices; + usb_service_->GetDevices(&devices); + + for (std::vector<scoped_refptr<UsbDevice> >::iterator iter = + devices.begin(); iter != devices.end(); ++iter) { + scoped_refptr<UsbDevice> &device = *iter; + + if (device->vendor_id() == 0x18D1) { + const uint16 product_id = device->product_id(); + bool found = false; + for (size_t i = 0; i < arraysize(kConfigurations); ++i) { + if (product_id == kConfigurations[i].product_id) { + found = true; + break; + } + } + if (!found) { + continue; + } + + scoped_refptr<UsbDeviceHandle> handle(device->Open()); + if (handle.get() == NULL) { + continue; + } + + base::string16 serial_utf16; + if (!handle->GetSerial(&serial_utf16)) { + continue; + } + + std::string serial = base::UTF16ToUTF8(serial_utf16); + if (serial != expected_serial) { + continue; + } + + device_ = device; + return true; + } + } + + return false; +} + +bool UsbTestGadgetImpl::ReadLocalVersion(std::string* version) { + base::FilePath file_path; + CHECK(PathService::Get(base::DIR_EXE, &file_path)); + file_path = file_path.AppendASCII("usb_gadget.zip.md5"); + + return ReadFile(file_path, version); +} + +bool UsbTestGadgetImpl::ReadLocalPackage(std::string* package) { + base::FilePath file_path; + CHECK(PathService::Get(base::DIR_EXE, &file_path)); + file_path = file_path.AppendASCII("usb_gadget.zip"); + + return ReadFile(file_path, package); +} + +bool UsbTestGadgetImpl::ReadFile(const base::FilePath& file_path, + std::string* content) { + base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) { + LOG(ERROR) << "Cannot open " << file_path.MaybeAsASCII() << ": " + << base::File::ErrorToString(file.error_details()); + return false; + } + + STLClearObject(content); + int rv; + do { + char buf[4096]; + rv = file.ReadAtCurrentPos(buf, sizeof buf); + if (rv == -1) { + LOG(ERROR) << "Cannot read " << file_path.MaybeAsASCII() << ": " + << base::File::ErrorToString(file.error_details()); + return false; + } + content->append(buf, rv); + } while (rv > 0); + + return true; +} + +bool UsbTestGadgetImpl::Unclaim() { + VLOG(1) << "Releasing the device at " << device_address_ << "."; + + const GURL url("http://" + device_address_ + "/unclaim"); + const int response_code = SimplePOSTRequest(url, ""); + + if (response_code != 200) { + LOG(ERROR) << "Unexpected HTTP " << response_code << " from /unclaim."; + return false; + } + return true; +} + +bool UsbTestGadgetImpl::SetType(Type type) { + const struct UsbTestGadgetConfiguration* config = NULL; + for (size_t i = 0; i < arraysize(kConfigurations); ++i) { + if (kConfigurations[i].type == type) { + config = &kConfigurations[i]; + } + } + CHECK(config); + + const GURL url("http://" + device_address_ + config->http_resource); + const int response_code = SimplePOSTRequest(url, ""); + + if (response_code != 200) { + LOG(ERROR) << "Unexpected HTTP " << response_code + << " from " << config->http_resource << "."; + return false; + } + + // Release the old reference to the device and try to open a new one. + int retries = kReconnectRetries; + while (true) { + device_ = NULL; + if (FindClaimed() && device_->product_id() == config->product_id) { + break; + } + if (--retries == 0) { + LOG(ERROR) << "Failed to find updated device."; + return false; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod)); + } + VLOG(1) << "It took " << (kReconnectRetries - retries) + << " retries to find the updated device."; + + return true; +} + +bool UsbTestGadgetImpl::Disconnect() { + const GURL url("http://" + device_address_ + "/disconnect"); + const int response_code = SimplePOSTRequest(url, ""); + + if (response_code != 200) { + LOG(ERROR) << "Unexpected HTTP " << response_code << " from /disconnect."; + return false; + } + + // Release the old reference to the device and wait until it can't be found. + int retries = kDisconnectRetries; + while (true) { + device_ = NULL; + if (!FindClaimed()) { + break; + } + if (--retries == 0) { + LOG(ERROR) << "Device did not disconnect."; + return false; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod)); + } + VLOG(1) << "It took " << (kDisconnectRetries - retries) + << " retries for the device to disconnect."; + + return true; +} + +bool UsbTestGadgetImpl::Reconnect() { + const GURL url("http://" + device_address_ + "/reconnect"); + const int response_code = SimplePOSTRequest(url, ""); + + if (response_code != 200) { + LOG(ERROR) << "Unexpected HTTP " << response_code << " from /reconnect."; + return false; + } + + int retries = kDisconnectRetries; + while (true) { + if (FindClaimed()) { + break; + } + if (--retries == 0) { + LOG(ERROR) << "Device did not reconnect."; + return false; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(kRetryPeriod)); + } + VLOG(1) << "It took " << (kDisconnectRetries - retries) + << " retries for the device to reconnect."; + + return true; +} + +} // namespace device |