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
124 ValueVisitor::result_type
operator ()(const filter_ast::QuotedString & x)125 ValueVisitor::operator()(const filter_ast::QuotedString& x)
126 {
127 return {x};
128 }
129
130 ValueVisitor::result_type
operator ()(const filter_ast::UnquotedString & x)131 ValueVisitor::operator()(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 std::string* strValue = entry.get_ptr<const std::string*>();
156 if (strValue != nullptr)
157 {
158 if (DateTimeString::isDateTimeKey(x))
159 {
160 return DateTimeString(*strValue);
161 }
162 return {*strValue};
163 }
164
165 BMCWEB_LOG_ERROR(
166 "Type for key {} was {} which does not have a comparison operator",
167 static_cast<std::string>(x), static_cast<int>(entry.type()));
168 return {};
169 }
170
171 struct ApplyFilter
172 {
173 const nlohmann::json& body;
174 const filter_ast::LogicalAnd& filter;
175 using result_type = bool;
176 bool operator()(const filter_ast::LogicalNot& x);
177 bool operator()(const filter_ast::LogicalOr& x);
178 bool operator()(const filter_ast::LogicalAnd& x);
179 bool operator()(const filter_ast::Comparison& x);
180 bool operator()(const filter_ast::BooleanOp& x);
181
182 public:
183 bool matches();
184 };
185
operator ()(const filter_ast::LogicalNot & x)186 bool ApplyFilter::operator()(const filter_ast::LogicalNot& x)
187 {
188 bool subValue = (*this)(x.operand);
189 if (x.isLogicalNot)
190 {
191 return !subValue;
192 }
193 return subValue;
194 }
195
196 // Helper function to reduce the number of permutations of a single comparison
197 // For all possible types.
doDoubleComparison(double left,filter_ast::ComparisonOpEnum comparator,double right)198 bool doDoubleComparison(double left, filter_ast::ComparisonOpEnum comparator,
199 double right)
200 {
201 if (!std::isfinite(left) || !std::isfinite(right))
202 {
203 BMCWEB_LOG_ERROR("Refusing to do comparision of non finite numbers");
204 return false;
205 }
206 switch (comparator)
207 {
208 case filter_ast::ComparisonOpEnum::Equals:
209 // Note, floating point comparisons are hard. Compare equality
210 // based on epsilon
211 return std::fabs(left - right) <=
212 std::numeric_limits<double>::epsilon();
213 case filter_ast::ComparisonOpEnum::NotEquals:
214 return std::fabs(left - right) >
215 std::numeric_limits<double>::epsilon();
216 case filter_ast::ComparisonOpEnum::GreaterThan:
217 return left > right;
218 case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
219 return left >= right;
220 case filter_ast::ComparisonOpEnum::LessThan:
221 return left < right;
222 case filter_ast::ComparisonOpEnum::LessThanOrEqual:
223 return left <= right;
224 default:
225 BMCWEB_LOG_ERROR("Got x.token that should never happen {}",
226 static_cast<int>(comparator));
227 return true;
228 }
229 }
230
doIntComparison(int64_t left,filter_ast::ComparisonOpEnum comparator,int64_t right)231 bool doIntComparison(int64_t left, filter_ast::ComparisonOpEnum comparator,
232 int64_t right)
233 {
234 switch (comparator)
235 {
236 case filter_ast::ComparisonOpEnum::Equals:
237 return left == right;
238 case filter_ast::ComparisonOpEnum::NotEquals:
239 return left != right;
240 case filter_ast::ComparisonOpEnum::GreaterThan:
241 return left > right;
242 case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
243 return left >= right;
244 case filter_ast::ComparisonOpEnum::LessThan:
245 return left < right;
246 case filter_ast::ComparisonOpEnum::LessThanOrEqual:
247 return left <= right;
248 default:
249 BMCWEB_LOG_ERROR("Got comparator that should never happen {}",
250 static_cast<int>(comparator));
251 return true;
252 }
253 }
254
doStringComparison(std::string_view left,filter_ast::ComparisonOpEnum comparator,std::string_view right)255 bool doStringComparison(std::string_view left,
256 filter_ast::ComparisonOpEnum comparator,
257 std::string_view right)
258 {
259 switch (comparator)
260 {
261 case filter_ast::ComparisonOpEnum::Equals:
262 return left == right;
263 case filter_ast::ComparisonOpEnum::NotEquals:
264 return left != right;
265 case filter_ast::ComparisonOpEnum::GreaterThan:
266 return alphanumComp(left, right) > 0;
267 case filter_ast::ComparisonOpEnum::GreaterThanOrEqual:
268 return alphanumComp(left, right) >= 0;
269 case filter_ast::ComparisonOpEnum::LessThan:
270 return alphanumComp(left, right) < 0;
271 case filter_ast::ComparisonOpEnum::LessThanOrEqual:
272 return alphanumComp(left, right) <= 0;
273 default:
274 BMCWEB_LOG_ERROR(
275 "Got comparator that should never happen. Attempt to do numeric comparison on string {}",
276 static_cast<int>(comparator));
277 return true;
278 }
279 }
280
operator ()(const filter_ast::Comparison & x)281 bool ApplyFilter::operator()(const filter_ast::Comparison& x)
282 {
283 ValueVisitor numeric(body);
284 std::variant<std::monostate, double, int64_t, std::string, DateTimeString>
285 left = boost::apply_visitor(numeric, x.left);
286 std::variant<std::monostate, double, int64_t, std::string, DateTimeString>
287 right = boost::apply_visitor(numeric, x.right);
288
289 // Numeric comparisons
290 const double* lDoubleValue = std::get_if<double>(&left);
291 const double* rDoubleValue = std::get_if<double>(&right);
292 const int64_t* lIntValue = std::get_if<int64_t>(&left);
293 const int64_t* rIntValue = std::get_if<int64_t>(&right);
294
295 if (lDoubleValue != nullptr)
296 {
297 if (rDoubleValue != nullptr)
298 {
299 // Both sides are doubles, do the comparison as doubles
300 return doDoubleComparison(*lDoubleValue, x.token, *rDoubleValue);
301 }
302 if (rIntValue != nullptr)
303 {
304 // If right arg is int, promote right arg to double
305 return doDoubleComparison(*lDoubleValue, x.token,
306 static_cast<double>(*rIntValue));
307 }
308 }
309 if (lIntValue != nullptr)
310 {
311 if (rIntValue != nullptr)
312 {
313 // Both sides are ints, do the comparison as ints
314 return doIntComparison(*lIntValue, x.token, *rIntValue);
315 }
316
317 if (rDoubleValue != nullptr)
318 {
319 // Left arg is int, promote left arg to double
320 return doDoubleComparison(static_cast<double>(*lIntValue), x.token,
321 *rDoubleValue);
322 }
323 }
324
325 // String comparisons
326 const std::string* lStrValue = std::get_if<std::string>(&left);
327 const std::string* rStrValue = std::get_if<std::string>(&right);
328
329 const DateTimeString* lDateValue = std::get_if<DateTimeString>(&left);
330 const DateTimeString* rDateValue = std::get_if<DateTimeString>(&right);
331
332 // If we're trying to compare a date string to a string, construct a
333 // datestring from the string
334 if (lDateValue != nullptr && rStrValue != nullptr)
335 {
336 rDateValue = &right.emplace<DateTimeString>(std::string(*rStrValue));
337 }
338 if (lStrValue != nullptr && rDateValue != nullptr)
339 {
340 lDateValue = &left.emplace<DateTimeString>(std::string(*lStrValue));
341 }
342
343 if (lDateValue != nullptr && rDateValue != nullptr)
344 {
345 return doIntComparison(lDateValue->value.count(), x.token,
346 rDateValue->value.count());
347 }
348
349 if (lStrValue != nullptr && rStrValue != nullptr)
350 {
351 return doStringComparison(*lStrValue, x.token, *rStrValue);
352 }
353
354 BMCWEB_LOG_ERROR(
355 "Fell through. Should never happen. Attempt to compare type {} to type {}",
356 static_cast<int>(left.index()), static_cast<int>(right.index()));
357 return true;
358 }
359
operator ()(const filter_ast::BooleanOp & x)360 bool ApplyFilter::operator()(const filter_ast::BooleanOp& x)
361 {
362 return boost::apply_visitor(*this, x);
363 }
364
operator ()(const filter_ast::LogicalOr & x)365 bool ApplyFilter::operator()(const filter_ast::LogicalOr& x)
366 {
367 bool value = (*this)(x.first);
368 for (const filter_ast::LogicalNot& bOp : x.rest)
369 {
370 value = value || (*this)(bOp);
371 }
372 return value;
373 }
374
operator ()(const filter_ast::LogicalAnd & x)375 bool ApplyFilter::operator()(const filter_ast::LogicalAnd& x)
376 {
377 bool value = (*this)(x.first);
378 for (const filter_ast::LogicalOr& bOp : x.rest)
379 {
380 value = value && (*this)(bOp);
381 }
382 return value;
383 }
384
matches()385 bool ApplyFilter::matches()
386 {
387 return (*this)(filter);
388 }
389
390 } // namespace
391
memberMatches(const nlohmann::json & member,const filter_ast::LogicalAnd & filterParam)392 bool memberMatches(const nlohmann::json& member,
393 const filter_ast::LogicalAnd& filterParam)
394 {
395 ApplyFilter filterApplier(member, filterParam);
396 return filterApplier.matches();
397 }
398
399 // Applies a filter expression to a member array
applyFilterToCollection(nlohmann::json & body,const filter_ast::LogicalAnd & filterParam)400 bool applyFilterToCollection(nlohmann::json& body,
401 const filter_ast::LogicalAnd& filterParam)
402 {
403 using nlohmann::json;
404
405 json::object_t* obj = body.get_ptr<json::object_t*>();
406 if (obj == nullptr)
407 {
408 BMCWEB_LOG_ERROR("Requested filter wasn't an object????");
409 return false;
410 }
411 json::object_t::iterator members = obj->find("Members");
412 if (members == obj->end())
413 {
414 BMCWEB_LOG_ERROR("Collection didn't have members?");
415 return false;
416 }
417 json::array_t* memberArr = members->second.get_ptr<json::array_t*>();
418 if (memberArr == nullptr)
419 {
420 BMCWEB_LOG_ERROR("Members wasn't an object????");
421 return false;
422 }
423
424 json::array_t::iterator it = memberArr->begin();
425 size_t index = 0;
426 while (it != memberArr->end())
427 {
428 if (!memberMatches(*it, filterParam))
429 {
430 BMCWEB_LOG_DEBUG("Removing item at index {}", index);
431 it = memberArr->erase(it);
432 index++;
433 continue;
434 }
435 it++;
436 index++;
437 }
438
439 return true;
440 }
441 } // namespace redfish
442