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 = std::to_array<std::string_view>(
23         {"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 
63     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 
78     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     const 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 
98 ValueVisitor::result_type ValueVisitor::operator()(double n)
99 {
100     return {n};
101 }
102 
103 ValueVisitor::result_type ValueVisitor::operator()(int64_t x)
104 {
105     return {x};
106 }
107 
108 ValueVisitor::result_type
109     ValueVisitor::operator()(const filter_ast::QuotedString& x)
110 {
111     return {x};
112 }
113 
114 ValueVisitor::result_type
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     const 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 
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.
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 
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 
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 
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 
341 bool ApplyFilter::operator()(const filter_ast::BooleanOp& x)
342 {
343     return boost::apply_visitor(*this, x);
344 }
345 
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 
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 
366 bool ApplyFilter::matches()
367 {
368     return (*this)(filter);
369 }
370 
371 } // namespace
372 
373 bool memberMatches(const nlohmann::json& member,
374                    const filter_ast::LogicalAnd& filterParam)
375 {
376     ApplyFilter filterApplier(member, filterParam);
377     return filterApplier.matches();
378 }
379 
380 // Applies a filter expression to a member array
381 bool applyFilterToCollection(nlohmann::json& body,
382                              const filter_ast::LogicalAnd& filterParam)
383 {
384     using nlohmann::json;
385 
386     json::object_t* obj = body.get_ptr<json::object_t*>();
387     if (obj == nullptr)
388     {
389         BMCWEB_LOG_ERROR("Requested filter wasn't an object????");
390         return false;
391     }
392     json::object_t::iterator members = obj->find("Members");
393     if (members == obj->end())
394     {
395         BMCWEB_LOG_ERROR("Collection didn't have members?");
396         return false;
397     }
398     json::array_t* memberArr = members->second.get_ptr<json::array_t*>();
399     if (memberArr == nullptr)
400     {
401         BMCWEB_LOG_ERROR("Members wasn't an object????");
402         return false;
403     }
404 
405     json::array_t::iterator it = memberArr->begin();
406     size_t index = 0;
407     while (it != memberArr->end())
408     {
409         if (!memberMatches(*it, filterParam))
410         {
411             BMCWEB_LOG_DEBUG("Removing item at index {}", index);
412             it = memberArr->erase(it);
413             index++;
414             continue;
415         }
416         it++;
417         index++;
418     }
419 
420     return true;
421 }
422 } // namespace redfish
423