libpldmresponder: Implement GetFRURecordByOption

Implement handler for GetFRURecordByOption command

Signed-off-by: John Wang <wangzqbj@inspur.com>
Change-Id: If2bc90872b2521f1771aa800de6fbce569a5b339
diff --git a/libpldm/fru.c b/libpldm/fru.c
index 64c1aba..207d4de 100644
--- a/libpldm/fru.c
+++ b/libpldm/fru.c
@@ -201,6 +201,73 @@
 	return PLDM_SUCCESS;
 }
 
+static bool is_table_end(const struct pldm_fru_record_data_format *p,
+			 const void *table, size_t table_size)
+{
+	return p ==
+	       (const struct pldm_fru_record_data_format *)((uint8_t *)table +
+							    table_size);
+}
+
+void get_fru_record_by_option(const uint8_t *table, size_t table_size,
+			      uint8_t *record_table, size_t *record_size,
+			      uint16_t rsi, uint8_t rt, uint8_t ft)
+{
+	const struct pldm_fru_record_data_format *record_data_src =
+	    (const struct pldm_fru_record_data_format *)table;
+	struct pldm_fru_record_data_format *record_data_dest;
+	int count = 0;
+
+	const struct pldm_fru_record_tlv *tlv;
+	size_t len;
+	uint8_t *pos = record_table;
+
+	while (!is_table_end(record_data_src, table, table_size)) {
+		if ((record_data_src->record_set_id != htole16(rsi) &&
+		     rsi != 0) ||
+		    (record_data_src->record_type != rt && rt != 0)) {
+			tlv = record_data_src->tlvs;
+			for (int i = 0; i < record_data_src->num_fru_fields;
+			     i++) {
+				len = sizeof(*tlv) - 1 + tlv->length;
+				tlv = (const struct pldm_fru_record_tlv
+					   *)((char *)tlv + len);
+			}
+			record_data_src =
+			    (const struct pldm_fru_record_data_format *)(tlv);
+			continue;
+		}
+
+		len = sizeof(struct pldm_fru_record_data_format) -
+		      sizeof(struct pldm_fru_record_tlv);
+
+		assert(pos - record_table + len < *record_size);
+		memcpy(pos, record_data_src, len);
+
+		record_data_dest = (struct pldm_fru_record_data_format *)pos;
+		pos += len;
+
+		tlv = record_data_src->tlvs;
+		count = 0;
+		for (int i = 0; i < record_data_src->num_fru_fields; i++) {
+			len = sizeof(*tlv) - 1 + tlv->length;
+			if (tlv->type == ft || ft == 0) {
+				assert(pos - record_table + len < *record_size);
+				memcpy(pos, tlv, len);
+				pos += len;
+				count++;
+			}
+			tlv = (const struct pldm_fru_record_tlv *)((char *)tlv +
+								   len);
+		}
+		record_data_dest->num_fru_fields = count;
+		record_data_src =
+		    (const struct pldm_fru_record_data_format *)(tlv);
+	}
+
+	*record_size = pos - record_table;
+}
+
 int encode_get_fru_record_by_option_req(
     uint8_t instance_id, uint32_t data_transfer_handle,
     uint16_t fru_table_handle, uint16_t record_set_identifier,
diff --git a/libpldm/fru.h b/libpldm/fru.h
index 3ec9e43..3739937 100644
--- a/libpldm/fru.h
+++ b/libpldm/fru.h
@@ -20,6 +20,10 @@
 
 #define FRU_TABLE_CHECKSUM_SIZE 4
 
+enum pldm_fru_completion_codes {
+	PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE = 0x85,
+};
+
 /** @brief PLDM FRU commands
  */
 enum pldm_fru_commands {
@@ -407,6 +411,19 @@
     uint32_t *next_transfer_handle, uint8_t *transfer_flag,
     struct variable_field *fru_structure_data);
 
+/** @brief Get FRU Record Table By Option
+ *  @param[in] table - The source fru record table
+ *  @param[in] table_size - Size of the source fru record table
+ *  @param[out] record_table - Fru table fetched based on the input option
+ *  @param[in/out] record_size - Size of the table fetched by fru record option
+ *  @param[in] rsi - FRU record set identifier
+ *  @param[in] rt - FRU record type
+ *  @param[in] ft - FRU field type
+ */
+void get_fru_record_by_option(const uint8_t *table, size_t table_size,
+			      uint8_t *record_table, size_t *record_size,
+			      uint16_t rsi, uint8_t rt, uint8_t ft);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libpldmresponder/fru.cpp b/libpldmresponder/fru.cpp
index ce470ab..cb7d5f7 100644
--- a/libpldmresponder/fru.cpp
+++ b/libpldmresponder/fru.cpp
@@ -204,6 +204,42 @@
                 iter);
 }
 
+int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData,
+                                  uint16_t /* fruTableHandle */,
+                                  uint16_t recordSetIdentifer,
+                                  uint8_t recordType, uint8_t fieldType)
+{
+    // FRU table is built lazily, build if not done.
+    buildFRUTable();
+
+    /* 7 is sizeof(checksum,4) + padBytesMax(3)
+     * We can not know size of the record table got by options in advance, but
+     * it must be less than the source table. So it's safe to use sizeof the
+     * source table + 7 as the buffer length
+     */
+    size_t recordTableSize = table.size() - padBytes + 7;
+    fruData.resize(recordTableSize, 0);
+
+    get_fru_record_by_option(table.data(), table.size() - padBytes,
+                             fruData.data(), &recordTableSize,
+                             recordSetIdentifer, recordType, fieldType);
+
+    if (recordTableSize == 0)
+    {
+        return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE;
+    }
+
+    auto pads = utils::getNumPadBytes(recordTableSize);
+    auto sum = crc32(fruData.data(), recordTableSize + pads);
+
+    auto iter = fruData.begin() + recordTableSize + pads;
+    std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
+                iter);
+    fruData.resize(recordTableSize + pads + sizeof(sum));
+
+    return PLDM_SUCCESS;
+}
+
 namespace fru
 {
 
@@ -262,6 +298,57 @@
     return response;
 }
 
+Response Handler::getFRURecordByOption(const pldm_msg* request,
+                                       size_t payloadLength)
+{
+    if (payloadLength != sizeof(pldm_get_fru_record_by_option_req))
+    {
+        return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
+    }
+
+    uint32_t retDataTransferHandle{};
+    uint16_t retFruTableHandle{};
+    uint16_t retRecordSetIdentifier{};
+    uint8_t retRecordType{};
+    uint8_t retFieldType{};
+    uint8_t retTransferOpFlag{};
+
+    auto rc = decode_get_fru_record_by_option_req(
+        request, payloadLength, &retDataTransferHandle, &retFruTableHandle,
+        &retRecordSetIdentifier, &retRecordType, &retFieldType,
+        &retTransferOpFlag);
+
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    std::vector<uint8_t> fruData;
+    rc = impl.getFRURecordByOption(fruData, retFruTableHandle,
+                                   retRecordSetIdentifier, retRecordType,
+                                   retFieldType);
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    auto respPayloadLength =
+        PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + fruData.size();
+    Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    rc = encode_get_fru_record_by_option_resp(
+        request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END,
+        fruData.data(), fruData.size(), responsePtr, respPayloadLength);
+
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    return response;
+}
+
 } // namespace fru
 
 } // namespace responder
diff --git a/libpldmresponder/fru.hpp b/libpldmresponder/fru.hpp
index 2597dc4..b19519c 100644
--- a/libpldmresponder/fru.hpp
+++ b/libpldmresponder/fru.hpp
@@ -106,6 +106,18 @@
      */
     void getFRUTable(Response& response);
 
+    /** @brief Get FRU Record Table By Option
+     *  @param[out] response - Populate response with the FRU table got by
+     *                         options
+     *  @param[in] fruTableHandle - The fru table handle
+     *  @param[in] recordSetIdentifer - The record set identifier
+     *  @param[in] recordType - The record type
+     *  @param[in] fieldType - The field type
+     */
+    int getFRURecordByOption(Response& response, uint16_t fruTableHandle,
+                             uint16_t recordSetIdentifer, uint8_t recordType,
+                             uint8_t fieldType);
+
     /** @brief FRU table is built by processing the D-Bus inventory namespace
      *         based on the config files for FRU. The table is populated based
      *         on the isBuilt flag.
@@ -166,6 +178,11 @@
                              return this->getFRURecordTable(request,
                                                             payloadLength);
                          });
+        handlers.emplace(PLDM_GET_FRU_RECORD_BY_OPTION,
+                         [this](const pldm_msg* request, size_t payloadLength) {
+                             return this->getFRURecordByOption(request,
+                                                               payloadLength);
+                         });
     }
 
     /** @brief Handler for Get FRURecordTableMetadata
@@ -195,6 +212,16 @@
         impl.buildFRUTable();
     }
 
+    /** @brief Handler for GetFRURecordByOption
+     *
+     *  @param[in] request - Request message payload
+     *  @param[in] payloadLength - Request payload length
+     *
+     *  @return PLDM response message
+     */
+    Response getFRURecordByOption(const pldm_msg* request,
+                                  size_t payloadLength);
+
   private:
     FruImpl impl;
 };