initial drop of phosphor-ipmi-ethstats

initial drop of phosphor-ipmi-ethstats OEM IPMI commands.  These
commands return ethernet device statistics from the BMC.

Change-Id: Iece5d8604ef90f48f08ce1ae710dd295288ff23f
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..ea71ad6
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,99 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterClass:      true
+  AfterControlStatement: true
+  AfterEnum:       true
+  AfterFunction:   true
+  AfterNamespace:  true
+  AfterObjCDeclaration: true
+  AfterStruct:     true
+  AfterUnion:      true
+  BeforeCatch:     true
+  BeforeElse:      true
+  IndentBraces:    false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeBlocks: Regroup
+IncludeCategories:
+  - Regex:           '^[<"](gtest|gmock)'
+    Priority:        5
+  - Regex:           '^"config.h"'
+    Priority:        -1
+  - Regex:           '^".*\.hpp"'
+    Priority:        1
+  - Regex:           '^<.*\.h>'
+    Priority:        2
+  - Regex:           '^<.*'
+    Priority:        3
+  - Regex:           '.*'
+    Priority:        4
+IndentCaseLabels: true
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+ReflowComments:  true
+SortIncludes:    true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        Cpp11
+TabWidth:        4
+UseTab:          Never
+...
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dbd5f0a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,64 @@
+# Template from:
+# https://github.com/github/gitignore/blob/master/Autotools.gitignore
+
+# http://www.gnu.org/software/automake
+
+Makefile.in
+/ar-lib
+/mdate-sh
+/py-compile
+/test-driver
+/ylwrap
+
+# http://www.gnu.org/software/autoconf
+
+/autom4te.cache
+/autoscan.log
+/autoscan-*.log
+/aclocal.m4
+/compile
+/config.guess
+/config.h.in
+/config.sub
+/configure
+/configure.scan
+/depcomp
+/install-sh
+/missing
+/stamp-h1
+
+# https://www.gnu.org/software/libtool/
+
+/ltmain.sh
+
+# http://www.gnu.org/software/texinfo
+
+/texinfo.tex
+
+# Repo Specific Items
+*.o
+/config.h
+/config.h.in~
+/config.log
+/config.status
+Makefile
+.deps
+.dirstamp
+/lib*
+.libs/
+/*-libtool
+/ipmid
+.project
+/test/*_unittest
+/test/*.log
+/test/*.trs
+
+# ignore vim swap files
+.*.sw*
+# failures from patch
+*.orig
+*.rej
+# backup files from some editors
+*~
+.cscope/
+build/
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..b918af2
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,45 @@
+How to use this list:
+    Find the most specific section entry (described below) that matches where
+    your change lives and add the reviewers (R) and maintainers (M) as
+    reviewers. You can use the same method to track down who knows a particular
+    code base best.
+
+    Your change/query may span multiple entries; that is okay.
+
+    If you do not find an entry that describes your request at all, someone
+    forgot to update this list; please at least file an issue or send an email
+    to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+    Section entries are structured according to the following scheme:
+
+    X:  NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+    X:  ...
+    .
+    .
+    .
+
+    Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+    organization; FILE_PATH is a file path within the repository, possibly with
+    wildcards; X is a tag of one of the following types:
+
+    M:  Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+        if omitted from an entry, assume one of the maintainers from the
+        MAINTAINERS entry.
+    R:  Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+        these people are to be added as reviewers for a change matching the repo
+        path.
+    F:  Denotes forked from an external repository; has fields URL.
+
+    Line comments are to be denoted "# SOME COMMENT" (typical shell style
+    comment); it is important to follow the correct syntax and semantics as we
+    may want to use automated tools with this file in the future.
+
+    A change cannot be added to an OpenBMC repository without a MAINTAINER's
+    approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+M:  Patrick Venture <venture@google.com> <venture!>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..8831daf
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,15 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+libethstatscmddir = ${libdir}/ipmid-providers
+libethstatscmd_LTLIBRARIES = libethstatscmd.la
+libethstatscmd_la_SOURCES = main.cpp
+
+libethstatscmd_la_LDFLAGS = $(SYSTEMD_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	-lstdc++fs \
+	-version-info 0:0:0 -shared
+
+libethstatscmd_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..74cb38f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+### Ethernet Statistics Command (0x30)
+
+There is the need to read a specific ethernet-level statistic value from the BMC.  This is driven primarily to detect link errors that require hardware swaps during manufacturing.
+
+This command will be well structured such that there is a request and response which mirror to some extent.
+
+The request will specify the ethernet interface by name, as a length-prepended string, and the field they're requesting by identifier (an unsigned byte).
+
+If the command is not supported, the IPMI OEM handler will respond accordingly, however, if the field is not supported or not recognized, the command will return 0xcc (invalid field).
+
+The current ethernet statistics available (all future additions must append):
+
+|Identifier |Human Readable Name
+|-----------|--------------------
+|0          |rx_bytes
+|1          |rx_compressed
+|2          |rx_crc_errors
+|3          |rx_dropped
+|4          |rx_errors
+|5          |rx_fifo_errors
+|6          |rx_frame_errors
+|7          |rx_length_errors
+|8          |rx_missed_errors
+|9          |rx_nohandler
+|10         |rx_over_errors
+|11         |rx_packets
+|12         |tx_aborted_errors
+|13         |tx_bytes
+|14         |tx_carrier_errors
+|15         |tx_compressed
+|16         |tx_dropped
+|17         |tx_errors
+|18         |tx_fifo_errors
+|19         |tx_heartbeat_errors
+|20         |tx_packets
+|21         |tx_window_errors
+
+Request
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00    |Statistic ID |The identifier of the desired statistic.
+|0x01    |Length       |Length of string (not including null termination).
+|0x02..  |The name     |The string, not null-terminated.
+
+Response
+
+|Byte(s) |Value  |Data
+|--------|-------|----
+|0x00    |Stat ID|The identifier of the desired statistic.
+|0x01....|Uint64 |The value.  Because these are counters we don't anticipate negative values, and we don't expect overflow.
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..50b75b7
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+        config.guess config.h.in config.sub configure depcomp install-sh \
+        ltmain.sh missing *libtool test-driver"
+
+case $1 in
+    clean)
+        test -f Makefile && make maintainer-clean
+        for file in ${AUTOCONF_FILES}; do
+            find -name "$file" | xargs -r rm -rf
+        done
+        exit 0
+        ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..d7987c9
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,38 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([phosphor-ipmi-ethstats], [1.0], [https://www.github.com/openbmc/phosphor-ipmi-ethstats/issues])
+AC_LANG([C++])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+AM_SILENT_RULES([yes])
+
+# Checks for programs.
+AC_PROG_CXX
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_17([noext])
+AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+AC_CHECK_HEADER([host-ipmid], [AC_MSG_ERROR(["phosphor-host-ipmid required and not found."])])
+AC_CHECK_HEADER(experimental/filesystem, ,[AC_MSG_ERROR([Could not find experimental/filesystem...libstdc++fs developement package required])])
+
+# Checks for library functions.
+LT_INIT # Required for systemd linking
+
+# Do you want to install with the Google OEM Number as well.
+AC_ARG_ENABLE([google],
+    AS_HELP_STRING([--enable-google], [Enable registering with Google OEN])
+)
+AC_ARG_VAR(ENABLE_GOOGLE, [Enable registering with Google OEN])
+AS_IF([test "x$enable_google" == "xyes"],
+      [ENABLE_GOOGLE="yes"]
+      AC_DEFINE_UNQUOTED([ENABLE_GOOGLE], ["$ENABLE_GOOGLE"], [Enable registering with Google OEN])
+)
+
+# Create configured output
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/ethstats.hpp b/ethstats.hpp
new file mode 100644
index 0000000..ac170aa
--- /dev/null
+++ b/ethstats.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <cstdint>
+
+namespace ethstats
+{
+
+/**
+ * @brief Ethstat Request structure.
+ */
+struct EthStatRequest
+{
+    uint8_t statId;
+    uint8_t if_name_len;
+    uint8_t if_name[0];
+} __attribute__((packed));
+
+/**
+ * @brief Ethstat Reply structure.
+ */
+struct EthStatReply
+{
+    uint8_t statId;
+    uint64_t value;
+} __attribute__((packed));
+
+enum EthernetStatisticsIds
+{
+    RX_BYTES = 0,
+    RX_COMPRESSED = 1,
+    RX_CRC_ERRORS = 2,
+    RX_DROPPED = 3,
+    RX_ERRORS = 4,
+    RX_FIFO_ERRORS = 5,
+    RX_FRAME_ERRORS = 6,
+    RX_LENGTH_ERRORS = 7,
+    RX_MISSED_ERRORS = 8,
+    RX_NOHANDLER = 9,
+    RX_OVER_ERRORS = 10,
+    RX_PACKETS = 11,
+    TX_ABORTED_ERRORS = 12,
+    TX_BYTES = 13,
+    TX_CARRIER_ERRORS = 14,
+    TX_COMPRESSED = 15,
+    TX_DROPPED = 16,
+    TX_ERRORS = 17,
+    TX_FIFO_ERRORS = 18,
+    TX_HEARTBEAT_ERRORS = 19,
+    TX_PACKETS = 20,
+    TX_WINDOW_ERRORS = 21,
+};
+
+} // namespace ethstats
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..e58d193
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * 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 "config.h"
+
+#include "ethstats.hpp"
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <cstdint>
+#include <cstring>
+#include <experimental/filesystem>
+#include <fstream>
+#include <host-ipmid/iana.hpp>
+#include <host-ipmid/oemrouter.hpp>
+#include <map>
+#include <sstream>
+#include <string>
+
+/* TODO: Swap out once https://gerrit.openbmc-project.xyz/12958 is merged */
+namespace oem
+{
+constexpr auto ethStatsCmd = 48;
+} // namespace oem
+
+namespace ethstats
+{
+namespace fs = std::experimental::filesystem;
+
+// If this changes in the future, there should be some alternative
+// source for the information if possible to provide continuined functionality.
+static const std::map<uint8_t, std::string> statLookup = {
+    {RX_BYTES, "rx_bytes"},
+    {RX_COMPRESSED, "rx_compressed"},
+    {RX_CRC_ERRORS, "rx_crc_errors"},
+    {RX_DROPPED, "rx_dropped"},
+    {RX_ERRORS, "rx_errors"},
+    {RX_FIFO_ERRORS, "rx_fifo_errors"},
+    {RX_FRAME_ERRORS, "rx_frame_errors"},
+    {RX_LENGTH_ERRORS, "rx_length_errors"},
+    {RX_MISSED_ERRORS, "rx_missed_errors"},
+    {RX_NOHANDLER, "rx_nohandler"},
+    {RX_OVER_ERRORS, "rx_over_errors"},
+    {RX_PACKETS, "rx_packets"},
+    {TX_ABORTED_ERRORS, "tx_aborted_errors"},
+    {TX_BYTES, "tx_bytes"},
+    {TX_CARRIER_ERRORS, "tx_carrier_errors"},
+    {TX_COMPRESSED, "tx_compressed"},
+    {TX_DROPPED, "tx_dropped"},
+    {TX_ERRORS, "tx_errors"},
+    {TX_FIFO_ERRORS, "tx_fifo_errors"},
+    {TX_HEARTBEAT_ERRORS, "tx_heartbeat_errors"},
+    {TX_PACKETS, "tx_packets"},
+    {TX_WINDOW_ERRORS, "tx_window_errors"},
+};
+
+/**
+ * Handle the OEM IPMI EthStat Command.
+ *
+ * @param[in] cmd - the OEM command
+ * @param[in] reqBuf - The IPMI request buffer.
+ * @param[in,out] replyCmdBuf - the IPMI reply buffer.
+ * @param[in,out] dataLen - The length of the request and reply.
+ * @return the IPMI result code.
+ */
+static ipmi_ret_t HandleEthStatCommand(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+                                       uint8_t* replyCmdBuf, size_t* dataLen)
+{
+    auto reqLength = (*dataLen);
+
+    // Verify the reqBuf is the minimum length.
+    // [0] == statistics id
+    // [1] == if_name_length
+    // [2..N] == if_name
+    // In theory the smallest can be a one-letter name. (3 bytes).
+    if (reqLength < sizeof(struct EthStatRequest) + sizeof(uint8_t))
+    {
+        std::fprintf(stderr, "*dataLen too small: %u\n",
+                     static_cast<uint32_t>(reqLength));
+        return IPMI_CC_INVALID;
+    }
+
+    // using struct prefix due to nature as c-style pod struct.
+    struct EthStatRequest request;
+    std::memcpy(&request, &reqBuf[0], sizeof(request));
+    auto nameLen = static_cast<uint32_t>(request.if_name_len);
+
+    if (reqLength < (sizeof(request) + nameLen))
+    {
+        std::fprintf(stderr, "*dataLen too small: %u\n",
+                     static_cast<uint32_t>(reqLength));
+        return IPMI_CC_INVALID;
+    }
+
+    // Check the statistic to see if we recognize it.
+    auto stat = statLookup.find(request.statId);
+    if (stat == statLookup.end())
+    {
+        std::fprintf(stderr, "stat not known: 0x%x\n", request.statId);
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    // The if_name handling plus a few other things was taken from the
+    // CableCheck command implementation.
+    //
+    // Ok, so we know what statistic they want.  Let's validate their
+    // if_name.  The string is length delimited (like dns).
+
+    // Copy the string out of the request buffer.
+    // Maximum length is 256 bytes, excluding the nul-terminator.
+    auto name = std::string(
+        reinterpret_cast<const char*>(&reqBuf[0] + sizeof(request)), nameLen);
+
+    // Minor sanity & security check (of course, I'm less certain if unicode
+    // comes into play here.
+    //
+    // Basically you can't easily inject ../ or /../ into the path below.
+    // Decided to make this more robust, although since it appends to the path
+    // it would limit any exposure.
+    if (name.find("/") != std::string::npos)
+    {
+        std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str());
+        return IPMI_CC_INVALID;
+    }
+
+    // TODO: Transition to using the netlink api.
+    std::ostringstream opath;
+    opath << "/sys/class/net/" << name << "/statistics/" << stat->second;
+    std::string path = opath.str();
+
+    std::error_code ec;
+    if (!fs::exists(path, ec))
+    {
+        std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str());
+        return IPMI_CC_INVALID;
+    }
+    // We're uninterested in the state of ec.
+
+    // Read the file and check the result.
+    // We read the number as int64, then check to make sure it's positive
+    // before casting to uint64.
+    uint64_t value = 0;
+    std::ifstream ifs;
+    ifs.exceptions(std::ifstream::failbit);
+
+    try
+    {
+        ifs.open(path);
+        ifs >> value;
+    }
+    catch (std::ios_base::failure& fail)
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    struct EthStatReply reply;
+    reply.statId = request.statId;
+    reply.value = value;
+
+    // Store the result.
+    std::memcpy(&replyCmdBuf[0], &reply, sizeof(reply));
+    (*dataLen) = sizeof(reply);
+
+    return IPMI_CC_OK;
+}
+
+void setupGlobalOemEthStats() __attribute__((constructor));
+
+void setupGlobalOemEthStats()
+{
+    oem::Router* oemRouter = oem::mutableRouter();
+
+#ifdef ENABLE_GOOGLE
+    /* Install in Google OEM Namespace when enabled. */
+    std::fprintf(stderr,
+                 "Registering OEM:[%#08X], Cmd:[%#04X] for Ethstats Commands\n",
+                 oem::googOemNumber, oem::ethStatsCmd);
+
+    oemRouter->registerHandler(oem::googOemNumber, oem::ethStatsCmd,
+                               HandleEthStatCommand);
+#endif
+
+    std::fprintf(stderr,
+                 "Registering OEM:[%#08X], Cmd:[%#04X] for Ethstats Commands\n",
+                 oem::obmcOemNumber, oem::ethStatsCmd);
+
+    oemRouter->registerHandler(oem::obmcOemNumber, oem::ethStatsCmd,
+                               HandleEthStatCommand);
+}
+
+} // namespace ethstats