ncsid: Import from gBMC

This is the initial code drop from gBMC.

Google-Bug-Id: 179618516
Upstream: 1e71af914bc8c54d8b91d0a1cf377e2696713c2f
Change-Id: Ic653e8271dacd205e04f2bc713071ef2ec5936a4
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/ncsid/test/nic_mock.cpp b/ncsid/test/nic_mock.cpp
new file mode 100644
index 0000000..8996125
--- /dev/null
+++ b/ncsid/test/nic_mock.cpp
@@ -0,0 +1,337 @@
+#include "nic_mock.h"
+
+#include "platforms/nemora/portable/ncsi.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <stdexcept>
+
+namespace mock
+{
+
+bool NCSIFrame::parse_ethernet_frame(const ncsi_buf_t& ncsi_buf)
+{
+    std::memcpy(&dst_mac_, ncsi_buf.data, sizeof(dst_mac_));
+    std::memcpy(&src_mac_, ncsi_buf.data + sizeof(dst_mac_), sizeof(src_mac_));
+    // The constant defined in a way that assumes big-endian platform, so we are
+    // just going to calculate it here properly.
+    const uint8_t et_hi = *(ncsi_buf.data + 2 * sizeof(mac_addr_t));
+    const uint8_t et_lo = *(ncsi_buf.data + 2 * sizeof(mac_addr_t) + 1);
+    ethertype_ = (et_hi << 8) + et_lo;
+
+    if (ethertype_ != NCSI_ETHERTYPE)
+    {
+        return false;
+    }
+
+    // This code parses the NC-SI command, according to spec and
+    // as defined in platforms/nemora/portable/ncsi.h
+    // It takes some shortcuts to only retrieve the data we are interested in,
+    // such as using offsetof ot get to a particular field.
+    control_packet_type_ =
+        *(ncsi_buf.data + offsetof(ncsi_header_t, control_packet_type));
+    channel_id_ = *(ncsi_buf.data + offsetof(ncsi_header_t, channel_id));
+
+    size_t payload_offset = sizeof(ncsi_header_t);
+    if (control_packet_type_ & NCSI_RESPONSE)
+    {
+        is_response_ = true;
+        control_packet_type_ &= ~NCSI_RESPONSE;
+        std::memcpy(&response_code_, ncsi_buf.data + payload_offset,
+                    sizeof(response_code_));
+        response_code_ = ntohs(response_code_);
+        std::memcpy(&reason_code_,
+                    ncsi_buf.data + payload_offset + sizeof(reason_code_),
+                    sizeof(reason_code_));
+        reason_code_ = ntohs(reason_code_);
+        payload_offset += sizeof(reason_code_) + sizeof(response_code_);
+    }
+
+    if (control_packet_type_ == NCSI_OEM_COMMAND)
+    {
+        std::memcpy(&manufacturer_id_, ncsi_buf.data + payload_offset,
+                    sizeof(manufacturer_id_));
+        manufacturer_id_ = ntohl(manufacturer_id_);
+        // Number of reserved bytes after manufacturer_id_ = 3
+        oem_command_ =
+            *(ncsi_buf.data + payload_offset + sizeof(manufacturer_id_) + 3);
+        payload_offset += sizeof(ncsi_oem_extension_header_t);
+    }
+
+    packet_raw_ =
+        std::vector<uint8_t>(ncsi_buf.data, ncsi_buf.data + ncsi_buf.len);
+    // TODO: Verify payload length.
+
+    return true;
+}
+
+uint32_t NIC::handle_request(const ncsi_buf_t& request_buf,
+                             ncsi_buf_t* response_buf)
+{
+    const ncsi_header_t* ncsi_header =
+        reinterpret_cast<const ncsi_header_t*>(request_buf.data);
+
+    NCSIFrame request_frame;
+    request_frame.parse_ethernet_frame(request_buf);
+    save_frame_to_log(request_frame);
+
+    uint32_t response_size;
+    if (is_loopback_)
+    {
+        std::memcpy(response_buf, &request_buf, sizeof(request_buf));
+        response_size = request_buf.len;
+    }
+    else if (std::find(simple_commands_.begin(), simple_commands_.end(),
+                       ncsi_header->control_packet_type) !=
+             simple_commands_.end())
+    {
+        // Simple Response
+        response_size =
+            ncsi_build_simple_ack(request_buf.data, response_buf->data);
+    }
+    else
+    {
+        // Not-so-Simple Response
+        switch (ncsi_header->control_packet_type)
+        {
+            case NCSI_GET_VERSION_ID:
+                response_size = ncsi_build_version_id_ack(
+                    request_buf.data, response_buf->data, &version_);
+                break;
+            case NCSI_GET_CAPABILITIES:
+                response_size = sizeof(ncsi_capabilities_response_t);
+                {
+                    ncsi_capabilities_response_t response;
+                    ncsi_build_response_header(
+                        request_buf.data, reinterpret_cast<uint8_t*>(&response),
+                        0, 0, response_size - sizeof(ncsi_header_t));
+                    response.channel_count = channel_count_;
+                    std::memcpy(response_buf->data, &response,
+                                sizeof(response));
+                }
+                break;
+            case NCSI_GET_PASSTHROUGH_STATISTICS:
+                if (is_legacy_)
+                {
+                    response_size = ncsi_build_pt_stats_legacy_ack(
+                        request_buf.data, response_buf->data, &stats_legacy_);
+                }
+                else
+                {
+                    response_size = ncsi_build_pt_stats_ack(
+                        request_buf.data, response_buf->data, &stats_);
+                }
+                break;
+            case NCSI_GET_LINK_STATUS:
+                response_size = ncsi_build_link_status_ack(
+                    request_buf.data, response_buf->data, &link_status_);
+                break;
+            case NCSI_OEM_COMMAND:
+                response_size = handle_oem_request(request_buf, response_buf);
+                break;
+            default:
+                response_size = ncsi_build_simple_nack(
+                    request_buf.data, response_buf->data, 1, 1);
+                break;
+        }
+    }
+
+    response_buf->len = response_size;
+
+    return response_size;
+}
+
+uint32_t NIC::handle_oem_request(const ncsi_buf_t& request_buf,
+                                 ncsi_buf_t* response_buf)
+{
+    const ncsi_oem_simple_cmd_t* oem_cmd =
+        reinterpret_cast<const ncsi_oem_simple_cmd_t*>(request_buf.data);
+    uint32_t response_size;
+    switch (oem_cmd->oem_header.oem_cmd)
+    {
+        case NCSI_OEM_COMMAND_GET_HOST_MAC:
+            response_size = ncsi_build_oem_get_mac_ack(
+                request_buf.data, response_buf->data, &mac_);
+            break;
+        case NCSI_OEM_COMMAND_SET_FILTER:
+        {
+            const ncsi_oem_set_filter_cmd_t* cmd =
+                reinterpret_cast<const ncsi_oem_set_filter_cmd_t*>(
+                    request_buf.data);
+            if (set_filter(cmd->hdr.channel_id, cmd->filter))
+            {
+                response_size = ncsi_build_oem_simple_ack(request_buf.data,
+                                                          response_buf->data);
+            }
+            else
+            {
+                response_size = ncsi_build_simple_nack(
+                    request_buf.data, response_buf->data, 3, 4);
+            }
+        }
+        break;
+        case NCSI_OEM_COMMAND_ECHO:
+            response_size =
+                ncsi_build_oem_echo_ack(request_buf.data, response_buf->data);
+            break;
+        case NCSI_OEM_COMMAND_GET_FILTER:
+        {
+            const ncsi_simple_command_t* cmd =
+                reinterpret_cast<const ncsi_simple_command_t*>(
+                    request_buf.data);
+            if (cmd->hdr.channel_id == 0)
+            {
+                response_size = ncsi_build_oem_get_filter_ack(
+                    request_buf.data, response_buf->data, &ch0_filter_);
+            }
+            else if (cmd->hdr.channel_id == 1)
+            {
+                response_size = ncsi_build_oem_get_filter_ack(
+                    request_buf.data, response_buf->data, &ch1_filter_);
+            }
+            else
+            {
+                response_size = ncsi_build_simple_nack(
+                    request_buf.data, response_buf->data, 3, 4);
+            }
+        }
+        break;
+        default:
+            response_size = ncsi_build_simple_nack(request_buf.data,
+                                                   response_buf->data, 1, 2);
+            break;
+    }
+
+    return response_size;
+}
+
+bool NIC::is_filter_configured(uint8_t channel) const
+{
+    if (channel == 0)
+    {
+        return is_ch0_filter_configured_;
+    }
+    else if (channel == 1)
+    {
+        return is_ch1_filter_configured_;
+    }
+
+    throw std::invalid_argument("Unsupported channel");
+}
+
+bool NIC::set_filter(uint8_t channel, const ncsi_oem_filter_t& filter)
+{
+    ncsi_oem_filter_t* nic_filter;
+    if (channel == 0)
+    {
+        nic_filter = &ch0_filter_;
+        is_ch0_filter_configured_ = true;
+    }
+    else if (channel == 1)
+    {
+        nic_filter = &ch1_filter_;
+        is_ch1_filter_configured_ = true;
+    }
+    else
+    {
+        throw std::invalid_argument("Unsupported channel");
+    }
+
+    std::memcpy(nic_filter->mac, filter.mac, MAC_ADDR_SIZE);
+    nic_filter->ip = 0;
+    nic_filter->port = filter.port;
+    return true;
+}
+
+const ncsi_oem_filter_t& NIC::get_filter(uint8_t channel) const
+{
+    if (channel == 0)
+    {
+        return ch0_filter_;
+    }
+    else if (channel == 1)
+    {
+        return ch1_filter_;
+    }
+
+    throw std::invalid_argument("Unsupported channel");
+}
+
+void NIC::set_hostless(bool is_hostless)
+{
+    auto set_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto
+    {
+        return lhs | rhs;
+    };
+
+    auto clear_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto
+    {
+        return lhs & ~rhs;
+    };
+
+    auto flag_op = is_hostless ? set_flag_op : clear_flag_op;
+
+    if (channel_count_ > 0)
+    {
+        ch0_filter_.flags =
+            flag_op(ch0_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
+    }
+
+    if (channel_count_ > 1)
+    {
+        ch1_filter_.flags =
+            flag_op(ch1_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
+    }
+}
+
+void NIC::toggle_hostless()
+{
+    if (channel_count_ > 0)
+    {
+        ch0_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
+    }
+
+    if (channel_count_ > 1)
+    {
+        ch1_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
+    }
+}
+
+bool NIC::is_hostless()
+{
+    return ch0_filter_.flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS;
+}
+
+void NIC::save_frame_to_log(const NCSIFrame& frame)
+{
+    if (cmd_log_.size() >= max_log_size_)
+    {
+        cmd_log_.erase(cmd_log_.begin());
+    }
+
+    cmd_log_.push_back(frame);
+}
+
+const std::vector<uint8_t> NIC::simple_commands_ = {
+    NCSI_CLEAR_INITIAL_STATE,
+    NCSI_SELECT_PACKAGE,
+    NCSI_DESELECT_PACKAGE,
+    NCSI_ENABLE_CHANNEL,
+    NCSI_DISABLE_CHANNEL,
+    NCSI_RESET_CHANNEL,
+    NCSI_ENABLE_CHANNEL_NETWORK_TX,
+    NCSI_DISABLE_CHANNEL_NETWORK_TX,
+    NCSI_AEN_ENABLE,
+    NCSI_SET_LINK,
+    NCSI_SET_VLAN_FILTER,
+    NCSI_ENABLE_VLAN,
+    NCSI_DISABLE_VLAN,
+    NCSI_SET_MAC_ADDRESS,
+    NCSI_ENABLE_BROADCAST_FILTER,
+    NCSI_DISABLE_BROADCAST_FILTER,
+    NCSI_ENABLE_GLOBAL_MULTICAST_FILTER,
+    NCSI_DISABLE_GLOBAL_MULTICAST_FILTER,
+    NCSI_SET_NCSI_FLOW_CONTROL,
+};
+
+} // namespace mock