pldmtool: Implement GetFRURecordByOption

Implement GetFRURecordByOption

Tested:

$pldmtool fru GetFRURecordByOption -i 0 -r 0 -f 0
...
FRU Record Set Identifier: 1
FRU Record Type: 1(General)
Number of FRU fields: 5
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 3(Part Number)
	FRU Field Length: 7
	FRU Field Value: 02CY415
	FRU Field Type: 4(Serial Number)
	FRU Field Length: 12
	FRU Field Value: YA1934319126
	FRU Field Type: 5(Manufacturer)
	FRU Field Length: 3
	FRU Field Value: IBM
	FRU Field Type: 8(Name)
	FRU Field Length: 16
	FRU Field Value: PROCESSOR MODULE
	FRU Field Type: 10(Version)
	FRU Field Length: 2
	FRU Field Value: 22
FRU Record Set Identifier: 2
FRU Record Type: 1(General)
Number of FRU fields: 4
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 2(Model)
	FRU Field Length: 20
	FRU Field Value: M393A2K43BB1-CTD
	FRU Field Type: 4(Serial Number)
	FRU Field Length: 10
	FRU Field Value: 0x367f4d92
	FRU Field Type: 5(Manufacturer)
	FRU Field Length: 19
	FRU Field Value: Samsung Electronics
	FRU Field Type: 8(Name)
	FRU Field Length: 32
	FRU Field Value: DDR4-2666 16GiB 64-bit ECC RDIMM
FRU Record Set Identifier: 3
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 8(Name)
	FRU Field Length: 12
	FRU Field Value: powersupply0
FRU Record Set Identifier: 4
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 8(Name)
	FRU Field Length: 12
	FRU Field Value: powersupply1

$pldmtool fru GetFRURecordByOption -i 0 -r 0 -f 8
FRU Record Set Identifier: 1
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 8(Name)
	FRU Field Length: 16
	FRU Field Value: PROCESSOR MODULE
FRU Record Set Identifier: 2
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 8(Name)
	FRU Field Length: 32
	FRU Field Value: DDR4-2666 16GiB 64-bit ECC RDIMM
FRU Record Set Identifier: 3
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 8(Name)
	FRU Field Length: 12
	FRU Field Value: powersupply0
FRU Record Set Identifier: 4
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 8(Name)
	FRU Field Length: 12
	FRU Field Value: powersupply1

$pldmtool fru GetFRURecordByOption -i 0 -r 0 -f 4
FRU Record Set Identifier: 1
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 4(Serial Number)
	FRU Field Length: 12
	FRU Field Value: YA1934319126
FRU Record Set Identifier: 2
FRU Record Type: 1(General)
Number of FRU fields: 1
Encoding Type for FRU fields: 1(ASCII)
	FRU Field Type: 4(Serial Number)
	FRU Field Length: 10
	FRU Field Value: 0x367f4d92
FRU Record Set Identifier: 3
FRU Record Type: 1(General)
Number of FRU fields: 0
Encoding Type for FRU fields: 1(ASCII)
FRU Record Set Identifier: 4
FRU Record Type: 1(General)
Number of FRU fields: 0
Encoding Type for FRU fields: 1(ASCII)

Signed-off-by: John Wang <wangzqbj@inspur.com>
Change-Id: Ia4bf6f938061dad69ab243704cfa9ac8da5fc80e
diff --git a/pldmtool/pldm_fru_cmd.cpp b/pldmtool/pldm_fru_cmd.cpp
index 2075a48..8442af1 100644
--- a/pldmtool/pldm_fru_cmd.cpp
+++ b/pldmtool/pldm_fru_cmd.cpp
@@ -2,6 +2,11 @@
 
 #include "pldm_cmd_helper.hpp"
 
+#include <endian.h>
+
+#include <functional>
+#include <tuple>
+
 namespace pldmtool
 {
 
@@ -74,6 +79,239 @@
     }
 };
 
+class FRUTablePrint
+{
+  public:
+    explicit FRUTablePrint(const uint8_t* table, size_t table_size) :
+        table(table), table_size(table_size)
+    {}
+
+    void print()
+    {
+        auto p = table;
+        while (!isTableEnd(p))
+        {
+            auto record =
+                reinterpret_cast<const pldm_fru_record_data_format*>(p);
+            std::cout << "FRU Record Set Identifier: "
+                      << (int)le16toh(record->record_set_id) << std::endl;
+            std::cout << "FRU Record Type: "
+                      << typeToString(fruRecordTypes, record->record_type)
+                      << std::endl;
+            std::cout << "Number of FRU fields: " << (int)record->num_fru_fields
+                      << std::endl;
+            std::cout << "Encoding Type for FRU fields: "
+                      << typeToString(fruEncodingType, record->encoding_type)
+                      << std::endl;
+
+            auto isGeneralRec = true;
+            if (record->record_type != PLDM_FRU_RECORD_TYPE_GENERAL)
+            {
+                isGeneralRec = false;
+            }
+
+            p += sizeof(pldm_fru_record_data_format) -
+                 sizeof(pldm_fru_record_tlv);
+            for (int i = 0; i < record->num_fru_fields; i++)
+            {
+                auto tlv = reinterpret_cast<const pldm_fru_record_tlv*>(p);
+                if (isGeneralRec)
+                {
+                    fruFieldPrint(record->record_type, tlv->type, tlv->length,
+                                  tlv->value);
+                }
+                p += sizeof(pldm_fru_record_tlv) - 1 + tlv->length;
+            }
+        }
+    }
+
+  private:
+    const uint8_t* table;
+    size_t table_size;
+
+    bool isTableEnd(const uint8_t* p)
+    {
+        auto offset = p - table;
+        return (table_size - offset) <= 7;
+    }
+
+    static inline const std::map<uint8_t, const char*> fruEncodingType{
+        {PLDM_FRU_ENCODING_UNSPECIFIED, "Unspecified"},
+        {PLDM_FRU_ENCODING_ASCII, "ASCII"},
+        {PLDM_FRU_ENCODING_UTF8, "UTF8"},
+        {PLDM_FRU_ENCODING_UTF16, "UTF16"},
+        {PLDM_FRU_ENCODING_UTF16LE, "UTF16LE"},
+        {PLDM_FRU_ENCODING_UTF16BE, "UTF16BE"}};
+
+    static inline const std::map<uint8_t, const char*> fruRecordTypes{
+        {PLDM_FRU_RECORD_TYPE_GENERAL, "General"},
+        {PLDM_FRU_RECORD_TYPE_OEM, "OEM"}};
+
+    std::string typeToString(std::map<uint8_t, const char*> typeMap,
+                             uint8_t type)
+    {
+        auto typeString = std::to_string(type);
+        try
+        {
+            return typeString + "(" + typeMap.at(type) + ")";
+        }
+        catch (const std::out_of_range& e)
+        {
+            return typeString;
+        }
+    }
+
+    using FruFieldParser =
+        std::function<std::string(const uint8_t* value, uint8_t length)>;
+
+    using FieldType = uint8_t;
+    using RecordType = uint8_t;
+    using FieldName = std::string;
+    using FruFieldTypes =
+        std::map<FieldType, std::tuple<FieldName, FruFieldParser>>;
+
+    static std::string fruFieldParserString(const uint8_t* value,
+                                            uint8_t length)
+    {
+        return std::string(reinterpret_cast<const char*>(value), length);
+    }
+
+    static std::string fruFieldParserTimestamp(const uint8_t*, uint8_t)
+    {
+        return std::string("TODO");
+    }
+
+    static std::string fruFieldParserU32(const uint8_t* value, uint8_t length)
+    {
+        assert(length == 4);
+        uint32_t v;
+        std::memcpy(&v, value, length);
+        return std::to_string(le32toh(v));
+    }
+
+    static inline const FruFieldTypes fruGeneralFieldTypes = {
+        {PLDM_FRU_FIELD_TYPE_CHASSIS, {"Chassis", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_MODEL, {"Model", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_PN, {"Part Number", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_SN, {"Serial Number", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_MANUFAC, {"Manufacturer", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_MANUFAC_DATE,
+         {"Manufacture Date", fruFieldParserTimestamp}},
+        {PLDM_FRU_FIELD_TYPE_VENDOR, {"Vendor", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_NAME, {"Name", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_SKU, {"SKU", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_VERSION, {"Version", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_ASSET_TAG, {"Asset Tag", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_DESC, {"Description", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_EC_LVL,
+         {"Engineering Change Level", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_OTHER,
+         {"Other Information", fruFieldParserString}},
+        {PLDM_FRU_FIELD_TYPE_IANA, {"Vendor IANA", fruFieldParserU32}},
+    };
+
+    static inline const FruFieldTypes fruOEMFieldTypes = {
+        {1, {"Vendor IANA", fruFieldParserU32}},
+
+    };
+
+    static inline const std::map<RecordType, FruFieldTypes> fruFieldTypes{
+        {PLDM_FRU_RECORD_TYPE_GENERAL, fruGeneralFieldTypes},
+        {PLDM_FRU_RECORD_TYPE_OEM, fruOEMFieldTypes}};
+
+    void fruFieldPrint(uint8_t recordType, uint8_t type, uint8_t length,
+                       const uint8_t* value)
+    {
+        auto& [typeString, parser] = fruFieldTypes.at(recordType).at(type);
+
+        std::cout << "\tFRU Field Type: " << typeString << std::endl;
+        std::cout << "\tFRU Field Length: " << (int)(length) << std::endl;
+        std::cout << "\tFRU Field Value: " << parser(value, length)
+                  << std::endl;
+    }
+};
+
+class GetFRURecordByOption : public CommandInterface
+{
+  public:
+    ~GetFRURecordByOption() = default;
+    GetFRURecordByOption() = delete;
+    GetFRURecordByOption(const GetFRURecordByOption&) = delete;
+    GetFRURecordByOption(GetFruRecordTableMetadata&&) = delete;
+    GetFRURecordByOption& operator=(const GetFRURecordByOption&) = delete;
+    GetFRURecordByOption& operator=(GetFRURecordByOption&&) = delete;
+
+    explicit GetFRURecordByOption(const char* type, const char* name,
+                                  CLI::App* app) :
+        CommandInterface(type, name, app)
+    {
+        app->add_option("-i, --identifier", recordSetIdentifier,
+                        "Record Set Identifier\n"
+                        "Possible values: {All record sets = 0, Specific "
+                        "record set = 1 – 65535}")
+            ->required();
+        app->add_option("-r, --record", recordType,
+                        "Record Type\n"
+                        "Possible values: {All record types = 0, Specific "
+                        "record types = 1 – 255}")
+            ->required();
+        app->add_option("-f, --field", fieldType,
+                        "Field Type\n"
+                        "Possible values: {All record field types = 0, "
+                        "Specific field types = 1 – 15}")
+            ->required();
+    }
+
+    std::pair<int, std::vector<uint8_t>> createRequestMsg() override
+    {
+        if (fieldType != 0 && recordType == 0)
+        {
+            throw std::invalid_argument("if field type is non-zero, the record "
+                                        "type shall also be non-zero");
+        }
+
+        auto payloadLength = sizeof(pldm_get_fru_record_by_option_req);
+
+        std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) + payloadLength,
+                                        0);
+        auto reqMsg = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+        auto rc = encode_get_fru_record_by_option_req(
+            instanceId, 0 /* DataTransferHandle */, 0 /* FRUTableHandle */,
+            recordSetIdentifier, recordType, fieldType, PLDM_GET_FIRSTPART,
+            reqMsg, payloadLength);
+
+        return {rc, requestMsg};
+    }
+
+    void parseResponseMsg(pldm_msg* responsePtr, size_t payloadLength) override
+    {
+        uint8_t cc;
+        uint32_t dataTransferHandle;
+        uint8_t transferFlag;
+        variable_field fruData;
+
+        auto rc = decode_get_fru_record_by_option_resp(
+            responsePtr, payloadLength, &cc, &dataTransferHandle, &transferFlag,
+            &fruData);
+
+        if (rc != PLDM_SUCCESS || cc != PLDM_SUCCESS)
+        {
+            std::cerr << "Response Message Error: "
+                      << "rc=" << rc << ",cc=" << (int)cc << std::endl;
+            return;
+        }
+
+        FRUTablePrint tablePrint(fruData.ptr, fruData.length);
+        tablePrint.print();
+    }
+
+  private:
+    uint16_t recordSetIdentifier;
+    uint8_t recordType;
+    uint8_t fieldType;
+};
+
 void registerCommand(CLI::App& app)
 {
     auto fru = app.add_subcommand("fru", "FRU type command");
@@ -82,6 +320,11 @@
         "GetFruRecordTableMetadata", "get FRU record table metadata");
     commands.push_back(std::make_unique<GetFruRecordTableMetadata>(
         "fru", "GetFruRecordTableMetadata", getFruRecordTableMetadata));
+
+    auto getFRURecordByOption =
+        fru->add_subcommand("GetFRURecordByOption", "get FRU Record By Option");
+    commands.push_back(std::make_unique<GetFRURecordByOption>(
+        "fru", "GetFRURecordByOption", getFRURecordByOption));
 }
 
 } // namespace fru