ncsi: encapsulate NC-SI commands with NCSICommand / NCSIResponse structs

... and rename Interface::sendOemCommand to Interface::sendCommand.

This provides a more clear facility to pass command and response objects
around, for future command and transport implementations.

Change-Id: I46e594ab6467ed87cfc27189c3ec4bd321726ee5
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
index 13c41f6..d8ad3b2 100644
--- a/src/ncsi_util.cpp
+++ b/src/ncsi_util.cpp
@@ -7,6 +7,8 @@
 
 #include <phosphor-logging/lg2.hpp>
 
+#include <optional>
+#include <span>
 #include <vector>
 
 namespace phosphor
@@ -16,6 +18,19 @@
 namespace ncsi
 {
 
+NCSICommand::NCSICommand(uint8_t opcode, uint8_t package,
+                         std::optional<uint8_t> channel,
+                         std::span<unsigned char> payload) :
+    opcode(opcode), package(package), channel(channel)
+{
+    this->payload.assign(payload.begin(), payload.end());
+}
+
+uint8_t NCSICommand::getChannel()
+{
+    return channel.value_or(CHANNEL_ID_NONE);
+}
+
 using CallBack = int (*)(struct nl_msg* msg, void* arg);
 
 namespace internal
@@ -33,6 +48,12 @@
     uint32_t rsvd[2];
 };
 
+struct NCSIResponsePayload
+{
+    uint16_t response;
+    uint16_t reason;
+};
+
 class NetlinkCommand
 {
   public:
@@ -203,7 +224,7 @@
 
 struct sendCallBackContext
 {
-    std::vector<unsigned char> msg;
+    NCSIResponse resp;
 };
 
 CallBack sendCallBack = [](struct nl_msg* msg, void* arg) {
@@ -237,11 +258,16 @@
         return -1;
     }
 
-    auto data_len = nla_len(tb[NCSI_ATTR_DATA]) - sizeof(NCSIPacketHeader);
-    unsigned char* data =
-        (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]) + sizeof(NCSIPacketHeader);
+    size_t data_len = nla_len(tb[NCSI_ATTR_DATA]);
+    unsigned char* data = (unsigned char*)nla_data(tb[NCSI_ATTR_DATA]);
 
-    ctx->msg.assign(data, data + data_len);
+    ctx->resp.full_payload.assign(data, data + data_len);
+
+    int rc = ctx->resp.parseFullPayload();
+    if (rc)
+    {
+        return -1;
+    }
 
     return static_cast<int>(NL_STOP);
 };
@@ -399,29 +425,27 @@
     return std::to_string(interface.ifindex);
 }
 
-std::optional<std::vector<unsigned char>>
-    Interface::sendOemCommand(int package, int channel, int operation,
-                              std::span<const unsigned char> payload)
+std::optional<NCSIResponse> Interface::sendCommand(NCSICommand& cmd)
 {
-    lg2::debug("Send OEM Command, CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, "
+    lg2::debug("Send Command, CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, "
                "INTERFACE: {INTERFACE}",
-               "CHANNEL", lg2::hex, channel, "PACKAGE", lg2::hex, package,
-               "INTERFACE", this);
+               "CHANNEL", lg2::hex, cmd.getChannel(), "PACKAGE", lg2::hex,
+               cmd.package, "INTERFACE", this);
 
     internal::sendCallBackContext ctx{};
 
-    internal::NetlinkCommand cmd(ncsi_nl_commands::NCSI_CMD_SEND_CMD, operation,
-                                 payload);
+    internal::NetlinkCommand nl_cmd(ncsi_nl_commands::NCSI_CMD_SEND_CMD,
+                                    cmd.opcode, cmd.payload);
 
-    int rc = internal::applyCmd(*this, cmd, package, channel, NONE,
-                                internal::sendCallBack, &ctx);
+    int rc = internal::applyCmd(*this, nl_cmd, cmd.package, cmd.getChannel(),
+                                NONE, internal::sendCallBack, &ctx);
 
     if (rc < 0)
     {
         return {};
     }
 
-    return ctx.msg;
+    return ctx.resp;
 }
 
 int Interface::setChannel(int package, int channel)
@@ -497,6 +521,41 @@
     return internal::applyCmd(*this, cmd);
 }
 
+int NCSIResponse::parseFullPayload()
+{
+    if (this->full_payload.size() < sizeof(internal::NCSIPacketHeader) +
+                                        sizeof(internal::NCSIResponsePayload))
+    {
+        lg2::error("Response: Not enough data for a response message");
+        return -1;
+    }
+
+    internal::NCSIPacketHeader* respHeader =
+        reinterpret_cast<decltype(respHeader)>(this->full_payload.data());
+
+    unsigned int payloadLen = ntohs(respHeader->length & htons(0x0fff));
+    /* we have determined that the payload size is larger than *respHeader,
+     * so cannot underflow here */
+    if (payloadLen > this->full_payload.size() - sizeof(*respHeader))
+    {
+        lg2::error("Invalid header length {HDRLEN} (vs {LEN}) in response",
+                   "HDRLEN", payloadLen, "LEN",
+                   this->full_payload.size() - sizeof(*respHeader));
+        return -1;
+    }
+
+    this->opcode = respHeader->type;
+    this->payload =
+        std::span(this->full_payload.begin() + sizeof(*respHeader), payloadLen);
+
+    internal::NCSIResponsePayload* respPayload =
+        reinterpret_cast<decltype(respPayload)>(this->payload.data());
+    this->response = ntohs(respPayload->response);
+    this->reason = ntohs(respPayload->reason);
+
+    return 0;
+}
+
 } // namespace ncsi
 } // namespace network
 } // namespace phosphor