chromium/components/fuchsia_component_support/annotations_manager.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/fuchsia_component_support/annotations_manager.h"

#include <lib/fpromise/promise.h>

#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>

#include "base/check.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/mem_buffer_util.h"
#include "base/logging.h"
#include "base/memory/raw_ref.h"
#include "base/strings/string_number_conversions.h"

namespace fuchsia_component_support {

namespace {
using AnnotationKeySet =
    std::set<fuchsia::element::AnnotationKey, AnnotationKeyCompare>;
}  // namespace

fuchsia::element::Annotation MakeAnnotation(std::string_view key,
                                            std::string_view value) {
  fuchsia::element::Annotation result;
  result.key.namespace_ = "global";
  result.key.value = std::string(key);

  constexpr size_t kArbitraryTextBufferThreshold = 128;
  if (value.size() <= kArbitraryTextBufferThreshold) {
    result.value.set_text(std::string(value));
  } else {
    result.value.set_buffer(base::MemBufferFromString(value, /*name=*/key));
  }

  return result;
}

fuchsia::element::Annotation MakeBoolAnnotation(std::string_view key,
                                                bool value) {
  static constexpr std::string_view kTrue("true");
  static constexpr std::string_view kFalse("false");
  return MakeAnnotation(key, value ? kTrue : kFalse);
}

fuchsia::element::Annotation MakeIntAnnotation(std::string_view key,
                                               int value) {
  return MakeAnnotation(key, base::NumberToString(value));
}

bool AnnotationKeyCompare::operator()(
    const fuchsia::element::AnnotationKey& key1,
    const fuchsia::element::AnnotationKey& key2) const {
  return std::tie(key1.namespace_, key1.value) <
         std::tie(key2.namespace_, key2.value);
}

class AnnotationsManager::ControllerImpl
    : public fuchsia::element::AnnotationController {
 public:
  explicit ControllerImpl(AnnotationsManager& manager) : manager_(manager) {}
  ~ControllerImpl() override = default;

  // fuchsia::element::AnnotationController implementation.
  void UpdateAnnotations(
      std::vector<fuchsia::element::Annotation> annotations_to_set,
      std::vector<fuchsia::element::AnnotationKey> annotations_to_delete,
      UpdateAnnotationsCallback callback) override {
    DVLOG(2) << __func__;
    if (manager_->UpdateAnnotations(std::move(annotations_to_set),
                                    std::move(annotations_to_delete))) {
      callback(fpromise::ok());
    } else {
      callback(fpromise::error(
          fuchsia::element::UpdateAnnotationsError::INVALID_ARGS));
    }
  }
  void GetAnnotations(GetAnnotationsCallback callback) override {
    DVLOG(2) << __func__;
    callback(fpromise::ok(manager_->GetAnnotations()));
  }
  void WatchAnnotations(WatchAnnotationsCallback callback) override {
    DVLOG(2) << __func__;

    if (have_changes_) {
      have_changes_ = false;
      callback(fpromise::ok(manager_->GetAnnotations()));
      return;
    }

    if (on_annotations_changed_) {
      manager_->bindings_.CloseBinding(this, ZX_ERR_BAD_STATE);
      return;
    }

    on_annotations_changed_ = std::move(callback);
  }

  void OnAnnotationsChanged() {
    have_changes_ = true;
    if (on_annotations_changed_) {
      WatchAnnotations(std::exchange(on_annotations_changed_, {}));
    }
  }

 private:
  const raw_ref<AnnotationsManager> manager_;

  // Initially true so that the first call to WatchAnnotations() will
  // return results immediately.
  bool have_changes_ = true;

  WatchAnnotationsCallback on_annotations_changed_;
};

AnnotationsManager::AnnotationsManager() = default;

AnnotationsManager::~AnnotationsManager() = default;

bool AnnotationsManager::UpdateAnnotations(
    std::vector<fuchsia::element::Annotation> to_set,
    std::vector<fuchsia::element::AnnotationKey> to_delete) {
  AnnotationKeySet changed;
  for (const auto& key : to_delete) {
    annotations_.erase(key);
    auto [it, inserted] = changed.insert(key);
    if (!inserted) {
      return false;
    }
  }
  for (auto& annotation : to_set) {
    annotations_.insert_or_assign(
        fuchsia::element::AnnotationKey(annotation.key),
        std::move(annotation.value));
    auto [it, inserted] = changed.insert(annotation.key);
    if (!inserted) {
      return false;
    }
  }

  if (!changed.empty()) {
    for (auto& binding : bindings_.bindings()) {
      binding->impl()->OnAnnotationsChanged();
    }
  }

  return true;
}

std::vector<fuchsia::element::Annotation> AnnotationsManager::GetAnnotations()
    const {
  std::vector<fuchsia::element::Annotation> result;
  result.reserve(annotations_.size());
  for (const auto& [key, value] : annotations_) {
    result.push_back(fuchsia::element::Annotation{.key = key});
    zx_status_t status = value.Clone(&result.back().value);
    ZX_CHECK(status == ZX_OK, status);
  }
  return result;
}

void AnnotationsManager::Connect(
    fidl::InterfaceRequest<fuchsia::element::AnnotationController> request) {
  bindings_.AddBinding(std::make_unique<ControllerImpl>(*this),
                       std::move(request));
}

}  // namespace fuchsia_component_support