| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "lorgnette/usb/usb_device.h" |
| |
| #include <map> |
| #include <string> |
| |
| #include <base/logging.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <chromeos/constants/lorgnette_dlc.h> |
| #include <libusb.h> |
| #include <set> |
| |
| #include "lorgnette/ippusb_device.h" |
| #include "lorgnette/scanner_match.h" |
| |
| namespace lorgnette { |
| |
| namespace { |
| |
| const char kScannerTypeMFP[] = "multi-function peripheral"; // Matches SANE. |
| |
| // Scanners requiring the sane-backends-pfu DLC. |
| std::set<VidPid> kScannersRequiringSaneBackendsPfuDlc = { |
| {0x04c5, 0x132e}, {0x04c5, 0x15fc}, {0x04c5, 0x15ff}, {0x05ca, 0x0307}}; |
| |
| // Creates a new key in `map` for each scanner in `scanners`, with the value |
| // `id`. |
| void SetScannerIds(const std::set<VidPid>& scanners, |
| const std::string& id, |
| std::map<VidPid, std::string>* map) { |
| DCHECK(map); |
| for (const auto& vidpid : scanners) { |
| auto itr = map->find(vidpid); |
| DCHECK(itr == map->end()); |
| (*map)[vidpid] = id; |
| } |
| } |
| |
| } // namespace |
| |
| UsbDevice::UsbDevice() { |
| SetScannerIds(kScannersRequiringSaneBackendsPfuDlc, kSaneBackendsPfuDlcId, |
| &default_dlc_backend_scanners_); |
| dlc_backend_scanners_ = &default_dlc_backend_scanners_; |
| } |
| |
| uint16_t UsbDevice::GetVid() const { |
| return vid_; |
| } |
| |
| uint16_t UsbDevice::GetPid() const { |
| return pid_; |
| } |
| |
| std::string UsbDevice::Description() const { |
| return vid_pid_; |
| } |
| |
| void UsbDevice::Init() { |
| auto descriptor = GetDeviceDescriptor(); |
| if (!descriptor) { |
| return; |
| } |
| |
| vid_ = descriptor->idVendor; |
| pid_ = descriptor->idProduct; |
| vid_pid_ = base::StringPrintf("%04x:%04x", vid_, pid_); |
| } |
| |
| bool UsbDevice::SupportsIppUsb() const { |
| auto maybe_descriptor = GetDeviceDescriptor(); |
| if (!maybe_descriptor.has_value()) { |
| return false; |
| } |
| libusb_device_descriptor& descriptor = maybe_descriptor.value(); |
| |
| // Printers always have a printer class interface defined. They don't define |
| // a top-level device class. |
| if (descriptor.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) { |
| return false; |
| } |
| |
| bool isPrinter = false; |
| bool isIppUsb = false; |
| for (uint8_t c = 0; c < descriptor.bNumConfigurations; c++) { |
| ScopedConfigDescriptor config = GetConfigDescriptor(c); |
| if (!config) { |
| continue; |
| } |
| |
| isIppUsb = ContainsIppUsbInterface(config.get(), &isPrinter); |
| |
| if (isIppUsb) { |
| break; |
| } |
| } |
| if (isPrinter && !isIppUsb) { |
| LOG(INFO) << "Device " << Description() << " is a printer without IPP-USB"; |
| } |
| |
| return isIppUsb; |
| } |
| |
| std::string UsbDevice::GetSerialNumber() { |
| auto maybe_descriptor = GetDeviceDescriptor(); |
| if (!maybe_descriptor.has_value()) { |
| return ""; |
| } |
| libusb_device_descriptor& descriptor = maybe_descriptor.value(); |
| |
| if (descriptor.iSerialNumber == 0) { |
| // A valid serial number string descriptor must be at index 1 or later. |
| return ""; |
| } |
| |
| auto serial = GetStringDescriptor(descriptor.iSerialNumber); |
| if (!serial.has_value() || serial->empty()) { |
| LOG(ERROR) << "Device " << Description() << " is missing serial number"; |
| return ""; |
| } |
| |
| return serial.value(); |
| } |
| |
| std::optional<ScannerInfo> UsbDevice::IppUsbScannerInfo() { |
| auto maybe_descriptor = GetDeviceDescriptor(); |
| if (!maybe_descriptor.has_value()) { |
| return std::nullopt; |
| } |
| libusb_device_descriptor& descriptor = maybe_descriptor.value(); |
| |
| auto mfgr_name = GetStringDescriptor(descriptor.iManufacturer); |
| if (!mfgr_name.has_value() || mfgr_name->empty()) { |
| LOG(ERROR) << "Device " << Description() << " is missing manufacturer"; |
| return std::nullopt; |
| } |
| |
| auto model_name = GetStringDescriptor(descriptor.iProduct); |
| if (!model_name.has_value() || model_name->empty()) { |
| LOG(ERROR) << "Device " << Description() << " is missing product"; |
| return std::nullopt; |
| } |
| |
| std::string printer_name; |
| if (base::StartsWith(*model_name, *mfgr_name, |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| printer_name = *model_name; |
| } else { |
| printer_name = *mfgr_name + " " + *model_name; |
| } |
| |
| std::string device_name = base::StringPrintf( |
| "ippusb:escl:%s:%04x_%04x/eSCL/", printer_name.c_str(), vid_, pid_); |
| ScannerInfo info; |
| info.set_name(device_name); |
| info.set_manufacturer(*mfgr_name); |
| info.set_model(*model_name); |
| info.set_type(kScannerTypeMFP); // Printer that can scan == MFP. |
| info.set_connection_type(lorgnette::CONNECTION_USB); |
| info.set_secure(true); |
| info.set_protocol_type(ProtocolTypeForScanner(info)); |
| info.set_display_name(DisplayNameForScanner(info)); |
| return info; |
| } |
| |
| std::map<VidPid, std::string>* UsbDevice::GetDlcBackendScanners() { |
| return dlc_backend_scanners_; |
| } |
| |
| void UsbDevice::SetDlcBackendScanners( |
| std::map<VidPid, std::string>* dlc_backend_scanners) { |
| CHECK(dlc_backend_scanners); |
| dlc_backend_scanners_ = dlc_backend_scanners; |
| } |
| |
| std::optional<std::string> UsbDevice::GetNonBundledBackendId() const { |
| VidPid curr_device = {GetVid(), GetPid()}; |
| auto itr = dlc_backend_scanners_->find(curr_device); |
| if (itr == dlc_backend_scanners_->end()) { |
| return std::nullopt; |
| } |
| return itr->second; |
| } |
| |
| } // namespace lorgnette |