1 #pragma once 2 3 #include "app.hpp" 4 #include "dbus_utility.hpp" 5 #include "generated/enums/log_service.hpp" 6 #include "query.hpp" 7 #include "registries/openbmc_message_registry.hpp" 8 #include "registries/privilege_registry.hpp" 9 #include "utils/time_utils.hpp" 10 11 #include <cstdint> 12 #include <memory> 13 #include <string_view> 14 #include <utility> 15 #include <vector> 16 17 namespace redfish 18 { 19 20 inline void handleSystemsLogServicesPostCodesGet( 21 App& app, const crow::Request& req, 22 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 23 const std::string& systemName) 24 { 25 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 26 { 27 return; 28 } 29 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 30 { 31 // Option currently returns no systems. TBD 32 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 33 systemName); 34 return; 35 } 36 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 37 { 38 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 39 systemName); 40 return; 41 } 42 asyncResp->res.jsonValue["@odata.id"] = 43 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes", 44 BMCWEB_REDFISH_SYSTEM_URI_NAME); 45 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 46 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 47 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 48 asyncResp->res.jsonValue["Id"] = "PostCodes"; 49 asyncResp->res.jsonValue["OverWritePolicy"] = 50 log_service::OverWritePolicy::WrapsWhenFull; 51 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 52 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 53 BMCWEB_REDFISH_SYSTEM_URI_NAME); 54 55 std::pair<std::string, std::string> redfishDateTimeOffset = 56 redfish::time_utils::getDateTimeOffsetNow(); 57 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 58 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 59 redfishDateTimeOffset.second; 60 61 asyncResp->res 62 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = std::format( 63 "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog", 64 BMCWEB_REDFISH_SYSTEM_URI_NAME); 65 } 66 67 inline void handleSystemsLogServicesPostCodesPost( 68 App& app, const crow::Request& req, 69 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 70 const std::string& systemName) 71 { 72 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 73 { 74 return; 75 } 76 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 77 { 78 // Option currently returns no systems. TBD 79 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 80 systemName); 81 return; 82 } 83 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 84 { 85 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 86 systemName); 87 return; 88 } 89 BMCWEB_LOG_DEBUG("Do delete all postcodes entries."); 90 91 // Make call to post-code service to request clear all 92 crow::connections::systemBus->async_method_call( 93 [asyncResp](const boost::system::error_code& ec) { 94 if (ec) 95 { 96 // TODO Handle for specific error code 97 BMCWEB_LOG_ERROR("doClearPostCodes resp_handler got error {}", 98 ec); 99 asyncResp->res.result( 100 boost::beast::http::status::internal_server_error); 101 messages::internalError(asyncResp->res); 102 return; 103 } 104 messages::success(asyncResp->res); 105 }, 106 "xyz.openbmc_project.State.Boot.PostCode0", 107 "/xyz/openbmc_project/State/Boot/PostCode0", 108 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 109 } 110 111 /** 112 * @brief Parse post code ID and get the current value and index value 113 * eg: postCodeID=B1-2, currentValue=1, index=2 114 * 115 * @param[in] postCodeID Post Code ID 116 * @param[out] currentValue Current value 117 * @param[out] index Index value 118 * 119 * @return bool true if the parsing is successful, false the parsing fails 120 */ 121 inline bool parsePostCode(std::string_view postCodeID, uint64_t& currentValue, 122 uint16_t& index) 123 { 124 std::vector<std::string> split; 125 bmcweb::split(split, postCodeID, '-'); 126 if (split.size() != 2) 127 { 128 return false; 129 } 130 std::string_view postCodeNumber = split[0]; 131 if (postCodeNumber.size() < 2) 132 { 133 return false; 134 } 135 if (postCodeNumber[0] != 'B') 136 { 137 return false; 138 } 139 postCodeNumber.remove_prefix(1); 140 auto [ptrIndex, ecIndex] = 141 std::from_chars(postCodeNumber.begin(), postCodeNumber.end(), index); 142 if (ptrIndex != postCodeNumber.end() || ecIndex != std::errc()) 143 { 144 return false; 145 } 146 147 std::string_view postCodeIndex = split[1]; 148 149 auto [ptrValue, ecValue] = std::from_chars( 150 postCodeIndex.begin(), postCodeIndex.end(), currentValue); 151 152 return ptrValue == postCodeIndex.end() && ecValue == std::errc(); 153 } 154 155 static bool fillPostCodeEntry( 156 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 157 const boost::container::flat_map< 158 uint64_t, std::tuple<std::vector<uint8_t>, std::vector<uint8_t>>>& 159 postcode, 160 const uint16_t bootIndex, const uint64_t codeIndex = 0, 161 const uint64_t skip = 0, const uint64_t top = 0) 162 { 163 // Get the Message from the MessageRegistry 164 const registries::Message* message = 165 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 166 if (message == nullptr) 167 { 168 BMCWEB_LOG_ERROR("Couldn't find known message?"); 169 return false; 170 } 171 uint64_t currentCodeIndex = 0; 172 uint64_t firstCodeTimeUs = 0; 173 for (const std::pair<uint64_t, std::tuple<std::vector<uint8_t>, 174 std::vector<uint8_t>>>& code : 175 postcode) 176 { 177 currentCodeIndex++; 178 std::string postcodeEntryID = 179 "B" + std::to_string(bootIndex) + "-" + 180 std::to_string(currentCodeIndex); // 1 based index in EntryID string 181 182 uint64_t usecSinceEpoch = code.first; 183 uint64_t usTimeOffset = 0; 184 185 if (1 == currentCodeIndex) 186 { // already incremented 187 firstCodeTimeUs = code.first; 188 } 189 else 190 { 191 usTimeOffset = code.first - firstCodeTimeUs; 192 } 193 194 // skip if no specific codeIndex is specified and currentCodeIndex does 195 // not fall between top and skip 196 if ((codeIndex == 0) && 197 (currentCodeIndex <= skip || currentCodeIndex > top)) 198 { 199 continue; 200 } 201 202 // skip if a specific codeIndex is specified and does not match the 203 // currentIndex 204 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 205 { 206 // This is done for simplicity. 1st entry is needed to calculate 207 // time offset. To improve efficiency, one can get to the entry 208 // directly (possibly with flatmap's nth method) 209 continue; 210 } 211 212 // currentCodeIndex is within top and skip or equal to specified code 213 // index 214 215 // Get the Created time from the timestamp 216 std::string entryTimeStr; 217 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch); 218 219 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 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 = 231 "0x" + bytesToHexString(std::get<0>(code.second)); 232 233 std::array<std::string_view, 3> messageArgs = { 234 bootIndexStr, timeOffsetString, hexCodeStr}; 235 236 std::string msg = 237 redfish::registries::fillMessageArgs(messageArgs, message->message); 238 if (msg.empty()) 239 { 240 messages::internalError(asyncResp->res); 241 return false; 242 } 243 244 // Get Severity template from message registry 245 std::string severity; 246 if (message != nullptr) 247 { 248 severity = message->messageSeverity; 249 } 250 251 // Format entry 252 nlohmann::json::object_t bmcLogEntry; 253 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 254 bmcLogEntry["@odata.id"] = boost::urls::format( 255 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/{}", 256 BMCWEB_REDFISH_SYSTEM_URI_NAME, postcodeEntryID); 257 bmcLogEntry["Name"] = "POST Code Log Entry"; 258 bmcLogEntry["Id"] = postcodeEntryID; 259 bmcLogEntry["Message"] = std::move(msg); 260 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode"; 261 bmcLogEntry["MessageArgs"] = messageArgs; 262 bmcLogEntry["EntryType"] = "Event"; 263 bmcLogEntry["Severity"] = std::move(severity); 264 bmcLogEntry["Created"] = entryTimeStr; 265 if (!std::get<1>(code.second).empty()) 266 { 267 bmcLogEntry["AdditionalDataURI"] = 268 std::format( 269 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/", 270 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 271 postcodeEntryID + "/attachment"; 272 } 273 274 // codeIndex is only specified when querying single entry, return only 275 // that entry in this case 276 if (codeIndex != 0) 277 { 278 asyncResp->res.jsonValue.update(bmcLogEntry); 279 return true; 280 } 281 282 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 283 logEntryArray.emplace_back(std::move(bmcLogEntry)); 284 } 285 286 // Return value is always false when querying multiple entries 287 return false; 288 } 289 290 inline void 291 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 292 const std::string& entryId) 293 { 294 uint16_t bootIndex = 0; 295 uint64_t codeIndex = 0; 296 if (!parsePostCode(entryId, codeIndex, bootIndex)) 297 { 298 // Requested ID was not found 299 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 300 return; 301 } 302 303 if (bootIndex == 0 || codeIndex == 0) 304 { 305 // 0 is an invalid index 306 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 307 return; 308 } 309 310 crow::connections::systemBus->async_method_call( 311 [asyncResp, entryId, bootIndex, 312 codeIndex](const boost::system::error_code& ec, 313 const boost::container::flat_map< 314 uint64_t, std::tuple<std::vector<uint8_t>, 315 std::vector<uint8_t>>>& postcode) { 316 if (ec) 317 { 318 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 319 messages::internalError(asyncResp->res); 320 return; 321 } 322 323 if (postcode.empty()) 324 { 325 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 326 return; 327 } 328 329 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 330 { 331 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 332 return; 333 } 334 }, 335 "xyz.openbmc_project.State.Boot.PostCode0", 336 "/xyz/openbmc_project/State/Boot/PostCode0", 337 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 338 bootIndex); 339 } 340 341 inline void 342 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 343 const uint16_t bootIndex, const uint16_t bootCount, 344 const uint64_t entryCount, size_t skip, size_t top) 345 { 346 crow::connections::systemBus->async_method_call( 347 [asyncResp, bootIndex, bootCount, entryCount, skip, 348 top](const boost::system::error_code& ec, 349 const boost::container::flat_map< 350 uint64_t, std::tuple<std::vector<uint8_t>, 351 std::vector<uint8_t>>>& postcode) { 352 if (ec) 353 { 354 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 355 messages::internalError(asyncResp->res); 356 return; 357 } 358 359 uint64_t endCount = entryCount; 360 if (!postcode.empty()) 361 { 362 endCount = entryCount + postcode.size(); 363 if (skip < endCount && (top + skip) > entryCount) 364 { 365 uint64_t thisBootSkip = 366 std::max(static_cast<uint64_t>(skip), entryCount) - 367 entryCount; 368 uint64_t thisBootTop = 369 std::min(static_cast<uint64_t>(top + skip), endCount) - 370 entryCount; 371 372 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 373 thisBootSkip, thisBootTop); 374 } 375 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 376 } 377 378 // continue to previous bootIndex 379 if (bootIndex < bootCount) 380 { 381 getPostCodeForBoot(asyncResp, 382 static_cast<uint16_t>(bootIndex + 1), 383 bootCount, endCount, skip, top); 384 } 385 else if (skip + top < endCount) 386 { 387 asyncResp->res.jsonValue["Members@odata.nextLink"] = 388 std::format( 389 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=", 390 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 391 std::to_string(skip + top); 392 } 393 }, 394 "xyz.openbmc_project.State.Boot.PostCode0", 395 "/xyz/openbmc_project/State/Boot/PostCode0", 396 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 397 bootIndex); 398 } 399 400 inline void 401 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 402 size_t skip, size_t top) 403 { 404 uint64_t entryCount = 0; 405 dbus::utility::getProperty<uint16_t>( 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<std::vector<uint8_t>, 507 std::vector<uint8_t>>>& 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 correct; we need "SubordinateOverrides" 590 // before we can automate it. 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