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