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