1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #pragma once
16 #include <lib/fit/function.h>
17 
18 #include <iterator>
19 #include <string>
20 #include <type_traits>
21 
22 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
23 #include "pw_bluetooth_sapphire/internal/host/common/inspect.h"
24 #include "pw_bluetooth_sapphire/internal/host/common/macros.h"
25 
26 namespace bt {
27 
28 // InspectableGuard is returned by |Inspectable::Mutable()|.
29 // It will update the corresponding Inspect property when it is destroyed.
30 // Therefore, the lifetime of InspectableGuard should usually be that of a
31 // temporary, or be scoped for a single update.
32 //
33 // InspectableGuard's primary use cases are calling non-const methods on objects
34 // and assigning member variable values in structs.
35 //
36 // Example:
37 //   StringInspectable<hci_spec::LMPFeatureSet> lmp_features;
38 //   lmp_features.Mutable()->SetPage(page, features);
39 template <typename ValueT>
40 class InspectableGuard {
41  public:
InspectableGuard(ValueT & value,fit::closure update_cb)42   InspectableGuard(ValueT& value, fit::closure update_cb)
43       : value_(value), update_cb_(std::move(update_cb)) {}
~InspectableGuard()44   ~InspectableGuard() { update_cb_(); }
45 
46   ValueT& operator*() { return value_; }
47 
48   ValueT* operator->() { return &value_; }
49 
50  private:
51   ValueT& value_;
52   fit::closure update_cb_;
53 
54   BT_DISALLOW_COPY_ASSIGN_AND_MOVE(InspectableGuard);
55 };
56 
57 // Inspectable is a utility class for keeping inspected values in sync with
58 // their corresponding Inspect property.
59 //
60 // PropertyT must be an Inspect property type such that
61 // PropertyT::Set(PropertyInnerT) is valid. PropertyInnerT corresponds to the
62 // type contained in PropertyT (e.g. string is contained by
63 // inspect::StringProperty).
64 //
65 // Example:
66 //   inspect::Inspector inspector;
67 //   auto& root = inspector.GetRoot();
68 //   Inspectable inspectable(std::string("A"),
69 //   root.CreateString("property_name", "foo"));
70 //
71 //   // Hierarchy: { root: property_name: "A" }
72 //
73 //   inspectable.Set("B");
74 //
75 //   // Hierarchy: { root: property_name: "B" }
76 template <typename ValueT, typename PropertyT, typename PropertyInnerT = ValueT>
77 class Inspectable {
78  public:
79   // When the desired property type DOES NOT match ValueT, a conversion function
80   // |convert| is necessary. This is often the case when using optionals or when
81   // converting a number/enum into a string is desired. Example:
82   //   auto convert = [](std::optional<hci_spec::HCIVersion> version) ->
83   //   std::string {
84   //     return version ? HCIVersionToString(*version) : "null";
85   //   };
86   //   Inspectable<std::optional<HCIVersion>> hci_version(
87   //     HCIVersion::k5_0,
88   //     inspect_node.CreateString("hci", ""),
89   //     convert);
90   using ConvertFunction = fit::function<PropertyInnerT(const ValueT&)>;
91   Inspectable(ValueT initial_value,
92               PropertyT property,
93               ConvertFunction convert = Inspectable::DefaultConvert)
value_(std::move (initial_value))94       : value_(std::move(initial_value)),
95         property_(std::move(property)),
96         convert_(std::move(convert)) {
97     // Update property immediately to ensure consistency between property and
98     // initial value.
99     UpdateProperty();
100   }
101 
102   // Construct with null property (updates will be no-ops).
103   explicit Inspectable(ValueT initial_value,
104                        ConvertFunction convert = &Inspectable::DefaultConvert)
value_(std::move (initial_value))105       : value_(std::move(initial_value)), convert_(std::move(convert)) {}
106 
107   // Construct with null property and with default value for ValueT.
108   explicit Inspectable(ConvertFunction convert = &Inspectable::DefaultConvert)
Inspectable(ValueT (),std::move (convert))109       : Inspectable(ValueT(), std::move(convert)) {
110     static_assert(std::is_default_constructible<ValueT>(),
111                   "ValueT is not default constructable");
112   }
113 
114   Inspectable(Inspectable&&) = default;
115   Inspectable& operator=(Inspectable&&) = default;
116   virtual ~Inspectable() = default;
117 
value()118   const ValueT& value() const { return value_; }
119 
120   const ValueT& operator*() const { return value_; }
121 
122   const ValueT* operator->() const { return &value_; }
123 
124   // Update value and property. This is the ONLY place the value should be
125   // updated directly.
Set(const ValueT & value)126   const ValueT& Set(const ValueT& value) {
127     value_ = value;
128     UpdateProperty();
129     return value_;
130   }
131 
SetProperty(PropertyT property)132   void SetProperty(PropertyT property) {
133     property_ = std::move(property);
134     UpdateProperty();
135   }
136 
AttachInspect(inspect::Node &,std::string)137   virtual void AttachInspect(inspect::Node& /*node*/, std::string /*name*/) {
138     PW_CHECK(false, "AttachInspect not implemented for PropertyT");
139   }
140 
141   // Returns a InspectableGuard wrapper around the contained value that allows
142   // for non-const methods to be called. The returned value should only be used
143   // as a temporary.
Mutable()144   InspectableGuard<ValueT> Mutable() {
145     return InspectableGuard(
146         value_, fit::bind_member<&Inspectable::UpdateProperty>(this));
147   }
148 
DefaultConvert(const ValueT & value)149   static PropertyInnerT DefaultConvert(const ValueT& value) { return value; }
150 
151  private:
UpdateProperty()152   void UpdateProperty() { property_.Set(convert_(value_)); }
153 
154   ValueT value_;
155   PropertyT property_;
156   ConvertFunction convert_;
157 
158   static_assert(!std::is_pointer_v<ValueT>, "Pointer passed to Inspectable");
159   static_assert(!std::is_reference_v<ValueT>,
160                 "Reference passed to Inspectable");
161 
162   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Inspectable);
163 };
164 
165 // Convenience Inspectable types:
166 
167 #define CREATE_INSPECTABLE_TYPE(property_t, inner_t)                         \
168   template <typename ValueT>                                                 \
169   class property_t##Inspectable                                              \
170       : public Inspectable<ValueT, inspect::property_t##Property, inner_t> { \
171    public:                                                                   \
172     using Inspectable<ValueT, inspect::property_t##Property, inner_t>::      \
173         Inspectable;                                                         \
174     void AttachInspect(inspect::Node& node, std::string name) override {     \
175       this->SetProperty(node.Create##property_t(name, inner_t()));           \
176     }                                                                        \
177   };                                                                         \
178   template <typename ValueT>                                                 \
179   property_t##Inspectable(ValueT, ...)->property_t##Inspectable<ValueT>
180 
181 CREATE_INSPECTABLE_TYPE(Int, int64_t);
182 CREATE_INSPECTABLE_TYPE(Uint, uint64_t);
183 CREATE_INSPECTABLE_TYPE(Bool, bool);
184 CREATE_INSPECTABLE_TYPE(String, std::string);
185 
186 // A common practice in the Bluetooth stack is to define ToString() for classes.
187 // MakeToStringInspectConvertFunction allows these classes to be used with
188 // StringInspectable. Example: class Foo {
189 //   public:
190 //     std::string ToString() { ... }
191 // };
192 //
193 // StringInspectable foo(Foo(), inspect_node.CreateString("foo", ""),
194 //                       MakeToStringInspectConvertFunction());
MakeToStringInspectConvertFunction()195 inline auto MakeToStringInspectConvertFunction() {
196   return [](auto value) { return value.ToString(); };
197 }
198 
199 // Similar to ToStringInspectable, but for containers of types that implement
200 // ToString(). The resulting string property will be formatted using the
201 // ContainerOfToStringOptions provided. Example: std::vector<Foo> values(2);
202 // StringInspectable foo(std::move(values), inspect_node.CreateString("foo",
203 // ""),
204 //                       MakeContainerOfToStringInspectConvertFunction());
205 //
206 // This does not generate an node hierarchy based on the container contents and
207 // is more appropriate for sequential containers at leaves of the inspect tree.
208 // More complex data structures, especially associative ones, should export full
209 // inspect trees.
210 struct ContainerOfToStringOptions {
211   const char* prologue = "{ ";
212   const char* delimiter = ", ";
213   const char* epilogue = " }";
214 };
215 inline auto MakeContainerOfToStringConvertFunction(
216     ContainerOfToStringOptions options = {}) {
217   return [options](auto value) {
218     std::string out(options.prologue);
219     for (auto iter = std::begin(value); iter != std::end(value);
220          std::advance(iter, 1)) {
221       out += iter->ToString();
222       if (std::next(iter) == std::end(value)) {
223         continue;
224       }
225       out += options.delimiter;
226     }
227     out += options.epilogue;
228     return out;
229   };
230 }
231 
232 }  // namespace bt
233