1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 4 #pragma once 5 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "human_sort.hpp" 10 #include "logging.hpp" 11 12 #include <boost/system/result.hpp> 13 #include <boost/url/parse.hpp> 14 #include <boost/url/url_view.hpp> 15 #include <nlohmann/json.hpp> 16 17 #include <algorithm> 18 #include <array> 19 #include <cmath> 20 #include <cstddef> 21 #include <cstdint> 22 #include <limits> 23 #include <map> 24 #include <optional> 25 #include <ranges> 26 #include <span> 27 #include <string> 28 #include <string_view> 29 #include <type_traits> 30 #include <utility> 31 #include <variant> 32 #include <vector> 33 34 // IWYU pragma: no_forward_declare crow::Request 35 36 namespace redfish 37 { 38 39 namespace json_util 40 { 41 42 /** 43 * @brief Processes request to extract JSON from its body. If it fails, adds 44 * MalformedJSON message to response and ends it. 45 * 46 * @param[io] res Response object 47 * @param[in] req Request object 48 * @param[out] reqJson JSON object extracted from request's body 49 * 50 * @return true if JSON is valid, false when JSON is invalid and response has 51 * been filled with message and ended. 52 */ 53 bool processJsonFromRequest(crow::Response& res, const crow::Request& req, 54 nlohmann::json& reqJson); 55 namespace details 56 { 57 58 template <typename Type> 59 struct IsOptional : std::false_type 60 {}; 61 62 template <typename Type> 63 struct IsOptional<std::optional<Type>> : std::true_type 64 {}; 65 66 template <typename Type> 67 struct IsVector : std::false_type 68 {}; 69 70 template <typename Type> 71 struct IsVector<std::vector<Type>> : std::true_type 72 {}; 73 74 template <typename Type> 75 struct IsStdArray : std::false_type 76 {}; 77 78 template <typename Type, std::size_t size> 79 struct IsStdArray<std::array<Type, size>> : std::true_type 80 {}; 81 82 template <typename Type> 83 struct IsVariant : std::false_type 84 {}; 85 86 template <typename... Types> 87 struct IsVariant<std::variant<Types...>> : std::true_type 88 {}; 89 90 enum class UnpackErrorCode 91 { 92 success, 93 invalidType, 94 outOfRange 95 }; 96 97 template <typename ToType, typename FromType> 98 bool checkRange(const FromType& from, std::string_view key) 99 { 100 if constexpr (std::is_floating_point_v<ToType>) 101 { 102 if (std::isnan(from)) 103 { 104 BMCWEB_LOG_DEBUG("Value for key {} was NAN", key); 105 return false; 106 } 107 // Assume for the moment that all floats can represent the full range 108 // of any int/uint in a cast. This is close enough to true for the 109 // precision of this json parser. 110 } 111 else 112 { 113 if (std::cmp_greater(from, std::numeric_limits<ToType>::max())) 114 { 115 BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key, 116 std::numeric_limits<FromType>::max()); 117 return false; 118 } 119 if (std::cmp_less(from, std::numeric_limits<ToType>::lowest())) 120 { 121 BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key, 122 std::numeric_limits<FromType>::lowest()); 123 return false; 124 } 125 } 126 127 return true; 128 } 129 130 template <typename Type> 131 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, 132 std::string_view key, Type& value); 133 134 template <std::size_t Index = 0, typename... Args> 135 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key, 136 std::variant<Args...>& v) 137 { 138 if constexpr (Index < std::variant_size_v<std::variant<Args...>>) 139 { 140 std::variant_alternative_t<Index, std::variant<Args...>> type{}; 141 UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type); 142 if (unpack == UnpackErrorCode::success) 143 { 144 v = std::move(type); 145 return unpack; 146 } 147 148 return unpackValueVariant<Index + 1, Args...>(j, key, v); 149 } 150 return UnpackErrorCode::invalidType; 151 } 152 153 template <typename Type> 154 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, 155 std::string_view key, Type& value) 156 { 157 UnpackErrorCode ret = UnpackErrorCode::success; 158 159 if constexpr (std::is_floating_point_v<Type>) 160 { 161 double helper = 0; 162 double* jsonPtr = jsonValue.get_ptr<double*>(); 163 164 if (jsonPtr == nullptr) 165 { 166 int64_t* intPtr = jsonValue.get_ptr<int64_t*>(); 167 if (intPtr != nullptr) 168 { 169 helper = static_cast<double>(*intPtr); 170 jsonPtr = &helper; 171 } 172 } 173 if (jsonPtr == nullptr) 174 { 175 return UnpackErrorCode::invalidType; 176 } 177 if (!checkRange<Type>(*jsonPtr, key)) 178 { 179 return UnpackErrorCode::outOfRange; 180 } 181 value = static_cast<Type>(*jsonPtr); 182 } 183 184 else if constexpr (std::is_signed_v<Type>) 185 { 186 int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>(); 187 if (jsonPtr == nullptr) 188 { 189 // Value wasn't int, check uint 190 uint64_t* uJsonPtr = jsonValue.get_ptr<uint64_t*>(); 191 if (uJsonPtr == nullptr) 192 { 193 return UnpackErrorCode::invalidType; 194 } 195 if (!checkRange<Type>(*uJsonPtr, key)) 196 { 197 return UnpackErrorCode::outOfRange; 198 } 199 value = static_cast<Type>(*uJsonPtr); 200 } 201 else 202 { 203 if (!checkRange<Type>(*jsonPtr, key)) 204 { 205 return UnpackErrorCode::outOfRange; 206 } 207 value = static_cast<Type>(*jsonPtr); 208 } 209 } 210 211 else if constexpr ((std::is_unsigned_v<Type>) && 212 (!std::is_same_v<bool, Type>)) 213 { 214 uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>(); 215 if (jsonPtr == nullptr) 216 { 217 int64_t* ijsonPtr = jsonValue.get_ptr<int64_t*>(); 218 if (ijsonPtr == nullptr) 219 { 220 return UnpackErrorCode::invalidType; 221 } 222 if (!checkRange<Type>(*ijsonPtr, key)) 223 { 224 return UnpackErrorCode::outOfRange; 225 } 226 value = static_cast<Type>(*ijsonPtr); 227 } 228 else 229 { 230 if (!checkRange<Type>(*jsonPtr, key)) 231 { 232 return UnpackErrorCode::outOfRange; 233 } 234 value = static_cast<Type>(*jsonPtr); 235 } 236 } 237 238 else if constexpr (std::is_same_v<nlohmann::json, Type>) 239 { 240 value = std::move(jsonValue); 241 } 242 else if constexpr (std::is_same_v<std::nullptr_t, Type>) 243 { 244 if (!jsonValue.is_null()) 245 { 246 return UnpackErrorCode::invalidType; 247 } 248 } 249 else if constexpr (IsVector<Type>::value) 250 { 251 nlohmann::json::object_t* obj = 252 jsonValue.get_ptr<nlohmann::json::object_t*>(); 253 if (obj == nullptr) 254 { 255 return UnpackErrorCode::invalidType; 256 } 257 258 for (const auto& val : *obj) 259 { 260 value.emplace_back(); 261 ret = unpackValueWithErrorCode<typename Type::value_type>( 262 val, key, value.back()) && 263 ret; 264 } 265 } 266 else 267 { 268 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 269 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 270 if (jsonPtr == nullptr) 271 { 272 BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key, 273 jsonValue.type_name()); 274 return UnpackErrorCode::invalidType; 275 } 276 value = std::move(*jsonPtr); 277 } 278 return ret; 279 } 280 281 template <typename Type> 282 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, 283 crow::Response& res, Type& value) 284 { 285 bool ret = true; 286 287 if constexpr (IsOptional<Type>::value) 288 { 289 value.emplace(); 290 ret = unpackValue<typename Type::value_type>(jsonValue, key, res, 291 *value) && 292 ret; 293 } 294 else if constexpr (IsStdArray<Type>::value) 295 { 296 nlohmann::json::array_t* arr = 297 jsonValue.get_ptr<nlohmann::json::array_t*>(); 298 if (arr == nullptr) 299 { 300 messages::propertyValueTypeError(res, jsonValue, key); 301 return false; 302 } 303 if (jsonValue.size() != value.size()) 304 { 305 messages::propertyValueTypeError(res, jsonValue, key); 306 return false; 307 } 308 size_t index = 0; 309 for (auto& val : *arr) 310 { 311 ret = unpackValue<typename Type::value_type>(val, key, res, 312 value[index++]) && 313 ret; 314 } 315 } 316 else if constexpr (IsVector<Type>::value) 317 { 318 nlohmann::json::array_t* arr = 319 jsonValue.get_ptr<nlohmann::json::array_t*>(); 320 if (arr == nullptr) 321 { 322 messages::propertyValueTypeError(res, jsonValue, key); 323 return false; 324 } 325 326 for (auto& val : *arr) 327 { 328 value.emplace_back(); 329 ret = unpackValue<typename Type::value_type>(val, key, res, 330 value.back()) && 331 ret; 332 } 333 } 334 else if constexpr (IsVariant<Type>::value) 335 { 336 UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value); 337 if (ec != UnpackErrorCode::success) 338 { 339 if (ec == UnpackErrorCode::invalidType) 340 { 341 messages::propertyValueTypeError(res, jsonValue, key); 342 } 343 else if (ec == UnpackErrorCode::outOfRange) 344 { 345 messages::propertyValueOutOfRange(res, jsonValue, key); 346 } 347 return false; 348 } 349 } 350 else 351 { 352 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 353 if (ec != UnpackErrorCode::success) 354 { 355 if (ec == UnpackErrorCode::invalidType) 356 { 357 messages::propertyValueTypeError(res, jsonValue, key); 358 } 359 else if (ec == UnpackErrorCode::outOfRange) 360 { 361 messages::propertyValueOutOfRange(res, jsonValue, key); 362 } 363 return false; 364 } 365 } 366 367 return ret; 368 } 369 370 template <typename Type> 371 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) 372 { 373 bool ret = true; 374 if constexpr (IsOptional<Type>::value) 375 { 376 value.emplace(); 377 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && 378 ret; 379 } 380 else if constexpr (IsStdArray<Type>::value) 381 { 382 nlohmann::json::array_t* arr = 383 jsonValue.get_ptr<nlohmann::json::array_t*>(); 384 if (arr == nullptr) 385 { 386 return false; 387 } 388 if (jsonValue.size() != value.size()) 389 { 390 return false; 391 } 392 size_t index = 0; 393 for (const auto& val : *arr) 394 { 395 ret = unpackValue<typename Type::value_type>(val, key, 396 value[index++]) && 397 ret; 398 } 399 } 400 else if constexpr (IsVector<Type>::value) 401 { 402 nlohmann::json::array_t* arr = 403 jsonValue.get_ptr<nlohmann::json::array_t*>(); 404 if (arr == nullptr) 405 { 406 return false; 407 } 408 409 for (const auto& val : *arr) 410 { 411 value.emplace_back(); 412 ret = unpackValue<typename Type::value_type>(val, key, 413 value.back()) && 414 ret; 415 } 416 } 417 else 418 { 419 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 420 if (ec != UnpackErrorCode::success) 421 { 422 return false; 423 } 424 } 425 426 return ret; 427 } 428 429 // boost::hash_combine 430 inline std::size_t combine(std::size_t seed, std::size_t h) noexcept 431 { 432 seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); 433 return seed; 434 } 435 } // namespace details 436 437 // clang-format off 438 using UnpackVariant = std::variant< 439 uint8_t*, 440 uint16_t*, 441 int16_t*, 442 uint32_t*, 443 int32_t*, 444 uint64_t*, 445 int64_t*, 446 bool*, 447 double*, 448 std::string*, 449 nlohmann::json::object_t*, 450 std::variant<std::string, std::nullptr_t>*, 451 std::variant<uint8_t, std::nullptr_t>*, 452 std::variant<int16_t, std::nullptr_t>*, 453 std::variant<uint16_t, std::nullptr_t>*, 454 std::variant<int32_t, std::nullptr_t>*, 455 std::variant<uint32_t, std::nullptr_t>*, 456 std::variant<int64_t, std::nullptr_t>*, 457 std::variant<uint64_t, std::nullptr_t>*, 458 std::variant<double, std::nullptr_t>*, 459 std::variant<bool, std::nullptr_t>*, 460 std::vector<uint8_t>*, 461 std::vector<uint16_t>*, 462 std::vector<int16_t>*, 463 std::vector<uint32_t>*, 464 std::vector<int32_t>*, 465 std::vector<uint64_t>*, 466 std::vector<int64_t>*, 467 //std::vector<bool>*, 468 std::vector<double>*, 469 std::vector<std::string>*, 470 std::vector<nlohmann::json::object_t>*, 471 std::optional<uint8_t>*, 472 std::optional<uint16_t>*, 473 std::optional<int16_t>*, 474 std::optional<uint32_t>*, 475 std::optional<int32_t>*, 476 std::optional<uint64_t>*, 477 std::optional<int64_t>*, 478 std::optional<bool>*, 479 std::optional<double>*, 480 std::optional<std::string>*, 481 std::optional<nlohmann::json::object_t>*, 482 std::optional<std::vector<uint8_t>>*, 483 std::optional<std::vector<uint16_t>>*, 484 std::optional<std::vector<int16_t>>*, 485 std::optional<std::vector<uint32_t>>*, 486 std::optional<std::vector<int32_t>>*, 487 std::optional<std::vector<uint64_t>>*, 488 std::optional<std::vector<int64_t>>*, 489 //std::optional<std::vector<bool>>*, 490 std::optional<std::vector<double>>*, 491 std::optional<std::vector<std::string>>*, 492 std::optional<std::vector<nlohmann::json::object_t>>*, 493 std::optional<std::variant<std::string, std::nullptr_t>>*, 494 std::optional<std::variant<uint8_t, std::nullptr_t>>*, 495 std::optional<std::variant<int16_t, std::nullptr_t>>*, 496 std::optional<std::variant<uint16_t, std::nullptr_t>>*, 497 std::optional<std::variant<int32_t, std::nullptr_t>>*, 498 std::optional<std::variant<uint32_t, std::nullptr_t>>*, 499 std::optional<std::variant<int64_t, std::nullptr_t>>*, 500 std::optional<std::variant<uint64_t, std::nullptr_t>>*, 501 std::optional<std::variant<double, std::nullptr_t>>*, 502 std::optional<std::variant<bool, std::nullptr_t>>*, 503 std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*, 504 std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>* 505 >; 506 // clang-format on 507 508 struct PerUnpack 509 { 510 std::string_view key; 511 UnpackVariant value; 512 bool complete = false; 513 }; 514 515 inline bool readJsonHelperObject(nlohmann::json::object_t& obj, 516 crow::Response& res, 517 std::span<PerUnpack> toUnpack) 518 { 519 bool result = true; 520 for (auto& item : obj) 521 { 522 size_t unpackIndex = 0; 523 for (; unpackIndex < toUnpack.size(); unpackIndex++) 524 { 525 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 526 std::string_view key = unpackSpec.key; 527 size_t keysplitIndex = key.find('/'); 528 std::string_view leftover; 529 if (keysplitIndex != std::string_view::npos) 530 { 531 leftover = key.substr(keysplitIndex + 1); 532 key = key.substr(0, keysplitIndex); 533 } 534 535 if (key != item.first || unpackSpec.complete) 536 { 537 continue; 538 } 539 540 // Sublevel key 541 if (!leftover.empty()) 542 { 543 // Include the slash in the key so we can compare later 544 key = unpackSpec.key.substr(0, keysplitIndex + 1); 545 nlohmann::json::object_t j; 546 result = details::unpackValue<nlohmann::json::object_t>( 547 item.second, key, res, j) && 548 result; 549 if (!result) 550 { 551 return result; 552 } 553 554 std::vector<PerUnpack> nextLevel; 555 for (PerUnpack& p : toUnpack) 556 { 557 if (!p.key.starts_with(key)) 558 { 559 continue; 560 } 561 std::string_view thisLeftover = p.key.substr(key.size()); 562 nextLevel.push_back({thisLeftover, p.value, false}); 563 p.complete = true; 564 } 565 566 result = readJsonHelperObject(j, res, nextLevel) && result; 567 break; 568 } 569 570 result = 571 std::visit( 572 [&item, &unpackSpec, &res](auto& val) { 573 using ContainedT = 574 std::remove_pointer_t<std::decay_t<decltype(val)>>; 575 return details::unpackValue<ContainedT>( 576 item.second, unpackSpec.key, res, *val); 577 }, 578 unpackSpec.value) && 579 result; 580 581 unpackSpec.complete = true; 582 break; 583 } 584 585 if (unpackIndex == toUnpack.size()) 586 { 587 messages::propertyUnknown(res, item.first); 588 result = false; 589 } 590 } 591 592 for (PerUnpack& perUnpack : toUnpack) 593 { 594 if (!perUnpack.complete) 595 { 596 bool isOptional = std::visit( 597 [](auto& val) { 598 using ContainedType = 599 std::remove_pointer_t<std::decay_t<decltype(val)>>; 600 return details::IsOptional<ContainedType>::value; 601 }, 602 perUnpack.value); 603 if (isOptional) 604 { 605 continue; 606 } 607 messages::propertyMissing(res, perUnpack.key); 608 result = false; 609 } 610 } 611 return result; 612 } 613 614 inline void packVariant(std::span<PerUnpack> /*toPack*/) {} 615 616 template <typename FirstType, typename... UnpackTypes> 617 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 618 FirstType&& first, UnpackTypes&&... in) 619 { 620 if (toPack.empty()) 621 { 622 return; 623 } 624 toPack[0].key = key; 625 toPack[0].value = &first; 626 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 627 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 628 } 629 630 template <typename FirstType, typename... UnpackTypes> 631 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res, 632 std::string_view key, FirstType&& first, 633 UnpackTypes&&... in) 634 { 635 const std::size_t n = sizeof...(UnpackTypes) + 2; 636 std::array<PerUnpack, n / 2> toUnpack2; 637 packVariant(toUnpack2, key, std::forward<FirstType>(first), 638 std::forward<UnpackTypes&&>(in)...); 639 return readJsonHelperObject(jsonRequest, res, toUnpack2); 640 } 641 642 template <typename FirstType, typename... UnpackTypes> 643 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 644 std::string_view key, FirstType&& first, UnpackTypes&&... in) 645 { 646 nlohmann::json::object_t* obj = 647 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 648 if (obj == nullptr) 649 { 650 BMCWEB_LOG_DEBUG("Json value is not an object"); 651 messages::unrecognizedRequestBody(res); 652 return false; 653 } 654 return readJsonObject(*obj, res, key, std::forward<FirstType>(first), 655 std::forward<UnpackTypes&&>(in)...); 656 } 657 658 inline std::optional<nlohmann::json::object_t> readJsonPatchHelper( 659 const crow::Request& req, crow::Response& res) 660 { 661 nlohmann::json jsonRequest; 662 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 663 { 664 BMCWEB_LOG_DEBUG("Json value not readable"); 665 return std::nullopt; 666 } 667 nlohmann::json::object_t* object = 668 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 669 if (object == nullptr || object->empty()) 670 { 671 BMCWEB_LOG_DEBUG("Json value is empty"); 672 messages::emptyJSON(res); 673 return std::nullopt; 674 } 675 std::erase_if(*object, 676 [](const std::pair<std::string, nlohmann::json>& item) { 677 return item.first.starts_with("@odata."); 678 }); 679 if (object->empty()) 680 { 681 // If the update request only contains OData annotations, the service 682 // should return the HTTP 400 Bad Request status code with the 683 // NoOperation message from the Base Message Registry, ... 684 messages::noOperation(res); 685 return std::nullopt; 686 } 687 688 return {std::move(*object)}; 689 } 690 691 inline const nlohmann::json* findNestedKey(std::string_view key, 692 const nlohmann::json& value) 693 { 694 size_t keysplitIndex = key.find('/'); 695 std::string_view leftover; 696 nlohmann::json::const_iterator it; 697 if (keysplitIndex != std::string_view::npos) 698 { 699 const nlohmann::json::object_t* obj = 700 value.get_ptr<const nlohmann::json::object_t*>(); 701 if (obj == nullptr || obj->empty()) 702 { 703 BMCWEB_LOG_ERROR("Requested key wasn't an object"); 704 return nullptr; 705 } 706 707 leftover = key.substr(keysplitIndex + 1); 708 std::string_view keypart = key.substr(0, keysplitIndex); 709 it = value.find(keypart); 710 if (it == value.end()) 711 { 712 // Entry didn't have key 713 return nullptr; 714 } 715 return findNestedKey(leftover, it.value()); 716 } 717 718 it = value.find(key); 719 if (it == value.end()) 720 { 721 return nullptr; 722 } 723 return &*it; 724 } 725 726 template <typename... UnpackTypes> 727 bool readJsonPatch(const crow::Request& req, crow::Response& res, 728 std::string_view key, UnpackTypes&&... in) 729 { 730 std::optional<nlohmann::json::object_t> jsonRequest = 731 readJsonPatchHelper(req, res); 732 if (!jsonRequest) 733 { 734 return false; 735 } 736 if (jsonRequest->empty()) 737 { 738 messages::emptyJSON(res); 739 return false; 740 } 741 742 return readJsonObject(*jsonRequest, res, key, 743 std::forward<UnpackTypes&&>(in)...); 744 } 745 746 inline std::optional<nlohmann::json::json_pointer> 747 createJsonPointerFromFragment(std::string_view input) 748 { 749 auto hashPos = input.find('#'); 750 if (hashPos == std::string_view::npos || hashPos + 1 >= input.size()) 751 { 752 BMCWEB_LOG_ERROR( 753 "createJsonPointerFromFragment() No fragment found after #"); 754 return std::nullopt; 755 } 756 757 std::string_view fragment = input.substr(hashPos + 1); 758 return nlohmann::json::json_pointer(std::string(fragment)); 759 } 760 761 template <typename... UnpackTypes> 762 bool readJsonAction(const crow::Request& req, crow::Response& res, 763 const char* key, UnpackTypes&&... in) 764 { 765 nlohmann::json jsonRequest; 766 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 767 { 768 BMCWEB_LOG_DEBUG("Json value not readable"); 769 return false; 770 } 771 nlohmann::json::object_t* object = 772 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 773 if (object == nullptr) 774 { 775 BMCWEB_LOG_DEBUG("Json value is empty"); 776 messages::emptyJSON(res); 777 return false; 778 } 779 return readJsonObject(*object, res, key, 780 std::forward<UnpackTypes&&>(in)...); 781 } 782 783 // Determines if two json objects are less, based on the presence of the 784 // @odata.id key 785 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a, 786 const nlohmann::json& b) 787 { 788 using object_t = nlohmann::json::object_t; 789 const object_t* aObj = a.get_ptr<const object_t*>(); 790 const object_t* bObj = b.get_ptr<const object_t*>(); 791 792 if (aObj == nullptr) 793 { 794 if (bObj == nullptr) 795 { 796 return 0; 797 } 798 return -1; 799 } 800 if (bObj == nullptr) 801 { 802 return 1; 803 } 804 object_t::const_iterator aIt = aObj->find(key); 805 object_t::const_iterator bIt = bObj->find(key); 806 // If either object doesn't have the key, they get "sorted" to the 807 // beginning. 808 if (aIt == aObj->end()) 809 { 810 if (bIt == bObj->end()) 811 { 812 return 0; 813 } 814 return -1; 815 } 816 if (bIt == bObj->end()) 817 { 818 return 1; 819 } 820 const nlohmann::json::string_t* nameA = 821 aIt->second.get_ptr<const std::string*>(); 822 const nlohmann::json::string_t* nameB = 823 bIt->second.get_ptr<const std::string*>(); 824 // If either object doesn't have a string as the key, they get "sorted" to 825 // the beginning. 826 if (nameA == nullptr) 827 { 828 if (nameB == nullptr) 829 { 830 return 0; 831 } 832 return -1; 833 } 834 if (nameB == nullptr) 835 { 836 return 1; 837 } 838 if (key != "@odata.id") 839 { 840 return alphanumComp(*nameA, *nameB); 841 } 842 843 boost::system::result<boost::urls::url_view> aUrl = 844 boost::urls::parse_relative_ref(*nameA); 845 boost::system::result<boost::urls::url_view> bUrl = 846 boost::urls::parse_relative_ref(*nameB); 847 if (!aUrl) 848 { 849 if (!bUrl) 850 { 851 return 0; 852 } 853 return -1; 854 } 855 if (!bUrl) 856 { 857 return 1; 858 } 859 860 auto segmentsAIt = aUrl->segments().begin(); 861 auto segmentsBIt = bUrl->segments().begin(); 862 863 while (true) 864 { 865 if (segmentsAIt == aUrl->segments().end()) 866 { 867 if (segmentsBIt == bUrl->segments().end()) 868 { 869 return 0; 870 } 871 return -1; 872 } 873 if (segmentsBIt == bUrl->segments().end()) 874 { 875 return 1; 876 } 877 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 878 if (res != 0) 879 { 880 return res; 881 } 882 883 segmentsAIt++; 884 segmentsBIt++; 885 } 886 return 0; 887 }; 888 889 // kept for backward compatibility 890 inline int odataObjectCmp(const nlohmann::json& left, 891 const nlohmann::json& right) 892 { 893 return objectKeyCmp("@odata.id", left, right); 894 } 895 896 struct ODataObjectLess 897 { 898 std::string_view key; 899 900 explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {} 901 902 bool operator()(const nlohmann::json& left, 903 const nlohmann::json& right) const 904 { 905 return objectKeyCmp(key, left, right) < 0; 906 } 907 }; 908 909 // Sort the JSON array by |element[key]|. 910 // Elements without |key| or type of |element[key]| is not string are smaller 911 // those whose |element[key]| is string. 912 inline void sortJsonArrayByKey(nlohmann::json::array_t& array, 913 std::string_view key) 914 { 915 std::ranges::sort(array, ODataObjectLess(key)); 916 } 917 918 // Sort the JSON array by |element[key]|. 919 // Elements without |key| or type of |element[key]| is not string are smaller 920 // those whose |element[key]| is string. 921 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 922 { 923 std::ranges::sort(array, ODataObjectLess("@odata.id")); 924 } 925 926 // Returns the estimated size of the JSON value 927 // The implementation walks through every key and every value, accumulates the 928 // total size of keys and values. 929 // Ideally, we should use a custom allocator that nlohmann JSON supports. 930 931 // Assumption made: 932 // 1. number: 8 characters 933 // 2. boolean: 5 characters (False) 934 // 3. string: len(str) + 2 characters (quote) 935 // 4. bytes: len(bytes) characters 936 // 5. null: 4 characters (null) 937 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 938 939 // Hashes a json value, recursively omitting every member with key `keyToIgnore` 940 inline size_t hashJsonWithoutKey(const nlohmann::json& jsonValue, 941 std::string_view keyToIgnore) 942 { 943 const nlohmann::json::object_t* obj = 944 jsonValue.get_ptr<const nlohmann::json::object_t*>(); 945 if (obj == nullptr) 946 { 947 // Object has no keys to remove so just return hash 948 return std::hash<nlohmann::json>{}(jsonValue); 949 } 950 951 const size_t type = static_cast<std::size_t>(jsonValue.type()); 952 size_t seed = details::combine(type, jsonValue.size()); 953 for (const auto& element : *obj) 954 { 955 const size_t h = std::hash<std::string>{}(element.first); 956 seed = details::combine(seed, h); 957 if (element.first != keyToIgnore) 958 { 959 seed = details::combine( 960 seed, std::hash<nlohmann::json>{}(element.second)); 961 } 962 } 963 return seed; 964 } 965 966 } // namespace json_util 967 } // namespace redfish 968