// Copyright 2019 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.

#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDER_MAP_H_
#define MOJO_PUBLIC_CPP_BINDINGS_BINDER_MAP_H_

#include <map>
#include <string>

#include "base/callback.h"
#include "base/component_export.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner_forward.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/lib/binder_map_internal.h"

namespace mojo {

// BinderMapWithContext is a helper class that maintains a registry of
// callbacks that bind receivers for arbitrary Mojo interfaces. By default the
// map is empty and cannot bind any interfaces.
//
// Call |Add()| to register a new binder for a specific interface.
// Call |TryBind()| to attempt to run a registered binder on the generic
// input receiver. If a suitable binder is found, it will take ownership of the
// receiver.
//
// If a non-void ContextType is specified, registered callbacks must accept an
// additional ContextType argument, and each invocation of |TryBind()| must
// provide such a value.
//
// NOTE: Most common uses of BinderMapWithContext do not require a context value
// per bind request. Use the BinderMap alias defined below this class in such
// cases.
template <typename ContextType>
class BinderMapWithContext {
 public:
  using Traits = internal::BinderContextTraits<ContextType>;
  using ContextValueType = typename Traits::ValueType;
  using GenericBinderType = typename Traits::GenericBinderType;

  template <typename Interface>
  using BinderType = typename Traits::template BinderType<Interface>;

  BinderMapWithContext() = default;
  BinderMapWithContext(const BinderMapWithContext&) = default;
  BinderMapWithContext(BinderMapWithContext&&) = default;
  ~BinderMapWithContext() = default;

  BinderMapWithContext& operator=(const BinderMapWithContext&) = default;
  BinderMapWithContext& operator=(BinderMapWithContext&&) = default;

  // Adds a new binder specifically for Interface receivers. This exists for the
  // convenience of being able to register strongly-typed binding methods like:
  //
  //   void OnBindFoo(mojo::PendingReceiver<Foo> receiver) { ... }
  //
  // more easily.
  //
  // If |Add()| is called multiple times for the same interface, the most
  // recent one replaces any existing binder.
  template <typename Interface>
  void Add(BinderType<Interface> binder,
           scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
    binders_[Interface::Name_] = std::make_unique<
        internal::GenericCallbackBinderWithContext<ContextType>>(
        Traits::MakeGenericBinder(std::move(binder)), std::move(task_runner));
  }

  // Attempts to bind the |receiver| using one of the registered binders in
  // this map. If a matching binder is found, ownership of the |receiver|'s
  // MessagePipe will be transferred and this will return |true|. If the binder
  // was registered with a SequencedTaskRunner, the binder will be dispatched
  // asynchronously on it; otherwise, it will be called directly.
  //
  // If no matching binder is found, this returns |false| and the |receiver|
  // will be left intact for the caller.
  //
  // This method is only usable when ContextType is void.
  bool TryBind(mojo::GenericPendingReceiver* receiver) WARN_UNUSED_RESULT {
    static_assert(IsVoidContext::value,
                  "TryBind() must be called with a context value when "
                  "ContextType is non-void.");
    auto it = binders_.find(*receiver->interface_name());
    if (it == binders_.end())
      return false;

    it->second->BindInterface(receiver->PassPipe());
    return true;
  }

  // Like above, but passes |context| to the binder if one exists. Only usable
  // when ContextType is non-void.
  bool TryBind(ContextValueType context,
               mojo::GenericPendingReceiver* receiver) WARN_UNUSED_RESULT {
    static_assert(!IsVoidContext::value,
                  "TryBind() must be called without a context value when "
                  "ContextType is void.");
    auto it = binders_.find(*receiver->interface_name());
    if (it == binders_.end())
      return default_binder_ && default_binder_.Run(context, *receiver);

    it->second->BindInterface(std::move(context), receiver->PassPipe());
    return true;
  }

  // DO NOT USE. This sets a generic default handler for any receiver that
  // doesn't match a registered binder. It's a transitional API to help migrate
  // some older code to BinderMap. Reliance on this mechanism makes security
  // auditing more difficult. Note that this intentionally only supports use
  // with a non-void ContextType, since that's the only existing use case.
  using DefaultBinder =
      base::RepeatingCallback<bool(ContextValueType context,
                                   mojo::GenericPendingReceiver&)>;
  void SetDefaultBinderDeprecated(DefaultBinder binder) {
    default_binder_ = std::move(binder);
  }

 private:
  using IsVoidContext = std::is_same<ContextType, void>;

  std::map<
      std::string,
      std::unique_ptr<internal::GenericCallbackBinderWithContext<ContextType>>>
      binders_;
  DefaultBinder default_binder_;
};

// Common alias for BinderMapWithContext that has no context. Binders added to
// this type of map will only take a single PendingReceiver<T> argument (or a
// GenericPendingReceiver).
class BinderMap : public BinderMapWithContext<void> {};

}  // namespace mojo

#endif  // MOJO_PUBLIC_CPP_BINDINGS_BINDER_MAP_H_
