// 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-C6000"
"|DS-790WN"
"|DS-C420W"
"|DS-C480W"
"|EM-C800"
"|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-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-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(
const 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