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