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