xref: /openbmc/bmcweb/redfish-core/src/filter_expr_executor.cpp (revision 8d9cf72d00ccbbdd070849601ce8f37bc01cb8c8)
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 
operator ()(const filter_ast::QuotedString & x)124 ValueVisitor::result_type ValueVisitor::operator()(
125     const filter_ast::QuotedString& x)
126 {
127     return {x};
128 }
129 
operator ()(const filter_ast::UnquotedString & x)130 ValueVisitor::result_type ValueVisitor::operator()(
131     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 uint64_t* uValue = entry.get_ptr<const uint64_t*>();
156     if (uValue != nullptr)
157     {
158         // For now all values are coerced to signed
159         if (*uValue > std::numeric_limits<int64_t>::max())
160         {
161             BMCWEB_LOG_WARNING("Parsed uint is outside limits");
162             return {};
163         }
164         return {static_cast<int64_t>(*uValue)};
165     }
166     const std::string* strValue = entry.get_ptr<const std::string*>();
167     if (strValue != nullptr)
168     {
169         if (DateTimeString::isDateTimeKey(x))
170         {
171             return DateTimeString(*strValue);
172         }
173         return {*strValue};
174     }
175 
176     BMCWEB_LOG_ERROR(
177         "Type for key {} was {} which does not have a comparison operator",
178         static_cast<std::string>(x), static_cast<int>(entry.type()));
179     return {};
180 }
181 
182 struct ApplyFilter
183 {
184     const nlohmann::json& body;
185     const filter_ast::LogicalAnd& filter;
186     using result_type = bool;
187     bool operator()(const filter_ast::LogicalNot& x);
188     bool operator()(const filter_ast::LogicalOr& x);
189     bool operator()(const filter_ast::LogicalAnd& x);
190     bool operator()(const filter_ast::Comparison& x);
191     bool operator()(const filter_ast::BooleanOp& x);
192 
193   public:
194     bool matches();
195 };
196 
operator ()(const filter_ast::LogicalNot & x)197 bool ApplyFilter::operator()(const filter_ast::LogicalNot& x)
198 {
199     bool subValue = (*this)(x.operand);
200     if (x.isLogicalNot)
201     {
202         return !subValue;
203     }
204     return subValue;
205 }
206 
207 // Helper function to reduce the number of permutations of a single comparison
208 // For all possible types.
doDoubleComparison(double left,filter_ast::ComparisonOpEnum comparator,double right)209 bool doDoubleComparison(double left, filter_ast::ComparisonOpEnum comparator,
210                         double right)
211 {
212     if (!std::isfinite(left) || !std::isfinite(right))
213     {
214         BMCWEB_LOG_ERROR("Refusing to do comparision of non finite numbers");
215         return false;
216     }
217     switch (comparator)
218     {
219         case filter_ast::ComparisonOpEnum::Equals:
220             // Note, floating point comparisons are hard.  Compare equality
221             // based on epsilon
222             return std::fabs(left - right) <=
223                    std::numeric_limits<double>::epsilon();
224         case filter_ast::ComparisonOpEnum::NotEquals:
225             return std::fabs(left - right) >
226                    std::numeric_limits<double>::epsilon();
227         case filter_ast::ComparisonOpEnum::GreaterThan:
228             return left > right;
229         case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
230             return left >= right;
231         case filter_ast::ComparisonOpEnum::LessThan:
232             return left < right;
233         case filter_ast::ComparisonOpEnum::LessThanOrEqual:
234             return left <= right;
235         default:
236             BMCWEB_LOG_ERROR("Got x.token that should never happen {}",
237                              static_cast<int>(comparator));
238             return true;
239     }
240 }
241 
doIntComparison(int64_t left,filter_ast::ComparisonOpEnum comparator,int64_t right)242 bool doIntComparison(int64_t left, filter_ast::ComparisonOpEnum comparator,
243                      int64_t right)
244 {
245     switch (comparator)
246     {
247         case filter_ast::ComparisonOpEnum::Equals:
248             return left == right;
249         case filter_ast::ComparisonOpEnum::NotEquals:
250             return left != right;
251         case filter_ast::ComparisonOpEnum::GreaterThan:
252             return left > right;
253         case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
254             return left >= right;
255         case filter_ast::ComparisonOpEnum::LessThan:
256             return left < right;
257         case filter_ast::ComparisonOpEnum::LessThanOrEqual:
258             return left <= right;
259         default:
260             BMCWEB_LOG_ERROR("Got comparator that should never happen {}",
261                              static_cast<int>(comparator));
262             return true;
263     }
264 }
265 
doStringComparison(std::string_view left,filter_ast::ComparisonOpEnum comparator,std::string_view right)266 bool doStringComparison(std::string_view left,
267                         filter_ast::ComparisonOpEnum comparator,
268                         std::string_view right)
269 {
270     switch (comparator)
271     {
272         case filter_ast::ComparisonOpEnum::Equals:
273             return left == right;
274         case filter_ast::ComparisonOpEnum::NotEquals:
275             return left != right;
276         case filter_ast::ComparisonOpEnum::GreaterThan:
277             return alphanumComp(left, right) > 0;
278         case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
279             return alphanumComp(left, right) >= 0;
280         case filter_ast::ComparisonOpEnum::LessThan:
281             return alphanumComp(left, right) < 0;
282         case filter_ast::ComparisonOpEnum::LessThanOrEqual:
283             return alphanumComp(left, right) <= 0;
284         default:
285             BMCWEB_LOG_ERROR(
286                 "Got comparator that should never happen.  Attempt to do numeric comparison on string {}",
287                 static_cast<int>(comparator));
288             return true;
289     }
290 }
291 
operator ()(const filter_ast::Comparison & x)292 bool ApplyFilter::operator()(const filter_ast::Comparison& x)
293 {
294     ValueVisitor numeric(body);
295     std::variant<std::monostate, double, int64_t, std::string, DateTimeString>
296         left = boost::apply_visitor(numeric, x.left);
297     std::variant<std::monostate, double, int64_t, std::string, DateTimeString>
298         right = boost::apply_visitor(numeric, x.right);
299 
300     // Numeric comparisons
301     const double* lDoubleValue = std::get_if<double>(&left);
302     const double* rDoubleValue = std::get_if<double>(&right);
303     const int64_t* lIntValue = std::get_if<int64_t>(&left);
304     const int64_t* rIntValue = std::get_if<int64_t>(&right);
305 
306     if (lDoubleValue != nullptr)
307     {
308         if (rDoubleValue != nullptr)
309         {
310             // Both sides are doubles, do the comparison as doubles
311             return doDoubleComparison(*lDoubleValue, x.token, *rDoubleValue);
312         }
313         if (rIntValue != nullptr)
314         {
315             // If right arg is int, promote right arg to double
316             return doDoubleComparison(*lDoubleValue, x.token,
317                                       static_cast<double>(*rIntValue));
318         }
319     }
320     if (lIntValue != nullptr)
321     {
322         if (rIntValue != nullptr)
323         {
324             // Both sides are ints, do the comparison as ints
325             return doIntComparison(*lIntValue, x.token, *rIntValue);
326         }
327 
328         if (rDoubleValue != nullptr)
329         {
330             // Left arg is int, promote left arg to double
331             return doDoubleComparison(static_cast<double>(*lIntValue), x.token,
332                                       *rDoubleValue);
333         }
334     }
335 
336     // String comparisons
337     const std::string* lStrValue = std::get_if<std::string>(&left);
338     const std::string* rStrValue = std::get_if<std::string>(&right);
339 
340     const DateTimeString* lDateValue = std::get_if<DateTimeString>(&left);
341     const DateTimeString* rDateValue = std::get_if<DateTimeString>(&right);
342 
343     // If we're trying to compare a date string to a string, construct a
344     // datestring from the string
345     if (lDateValue != nullptr && rStrValue != nullptr)
346     {
347         rDateValue = &right.emplace<DateTimeString>(std::string(*rStrValue));
348     }
349     if (lStrValue != nullptr && rDateValue != nullptr)
350     {
351         lDateValue = &left.emplace<DateTimeString>(std::string(*lStrValue));
352     }
353 
354     if (lDateValue != nullptr && rDateValue != nullptr)
355     {
356         return doIntComparison(lDateValue->value.count(), x.token,
357                                rDateValue->value.count());
358     }
359 
360     if (lStrValue != nullptr && rStrValue != nullptr)
361     {
362         return doStringComparison(*lStrValue, x.token, *rStrValue);
363     }
364 
365     BMCWEB_LOG_ERROR(
366         "Fell through.  Should never happen.  Attempt to compare type {} to type {}",
367         static_cast<int>(left.index()), static_cast<int>(right.index()));
368     return true;
369 }
370 
operator ()(const filter_ast::BooleanOp & x)371 bool ApplyFilter::operator()(const filter_ast::BooleanOp& x)
372 {
373     return boost::apply_visitor(*this, x);
374 }
375 
operator ()(const filter_ast::LogicalOr & x)376 bool ApplyFilter::operator()(const filter_ast::LogicalOr& x)
377 {
378     bool value = (*this)(x.first);
379     for (const filter_ast::LogicalNot& bOp : x.rest)
380     {
381         value = value || (*this)(bOp);
382     }
383     return value;
384 }
385 
operator ()(const filter_ast::LogicalAnd & x)386 bool ApplyFilter::operator()(const filter_ast::LogicalAnd& x)
387 {
388     bool value = (*this)(x.first);
389     for (const filter_ast::LogicalOr& bOp : x.rest)
390     {
391         value = value && (*this)(bOp);
392     }
393     return value;
394 }
395 
matches()396 bool ApplyFilter::matches()
397 {
398     return (*this)(filter);
399 }
400 
401 } // namespace
402 
memberMatches(const nlohmann::json & member,const filter_ast::LogicalAnd & filterParam)403 bool memberMatches(const nlohmann::json& member,
404                    const filter_ast::LogicalAnd& filterParam)
405 {
406     ApplyFilter filterApplier(member, filterParam);
407     return filterApplier.matches();
408 }
409 
410 // Applies a filter expression to a member array
applyFilterToCollection(nlohmann::json & body,const filter_ast::LogicalAnd & filterParam)411 bool applyFilterToCollection(nlohmann::json& body,
412                              const filter_ast::LogicalAnd& filterParam)
413 {
414     using nlohmann::json;
415 
416     json::object_t* obj = body.get_ptr<json::object_t*>();
417     if (obj == nullptr)
418     {
419         BMCWEB_LOG_ERROR("Requested filter wasn't an object????");
420         return false;
421     }
422     json::object_t::iterator members = obj->find("Members");
423     if (members == obj->end())
424     {
425         BMCWEB_LOG_ERROR("Collection didn't have members?");
426         return false;
427     }
428     json::array_t* memberArr = members->second.get_ptr<json::array_t*>();
429     if (memberArr == nullptr)
430     {
431         BMCWEB_LOG_ERROR("Members wasn't an object????");
432         return false;
433     }
434 
435     json::array_t::iterator it = memberArr->begin();
436     size_t index = 0;
437     while (it != memberArr->end())
438     {
439         if (!memberMatches(*it, filterParam))
440         {
441             BMCWEB_LOG_DEBUG("Removing item at index {}", index);
442             it = memberArr->erase(it);
443             index++;
444             continue;
445         }
446         it++;
447         index++;
448     }
449 
450     return true;
451 }
452 } // namespace redfish
453