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