Make Send Command feature more flexible

The sendOemCommand command, used to send NCSI_CMD_SEND_CMD payloads,
is hardcoded to only send one command.

Update the sendOemCommand function to allow the sub-operation to be
passed as a command line argument. This is done by prepending the
sub-operation byte to the front of the Send Cmd payload.

Doing this allows sub-operations without any payload bytes to be
called. For example "-o 0a", where the sub-operation for Send Cmd is
the 0x0a value.

Tested:
Sent 'ncsi-netlink -x 3 -p 0 -c 0 -o 50000001572100' and confirmed
the 0x50 byte worked the same way as the original hard-coded value.
Sent 'ncsi-netlink -x 3 -p 0 -c 0 -o 0a' and confirmed the 0x0a
sub-operation functioned on the submitters SUT.

Change-Id: I20f093fd8296f549fce03dc5729b8e5fedcab313
Signed-off-by: Johnathan Mantey <johnathanx.mantey@intel.com>
diff --git a/src/argument.cpp b/src/argument.cpp
index 0c49b11..6bc40bc 100644
--- a/src/argument.cpp
+++ b/src/argument.cpp
@@ -66,18 +66,29 @@
 void ArgumentParser::usage(char** argv)
 {
     std::cerr << "Usage: " << argv[0] << " [options]\n";
-    std::cerr << "Options:\n";
-    std::cerr << "    --help            Print this menu.\n";
-    std::cerr << "    --info=<info>     Retrieve info about NCSI topology.\n";
-    std::cerr << "    --set=<set>       Set a specific package/channel.\n";
     std::cerr
-        << "    --clear=<clear>   Clear all the settings on the interface.\n";
-    std::cerr
-        << "    --oem-payload=<hex data> Send an OEM command with payload.\n";
-    std::cerr << "    --package=<package>  Specify a package.\n";
-    std::cerr << "    --channel=<channel> Specify a channel.\n";
-    std::cerr << "    --index=<device index> Specify device ifindex.\n";
-    std::cerr << std::flush;
+        << "Options:\n"
+           "    --help | -h       Print this menu.\n"
+           "    --index=<device index> | -x <device index> Specify device ifindex.\n"
+           "    --package=<package> | -p <package> Specify a package.\n"
+           "    --channel=<channel> | -c <channel> Specify a channel.\n"
+           "    --info  | -i      Retrieve info about NCSI topology.\n"
+           "    --set   | -s      Set a specific package/channel.\n"
+           "    --clear | -r      Clear all the settings on the interface.\n"
+           "    --oem-payload=<hex data...> | -o <hex data...> Send an OEM command with payload.\n"
+           "\n"
+           "Example commands:\n"
+           "    1) Retrieve topology information:\n"
+           "         ncsi-netlink -x 3 -p 0 -i\n"
+           "    2) Set preferred package\n"
+           "         ncsi-netlink -x 3 -p 0 -s\n"
+           "    3) Set preferred channel\n"
+           "         ncsi-netlink -x 3 -p 0 -c 1 -s\n"
+           "    4) Clear preferred channel\n"
+           "         ncsi-netlink -x 3 -p 0 -r\n"
+           "    5) Send NCSI Command\n"
+           "         ncsi-netlink -x 3 -p 0 -c 0 -o 50000001572100\n"
+           "\n";
 }
 
 const option ArgumentParser::options[] = {
@@ -92,7 +103,7 @@
     {0, 0, 0, 0},
 };
 
-const char* ArgumentParser::optionStr = "i:s:r:o:p:c:x:h?";
+const char* ArgumentParser::optionStr = "irsx:o:p:c:h?";
 
 const std::string ArgumentParser::trueString = "true";
 const std::string ArgumentParser::emptyString = "";
diff --git a/src/ncsi_netlink_main.cpp b/src/ncsi_netlink_main.cpp
index 5f65777..7808ef7 100644
--- a/src/ncsi_netlink_main.cpp
+++ b/src/ncsi_netlink_main.cpp
@@ -37,6 +37,7 @@
     int packageInt{};
     int channelInt{};
     int indexInt{};
+    int operationInt{DEFAULT_VALUE};
 
     // Parse out interface argument.
     auto ifIndex = (options)["index"];
@@ -97,7 +98,11 @@
             exitWithError("Payload invalid: specify two hex digits per byte.",
                           argv);
 
-        // Parse the payload string (e.g. "000001572100") to byte data
+        // Parse the payload string (e.g. "50000001572100") to byte data
+        // The first two characters (i.e. "50") represent the Send Cmd Operation
+        // All remaining pairs, interpreted in hex radix, represent the command
+        // payload
+        int sendCmdSelect{};
         for (unsigned int i = 1; i < payloadStr.size(); i += 2)
         {
             byte[0] = payloadStr[i - 1];
@@ -105,15 +110,23 @@
 
             try
             {
-                payload.push_back(stoi(byte, nullptr, 16));
+                sendCmdSelect = stoi(byte, nullptr, 16);
             }
             catch (const std::exception& e)
             {
                 exitWithError("Payload invalid.", argv);
             }
+            if (i == 1)
+            {
+                operationInt = sendCmdSelect;
+            }
+            else
+            {
+                payload.push_back(sendCmdSelect);
+            }
         }
 
-        if (payload.empty())
+        if (operationInt == DEFAULT_VALUE)
         {
             exitWithError("No payload specified.", argv);
         }
@@ -124,7 +137,7 @@
         }
 
         return ncsi::sendOemCommand(
-            indexInt, packageInt, channelInt,
+            indexInt, packageInt, channelInt, operationInt,
             std::span<const unsigned char>(payload.begin(), payload.end()));
     }
     else if ((options)["set"] == "true")
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
index c739d6f..826d7de 100644
--- a/src/ncsi_util.cpp
+++ b/src/ncsi_util.cpp
@@ -30,7 +30,7 @@
         return ret;
     }
     stdplus::IntToStr<16, uint8_t> its;
-    auto oit = ret.append(c.size() * 3 - 1);
+    auto oit = ret.append(c.size() * 3);
     auto cit = c.begin();
     oit = its(oit, *cit++, 2);
     for (; cit != c.end(); ++cit)
@@ -38,6 +38,7 @@
         *oit++ = ' ';
         oit = its(oit, *cit, 2);
     }
+    *oit = 0;
     return ret;
 }
 
@@ -66,14 +67,14 @@
     Command(Command&&) = default;
     Command& operator=(Command&&) = default;
     Command(
-        int c, int nc = DEFAULT_VALUE,
+        int ncsiCmd, int operation = DEFAULT_VALUE,
         std::span<const unsigned char> p = std::span<const unsigned char>()) :
-        cmd(c),
-        ncsi_cmd(nc), payload(p)
+        ncsi_cmd(ncsiCmd),
+        operation(operation), payload(p)
     {}
 
-    int cmd;
     int ncsi_cmd;
+    int operation;
     std::span<const unsigned char> payload;
 };
 
@@ -300,11 +301,12 @@
         return -ENOMEM;
     }
 
-    auto msgHdr = genlmsg_put(msg.get(), 0, 0, driverID, 0, flags, cmd.cmd, 0);
+    auto msgHdr = genlmsg_put(msg.get(), NL_AUTO_PORT, NL_AUTO_SEQ, driverID, 0,
+                              flags, cmd.ncsi_cmd, 0);
     if (!msgHdr)
     {
         lg2::error("Unable to add the netlink headers , COMMAND : {COMMAND}",
-                   "COMMAND", cmd.cmd);
+                   "COMMAND", cmd.ncsi_cmd);
         return -ENOMEM;
     }
 
@@ -343,7 +345,7 @@
         return ret;
     }
 
-    if (cmd.ncsi_cmd != DEFAULT_VALUE)
+    if (cmd.operation != DEFAULT_VALUE)
     {
         std::vector<unsigned char> pl(sizeof(NCSIPacketHeader) +
                                       cmd.payload.size());
@@ -352,7 +354,7 @@
         std::copy(cmd.payload.begin(), cmd.payload.end(),
                   pl.begin() + sizeof(NCSIPacketHeader));
 
-        hdr->type = cmd.ncsi_cmd;
+        hdr->type = cmd.operation;
         hdr->length = htons(cmd.payload.size());
 
         ret = nla_put(msg.get(), ncsi_nl_attrs::NCSI_ATTR_DATA, pl.size(),
@@ -398,11 +400,9 @@
 
 } // namespace internal
 
-int sendOemCommand(int ifindex, int package, int channel,
+int sendOemCommand(int ifindex, int package, int channel, int operation,
                    std::span<const unsigned char> payload)
 {
-    constexpr auto cmd = 0x50;
-
     lg2::debug("Send OEM Command, CHANNEL : {CHANNEL} , PACKAGE : {PACKAGE}, "
                "INTERFACE_INDEX: {INTERFACE_INDEX}",
                "CHANNEL", lg2::hex, channel, "PACKAGE", lg2::hex, package,
@@ -414,7 +414,8 @@
 
     return internal::applyCmd(
         ifindex,
-        internal::Command(ncsi_nl_commands::NCSI_CMD_SEND_CMD, cmd, payload),
+        internal::Command(ncsi_nl_commands::NCSI_CMD_SEND_CMD, operation,
+                          payload),
         package, channel, NONE, internal::sendCallBack);
 }
 
diff --git a/src/ncsi_util.hpp b/src/ncsi_util.hpp
index eaa076d..750f0f1 100644
--- a/src/ncsi_util.hpp
+++ b/src/ncsi_util.hpp
@@ -20,10 +20,11 @@
  * @param[in] ifindex - Interface Index.
  * @param[in] package - NCSI Package.
  * @param[in] channel - Channel number with in the package.
+ * @param[in] opcode  - NCSI Send Command sub-operation
  * @param[in] payload - OEM data to send.
  * @returns 0 on success and negative value for failure.
  */
-int sendOemCommand(int ifindex, int package, int channel,
+int sendOemCommand(int ifindex, int package, int channel, int opcode,
                    std::span<const unsigned char> payload);
 
 /* @brief  This function will ask underlying NCSI driver