Reland: Adding IEEE1284 USB-Printer Device ID

For some USB-printers we need some information from the IEEE 1284 Device
ID to resolve PPD matching. This change now queries and saves this ID
for any USB-discovered printers.

This reverts commit 77625ed97f17bf1b9b8eb33927a90d5725236a18.
Revert reason fixed by https://chromium-review.googlesource.com/c/chromium/src/+/1681340

Originally reviewed as https://chromium-review.googlesource.com/c/chromium/src/+/1661117

Bug: chromium:895037, chromium:926022
Test: manually tested

Change-Id: I67cf48eb1a62c316e35d28c7e8ab0f61f3901365
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1681361
Commit-Queue: Sean Kau <[email protected]>
Reviewed-by: Steven Bennetts <[email protected]>
Cr-Commit-Position: refs/heads/master@{#674170}
diff --git a/chrome/browser/chromeos/printing/usb_printer_detector.cc b/chrome/browser/chromeos/printing/usb_printer_detector.cc
index 0e5de53..5c3a75d4 100644
--- a/chrome/browser/chromeos/printing/usb_printer_detector.cc
+++ b/chrome/browser/chromeos/printing/usb_printer_detector.cc
@@ -27,11 +27,13 @@
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/debug_daemon_client.h"
 #include "chromeos/printing/ppd_provider.h"
+#include "chromeos/printing/usb_printer_id.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/system_connector.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
 #include "services/device/public/mojom/constants.mojom.h"
+#include "services/device/public/mojom/usb_device.mojom.h"
 #include "services/device/public/mojom/usb_manager_client.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 
@@ -128,9 +130,24 @@
         GuessEffectiveMakeAndModel(device_info));
     entry.ppd_search_data.discovery_type =
         PrinterSearchData::PrinterDiscoveryType::kUsb;
-    // TODO(https://crbug.com/895037): Add in command set from IEEE1284
 
-    printers_[device_info.guid] = entry;
+    // Query printer for an IEEE Device ID.
+    device::mojom::UsbDevicePtr device_ptr;
+    device_manager_->GetDevice(device_info.guid, mojo::MakeRequest(&device_ptr),
+                               nullptr /* device_client */);
+    GetDeviceId(std::move(device_ptr),
+                base::BindOnce(&UsbPrinterDetectorImpl::OnGetDeviceId,
+                               weak_factory_.GetWeakPtr(), std::move(entry),
+                               device_info.guid));
+  }
+
+  void OnGetDeviceId(DetectedPrinter entry,
+                     std::string guid,
+                     UsbPrinterId printer_id) {
+    entry.ppd_search_data.printer_id = std::move(printer_id);
+
+    // Add detected printer.
+    printers_[guid] = entry;
     if (on_printers_found_callback_) {
       on_printers_found_callback_.Run(GetPrinters());
     }
diff --git a/chrome/browser/chromeos/printing/usb_printer_util.cc b/chrome/browser/chromeos/printing/usb_printer_util.cc
index fe57a93..e486a02 100644
--- a/chrome/browser/chromeos/printing/usb_printer_util.cc
+++ b/chrome/browser/chromeos/printing/usb_printer_util.cc
@@ -7,10 +7,13 @@
 #include <ctype.h>
 #include <stdint.h>
 
+#include <algorithm>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/big_endian.h"
+#include "base/bind_helpers.h"
 #include "base/hash/md5.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
@@ -19,7 +22,9 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/printing/printer_configuration.h"
+#include "chromeos/printing/usb_printer_id.h"
 #include "services/device/public/cpp/usb/usb_utils.h"
+#include "services/device/public/mojom/usb_device.mojom.h"
 #include "services/device/public/mojom/usb_enumeration_options.mojom.h"
 #include "services/device/public/mojom/usb_manager.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -41,6 +46,69 @@
 // (http://www.usb.org/developers/docs/devclass_docs/IPP.zip).
 constexpr uint8_t kPrinterIppusbProtocol = 4;
 
+// Configuration for a GET_DEVICE_ID Printer Class-Specific Request.
+const int kGetDeviceIdRequest = 0;
+const int kDefaultInterface = 0;
+const int kDefaultConfiguration = 0;
+
+// Callback for device.mojom.UsbDevice.ControlTransferIn.
+// Expects |data| to hold a newly queried Device ID.
+void OnControlTransfer(device::mojom::UsbDevicePtr device_ptr,
+                       GetDeviceIdCallback cb,
+                       device::mojom::UsbTransferStatus status,
+                       const std::vector<uint8_t>& data) {
+  if (status != device::mojom::UsbTransferStatus::COMPLETED || data.empty()) {
+    return std::move(cb).Run({});
+  }
+
+  // Cleanup device_ptr.
+  device_ptr->ReleaseInterface(kDefaultInterface, base::DoNothing());
+  device_ptr->Close(base::DoNothing());
+
+  return std::move(cb).Run(UsbPrinterId(data));
+}
+
+// Callback for device.mojom.UsbDevice.ClaimInterface.
+// If interface was claimed successfully, attempts to query printer for a
+// Device ID.
+void OnClaimInterface(device::mojom::UsbDevicePtr device_ptr,
+                      GetDeviceIdCallback cb,
+                      bool success) {
+  if (!success) {
+    return std::move(cb).Run({});
+  }
+
+  auto params = device::mojom::UsbControlTransferParams::New();
+  params->type = device::mojom::UsbControlTransferType::CLASS;
+  params->recipient = device::mojom::UsbControlTransferRecipient::INTERFACE;
+  params->request = kGetDeviceIdRequest;
+  params->value = kDefaultConfiguration;  // default config index
+  params->index = kDefaultInterface;      // default interface index
+
+  // Query for IEEE1284 string.
+  auto* device = device_ptr.get();
+  device->ControlTransferIn(
+      std::move(params), 255 /* max size */, 2000 /* 2 second timeout */,
+      base::BindOnce(OnControlTransfer, std::move(device_ptr), std::move(cb)));
+}
+
+// Callback for device.mojom.UsbDevice.Open.
+// If device was opened successfully, attempts to claim printer's default
+// interface.
+void OnDeviceOpen(device::mojom::UsbDevicePtr device_ptr,
+                  GetDeviceIdCallback cb,
+                  device::mojom::UsbOpenDeviceError error) {
+  if (error != device::mojom::UsbOpenDeviceError::OK || !device_ptr) {
+    return std::move(cb).Run({});
+  }
+
+  // Claim interface.
+  auto* device = device_ptr.get();
+  device->ClaimInterface(
+      kDefaultInterface,
+      base::BindOnce(OnClaimInterface, std::move(device_ptr), std::move(cb)));
+}
+
 // Escape URI strings the same way cups does it, so we end up with a URI cups
 // recognizes.  Cups hex-encodes '%', ' ', and anything not in the standard
 // ASCII range.  CUPS lets everything else through unchanged.
@@ -108,7 +176,7 @@
 // possible for that device.  So we basically toss every bit of stable
 // information from the device into an MD5 hash, and then hexify the hash value
 // as a suffix to "usb-" as the final printer id.
-std::string UsbPrinterId(const UsbDeviceInfo& device_info) {
+std::string CreateUsbPrinterId(const UsbDeviceInfo& device_info) {
   // Paranoid checks; in the unlikely event someone messes with the USB device
   // definition, our (supposedly stable) hashes will change.
   static_assert(sizeof(device_info.class_code) == 1, "Class size changed");
@@ -258,9 +326,17 @@
 
   printer->set_description(printer->display_name());
   printer->set_uri(UsbPrinterUri(device_info));
-  printer->set_id(UsbPrinterId(device_info));
+  printer->set_id(CreateUsbPrinterId(device_info));
   printer->set_supports_ippusb(UsbDeviceSupportsIppusb(device_info));
   return printer;
 }
 
+void GetDeviceId(device::mojom::UsbDevicePtr device_ptr,
+                 GetDeviceIdCallback cb) {
+  // Open device.
+  auto* device = device_ptr.get();
+  device->Open(
+      base::BindOnce(OnDeviceOpen, std::move(device_ptr), std::move(cb)));
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/usb_printer_util.h b/chrome/browser/chromeos/printing/usb_printer_util.h
index 76066b5..f05d8ce 100644
--- a/chrome/browser/chromeos/printing/usb_printer_util.h
+++ b/chrome/browser/chromeos/printing/usb_printer_util.h
@@ -14,6 +14,7 @@
 namespace chromeos {
 
 class Printer;
+class UsbPrinterId;
 
 base::string16 GetManufacturerName(
     const device::mojom::UsbDeviceInfo& device_info);
@@ -33,6 +34,12 @@
 std::unique_ptr<Printer> UsbDeviceToPrinter(
     const device::mojom::UsbDeviceInfo& device_info);
 
+// Expects |device_ptr| to be linked to a Printer-class USB Device. Queries the
+// printer for its IEEE 1284 Standard Device ID.
+using GetDeviceIdCallback = base::OnceCallback<void(UsbPrinterId)>;
+void GetDeviceId(device::mojom::UsbDevicePtr device_ptr,
+                 GetDeviceIdCallback cb);
+
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_CHROMEOS_PRINTING_USB_PRINTER_UTIL_H__
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 18369f10..d4947c69 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -62,6 +62,8 @@
     "printing/printer_translator.h",
     "printing/uri_components.cc",
     "printing/uri_components.h",
+    "printing/usb_printer_id.cc",
+    "printing/usb_printer_id.h",
     "process_proxy/process_output_watcher.cc",
     "process_proxy/process_output_watcher.h",
     "process_proxy/process_proxy.cc",
@@ -182,6 +184,7 @@
     "printing/ppd_provider_unittest.cc",
     "printing/printer_configuration_unittest.cc",
     "printing/printer_translator_unittest.cc",
+    "printing/usb_printer_id_unittest.cc",
     "process_proxy/process_output_watcher_unittest.cc",
     "process_proxy/process_proxy_unittest.cc",
     "test/run_all_unittests.cc",
diff --git a/chromeos/printing/epson_driver_matching.cc b/chromeos/printing/epson_driver_matching.cc
index d25b5a6..8f1bd00d 100644
--- a/chromeos/printing/epson_driver_matching.cc
+++ b/chromeos/printing/epson_driver_matching.cc
@@ -34,7 +34,7 @@
                             "application/octet-stream");
 
     case PrinterSearchData::PrinterDiscoveryType::kUsb:
-      return base::Contains(sd.usb_command_set, "ESC/P-R");
+      return base::Contains(sd.printer_id.command_set(), "ESC/P-R");
 
     case PrinterSearchData::PrinterDiscoveryType::kZeroconf:
       // For printers found through mDNS/DNS-SD discovery,
diff --git a/chromeos/printing/epson_driver_matching_unittest.cc b/chromeos/printing/epson_driver_matching_unittest.cc
index 42d274a..8bf627a 100644
--- a/chromeos/printing/epson_driver_matching_unittest.cc
+++ b/chromeos/printing/epson_driver_matching_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/printing/epson_driver_matching.h"
 
 #include <string>
+#include <vector>
 
 #include "chromeos/printing/ppd_provider.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -31,7 +32,7 @@
 
     case PrinterDiscoveryType::kUsb:
       sd.discovery_type = PrinterDiscoveryType::kUsb;
-      sd.usb_command_set.push_back(kEscPr);
+      sd.printer_id.set_command_set({kEscPr});
       break;
 
     case PrinterDiscoveryType::kZeroconf:
@@ -96,7 +97,7 @@
   sd.supported_document_formats.push_back(std::string(kOctetStream) + "afds");
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
-  sd.usb_command_set.push_back(kOctetStream);
+  sd.printer_id.set_command_set({kOctetStream});
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
   sd.supported_document_formats.push_back(kOctetStream);
@@ -106,18 +107,21 @@
 // Simple PrinterDiscoveryType::kUsb checks.
 TEST(EpsonDriverMatchingTest, UsbDiscovery) {
   PrinterSearchData sd(GetTestPrinterSearchData(PrinterDiscoveryType::kUsb));
-  sd.usb_command_set.clear();
+  std::vector<std::string> command_set;
 
-  sd.usb_command_set.push_back("ESC");
+  command_set.push_back("ESC");
+  sd.printer_id.set_command_set(command_set);
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
-  sd.usb_command_set.push_back(std::string(kEscPr) + ":asfd");
+  command_set.push_back(std::string(kEscPr) + ":asfd");
+  sd.printer_id.set_command_set(command_set);
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
   sd.supported_document_formats.push_back(kEscPr);
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
-  sd.usb_command_set.push_back(kEscPr);
+  command_set.push_back(kEscPr);
+  sd.printer_id.set_command_set(command_set);
   EXPECT_TRUE(CanUseEpsonGenericPPD(sd));
 }
 
@@ -132,7 +136,7 @@
   sd.supported_document_formats.push_back(std::string(kEpsonEscpr) + ":asfd");
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
-  sd.usb_command_set.push_back(kEpsonEscpr);
+  sd.printer_id.set_command_set({kEpsonEscpr});
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
   sd.supported_document_formats.push_back(kEpsonEscpr);
diff --git a/chromeos/printing/ppd_provider.h b/chromeos/printing/ppd_provider.h
index 51baa82a..b5110b03 100644
--- a/chromeos/printing/ppd_provider.h
+++ b/chromeos/printing/ppd_provider.h
@@ -16,6 +16,7 @@
 #include "base/version.h"
 #include "chromeos/chromeos_export.h"
 #include "chromeos/printing/printer_configuration.h"
+#include "chromeos/printing/usb_printer_id.h"
 
 namespace network {
 namespace mojom {
@@ -58,9 +59,9 @@
   // Set of MIME types supported by this printer.
   std::vector<std::string> supported_document_formats;
 
-  // Stripped from IEEE1284 signaling method(from the device ID key 'CMD').
-  // Details a set of languages this printer understands.
-  std::vector<std::string> usb_command_set;
+  // Representation of IEEE1284 standard printing device ID.
+  // Contains a set of languages this printer understands.
+  UsbPrinterId printer_id;
 };
 
 // PpdProvider is responsible for mapping printer descriptions to
diff --git a/chromeos/printing/usb_printer_id.cc b/chromeos/printing/usb_printer_id.cc
new file mode 100644
index 0000000..5c7a81e
--- /dev/null
+++ b/chromeos/printing/usb_printer_id.cc
@@ -0,0 +1,88 @@
+// Copyright 2019 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 "chromeos/printing/usb_printer_id.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+
+namespace chromeos {
+
+// Device ID keys pulled from IEEE Standard 1284.
+const char kManufacturer[] = "MANUFACTURER";
+const char kManufacturerAbbr[] = "MFG";
+const char kModel[] = "MODEL";
+const char kModelAbbr[] = "MDL";
+const char kCommandSet[] = "COMMAND SET";
+const char kCommandSetAbbr[] = "CMD";
+
+UsbPrinterId::UsbPrinterId(const std::vector<uint8_t>& device_id_data) {
+  // Build mapping.
+  id_mappings_ = BuildDeviceIdMapping(device_id_data);
+
+  // Save required mappings.
+  // Save make_.
+  if (base::Contains(id_mappings_, kManufacturer)) {
+    make_ = id_mappings_[kManufacturer].front();
+  } else if (base::Contains(id_mappings_, kManufacturerAbbr)) {
+    make_ = id_mappings_[kManufacturerAbbr].front();
+  }
+
+  // Save model_.
+  if (base::Contains(id_mappings_, kModel)) {
+    model_ = id_mappings_[kModel].front();
+  } else if (base::Contains(id_mappings_, kModelAbbr)) {
+    model_ = id_mappings_[kModelAbbr].front();
+  }
+
+  // Save command_set_.
+  if (base::Contains(id_mappings_, kCommandSet)) {
+    command_set_ = id_mappings_[kCommandSet];
+  } else if (base::Contains(id_mappings_, kCommandSetAbbr)) {
+    command_set_ = id_mappings_[kCommandSetAbbr];
+  }
+}
+
+UsbPrinterId::UsbPrinterId() = default;
+UsbPrinterId::UsbPrinterId(const UsbPrinterId& other) = default;
+UsbPrinterId::~UsbPrinterId() = default;
+
+std::map<std::string, std::vector<std::string>> BuildDeviceIdMapping(
+    const std::vector<uint8_t>& data) {
+  // Must contain at least the length information.
+  if (data.size() < 2) {
+    return {};
+  }
+
+  std::map<std::string, std::vector<std::string>> ret;
+
+  // Convert to string to work on.
+  // Note: First two bytes contain the length, so we skip those.
+  std::string printer_id;
+  std::copy(data.begin() + 2, data.end(), std::back_inserter(printer_id));
+
+  // We filter out terms with empty keys or values.
+  base::StringPairs terms;
+  base::SplitStringIntoKeyValuePairs(printer_id, ':', ';', &terms);
+  for (const auto& term : terms) {
+    if (term.first.empty()) {
+      continue;
+    }
+
+    std::vector<std::string> values = base::SplitString(
+        term.second, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    if (values.empty()) {
+      continue;
+    }
+
+    ret[term.first] = values;
+  }
+
+  return ret;
+}
+
+}  // namespace chromeos
diff --git a/chromeos/printing/usb_printer_id.h b/chromeos/printing/usb_printer_id.h
new file mode 100644
index 0000000..3df4257
--- /dev/null
+++ b/chromeos/printing/usb_printer_id.h
@@ -0,0 +1,60 @@
+// Copyright 2019 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 CHROMEOS_PRINTING_USB_PRINTER_ID_H_
+#define CHROMEOS_PRINTING_USB_PRINTER_ID_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "chromeos/chromeos_export.h"
+
+namespace chromeos {
+
+// This class parses and holds the IEEE 1284 Device ID string as queried
+// from a USB-connected printer.
+class CHROMEOS_EXPORT UsbPrinterId {
+ public:
+  UsbPrinterId();
+  UsbPrinterId(const UsbPrinterId& other);
+  ~UsbPrinterId();
+
+  // Expects |printer_id_data| to contain the data portion response to a USB
+  // Printer Class-Specific GET_DEVICE_ID Request.
+  explicit UsbPrinterId(const std::vector<uint8_t>& printer_id_data);
+
+  // Accessors.
+  std::string make() const { return make_; }
+  std::string model() const { return model_; }
+  std::vector<std::string> command_set() const { return command_set_; }
+
+  // Setters (only used in testing).
+  void set_make(std::string make) { make_ = make; }
+  void set_model(std::string model) { model_ = model; }
+  void set_command_set(std::vector<std::string> command_set) {
+    command_set_ = std::move(command_set);
+  }
+
+ private:
+  std::string make_;
+  std::string model_;
+
+  // List of supported document formats (MIME types).
+  std::vector<std::string> command_set_;
+
+  // Holds the fully parsed IEEE 1284 Device ID.
+  std::map<std::string, std::vector<std::string>> id_mappings_;
+};
+
+// Expects data to hold a IEEE 1284 Device ID. Parses |data| and returns the
+// resulting key-value(s) pairs.
+CHROMEOS_EXPORT std::map<std::string, std::vector<std::string>>
+BuildDeviceIdMapping(const std::vector<uint8_t>& data);
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_PRINTING_USB_PRINTER_ID_H_
diff --git a/chromeos/printing/usb_printer_id_unittest.cc b/chromeos/printing/usb_printer_id_unittest.cc
new file mode 100644
index 0000000..7ad21a1
--- /dev/null
+++ b/chromeos/printing/usb_printer_id_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2019 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 "chromeos/printing/usb_printer_id.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace {
+
+using testing::IsEmpty;
+
+using MapType = std::map<std::string, std::vector<std::string>>;
+
+MapType GetDefaultDeviceId() {
+  MapType ret;
+
+  // Make.
+  ret["MFG"].push_back("EPSON");
+
+  // Model.
+  ret["MDL"].push_back("ET-2700");
+
+  // Command set.
+  ret["CMD"].push_back("ESCPL2");
+  ret["CMD"].push_back("BDC");
+  ret["CMD"].push_back("D4");
+  ret["CMD"].push_back("END4");
+  ret["CMD"].push_back("GENEP");
+
+  return ret;
+}
+
+std::string MapToString(const MapType& map) {
+  std::vector<std::string> terms;
+  for (auto& term : map) {
+    std::string values = base::JoinString(term.second, ",");
+    terms.push_back(base::JoinString({term.first, values}, ":"));
+  }
+
+  std::string device_id_str = "xx";  // Two unused bytes for the length.
+  device_id_str += base::JoinString(terms, ";") + ";";
+  return device_id_str;
+}
+
+std::vector<uint8_t> MapToBuffer(const MapType& map) {
+  std::string device_id_str = MapToString(map);
+
+  std::vector<uint8_t> ret;
+  std::copy(device_id_str.begin(), device_id_str.end(),
+            std::back_inserter(ret));
+  return ret;
+}
+
+TEST(UsbPrinterIdTest, EmptyDeviceId) {
+  EXPECT_THAT(BuildDeviceIdMapping({}), IsEmpty());
+}
+
+// Tests that we get the same map back after parsing.
+TEST(UsbPrinterIdTest, SimpleSanityTest) {
+  MapType mapping = GetDefaultDeviceId();
+  std::vector<uint8_t> buffer = MapToBuffer(mapping);
+  EXPECT_EQ(mapping, BuildDeviceIdMapping(buffer));
+}
+
+}  // namespace
+}  // namespace chromeos