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