Add a class to handle RDE commands
This class is used to process RDE packets received from BIOS-BMC
circular buffer. RdeCommandHandler will manage dictionary data,
decode RDE BEJ encoded payloads and push them to the ExternalStorer.
Tested:
Tested this with unit tests.
Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: Ic66e4e4e2afa523906835713d36015457f324fcc
diff --git a/src/rde/rde_handler.cpp b/src/rde/rde_handler.cpp
new file mode 100644
index 0000000..e5100e2
--- /dev/null
+++ b/src/rde/rde_handler.cpp
@@ -0,0 +1,334 @@
+#include "rde/rde_handler.hpp"
+
+#include <fmt/format.h>
+
+#include <iostream>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace rde
+{
+
+/**
+ * @brief CRC-32 divisor.
+ *
+ * This is equivalent to the one used by IEEE802.3.
+ */
+constexpr uint32_t crcDevisor = 0xedb88320;
+
+RdeCommandHandler::RdeCommandHandler(
+ std::unique_ptr<ExternalStorerInterface> exStorer) :
+ flagState(RdeDictTransferFlagState::RdeStateIdle),
+ exStorer(std::move(exStorer))
+{
+ // Initialize CRC table.
+ calcCrcTable();
+}
+
+RdeDecodeStatus
+ RdeCommandHandler::decodeRdeCommand(std::span<const uint8_t> rdeCommand,
+ RdeCommandType type)
+{
+ if (type == RdeCommandType::RdeMultiPartReceiveResponse)
+ {
+ return multiPartReceiveResp(rdeCommand);
+ }
+ if (type == RdeCommandType::RdeOperationInitRequest)
+ {
+ return operationInitRequest(rdeCommand);
+ }
+
+ fmt::print(stderr, "Invalid command type\n");
+ return RdeDecodeStatus::RdeInvalidCommand;
+}
+
+uint32_t RdeCommandHandler::getDictionaryCount()
+{
+ return dictionaryManager.getDictionaryCount();
+}
+
+RdeDecodeStatus
+ RdeCommandHandler::operationInitRequest(std::span<const uint8_t> rdeCommand)
+{
+ const RdeOperationInitReqHeader* header =
+ reinterpret_cast<const RdeOperationInitReqHeader*>(rdeCommand.data());
+ // Check if there is a payload. If not, we are not doing anything.
+ if (!header->containsRequestPayload)
+ {
+ return RdeDecodeStatus::RdeOk;
+ }
+
+ if (header->operationType != rdeOpInitOperationUpdate)
+ {
+ fmt::print(stderr, "Operation not supported\n");
+ return RdeDecodeStatus::RdeUnsupportedOperation;
+ }
+
+ // OperationInit payload overflows are not suported.
+ if (header->sendDataTransferHandle != 0)
+ {
+ fmt::print(stderr, "Payload should fit in within the request\n");
+ return RdeDecodeStatus::RdePayloadOverflow;
+ }
+
+ auto schemaDictOrErr = dictionaryManager.getDictionary(header->resourceID);
+ if (!schemaDictOrErr)
+ {
+ fmt::print(stderr, "Schema Dictionary not found for resourceId: {}\n",
+ header->resourceID);
+ return RdeDecodeStatus::RdeNoDictionary;
+ }
+
+ auto annotationDictOrErr = dictionaryManager.getAnnotationDictionary();
+ if (!annotationDictOrErr)
+ {
+ fmt::print(stderr, "Annotation dictionary not found\n");
+ return RdeDecodeStatus::RdeNoDictionary;
+ }
+
+ BejDictionaries dictionaries = {
+ .schemaDictionary = (*schemaDictOrErr).data(),
+ .annotationDictionary = (*annotationDictOrErr).data(),
+ // We do not use the error dictionary.
+ .errorDictionary = nullptr,
+ };
+
+ // Soon after header, we have bejLocator field. Then we have the encoded
+ // data.
+ const uint8_t* encodedPldmBlock = rdeCommand.data() +
+ sizeof(RdeOperationInitReqHeader) +
+ header->operationLocatorLength;
+
+ // Decoded the data.
+ if (decoder.decode(dictionaries, std::span(encodedPldmBlock,
+ header->requestPayloadLength)) !=
+ 0)
+ {
+ fmt::print(stderr, "BEJ decoding failed.\n");
+ return RdeDecodeStatus::RdeBejDecodingError;
+ }
+
+ // Post the output.
+ if (!exStorer->publishJson(decoder.getOutput()))
+ {
+ fmt::print(stderr, "Failed to write to ExternalStorer.\n");
+ return RdeDecodeStatus::RdeExternalStorerError;
+ }
+ return RdeDecodeStatus::RdeOk;
+}
+
+RdeDecodeStatus
+ RdeCommandHandler::multiPartReceiveResp(std::span<const uint8_t> rdeCommand)
+{
+ const MultipartReceiveResHeader* header =
+ reinterpret_cast<const MultipartReceiveResHeader*>(rdeCommand.data());
+
+ // This is a hack to get the resource ID for the dictionary data. Even
+ // though nextDataTransferHandle field is supposed to be used for something
+ // else, BIOS is using it to specify the resource ID corresponding to the
+ // dictionary data.
+ uint32_t resourceId = header->nextDataTransferHandle;
+
+ // data points to the payload of the MultipartReceive.
+ const uint8_t* data = rdeCommand.data() + sizeof(MultipartReceiveResHeader);
+ RdeDecodeStatus ret = RdeDecodeStatus::RdeOk;
+
+ switch (header->transferFlag)
+ {
+ case rdeMRecFlagStart:
+ handleFlagStart(header, data, resourceId);
+ break;
+ case rdeMRecFlagMiddle:
+ ret = handleFlagMiddle(header, data, resourceId);
+ break;
+ case rdeMRecFlagEnd:
+ ret = handleFlagEnd(rdeCommand, header, data, resourceId);
+ break;
+ case rdeMRecFlagStartAndEnd:
+ ret = handleFlagStartAndEnd(rdeCommand, header, data, resourceId);
+ break;
+ default:
+ fmt::print(stderr, "Invalid transfer flag: {}\n",
+ header->transferFlag);
+ ret = RdeDecodeStatus::RdeInvalidCommand;
+ }
+
+ // If there is a failure, this assignment is not useful. So we can do it
+ // even if there is a failure.
+ prevDictResourceId = resourceId;
+ return ret;
+}
+
+void RdeCommandHandler::calcCrcTable()
+{
+ for (uint32_t i = 0; i < UINT8_MAX + 1; ++i)
+ {
+ uint32_t rem = i;
+ for (uint8_t k = 0; k < 8; ++k)
+ {
+ rem = (rem & 1) ? (rem >> 1) ^ crcDevisor : rem >> 1;
+ }
+ crcTable[i] = rem;
+ }
+}
+
+void RdeCommandHandler::updateCrc(std::span<const uint8_t> stream)
+{
+ for (uint32_t i = 0; i < stream.size_bytes(); ++i)
+ {
+ crc = crcTable[(crc ^ stream[i]) & 0xff] ^ (crc >> 8);
+ }
+}
+
+uint32_t RdeCommandHandler::finalChecksum()
+{
+ return (crc ^ 0xFFFFFFFF);
+}
+
+RdeDecodeStatus
+ RdeCommandHandler::handleCrc(std::span<const uint8_t> multiReceiveRespCmd)
+{
+ const MultipartReceiveResHeader* header =
+ reinterpret_cast<const MultipartReceiveResHeader*>(
+ multiReceiveRespCmd.data());
+ const uint8_t* checksumPtr = multiReceiveRespCmd.data() +
+ sizeof(MultipartReceiveResHeader) +
+ header->dataLengthBytes;
+ uint32_t checksum = checksumPtr[0] | (checksumPtr[1] << 8) |
+ (checksumPtr[2] << 16) | (checksumPtr[3] << 24);
+
+ if (finalChecksum() != checksum)
+ {
+ fmt::print(stderr, "Checksum failed. Ex: {} Calculated: {}\n", checksum,
+ finalChecksum());
+ dictionaryManager.invalidateDictionaries();
+ return RdeDecodeStatus::RdeInvalidChecksum;
+ }
+ return RdeDecodeStatus::RdeOk;
+}
+
+void RdeCommandHandler::handleFlagStart(const MultipartReceiveResHeader* header,
+ const uint8_t* data,
+ uint32_t resourceId)
+{
+ // This is a beginning of a dictionary. Reset CRC.
+ crc = 0xFFFFFFFF;
+ std::span dataS(data, header->dataLengthBytes);
+ dictionaryManager.startDictionaryEntry(resourceId, dataS);
+ // Start checksum calculation only for the data portion.
+ updateCrc(dataS);
+ flagState = RdeDictTransferFlagState::RdeStateStartRecvd;
+}
+
+RdeDecodeStatus
+ RdeCommandHandler::handleFlagMiddle(const MultipartReceiveResHeader* header,
+ const uint8_t* data,
+ uint32_t resourceId)
+{
+ if (flagState != RdeDictTransferFlagState::RdeStateStartRecvd)
+ {
+ fmt::print(
+ stderr,
+ "Invalid dictionary packet order. Need start before middle.\n");
+ return RdeDecodeStatus::RdeInvalidPktOrder;
+ }
+
+ std::span dataS(data, header->dataLengthBytes);
+ if (prevDictResourceId != resourceId)
+ {
+ // Start of a new dictionary. Mark previous dictionary as
+ // complete.
+ dictionaryManager.markDataComplete(prevDictResourceId);
+ dictionaryManager.startDictionaryEntry(resourceId, dataS);
+ }
+ else
+ {
+ // Not a new dictionary. Add the received data to the existing
+ // dictionary.
+ if (!dictionaryManager.addDictionaryData(resourceId, dataS))
+ {
+ fmt::print(stderr,
+ "Failed to add dictionary data: ResourceId: {}\n",
+ resourceId);
+ return RdeDecodeStatus::RdeDictionaryError;
+ }
+ }
+ // Continue checksum calculation only for the data portion.
+ updateCrc(dataS);
+ return RdeDecodeStatus::RdeOk;
+}
+
+RdeDecodeStatus
+ RdeCommandHandler::handleFlagEnd(std::span<const uint8_t> rdeCommand,
+ const MultipartReceiveResHeader* header,
+ const uint8_t* data, uint32_t resourceId)
+{
+ if (flagState != RdeDictTransferFlagState::RdeStateStartRecvd)
+ {
+ fmt::print(
+ stderr,
+ "Invalid dictionary packet order. Need start before middle.\n");
+ return RdeDecodeStatus::RdeInvalidPktOrder;
+ }
+ flagState = RdeDictTransferFlagState::RdeStateIdle;
+
+ std::span dataS(data, header->dataLengthBytes);
+ if (prevDictResourceId != resourceId)
+ {
+ // Start of a new dictionary. Mark previous dictionary as
+ // complete.
+ dictionaryManager.markDataComplete(prevDictResourceId);
+ dictionaryManager.startDictionaryEntry(resourceId, dataS);
+ }
+ else
+ {
+ if (!dictionaryManager.addDictionaryData(resourceId, dataS))
+ {
+ fmt::print(stderr,
+ "Failed to add dictionary data: ResourceId: {}\n",
+ resourceId);
+ return RdeDecodeStatus::RdeDictionaryError;
+ }
+ }
+ dictionaryManager.markDataComplete(resourceId);
+
+ // Continue checksum calculation only for the data portion. At the end of
+ // data, we will have the DataIntegrityChecksum field. So omit that when
+ // calculating checksum.
+ updateCrc(dataS);
+ auto ret = handleCrc(rdeCommand);
+ if (ret != RdeDecodeStatus::RdeOk)
+ {
+ return ret;
+ }
+ return RdeDecodeStatus::RdeStopFlagReceived;
+}
+
+RdeDecodeStatus RdeCommandHandler::handleFlagStartAndEnd(
+ std::span<const uint8_t> rdeCommand,
+ const MultipartReceiveResHeader* header, const uint8_t* data,
+ uint32_t resourceId)
+{
+ // This is a beginning of a dictionary. Reset CRC.
+ crc = 0xFFFFFFFF;
+ // This is a beginning and end of a dictionary.
+ dictionaryManager.startDictionaryEntry(
+ resourceId, std::span(data, header->dataLengthBytes));
+ dictionaryManager.markDataComplete(resourceId);
+ flagState = RdeDictTransferFlagState::RdeStateIdle;
+
+ // Do checksum calculation only for the data portion. At the end of data, we
+ // will have the DataIntegrityChecksum field. So omit that when calculating
+ // checksum.
+ updateCrc(std::span(data, header->dataLengthBytes));
+
+ auto ret = handleCrc(rdeCommand);
+ if (ret != RdeDecodeStatus::RdeOk)
+ {
+ return ret;
+ }
+ return RdeDecodeStatus::RdeStopFlagReceived;
+}
+
+} // namespace rde
+} // namespace bios_bmc_smm_error_logger