blob: 39b8378d11971e5e0bfbb639de7d25375be922d0 [file] [log] [blame]
// 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 <ncsi_state_machine.h>
#include <net_config.h>
#include <net_sockio.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <gmock/gmock.h>
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<const struct ether_header*>(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);
}