| // Copyright 2021 Google LLC |
| // |
| // 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 "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 |