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