1 #pragma once 2 3 #include "app.hpp" 4 #include "generated/enums/log_service.hpp" 5 #include "query.hpp" 6 #include "registries/openbmc_message_registry.hpp" 7 #include "registries/privilege_registry.hpp" 8 #include "utils/time_utils.hpp" 9 10 #include <cstdint> 11 #include <memory> 12 #include <string_view> 13 #include <utility> 14 #include <vector> 15 16 namespace redfish 17 { 18 /**************************************************** 19 * Redfish PostCode interfaces 20 * using DBUS interface: getPostCodesTS 21 ******************************************************/ 22 inline void requestRoutesPostCodesLogService(App& app) 23 { 24 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/") 25 .privileges(redfish::privileges::getLogService) 26 .methods( 27 boost::beast::http::verb:: 28 get)([&app](const crow::Request& req, 29 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 30 const std::string& systemName) { 31 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 32 { 33 return; 34 } 35 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 36 { 37 // Option currently returns no systems. TBD 38 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 39 systemName); 40 return; 41 } 42 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 43 { 44 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 45 systemName); 46 return; 47 } 48 asyncResp->res.jsonValue["@odata.id"] = 49 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes", 50 BMCWEB_REDFISH_SYSTEM_URI_NAME); 51 asyncResp->res.jsonValue["@odata.type"] = 52 "#LogService.v1_2_0.LogService"; 53 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 54 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 55 asyncResp->res.jsonValue["Id"] = "PostCodes"; 56 asyncResp->res.jsonValue["OverWritePolicy"] = 57 log_service::OverWritePolicy::WrapsWhenFull; 58 asyncResp->res.jsonValue["Entries"]["@odata.id"] = std::format( 59 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 60 BMCWEB_REDFISH_SYSTEM_URI_NAME); 61 62 std::pair<std::string, std::string> redfishDateTimeOffset = 63 redfish::time_utils::getDateTimeOffsetNow(); 64 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 65 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 66 redfishDateTimeOffset.second; 67 68 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] 69 ["target"] = std::format( 70 "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog", 71 BMCWEB_REDFISH_SYSTEM_URI_NAME); 72 }); 73 } 74 75 inline void requestRoutesPostCodesClear(App& app) 76 { 77 BMCWEB_ROUTE( 78 app, 79 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/") 80 // The following privilege is incorrect; It should be ConfigureManager 81 //.privileges(redfish::privileges::postLogService) 82 .privileges({{"ConfigureComponents"}}) 83 .methods(boost::beast::http::verb::post)( 84 [&app](const crow::Request& req, 85 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 86 const std::string& systemName) { 87 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 88 { 89 return; 90 } 91 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 92 { 93 // Option currently returns no systems. TBD 94 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 95 systemName); 96 return; 97 } 98 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 99 { 100 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 101 systemName); 102 return; 103 } 104 BMCWEB_LOG_DEBUG("Do delete all postcodes entries."); 105 106 // Make call to post-code service to request clear all 107 crow::connections::systemBus->async_method_call( 108 [asyncResp](const boost::system::error_code& ec) { 109 if (ec) 110 { 111 // TODO Handle for specific error code 112 BMCWEB_LOG_ERROR( 113 "doClearPostCodes resp_handler got error {}", 114 ec); 115 asyncResp->res.result(boost::beast::http::status:: 116 internal_server_error); 117 messages::internalError(asyncResp->res); 118 return; 119 } 120 messages::success(asyncResp->res); 121 }, 122 "xyz.openbmc_project.State.Boot.PostCode0", 123 "/xyz/openbmc_project/State/Boot/PostCode0", 124 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 125 }); 126 } 127 128 /** 129 * @brief Parse post code ID and get the current value and index value 130 * eg: postCodeID=B1-2, currentValue=1, index=2 131 * 132 * @param[in] postCodeID Post Code ID 133 * @param[out] currentValue Current value 134 * @param[out] index Index value 135 * 136 * @return bool true if the parsing is successful, false the parsing fails 137 */ 138 inline bool parsePostCode(std::string_view postCodeID, uint64_t& currentValue, 139 uint16_t& index) 140 { 141 std::vector<std::string> split; 142 bmcweb::split(split, postCodeID, '-'); 143 if (split.size() != 2) 144 { 145 return false; 146 } 147 std::string_view postCodeNumber = split[0]; 148 if (postCodeNumber.size() < 2) 149 { 150 return false; 151 } 152 if (postCodeNumber[0] != 'B') 153 { 154 return false; 155 } 156 postCodeNumber.remove_prefix(1); 157 auto [ptrIndex, ecIndex] = 158 std::from_chars(postCodeNumber.begin(), postCodeNumber.end(), index); 159 if (ptrIndex != postCodeNumber.end() || ecIndex != std::errc()) 160 { 161 return false; 162 } 163 164 std::string_view postCodeIndex = split[1]; 165 166 auto [ptrValue, ecValue] = std::from_chars( 167 postCodeIndex.begin(), postCodeIndex.end(), currentValue); 168 169 return ptrValue == postCodeIndex.end() && ecValue == std::errc(); 170 } 171 172 static bool fillPostCodeEntry( 173 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 174 const boost::container::flat_map< 175 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 176 const uint16_t bootIndex, const uint64_t codeIndex = 0, 177 const uint64_t skip = 0, const uint64_t top = 0) 178 { 179 // Get the Message from the MessageRegistry 180 const registries::Message* message = 181 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 182 if (message == nullptr) 183 { 184 BMCWEB_LOG_ERROR("Couldn't find known message?"); 185 return false; 186 } 187 uint64_t currentCodeIndex = 0; 188 uint64_t firstCodeTimeUs = 0; 189 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 190 code : postcode) 191 { 192 currentCodeIndex++; 193 std::string postcodeEntryID = 194 "B" + std::to_string(bootIndex) + "-" + 195 std::to_string(currentCodeIndex); // 1 based index in EntryID string 196 197 uint64_t usecSinceEpoch = code.first; 198 uint64_t usTimeOffset = 0; 199 200 if (1 == currentCodeIndex) 201 { // already incremented 202 firstCodeTimeUs = code.first; 203 } 204 else 205 { 206 usTimeOffset = code.first - firstCodeTimeUs; 207 } 208 209 // skip if no specific codeIndex is specified and currentCodeIndex does 210 // not fall between top and skip 211 if ((codeIndex == 0) && 212 (currentCodeIndex <= skip || currentCodeIndex > top)) 213 { 214 continue; 215 } 216 217 // skip if a specific codeIndex is specified and does not match the 218 // currentIndex 219 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 220 { 221 // This is done for simplicity. 1st entry is needed to calculate 222 // time offset. To improve efficiency, one can get to the entry 223 // directly (possibly with flatmap's nth method) 224 continue; 225 } 226 227 // currentCodeIndex is within top and skip or equal to specified code 228 // index 229 230 // Get the Created time from the timestamp 231 std::string entryTimeStr; 232 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch); 233 234 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 235 std::ostringstream hexCode; 236 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 237 << std::get<0>(code.second); 238 std::ostringstream timeOffsetStr; 239 // Set Fixed -Point Notation 240 timeOffsetStr << std::fixed; 241 // Set precision to 4 digits 242 timeOffsetStr << std::setprecision(4); 243 // Add double to stream 244 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 245 246 std::string bootIndexStr = std::to_string(bootIndex); 247 std::string timeOffsetString = timeOffsetStr.str(); 248 std::string hexCodeStr = hexCode.str(); 249 250 std::array<std::string_view, 3> messageArgs = { 251 bootIndexStr, timeOffsetString, hexCodeStr}; 252 253 std::string msg = 254 redfish::registries::fillMessageArgs(messageArgs, message->message); 255 if (msg.empty()) 256 { 257 messages::internalError(asyncResp->res); 258 return false; 259 } 260 261 // Get Severity template from message registry 262 std::string severity; 263 if (message != nullptr) 264 { 265 severity = message->messageSeverity; 266 } 267 268 // Format entry 269 nlohmann::json::object_t bmcLogEntry; 270 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 271 bmcLogEntry["@odata.id"] = boost::urls::format( 272 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/{}", 273 BMCWEB_REDFISH_SYSTEM_URI_NAME, postcodeEntryID); 274 bmcLogEntry["Name"] = "POST Code Log Entry"; 275 bmcLogEntry["Id"] = postcodeEntryID; 276 bmcLogEntry["Message"] = std::move(msg); 277 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode"; 278 bmcLogEntry["MessageArgs"] = messageArgs; 279 bmcLogEntry["EntryType"] = "Event"; 280 bmcLogEntry["Severity"] = std::move(severity); 281 bmcLogEntry["Created"] = entryTimeStr; 282 if (!std::get<std::vector<uint8_t>>(code.second).empty()) 283 { 284 bmcLogEntry["AdditionalDataURI"] = 285 std::format( 286 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/", 287 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 288 postcodeEntryID + "/attachment"; 289 } 290 291 // codeIndex is only specified when querying single entry, return only 292 // that entry in this case 293 if (codeIndex != 0) 294 { 295 asyncResp->res.jsonValue.update(bmcLogEntry); 296 return true; 297 } 298 299 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 300 logEntryArray.emplace_back(std::move(bmcLogEntry)); 301 } 302 303 // Return value is always false when querying multiple entries 304 return false; 305 } 306 307 static void 308 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 309 const std::string& entryId) 310 { 311 uint16_t bootIndex = 0; 312 uint64_t codeIndex = 0; 313 if (!parsePostCode(entryId, codeIndex, bootIndex)) 314 { 315 // Requested ID was not found 316 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 317 return; 318 } 319 320 if (bootIndex == 0 || codeIndex == 0) 321 { 322 // 0 is an invalid index 323 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 324 return; 325 } 326 327 crow::connections::systemBus->async_method_call( 328 [asyncResp, entryId, bootIndex, 329 codeIndex](const boost::system::error_code& ec, 330 const boost::container::flat_map< 331 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 332 postcode) { 333 if (ec) 334 { 335 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 336 messages::internalError(asyncResp->res); 337 return; 338 } 339 340 if (postcode.empty()) 341 { 342 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 343 return; 344 } 345 346 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 347 { 348 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 349 return; 350 } 351 }, 352 "xyz.openbmc_project.State.Boot.PostCode0", 353 "/xyz/openbmc_project/State/Boot/PostCode0", 354 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 355 bootIndex); 356 } 357 358 static void 359 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 360 const uint16_t bootIndex, const uint16_t bootCount, 361 const uint64_t entryCount, size_t skip, size_t top) 362 { 363 crow::connections::systemBus->async_method_call( 364 [asyncResp, bootIndex, bootCount, entryCount, skip, 365 top](const boost::system::error_code& ec, 366 const boost::container::flat_map< 367 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 368 postcode) { 369 if (ec) 370 { 371 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 372 messages::internalError(asyncResp->res); 373 return; 374 } 375 376 uint64_t endCount = entryCount; 377 if (!postcode.empty()) 378 { 379 endCount = entryCount + postcode.size(); 380 if (skip < endCount && (top + skip) > entryCount) 381 { 382 uint64_t thisBootSkip = 383 std::max(static_cast<uint64_t>(skip), entryCount) - 384 entryCount; 385 uint64_t thisBootTop = 386 std::min(static_cast<uint64_t>(top + skip), endCount) - 387 entryCount; 388 389 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 390 thisBootSkip, thisBootTop); 391 } 392 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 393 } 394 395 // continue to previous bootIndex 396 if (bootIndex < bootCount) 397 { 398 getPostCodeForBoot(asyncResp, 399 static_cast<uint16_t>(bootIndex + 1), 400 bootCount, endCount, skip, top); 401 } 402 else if (skip + top < endCount) 403 { 404 asyncResp->res.jsonValue["Members@odata.nextLink"] = 405 std::format( 406 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=", 407 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 408 std::to_string(skip + top); 409 } 410 }, 411 "xyz.openbmc_project.State.Boot.PostCode0", 412 "/xyz/openbmc_project/State/Boot/PostCode0", 413 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 414 bootIndex); 415 } 416 417 static void 418 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 419 size_t skip, size_t top) 420 { 421 uint64_t entryCount = 0; 422 sdbusplus::asio::getProperty<uint16_t>( 423 *crow::connections::systemBus, 424 "xyz.openbmc_project.State.Boot.PostCode0", 425 "/xyz/openbmc_project/State/Boot/PostCode0", 426 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 427 [asyncResp, entryCount, skip, 428 top](const boost::system::error_code& ec, const uint16_t bootCount) { 429 if (ec) 430 { 431 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 432 messages::internalError(asyncResp->res); 433 return; 434 } 435 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top); 436 }); 437 } 438 439 inline void requestRoutesPostCodesEntryCollection(App& app) 440 { 441 BMCWEB_ROUTE(app, 442 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/") 443 .privileges(redfish::privileges::getLogEntryCollection) 444 .methods(boost::beast::http::verb::get)( 445 [&app](const crow::Request& req, 446 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 447 const std::string& systemName) { 448 query_param::QueryCapabilities capabilities = { 449 .canDelegateTop = true, 450 .canDelegateSkip = true, 451 }; 452 query_param::Query delegatedQuery; 453 if (!redfish::setUpRedfishRouteWithDelegation( 454 app, req, asyncResp, delegatedQuery, capabilities)) 455 { 456 return; 457 } 458 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 459 { 460 // Option currently returns no systems. TBD 461 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 462 systemName); 463 return; 464 } 465 466 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 467 { 468 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 469 systemName); 470 return; 471 } 472 asyncResp->res.jsonValue["@odata.type"] = 473 "#LogEntryCollection.LogEntryCollection"; 474 asyncResp->res.jsonValue["@odata.id"] = std::format( 475 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 476 BMCWEB_REDFISH_SYSTEM_URI_NAME); 477 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 478 asyncResp->res.jsonValue["Description"] = 479 "Collection of POST Code Log Entries"; 480 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 481 asyncResp->res.jsonValue["Members@odata.count"] = 0; 482 size_t skip = delegatedQuery.skip.value_or(0); 483 size_t top = 484 delegatedQuery.top.value_or(query_param::Query::maxTop); 485 getCurrentBootNumber(asyncResp, skip, top); 486 }); 487 } 488 489 inline void requestRoutesPostCodesEntryAdditionalData(App& app) 490 { 491 BMCWEB_ROUTE( 492 app, 493 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/") 494 .privileges(redfish::privileges::getLogEntry) 495 .methods( 496 boost::beast::http::verb:: 497 get)([&app](const crow::Request& req, 498 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 499 const std::string& systemName, 500 const std::string& postCodeID) { 501 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 502 { 503 return; 504 } 505 if (!http_helpers::isContentTypeAllowed( 506 req.getHeaderValue("Accept"), 507 http_helpers::ContentType::OctetStream, true)) 508 { 509 asyncResp->res.result(boost::beast::http::status::bad_request); 510 return; 511 } 512 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 513 { 514 // Option currently returns no systems. TBD 515 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 516 systemName); 517 return; 518 } 519 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 520 { 521 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 522 systemName); 523 return; 524 } 525 526 uint64_t currentValue = 0; 527 uint16_t index = 0; 528 if (!parsePostCode(postCodeID, currentValue, index)) 529 { 530 messages::resourceNotFound(asyncResp->res, "LogEntry", 531 postCodeID); 532 return; 533 } 534 535 crow::connections::systemBus->async_method_call( 536 [asyncResp, postCodeID, currentValue]( 537 const boost::system::error_code& ec, 538 const std::vector<std::tuple< 539 uint64_t, std::vector<uint8_t>>>& postcodes) { 540 if (ec.value() == EBADR) 541 { 542 messages::resourceNotFound(asyncResp->res, "LogEntry", 543 postCodeID); 544 return; 545 } 546 if (ec) 547 { 548 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 549 messages::internalError(asyncResp->res); 550 return; 551 } 552 553 size_t value = static_cast<size_t>(currentValue) - 1; 554 if (value == std::string::npos || 555 postcodes.size() < currentValue) 556 { 557 BMCWEB_LOG_WARNING("Wrong currentValue value"); 558 messages::resourceNotFound(asyncResp->res, "LogEntry", 559 postCodeID); 560 return; 561 } 562 563 const auto& [tID, c] = postcodes[value]; 564 if (c.empty()) 565 { 566 BMCWEB_LOG_WARNING("No found post code data"); 567 messages::resourceNotFound(asyncResp->res, "LogEntry", 568 postCodeID); 569 return; 570 } 571 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 572 const char* d = reinterpret_cast<const char*>(c.data()); 573 std::string_view strData(d, c.size()); 574 575 asyncResp->res.addHeader( 576 boost::beast::http::field::content_type, 577 "application/octet-stream"); 578 asyncResp->res.addHeader( 579 boost::beast::http::field::content_transfer_encoding, 580 "Base64"); 581 asyncResp->res.write(crow::utility::base64encode(strData)); 582 }, 583 "xyz.openbmc_project.State.Boot.PostCode0", 584 "/xyz/openbmc_project/State/Boot/PostCode0", 585 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", 586 index); 587 }); 588 } 589 590 inline void requestRoutesPostCodesEntry(App& app) 591 { 592 BMCWEB_ROUTE( 593 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/") 594 .privileges(redfish::privileges::getLogEntry) 595 .methods(boost::beast::http::verb::get)( 596 [&app](const crow::Request& req, 597 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 598 const std::string& systemName, const std::string& targetID) { 599 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 600 { 601 return; 602 } 603 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 604 { 605 // Option currently returns no systems. TBD 606 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 607 systemName); 608 return; 609 } 610 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 611 { 612 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 613 systemName); 614 return; 615 } 616 617 getPostCodeForEntry(asyncResp, targetID); 618 }); 619 } 620 621 inline void requestRoutesSystemsLogServicesPostCode(App& app) 622 { 623 requestRoutesPostCodesClear(app); 624 requestRoutesPostCodesEntry(app); 625 requestRoutesPostCodesEntryAdditionalData(app); 626 requestRoutesPostCodesEntryCollection(app); 627 requestRoutesPostCodesLogService(app); 628 } 629 } // namespace redfish 630