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