chromium/chromeos/printing/ppd_metadata_manager.cc

// Copyright 2020 The Chromium Authors
// 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 <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#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_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.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 "ppd_metadata_manager.h"

namespace chromeos {

namespace {

// 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 <typename T>
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<std::string>& 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.
  std::string_view 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::SequencedTaskRunner::GetCurrentDefault()->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<std::string> 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<std::string, ParsedIndexValues> 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<ForwardIndexSearchContext> 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 <typename T>
using CachedParsedMetadataMap =
    base::flat_map<std::string, ParsedMetadataWithTimestamp<T>>;

// Returns whether |map| has a value for |key| fresher than
// |expiration|.
template <typename T>
bool MapHasValueFresherThan(const CachedParsedMetadataMap<T>& metadata_map,
                            std::string_view 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(std::string_view 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.
  std::string_view BestCandidate(base::span<const std::string> 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 std::string_view();
  }

 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(std::string_view 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(std::string_view locale) {
    const std::vector<std::string_view> 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(std::string_view 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<const std::string> locales) {
    for (std::string_view 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 std::string_view browser_locale_;
  const std::vector<std::string_view> 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,
  };

  PpdMetadataPathSpecifier(Type type, PpdIndexChannel channel)
      : type_(type),
        channel_(channel),
        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", MetadataParentDirectory());

      case Type::kManufacturers:
        DCHECK(metadata_locale_);
        DCHECK(!std::string_view(metadata_locale_).empty());
        return base::StringPrintf("%s/manufacturers-%s.json",
                                  MetadataParentDirectory(), metadata_locale_);

      case Type::kPrinters:
        DCHECK(printers_basename_);
        DCHECK(!std::string_view(printers_basename_).empty());
        return base::StringPrintf("%s/%s", MetadataParentDirectory(),
                                  printers_basename_);

      case Type::kForwardIndex:
        DCHECK(shard_ >= 0 && shard_ < kNumShards);
        return base::StringPrintf("%s/index-%02d.json",
                                  MetadataParentDirectory(), shard_);

      case Type::kReverseIndex:
        DCHECK(metadata_locale_);
        DCHECK(!std::string_view(metadata_locale_).empty());
        DCHECK(shard_ >= 0 && shard_ < kNumShards);
        return base::StringPrintf("%s/reverse_index-%s-%02d.json",
                                  MetadataParentDirectory(), metadata_locale_,
                                  shard_);

      case Type::kUsbIndex:
        DCHECK(usb_vendor_id_ >= 0 && usb_vendor_id_ <= kSixteenBitsMaximum);
        return base::StringPrintf("%s/usb-%04x.json", MetadataParentDirectory(),
                                  usb_vendor_id_);

      case Type::kUsbVendorIds:
        return base::StringPrintf("%s/usb_vendor_ids.json",
                                  MetadataParentDirectory());
    }

    // This function cannot fail except by maintainer error.
    NOTREACHED_IN_MIGRATION();

    return std::string();
  }

 private:
  // Defines the containing directory of all metadata in the serving root.
  const char* MetadataParentDirectory() const {
    switch (channel_) {
      case PpdIndexChannel::kLocalhost:
        [[fallthrough]];
      case PpdIndexChannel::kProduction:
        return "metadata_v3";
      case PpdIndexChannel::kStaging:
        return "metadata_v3_staging";
      case PpdIndexChannel::kDev:
        return "metadata_v3_dev";
    }
  }

  Type type_;
  const PpdIndexChannel channel_;

  // Private const char* members are const char* for compatibility with
  // base::StringPrintf().

  // 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(std::string_view browser_locale,
                         PpdIndexChannel channel,
                         base::Clock* clock,
                         std::unique_ptr<PrinterConfigCache> config_cache)
      : browser_locale_(browser_locale),
        channel_(channel),
        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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), true));
      return;
    }

    PpdMetadataPathSpecifier path(PpdMetadataPathSpecifier::Type::kLocales,
                                  channel_);
    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, channel_);
    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(std::string_view 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::SequencedTaskRunner::GetCurrentDefault()->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<std::string>& 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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), std::string()));
      return;
    }

    PpdMetadataPathSpecifier path(PpdMetadataPathSpecifier::Type::kUsbIndex,
                                  channel_);
    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,
                                  channel_);
    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(std::string_view 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,
                                  channel_);
    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(std::string_view locale) override {
    metadata_locale_ = std::string(locale);
  }

  // This method should read much the same as OnManufacturersFetched().
  bool SetManufacturersForTesting(
      std::string_view 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, channel_);
    path.SetMetadataLocale(metadata_locale_.c_str());
    const std::string manufacturers_name = path.AsString();

    ParsedMetadataWithTimestamp<ParsedManufacturers> value = {clock_->Now(),
                                                              parsed.value()};
    cached_manufacturers_.insert_or_assign(manufacturers_name, value);
    return true;
  }

  std::string_view 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<std::string>& 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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), false));
      return;
    }
    const auto parsed = ParseLocales(result.contents);
    if (!parsed.has_value()) {
      base::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->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(std::string_view metadata_name,
                                PpdProvider::ResolveManufacturersCallback cb) {
    const auto& parsed_manufacturers = cached_manufacturers_.at(metadata_name);
    std::vector<std::string> 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::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(cb),
                         PpdProvider::CallbackResultCode::SERVER_ERROR,
                         std::vector<std::string>{}));
      return;
    }

    const auto parsed = ParseManufacturers(result.contents);
    if (!parsed.has_value()) {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(cb),
                         PpdProvider::CallbackResultCode::INTERNAL_ERROR,
                         std::vector<std::string>{}));
      return;
    }

    ParsedMetadataWithTimestamp<ParsedManufacturers> 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|.
  std::optional<std::string> GetPrintersMetadataName(
      std::string_view manufacturer) {
    PpdMetadataPathSpecifier manufacturers_path(
        PpdMetadataPathSpecifier::Type::kManufacturers, channel_);
    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 std::nullopt;
    }

    const ParsedMetadataWithTimestamp<ParsedManufacturers>& 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 std::nullopt;
    }

    PpdMetadataPathSpecifier printers_path(
        PpdMetadataPathSpecifier::Type::kPrinters, channel_);
    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(std::string_view metadata_name,
                           GetPrintersCallback cb) {
    const auto& parsed_printers = cached_printers_.at(metadata_name);
    base::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), false, ParsedPrinters{}));
      return;
    }

    const auto parsed = ParsePrinters(result.contents);
    if (!parsed.has_value()) {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), false, ParsedPrinters{}));
      return;
    }

    ParsedMetadataWithTimestamp<ParsedPrinters> 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(std::string_view 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<ParsedIndex> 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,
                                  channel_);
    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(std::string_view 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::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), std::string()));
      return;
    }

    std::optional<ParsedUsbIndex> parsed = ParseUsbIndex(fetch_result.contents);
    if (!parsed.has_value()) {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), std::string()));
      return;
    }

    DCHECK(fetch_result.key == metadata_name);
    ParsedMetadataWithTimestamp<ParsedUsbIndex> 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(std::string_view 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::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), std::string()));
      return;
    }

    const std::optional<ParsedUsbVendorIdMap> parsed =
        ParseUsbVendorIdMap(fetch_result.contents);
    if (!parsed.has_value()) {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(cb), std::string()));
      return;
    }

    ParsedMetadataWithTimestamp<ParsedUsbVendorIdMap> 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(std::string_view metadata_name,
                               std::string_view 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::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(cb),
                         PpdProvider::CallbackResultCode::SERVER_ERROR, "",
                         ""));
      return;
    }

    const auto parsed = ParseReverseIndex(result.contents);
    if (!parsed.has_value()) {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(cb),
                         PpdProvider::CallbackResultCode::INTERNAL_ERROR, "",
                         ""));
      return;
    }

    ParsedMetadataWithTimestamp<ParsedReverseIndex> 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_;
  const PpdIndexChannel channel_;
  raw_ptr<const base::Clock> clock_;

  // The closest match to |browser_locale_| for which the serving root
  // claims to serve metadata.
  std::string metadata_locale_;

  std::unique_ptr<PrinterConfigCache> config_cache_;

  CachedParsedMetadataMap<ParsedManufacturers> cached_manufacturers_;
  CachedParsedMetadataMap<ParsedPrinters> cached_printers_;
  CachedParsedMetadataMap<ParsedIndex> cached_forward_indices_;
  CachedParsedMetadataMap<ParsedUsbIndex> cached_usb_indices_;
  CachedParsedMetadataMap<ParsedUsbVendorIdMap> cached_usb_vendor_id_map_;
  CachedParsedMetadataMap<ParsedReverseIndex> 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<PpdMetadataManagerImpl> weak_factory_;
};

}  // namespace

// static
std::unique_ptr<PpdMetadataManager> PpdMetadataManager::Create(
    std::string_view browser_locale,
    PpdIndexChannel channel,
    base::Clock* clock,
    std::unique_ptr<PrinterConfigCache> config_cache) {
  return std::make_unique<PpdMetadataManagerImpl>(
      browser_locale, channel, clock, std::move(config_cache));
}

}  // namespace chromeos