// 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/printing/ppd_metadata_manager.h" #include #include #include #include #include #include "base/containers/flat_map.h" #include "base/containers/queue.h" #include "base/containers/span.h" #include "base/json/json_reader.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/sequence_checker.h" #include "base/strings/strcat.h" #include "base/strings/string_piece.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/time/clock.h" #include "base/time/time.h" #include "base/values.h" #include "chromeos/printing/ppd_metadata_parser.h" #include "chromeos/printing/ppd_provider.h" #include "chromeos/printing/printer_config_cache.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace chromeos { namespace { // Defines the containing directory of all metadata in the serving root. const char kMetadataParentDirectory[] = "metadata_v3"; // Defines the number of shards of sharded metadata. constexpr int kNumShards = 20; // Defines the magic maximal number for USB vendor IDs and product IDs // (restricted to 16 bits). constexpr int kSixteenBitsMaximum = 0xffff; // Convenience struct containing parsed metadata of type T. template struct ParsedMetadataWithTimestamp { base::Time time_of_parse; T value; }; // Tracks the progress of a single call to // PpdMetadataManager::FindAllEmmsAvailableInIndex(). class ForwardIndexSearchContext { public: ForwardIndexSearchContext( const std::vector& emms, base::Time max_age, PpdMetadataManager::FindAllEmmsAvailableInIndexCallback cb) : emms_(emms), current_index_(), max_age_(max_age), cb_(std::move(cb)) {} ~ForwardIndexSearchContext() = default; ForwardIndexSearchContext(const ForwardIndexSearchContext&) = delete; ForwardIndexSearchContext& operator=(const ForwardIndexSearchContext&) = delete; ForwardIndexSearchContext(ForwardIndexSearchContext&&) = default; // The effective-make-and-model string currently being sought in the // forward index search tracked by this struct. base::StringPiece CurrentEmm() const { DCHECK_LT(current_index_, emms_.size()); return emms_[current_index_]; } // Returns whether the CurrentEmm() is the last one in |this| // that needs searching. bool CurrentEmmIsLast() const { DCHECK_LT(current_index_, emms_.size()); return current_index_ + 1 == emms_.size(); } void AdvanceToNextEmm() { DCHECK_LT(current_index_, emms_.size()); current_index_++; } // Called when the PpdMetadataManager has searched all appropriate // forward index metadata for all |emms_|. void PostCallback() { DCHECK(CurrentEmmIsLast()); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb_), cb_arg_)); } // Called when the PpdMetadataManager successfully maps the // CurrentEmm() to a ParsedIndexValues struct. void AddDataFromForwardIndexForCurrentEmm(const ParsedIndexValues& value) { cb_arg_.insert_or_assign(CurrentEmm(), value); } base::Time MaxAge() const { return max_age_; } private: // List of all effective-make-and-model strings that caller gave to // PpdMetadataManager::FindAllEmmsAvailableInIndex(). std::vector emms_; // Index into |emms| that marks the effective-make-and-model string // currently being searched. size_t current_index_; // Freshness requirement for forward indices that this search reads. base::Time max_age_; // Callback that caller gave to // PpdMetadataManager::FindAllEmmsAvailableInIndex(). PpdMetadataManager::FindAllEmmsAvailableInIndexCallback cb_; // Accrues data to pass to |cb|. base::flat_map cb_arg_; }; // Enqueues calls to PpdMetadataManager::FindAllEmmsAvailableInIndex(). class ForwardIndexSearchQueue { public: ForwardIndexSearchQueue() = default; ~ForwardIndexSearchQueue() = default; ForwardIndexSearchQueue(const ForwardIndexSearchQueue&) = delete; ForwardIndexSearchQueue& operator=(const ForwardIndexSearchQueue&) = delete; void Enqueue(ForwardIndexSearchContext context) { contexts_.push(std::move(context)); } bool IsIdle() const { return contexts_.empty(); } ForwardIndexSearchContext& CurrentContext() { DCHECK(!IsIdle()); return contexts_.front(); } // Progresses the frontmost search context, advancing it to its // next effective-make-and-model string to find in forward index // metadata. // // If the frontmost search context has no more // effective-make-and-model strings to search, then // 1. its callback is posted from here and // 2. it is popped off the |contexts| queue. void AdvanceToNextEmm() { DCHECK(!IsIdle()); if (CurrentContext().CurrentEmmIsLast()) { CurrentContext().PostCallback(); contexts_.pop(); } else { CurrentContext().AdvanceToNextEmm(); } } private: base::queue contexts_; }; // Maps parsed metadata by name to parsed contents. // // Implementation note: the keys (metadata names) used here are // basenames attached to their containing directory - e.g. // * "metadata_v3/index-00.json" // * "metadata_v3/locales.json" // This is done to match up with the PrinterConfigCache class and // with the folder layout of the Chrome OS Printing serving root. template using CachedParsedMetadataMap = base::flat_map>; // Returns whether |map| has a value for |key| fresher than // |expiration|. template bool MapHasValueFresherThan(const CachedParsedMetadataMap& metadata_map, base::StringPiece key, base::Time expiration) { if (!metadata_map.contains(key)) { return false; } const auto& value = metadata_map.at(key); return value.time_of_parse > expiration; } // Calculates the shard number of |key| inside sharded metadata. int IndexShard(base::StringPiece key) { unsigned int hash = 5381; for (char c : key) { hash = hash * 33 + c; } return hash % kNumShards; } // Helper class used by PpdMetadataManagerImpl::SetMetadataLocale(). // Sifts through the list of locales advertised by the Chrome OS // Printing serving root and selects the best match for a // particular browser locale. // // This class must not outlive any data it is fed. // This class is neither copyable nor movable. class MetadataLocaleFinder { public: explicit MetadataLocaleFinder(const std::string& browser_locale) : browser_locale_(browser_locale), browser_locale_pieces_(base::SplitStringPiece(browser_locale, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)), is_english_available_(false) {} ~MetadataLocaleFinder() = default; MetadataLocaleFinder(const MetadataLocaleFinder&) = delete; MetadataLocaleFinder& operator=(const MetadataLocaleFinder&) = delete; // Finds and returns the best-fit metadata locale from |locales|. // Returns the empty string if no best candidate was found. base::StringPiece BestCandidate(base::span locales) { AnalyzeCandidates(locales); if (!best_parent_locale_.empty()) { return best_parent_locale_; } else if (!best_distant_relative_locale_.empty()) { return best_distant_relative_locale_; } else if (is_english_available_) { return "en"; } return base::StringPiece(); } private: // Returns whether or not |locale| appears to be a parent of our // |browser_locale_|. For example, "en-GB" is a parent of "en-GB-foo." bool IsParentOfBrowserLocale(base::StringPiece locale) const { const std::string locale_with_trailing_hyphen = base::StrCat({locale, "-"}); return base::StartsWith(browser_locale_, locale_with_trailing_hyphen); } // Updates our |best_distant_relative_locale_| to |locale| if we find // that it's a better match. // // The best distant relative locale is the one that // * has the longest piecewise match with |browser_locale_| but // * has the shortest piecewise length. // So given a |browser_locale_| "es," the better distant relative // locale between "es-GB" and "es-GB-foo" is "es-GB." void AnalyzeCandidateAsDistantRelative(base::StringPiece locale) { const std::vector locale_pieces = base::SplitStringPiece( locale, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); const size_t locale_piecewise_length = locale_pieces.size(); const size_t iter_limit = std::min(browser_locale_pieces_.size(), locale_piecewise_length); size_t locale_piecewise_match_length = 0; for (; locale_piecewise_match_length < iter_limit; locale_piecewise_match_length++) { if (locale_pieces[locale_piecewise_match_length] != browser_locale_pieces_[locale_piecewise_match_length]) { break; } } if (locale_piecewise_match_length == 0) { return; } else if (locale_piecewise_match_length > best_distant_relative_locale_piecewise_match_length_ || (locale_piecewise_match_length == best_distant_relative_locale_piecewise_match_length_ && locale_piecewise_length < best_distant_relative_locale_piecewise_length_)) { best_distant_relative_locale_ = std::string(locale); best_distant_relative_locale_piecewise_match_length_ = locale_piecewise_match_length; best_distant_relative_locale_piecewise_length_ = locale_piecewise_length; } } // Reads |locale| and updates our members as necessary. // For example, |locale| could reveal support for the "en" locale. void AnalyzeCandidate(base::StringPiece locale) { if (locale == "en") { is_english_available_ = true; } if (IsParentOfBrowserLocale(locale) && locale.size() > best_parent_locale_.size()) { best_parent_locale_ = std::string(locale); } else if (best_parent_locale_.empty()) { // We need only track distant relative locales if we don't have a // |best_parent_locale_|, which is always a better choice. AnalyzeCandidateAsDistantRelative(locale); } } // Analyzes all candidate locales in |locales|, updating our // private members with best-fit locale(s). void AnalyzeCandidates(base::span locales) { for (base::StringPiece locale : locales) { // The serving root indicates direct support for our browser // locale; there's no need to analyze anything else, since this // is definitely the best match we're going to get. if (locale == browser_locale_) { best_parent_locale_ = std::string(browser_locale_); return; } AnalyzeCandidate(locale); } } const base::StringPiece browser_locale_; const std::vector browser_locale_pieces_; // See IsParentOfBrowserLocale(). std::string best_parent_locale_; // See AnalyzeCandidateAsDistantRelative(). std::string best_distant_relative_locale_; size_t best_distant_relative_locale_piecewise_match_length_; size_t best_distant_relative_locale_piecewise_length_; // Denotes whether or not the Chrome OS Printing serving root serves // metadata for the "en" locale - our final fallback. bool is_english_available_; }; // Represents the basename and containing directory of a piece of PPD // metadata. Does not own any strings given to its setter methods and // must not outlive them. class PpdMetadataPathSpecifier { public: enum class Type { kLocales, kManufacturers, // locale-sensitive kPrinters, // locale-sensitive kForwardIndex, // sharded kReverseIndex, // locale-sensitive; sharded kUsbIndex, kUsbVendorIds, }; explicit PpdMetadataPathSpecifier(Type type) : type_(type), printers_basename_(nullptr), metadata_locale_(nullptr), shard_(0), usb_vendor_id_(0) {} ~PpdMetadataPathSpecifier() = default; // PpdMetadataPathSpecifier is neither copyable nor movable. PpdMetadataPathSpecifier(const PpdMetadataPathSpecifier&) = delete; PpdMetadataPathSpecifier& operator=(const PpdMetadataPathSpecifier&) = delete; void SetPrintersBasename(const char* const basename) { DCHECK_EQ(type_, Type::kPrinters); printers_basename_ = basename; } void SetMetadataLocale(const char* const locale) { DCHECK(type_ == Type::kManufacturers || type_ == Type::kReverseIndex); metadata_locale_ = locale; } void SetUsbVendorId(const int vendor_id) { DCHECK_EQ(type_, Type::kUsbIndex); usb_vendor_id_ = vendor_id; } void SetShard(const int shard) { DCHECK(type_ == Type::kForwardIndex || type_ == Type::kReverseIndex); shard_ = shard; } std::string AsString() const { switch (type_) { case Type::kLocales: return base::StringPrintf("%s/locales.json", kMetadataParentDirectory); case Type::kManufacturers: DCHECK(metadata_locale_); DCHECK(!base::StringPiece(metadata_locale_).empty()); return base::StringPrintf("%s/manufacturers-%s.json", kMetadataParentDirectory, metadata_locale_); case Type::kPrinters: DCHECK(printers_basename_); DCHECK(!base::StringPiece(printers_basename_).empty()); return base::StringPrintf("%s/%s", kMetadataParentDirectory, printers_basename_); case Type::kForwardIndex: DCHECK(shard_ >= 0 && shard_ < kNumShards); return base::StringPrintf("%s/index-%02d.json", kMetadataParentDirectory, shard_); case Type::kReverseIndex: DCHECK(metadata_locale_); DCHECK(!base::StringPiece(metadata_locale_).empty()); DCHECK(shard_ >= 0 && shard_ < kNumShards); return base::StringPrintf("%s/reverse_index-%s-%02d.json", kMetadataParentDirectory, metadata_locale_, shard_); case Type::kUsbIndex: DCHECK(usb_vendor_id_ >= 0 && usb_vendor_id_ <= kSixteenBitsMaximum); return base::StringPrintf("%s/usb-%04x.json", kMetadataParentDirectory, usb_vendor_id_); case Type::kUsbVendorIds: return base::StringPrintf("%s/usb_vendor_ids.json", kMetadataParentDirectory); } // This function cannot fail except by maintainer error. NOTREACHED(); return std::string(); } // Private const char* members are const char* for compatibility with // base::StringPrintf(). private: Type type_; // Populated only when |type_| == kPrinters. // Contains the basename of the target printers metadata file. const char* printers_basename_; // Populated only when |type_| is locale-sensitive and != kPrinters. // Contains the metadata locale for which we intend to fetch metadata. const char* metadata_locale_; // Populated only when |type_| is sharded. int shard_; // Populated only when |type_| == kUsbIndex. int usb_vendor_id_; }; // Note: generally, each Get*() method is segmented into three parts: // 1. check if query can be answered immediately, // 2. fetch appropriate metadata if it can't [defer to On*Fetched()], // and (time passes) // 3. answer query with appropriate metadata [call On*Available()]. class PpdMetadataManagerImpl : public PpdMetadataManager { public: PpdMetadataManagerImpl(base::StringPiece browser_locale, base::Clock* clock, std::unique_ptr config_cache) : browser_locale_(browser_locale), clock_(clock), config_cache_(std::move(config_cache)), weak_factory_(this) {} ~PpdMetadataManagerImpl() override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } void GetLocale(GetLocaleCallback cb) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Per header comment: if a best-fit metadata locale is already set, // we don't refresh it; we just immediately declare success. // // Side effect: classes composing |this| can call // SetLocaleForTesting() before composition and get this cop-out // for free. if (!metadata_locale_.empty()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), true)); return; } PpdMetadataPathSpecifier path(PpdMetadataPathSpecifier::Type::kLocales); const std::string metadata_name = path.AsString(); PrinterConfigCache::FetchCallback fetch_cb = base::BindOnce(&PpdMetadataManagerImpl::OnLocalesFetched, weak_factory_.GetWeakPtr(), std::move(cb)); // We call Fetch() with a default-constructed TimeDelta(): "give // me the freshest possible locales metadata." config_cache_->Fetch(metadata_name, base::TimeDelta(), std::move(fetch_cb)); } void GetManufacturers(base::TimeDelta age, PpdProvider::ResolveManufacturersCallback cb) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!metadata_locale_.empty()); PpdMetadataPathSpecifier path( PpdMetadataPathSpecifier::Type::kManufacturers); path.SetMetadataLocale(metadata_locale_.c_str()); const std::string metadata_name = path.AsString(); if (MapHasValueFresherThan(cached_manufacturers_, metadata_name, clock_->Now() - age)) { OnManufacturersAvailable(metadata_name, std::move(cb)); return; } PrinterConfigCache::FetchCallback fetch_cb = base::BindOnce(&PpdMetadataManagerImpl::OnManufacturersFetched, weak_factory_.GetWeakPtr(), std::move(cb)); config_cache_->Fetch(metadata_name, age, std::move(fetch_cb)); } void GetPrinters(base::StringPiece manufacturer, base::TimeDelta age, GetPrintersCallback cb) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!metadata_locale_.empty()); const auto metadata_name = GetPrintersMetadataName(manufacturer); if (!metadata_name.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), false, ParsedPrinters{})); return; } if (MapHasValueFresherThan(cached_printers_, metadata_name.value(), clock_->Now() - age)) { OnPrintersAvailable(metadata_name.value(), std::move(cb)); return; } PrinterConfigCache::FetchCallback fetch_cb = base::BindOnce(&PpdMetadataManagerImpl::OnPrintersFetched, weak_factory_.GetWeakPtr(), std::move(cb)); config_cache_->Fetch(metadata_name.value(), age, std::move(fetch_cb)); } void FindAllEmmsAvailableInIndex( const std::vector& emms, base::TimeDelta age, FindAllEmmsAvailableInIndexCallback cb) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ForwardIndexSearchContext context(emms, clock_->Now() - age, std::move(cb)); bool queue_was_idle = forward_index_search_queue_.IsIdle(); forward_index_search_queue_.Enqueue(std::move(context)); // If we are the prime movers, then we need to set the forward // index search in motion. if (queue_was_idle) { ContinueSearchingForwardIndices(); } // If we're not the prime movers, then a search is already ongoing // and we need not provide extra impetus. } void FindDeviceInUsbIndex(int vendor_id, int product_id, base::TimeDelta age, FindDeviceInUsbIndexCallback cb) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Fails the |cb| immediately if the |vendor_id| or |product_id| are // obviously out of range. if (vendor_id < 0 || vendor_id > kSixteenBitsMaximum || product_id < 0 || product_id > kSixteenBitsMaximum) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), std::string())); return; } PpdMetadataPathSpecifier path(PpdMetadataPathSpecifier::Type::kUsbIndex); path.SetUsbVendorId(vendor_id); const std::string metadata_name = path.AsString(); if (MapHasValueFresherThan(cached_usb_indices_, metadata_name, clock_->Now() - age)) { OnUsbIndexAvailable(metadata_name, product_id, std::move(cb)); return; } auto callback = base::BindOnce(&PpdMetadataManagerImpl::OnUsbIndexFetched, weak_factory_.GetWeakPtr(), metadata_name, product_id, std::move(cb)); config_cache_->Fetch(metadata_name, age, std::move(callback)); } void GetUsbManufacturerName(int vendor_id, base::TimeDelta age, GetUsbManufacturerNameCallback cb) override { PpdMetadataPathSpecifier path( PpdMetadataPathSpecifier::Type::kUsbVendorIds); const std::string metadata_name = path.AsString(); if (MapHasValueFresherThan(cached_usb_vendor_id_map_, metadata_name, clock_->Now() - age)) { OnUsbVendorIdMapAvailable(metadata_name, vendor_id, std::move(cb)); return; } auto fetch_cb = base::BindOnce(&PpdMetadataManagerImpl::OnUsbVendorIdMapFetched, weak_factory_.GetWeakPtr(), vendor_id, std::move(cb)); config_cache_->Fetch(metadata_name, age, std::move(fetch_cb)); } void SplitMakeAndModel(base::StringPiece effective_make_and_model, base::TimeDelta age, PpdProvider::ReverseLookupCallback cb) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!metadata_locale_.empty()); PpdMetadataPathSpecifier path( PpdMetadataPathSpecifier::Type::kReverseIndex); path.SetMetadataLocale(metadata_locale_.c_str()); path.SetShard(IndexShard(effective_make_and_model)); const std::string metadata_name = path.AsString(); if (MapHasValueFresherThan(cached_reverse_indices_, metadata_name, clock_->Now() - age)) { OnReverseIndexAvailable(metadata_name, effective_make_and_model, std::move(cb)); return; } PrinterConfigCache::FetchCallback fetch_cb = base::BindOnce(&PpdMetadataManagerImpl::OnReverseIndexFetched, weak_factory_.GetWeakPtr(), std::string(effective_make_and_model), std::move(cb)); config_cache_->Fetch(metadata_name, age, std::move(fetch_cb)); } PrinterConfigCache* GetPrinterConfigCacheForTesting() const override { return config_cache_.get(); } void SetLocaleForTesting(base::StringPiece locale) override { metadata_locale_ = std::string(locale); } // This method should read much the same as OnManufacturersFetched(). bool SetManufacturersForTesting( base::StringPiece manufacturers_json) override { DCHECK(!metadata_locale_.empty()); const auto parsed = ParseManufacturers(manufacturers_json); if (!parsed.has_value()) { return false; } // We need to name the manufacturers metadata manually to store it. PpdMetadataPathSpecifier path( PpdMetadataPathSpecifier::Type::kManufacturers); path.SetMetadataLocale(metadata_locale_.c_str()); const std::string manufacturers_name = path.AsString(); ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_manufacturers_.insert_or_assign(manufacturers_name, value); return true; } base::StringPiece ExposeMetadataLocaleForTesting() const override { return metadata_locale_; } private: // Denotes the status of an ongoing forward index search - see // FindAllEmmsAvailableInIndex(). enum class ForwardIndexSearchStatus { // We called |config_cache_|::Fetch(). We provided a bound // callback that will resume the forward index search for us when // the fetch completes. kWillResumeOnFetchCompletion, // We did not call |config_cache_|::Fetch(), so |this| still has // control of the progression of the forward index search. kCanContinue, }; // Called by OnLocalesFetched(). // Continues a prior call to GetLocale(). // // Attempts to set |metadata_locale_| given the advertised // |locales_list|. Returns true if successful and false if not. bool SetMetadataLocale(const std::vector& locales_list) { // This class helps track all the locales that _could_ be good fits // given our |browser_locale_| but which are not exact matches. MetadataLocaleFinder locale_finder(browser_locale_); metadata_locale_ = std::string(locale_finder.BestCandidate(locales_list)); return !metadata_locale_.empty(); } // Called back by |config_cache_|.Fetch(). // Continues a prior call to GetLocale(). // // On successful |result|, parses and sets the |metadata_locale_|. // Calls |cb| with the |result|. void OnLocalesFetched(GetLocaleCallback cb, const PrinterConfigCache::FetchResult& result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!result.succeeded) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), false)); return; } const auto parsed = ParseLocales(result.contents); if (!parsed.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), false)); return; } // SetMetadataLocale() _can_ fail, but that would be an // extraordinarily bad thing - i.e. that the Chrome OS Printing // serving root is itself in an invalid state. base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), SetMetadataLocale(parsed.value()))); } // Called by one of // * GetManufacturers() or // * OnManufacturersFetched(). // Continues a prior call to GetManufacturers(). // // Invokes |cb| with success, providing it with a list of // manufacturers. void OnManufacturersAvailable(base::StringPiece metadata_name, PpdProvider::ResolveManufacturersCallback cb) { const auto& parsed_manufacturers = cached_manufacturers_.at(metadata_name); std::vector manufacturers_for_cb; for (const auto& iter : parsed_manufacturers.value) { manufacturers_for_cb.push_back(iter.first); } std::sort(manufacturers_for_cb.begin(), manufacturers_for_cb.end()); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::SUCCESS, manufacturers_for_cb)); } // Called by |config_cache_|.Fetch(). // Continues a prior call to GetManufacturers(). // // Parses and updates our cached map of manufacturers if |result| // indicates a successful fetch. Calls |cb| accordingly. void OnManufacturersFetched(PpdProvider::ResolveManufacturersCallback cb, const PrinterConfigCache::FetchResult& result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!result.succeeded) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::SERVER_ERROR, std::vector{})); return; } const auto parsed = ParseManufacturers(result.contents); if (!parsed.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR, std::vector{})); return; } ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_manufacturers_.insert_or_assign(result.key, value); OnManufacturersAvailable(result.key, std::move(cb)); } // Called by GetPrinters(). // Returns the known name for the Printers metadata named by // |manufacturer|. absl::optional GetPrintersMetadataName( base::StringPiece manufacturer) { PpdMetadataPathSpecifier manufacturers_path( PpdMetadataPathSpecifier::Type::kManufacturers); manufacturers_path.SetMetadataLocale(metadata_locale_.c_str()); const std::string manufacturers_metadata_name = manufacturers_path.AsString(); if (!cached_manufacturers_.contains(manufacturers_metadata_name)) { // This is likely a bug: we don't have the expected manufacturers // metadata. return absl::nullopt; } const ParsedMetadataWithTimestamp& manufacturers = cached_manufacturers_.at(manufacturers_metadata_name); if (!manufacturers.value.contains(manufacturer)) { // This is likely a bug: we don't know about this manufacturer. return absl::nullopt; } PpdMetadataPathSpecifier printers_path( PpdMetadataPathSpecifier::Type::kPrinters); printers_path.SetPrintersBasename( manufacturers.value.at(manufacturer).c_str()); return printers_path.AsString(); } // Called by one of // * GetPrinters() or // * OnPrintersFetched(). // Continues a prior call to GetPrinters(). // // Invokes |cb| with success, providing it a map of printers. void OnPrintersAvailable(base::StringPiece metadata_name, GetPrintersCallback cb) { const auto& parsed_printers = cached_printers_.at(metadata_name); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), true, parsed_printers.value)); } // Called by |config_cache_|.Fetch(). // Continues a prior call to GetPrinters(). // // Parses and updates our cached map of printers if |result| indicates // a successful fetch. Calls |cb| accordingly. void OnPrintersFetched(GetPrintersCallback cb, const PrinterConfigCache::FetchResult& result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!result.succeeded) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), false, ParsedPrinters{})); return; } const auto parsed = ParsePrinters(result.contents); if (!parsed.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), false, ParsedPrinters{})); return; } ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_printers_.insert_or_assign(result.key, value); OnPrintersAvailable(result.key, std::move(cb)); } // Called when one unit of sufficiently fresh forward index metadata // is available. Seeks out the current effective-make-and-model string // in said metadata. void FindEmmInForwardIndex(base::StringPiece metadata_name) { // Caller must have verified that this index is already present (and // sufficiently fresh) before entering this method. DCHECK(cached_forward_indices_.contains(metadata_name)); ForwardIndexSearchContext& context = forward_index_search_queue_.CurrentContext(); const ParsedIndex& index = cached_forward_indices_.at(metadata_name).value; const auto& iter = index.find(context.CurrentEmm()); if (iter != index.end()) { context.AddDataFromForwardIndexForCurrentEmm(iter->second); } forward_index_search_queue_.AdvanceToNextEmm(); } // Called by |config_cache_|.Fetch(). // Continues a prior call to FindAllEmmsAvailableInForwardIndex(). // // Parses and updates our cached map of forward indices if |result| // indicates a successful fetch. Continues the action that // necessitated fetching the present forward index. void OnForwardIndexFetched(const PrinterConfigCache::FetchResult& result) { if (!result.succeeded) { // We failed to fetch the forward index containing the current // effective-make-and-model string. There's nothing we can do but // carry on, e.g. by moving to deal with the next emm. forward_index_search_queue_.AdvanceToNextEmm(); ContinueSearchingForwardIndices(); return; } const auto parsed = ParseForwardIndex(result.contents); if (!parsed.has_value()) { // Same drill as fetch failure above. forward_index_search_queue_.AdvanceToNextEmm(); ContinueSearchingForwardIndices(); return; } ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_forward_indices_.insert_or_assign(result.key, value); ContinueSearchingForwardIndices(); } // Works on searching the forward index for the current // effective-make-and-model string in the frontmost entry in the // forward index search queue. // // One invocation of this method ultimately processes exactly one // effective-make-and-model string: either we find it in some forward // index metadata or we don't. ForwardIndexSearchStatus SearchForwardIndicesForOneEmm() { const ForwardIndexSearchContext& context = forward_index_search_queue_.CurrentContext(); PpdMetadataPathSpecifier path( PpdMetadataPathSpecifier::Type::kForwardIndex); path.SetShard(IndexShard(context.CurrentEmm())); const std::string forward_index_name = path.AsString(); if (MapHasValueFresherThan(cached_forward_indices_, forward_index_name, context.MaxAge())) { // We have the appropriate forward index metadata and it's fresh // enough to make a determination: is the current // effective-make-and-model string present in this metadata? FindEmmInForwardIndex(forward_index_name); return ForwardIndexSearchStatus::kCanContinue; } // We don't have the appropriate forward index metadata. We need to // get it before we can determine if the current // effective-make-and-model string is present in it. // // PrinterConfigCache::Fetch() accepts a TimeDelta expressing the // maximum permissible age of the cached response; to simulate the // original TimeDelta that caller gave to // FindAllEmmsAvailableInIndex(), we find the delta between Now() // and the absolute time ceiling recorded in the // ForwardIndexSearchContext. auto callback = base::BindOnce(&PpdMetadataManagerImpl::OnForwardIndexFetched, weak_factory_.GetWeakPtr()); config_cache_->Fetch(forward_index_name, clock_->Now() - context.MaxAge(), std::move(callback)); return ForwardIndexSearchStatus::kWillResumeOnFetchCompletion; } // Continues working on the forward index search queue. void ContinueSearchingForwardIndices() { while (!forward_index_search_queue_.IsIdle()) { ForwardIndexSearchStatus status = SearchForwardIndicesForOneEmm(); // If we invoked |config_cache_|::Fetch(), then control has passed // out of this class for now. It will resume from // OnForwardIndexFetched(). if (status == ForwardIndexSearchStatus::kWillResumeOnFetchCompletion) { break; } } } // Called by one of // * FindDeviceInUsbIndex() or // * OnUsbIndexFetched(). // Searches the now-available USB index metadata with |metadata_name| // for a device with given |product_id|, calling |cb| appropriately. void OnUsbIndexAvailable(base::StringPiece metadata_name, int product_id, FindDeviceInUsbIndexCallback cb) { DCHECK(cached_usb_indices_.contains(metadata_name)); const ParsedUsbIndex& usb_index = cached_usb_indices_.at(metadata_name).value; const auto& iter = usb_index.find(product_id); std::string effective_make_and_model; if (iter != usb_index.end()) { effective_make_and_model = iter->second; } base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), std::move(effective_make_and_model))); } // Called by |config_cache_|.Fetch(). // Continues a prior call to FindDeviceInUsbIndex(). // // Parses and updates our cached map of USB index metadata if |result| // indicates a successful fetch. void OnUsbIndexFetched(std::string metadata_name, int product_id, FindDeviceInUsbIndexCallback cb, const PrinterConfigCache::FetchResult& fetch_result) { if (!fetch_result.succeeded) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), std::string())); return; } absl::optional parsed = ParseUsbIndex(fetch_result.contents); if (!parsed.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), std::string())); return; } DCHECK(fetch_result.key == metadata_name); ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_usb_indices_.insert_or_assign(fetch_result.key, value); OnUsbIndexAvailable(fetch_result.key, product_id, std::move(cb)); } // Called by one of // * GetUsbManufacturerName() or // * OnUsbVendorIdMapFetched(). // // Searches the available USB vendor ID map (named by |metadata_name|) // for |vendor_id| and invokes |cb| accordingly. void OnUsbVendorIdMapAvailable(base::StringPiece metadata_name, int vendor_id, GetUsbManufacturerNameCallback cb) { DCHECK(cached_usb_vendor_id_map_.contains(metadata_name)); ParsedUsbVendorIdMap usb_vendor_id_map = cached_usb_vendor_id_map_.at(metadata_name).value; std::string manufacturer_name; const auto& iter = usb_vendor_id_map.find(vendor_id); if (iter != usb_vendor_id_map.end()) { manufacturer_name = iter->second; } base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), manufacturer_name)); } // Called by |config_cache_|->Fetch(). // Continues a prior call to GetUsbManufacturerName. // // Parses and updates our cached map of USB vendor IDs if |result| // indicates a successful fetch. // // If we're haggling over bits, it is wasteful to have a map that // only ever has at most one key-value pair. We willfully accept this // inefficiency to maintain consistency with other metadata // operations. void OnUsbVendorIdMapFetched( int vendor_id, GetUsbManufacturerNameCallback cb, const PrinterConfigCache::FetchResult& fetch_result) { if (!fetch_result.succeeded) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), std::string())); return; } const absl::optional parsed = ParseUsbVendorIdMap(fetch_result.contents); if (!parsed.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), std::string())); return; } ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_usb_vendor_id_map_.insert_or_assign(fetch_result.key, value); OnUsbVendorIdMapAvailable(fetch_result.key, vendor_id, std::move(cb)); } // Called by one of // * SplitMakeAndModel() or // * OnReverseIndexFetched(). // Continues a prior call to SplitMakeAndModel(). // // Looks for |effective_make_and_model| in the reverse index named by // |metadata_name|, and tries to invoke |cb| with the split make and // model. void OnReverseIndexAvailable(base::StringPiece metadata_name, base::StringPiece effective_make_and_model, PpdProvider::ReverseLookupCallback cb) { const auto& parsed_reverse_index = cached_reverse_indices_.at(metadata_name); // We expect this reverse index shard to contain the decomposition // for |effective_make_and_model|. if (!parsed_reverse_index.value.contains(effective_make_and_model)) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::NOT_FOUND, "", "")); return; } const ReverseIndexLeaf& leaf = parsed_reverse_index.value.at(effective_make_and_model); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::SUCCESS, leaf.manufacturer, leaf.model)); } // Called by |config_cache_|.Fetch(). // Continues a prior call to SplitMakeAndModel(). // // Parses and updates our cached map of reverse indices if |result| // indicates a successful fetch. Calls |cb| accordingly. void OnReverseIndexFetched(std::string effective_make_and_model, PpdProvider::ReverseLookupCallback cb, const PrinterConfigCache::FetchResult& result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!result.succeeded) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::SERVER_ERROR, "", "")); return; } const auto parsed = ParseReverseIndex(result.contents); if (!parsed.has_value()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR, "", "")); return; } ParsedMetadataWithTimestamp value = {clock_->Now(), parsed.value()}; cached_reverse_indices_.insert_or_assign(result.key, value); OnReverseIndexAvailable(result.key, effective_make_and_model, std::move(cb)); } const std::string browser_locale_; raw_ptr clock_; // The closest match to |browser_locale_| for which the serving root // claims to serve metadata. std::string metadata_locale_; std::unique_ptr config_cache_; CachedParsedMetadataMap cached_manufacturers_; CachedParsedMetadataMap cached_printers_; CachedParsedMetadataMap cached_forward_indices_; CachedParsedMetadataMap cached_usb_indices_; CachedParsedMetadataMap cached_usb_vendor_id_map_; CachedParsedMetadataMap cached_reverse_indices_; // Processing queue for FindAllEmmsAvailableInIndex(). ForwardIndexSearchQueue forward_index_search_queue_; SEQUENCE_CHECKER(sequence_checker_); // Dispenses weak pointers to the |config_cache_|. This is necessary // because |this| could be deleted while the |config_cache_| is // processing something off-sequence. base::WeakPtrFactory weak_factory_; }; } // namespace // static std::unique_ptr PpdMetadataManager::Create( base::StringPiece browser_locale, base::Clock* clock, std::unique_ptr config_cache) { return std::make_unique(browser_locale, clock, std::move(config_cache)); } } // namespace chromeos