// 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 "net_iface_mock.h" #include "nic_mock.h" #include "platforms/nemora/portable/default_addresses.h" #include "platforms/nemora/portable/ncsi.h" #include "platforms/nemora/portable/ncsi_fsm.h" #include "platforms/nemora/portable/net_types.h" #include #include #include #include #include #include namespace { constexpr uint32_t ETHER_NCSI = 0x88f8; class MockConfig : public net::ConfigBase { public: int get_mac_addr(mac_addr_t* mac) override { std::memcpy(mac, &mac_addr, sizeof(mac_addr_t)); return 0; } int set_mac_addr(const mac_addr_t& mac) override { mac_addr = mac; return 0; } int set_nic_hostless(bool is_hostless) override { is_nic_hostless = is_hostless; return 0; } mac_addr_t mac_addr; bool is_nic_hostless = true; }; class NICConnection : public net::SockIO { public: int write(const void* buf, size_t len) override { conseq_reads = 0; ++n_writes; std::memcpy(last_write.data, buf, len); last_write.len = len; const auto* hdr = reinterpret_cast(buf); if (ETHER_NCSI == ntohs(hdr->ether_type)) { ++n_handles; next_read.len = nic_mock.handle_request(last_write, &next_read); } return len; } int recv(void* buf, size_t maxlen) override { ++n_reads; ++conseq_reads; if (read_timeout > 0) { if (conseq_reads > read_timeout) { return 0; } } if (maxlen < next_read.len) { ++n_read_errs; return 0; } std::memcpy(buf, next_read.data, next_read.len); return next_read.len; } mock::NIC nic_mock{false, 2}; int n_writes = 0; int n_reads = 0; int n_handles = 0; int n_read_errs = 0; // Max number of consequitive reads without writes. int read_timeout = -1; int conseq_reads = 0; ncsi_buf_t last_write = {}; ncsi_buf_t next_read = {}; }; } // namespace class TestNcsi : public testing::Test { public: void SetUp() override { ncsi_sm.set_sockio(&ncsi_sock); ncsi_sm.set_net_config(&net_config_mock); ncsi_sm.set_retest_delay(0); ncsi_sock.nic_mock.set_mac(nic_mac); ncsi_sock.nic_mock.set_hostless(true); ncsi_sock.read_timeout = 10; } protected: void ExpectFiltersNotConfigured() { for (uint8_t i = 0; i < ncsi_sock.nic_mock.get_channel_count(); ++i) { EXPECT_FALSE(ncsi_sock.nic_mock.is_filter_configured(i)); } } void ExpectFiltersConfigured() { // Check that filters are configured on all channels. for (uint8_t i = 0; i < ncsi_sock.nic_mock.get_channel_count(); ++i) { EXPECT_TRUE(ncsi_sock.nic_mock.is_filter_configured(i)); const ncsi_oem_filter_t& ch_filter = ncsi_sock.nic_mock.get_filter(i); for (unsigned i = 0; i < sizeof(nic_mac.octet); ++i) { EXPECT_EQ(nic_mac.octet[i], ch_filter.mac[i]); } EXPECT_EQ(ch_filter.ip, 0); const uint16_t filter_port = ntohs(ch_filter.port); EXPECT_EQ(filter_port, DEFAULT_ADDRESSES_RX_PORT); } } MockConfig net_config_mock; NICConnection ncsi_sock; ncsi::StateMachine ncsi_sm; const mac_addr_t nic_mac = {{0xde, 0xca, 0xfb, 0xad, 0x01, 0x02}}; // Number of states in each state machine static constexpr int l2_num_states = 26; static constexpr int l3l4_num_states = 2; static constexpr int test_num_states = 9; // Total number of states in all three state machines. static constexpr int total_num_states = l2_num_states + l3l4_num_states + test_num_states; }; TEST_F(TestNcsi, TestMACAddrPropagation) { ncsi_sm.run(total_num_states); EXPECT_EQ(ncsi_sock.n_read_errs, 0); EXPECT_EQ(ncsi_sock.n_handles, ncsi_sock.n_writes); EXPECT_EQ(0, std::memcmp(nic_mac.octet, net_config_mock.mac_addr.octet, sizeof(nic_mac.octet))); // Since network is not configured, the filters should not be configured // either. ExpectFiltersNotConfigured(); } TEST_F(TestNcsi, TestFilterConfiguration) { ncsi_sm.run(total_num_states); EXPECT_EQ(ncsi_sock.n_read_errs, 0); EXPECT_EQ(ncsi_sock.n_handles, ncsi_sock.n_writes); ExpectFiltersConfigured(); } TEST_F(TestNcsi, TestFilterReset) { ncsi_sm.run(total_num_states); EXPECT_EQ(ncsi_sock.n_read_errs, 0); EXPECT_EQ(ncsi_sock.n_handles, ncsi_sock.n_writes); // Since network is not configured, the filters should not be configured // either. ExpectFiltersNotConfigured(); ncsi_sm.run(total_num_states); ExpectFiltersConfigured(); } TEST_F(TestNcsi, TestRetest) { ncsi_sm.run(total_num_states + test_num_states); // Verify that the test state machine was stepped through twice, // by counting how many times the last command of the state machine // has been executed. const uint8_t last_test_command = NCSI_GET_LINK_STATUS; const auto& cmd_log = ncsi_sock.nic_mock.get_command_log(); int num_test_runs = 0; for (const auto& ncsi_frame : cmd_log) { if (ncsi_frame.get_control_packet_type() == last_test_command) { ++num_test_runs; } } EXPECT_EQ(num_test_runs, 2); } TEST_F(TestNcsi, TestHostlessSwitch) { // By default the NIC is in hostless mode. // Verify that net config flag changes after FSM run. net_config_mock.is_nic_hostless = false; ncsi_sm.run(total_num_states); EXPECT_EQ(ncsi_sock.n_read_errs, 0); EXPECT_EQ(ncsi_sock.n_handles, ncsi_sock.n_writes); EXPECT_TRUE(net_config_mock.is_nic_hostless); // Now disable the hostless mode and verify that net config // flag changes to false. ncsi_sock.nic_mock.set_hostless(false); ncsi_sm.run(total_num_states); EXPECT_EQ(ncsi_sock.n_read_errs, 0); EXPECT_EQ(ncsi_sock.n_handles, ncsi_sock.n_writes); EXPECT_FALSE(net_config_mock.is_nic_hostless); }