blob: 4169ff2fbb667bb935c97d0e24a297831babc42d [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_INSPECTABLE_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_INSPECTABLE_H_
#include <lib/fit/function.h>
#include <lib/sys/inspect/cpp/component.h>
#include <string>
#include <fbl/macros.h>
namespace bt {
// InspectableGuard is returned by |Inspectable::Mutable()|.
// It will update the corresponding Inspect property when it is destroyed.
// Therefore, the lifetime of InspectableGuard should usually be that of a temporary, or be scoped
// for a single update.
//
// InspectableGuard's primary use cases are calling non-const methods on objects and assigning
// member variable values in structs.
//
// Example:
// StringInspectable<hci::LMPFeatureSet> lmp_features;
// lmp_features.Mutable()->SetPage(page, features);
template <typename ValueT>
class InspectableGuard {
public:
InspectableGuard(ValueT& value, fit::closure update_cb)
: value_(value), update_cb_(std::move(update_cb)) {}
~InspectableGuard() { update_cb_(); }
ValueT& operator*() { return value_; }
ValueT* operator->() { return &value_; }
private:
ValueT& value_;
fit::closure update_cb_;
DISALLOW_COPY_ASSIGN_AND_MOVE(InspectableGuard);
};
// Inspectable is a utility class for keeping inspected values in sync with their corresponding
// Inspect property.
//
// PropertyT must be an Inspect property type such that PropertyT::Set(PropertyInnerT) is valid.
// PropertyInnerT corresponds to the type contained in PropertyT (e.g. string is contained by
// inspect::StringProperty).
//
// Example:
// inspect::Inspector inspector;
// auto& root = inspector.GetRoot();
// Inspectable inspectable(std::string("A"), root.CreateString(std::string("property_name"));
//
// // Hierarchy: { root: property_name: "A" }
//
// inspectable.Set("B");
//
// // Hierarchy: { root: property_name: "B" }
template <typename ValueT, typename PropertyT = inspect::StringProperty,
typename PropertyInnerT = std::string>
class Inspectable {
public:
// When the desired property type DOES NOT match ValueT, a conversion function |convert| is
// necessary. This is often the case when using optionals or when converting a number/enum into a
// string is desired. Example:
// auto convert = [](std::optional<hci::HCIVersion> version) -> std::string {
// return version ? HCIVersionToString(*version) : "null";
// };
// Inspectable<std::optional<HCIVersion>> hci_version(
// HCIVersion::k5_0,
// inspect_node.CreateString("hci", ""),
// convert);
using ConvertFunction = fit::function<PropertyInnerT(const ValueT&)>;
Inspectable(ValueT initial_value, PropertyT property,
ConvertFunction convert = &Inspectable::DefaultConvert)
: value_(std::move(initial_value)),
property_(std::move(property)),
convert_(std::move(convert)) {
static_assert(!std::is_pointer_v<ValueT>, "Pointer passed to Inspectable");
// Update property immediately to ensure consistency between property and initial value.
UpdateProperty();
}
Inspectable(Inspectable&&) = default;
Inspectable& operator=(Inspectable&&) = default;
virtual ~Inspectable() = default;
const ValueT& value() const { return value_; }
const ValueT& operator*() const { return value_; }
const ValueT* operator->() const { return &value_; }
// Update value and property. This is the ONLY place the value should be updated directly.
const ValueT& Set(const ValueT& value) {
value_ = value;
UpdateProperty();
return value_;
}
// Returns a InspectableGuard wrapper around the contained value that allows for non-const methods
// to be called. The returned value should only be used as a temporary.
InspectableGuard<ValueT> Mutable() {
return InspectableGuard(value_, fit::bind_member(this, &Inspectable::UpdateProperty));
}
static PropertyInnerT DefaultConvert(const ValueT& value) { return value; }
private:
void UpdateProperty() { property_.Set(convert_(value_)); }
ValueT value_;
PropertyT property_;
ConvertFunction convert_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Inspectable);
};
// Convenience Inspectable types:
template <typename ValueT>
using IntInspectable = Inspectable<ValueT, inspect::IntProperty, uint64_t>;
template <typename ValueT>
using UintInspectable = Inspectable<ValueT, inspect::UintProperty, uint64_t>;
template <typename ValueT>
using BoolInspectable = Inspectable<ValueT, inspect::BoolProperty, bool>;
template <typename ValueT>
using StringInspectable = Inspectable<ValueT, inspect::StringProperty, std::string>;
// A common practice in the Bluetooth stack is to define ToString() for classes.
// ToStringInspectable is for use with such a class |ValueT| where |ValueT::ToString()| is defined.
// |PropertyT::Set(std::string)| must accept a string.
// Example:
// class Foo {
// public:
// std::string ToString() { ... }
// };
//
// ToStringInspectable<Foo> foo(Foo(), inspect_node.CreateString("foo", ""));
template <class ValueT, class PropertyT = inspect::StringProperty>
class ToStringInspectable final : public Inspectable<ValueT, PropertyT, std::string> {
public:
ToStringInspectable(ValueT value, PropertyT property)
: Inspectable<ValueT, PropertyT, std::string>(std::move(value), std::move(property),
&ToStringInspectable::Convert) {}
ToStringInspectable(ToStringInspectable&&) = default;
ToStringInspectable& operator=(ToStringInspectable&&) = default;
static std::string Convert(const ValueT& value) { return value.ToString(); }
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ToStringInspectable);
};
} // namespace bt
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_INSPECTABLE_H_