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