blob: 7e86b0fa391057c5c4dc50f9af57d36bb06d5a4a [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/smart_card/smart_card_reader_tracker.h"
#include "base/functional/callback.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/smart_card_delegate.h"
#include "third_party/blink/public/mojom/smart_card/smart_card.mojom.h"
namespace content {
namespace {
// Special string that, when specified as the reader name in GetStatusChange(),
// enables the client to be notified when a new smart card reader is added to
// the system.
// This a hack put on top of the PC/SC spec (which predates USB) and supported
// by most winscard implementations.
static const char kPnpNotification[] = R"(\\?PnP?\Notification)";
constexpr auto kStatusChangeTimeout = base::Minutes(5);
// Converts the mess that is the SCARD_STATE_* flags into a value of our flat
// enumeration.
//
// Shouldn't be called with `unaware`, `ignore` or `unknown` flags set as they
// have special meaning and should be handled elsewhere.
// Ie, they do not translate to a blink::mojom::SmartCardReaderState value.
blink::mojom::SmartCardReaderState ToBlinkSmartCardReaderState(
const device::mojom::SmartCardReaderStateFlags& flags) {
CHECK(!flags.unaware);
CHECK(!flags.ignore);
CHECK(!flags.unknown);
if (flags.unavailable) {
return blink::mojom::SmartCardReaderState::kUnavailable;
}
if (flags.empty) {
return blink::mojom::SmartCardReaderState::kEmpty;
}
if (flags.exclusive) {
if (!flags.present) {
// The browser has no control of the platforms' PC/SC stack. Thus even
// though this combination shouldn't happen, technically nothing stops the
// PC/SC stack from returning such a combination of flags.
// Same applies for other warnings in this function.
LOG(WARNING) << "It's invalid to have SCARD_STATE_EXCLUSIVE without "
"SCARD_STATE_PRESENT.";
}
return blink::mojom::SmartCardReaderState::kExclusive;
}
if (flags.inuse) {
if (!flags.present) {
LOG(WARNING) << "It's invalid to have SCARD_STATE_INUSE without "
"SCARD_STATE_PRESENT.";
}
return blink::mojom::SmartCardReaderState::kInuse;
}
if (flags.mute) {
if (!flags.present) {
LOG(WARNING) << "It's invalid to have SCARD_STATE_MUTE without "
"SCARD_STATE_PRESENT.";
}
return blink::mojom::SmartCardReaderState::kMute;
}
if (flags.unpowered) {
if (!flags.present) {
LOG(WARNING) << "It's invalid to have SCARD_STATE_UNPOWERED without "
"SCARD_STATE_PRESENT.";
}
return blink::mojom::SmartCardReaderState::kUnpowered;
}
if (flags.present) {
// There is a card and it's powered, responsive and not in use
// by any other application.
return blink::mojom::SmartCardReaderState::kPresent;
}
LOG(ERROR) << "Invalid ReaderStateFlags";
return blink::mojom::SmartCardReaderState::kUnavailable;
}
blink::mojom::SmartCardResponseCode ToBlinkResponseCode(
device::mojom::SmartCardError error) {
#define FORWARD(Name) \
case device::mojom::SmartCardError::Name: \
return blink::mojom::SmartCardResponseCode::Name
switch (error) {
// Errors with a 1:1 mapping between device and blink:
FORWARD(kRemovedCard);
FORWARD(kResetCard);
FORWARD(kUnpoweredCard);
FORWARD(kUnresponsiveCard);
FORWARD(kUnsupportedCard);
FORWARD(kReaderUnavailable);
FORWARD(kSharingViolation);
FORWARD(kNotTransacted);
FORWARD(kProtoMismatch);
FORWARD(kSystemCancelled);
FORWARD(kNotReady);
FORWARD(kUnsupportedFeature);
FORWARD(kNoService);
FORWARD(kServerTooBusy);
case device::mojom::SmartCardError::kNoSmartcard:
return blink::mojom::SmartCardResponseCode::kNoSmartCard;
case device::mojom::SmartCardError::kInvalidHandle:
return blink::mojom::SmartCardResponseCode::kInvalidConnection;
case device::mojom::SmartCardError::kServiceStopped:
case device::mojom::SmartCardError::kShutdown:
case device::mojom::SmartCardError::kCommError:
case device::mojom::SmartCardError::kInternalError:
case device::mojom::SmartCardError::kNoMemory:
case device::mojom::SmartCardError::kUnexpected:
case device::mojom::SmartCardError::kUnknownError:
case device::mojom::SmartCardError::kUnknown:
LOG(WARNING) << "An unmapped PC/SC error has occurred.";
return blink::mojom::SmartCardResponseCode::kUnknownError;
// Handled internally but listed here for completeness.
// Also, technically nothing stops the PC/SC stack from spilling those
// unexpectedly (eg, in unrelated requests).
case device::mojom::SmartCardError::kCancelled:
case device::mojom::SmartCardError::kTimeout:
case device::mojom::SmartCardError::kUnknownReader:
case device::mojom::SmartCardError::kNoReadersAvailable:
// Errors that indicate bad usage of the API (ie, a programming
// error in browser code).
// But technically nothing stops the PC/SC stack from spilling those
// unexpectedly.
case device::mojom::SmartCardError::kInsufficientBuffer:
case device::mojom::SmartCardError::kInvalidParameter:
case device::mojom::SmartCardError::kInvalidValue:
LOG(WARNING) << "An unexpected PC/SC error has occurred: " << error;
return blink::mojom::SmartCardResponseCode::kUnexpected;
}
#undef FORWARD
}
void FailRequests(base::queue<SmartCardReaderTracker::StartCallback>& requests,
blink::mojom::SmartCardResponseCode response_code) {
while (!requests.empty()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(requests.front()),
blink::mojom::SmartCardGetReadersResult::NewResponseCode(
response_code)));
requests.pop();
}
}
std::unique_ptr<SmartCardReaderTracker> CreateSmartCardReaderTracker(
BrowserContext& browser_context,
SmartCardDelegate& delegate) {
return std::make_unique<SmartCardReaderTracker>(
delegate.GetSmartCardContextFactory(browser_context),
delegate.SupportsReaderAddedRemovedNotifications());
}
} // namespace
// Contains the known state of a smart card reader.
class SmartCardReaderTracker::Reader {
public:
explicit Reader(const device::mojom::SmartCardReaderStateOut& state_out)
: blink_reader_info_(blink::mojom::SmartCardReaderInfo::New(
state_out.reader,
ToBlinkSmartCardReaderState(*state_out.event_state.get()),
state_out.answer_to_reset)) {
CopyStateFlags(*state_out.event_state.get());
}
// Returns whether any change was made.
bool Update(const device::mojom::SmartCardReaderStateOut& state_out) {
CHECK_EQ(state_out.reader, blink_reader_info_->name);
bool changed = false;
if (blink_reader_info_->atr != state_out.answer_to_reset) {
blink_reader_info_->atr = state_out.answer_to_reset;
changed = true;
}
blink::mojom::SmartCardReaderState new_blink_state =
ToBlinkSmartCardReaderState(*state_out.event_state.get());
if (new_blink_state != blink_reader_info_->state) {
blink_reader_info_->state = new_blink_state;
changed = true;
}
CopyStateFlags(*state_out.event_state.get());
return changed;
}
device::mojom::SmartCardReaderStateFlagsPtr CreateCurrentStateFlags() {
auto flags = device::mojom::SmartCardReaderStateFlags::New();
flags->unavailable = unavailable_;
flags->empty = empty_;
flags->present = present_;
flags->exclusive = exclusive_;
flags->inuse = inuse_;
flags->mute = mute_;
flags->unpowered = unpowered_;
return flags;
}
const blink::mojom::SmartCardReaderInfo& blink_reader_info() const {
return *blink_reader_info_.get();
}
private:
void CopyStateFlags(
const device::mojom::SmartCardReaderStateFlags& state_flags) {
unavailable_ = state_flags.unavailable;
empty_ = state_flags.empty;
present_ = state_flags.present;
exclusive_ = state_flags.exclusive;
inuse_ = state_flags.inuse;
mute_ = state_flags.mute;
unpowered_ = state_flags.unpowered;
}
blink::mojom::SmartCardReaderInfoPtr blink_reader_info_;
// As received from the last GetStatusChange() run.
// To be used on the next GetStatusChange() call as the known/expected state.
//
// Note that there's no strict 1:1 mapping between those and
// blink::mojom::SmartCardReaderState. Therefore we have to store them
// separately.
bool unavailable_;
bool empty_;
bool present_;
bool exclusive_;
bool inuse_;
bool mute_;
bool unpowered_;
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::State
// Represents a state in the `SmartCardReaderTracker` state machine.
// It defines how the `SmartCardReaderTracker` reacts to calls to its public
// methods and to its callbacks.
class SmartCardReaderTracker::State {
public:
explicit State(SmartCardReaderTracker& tracker) : tracker_(tracker) {}
virtual ~State() = default;
virtual std::string ToString() const = 0;
virtual void Enter() {}
virtual void Start(StartCallback) {}
protected:
// Owns `this`.
const raw_ref<SmartCardReaderTracker> tracker_;
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::Uninitialized declaration
// Initial state of `SmartCardReaderTracker`.
class SmartCardReaderTracker::Uninitialized
: public SmartCardReaderTracker::State {
public:
explicit Uninitialized(SmartCardReaderTracker& tracker) : State(tracker) {}
std::string ToString() const override;
void Start(StartCallback) override;
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::WaitReadersList declaration
// `SmartCardReaderTracker` called `ListReaders` and is waiting for its result.
class SmartCardReaderTracker::WaitReadersList
: public SmartCardReaderTracker::State {
public:
WaitReadersList(SmartCardReaderTracker& tracker,
mojo::Remote<device::mojom::SmartCardContext> context,
base::queue<StartCallback> pending_get_readers_requests =
base::queue<StartCallback>());
WaitReadersList(
SmartCardReaderTracker& tracker,
mojo::PendingRemote<device::mojom::SmartCardContext> pending_context,
base::queue<StartCallback> pending_get_readers_requests);
~WaitReadersList() override;
std::string ToString() const override;
void Enter() override;
void Start(StartCallback callback) override;
private:
void OnListReadersDone(device::mojom::SmartCardListReadersResultPtr result);
void RemoveAbsentReaders(const std::vector<std::string>& current_readers);
std::vector<std::string> IdentifyNewReaders(
const std::vector<std::string>& current_readers);
void ReplyNoReaders();
mojo::Remote<device::mojom::SmartCardContext> context_;
base::queue<StartCallback> pending_get_readers_requests_;
base::WeakPtrFactory<SmartCardReaderTracker::WaitReadersList>
weak_ptr_factory_{this};
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::KeepContext
// Keeps a valid `SmartCardContext` until the next
// `SmartCardReaderTracker::Start` call.
// Goes to `Uninitialized` state on timeout.
class SmartCardReaderTracker::KeepContext
: public SmartCardReaderTracker::State {
public:
KeepContext(SmartCardReaderTracker& tracker,
mojo::Remote<device::mojom::SmartCardContext> context)
: State(tracker), context_(std::move(context)) {}
~KeepContext() override = default;
std::string ToString() const override { return "KeepContext"; }
void Enter() override {
// TODO(crbug.com/1386175): start timer and go to Uninitialized on timeout.
}
void Start(StartCallback callback) override {
pending_get_readers_requests_.push(std::move(callback));
tracker_->ChangeState(std::make_unique<WaitReadersList>(
*tracker_, std::move(context_),
std::move(pending_get_readers_requests_)));
}
private:
mojo::Remote<device::mojom::SmartCardContext> context_;
base::queue<StartCallback> pending_get_readers_requests_;
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::Tracking
// Main state of `SmartCardReaderTracker`.
//
// `SmartCardReaderTracker::readers_` has an up to date state of all available
// smart card readers.
//
// It has a pending `SmartCardContext::GetStatusChange`request which returns
// once a change takes place.
class SmartCardReaderTracker::Tracking : public SmartCardReaderTracker::State {
public:
Tracking(SmartCardReaderTracker& tracker,
mojo::Remote<device::mojom::SmartCardContext> context)
: State(tracker), context_(std::move(context)) {}
std::string ToString() const override { return "Tracking"; }
void Enter() override {
CHECK(tracker_->CanTrack());
std::vector<device::mojom::SmartCardReaderStateInPtr> reader_states;
// Get notified when a reader is added to the system.
if (tracker_->context_supports_reader_added_) {
auto state = device::mojom::SmartCardReaderStateIn::New(
kPnpNotification, device::mojom::SmartCardReaderStateFlags::New());
reader_states.push_back(std::move(state));
}
// Get notified when a known, existing, reader changes its state or is
// removed from the system.
for (auto const& [name, reader] : tracker_->readers_) {
reader_states.push_back(device::mojom::SmartCardReaderStateIn::New(
name, reader->CreateCurrentStateFlags()));
}
// Instead of waiting indefinitely until anything changes, wait
// for some time and then expect to receive a timeout from PC/SC.
// Useful as a way of telling whether the PC/SC backend is still "alive".
context_->GetStatusChange(
kStatusChangeTimeout, std::move(reader_states),
base::BindOnce(&SmartCardReaderTracker::Tracking::OnGetStatusChangeDone,
weak_ptr_factory_.GetWeakPtr()));
}
void Start(StartCallback callback) override {
// TODO(crbug.com/1386175): Cancel current GetStatusChange to force a
// refresh if we are in this state for longer than MIN_REFRESH_INTERVAL.
// otherwise return cached result as below.
tracker_->GetReadersFromCache(std::move(callback));
}
private:
void OnGetStatusChangeDone(
device::mojom::SmartCardStatusChangeResultPtr result) {
if (result->is_error()) {
const device::mojom::SmartCardError error = result->get_error();
if (error == device::mojom::SmartCardError::kCancelled ||
error == device::mojom::SmartCardError::kTimeout) {
TrackChangesOrGiveUp();
} else {
tracker_->NotifyError(ToBlinkResponseCode(result->get_error()));
tracker_->ChangeState(std::make_unique<Uninitialized>(*tracker_));
}
return;
}
tracker_->UpdateCache(result->get_reader_states());
TrackChangesOrGiveUp();
}
void TrackChangesOrGiveUp() {
if (tracker_->CanTrack()) {
tracker_->ChangeState(
std::make_unique<WaitReadersList>(*tracker_, std::move(context_)));
} else {
tracker_->ChangeState(
std::make_unique<KeepContext>(*tracker_, std::move(context_)));
}
}
mojo::Remote<device::mojom::SmartCardContext> context_;
base::WeakPtrFactory<SmartCardReaderTracker::Tracking> weak_ptr_factory_{
this};
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::WaitInitialReaderStatus
// It's waiting for a `SmartCardContext::GetStatusChange` request to return
// with information on the available, but unknown to the tracker, smart card
// readers.
class SmartCardReaderTracker::WaitInitialReaderStatus
: public SmartCardReaderTracker::State {
public:
explicit WaitInitialReaderStatus(
SmartCardReaderTracker& tracker,
mojo::Remote<device::mojom::SmartCardContext> context,
base::queue<StartCallback> pending_get_readers_requests,
std::vector<std::string>& new_readers)
: State(tracker),
context_(std::move(context)),
pending_get_readers_requests_(std::move(pending_get_readers_requests)),
new_readers_(new_readers) {}
std::string ToString() const override { return "WaitInitialReaderStatus"; }
void Enter() override {
CHECK(!new_readers_.empty());
std::vector<device::mojom::SmartCardReaderStateInPtr> reader_states;
for (const std::string& reader_name : new_readers_) {
CHECK_EQ(tracker_->readers_.count(reader_name), size_t(0));
auto state = device::mojom::SmartCardReaderStateIn::New();
state->reader = reader_name;
state->current_state = device::mojom::SmartCardReaderStateFlags::New(
/*unaware=*/true, false, false, false, false, false, false, false,
false, false, false);
reader_states.push_back(std::move(state));
}
context_->GetStatusChange(
base::TimeDelta::Max(), std::move(reader_states),
base::BindOnce(&SmartCardReaderTracker::WaitInitialReaderStatus::
OnGetStatusChangeDone,
weak_ptr_factory_.GetWeakPtr()));
}
void Start(StartCallback callback) override {
pending_get_readers_requests_.push(std::move(callback));
}
private:
void OnGetStatusChangeDone(
device::mojom::SmartCardStatusChangeResultPtr result) {
if (result->is_error()) {
FailRequests(pending_get_readers_requests_,
blink::mojom::SmartCardResponseCode::kNoService);
tracker_->NotifyError(ToBlinkResponseCode(result->get_error()));
tracker_->ChangeState(std::make_unique<Uninitialized>(*tracker_));
return;
}
tracker_->UpdateCache(result->get_reader_states());
while (!pending_get_readers_requests_.empty()) {
tracker_->GetReadersFromCache(
std::move(pending_get_readers_requests_.front()));
pending_get_readers_requests_.pop();
}
if (tracker_->CanTrack()) {
tracker_->ChangeState(
std::make_unique<Tracking>(*tracker_, std::move(context_)));
} else {
tracker_->ChangeState(
std::make_unique<KeepContext>(*tracker_, std::move(context_)));
}
}
mojo::Remote<device::mojom::SmartCardContext> context_;
base::queue<StartCallback> pending_get_readers_requests_;
std::vector<std::string> new_readers_;
base::WeakPtrFactory<SmartCardReaderTracker::WaitInitialReaderStatus>
weak_ptr_factory_{this};
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::WaitReadersList implementation
SmartCardReaderTracker::WaitReadersList::WaitReadersList(
SmartCardReaderTracker& tracker,
mojo::Remote<device::mojom::SmartCardContext> context,
base::queue<StartCallback> pending_get_readers_requests)
: State(tracker),
context_(std::move(context)),
pending_get_readers_requests_(std::move(pending_get_readers_requests)) {}
SmartCardReaderTracker::WaitReadersList::WaitReadersList(
SmartCardReaderTracker& tracker,
mojo::PendingRemote<device::mojom::SmartCardContext> pending_context,
base::queue<StartCallback> pending_get_readers_requests)
: State(tracker),
context_(std::move(pending_context)),
pending_get_readers_requests_(std::move(pending_get_readers_requests)) {}
SmartCardReaderTracker::WaitReadersList::~WaitReadersList() = default;
std::string SmartCardReaderTracker::WaitReadersList::ToString() const {
return "WaitReadersList";
}
void SmartCardReaderTracker::WaitReadersList::Enter() {
context_->ListReaders(base::BindOnce(
&SmartCardReaderTracker::WaitReadersList::OnListReadersDone,
weak_ptr_factory_.GetWeakPtr()));
}
void SmartCardReaderTracker::WaitReadersList::Start(StartCallback callback) {
pending_get_readers_requests_.push(std::move(callback));
}
void SmartCardReaderTracker::WaitReadersList::OnListReadersDone(
device::mojom::SmartCardListReadersResultPtr result) {
std::vector<std::string> current_readers;
if (result->is_error()) {
if (result->get_error() !=
device::mojom::SmartCardError::kNoReadersAvailable) {
FailRequests(pending_get_readers_requests_,
ToBlinkResponseCode(result->get_error()));
tracker_->ChangeState(std::make_unique<Uninitialized>(*tracker_));
return;
}
} else {
current_readers = result->get_readers();
}
RemoveAbsentReaders(current_readers);
if (current_readers.empty()) {
ReplyNoReaders();
tracker_->ChangeState(
std::make_unique<Tracking>(*tracker_, std::move(context_)));
return;
}
std::vector<std::string> new_readers = IdentifyNewReaders(current_readers);
if (new_readers.empty()) {
// We already know about all existing readers and their states.
// The cache can be considered still up to date.
while (!pending_get_readers_requests_.empty()) {
tracker_->GetReadersFromCache(
std::move(pending_get_readers_requests_.front()));
pending_get_readers_requests_.pop();
}
// And we can go straight to Tracking (skipping WaitInitialReaderStatus).
tracker_->ChangeState(
std::make_unique<Tracking>(*tracker_, std::move(context_)));
return;
}
tracker_->ChangeState(std::make_unique<WaitInitialReaderStatus>(
*tracker_, std::move(context_), std::move(pending_get_readers_requests_),
new_readers));
}
void SmartCardReaderTracker::WaitReadersList::RemoveAbsentReaders(
const std::vector<std::string>& current_readers) {
std::unordered_set<std::string> current_readers_set;
for (const auto& reader_name : current_readers) {
current_readers_set.insert(reader_name);
}
for (auto it = tracker_->readers_.begin(); it != tracker_->readers_.end();) {
std::string reader_name = it->first;
if (current_readers_set.count(reader_name) == 0) {
std::unique_ptr<Reader> reader = std::move(it->second);
it = tracker_->readers_.erase(it);
tracker_->NotifyReaderRemoved(reader->blink_reader_info());
} else {
++it;
}
}
}
std::vector<std::string>
SmartCardReaderTracker::WaitReadersList::IdentifyNewReaders(
const std::vector<std::string>& current_readers) {
std::vector<std::string> new_readers;
for (const std::string& reader_name : current_readers) {
if (tracker_->readers_.count(reader_name) == 0) {
new_readers.push_back(reader_name);
}
}
return new_readers;
}
void SmartCardReaderTracker::WaitReadersList::ReplyNoReaders() {
while (!pending_get_readers_requests_.empty()) {
std::move(pending_get_readers_requests_.front())
.Run(blink::mojom::SmartCardGetReadersResult::NewReaders(
std::vector<::blink::mojom::SmartCardReaderInfoPtr>()));
pending_get_readers_requests_.pop();
}
}
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::WaitContext
// State where the `SmartCardReaderTracker` is waiting for a
// `SmartCardContextFactory::CreateContext` call to return.
class SmartCardReaderTracker::WaitContext
: public SmartCardReaderTracker::State {
public:
std::string ToString() const override { return "WaitContext"; }
WaitContext(SmartCardReaderTracker& tracker, StartCallback callback)
: State(tracker) {
pending_get_readers_requests_.push(std::move(callback));
}
void Enter() override {
tracker_->context_factory_->CreateContext(base::BindOnce(
&SmartCardReaderTracker::WaitContext::OnEstablishContextDone,
weak_ptr_factory_.GetWeakPtr()));
}
void Start(StartCallback callback) override {
pending_get_readers_requests_.push(std::move(callback));
}
private:
void OnEstablishContextDone(
device::mojom::SmartCardCreateContextResultPtr result) {
if (result->is_error()) {
FailRequests(pending_get_readers_requests_,
ToBlinkResponseCode(result->get_error()));
tracker_->ChangeState(std::make_unique<Uninitialized>(*tracker_));
return;
}
tracker_->ChangeState(std::make_unique<WaitReadersList>(
*tracker_, std::move(result->get_context()),
std::move(pending_get_readers_requests_)));
}
base::queue<StartCallback> pending_get_readers_requests_;
base::WeakPtrFactory<SmartCardReaderTracker::WaitContext> weak_ptr_factory_{
this};
};
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker::Uninitialized implementation
std::string SmartCardReaderTracker::Uninitialized::ToString() const {
return "Uninitialized";
}
void SmartCardReaderTracker::Uninitialized::Start(StartCallback callback) {
tracker_->ChangeState(
std::make_unique<WaitContext>(*tracker_, std::move(callback)));
}
////////////////////////////////////////////////////////////////////////////////
// SmartCardReaderTracker
// static
SmartCardReaderTracker& SmartCardReaderTracker::GetForBrowserContext(
BrowserContext& browser_context,
SmartCardDelegate& delegate) {
static constexpr char kSmartCardReaderTrackerKey[] =
"SmartCardReaderTrackerKey";
if (!browser_context.GetUserData(kSmartCardReaderTrackerKey)) {
browser_context.SetUserData(
kSmartCardReaderTrackerKey,
CreateSmartCardReaderTracker(browser_context, delegate));
}
return *static_cast<SmartCardReaderTracker*>(
browser_context.GetUserData(kSmartCardReaderTrackerKey));
}
SmartCardReaderTracker::SmartCardReaderTracker(
mojo::PendingRemote<device::mojom::SmartCardContextFactory> context_factory,
bool context_supports_reader_added)
: state_(std::make_unique<Uninitialized>(*this)),
context_factory_(std::move(context_factory)),
context_supports_reader_added_(context_supports_reader_added) {}
SmartCardReaderTracker::~SmartCardReaderTracker() = default;
void SmartCardReaderTracker::Start(Observer* observer, StartCallback callback) {
if (!observer_list_.HasObserver(observer)) {
observer_list_.AddObserver(observer);
}
state_->Start(std::move(callback));
}
void SmartCardReaderTracker::Stop(Observer* observer) {
observer_list_.RemoveObserver(observer);
// TODO(crbug.com/1386175): call state if there are no readers left. Tracking
// state should cancel its pending GetStatusChange.
}
void SmartCardReaderTracker::ChangeState(std::unique_ptr<State> next_state) {
CHECK(next_state);
auto current_state = std::move(state_);
VLOG(1) << "ChangeState: " << current_state->ToString() << " -> "
<< next_state->ToString();
state_ = std::move(next_state);
state_->Enter();
// This method is invoked from inside `current_state`, so we postpone
// destruction to ensure it has a chance to finish its current method
// without crashing because it was deleted.
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, std::move(current_state));
}
bool SmartCardReaderTracker::CanTrack() const {
return !observer_list_.empty() &&
(!readers_.empty() || context_supports_reader_added_);
}
void SmartCardReaderTracker::AddReader(
const device::mojom::SmartCardReaderStateOut& state_out) {
auto unique_reader = std::make_unique<Reader>(state_out);
const blink::mojom::SmartCardReaderInfo& reader_info =
unique_reader->blink_reader_info();
readers_[state_out.reader] = std::move(unique_reader);
if (!context_supports_reader_added_) {
// For consistency, never send it if not supported.
// Otherwise one user calling Start() would have a side
// effect on the obsevers of other users as they would
// be notified even though the didn't call Start()
// themselves.
return;
}
NotifyReaderAdded(reader_info);
}
void SmartCardReaderTracker::AddOrUpdateReader(
const device::mojom::SmartCardReaderStateOut& state_out) {
auto it = readers_.find(state_out.reader);
if (it == readers_.end()) {
AddReader(state_out);
} else {
std::unique_ptr<Reader>& reader = it->second;
if (reader->Update(state_out)) {
NotifyReaderChanged(reader->blink_reader_info());
}
}
}
void SmartCardReaderTracker::RemoveReader(
const device::mojom::SmartCardReaderStateOut& state_out) {
auto it = readers_.find(state_out.reader);
if (it == readers_.end()) {
return;
}
std::unique_ptr<Reader> reader = std::move(it->second);
readers_.erase(it);
NotifyReaderRemoved(reader->blink_reader_info());
}
void SmartCardReaderTracker::GetReadersFromCache(StartCallback callback) {
std::vector<::blink::mojom::SmartCardReaderInfoPtr> reader_list;
reader_list.reserve(readers_.size());
for (const auto& [_, reader] : readers_) {
reader_list.push_back(reader->blink_reader_info().Clone());
}
std::move(callback).Run(blink::mojom::SmartCardGetReadersResult::NewReaders(
std::move(reader_list)));
}
void SmartCardReaderTracker::UpdateCache(
const std::vector<device::mojom::SmartCardReaderStateOutPtr>&
reader_states) {
for (const auto& reader_state : reader_states) {
if (reader_state->reader == kPnpNotification) {
// This is not an actual reader device.
continue;
}
if (reader_state->event_state->unknown ||
reader_state->event_state->ignore ||
reader_state->event_state->unaware) {
RemoveReader(*reader_state.get());
} else {
AddOrUpdateReader(*reader_state.get());
}
}
}
void SmartCardReaderTracker::NotifyReaderAdded(
const blink::mojom::SmartCardReaderInfo& reader_info) {
for (Observer& obs : observer_list_) {
obs.OnReaderAdded(reader_info);
}
}
void SmartCardReaderTracker::NotifyReaderChanged(
const blink::mojom::SmartCardReaderInfo& reader_info) {
for (Observer& obs : observer_list_) {
obs.OnReaderChanged(reader_info);
}
}
void SmartCardReaderTracker::NotifyReaderRemoved(
const blink::mojom::SmartCardReaderInfo& reader_info) {
for (Observer& obs : observer_list_) {
obs.OnReaderRemoved(reader_info);
}
}
void SmartCardReaderTracker::NotifyError(
blink::mojom::SmartCardResponseCode response_code) {
for (Observer& obs : observer_list_) {
obs.OnError(response_code);
}
}
} // namespace content