1 /**
2 * Copyright © 2021 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "modifier.hpp"
18
19 #include "json/config_base.hpp"
20 #include "json/manager.hpp"
21
22 #include <phosphor-logging/log.hpp>
23
24 #include <format>
25
26 using namespace phosphor::logging;
27
28 namespace phosphor::fan::control::json
29 {
30
31 /**
32 * @brief Variant visitor to return a value of the template type specified.
33 */
34 template <typename T>
35 struct ToTypeVisitor
36 {
37 template <typename U>
operator ()phosphor::fan::control::json::ToTypeVisitor38 T operator()(const U& t) const
39 {
40 if constexpr (std::is_arithmetic_v<U> && std::is_arithmetic_v<T>)
41 {
42 return static_cast<T>(t);
43 }
44 throw std::invalid_argument(
45 "Non arithmetic type used in ToTypeVisitor");
46 }
47 };
48
49 /**
50 * @brief Return a default value to use when the argument passed
51 * to LessThanOperator is out of range.
52 */
53 PropertyVariantType
getDefaultValue(const PropertyVariantType & val,const std::optional<PropertyVariantType> & defaultValue)54 getDefaultValue(const PropertyVariantType& val,
55 const std::optional<PropertyVariantType>& defaultValue)
56 {
57 // When a default value is given, return that
58 if (defaultValue)
59 {
60 return *defaultValue;
61 }
62
63 if (std::holds_alternative<bool>(val))
64 {
65 return false;
66 }
67 else if (std::holds_alternative<std::string>(val))
68 {
69 return std::string{};
70 }
71 else if (std::holds_alternative<double>(val))
72 {
73 return std::numeric_limits<double>::quiet_NaN();
74 }
75 else if (std::holds_alternative<int32_t>(val))
76 {
77 return std::numeric_limits<int32_t>::quiet_NaN();
78 }
79 else if (std::holds_alternative<int64_t>(val))
80 {
81 return std::numeric_limits<int64_t>::quiet_NaN();
82 }
83
84 throw std::runtime_error(
85 "Invalid variant type when determining default value");
86 }
87
88 /**
89 * @brief Implements the minus operator to subtract two values.
90 *
91 * With strings values, A - B removes all occurrences of B in A.
92 * Throws if the type is a bool.
93 */
94 struct MinusOperator : public Modifier::BaseOperator
95 {
MinusOperatorphosphor::fan::control::json::MinusOperator96 explicit MinusOperator(const json& jsonObj) :
97 arg(ConfigBase::getJsonValue(jsonObj["value"]))
98 {}
99
operator ()phosphor::fan::control::json::MinusOperator100 PropertyVariantType operator()(double val) override
101 {
102 return val - std::visit(ToTypeVisitor<double>(), arg);
103 }
104
operator ()phosphor::fan::control::json::MinusOperator105 PropertyVariantType operator()(int32_t val) override
106 {
107 return val - std::visit(ToTypeVisitor<int32_t>(), arg);
108 }
109
operator ()phosphor::fan::control::json::MinusOperator110 PropertyVariantType operator()(int64_t val) override
111 {
112 return val - std::visit(ToTypeVisitor<int64_t>(), arg);
113 }
114
operator ()phosphor::fan::control::json::MinusOperator115 PropertyVariantType operator()(const std::string& val) override
116 {
117 // Remove all occurrences of arg from val.
118 auto value = val;
119 auto toRemove = std::get<std::string>(arg);
120 size_t pos;
121 while ((pos = value.find(toRemove)) != std::string::npos)
122 {
123 value.erase(pos, toRemove.size());
124 }
125
126 return value;
127 }
128
operator ()phosphor::fan::control::json::MinusOperator129 PropertyVariantType operator()(bool) override
130 {
131 throw std::runtime_error{
132 "Bool not allowed as a 'minus' modifier value"};
133 }
134
135 PropertyVariantType arg;
136 };
137
138 /**
139 * @brief Implements an operator to return a value specified in the JSON that is
140 * chosen based on if the value passed into the operator is less than the lowest
141 * arg_value it is true for or the given `default_value` if not found to be less
142 * than any entries.
143 *
144 * "modifier": {
145 * "operator": "less_than",
146 * "default_value": 1000, // OPTIONAL
147 * "value": [
148 * {
149 * "arg_value": 30, // if value is less than 30
150 * "parameter_value": 300 // then return 300
151 * },
152 * {
153 * "arg_value": 40, // else if value is less than 40
154 * "parameter_value": 400 // then return 400
155 * },
156 * ]
157 * }
158 *
159 * If the value passed in is higher than the highest arg_value, it returns a
160 * default value this is the `default_value` given or based on the data type of
161 * parameter_value.
162 */
163 struct LessThanOperator : public Modifier::BaseOperator
164 {
LessThanOperatorphosphor::fan::control::json::LessThanOperator165 explicit LessThanOperator(const json& jsonObj)
166 {
167 const auto& valueArray = jsonObj["value"];
168 if (!valueArray.is_array())
169 {
170 log<level::ERR>(
171 std::format("Invalid JSON data for less_than config: {}",
172 valueArray.dump())
173 .c_str());
174 throw std::invalid_argument("Invalid modifier JSON");
175 }
176
177 for (const auto& valueEntry : valueArray)
178 {
179 if (!valueEntry.contains("arg_value") ||
180 !valueEntry.contains("parameter_value"))
181 {
182 log<level::ERR>(
183 std::format("Missing arg_value or parameter_value keys "
184 "in less_than config: {}",
185 valueArray.dump())
186 .c_str());
187 throw std::invalid_argument("Invalid modifier JSON");
188 }
189
190 auto argVal = ConfigBase::getJsonValue(valueEntry.at("arg_value"));
191
192 if (std::holds_alternative<bool>(argVal))
193 {
194 log<level::ERR>(
195 std::format(
196 "Invalid data type in arg_value key in modifier JSON "
197 "config: {}",
198 valueArray.dump())
199 .c_str());
200 throw std::invalid_argument("Invalid modifier JSON");
201 }
202
203 auto paramVal =
204 ConfigBase::getJsonValue(valueEntry.at("parameter_value"));
205
206 rangeValues.emplace_back(argVal, paramVal);
207 }
208
209 if (rangeValues.empty())
210 {
211 log<level::ERR>(std::format("No valid range values found in "
212 "modifier json: {}",
213 valueArray.dump())
214 .c_str());
215 throw std::invalid_argument("Invalid modifier JSON");
216 }
217
218 if (jsonObj.contains("default_value"))
219 {
220 defaultValue = ConfigBase::getJsonValue(jsonObj["default_value"]);
221 }
222 }
223
operator ()phosphor::fan::control::json::LessThanOperator224 PropertyVariantType operator()(double val) override
225 {
226 for (const auto& rangeValue : rangeValues)
227 {
228 if (val < std::visit(ToTypeVisitor<double>(), rangeValue.first))
229 {
230 return rangeValue.second;
231 }
232 }
233 // Return a default value based on last entry type
234 return getDefaultValue(rangeValues.back().second, defaultValue);
235 }
236
operator ()phosphor::fan::control::json::LessThanOperator237 PropertyVariantType operator()(int32_t val) override
238 {
239 for (const auto& rangeValue : rangeValues)
240 {
241 if (val < std::visit(ToTypeVisitor<int32_t>(), rangeValue.first))
242 {
243 return rangeValue.second;
244 }
245 }
246 return getDefaultValue(rangeValues.back().second, defaultValue);
247 }
248
operator ()phosphor::fan::control::json::LessThanOperator249 PropertyVariantType operator()(int64_t val) override
250 {
251 for (const auto& rangeValue : rangeValues)
252 {
253 if (val < std::visit(ToTypeVisitor<int64_t>(), rangeValue.first))
254 {
255 return rangeValue.second;
256 }
257 }
258 return getDefaultValue(rangeValues.back().second, defaultValue);
259 }
260
operator ()phosphor::fan::control::json::LessThanOperator261 PropertyVariantType operator()(const std::string& val) override
262 {
263 for (const auto& rangeValue : rangeValues)
264 {
265 if (val <
266 std::visit(ToTypeVisitor<std::string>(), rangeValue.first))
267 {
268 return rangeValue.second;
269 }
270 }
271 return getDefaultValue(rangeValues.back().second, defaultValue);
272 }
273
operator ()phosphor::fan::control::json::LessThanOperator274 PropertyVariantType operator()(bool) override
275 {
276 throw std::runtime_error{
277 "Bool not allowed as a 'less_than' modifier value"};
278 }
279
280 std::vector<std::pair<PropertyVariantType, PropertyVariantType>>
281 rangeValues;
282 std::optional<PropertyVariantType> defaultValue;
283 };
284
Modifier(const json & jsonObj)285 Modifier::Modifier(const json& jsonObj)
286 {
287 setOperator(jsonObj);
288 }
289
setOperator(const json & jsonObj)290 void Modifier::setOperator(const json& jsonObj)
291 {
292 if (!jsonObj.contains("operator") || !jsonObj.contains("value"))
293 {
294 log<level::ERR>(
295 std::format(
296 "Modifier entry in JSON missing 'operator' or 'value': {}",
297 jsonObj.dump())
298 .c_str());
299 throw std::invalid_argument("Invalid modifier JSON");
300 }
301
302 auto op = jsonObj["operator"].get<std::string>();
303
304 if (op == "minus")
305 {
306 _operator = std::make_unique<MinusOperator>(jsonObj);
307 }
308 else if (op == "less_than")
309 {
310 _operator = std::make_unique<LessThanOperator>(jsonObj);
311 }
312 else
313 {
314 log<level::ERR>(std::format("Invalid operator in the modifier JSON: {}",
315 jsonObj.dump())
316 .c_str());
317 throw std::invalid_argument("Invalid operator in the modifier JSON");
318 }
319 }
320
doOp(const PropertyVariantType & val)321 PropertyVariantType Modifier::doOp(const PropertyVariantType& val)
322 {
323 return std::visit(*_operator, val);
324 }
325
326 } // namespace phosphor::fan::control::json
327