| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h" |
| |
| #include <array> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/map_util.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/unguessable_token.h" |
| #include "base/uuid.h" |
| #include "chrome/browser/ash/scanning/lorgnette_notification_controller.h" |
| #include "chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h" |
| #include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/ash/components/dbus/dbus_thread_manager.h" |
| #include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h" |
| #include "chromeos/ash/components/dbus/lorgnette/lorgnette_service.pb.h" |
| #include "chromeos/ash/components/dbus/lorgnette_manager/lorgnette_manager_client.h" |
| #include "chromeos/ash/components/scanning/scanner.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "net/base/ip_address.h" |
| #include "third_party/re2/src/re2/re2.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Used as the client ID when calling ListScanners to retrieve scanner names. |
| constexpr char kListScannersDiscoveryClientId[] = "GetScannerNames"; |
| // Used as the client ID when verifying scanner connectivity. |
| constexpr char kVerifyScannerClientId[] = "ZeroconfScannerChecker"; |
| |
| // A list of Epson models that do not rotate alternating ADF scanned pages |
| // to be excluded in IsRotateAlternate(). |
| constexpr char kEpsonNoFlipModels[] = |
| "\\b(" |
| "AM-C400" |
| "|AM-C4000" |
| "|AM-C5000" |
| "|AM-C550" |
| "|AM-C550z" |
| "|AM-C6000" |
| "|DS-790WN" |
| "|DS-800WN" |
| "|DS-900WN" |
| "|DS-C420W" |
| "|DS-C480W" |
| "|EM-C7100" |
| "|EM-C800" |
| "|EM-C8100" |
| "|EM-C8101" |
| "|ES-C320W" |
| "|ES-C380W" |
| "|LM-C400" |
| "|LM-C4000" |
| "|LM-C5000" |
| "|LM-C6000" |
| "|LP-M8180A" |
| "|LP-M8180F" |
| "|LX-10020M" |
| "|LX-10050KF" |
| "|LX-10050MF" |
| "|LX-6050MF" |
| "|LX-7550MF" |
| "|PX-M382F" |
| "|PX-M7070FX" |
| "|PX-M7080FX" |
| "|PX-M7090FX" |
| "|PX-M7110F" |
| "|PX-M7110FP" |
| "|PX-M7120F" |
| "|PX-M7120FP" |
| "|PX-M8000FX" |
| "|PX-M8010FX" |
| "|PX-M860F" |
| "|PX-M880FX" |
| "|PX-M890FX" |
| "|RR-400W" |
| "|WF-6530" |
| "|WF-6590" |
| "|WF-6593" |
| "|WF-C20600" |
| "|WF-C20600a" |
| "|WF-C20600c" |
| "|WF-C20750" |
| "|WF-C20750a" |
| "|WF-C20750c" |
| "|WF-C21000" |
| "|WF-C21000a" |
| "|WF-C21000c" |
| "|WF-C579R" |
| "|WF-C579Ra" |
| "|WF-C5891" |
| "|WF-C8610" |
| "|WF-C8690" |
| "|WF-C8690a" |
| "|WF-C869R" |
| "|WF-C869Ra" |
| "|WF-C878R" |
| "|WF-C878Ra" |
| "|WF-C879R" |
| "|WF-C879Ra" |
| "|WF-M5899" |
| "|WF-M21000" |
| "|WF-M21000a" |
| "|WF-M21000c" |
| ")\\b"; |
| |
| // A prioritized list of scan protocols. Protocols that appear earlier in the |
| // list are preferred over those that appear later in the list when |
| // communicating with a connected scanner. |
| constexpr std::array<ScanProtocol, 4> kPrioritizedProtocols = { |
| ScanProtocol::kEscls, ScanProtocol::kEscl, ScanProtocol::kLegacyNetwork, |
| ScanProtocol::kLegacyUsb}; |
| |
| // Returns a pointer to LorgnetteManagerClient, which is used to detect and |
| // interact with scanners via the lorgnette D-Bus service. |
| LorgnetteManagerClient* GetLorgnetteManagerClient() { |
| return LorgnetteManagerClient::Get(); |
| } |
| |
| // Creates a base name by concatenating the manufacturer and model, if the |
| // model doesn't already include the manufacturer. Appends "(USB)" for USB |
| // scanners. |
| std::string CreateBaseName(const lorgnette::ScannerInfo& lorgnette_scanner, |
| const bool is_usb_scanner) { |
| const std::string model = lorgnette_scanner.model(); |
| const std::string manufacturer = lorgnette_scanner.manufacturer(); |
| |
| // It's assumed that, if present, the manufacturer would be the first word in |
| // the model. |
| const std::string maybe_manufacturer = |
| RE2::PartialMatch(model.c_str(), base::StringPrintf("(?i)\\A%s\\b", |
| manufacturer.c_str())) |
| ? "" |
| : manufacturer + " "; |
| |
| return base::StringPrintf("%s%s%s", maybe_manufacturer.c_str(), model.c_str(), |
| is_usb_scanner ? " (USB)" : ""); |
| } |
| |
| std::string ScannerCapabilitiesToString( |
| const lorgnette::ScannerCapabilities& capabilities) { |
| std::vector<std::string> sources; |
| sources.reserve(capabilities.sources_size()); |
| for (const lorgnette::DocumentSource& source : capabilities.sources()) { |
| std::vector<std::string> resolutions; |
| resolutions.reserve(source.resolutions_size()); |
| for (const uint32_t resolution : source.resolutions()) { |
| resolutions.emplace_back(base::StringPrintf("%d", resolution)); |
| } |
| |
| std::vector<std::string> color_modes; |
| color_modes.reserve(source.color_modes_size()); |
| for (int i = 0; i < source.color_modes_size(); i++) { |
| // Loop manually because `color_modes()` returns a RepeatedField<int> |
| // instead of ColorMode. |
| color_modes.emplace_back( |
| lorgnette::ColorMode_Name(source.color_modes(i))); |
| } |
| |
| sources.emplace_back(base::StringPrintf( |
| "{ %s (%s) area=%0.1fx%0.1f resolutions=%s color_modes=%s }", |
| lorgnette::SourceType_Name(source.type()).c_str(), |
| source.name().c_str(), source.area().width(), source.area().height(), |
| base::JoinString(resolutions, ",").c_str(), |
| base::JoinString(color_modes, ",").c_str())); |
| } |
| return base::JoinString(sources, ", "); |
| } |
| |
| // Create a unique ID for a scanner based off the scanner's UUID and its |
| // connection string. A single scanner can have multiple ways to connect to it |
| // (http and https, for example), and the UUID will be the same between these |
| // two connection strings (since the UUID should identify a unique device and |
| // not the connection protocol). So, UUID itself is not unique. UUID combined |
| // with the connection string should be unique. |
| std::string CreateScannerId(std::string_view uuid, |
| std::string_view connection_string) { |
| return base::StrCat({uuid, ":", connection_string}); |
| } |
| |
| class LorgnetteScannerManagerImpl final : public LorgnetteScannerManager { |
| public: |
| LorgnetteScannerManagerImpl( |
| std::unique_ptr<ZeroconfScannerDetector> zeroconf_scanner_detector, |
| Profile* profile) |
| : zeroconf_scanner_detector_(std::move(zeroconf_scanner_detector)) { |
| zeroconf_scanner_detector_->RegisterScannersDetectedCallback( |
| base::BindRepeating(&LorgnetteScannerManagerImpl::OnScannersDetected, |
| weak_ptr_factory_.GetWeakPtr())); |
| OnScannersDetected(zeroconf_scanner_detector_->GetScanners()); |
| lorgnette_notification_controller_ = |
| std::make_unique<LorgnetteNotificationController>(profile); |
| } |
| |
| ~LorgnetteScannerManagerImpl() override = default; |
| |
| // KeyedService: |
| void Shutdown() override { weak_ptr_factory_.InvalidateWeakPtrs(); } |
| |
| // LorgnetteScannerManager: |
| void GetScannerNames(GetScannerNamesCallback callback) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| GetLorgnetteManagerClient()->ListScanners( |
| kListScannersDiscoveryClientId, |
| /*local_only=*/false, |
| /*preferred_only=*/true, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnListScannerNamesResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void GetScannerInfoList(const std::string& client_id, |
| LocalScannerFilter local_only, |
| SecureScannerFilter secure_only, |
| GetScannerInfoListCallback callback) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| GetLorgnetteManagerClient()->ListScanners( |
| client_id, (local_only == LocalScannerFilter::kLocalScannersOnly), |
| /*preferred_only=*/false, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnListScannerInfoResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(client_id), |
| std::move(callback), local_only, secure_only)); |
| } |
| |
| // LorgnetteScannerManager: |
| void GetScannerCapabilities( |
| const std::string& scanner_name, |
| GetScannerCapabilitiesCallback callback) override { |
| std::string device_name; |
| ScanProtocol protocol; |
| if (!GetUsableDeviceNameAndProtocol(scanner_name, device_name, protocol)) { |
| PRINTER_LOG(ERROR) << "GetScannerCapabilities failed for: " |
| << scanner_name; |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| GetLorgnetteManagerClient()->GetScannerCapabilities( |
| device_name, |
| base::BindOnce( |
| &LorgnetteScannerManagerImpl::OnScannerCapabilitiesResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), scanner_name, |
| device_name, protocol)); |
| } |
| |
| // LorgnetteScannerManager: |
| void OpenScanner(const lorgnette::OpenScannerRequest& request, |
| OpenScannerCallback callback) override { |
| std::string connection_string = request.scanner_id().connection_string(); |
| |
| // If the client doesn't have any tokens, whatever they supplied can't be |
| // valid. |
| TokenToScannerId* valid_tokens = |
| base::FindOrNull(client_tokens_, request.client_id()); |
| if (!valid_tokens) { |
| lorgnette::OpenScannerResponse response; |
| *response.mutable_scanner_id() = request.scanner_id(); |
| response.set_result(lorgnette::OPERATION_RESULT_INVALID); |
| PRINTER_LOG(ERROR) << "OpenScanner: No valid tokens for " |
| << connection_string; |
| std::move(callback).Run(std::move(response)); |
| return; |
| } |
| |
| // If the token isn't found in the previously returned set, it isn't valid. |
| std::optional<ScannerId>* device_id = |
| base::FindOrNull(*valid_tokens, connection_string); |
| if (!device_id) { |
| lorgnette::OpenScannerResponse response; |
| *response.mutable_scanner_id() = request.scanner_id(); |
| response.set_result(lorgnette::OPERATION_RESULT_INVALID); |
| PRINTER_LOG(ERROR) << "OpenScanner: No device ID for " |
| << connection_string; |
| std::move(callback).Run(std::move(response)); |
| return; |
| } |
| |
| // If the token is found but doesn't have a value, the referenced device is |
| // no longer available. |
| if (!device_id->has_value()) { |
| lorgnette::OpenScannerResponse response; |
| *response.mutable_scanner_id() = request.scanner_id(); |
| response.set_result(lorgnette::OPERATION_RESULT_MISSING); |
| PRINTER_LOG(ERROR) << "OpenScanner: Empty device ID for " |
| << connection_string; |
| std::move(callback).Run(std::move(response)); |
| return; |
| } |
| |
| // Token is valid. The necessary SANE connection string is the second |
| // field. |
| connection_string = device_id->value().second; |
| lorgnette::OpenScannerRequest lorgnette_request = request; |
| lorgnette_request.mutable_scanner_id()->set_connection_string( |
| connection_string); |
| PRINTER_LOG(EVENT) << "OpenScanner for " << connection_string; |
| GetLorgnetteManagerClient()->OpenScanner( |
| std::move(lorgnette_request), |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnOpenScannerResponse, |
| weak_ptr_factory_.GetWeakPtr(), request.scanner_id(), |
| std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void CloseScanner(const lorgnette::CloseScannerRequest& request, |
| CloseScannerCallback callback) override { |
| PRINTER_LOG(EVENT) << "CloseScanner: " << request.scanner().token(); |
| GetLorgnetteManagerClient()->CloseScanner( |
| request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnCloseScannerResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void SetOptions(const lorgnette::SetOptionsRequest& request, |
| SetOptionsCallback callback) override { |
| GetLorgnetteManagerClient()->SetOptions( |
| request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnSetOptionsResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void GetCurrentConfig(const lorgnette::GetCurrentConfigRequest& request, |
| GetCurrentConfigCallback callback) override { |
| GetLorgnetteManagerClient()->GetCurrentConfig( |
| request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnGetCurrentConfigResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void StartPreparedScan(const lorgnette::StartPreparedScanRequest& request, |
| StartPreparedScanCallback callback) override { |
| GetLorgnetteManagerClient()->StartPreparedScan( |
| request, base::BindOnce( |
| &LorgnetteScannerManagerImpl::OnStartPreparedScanResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void ReadScanData(const lorgnette::ReadScanDataRequest& request, |
| ReadScanDataCallback callback) override { |
| GetLorgnetteManagerClient()->ReadScanData( |
| request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnReadScanDataResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| bool IsRotateAlternate(const std::string& scanner_name, |
| const std::string& source_name) override { |
| if (!RE2::PartialMatch(source_name, RE2("(?i)adf duplex"))) { |
| return false; |
| } |
| |
| std::string device_name; |
| ScanProtocol protocol; |
| if (!GetUsableDeviceNameAndProtocol(scanner_name, device_name, protocol)) { |
| PRINTER_LOG(ERROR) << "IsRotateAlternate: Failed to get device name for " |
| << scanner_name; |
| return false; |
| } |
| |
| std::string exclude_regex = std::string("^(airscan|ippusb).*(EPSON\\s+)?") + |
| std::string(kEpsonNoFlipModels); |
| if (RE2::PartialMatch(device_name, RE2("^(epsonds|epson2)")) || |
| RE2::PartialMatch(device_name, RE2(exclude_regex))) { |
| return false; |
| } |
| |
| return RE2::PartialMatch(device_name, RE2("(?i)epson")); |
| } |
| |
| // LorgnetteScannerManager: |
| void Scan(const std::string& scanner_name, |
| const lorgnette::ScanSettings& settings, |
| ProgressCallback progress_callback, |
| PageCallback page_callback, |
| CompletionCallback completion_callback) override { |
| std::string device_name; |
| ScanProtocol protocol; // Unused. |
| if (!GetUsableDeviceNameAndProtocol(scanner_name, device_name, protocol)) { |
| std::move(completion_callback).Run(lorgnette::SCAN_FAILURE_MODE_UNKNOWN); |
| return; |
| } |
| |
| GetLorgnetteManagerClient()->StartScan( |
| device_name, settings, std::move(completion_callback), |
| std::move(page_callback), std::move(progress_callback)); |
| } |
| |
| // LorgnetteScannerManager: |
| void CancelScan(CancelCallback cancel_callback) override { |
| GetLorgnetteManagerClient()->CancelScan(std::move(cancel_callback)); |
| } |
| |
| // LorgnetteScannerManager: |
| void CancelScan(const lorgnette::CancelScanRequest& request, |
| CancelScanCallback callback) override { |
| GetLorgnetteManagerClient()->CancelScan( |
| request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnCancelScanResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| private: |
| // Scanner device UUID and connection string, because connection string alone |
| // can point to different devices over time. |
| using ScannerId = std::pair<std::string, std::string>; |
| using TokenToScannerId = std::map<std::string, std::optional<ScannerId>>; |
| |
| // Called when scanners are detected by a ScannerDetector. |
| void OnScannersDetected(std::vector<Scanner> scanners) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| zeroconf_scanners_ = scanners; |
| } |
| |
| void SendFinalScannerList(GetScannerNamesCallback callback) { |
| std::vector<std::string> scanner_names; |
| scanner_names.reserve(deduped_scanners_.size()); |
| for (const auto& entry : deduped_scanners_) |
| scanner_names.push_back(entry.first); |
| |
| std::move(callback).Run(std::move(scanner_names)); |
| } |
| |
| // Removes a scanner name from deduped_scanners_ if it has no capabilities. If |
| // there are remaining scanners to filter, start the recursive loop again with |
| // a call to GetScannerCapabilities with the next scanner in |
| // scanners_to_filter_. |
| void RemoveScannersIfUnusable( |
| GetScannerNamesCallback callback, |
| const std::string& scanner_name, |
| const std::optional<lorgnette::ScannerCapabilities>& capabilities) { |
| if (!capabilities) |
| deduped_scanners_.erase(scanner_name); |
| scanners_to_filter_.pop_back(); |
| if (scanners_to_filter_.empty()) { |
| SendFinalScannerList(std::move(callback)); |
| } else { |
| GetScannerCapabilities( |
| scanners_to_filter_.back(), |
| base::BindOnce(&LorgnetteScannerManagerImpl::RemoveScannersIfUnusable, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| scanners_to_filter_.back())); |
| } |
| } |
| |
| // Starts a recursive loop of GetScannerCapabilities, |
| // OnScannerCapabilitiesResponse, and RemoveScannersIfUnusable. |
| // GetScannerCapabilities takes a scanner name, then creates a loop with |
| // OnScannerCapabilitiesResponse to check all usable device names for |
| // capabilities, marking them unusable along the way if they return no |
| // capabilities. Once all device names have been checked, or capabilities have |
| // been found, RemoveScannersIfUnusable is called with the scanner name. |
| void FilterScannersAndRespond(GetScannerNamesCallback callback) { |
| if (!scanners_to_filter_.empty()) { |
| // Run GetScannerCapabilities with a callback that removes scanners from |
| // the deduped_scanners_ mapping if none of their names return |
| // capabilities. |
| GetScannerCapabilities( |
| scanners_to_filter_.back(), |
| base::BindOnce(&LorgnetteScannerManagerImpl::RemoveScannersIfUnusable, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| scanners_to_filter_.back())); |
| } else { |
| SendFinalScannerList(std::move(callback)); |
| } |
| } |
| |
| // Handles the result of calling LorgnetteManagerClient::ListScanners() for |
| // GetScannerNames. |
| void OnListScannerNamesResponse( |
| GetScannerNamesCallback callback, |
| std::optional<lorgnette::ListScannersResponse> response) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| RebuildDedupedScanners(response); |
| FilterScannersAndRespond(std::move(callback)); |
| } |
| |
| // `scanners_to_verify` is a (potentially empty) list of scanners that need to |
| // be verified before being passed back to the caller. If there are some |
| // scanners in this list, this will send an `OpenScanner` request to see if |
| // the scanner is responsive. The callback to that request will update the |
| // list of scanners that still need to be verified as well as update |
| // `response`, and then call this method again to verify the remaining |
| // scanners. When the list is empty, this will simply call `callback` with |
| // `response`. |
| void VerifyScanners(const std::string& client_id, |
| std::vector<lorgnette::ScannerInfo> scanners_to_verify, |
| lorgnette::ListScannersResponse response, |
| GetScannerInfoListCallback callback) { |
| if (scanners_to_verify.empty()) { |
| // TODO(nmuggli): Figure out how to associate a lorgnette scanner to a |
| // zeroconf scanner. If they represent the same physical scanner, the |
| // ScannerInfo objects should have the same device_uuid. For now, just |
| // ensure each ScannerInfo has a device_uuid (the lorgnette backend is not |
| // yet populating the device_uuid). |
| for (lorgnette::ScannerInfo& info : *response.mutable_scanners()) { |
| if (info.device_uuid().empty()) { |
| info.set_device_uuid( |
| base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| } |
| } |
| |
| UpdateScannerTokens(std::move(client_id), std::move(callback), |
| std::move(response)); |
| return; |
| } |
| |
| lorgnette::OpenScannerRequest open_request; |
| open_request.mutable_scanner_id()->set_connection_string( |
| scanners_to_verify.back().name()); |
| // Just use a hard-coded client ID for this. The scanner, if successfully |
| // opened, will get closed right away anyhow. |
| open_request.set_client_id(kVerifyScannerClientId); |
| GetLorgnetteManagerClient()->OpenScanner( |
| open_request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnVerifyScanner, |
| weak_ptr_factory_.GetWeakPtr(), std::move(client_id), |
| std::move(scanners_to_verify), std::move(response), |
| std::move(callback))); |
| } |
| |
| // Called in response to an `OpenScanner` request while checking if a scanner |
| // is responsive. This works in conjunction with `VerifyScanners` and will |
| // update the list of scanners still left to verify. This will populate |
| // `list_response` based on `open_response` and will call `VerifyScanners` |
| // once that has happened to verify any remaining scanners. |
| void OnVerifyScanner( |
| std::string client_id, |
| std::vector<lorgnette::ScannerInfo> scanners_to_verify, |
| lorgnette::ListScannersResponse list_response, |
| GetScannerInfoListCallback callback, |
| std::optional<lorgnette::OpenScannerResponse> open_response) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| // This should only get called when there are scanners that need to be |
| // verified. |
| CHECK(!scanners_to_verify.empty()); |
| lorgnette::ScannerInfo scanner = std::move(scanners_to_verify.back()); |
| scanners_to_verify.pop_back(); |
| |
| if (!open_response) { |
| LOG(WARNING) << "Unable to open '" << scanner.name() |
| << "' while attempting to verify connectivity." << std::endl; |
| VerifyScanners(std::move(client_id), std::move(scanners_to_verify), |
| std::move(list_response), std::move(callback)); |
| return; |
| } |
| |
| // If the scanner was opened, close it. |
| if (open_response->has_config()) { |
| lorgnette::CloseScannerRequest close_request; |
| *close_request.mutable_scanner() = open_response->config().scanner(); |
| GetLorgnetteManagerClient()->CloseScanner( |
| close_request, |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnVerifyScannerClose, |
| weak_ptr_factory_.GetWeakPtr(), scanner.name())); |
| } |
| |
| // If the result is success or busy (busy means the device is reachable but |
| // another client is using it), insert this scanner into the response of |
| // available scanners. |
| if (open_response->result() == lorgnette::OPERATION_RESULT_SUCCESS || |
| open_response->result() == lorgnette::OPERATION_RESULT_DEVICE_BUSY) { |
| verified_scanners_.insert( |
| CreateScannerId(scanner.device_uuid(), scanner.name())); |
| *list_response.add_scanners() = std::move(scanner); |
| } |
| |
| VerifyScanners(std::move(client_id), std::move(scanners_to_verify), |
| std::move(list_response), std::move(callback)); |
| } |
| |
| // While verifying connectivity for a scanner, an open request is sent to the |
| // scanner. If that succeeds, a close request is sent to the scanner. This |
| // method is called in response to that close request. |
| void OnVerifyScannerClose( |
| const std::string& connection_string, |
| std::optional<lorgnette::CloseScannerResponse> response) { |
| if (!response || |
| response->result() != lorgnette::OPERATION_RESULT_SUCCESS) { |
| LOG(WARNING) << "Unable to close scanner '" << connection_string |
| << "' while attempting to verify connectivity." << std::endl; |
| } |
| } |
| |
| // Instead of returning the raw SANE connection, give each client an |
| // unguessable token representing the scanner. This improves privacy by |
| // removing IP addresses and USB serial numbers from the response. In |
| // addition, this makes it possible to return a new token when the SANE |
| // connection string no longer refers to the same device (e.g., if the device |
| // changes networks and a new scanner has the same IP as an old one). |
| void UpdateScannerTokens(const std::string& client_id, |
| GetScannerInfoListCallback callback, |
| lorgnette::ListScannersResponse response) { |
| TokenToScannerId& old_tokens = client_tokens_[client_id]; |
| TokenToScannerId new_tokens; |
| |
| // First ensure tokens are created for all newly-returned scanners. If the |
| // connection string and device UUID match a previously-returned scanner, |
| // preserve the token value. |
| // |
| // This does a linear search of `old_tokens` for each new token, which takes |
| // O(m*n) time. This could be reduced by pre-parsing `old_tokens` into a |
| // reverse map, but this isn't likely to be worth it because these lists are |
| // expected to be very small in most cases. Additionally, fetching the list |
| // of scanners is already a very slow operation because it has to wait for |
| // network responses and USB enumeration. |
| for (lorgnette::ScannerInfo& scanner : *response.mutable_scanners()) { |
| ScannerId new_id{scanner.device_uuid(), scanner.name()}; |
| std::string token; |
| bool copied = false; |
| for (auto& old : old_tokens) { |
| if (old.second.has_value() && new_id == old.second.value()) { |
| token = old.first; |
| new_tokens.emplace(std::move(old)); |
| copied = true; |
| break; |
| } |
| } |
| if (!copied) { |
| token = base::UnguessableToken::Create().ToString(); |
| new_tokens.emplace(token, new_id); |
| } |
| scanner.set_name(token); |
| } |
| |
| // Create tombstones for any previously-returned tokens that are no longer |
| // part of the response. |
| for (const auto& [token, id] : old_tokens) { |
| if (!base::Contains(new_tokens, token)) { |
| new_tokens.emplace(token, std::nullopt); |
| } |
| } |
| |
| old_tokens.swap(new_tokens); |
| std::move(callback).Run(std::move(response)); |
| } |
| |
| // Handles the result of calling LorgnetteManagerClient::ListScanners() for |
| // GetScannerInfoList. |
| void OnListScannerInfoResponse( |
| const std::string& client_id, |
| GetScannerInfoListCallback callback, |
| LocalScannerFilter local_only, |
| SecureScannerFilter secure_only, |
| std::optional<lorgnette::ListScannersResponse> response) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| |
| // Combine zeroconf scanners and lorgnette scanners and send in callback. |
| CreateCombinedScanners(std::move(client_id), local_only, secure_only, |
| response.value_or(lorgnette::ListScannersResponse()), |
| std::move(callback)); |
| } |
| |
| // Handles the result of calling |
| // LorgnetteManagerClient::GetScannerCapabilities(). If getting the scanner |
| // capabilities fails, |scanner_name|, |device_name|, and |protocol| are used |
| // to mark the device name that was used as unusable and retry the |
| // operation with the next available device name. This pattern of trying |
| // each device name cannot be used when performing a scan since the backend |
| // used to obtain the capabilities must be the same backend used to perform |
| // the scan. |
| void OnScannerCapabilitiesResponse( |
| GetScannerCapabilitiesCallback callback, |
| const std::string& scanner_name, |
| const std::string& device_name, |
| const ScanProtocol protocol, |
| std::optional<lorgnette::ScannerCapabilities> capabilities) { |
| if (!capabilities) { |
| LOG(WARNING) << "Failed to get scanner capabilities using device name: " |
| << device_name; |
| MarkDeviceNameUnusable(scanner_name, device_name, protocol); |
| GetScannerCapabilities(scanner_name, std::move(callback)); |
| return; |
| } |
| |
| PRINTER_LOG(DEBUG) << "Scanner capabilities for " << scanner_name << " at " |
| << device_name << " => " |
| << ScannerCapabilitiesToString(capabilities.value()); |
| |
| std::move(callback).Run(capabilities); |
| } |
| |
| void OnOpenScannerResponse( |
| const lorgnette::ScannerId scanner_id, |
| OpenScannerCallback callback, |
| std::optional<lorgnette::OpenScannerResponse> response) { |
| if (response) { |
| PRINTER_LOG(EVENT) << "OpenScanner response received. Handle: " |
| << response->config().scanner().token(); |
| *response->mutable_scanner_id() = scanner_id; |
| } else { |
| PRINTER_LOG(ERROR) << "OpenScanner null response received."; |
| } |
| std::move(callback).Run(response); |
| } |
| |
| void OnCloseScannerResponse( |
| CloseScannerCallback callback, |
| std::optional<lorgnette::CloseScannerResponse> response) { |
| std::move(callback).Run(response); |
| } |
| |
| void OnSetOptionsResponse( |
| SetOptionsCallback callback, |
| std::optional<lorgnette::SetOptionsResponse> response) { |
| std::move(callback).Run(response); |
| } |
| |
| void OnGetCurrentConfigResponse( |
| GetCurrentConfigCallback callback, |
| std::optional<lorgnette::GetCurrentConfigResponse> response) { |
| std::move(callback).Run(response); |
| } |
| |
| void OnStartPreparedScanResponse( |
| StartPreparedScanCallback callback, |
| std::optional<lorgnette::StartPreparedScanResponse> response) { |
| std::move(callback).Run(response); |
| } |
| |
| // Return true if |scanner| should be included in the results based on |
| // |local_only| and |secure_only|, false if not. |
| bool ShouldIncludeScanner(const lorgnette::ScannerInfo& scanner, |
| LocalScannerFilter local_only, |
| SecureScannerFilter secure_only) { |
| if (local_only == LocalScannerFilter::kLocalScannersOnly && |
| scanner.connection_type() != lorgnette::CONNECTION_USB) { |
| return false; |
| } |
| |
| if (secure_only == SecureScannerFilter::kSecureScannersOnly && |
| !scanner.secure()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // For a given `scanner` return a list of ScannerInfo objects. One `scanner` |
| // may have multiple device_names where each one corresponds to a new |
| // ScannerInfo object. `scanners_to_verify` is a list owned and provided by |
| // the caller. If a scanner needs to be verified for connectivity before |
| // being returned to the caller, it will get inserted in this list instead of |
| // the returned list. |
| std::vector<lorgnette::ScannerInfo> CreateScannerInfosFromScanner( |
| const Scanner& scanner, |
| LocalScannerFilter local_only, |
| SecureScannerFilter secure_only, |
| std::vector<lorgnette::ScannerInfo>* scanners_to_verify) { |
| CHECK(scanners_to_verify); |
| std::vector<lorgnette::ScannerInfo> retval; |
| |
| // All ScannerInfo objects created from this scanner need to have the same |
| // UUID. If the scanner does not have a UUID, generate one to use. |
| const std::string uuid = |
| scanner.uuid.empty() |
| ? base::Uuid::GenerateRandomV4().AsLowercaseString() |
| : scanner.uuid; |
| |
| for (const auto& [protocol, device_names] : scanner.device_names) { |
| for (const ScannerDeviceName& device_name : device_names) { |
| if (!device_name.usable) { |
| continue; |
| } |
| lorgnette::ConnectionType connection_type = |
| lorgnette::CONNECTION_UNSPECIFIED; |
| bool secure = false; |
| bool need_to_verify = false; |
| switch (protocol) { |
| case (ScanProtocol::kEscl): |
| connection_type = lorgnette::CONNECTION_NETWORK; |
| secure = false; |
| break; |
| case (ScanProtocol::kEscls): |
| connection_type = lorgnette::CONNECTION_NETWORK; |
| secure = true; |
| break; |
| case (ScanProtocol::kLegacyNetwork): |
| // These types of scanners need to have their connectivity verified |
| // before they are returned to a client. |
| connection_type = lorgnette::CONNECTION_NETWORK; |
| secure = false; |
| need_to_verify = !verified_scanners_.contains( |
| CreateScannerId(uuid, device_name.device_name)); |
| break; |
| case (ScanProtocol::kLegacyUsb): |
| connection_type = lorgnette::CONNECTION_USB; |
| secure = true; |
| break; |
| default: |
| // Use defaults from above. |
| break; |
| } |
| |
| lorgnette::ScannerInfo info; |
| info.set_name(device_name.device_name); |
| info.set_manufacturer(scanner.manufacturer); |
| info.set_model(scanner.model); |
| info.set_display_name(scanner.display_name); |
| // TODO(nmuggli): See if there's a way to determine the type of scanner. |
| info.set_type("multi-function peripheral"); |
| info.set_device_uuid(uuid); |
| info.set_connection_type(connection_type); |
| info.set_secure(secure); |
| // TODO(b/308191406): SANE backend only supports JPG and PNG, so |
| // hardcode those for now. |
| info.add_image_format("image/jpeg"); |
| info.add_image_format("image/png"); |
| info.set_protocol_type(ProtocolTypeForScanner(info)); |
| if (ShouldIncludeScanner(info, local_only, secure_only)) { |
| if (need_to_verify) { |
| scanners_to_verify->emplace_back(std::move(info)); |
| } else { |
| retval.emplace_back(std::move(info)); |
| } |
| } |
| } |
| } |
| |
| return retval; |
| } |
| |
| // Use |response| and |zeroconf_scanners_| to build a combined |
| // ListScannersResponse that will be sent in |callback|. |local_only| and |
| // |secure_only| are used to filter out network scanners and/or non-secure |
| // scanners. |
| void CreateCombinedScanners(const std::string& client_id, |
| LocalScannerFilter local_only, |
| SecureScannerFilter secure_only, |
| const lorgnette::ListScannersResponse& response, |
| GetScannerInfoListCallback callback) { |
| lorgnette::ListScannersResponse combined_results; |
| combined_results.set_result(response.result()); |
| |
| for (const auto& scanner : response.scanners()) { |
| if (!ShouldIncludeScanner(scanner, local_only, secure_only)) { |
| continue; |
| } |
| |
| lorgnette::ScannerInfo* scanner_out = combined_results.add_scanners(); |
| *scanner_out = scanner; |
| |
| for (Scanner& zeroconf_scanner : zeroconf_scanners_) { |
| if (MergeDuplicateScannerRecords(scanner_out, zeroconf_scanner)) { |
| PRINTER_LOG(DEBUG) |
| << "Updating " << scanner.name() << ": " << scanner.display_name() |
| << " -> " << scanner_out->display_name(); |
| break; |
| } |
| } |
| } |
| |
| // Some of the zeroconf scanners may need to be verified before they can be |
| // returned to the caller. Keep track of those here. |
| std::vector<lorgnette::ScannerInfo> scanners_to_verify; |
| for (const Scanner& scanner : zeroconf_scanners_) { |
| for (auto& info : CreateScannerInfosFromScanner( |
| scanner, local_only, secure_only, &scanners_to_verify)) { |
| *combined_results.add_scanners() = std::move(info); |
| } |
| } |
| |
| // For any of the non-escl network zeroconf scanners, make sure the scanner |
| // is reachable before returning it to the user. |
| VerifyScanners(std::move(client_id), std::move(scanners_to_verify), |
| std::move(combined_results), std::move(callback)); |
| } |
| |
| void OnReadScanDataResponse( |
| ReadScanDataCallback callback, |
| std::optional<lorgnette::ReadScanDataResponse> response) { |
| std::move(callback).Run(response); |
| } |
| |
| void OnCancelScanResponse( |
| CancelScanCallback callback, |
| std::optional<lorgnette::CancelScanResponse> response) { |
| std::move(callback).Run(response); |
| } |
| |
| // Uses |response| and zeroconf_scanners_ to rebuild deduped_scanners_. |
| void RebuildDedupedScanners( |
| std::optional<lorgnette::ListScannersResponse> response) { |
| ResetDedupedScanners(); |
| ResetScannersToFilter(); |
| if (!response || response->scanners_size() == 0) |
| return; |
| |
| // Iterate through each lorgnette scanner and add its info to an existing |
| // Scanner if it has a matching IP address. Otherwise, create a new Scanner |
| // for the lorgnette scanner. |
| base::flat_map<net::IPAddress, std::string> known_ip_addresses = |
| GetKnownIpAddresses(); |
| for (const auto& lorgnette_scanner : response->scanners()) { |
| std::string ip_address_str; |
| ScanProtocol protocol = ScanProtocol::kUnknown; |
| ParseScannerName(lorgnette_scanner.name(), ip_address_str, protocol); |
| if (!ip_address_str.empty()) { |
| net::IPAddress ip_address; |
| if (ip_address.AssignFromIPLiteral(ip_address_str)) { |
| const auto it = known_ip_addresses.find(ip_address); |
| if (it != known_ip_addresses.end()) { |
| const auto existing = deduped_scanners_.find(it->second); |
| DCHECK(existing != deduped_scanners_.end()); |
| existing->second.device_names[protocol].emplace( |
| lorgnette_scanner.name()); |
| continue; |
| } |
| } |
| } |
| |
| const bool is_usb_scanner = protocol == ScanProtocol::kLegacyUsb; |
| const std::string base_name = |
| CreateBaseName(lorgnette_scanner, is_usb_scanner); |
| const std::string display_name = CreateUniqueDisplayName(base_name); |
| |
| Scanner scanner; |
| scanner.display_name = display_name; |
| scanner.device_names[protocol].emplace(lorgnette_scanner.name()); |
| deduped_scanners_[display_name] = scanner; |
| scanners_to_filter_.push_back(display_name); |
| } |
| } |
| |
| // Resets |deduped_scanners_| by clearing it and repopulating it with |
| // zeroconf_scanners_. |
| void ResetDedupedScanners() { |
| deduped_scanners_.clear(); |
| deduped_scanners_.reserve(zeroconf_scanners_.size()); |
| for (const auto& scanner : zeroconf_scanners_) |
| deduped_scanners_[scanner.display_name] = scanner; |
| } |
| |
| // Resets |scanners_to_filter| by clearing it and repopulating it with |
| // zeroconf_scanners_ names. |
| void ResetScannersToFilter() { |
| scanners_to_filter_.clear(); |
| scanners_to_filter_.reserve(zeroconf_scanners_.size()); |
| for (const auto& scanner : zeroconf_scanners_) |
| scanners_to_filter_.push_back(scanner.display_name); |
| } |
| |
| // Returns a map of IP addresses to the display names (lookup keys) of |
| // scanners they correspond to in deduped_scanners_. This enables |
| // deduplication of network scanners by making it easy to check for and modify |
| // them using their IP addresses. |
| base::flat_map<net::IPAddress, std::string> GetKnownIpAddresses() { |
| base::flat_map<net::IPAddress, std::string> known_ip_addresses; |
| for (auto& entry : deduped_scanners_) { |
| for (const auto& ip_address : entry.second.ip_addresses) |
| known_ip_addresses[ip_address] = entry.second.display_name; |
| } |
| |
| return known_ip_addresses; |
| } |
| |
| // Creates a unique display name by appending a copy number to a duplicate |
| // name (e.g. if Scanner Name already exists, the second instance will be |
| // renamed Scanner Name (1)). |
| std::string CreateUniqueDisplayName(const std::string& base_name) { |
| std::string display_name = base_name; |
| int i = 1; // The first duplicate will become "Scanner Name (1)." |
| while (deduped_scanners_.find(display_name) != deduped_scanners_.end()) { |
| display_name = base::StringPrintf("%s (%d)", base_name.c_str(), i); |
| i++; |
| } |
| |
| return display_name; |
| } |
| |
| // Gets the first usable device name corresponding to the highest priority |
| // protocol for the scanner specified by |scanner_name|. Returns true on |
| // success, false on failure. |
| bool GetUsableDeviceNameAndProtocol(const std::string& scanner_name, |
| std::string& device_name_out, |
| ScanProtocol& protocol_out) { |
| const auto scanner_it = deduped_scanners_.find(scanner_name); |
| if (scanner_it == deduped_scanners_.end()) { |
| PRINTER_LOG(ERROR) << "Failed to find scanner with name " << scanner_name; |
| return false; |
| } |
| |
| for (const auto& protocol : kPrioritizedProtocols) { |
| const auto device_names_it = |
| scanner_it->second.device_names.find(protocol); |
| if (device_names_it == scanner_it->second.device_names.end()) |
| continue; |
| |
| for (const ScannerDeviceName& name : device_names_it->second) { |
| if (name.usable) { |
| device_name_out = name.device_name; |
| protocol_out = protocol; |
| return true; |
| } |
| } |
| } |
| |
| PRINTER_LOG(ERROR) << "Failed to find usable device name for " |
| << scanner_name; |
| return false; |
| } |
| |
| // Marks a device name as unusable to prevent it from being returned by future |
| // calls to GetUsableDeviceNameAndProtocol(). |
| void MarkDeviceNameUnusable(const std::string& scanner_name, |
| const std::string& device_name, |
| const ScanProtocol protocol) { |
| auto scanner_it = deduped_scanners_.find(scanner_name); |
| if (scanner_it == deduped_scanners_.end()) |
| return; |
| |
| auto device_names_it = scanner_it->second.device_names.find(protocol); |
| if (device_names_it == scanner_it->second.device_names.end()) |
| return; |
| |
| for (ScannerDeviceName& name : device_names_it->second) { |
| if (name.device_name == device_name) { |
| name.usable = false; |
| return; |
| } |
| } |
| } |
| |
| // Used to detect zeroconf scanners. |
| std::unique_ptr<ZeroconfScannerDetector> zeroconf_scanner_detector_; |
| |
| // The deduplicated zeroconf scanners reported by the |
| // zeroconf_scanner_detector_. |
| std::vector<Scanner> zeroconf_scanners_; |
| |
| // Stores the deduplicated scanners from all sources in a map of display name |
| // to Scanner. Clients are given display names and can use them to |
| // interact with the corresponding scanners. |
| base::flat_map<std::string, Scanner> deduped_scanners_; |
| |
| // Stores a list of scanner display names to check while filtering. |
| std::vector<std::string> scanners_to_filter_; |
| |
| // Stores the UUID for zeroconf scanners that have already been verified. |
| std::set<std::string> verified_scanners_; |
| |
| // For each client that has called GetScannerInfoList, maps scanner tokens |
| // back to the original UUID and SANE connection string needed to open the |
| // device. |
| std::map<std::string, TokenToScannerId> client_tokens_; |
| |
| // Controls scanner notifications. |
| std::unique_ptr<LorgnetteNotificationController> |
| lorgnette_notification_controller_; |
| |
| SEQUENCE_CHECKER(sequence_); |
| |
| base::WeakPtrFactory<LorgnetteScannerManagerImpl> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<LorgnetteScannerManager> LorgnetteScannerManager::Create( |
| std::unique_ptr<ZeroconfScannerDetector> zeroconf_scanner_detector, |
| Profile* profile) { |
| PRINTER_LOG(EVENT) << "LorgnetteScannerManager::Create"; |
| return std::make_unique<LorgnetteScannerManagerImpl>( |
| std::move(zeroconf_scanner_detector), profile); |
| } |
| |
| } // namespace ash |