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