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