| // 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 |