xref: /openbmc/bmcweb/redfish-core/src/filter_expr_executor.cpp (revision 3c9e6b1cc6d747299dd0e4fc2ab32a35ec269349)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include "filter_expr_executor.hpp"
4 
5 #include "filter_expr_parser_ast.hpp"
6 #include "human_sort.hpp"
7 #include "logging.hpp"
8 #include "utils/json_utils.hpp"
9 #include "utils/time_utils.hpp"
10 
11 #include <nlohmann/json.hpp>
12 
13 #include <algorithm>
14 #include <array>
15 #include <cmath>
16 #include <cstddef>
17 #include <cstdint>
18 #include <limits>
19 #include <optional>
20 #include <string>
21 #include <string_view>
22 #include <variant>
23 
24 namespace redfish
25 {
26 
27 namespace
28 {
29 
30 // A value that has been parsed as a time string per Edm.DateTimeOffset
31 struct DateTimeString
32 {
33     time_utils::usSinceEpoch value = time_utils::usSinceEpoch::zero();
34 
35     // The following is created by dumping all key names of type
36     // Edm.DateTimeOffset.  While imperfect that it's a hardcoded list, these
37     // keys don't change that often
38     static constexpr auto timeKeys = std::to_array<std::string_view>(
39         {"AccountExpiration",
40          "CalibrationTime",
41          "CoefficientUpdateTime",
42          "Created",
43          "CreatedDate",
44          "CreatedTime",
45          "CreateTime",
46          "DateTime",
47          "EndDateTime",
48          "EndTime",
49          "EventTimestamp",
50          "ExpirationDate",
51          "FirstOverflowTimestamp",
52          "InitialStartTime",
53          "InstallDate",
54          "LastOverflowTimestamp",
55          "LastResetTime",
56          "LastStateTime",
57          "LastUpdated",
58          "LifetimeStartDateTime",
59          "LowestReadingTime",
60          "MaintenanceWindowStartTime",
61          "Modified",
62          "PasswordExpiration",
63          "PeakReadingTime",
64          "PresentedPublicHostKeyTimestamp",
65          "ProductionDate",
66          "ReadingTime",
67          "ReleaseDate",
68          "ReservationTime",
69          "SensorResetTime",
70          "ServicedDate",
71          "SetPointUpdateTime",
72          "StartDateTime",
73          "StartTime",
74          "Time",
75          "Timestamp",
76          "ValidNotAfter",
77          "ValidNotBefore"});
78 
DateTimeStringredfish::__anonc6eec6550111::DateTimeString79     explicit DateTimeString(std::string_view strvalue)
80     {
81         std::optional<time_utils::usSinceEpoch> out =
82             time_utils::dateStringToEpoch(strvalue);
83         if (!out)
84         {
85             BMCWEB_LOG_ERROR(
86                 "Internal datetime value didn't parse as datetime?");
87         }
88         else
89         {
90             value = *out;
91         }
92     }
93 
isDateTimeKeyredfish::__anonc6eec6550111::DateTimeString94     static bool isDateTimeKey(std::string_view key)
95     {
96         auto out = std::equal_range(timeKeys.begin(), timeKeys.end(), key);
97         return out.first != out.second;
98     }
99 };
100 
101 // Class that can convert an arbitrary AST type into a structured value
102 // Pulling from the json pointer when required
103 struct ValueVisitor
104 {
105     using result_type = std::variant<std::monostate, double, int64_t,
106                                      std::string, DateTimeString>;
107     const nlohmann::json& body;
108     result_type operator()(double n);
109     result_type operator()(int64_t x);
110     result_type operator()(const filter_ast::UnquotedString& x);
111     result_type operator()(const filter_ast::QuotedString& x);
112 };
113 
operator ()(double n)114 ValueVisitor::result_type ValueVisitor::operator()(double n)
115 {
116     return {n};
117 }
118 
operator ()(int64_t x)119 ValueVisitor::result_type ValueVisitor::operator()(int64_t x)
120 {
121     return {x};
122 }
123 
124 ValueVisitor::result_type
operator ()(const filter_ast::QuotedString & x)125     ValueVisitor::operator()(const filter_ast::QuotedString& x)
126 {
127     return {x};
128 }
129 
130 ValueVisitor::result_type
operator ()(const filter_ast::UnquotedString & x)131     ValueVisitor::operator()(const filter_ast::UnquotedString& x)
132 {
133     // find key including paths with / in them
134     const nlohmann::json* it =
135         json_util::findNestedKey(static_cast<std::string_view>(x), body);
136     if (it == nullptr)
137     {
138         BMCWEB_LOG_ERROR("Key {} doesn't exist in output, cannot filter",
139                          static_cast<std::string>(x));
140         BMCWEB_LOG_DEBUG("Output {}", body.dump());
141         return {};
142     }
143 
144     const nlohmann::json& entry = *it;
145     const double* dValue = entry.get_ptr<const double*>();
146     if (dValue != nullptr)
147     {
148         return {*dValue};
149     }
150     const int64_t* iValue = entry.get_ptr<const int64_t*>();
151     if (iValue != nullptr)
152     {
153         return {*iValue};
154     }
155     const std::string* strValue = entry.get_ptr<const std::string*>();
156     if (strValue != nullptr)
157     {
158         if (DateTimeString::isDateTimeKey(x))
159         {
160             return DateTimeString(*strValue);
161         }
162         return {*strValue};
163     }
164 
165     BMCWEB_LOG_ERROR(
166         "Type for key {} was {} which does not have a comparison operator",
167         static_cast<std::string>(x), static_cast<int>(entry.type()));
168     return {};
169 }
170 
171 struct ApplyFilter
172 {
173     const nlohmann::json& body;
174     const filter_ast::LogicalAnd& filter;
175     using result_type = bool;
176     bool operator()(const filter_ast::LogicalNot& x);
177     bool operator()(const filter_ast::LogicalOr& x);
178     bool operator()(const filter_ast::LogicalAnd& x);
179     bool operator()(const filter_ast::Comparison& x);
180     bool operator()(const filter_ast::BooleanOp& x);
181 
182   public:
183     bool matches();
184 };
185 
operator ()(const filter_ast::LogicalNot & x)186 bool ApplyFilter::operator()(const filter_ast::LogicalNot& x)
187 {
188     bool subValue = (*this)(x.operand);
189     if (x.isLogicalNot)
190     {
191         return !subValue;
192     }
193     return subValue;
194 }
195 
196 // Helper function to reduce the number of permutations of a single comparison
197 // For all possible types.
doDoubleComparison(double left,filter_ast::ComparisonOpEnum comparator,double right)198 bool doDoubleComparison(double left, filter_ast::ComparisonOpEnum comparator,
199                         double right)
200 {
201     if (!std::isfinite(left) || !std::isfinite(right))
202     {
203         BMCWEB_LOG_ERROR("Refusing to do comparision of non finite numbers");
204         return false;
205     }
206     switch (comparator)
207     {
208         case filter_ast::ComparisonOpEnum::Equals:
209             // Note, floating point comparisons are hard.  Compare equality
210             // based on epsilon
211             return std::fabs(left - right) <=
212                    std::numeric_limits<double>::epsilon();
213         case filter_ast::ComparisonOpEnum::NotEquals:
214             return std::fabs(left - right) >
215                    std::numeric_limits<double>::epsilon();
216         case filter_ast::ComparisonOpEnum::GreaterThan:
217             return left > right;
218         case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
219             return left >= right;
220         case filter_ast::ComparisonOpEnum::LessThan:
221             return left < right;
222         case filter_ast::ComparisonOpEnum::LessThanOrEqual:
223             return left <= right;
224         default:
225             BMCWEB_LOG_ERROR("Got x.token that should never happen {}",
226                              static_cast<int>(comparator));
227             return true;
228     }
229 }
230 
doIntComparison(int64_t left,filter_ast::ComparisonOpEnum comparator,int64_t right)231 bool doIntComparison(int64_t left, filter_ast::ComparisonOpEnum comparator,
232                      int64_t right)
233 {
234     switch (comparator)
235     {
236         case filter_ast::ComparisonOpEnum::Equals:
237             return left == right;
238         case filter_ast::ComparisonOpEnum::NotEquals:
239             return left != right;
240         case filter_ast::ComparisonOpEnum::GreaterThan:
241             return left > right;
242         case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
243             return left >= right;
244         case filter_ast::ComparisonOpEnum::LessThan:
245             return left < right;
246         case filter_ast::ComparisonOpEnum::LessThanOrEqual:
247             return left <= right;
248         default:
249             BMCWEB_LOG_ERROR("Got comparator that should never happen {}",
250                              static_cast<int>(comparator));
251             return true;
252     }
253 }
254 
doStringComparison(std::string_view left,filter_ast::ComparisonOpEnum comparator,std::string_view right)255 bool doStringComparison(std::string_view left,
256                         filter_ast::ComparisonOpEnum comparator,
257                         std::string_view right)
258 {
259     switch (comparator)
260     {
261         case filter_ast::ComparisonOpEnum::Equals:
262             return left == right;
263         case filter_ast::ComparisonOpEnum::NotEquals:
264             return left != right;
265         case filter_ast::ComparisonOpEnum::GreaterThan:
266             return alphanumComp(left, right) > 0;
267         case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
268             return alphanumComp(left, right) >= 0;
269         case filter_ast::ComparisonOpEnum::LessThan:
270             return alphanumComp(left, right) < 0;
271         case filter_ast::ComparisonOpEnum::LessThanOrEqual:
272             return alphanumComp(left, right) <= 0;
273         default:
274             BMCWEB_LOG_ERROR(
275                 "Got comparator that should never happen.  Attempt to do numeric comparison on string {}",
276                 static_cast<int>(comparator));
277             return true;
278     }
279 }
280 
operator ()(const filter_ast::Comparison & x)281 bool ApplyFilter::operator()(const filter_ast::Comparison& x)
282 {
283     ValueVisitor numeric(body);
284     std::variant<std::monostate, double, int64_t, std::string, DateTimeString>
285         left = boost::apply_visitor(numeric, x.left);
286     std::variant<std::monostate, double, int64_t, std::string, DateTimeString>
287         right = boost::apply_visitor(numeric, x.right);
288 
289     // Numeric comparisons
290     const double* lDoubleValue = std::get_if<double>(&left);
291     const double* rDoubleValue = std::get_if<double>(&right);
292     const int64_t* lIntValue = std::get_if<int64_t>(&left);
293     const int64_t* rIntValue = std::get_if<int64_t>(&right);
294 
295     if (lDoubleValue != nullptr)
296     {
297         if (rDoubleValue != nullptr)
298         {
299             // Both sides are doubles, do the comparison as doubles
300             return doDoubleComparison(*lDoubleValue, x.token, *rDoubleValue);
301         }
302         if (rIntValue != nullptr)
303         {
304             // If right arg is int, promote right arg to double
305             return doDoubleComparison(*lDoubleValue, x.token,
306                                       static_cast<double>(*rIntValue));
307         }
308     }
309     if (lIntValue != nullptr)
310     {
311         if (rIntValue != nullptr)
312         {
313             // Both sides are ints, do the comparison as ints
314             return doIntComparison(*lIntValue, x.token, *rIntValue);
315         }
316 
317         if (rDoubleValue != nullptr)
318         {
319             // Left arg is int, promote left arg to double
320             return doDoubleComparison(static_cast<double>(*lIntValue), x.token,
321                                       *rDoubleValue);
322         }
323     }
324 
325     // String comparisons
326     const std::string* lStrValue = std::get_if<std::string>(&left);
327     const std::string* rStrValue = std::get_if<std::string>(&right);
328 
329     const DateTimeString* lDateValue = std::get_if<DateTimeString>(&left);
330     const DateTimeString* rDateValue = std::get_if<DateTimeString>(&right);
331 
332     // If we're trying to compare a date string to a string, construct a
333     // datestring from the string
334     if (lDateValue != nullptr && rStrValue != nullptr)
335     {
336         rDateValue = &right.emplace<DateTimeString>(std::string(*rStrValue));
337     }
338     if (lStrValue != nullptr && rDateValue != nullptr)
339     {
340         lDateValue = &left.emplace<DateTimeString>(std::string(*lStrValue));
341     }
342 
343     if (lDateValue != nullptr && rDateValue != nullptr)
344     {
345         return doIntComparison(lDateValue->value.count(), x.token,
346                                rDateValue->value.count());
347     }
348 
349     if (lStrValue != nullptr && rStrValue != nullptr)
350     {
351         return doStringComparison(*lStrValue, x.token, *rStrValue);
352     }
353 
354     BMCWEB_LOG_ERROR(
355         "Fell through.  Should never happen.  Attempt to compare type {} to type {}",
356         static_cast<int>(left.index()), static_cast<int>(right.index()));
357     return true;
358 }
359 
operator ()(const filter_ast::BooleanOp & x)360 bool ApplyFilter::operator()(const filter_ast::BooleanOp& x)
361 {
362     return boost::apply_visitor(*this, x);
363 }
364 
operator ()(const filter_ast::LogicalOr & x)365 bool ApplyFilter::operator()(const filter_ast::LogicalOr& x)
366 {
367     bool value = (*this)(x.first);
368     for (const filter_ast::LogicalNot& bOp : x.rest)
369     {
370         value = value || (*this)(bOp);
371     }
372     return value;
373 }
374 
operator ()(const filter_ast::LogicalAnd & x)375 bool ApplyFilter::operator()(const filter_ast::LogicalAnd& x)
376 {
377     bool value = (*this)(x.first);
378     for (const filter_ast::LogicalOr& bOp : x.rest)
379     {
380         value = value && (*this)(bOp);
381     }
382     return value;
383 }
384 
matches()385 bool ApplyFilter::matches()
386 {
387     return (*this)(filter);
388 }
389 
390 } // namespace
391 
memberMatches(const nlohmann::json & member,const filter_ast::LogicalAnd & filterParam)392 bool memberMatches(const nlohmann::json& member,
393                    const filter_ast::LogicalAnd& filterParam)
394 {
395     ApplyFilter filterApplier(member, filterParam);
396     return filterApplier.matches();
397 }
398 
399 // Applies a filter expression to a member array
applyFilterToCollection(nlohmann::json & body,const filter_ast::LogicalAnd & filterParam)400 bool applyFilterToCollection(nlohmann::json& body,
401                              const filter_ast::LogicalAnd& filterParam)
402 {
403     using nlohmann::json;
404 
405     json::object_t* obj = body.get_ptr<json::object_t*>();
406     if (obj == nullptr)
407     {
408         BMCWEB_LOG_ERROR("Requested filter wasn't an object????");
409         return false;
410     }
411     json::object_t::iterator members = obj->find("Members");
412     if (members == obj->end())
413     {
414         BMCWEB_LOG_ERROR("Collection didn't have members?");
415         return false;
416     }
417     json::array_t* memberArr = members->second.get_ptr<json::array_t*>();
418     if (memberArr == nullptr)
419     {
420         BMCWEB_LOG_ERROR("Members wasn't an object????");
421         return false;
422     }
423 
424     json::array_t::iterator it = memberArr->begin();
425     size_t index = 0;
426     while (it != memberArr->end())
427     {
428         if (!memberMatches(*it, filterParam))
429         {
430             BMCWEB_LOG_DEBUG("Removing item at index {}", index);
431             it = memberArr->erase(it);
432             index++;
433             continue;
434         }
435         it++;
436         index++;
437     }
438 
439     return true;
440 }
441 } // namespace redfish
442