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/lg2.hpp>
23
24 namespace phosphor::fan::control::json
25 {
26
27 /**
28 * @brief Variant visitor to return a value of the template type specified.
29 */
30 template <typename T>
31 struct ToTypeVisitor
32 {
33 template <typename U>
operator ()phosphor::fan::control::json::ToTypeVisitor34 T operator()(const U& t) const
35 {
36 if constexpr (std::is_arithmetic_v<U> && std::is_arithmetic_v<T>)
37 {
38 return static_cast<T>(t);
39 }
40 throw std::invalid_argument(
41 "Non arithmetic type used in ToTypeVisitor");
42 }
43 };
44
45 /**
46 * @brief Return a default value to use when the argument passed
47 * to LessThanOperator is out of range.
48 */
getDefaultValue(const PropertyVariantType & val,const std::optional<PropertyVariantType> & defaultValue)49 PropertyVariantType getDefaultValue(
50 const PropertyVariantType& val,
51 const std::optional<PropertyVariantType>& defaultValue)
52 {
53 // When a default value is given, return that
54 if (defaultValue)
55 {
56 return *defaultValue;
57 }
58
59 if (std::holds_alternative<bool>(val))
60 {
61 return false;
62 }
63 else if (std::holds_alternative<std::string>(val))
64 {
65 return std::string{};
66 }
67 else if (std::holds_alternative<double>(val))
68 {
69 return std::numeric_limits<double>::quiet_NaN();
70 }
71 else if (std::holds_alternative<int32_t>(val))
72 {
73 return std::numeric_limits<int32_t>::quiet_NaN();
74 }
75 else if (std::holds_alternative<int64_t>(val))
76 {
77 return std::numeric_limits<int64_t>::quiet_NaN();
78 }
79
80 throw std::runtime_error(
81 "Invalid variant type when determining default value");
82 }
83
84 /**
85 * @brief Implements the minus operator to subtract two values.
86 *
87 * With strings values, A - B removes all occurrences of B in A.
88 * Throws if the type is a bool.
89 */
90 struct MinusOperator : public Modifier::BaseOperator
91 {
MinusOperatorphosphor::fan::control::json::MinusOperator92 explicit MinusOperator(const json& jsonObj) :
93 arg(ConfigBase::getJsonValue(jsonObj["value"]))
94 {}
95
operator ()phosphor::fan::control::json::MinusOperator96 PropertyVariantType operator()(double val) override
97 {
98 return val - std::visit(ToTypeVisitor<double>(), arg);
99 }
100
operator ()phosphor::fan::control::json::MinusOperator101 PropertyVariantType operator()(int32_t val) override
102 {
103 return val - std::visit(ToTypeVisitor<int32_t>(), arg);
104 }
105
operator ()phosphor::fan::control::json::MinusOperator106 PropertyVariantType operator()(int64_t val) override
107 {
108 return val - std::visit(ToTypeVisitor<int64_t>(), arg);
109 }
110
operator ()phosphor::fan::control::json::MinusOperator111 PropertyVariantType operator()(const std::string& val) override
112 {
113 // Remove all occurrences of arg from val.
114 auto value = val;
115 auto toRemove = std::get<std::string>(arg);
116 size_t pos;
117 while ((pos = value.find(toRemove)) != std::string::npos)
118 {
119 value.erase(pos, toRemove.size());
120 }
121
122 return value;
123 }
124
operator ()phosphor::fan::control::json::MinusOperator125 PropertyVariantType operator()(bool) override
126 {
127 throw std::runtime_error{
128 "Bool not allowed as a 'minus' modifier value"};
129 }
130
131 PropertyVariantType arg;
132 };
133
134 /**
135 * @brief Implements an operator to return a value specified in the JSON that is
136 * chosen based on if the value passed into the operator is less than the lowest
137 * arg_value it is true for or the given `default_value` if not found to be less
138 * than any entries.
139 *
140 * "modifier": {
141 * "operator": "less_than",
142 * "default_value": 1000, // OPTIONAL
143 * "value": [
144 * {
145 * "arg_value": 30, // if value is less than 30
146 * "parameter_value": 300 // then return 300
147 * },
148 * {
149 * "arg_value": 40, // else if value is less than 40
150 * "parameter_value": 400 // then return 400
151 * },
152 * ]
153 * }
154 *
155 * If the value passed in is higher than the highest arg_value, it returns a
156 * default value this is the `default_value` given or based on the data type of
157 * parameter_value.
158 */
159 struct LessThanOperator : public Modifier::BaseOperator
160 {
LessThanOperatorphosphor::fan::control::json::LessThanOperator161 explicit LessThanOperator(const json& jsonObj)
162 {
163 const auto& valueArray = jsonObj["value"];
164 if (!valueArray.is_array())
165 {
166 lg2::error("Invalid JSON data for less_than config: {VALUE_ARRAY}",
167 "VALUE_ARRAY", valueArray.dump());
168 throw std::invalid_argument("Invalid modifier JSON");
169 }
170
171 for (const auto& valueEntry : valueArray)
172 {
173 if (!valueEntry.contains("arg_value") ||
174 !valueEntry.contains("parameter_value"))
175 {
176 lg2::error("Missing arg_value or parameter_value keys "
177 "in less_than config: {VALUE_ARRAY}",
178 "VALUE_ARRAY", valueArray.dump());
179 throw std::invalid_argument("Invalid modifier JSON");
180 }
181
182 auto argVal = ConfigBase::getJsonValue(valueEntry.at("arg_value"));
183
184 if (std::holds_alternative<bool>(argVal))
185 {
186 lg2::error(
187 "Invalid data type in arg_value key in modifier JSON "
188 "config: {VALUE_ARRAY}",
189 "VALUE_ARRAY", valueArray.dump());
190 throw std::invalid_argument("Invalid modifier JSON");
191 }
192
193 auto paramVal =
194 ConfigBase::getJsonValue(valueEntry.at("parameter_value"));
195
196 rangeValues.emplace_back(argVal, paramVal);
197 }
198
199 if (rangeValues.empty())
200 {
201 lg2::error("No valid range values found in "
202 "modifier json: {VALUE_ARRAY}",
203 "VALUE_ARRAY", valueArray.dump());
204 throw std::invalid_argument("Invalid modifier JSON");
205 }
206
207 if (jsonObj.contains("default_value"))
208 {
209 defaultValue = ConfigBase::getJsonValue(jsonObj["default_value"]);
210 }
211 }
212
operator ()phosphor::fan::control::json::LessThanOperator213 PropertyVariantType operator()(double val) override
214 {
215 for (const auto& rangeValue : rangeValues)
216 {
217 if (val < std::visit(ToTypeVisitor<double>(), rangeValue.first))
218 {
219 return rangeValue.second;
220 }
221 }
222 // Return a default value based on last entry type
223 return getDefaultValue(rangeValues.back().second, defaultValue);
224 }
225
operator ()phosphor::fan::control::json::LessThanOperator226 PropertyVariantType operator()(int32_t val) override
227 {
228 for (const auto& rangeValue : rangeValues)
229 {
230 if (val < std::visit(ToTypeVisitor<int32_t>(), rangeValue.first))
231 {
232 return rangeValue.second;
233 }
234 }
235 return getDefaultValue(rangeValues.back().second, defaultValue);
236 }
237
operator ()phosphor::fan::control::json::LessThanOperator238 PropertyVariantType operator()(int64_t val) override
239 {
240 for (const auto& rangeValue : rangeValues)
241 {
242 if (val < std::visit(ToTypeVisitor<int64_t>(), rangeValue.first))
243 {
244 return rangeValue.second;
245 }
246 }
247 return getDefaultValue(rangeValues.back().second, defaultValue);
248 }
249
operator ()phosphor::fan::control::json::LessThanOperator250 PropertyVariantType operator()(const std::string& val) override
251 {
252 for (const auto& rangeValue : rangeValues)
253 {
254 if (val <
255 std::visit(ToTypeVisitor<std::string>(), rangeValue.first))
256 {
257 return rangeValue.second;
258 }
259 }
260 return getDefaultValue(rangeValues.back().second, defaultValue);
261 }
262
operator ()phosphor::fan::control::json::LessThanOperator263 PropertyVariantType operator()(bool) override
264 {
265 throw std::runtime_error{
266 "Bool not allowed as a 'less_than' modifier value"};
267 }
268
269 std::vector<std::pair<PropertyVariantType, PropertyVariantType>>
270 rangeValues;
271 std::optional<PropertyVariantType> defaultValue;
272 };
273
Modifier(const json & jsonObj)274 Modifier::Modifier(const json& jsonObj)
275 {
276 setOperator(jsonObj);
277 }
278
setOperator(const json & jsonObj)279 void Modifier::setOperator(const json& jsonObj)
280 {
281 if (!jsonObj.contains("operator") || !jsonObj.contains("value"))
282 {
283 lg2::error(
284 "Modifier entry in JSON missing 'operator' or 'value': {JSON_OBJECT}",
285 "JSON_OBJECT", jsonObj.dump());
286 throw std::invalid_argument("Invalid modifier JSON");
287 }
288
289 auto op = jsonObj["operator"].get<std::string>();
290
291 if (op == "minus")
292 {
293 _operator = std::make_unique<MinusOperator>(jsonObj);
294 }
295 else if (op == "less_than")
296 {
297 _operator = std::make_unique<LessThanOperator>(jsonObj);
298 }
299 else
300 {
301 lg2::error("Invalid operator in the modifier JSON: {JSON_OBJECT}",
302 "JSON_OBJECT", jsonObj.dump());
303 throw std::invalid_argument("Invalid operator in the modifier JSON");
304 }
305 }
306
doOp(const PropertyVariantType & val)307 PropertyVariantType Modifier::doOp(const PropertyVariantType& val)
308 {
309 return std::visit(*_operator, val);
310 }
311
312 } // namespace phosphor::fan::control::json
313