blob: 520982c433c6d04d608483c3c6afa43e8bf113fe [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lorgnette/device_tracker.h"
#include <utility>
#include <base/containers/contains.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/run_loop.h>
#include <base/task/single_thread_task_runner.h>
#include <base/time/time.h>
#include "lorgnette/firewall_manager.h"
#include "lorgnette/sane_client.h"
#include "lorgnette/usb/libusb_wrapper.h"
#include "lorgnette/usb/usb_device.h"
#include "lorgnette/uuid_util.h"
namespace lorgnette {
DeviceTracker::DeviceTracker(SaneClient* sane_client, LibusbWrapper* libusb)
: sane_client_(sane_client), libusb_(libusb) {
DCHECK(sane_client_);
DCHECK(libusb_);
}
DeviceTracker::~DeviceTracker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void DeviceTracker::SetScannerListChangedSignalSender(
ScannerListChangedSignalSender sender) {
signal_sender_ = sender;
}
void DeviceTracker::SetFirewallManager(FirewallManager* firewall_manager) {
firewall_manager_ = firewall_manager;
}
size_t DeviceTracker::NumActiveDiscoverySessions() const {
return discovery_sessions_.size();
}
base::Time DeviceTracker::LastDiscoverySessionActivity() const {
base::Time activity = base::Time::UnixEpoch();
for (const auto& session : discovery_sessions_) {
if (session.second.start_time > activity) {
activity = session.second.start_time;
}
}
return activity;
}
StartScannerDiscoveryResponse DeviceTracker::StartScannerDiscovery(
const StartScannerDiscoveryRequest& request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
StartScannerDiscoveryResponse response;
std::string client_id = request.client_id();
if (client_id.empty()) {
LOG(ERROR) << __func__
<< ": Missing client_id in StartScannerDiscovery request";
return response;
}
std::string session_id;
for (auto& kv : discovery_sessions_) {
if (kv.second.client_id == client_id) {
// TODO(b/274860789): Clean up open scanner handles and jobs if
// the same client requests a new discovery session.
session_id = kv.first;
LOG(INFO) << __func__ << ": Reusing existing discovery session "
<< session_id << " for client " << client_id;
break;
}
}
if (session_id.empty()) {
session_id = GenerateUUID();
LOG(INFO) << __func__ << ": Starting new discovery session " << session_id
<< " for client " << client_id;
}
DiscoverySessionState& session = discovery_sessions_[session_id];
session.client_id = client_id;
session.start_time = base::Time::Now();
session.dlc_policy = request.download_policy();
session.dlc_started = false;
session.local_only = request.local_only();
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DeviceTracker::StartDiscoverySessionInternal,
weak_factory_.GetWeakPtr(), session_id));
response.set_started(true);
response.set_session_id(session_id);
return response;
}
StopScannerDiscoveryResponse DeviceTracker::StopScannerDiscovery(
const StopScannerDiscoveryRequest& request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
StopScannerDiscoveryResponse response;
std::string session_id = request.session_id();
if (session_id.empty()) {
LOG(ERROR) << __func__ << ": Missing session_id in request";
return response;
}
discovery_sessions_.erase(session_id);
SendSessionEndingSignal(session_id);
response.set_stopped(true);
return response;
}
std::optional<DeviceTracker::DiscoverySessionState*> DeviceTracker::GetSession(
const std::string& session_id) {
if (session_id.empty()) {
LOG(ERROR) << "Missing session id";
return std::nullopt;
}
if (!base::Contains(discovery_sessions_, session_id)) {
LOG(ERROR) << "No active session found for session_id=" << session_id;
return std::nullopt;
}
return &discovery_sessions_.at(session_id);
}
void DeviceTracker::StartDiscoverySessionInternal(std::string session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto maybe_session = GetSession(session_id);
if (!maybe_session) {
LOG(ERROR) << __func__ << ": Failed to get session " << session_id;
return;
}
DiscoverySessionState* session = *maybe_session;
LOG(INFO) << __func__ << ": Starting discovery session " << session_id;
if (!session->local_only) {
// TODO(b/277049004): Open firewall ports before starting discovery.
}
if (session->dlc_policy == BackendDownloadPolicy::DOWNLOAD_ALWAYS) {
// TODO(rishabhagr): Kick off background DLC download.
session->dlc_started = true;
}
// If a previous session already discovered some devices, go ahead and send
// signals for those right away. Any newly-plugged devices will be added
// later when we re-enumerate everything.
for (const auto& device : known_devices_) {
ScannerListChangedSignal signal;
signal.set_event_type(ScannerListChangedSignal::SCANNER_ADDED);
signal.set_session_id(session_id);
*signal.mutable_scanner() = device;
signal_sender_.Run(signal);
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DeviceTracker::EnumerateUSBDevices,
weak_factory_.GetWeakPtr(), session_id));
}
void DeviceTracker::EnumerateUSBDevices(std::string session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto maybe_session = GetSession(session_id);
if (!maybe_session) {
LOG(ERROR) << __func__ << ": Failed to get session " << session_id;
return;
}
DiscoverySessionState* session = *maybe_session;
LOG(INFO) << __func__ << ": Enumerating USB devices for " << session_id;
for (auto& device : libusb_->GetDevices()) {
if (!session->dlc_started && device->NeedsNonBundledBackend()) {
// TODO(rishabhagr): Kick off background DLC download.
session->dlc_started = true;
}
if (device->SupportsIppUsb()) {
LOG(INFO) << __func__ << ": Device " << device->Description()
<< " supports IPP-USB and needs to be probed";
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DeviceTracker::ProbeIPPUSBDevice,
weak_factory_.GetWeakPtr(), session_id,
std::move(device)));
}
}
if (session->dlc_started) {
LOG(INFO) << __func__ << ": Waiting for DLC to finish";
// TODO(rishabhagr): Track that DLC completion needs to run
// EnumerateSANEDevices to continue.
} else {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DeviceTracker::EnumerateSANEDevices,
weak_factory_.GetWeakPtr(), session_id));
}
}
void DeviceTracker::ProbeIPPUSBDevice(std::string session_id,
std::unique_ptr<UsbDevice> device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto maybe_session = GetSession(session_id);
if (!maybe_session) {
LOG(ERROR) << __func__ << ": Failed to get session " << session_id;
return;
}
LOG(INFO) << __func__ << ": Probing IPP-USB device " << device->Description()
<< " for " << session_id;
std::optional<ScannerInfo> scanner_info = device->IppUsbScannerInfo();
if (!scanner_info) {
LOG(ERROR) << __func__ << ": Unable to get scanner info from device "
<< device->Description();
return;
}
LOG(INFO) << __func__ << ": Attempting eSCL connection for "
<< device->Description() << " at " << scanner_info->name();
brillo::ErrorPtr error;
SANE_Status status;
std::unique_ptr<SaneDevice> sane_device =
sane_client_->ConnectToDevice(&error, &status, scanner_info->name());
if (!sane_device) {
LOG(ERROR) << __func__ << ": Failed to open device "
<< device->Description() << " as " << scanner_info->name()
<< ": " << sane_strstatus(status);
return;
}
// TODO(b/277049537): Fetch device UUID from the scanner.
LOG(INFO) << __func__ << ": Device " << device->Description()
<< " supports eSCL over IPP-USB at " << scanner_info->name();
ScannerListChangedSignal signal;
signal.set_event_type(ScannerListChangedSignal::SCANNER_ADDED);
signal.set_session_id(session_id);
*signal.mutable_scanner() = *scanner_info;
signal_sender_.Run(signal);
known_devices_.push_back(std::move(*scanner_info));
}
void DeviceTracker::EnumerateSANEDevices(std::string session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto maybe_session = GetSession(session_id);
if (!maybe_session) {
LOG(ERROR) << __func__ << ": Failed to get session " << session_id;
return;
}
LOG(INFO) << __func__ << ": Checking for SANE devices in " << session_id;
// TODO(b/277049004): Call SaneClient
// Foreach device from sane_get_devices
(void)sane_client_;
{
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DeviceTracker::ProbeSANEDevice,
weak_factory_.GetWeakPtr(), session_id));
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DeviceTracker::SendEnumerationCompletedSignal,
weak_factory_.GetWeakPtr(), session_id));
}
void DeviceTracker::ProbeSANEDevice(std::string session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto maybe_session = GetSession(session_id);
if (!maybe_session) {
LOG(ERROR) << __func__ << ": Failed to get session " << session_id;
return;
}
LOG(INFO) << __func__ << ": Probing SANE device for " << session_id;
// TODO(b/277049004): Check device and match against existing entries.
}
void DeviceTracker::SendEnumerationCompletedSignal(std::string session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto maybe_session = GetSession(session_id);
if (!maybe_session) {
LOG(ERROR) << __func__ << ": Failed to get session " << session_id;
return;
}
LOG(INFO) << __func__ << ": Enumeration completed for " << session_id;
ScannerListChangedSignal signal;
signal.set_event_type(ScannerListChangedSignal::ENUM_COMPLETE);
signal.set_session_id(session_id);
signal_sender_.Run(signal);
}
void DeviceTracker::SendSessionEndingSignal(std::string session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (session_id.empty()) {
LOG(ERROR) << __func__ << ": Missing session id";
}
LOG(INFO) << __func__ << ": Session ending for " << session_id;
// Deliberately don't check for an active session. This lets us
// notify ended sessions even if lorgnette has restarted.
ScannerListChangedSignal signal;
signal.set_event_type(ScannerListChangedSignal::SESSION_ENDING);
signal.set_session_id(session_id);
signal_sender_.Run(signal);
}
} // namespace lorgnette