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