Commit f25dad99 authored by Jesse Schettler's avatar Jesse Schettler Committed by Commit Bot

scanning: Create ZeroconfScannerDetector

Create a ZeroconfScannerDetector to detect network scanners using mDNS
and DNS-SD. The detector detects eSCL and eSCLS scanners.

The detector stores information for each detected scanner, but clients
receive a deduplicated list of scanners (i.e. if the same scanner is
reported for more than one service type, the information from each
detection is merged before being sent to clients).

Bug: b:153541027
Change-Id: Ia69d98ca176f2d6d09f8c6db94ce30d8f96b9249
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2307619Reviewed-by: default avatarSean Kau <skau@chromium.org>
Reviewed-by: default avatarZentaro Kavanagh <zentaro@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Commit-Queue: Jesse Schettler <jschettler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#796135}
parent b2e1d317
...@@ -2392,6 +2392,11 @@ source_set("chromeos") { ...@@ -2392,6 +2392,11 @@ source_set("chromeos") {
"remote_apps/remote_apps_model.cc", "remote_apps/remote_apps_model.cc",
"remote_apps/remote_apps_model.h", "remote_apps/remote_apps_model.h",
"reset/metrics.h", "reset/metrics.h",
"scanning/scanner_detector.h",
"scanning/zeroconf_scanner_detector.cc",
"scanning/zeroconf_scanner_detector.h",
"scanning/zeroconf_scanner_detector_utils.cc",
"scanning/zeroconf_scanner_detector_utils.h",
"scheduler_configuration_manager.cc", "scheduler_configuration_manager.cc",
"scheduler_configuration_manager.h", "scheduler_configuration_manager.h",
"secure_channel/secure_channel_client_provider.cc", "secure_channel/secure_channel_client_provider.cc",
...@@ -3419,6 +3424,7 @@ source_set("unit_tests") { ...@@ -3419,6 +3424,7 @@ source_set("unit_tests") {
"release_notes/release_notes_notification_unittest.cc", "release_notes/release_notes_notification_unittest.cc",
"release_notes/release_notes_storage_unittest.cc", "release_notes/release_notes_storage_unittest.cc",
"remote_apps/remote_apps_model_unittest.cc", "remote_apps/remote_apps_model_unittest.cc",
"scanning/zeroconf_scanner_detector_unittest.cc",
"scheduler_configuration_manager_unittest.cc", "scheduler_configuration_manager_unittest.cc",
"session_length_limiter_unittest.cc", "session_length_limiter_unittest.cc",
"settings/cros_settings_unittest.cc", "settings/cros_settings_unittest.cc",
......
file://chromeos/scanning/OWNERS
// Copyright 2020 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 CHROME_BROWSER_CHROMEOS_SCANNING_SCANNER_DETECTOR_H_
#define CHROME_BROWSER_CHROMEOS_SCANNING_SCANNER_DETECTOR_H_
#include <vector>
#include "base/callback.h"
#include "chromeos/scanning/scanner.h"
namespace chromeos {
// Interface for automatic scanner detection. This API allows for incremental
// discovery of scanners and provides a notification when discovery is complete.
//
// All of the interface calls in this class must be called from the same
// sequence but do not have to be on any specific thread.
//
// The usual usage of this interface by a class that wants to maintain an
// up-to-date list of detected scanners is:
//
// auto detector_ = ScannerDetectorImplementation::Create();
// detector_->RegisterScannersDetectedCallback(callback);
// scanners_ = detector_->GetScanners();
//
class CHROMEOS_EXPORT ScannerDetector {
public:
virtual ~ScannerDetector() = default;
// Registers the callback used to provide notifications when scanners are
// detected.
using OnScannersDetectedCallback =
base::RepeatingCallback<void(const std::vector<Scanner>& scanners)>;
virtual void RegisterScannersDetectedCallback(
OnScannersDetectedCallback callback) = 0;
// Returns the detected scanners.
virtual std::vector<Scanner> GetScanners() = 0;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_SCANNING_SCANNER_DETECTOR_H_
// Copyright 2020 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 "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
#include <array>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
#include "chrome/browser/local_discovery/service_discovery_shared_client.h"
#include "chromeos/scanning/scanner.h"
namespace chromeos {
// Supported service types for scanners.
const char ZeroconfScannerDetector::kEsclServiceType[] = "_uscan._tcp.local";
const char ZeroconfScannerDetector::kEsclsServiceType[] = "_uscans._tcp.local";
constexpr std::array<const char*, 2> kServiceTypes = {
ZeroconfScannerDetector::kEsclsServiceType,
ZeroconfScannerDetector::kEsclServiceType,
};
namespace {
using local_discovery::ServiceDescription;
using local_discovery::ServiceDiscoveryDeviceLister;
using local_discovery::ServiceDiscoverySharedClient;
// TODO(jschettler): Update this class once the eSCL specification is released.
// These fields (including the default values) come from the eSCL specification.
// Not all of these will necessarily be specified for a given scanner. Also,
// unused fields are excluded here.
class ParsedMetadata {
public:
explicit ParsedMetadata(const ServiceDescription& service_description) {
for (const std::string& entry : service_description.metadata) {
const base::StringPiece key_value(entry);
const size_t equal_pos = key_value.find("=");
if (equal_pos == base::StringPiece::npos)
continue;
const base::StringPiece key = key_value.substr(0, equal_pos);
const base::StringPiece value = key_value.substr(equal_pos + 1);
if (key == "rs")
rs_ = value.as_string();
}
}
ParsedMetadata(const ParsedMetadata&) = delete;
ParsedMetadata& operator=(const ParsedMetadata&) = delete;
~ParsedMetadata() = default;
const std::string& rs() const { return rs_; }
private:
// Used to construct the path for a device name URL.
std::string rs_ = "none";
};
// Attempts to create a Scanner using the information in |service_description|
// and |metadata|. Returns the Scanner on success, base::nullopt on failure.
base::Optional<Scanner> CreateScanner(
const std::string& service_type,
const ServiceDescription& service_description,
const ParsedMetadata& metadata) {
// If there isn't enough information available to interact with the scanner,
// fail. Also fail if the port number is 0, as this is used to indicate that
// the service doesn't *actually* exist, the device just wants to guard the
// name.
if (service_description.service_name.empty() ||
service_description.ip_address.empty() ||
service_description.address.port() == 0) {
return base::nullopt;
}
return CreateSaneAirscanScanner(
service_description.instance_name(), service_type, metadata.rs(),
service_description.ip_address, service_description.address.port());
}
class ZeroconfScannerDetectorImpl final : public ZeroconfScannerDetector {
public:
// Normal constructor that connects to service discovery.
ZeroconfScannerDetectorImpl()
: discovery_client_(ServiceDiscoverySharedClient::GetInstance()) {}
// Testing constructor that uses injected backends.
explicit ZeroconfScannerDetectorImpl(ListersMap&& device_listers) {
device_listers_ = std::move(device_listers);
for (auto& lister : device_listers_) {
lister.second->Start();
lister.second->DiscoverNewDevices();
}
}
ZeroconfScannerDetectorImpl(const ZeroconfScannerDetectorImpl&) = delete;
ZeroconfScannerDetectorImpl& operator=(const ZeroconfScannerDetectorImpl&) =
delete;
~ZeroconfScannerDetectorImpl() override = default;
// Initializes the detector by creating its device listers.
void Init() {
for (const char* service_type : kServiceTypes)
CreateDeviceLister(service_type);
}
// ScannerDetector:
void RegisterScannersDetectedCallback(
OnScannersDetectedCallback callback) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
DCHECK(!on_scanners_detected_callback_);
on_scanners_detected_callback_ = std::move(callback);
}
// ScannerDetector:
std::vector<Scanner> GetScanners() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
return GetDedupedScanners();
}
// ServiceDiscoveryDeviceLister::Delegate:
void OnDeviceChanged(const std::string& service_type,
bool added,
const ServiceDescription& service_description) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
// Generate an update whether the device was added or not.
ParsedMetadata metadata(service_description);
auto scanner = CreateScanner(service_type, service_description, metadata);
if (!scanner.has_value())
return;
scanners_[service_description.service_name] = scanner.value();
if (on_scanners_detected_callback_)
on_scanners_detected_callback_.Run(GetDedupedScanners());
}
// ServiceDiscoveryDeviceLister::Delegate:
void OnDeviceRemoved(const std::string& service_type,
const std::string& service_name) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (scanners_.erase(service_name)) {
if (on_scanners_detected_callback_)
on_scanners_detected_callback_.Run(GetDedupedScanners());
} else {
LOG(WARNING) << "Device removal requested for unknown service: "
<< service_name;
}
}
// ServiceDiscoveryDeviceLister::Delegate:
// Removes all devices that originated on all service types and requests a new
// round of discovery. Clears all scanners to avoid returning stale cached
// scanners.
void OnDeviceCacheFlushed(const std::string& service_type) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (!scanners_.empty()) {
scanners_.clear();
if (on_scanners_detected_callback_)
on_scanners_detected_callback_.Run(GetDedupedScanners());
}
// Request a new round of discovery from the lister.
auto lister_entry = device_listers_.find(service_type);
DCHECK(lister_entry != device_listers_.end());
lister_entry->second->DiscoverNewDevices();
}
private:
// Creates a new device lister for the given |service_type| and adds it to the
// ones managed by this object.
void CreateDeviceLister(const std::string& service_type) {
auto lister = ServiceDiscoveryDeviceLister::Create(
this, discovery_client_.get(), service_type);
lister->Start();
lister->DiscoverNewDevices();
DCHECK(!base::Contains(device_listers_, service_type));
device_listers_[service_type] = std::move(lister);
}
// Returns the detected scanners after merging duplicates.
std::vector<Scanner> GetDedupedScanners() {
// Use a map of display name to Scanner to deduplicate the detected
// scanners. If a Scanner has the same display name as one that's already
// been added to the map, merge the two by adding the new Scanner's
// information to the existing Scanner.
base::flat_map<std::string, Scanner> deduped_scanners;
for (const auto& entry : scanners_) {
const Scanner* scanner = &entry.second;
auto it = deduped_scanners.find(scanner->display_name);
if (it == deduped_scanners.end()) {
deduped_scanners.insert({scanner->display_name, *scanner});
} else {
// Each Scanner in scanners_ should have a single device name
// corresponding to a known protocol.
ScanProtocol protocol = ScanProtocol::kUnknown;
if (scanner->device_names.find(ScanProtocol::kEscls) !=
scanner->device_names.end()) {
protocol = ScanProtocol::kEscls;
} else if (scanner->device_names.find(ScanProtocol::kEscl) !=
scanner->device_names.end()) {
protocol = ScanProtocol::kEscl;
} else {
NOTREACHED() << "Zeroconf scanner with unknown protocol.";
}
it->second.device_names[protocol].insert(
scanner->device_names.at(protocol).begin(),
scanner->device_names.at(protocol).end());
it->second.ip_addresses.insert(scanner->ip_addresses.begin(),
scanner->ip_addresses.end());
}
}
std::vector<Scanner> scanners;
scanners.reserve(deduped_scanners.size());
for (const auto& entry : deduped_scanners)
scanners.push_back(entry.second);
return scanners;
}
SEQUENCE_CHECKER(sequence_);
// Map from service name to Scanner.
base::flat_map<std::string, Scanner> scanners_;
// Keep a reference to the shared device client around for the lifetime of
// this object.
scoped_refptr<ServiceDiscoverySharedClient> discovery_client_;
// Map from service_type to associated lister.
ListersMap device_listers_;
// Callback used to notify when scanners are detected.
OnScannersDetectedCallback on_scanners_detected_callback_;
};
} // namespace
// static
std::unique_ptr<ZeroconfScannerDetector> ZeroconfScannerDetector::Create() {
std::unique_ptr<ZeroconfScannerDetectorImpl> detector =
std::make_unique<ZeroconfScannerDetectorImpl>();
detector->Init();
return std::move(detector);
}
// static
std::unique_ptr<ZeroconfScannerDetector>
ZeroconfScannerDetector::CreateForTesting(ListersMap&& device_listers) {
return std::make_unique<ZeroconfScannerDetectorImpl>(
std::move(device_listers));
}
} // namespace chromeos
// Copyright 2020 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 CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
#define CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
#include <memory>
#include <string>
#include "base/containers/flat_map.h"
#include "chrome/browser/chromeos/scanning/scanner_detector.h"
#include "chrome/browser/local_discovery/service_discovery_device_lister.h"
namespace chromeos {
// Uses mDNS and DNS-SD to detect nearby networked scanners.
class ZeroconfScannerDetector
: public ScannerDetector,
public local_discovery::ServiceDiscoveryDeviceLister::Delegate {
public:
// Service types used by the detector.
static const char kEsclServiceType[];
static const char kEsclsServiceType[];
~ZeroconfScannerDetector() override = default;
static std::unique_ptr<ZeroconfScannerDetector> Create();
// Creates an instance that uses the passed device listers instead of creating
// its own. |device_listers| is a map from service type to lister and should
// supply a lister for each of the service types used by the detector.
// Ownership is taken of the listers map.
using ListersMap = base::flat_map<
std::string,
std::unique_ptr<local_discovery::ServiceDiscoveryDeviceLister>>;
static std::unique_ptr<ZeroconfScannerDetector> CreateForTesting(
ListersMap&& device_listers);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
// Copyright 2020 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 "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
#include <algorithm>
#include <functional>
#include <iterator>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/strings/strcat.h"
#include "base/test/task_environment.h"
#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
#include "chrome/browser/local_discovery/fake_service_discovery_device_lister.h"
#include "chrome/browser/local_discovery/service_discovery_device_lister.h"
#include "chromeos/scanning/scanner.h"
#include "net/base/ip_address.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace {
using local_discovery::FakeServiceDiscoveryDeviceLister;
using local_discovery::ServiceDescription;
using local_discovery::ServiceDiscoveryDeviceLister;
MATCHER_P(ScannerIsEqual, expected_scanner, "") {
return arg.display_name == expected_scanner.display_name &&
arg.device_names == expected_scanner.device_names &&
arg.ip_addresses == expected_scanner.ip_addresses;
}
MATCHER_P(ScannersAreEqual, expected_scanners, "") {
if (arg.size() != expected_scanners.size())
return false;
std::vector<Scanner> sorted_expected = expected_scanners;
std::vector<Scanner> sorted_actual = arg;
std::sort(sorted_expected.begin(), sorted_expected.end(),
[](const Scanner& a, const Scanner& b) -> bool {
return a.display_name < b.display_name;
});
std::sort(sorted_actual.begin(), sorted_actual.end(),
[](const Scanner& a, const Scanner& b) -> bool {
return a.display_name < b.display_name;
});
for (size_t i = 0; i < sorted_expected.size(); ++i)
EXPECT_THAT(sorted_actual[i], ScannerIsEqual(sorted_expected[i]));
return true;
}
// TODO(jschettler): Move these functions and the copies in
// zeroconf_printer_detector_unittest.cc into a shared file.
// Determine basic scanner attributes deterministically but pseudorandomly based
// on the scanner name. The exact values returned here are not important. The
// important parts are that there's variety based on the name, and it's
// deterministic.
// Gets an IP address for this scanner. The returned address may be IPv4 or
// IPv6.
net::IPAddress GetIPAddressFor(const std::string& name) {
std::mt19937 rng(std::hash<std::string>()(name));
if (rng() & 1) {
// Give an IPv4 address.
return net::IPAddress(rng(), rng(), rng(), rng());
}
// Give an IPv6 address.
return net::IPAddress(rng(), rng(), rng(), rng(), rng(), rng(), rng(), rng(),
rng(), rng(), rng(), rng(), rng(), rng(), rng(), rng());
}
// Gets a port number for this scanner.
int GetPortFor(const std::string& name) {
return (std::hash<std::string>()(name) % 1000) + 1;
}
// This corresponds to MakeServiceDescription() below. Given the same name and
// correct service type, this generates the expected Scanner that the
// ZeroconfScannerDetector should create when it gets the ServiceDescription
// created by MakeServiceDescription(). This needs to be kept in sync with
// MakeServiceDescription().
Scanner MakeExpectedScanner(const std::string& name,
const std::string& service_type,
const std::string& rs) {
const net::IPAddress ip_address = GetIPAddressFor(name);
const int port = GetPortFor(name);
auto scanner =
CreateSaneAirscanScanner(name, service_type, rs, ip_address, port);
return scanner.value();
}
// Merges all of the Scanners in |scanners| into a single Scanner. Used to
// create the expected result of a scanner announced by more than one lister.
Scanner MergeScanners(const std::vector<Scanner>& scanners) {
if (scanners.empty())
return Scanner();
Scanner merged_scanner = scanners[0];
for (auto it = std::next(scanners.begin()); it != scanners.end(); ++it) {
merged_scanner.device_names.insert(it->device_names.begin(),
it->device_names.end());
merged_scanner.ip_addresses.insert(it->ip_addresses.begin(),
it->ip_addresses.end());
}
return merged_scanner;
}
// Creates a deterministic ServiceDescription based on the service name and
// type. See the note on MakeExpectedScanner() above. This must be kept in sync
// with MakeExpectedScanner().
ServiceDescription MakeServiceDescription(const std::string& name,
const std::string& service_type,
const std::string& rs) {
ServiceDescription service_description;
service_description.service_name = base::StrCat({name, ".", service_type});
service_description.address.set_host(base::StrCat({name, ".local"}));
service_description.address.set_port(GetPortFor(name));
service_description.ip_address = GetIPAddressFor(name);
service_description.metadata.push_back(base::StrCat({"rs=", rs}));
return service_description;
}
} // namespace
class ZeroconfScannerDetectorTest : public testing::Test {
public:
ZeroconfScannerDetectorTest() = default;
~ZeroconfScannerDetectorTest() override = default;
void SetUp() override {
auto* runner = task_environment_.GetMainThreadTaskRunner().get();
auto escl_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
runner, ZeroconfScannerDetector::kEsclServiceType);
escl_lister_ = escl_lister.get();
auto escls_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
runner, ZeroconfScannerDetector::kEsclsServiceType);
escls_lister_ = escls_lister.get();
listers_[ZeroconfScannerDetector::kEsclServiceType] =
std::move(escl_lister);
listers_[ZeroconfScannerDetector::kEsclsServiceType] =
std::move(escls_lister);
}
void CreateDetector() {
detector_ = ZeroconfScannerDetector::CreateForTesting(std::move(listers_));
// Ownership of the previously allocated listers map is transferred to the
// detector, so the unique_ptr values of the listers map are no longer valid
// at this point. The lister's raw pointers are kept as separate members to
// keep the lister fakes accessible after ownership is transferred into the
// detector.
listers_.clear();
detector_->RegisterScannersDetectedCallback(
base::BindRepeating(&ZeroconfScannerDetectorTest::OnScannersDetected,
base::Unretained(this)));
escl_lister_->SetDelegate(detector_.get());
escls_lister_->SetDelegate(detector_.get());
}
// ScannerDetector callback.
void OnScannersDetected(const std::vector<Scanner>& scanners) {
scanners_ = scanners;
}
protected:
// Runs pending tasks regardless of delay.
void CompleteTasks() { task_environment_.FastForwardUntilNoTasksRemain(); }
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
// Device lister fakes. These are initialized when the test is constructed.
// These pointers don't involve ownership; ownership of the listers starts
// with this class in listers_ when the test starts and is transferred to
// detector_ when the detector is created. Throughout, the listers remain
// available to the test via these pointers.
FakeServiceDiscoveryDeviceLister* escl_lister_;
FakeServiceDiscoveryDeviceLister* escls_lister_;
// Detector under test.
std::unique_ptr<ZeroconfScannerDetector> detector_;
// Latest scanners received in OnScannersDetected().
std::vector<Scanner> scanners_;
private:
// Temporary storage for the device listers, between the time the test is
// constructed and the detector is created. Tests shouldn't access this
// directly, use the *_lister_ variables instead.
ZeroconfScannerDetector::ListersMap listers_;
};
// Test that an eSCL scanner can be detected.
TEST_F(ZeroconfScannerDetectorTest, EsclScanner) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that an eSCLS scanner can be detected.
TEST_F(ZeroconfScannerDetectorTest, EsclsScanner) {
escls_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclsServiceType, ""));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclsServiceType, "")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that the same scanner detected by two listers is merged into a single
// Scanner.
TEST_F(ZeroconfScannerDetectorTest, MergedScanner) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
escls_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclsServiceType, ""));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MergeScanners(
{MakeExpectedScanner("Scanner1",
ZeroconfScannerDetector::kEsclServiceType, ""),
MakeExpectedScanner("Scanner1",
ZeroconfScannerDetector::kEsclsServiceType, "")})};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that two separate scanners can be detected.
TEST_F(ZeroconfScannerDetectorTest, EsclAndEsclsScanners) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
escls_lister_->Announce(MakeServiceDescription(
"Scanner2", ZeroconfScannerDetector::kEsclsServiceType, ""));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {
MakeExpectedScanner("Scanner1", ZeroconfScannerDetector::kEsclServiceType,
""),
MakeExpectedScanner("Scanner2",
ZeroconfScannerDetector::kEsclsServiceType, "")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that calling GetScanners() returns the same scanners reported in
// OnScannersDetected().
TEST_F(ZeroconfScannerDetectorTest, GetScanners) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
EXPECT_THAT(detector_->GetScanners(), ScannersAreEqual(scanners_));
}
// Test that the detector detects a scanner that is announced after its
// creation.
TEST_F(ZeroconfScannerDetectorTest, AnnounceAfterDetectorCreation) {
CreateDetector();
CompleteTasks();
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that failing to parse the service metadata is handled gracefully.
TEST_F(ZeroconfScannerDetectorTest, InvalidMetadata) {
ServiceDescription service_description = MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "");
service_description.metadata = {"no_equal_sign"};
escl_lister_->Announce(service_description);
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that a service without a service name does not get added as a detected
// scanner.
TEST_F(ZeroconfScannerDetectorTest, NoServiceName) {
ServiceDescription service_description = MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "");
service_description.service_name = "";
escl_lister_->Announce(service_description);
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that a service without an IP address does not get added as a detected
// scanner.
TEST_F(ZeroconfScannerDetectorTest, NoIpAddress) {
ServiceDescription service_description = MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "");
service_description.ip_address = net::IPAddress();
escl_lister_->Announce(service_description);
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that a service with a port number of 0 does not get added as a detected
// scanner.
TEST_F(ZeroconfScannerDetectorTest, PortIs0) {
ServiceDescription service_description = MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "");
service_description.address.set_port(0);
escl_lister_->Announce(service_description);
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that a valid "rs" value gets incorporated into the device name.
TEST_F(ZeroconfScannerDetectorTest, Rs) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "test"));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "test")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that providing no "rs" value results in the default path being used in
// the device name.
TEST_F(ZeroconfScannerDetectorTest, NoRs) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none"));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that a detected scanner can be removed.
TEST_F(ZeroconfScannerDetectorTest, RemoveAddedScanner) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none"));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
escl_lister_->Remove("Scanner1");
CompleteTasks();
expected_scanners.clear();
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that removing an undetected scanner is ignored.
TEST_F(ZeroconfScannerDetectorTest, RemoveUnaddedScanner) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none"));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, "none")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
escl_lister_->Remove("Scanner2");
CompleteTasks();
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that removing a scanner from only one of two listers it was announced on
// does not completely remove the scanner (i.e. it goes from being a merged
// scanner to a single unmerged scanner).
TEST_F(ZeroconfScannerDetectorTest, RemovePartOfMergedScanner) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
escls_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclsServiceType, ""));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {MergeScanners(
{MakeExpectedScanner("Scanner1",
ZeroconfScannerDetector::kEsclServiceType, ""),
MakeExpectedScanner("Scanner1",
ZeroconfScannerDetector::kEsclsServiceType, "")})};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
escl_lister_->Remove("Scanner1");
CompleteTasks();
expected_scanners = {MakeExpectedScanner(
"Scanner1", ZeroconfScannerDetector::kEsclsServiceType, "")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
}
// Test that a cache flush correctly removes scanners.
TEST_F(ZeroconfScannerDetectorTest, CacheFlush) {
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
escl_lister_->Announce(MakeServiceDescription(
"Scanner2", ZeroconfScannerDetector::kEsclServiceType, "none"));
escls_lister_->Announce(MakeServiceDescription(
"Scanner3", ZeroconfScannerDetector::kEsclsServiceType, ""));
escls_lister_->Announce(MakeServiceDescription(
"Scanner4", ZeroconfScannerDetector::kEsclsServiceType, "test"));
escl_lister_->Announce(MakeServiceDescription(
"Scanner5", ZeroconfScannerDetector::kEsclServiceType, "none"));
CreateDetector();
CompleteTasks();
std::vector<Scanner> expected_scanners = {
MakeExpectedScanner("Scanner1", ZeroconfScannerDetector::kEsclServiceType,
""),
MakeExpectedScanner("Scanner2", ZeroconfScannerDetector::kEsclServiceType,
"none"),
MakeExpectedScanner("Scanner3",
ZeroconfScannerDetector::kEsclsServiceType, ""),
MakeExpectedScanner("Scanner4",
ZeroconfScannerDetector::kEsclsServiceType, "test"),
MakeExpectedScanner("Scanner5", ZeroconfScannerDetector::kEsclServiceType,
"none")};
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
escls_lister_->Clear();
CompleteTasks();
// With the eSCLS lister cleared, all scanners should be cleared.
expected_scanners.clear();
EXPECT_THAT(scanners_, ScannersAreEqual(expected_scanners));
// Discovery should have started after dealing with the cache flush.
EXPECT_TRUE(escls_lister_->discovery_started());
}
// Verify tasks are cleaned up properly when the detector is destroyed.
TEST_F(ZeroconfScannerDetectorTest, DestroyedWithTasksPending) {
CreateDetector();
escl_lister_->Announce(MakeServiceDescription(
"Scanner1", ZeroconfScannerDetector::kEsclServiceType, ""));
// Run listers but don't run the delayed tasks.
task_environment_.RunUntilIdle();
detector_.reset();
CompleteTasks();
SUCCEED();
}
} // namespace chromeos
// Copyright 2020 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 "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
#include <string>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
#include "url/gurl.h"
namespace chromeos {
namespace {
// Sets the values of |scheme| and |protocol| based on |service_type|.
void SetSchemeAndProtocol(const std::string& service_type,
std::string& scheme_out,
ScanProtocol& protocol_out) {
if (service_type == ZeroconfScannerDetector::kEsclsServiceType) {
scheme_out = "https";
protocol_out = ScanProtocol::kEscls;
} else if (service_type == ZeroconfScannerDetector::kEsclServiceType) {
scheme_out = "http";
protocol_out = ScanProtocol::kEscl;
} else {
NOTREACHED() << "Zeroconf scanner with unknown service type: "
<< service_type;
}
}
// Creates a device name compatible with the sane-airscan backend. Returns the
// name on success and an empty string on failure.
std::string CreateDeviceName(const std::string& name,
const std::string& scheme,
const std::string& rs,
const net::IPAddress& ip_address,
int port) {
std::string path;
if (rs == "none")
path = "eSCL/";
else if (!rs.empty())
path = rs + "/";
const std::string ip_address_str =
ip_address.IsIPv6()
? base::StringPrintf("[%s]", ip_address.ToString().c_str())
: ip_address.ToString();
GURL url(base::StringPrintf("%s://%s:%d/%s", scheme.c_str(),
ip_address_str.c_str(), port, path.c_str()));
if (!url.is_valid()) {
LOG(ERROR) << "Cannot create device name with invalid URL: "
<< url.possibly_invalid_spec();
return "";
}
return base::StringPrintf("airscan:escl:%s:%s", name.c_str(),
url.spec().c_str());
}
} // namespace
base::Optional<Scanner> CreateSaneAirscanScanner(
const std::string& name,
const std::string& service_type,
const std::string& rs,
const net::IPAddress& ip_address,
int port) {
std::string scheme;
ScanProtocol protocol = ScanProtocol::kUnknown;
SetSchemeAndProtocol(service_type, scheme, protocol);
const std::string device_name =
CreateDeviceName(name, scheme, rs, ip_address, port);
if (device_name.empty())
return base::nullopt;
Scanner scanner;
scanner.display_name = name;
scanner.device_names[protocol].insert(ScannerDeviceName(device_name));
scanner.ip_addresses.insert(ip_address);
return scanner;
}
} // namespace chromeos
// Copyright 2020 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 CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
#define CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
#include <string>
#include "base/optional.h"
#include "chromeos/scanning/scanner.h"
#include "net/base/ip_address.h"
namespace chromeos {
// Creates a Scanner with a device name that can be used to interact with a
// scanner via the sane-airscan backend. If errors occur, base::nullopts is
// returned. The device name must be in the format "airscan:escl:name:url",
// where name is an arbitrary name. The IP address is used instead of the host
// name since sane-airscan may not be able to resolve host names it did not
// discover itself. See mdns_make_escl_endpoint() at
// https://github.com/alexpevzner/sane-airscan/blob/master/airscan-mdns.c for
// more details.
base::Optional<Scanner> CreateSaneAirscanScanner(
const std::string& name,
const std::string& service_type,
const std::string& rs,
const net::IPAddress& ip_address,
int port);
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
...@@ -85,6 +85,8 @@ component("chromeos") { ...@@ -85,6 +85,8 @@ component("chromeos") {
"process_proxy/process_proxy.h", "process_proxy/process_proxy.h",
"process_proxy/process_proxy_registry.cc", "process_proxy/process_proxy_registry.cc",
"process_proxy/process_proxy_registry.h", "process_proxy/process_proxy_registry.h",
"scanning/scanner.cc",
"scanning/scanner.h",
] ]
if (current_cpu == "arm" || current_cpu == "x86") { if (current_cpu == "arm" || current_cpu == "x86") {
defines = [ "BINDER_IPC_32BIT" ] defines = [ "BINDER_IPC_32BIT" ]
......
jschettler@chromium.org
skau@chromium.org
# Backup reviewers:
zentaro@chromium.org
// Copyright 2020 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/scanning/scanner.h"
namespace chromeos {
ScannerDeviceName::ScannerDeviceName(const std::string& device_name)
: device_name(device_name) {}
ScannerDeviceName::~ScannerDeviceName() = default;
ScannerDeviceName::ScannerDeviceName(const ScannerDeviceName& other) = default;
ScannerDeviceName& ScannerDeviceName::operator=(
const ScannerDeviceName& other) = default;
bool ScannerDeviceName::operator<(const ScannerDeviceName& other) const {
return device_name < other.device_name;
}
bool ScannerDeviceName::operator==(const ScannerDeviceName& other) const {
return device_name == other.device_name;
}
Scanner::Scanner() = default;
Scanner::~Scanner() = default;
Scanner::Scanner(const Scanner& other) = default;
Scanner& Scanner::operator=(const Scanner& other) = default;
} // namespace chromeos
// Copyright 2020 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_SCANNING_SCANNER_H_
#define CHROMEOS_SCANNING_SCANNER_H_
#include <string>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "chromeos/chromeos_export.h"
#include "net/base/ip_address.h"
namespace chromeos {
// The type of protocol used to communicate with a scanner.
enum class CHROMEOS_EXPORT ScanProtocol {
kUnknown,
kEscl, // eSCL protocol.
kEscls, // eSCLS protocol.
kLegacyNetwork, // Non-eSCL(S), legacy network protocol.
kLegacyUsb // Non-eSCL(S), legacy USB protocol.
};
struct CHROMEOS_EXPORT ScannerDeviceName {
explicit ScannerDeviceName(const std::string& device_name);
~ScannerDeviceName();
ScannerDeviceName(const ScannerDeviceName& scanner_device_name);
ScannerDeviceName& operator=(const ScannerDeviceName& scanner_device_name);
bool operator<(const ScannerDeviceName& other) const;
bool operator==(const ScannerDeviceName& other) const;
// The device name corresponding to the SANE backend, and therefore the
// protocol, used to interact with the scanner.
std::string device_name;
// Signifies whether the device name can be used to interact with the scanner.
// Device names are considered usable until proven otherwise (i.e. a device
// name is marked as unusable when attempting to use it fails).
bool usable = true;
};
struct CHROMEOS_EXPORT Scanner {
Scanner();
~Scanner();
Scanner(const Scanner& scanner);
Scanner& operator=(const Scanner& scanner);
// Name to display in a UI.
std::string display_name;
// Map of ScanProtocol to a set of corresponding ScannerDeviceNames that can
// be used with the lorgnette D-Bus service. Clients are responsible for
// selecting which device name to use.
base::flat_map<ScanProtocol, std::set<ScannerDeviceName>> device_names;
// Known IP addresses for this scanner. Used to deduplicate network scanners
// from multiple sources.
base::flat_set<net::IPAddress> ip_addresses;
};
} // namespace chromeos
#endif // CHROMEOS_SCANNING_SCANNER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment