blob: 07a8d7e3938dbc17fda8ccdc8476f2d51a0da6e3 [file] [log] [blame]
/**
* Copyright © 2018 IBM Corporation
* Copyright © 2024 Code Construct
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ncsi_util.hpp"
#include <assert.h>
#include <getopt.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <phosphor-logging/lg2.hpp>
#include <stdplus/numeric/str.hpp>
#include <stdplus/str/buf.hpp>
#include <stdplus/str/conv.hpp>
#include <climits>
#include <iostream>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
using namespace phosphor::network::ncsi;
struct GlobalOptions
{
std::unique_ptr<Interface> interface;
unsigned int package;
std::optional<unsigned int> channel;
};
const struct option options[] = {
{"package", required_argument, NULL, 'p'},
{"channel", required_argument, NULL, 'c'},
{"interface", required_argument, NULL, 'i'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0},
};
static void print_usage(const char* progname)
{
// clang-format off
std::cerr
<< "Usage:\n"
" " << progname << " <options> raw TYPE [PAYLOAD...]\n"
" " << progname << " <options> oem [PAYLOAD...]\n"
"\n"
"Global options:\n"
" --interface IFACE, -i Specify net device by ifindex.\n"
" --package PACKAGE, -p Specify a package.\n"
" --channel CHANNEL, -c Specify a channel.\n"
"\n"
"Both --interface/-i and --package/-p are required.\n"
"\n"
"Subcommands:\n"
"\n"
"raw TYPE [PAYLOAD...]\n"
" Send a single command using raw type/payload data.\n"
" TYPE NC-SI command type, in hex\n"
" PAYLOAD Command payload bytes, as hex\n"
"\n"
"oem PAYLOAD\n"
" Send a single OEM command (type 0x50).\n"
" PAYLOAD Command payload bytes, as hex\n";
// clang-format on
}
static std::optional<unsigned int>
parseUnsigned(const char* str, const char* label)
{
try
{
unsigned long tmp = std::stoul(str, NULL, 16);
if (tmp <= UINT_MAX)
return tmp;
}
catch (const std::exception& e)
{}
std::cerr << "Invalid " << label << " argument '" << str << "'\n";
return {};
}
static std::optional<std::vector<unsigned char>>
parsePayload(int argc, const char* const argv[])
{
/* we have already checked that there are sufficient args in callers */
assert(argc >= 1);
std::vector<unsigned char> payload;
/* we support two formats of payload - all as one argument:
* 00010c202f
*
* or single bytes in separate arguments:
* 00 01 0c 20 2f
*
* both are assumed as entirely hex, but the latter format does not
* need to be exactly two chars per byte:
* 0 1 c 20 2f
*/
size_t len0 = strlen(argv[0]);
if (argc == 1 && len0 > 2)
{
/* single argument format, parse as multiple bytes */
if (len0 % 2 != 0)
{
std::cerr << "Invalid payload length " << len0
<< " (must be a multiple of 2 chars)\n";
return {};
}
std::string str(argv[0]);
std::string_view sv(str);
for (unsigned int i = 0; i < sv.size(); i += 2)
{
unsigned char byte;
auto begin = sv.data() + i;
auto end = begin + 2;
auto [next, err] = std::from_chars(begin, end, byte, 16);
if (err != std::errc() || next != end)
{
std::cerr << "Invalid payload string\n";
return {};
}
payload.push_back(byte);
}
}
else
{
/* multiple payload arguments, each is a separate hex byte */
for (int i = 0; i < argc; i++)
{
unsigned char byte;
auto begin = argv[i];
auto end = begin + strlen(begin);
auto [next, err] = std::from_chars(begin, end, byte, 16);
if (err != std::errc() || next != end)
{
std::cerr << "Invalid payload argument '" << begin << "'\n";
return {};
}
payload.push_back(byte);
}
}
return payload;
}
static std::optional<std::tuple<GlobalOptions, int>>
parseGlobalOptions(int argc, char* const* argv)
{
std::optional<unsigned int> chan, package, interface;
const char* progname = argv[0];
GlobalOptions opts{};
for (;;)
{
/* We're using + here as we want to stop parsing at the subcommand
* name
*/
int opt = getopt_long(argc, argv, "+p:c:i:h", options, NULL);
if (opt == -1)
{
break;
}
switch (opt)
{
case 'i':
interface = parseUnsigned(optarg, "interface");
if (!interface.has_value())
{
return {};
}
break;
case 'p':
package = parseUnsigned(optarg, "package");
if (!package.has_value())
{
return {};
}
break;
case 'c':
chan = parseUnsigned(optarg, "channel");
if (!chan.has_value())
{
return {};
}
opts.channel = *chan;
break;
case 'h':
default:
print_usage(progname);
return {};
}
}
if (!interface.has_value())
{
std::cerr << "Missing interface, add an --interface argument\n";
return {};
}
if (!package.has_value())
{
std::cerr << "Missing package, add a --package argument\n";
return {};
}
opts.interface = std::make_unique<NetlinkInterface>(*interface);
opts.package = *package;
return std::make_tuple(std::move(opts), optind);
}
static stdplus::StrBuf toHexStr(std::span<const uint8_t> c) noexcept
{
stdplus::StrBuf ret;
if (c.empty())
{
/* workaround for lg2's handling of string_view */
*ret.data() = '\0';
return ret;
}
stdplus::IntToStr<16, uint8_t> its;
auto oit = ret.append(c.size() * 3);
auto cit = c.begin();
oit = its(oit, *cit++, 2);
for (; cit != c.end(); ++cit)
{
*oit++ = ' ';
oit = its(oit, *cit, 2);
}
*oit = 0;
return ret;
}
/* Helper for the 'raw' and 'oem' command handlers: Construct a single command,
* issue it to the interface, and print the resulting response payload.
*/
static int ncsiCommand(GlobalOptions& options, uint8_t type,
std::vector<unsigned char> payload)
{
NCSICommand cmd(type, options.package, options.channel, payload);
lg2::debug("Command: type {TYPE}, payload {PAYLOAD_LEN} bytes: {PAYLOAD}",
"TYPE", lg2::hex, type, "PAYLOAD_LEN", payload.size(), "PAYLOAD",
toHexStr(payload));
auto resp = options.interface->sendCommand(cmd);
if (!resp)
{
return -1;
}
lg2::debug("Response {DATA_LEN} bytes: {DATA}", "DATA_LEN",
resp->full_payload.size(), "DATA", toHexStr(resp->full_payload));
return 0;
}
static int ncsiCommandRaw(GlobalOptions& options, int argc,
const char* const* argv)
{
std::vector<unsigned char> payload;
std::optional<uint8_t> type;
if (argc < 2)
{
std::cerr << "Invalid arguments for 'raw' subcommand\n";
return -1;
}
/* Not only does the type need to fit into one byte, but the top bit
* is used for the request/response flag, so check for 0x80 here as
* our max here.
*/
type = parseUnsigned(argv[1], "command type");
if (!type.has_value() || *type > 0x80)
{
std::cerr << "Invalid command type value\n";
return -1;
}
if (argc >= 3)
{
auto tmp = parsePayload(argc - 2, argv + 2);
if (!tmp.has_value())
{
return -1;
}
payload = *tmp;
}
return ncsiCommand(options, *type, payload);
}
static int ncsiCommandOEM(GlobalOptions& options, int argc,
const char* const* argv)
{
constexpr uint8_t oemType = 0x50;
if (argc < 2)
{
std::cerr << "Invalid arguments for 'oem' subcommand\n";
return -1;
}
auto payload = parsePayload(argc - 1, argv + 1);
if (!payload.has_value())
{
return -1;
}
return ncsiCommand(options, oemType, *payload);
}
/* A note on log output:
* For output that relates to command-line usage, we just output directly to
* stderr. Once we have a properly parsed command line invocation, we use lg2
* for log output, as we want that to use the standard log facilities to
* catch runtime error scenarios
*/
int main(int argc, char** argv)
{
const char* progname = argv[0];
auto opts = parseGlobalOptions(argc, argv);
if (!opts.has_value())
{
return EXIT_FAILURE;
}
auto [globalOptions, consumed] = std::move(*opts);
if (consumed >= argc)
{
std::cerr << "Missing subcommand command type\n";
return EXIT_FAILURE;
}
/* We have parsed the global options, advance argv & argc to allow the
* subcommand handlers to consume their own options
*/
argc -= consumed;
argv += consumed;
std::string subcommand = argv[0];
int ret = -1;
if (subcommand == "raw")
{
ret = ncsiCommandRaw(globalOptions, argc, argv);
}
else if (subcommand == "oem")
{
ret = ncsiCommandOEM(globalOptions, argc, argv);
}
else
{
std::cerr << "Unknown subcommand '" << subcommand << "'\n";
print_usage(progname);
}
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
}