/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * 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 <cstring>

#include <gtest/gtest.h>
#include "../hostapd.cpp"

namespace aidl::android::hardware::wifi::hostapd {
unsigned char kTestSsid[] = {0x31, 0x32, 0x33, 0x61, 0x62, 0x63, 0x64};

class HostapdConfigTest : public testing::Test {
	protected:
	void SetUp() override {
		resetOverrides();

		mIface_params = {
			.name = "wlan42",
			.hwModeParams = {
				.enable80211N = true,
				.enable80211AC = false,
				.enable80211AX = false,
				.enable6GhzBand = false,
				.enableHeSingleUserBeamformer = false,
				.enableHeSingleUserBeamformee = false,
				.enableHeMultiUserBeamformer = false,
				.enableHeTargetWakeTime = false,
				.enableEdmg = false,
				.enable80211BE = false,
				.maximumChannelBandwidth = ChannelBandwidth::BANDWIDTH_AUTO,
			},
			.channelParams = {},  // not used in config creation
			.vendorData = {},  // not used in config creation
			.instanceIdentities = {},  // not used in config creation
			.usesMlo = false,
		};
		mChannel_params = {
			.bandMask = BandMask::BAND_2_GHZ,
			.acsChannelFreqRangesMhz = {},
			.enableAcs = false,
			.acsShouldExcludeDfs = false,
			.channel = 6,
		};
		mNetwork_params = {
			.ssid =  std::vector<uint8_t>(kTestSsid, kTestSsid + sizeof(kTestSsid)),
			.isHidden = false,
			.encryptionType = EncryptionType::WPA2,
			.passphrase = "verysecurewowe",
			.isMetered = true,  // default for tethered softap, change to false for lohs.
			.vendorElements = {},
		};
	}

	std::string mWlan42_tethered_config = "\ninterface=wlan42\n"
		"driver=nl80211\n"
		"ctrl_interface=/data/vendor/wifi/hostapd/ctrl\n"
		"ssid2=31323361626364\n"
		"channel=6\n"
		"op_class=83\n"
		"ieee80211n=1\n"
		"ieee80211ac=0\n\n\n"
		"hw_mode=g\n\n"
		"ignore_broadcast_ssid=0\n"
		"wowlan_triggers=any\n"
		"interworking=1\n"
		"access_network_type=2\n\n"
		"wpa=2\n"
		"rsn_pairwise=CCMP\n"
		"wpa_passphrase=verysecurewowe\n\n\n\n\n\n"
		"ap_isolate=0\n";

	std::string mWlan42_lohs_config = "dtim_period=2   \n"
		"   ap_max_inactivity=300\n"
		"skip_inactivity_poll = 1\n\n"
		"interface=wlan42\n"
		"driver=nl80211\n"
		"ctrl_interface=/data/vendor/wifi/hostapd/ctrl\n"
		"ssid2=31323361626364\n"
		"channel=6\n"
		"op_class=83\n"
		"ieee80211n=1\n"
		"ieee80211ac=0\n\n\n"
		"hw_mode=g\n\n"
		"ignore_broadcast_ssid=0\n"
		"wowlan_triggers=any\n"
		"interworking=0\n\n"
		"wpa=2\n"
		"rsn_pairwise=CCMP\n"
		"wpa_passphrase=verysecurewowe\n\n\n\n\n\n"
		"ap_isolate=0\n";

	std::string mWlan42_lohs_config_no_overlay = "\ninterface=wlan42\n"
		"driver=nl80211\n"
		"ctrl_interface=/data/vendor/wifi/hostapd/ctrl\n"
		"ssid2=31323361626364\n"
		"channel=6\n"
		"op_class=83\n"
		"ieee80211n=1\n"
		"ieee80211ac=0\n\n\n"
		"hw_mode=g\n\n"
		"ignore_broadcast_ssid=0\n"
		"wowlan_triggers=any\n"
		"interworking=0\n\n"
		"wpa=2\n"
		"rsn_pairwise=CCMP\n"
		"wpa_passphrase=verysecurewowe\n\n\n\n\n\n"
		"ap_isolate=0\n";

	IfaceParams mIface_params;
	ChannelParams mChannel_params;
	NetworkParams mNetwork_params;
	std::string mBr_name = "";
	std::string mOwe_transition_ifname = "";
};

/**
 * Null hostapd_data* and null mac address (u8*)
 * There's an || check on these that should return nullopt
 */
TEST(getStaInfoByMacAddrTest, NullArguments) {
	EXPECT_EQ(std::nullopt, getStaInfoByMacAddr(nullptr, nullptr));
}


/**
 * We pass valid arguments to get past the nullptr check, but hostapd_data->sta_list is nullptr.
 * Don't loop through the sta_info* list, just return nullopt.
 */
TEST(getStaInfoByMacAddrTest, NullStaList) {
	struct hostapd_data iface_hapd = {};
	u8 mac_addr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xD0, 0x0D};
	EXPECT_EQ(std::nullopt, getStaInfoByMacAddr(&iface_hapd, mac_addr));
}

/**
 * Mac doesn't match, and we hit the end of the sta_info list.
 * Don't run over the end of the list and return nullopt.
 */
TEST(getStaInfoByMacAddrTest, NoMatchingMac) {
	struct hostapd_data iface_hapd = {};
	struct sta_info sta0 = {};
	struct sta_info sta1 = {};
	struct sta_info sta2 = {};
	iface_hapd.sta_list = &sta0;
	sta0.next = &sta1;
	sta1.next = &sta2;
	u8 mac_addr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xD0, 0x0D};
	EXPECT_EQ(std::nullopt, getStaInfoByMacAddr(&iface_hapd, mac_addr));
}

/**
 * There is a matching address and we return it.
 */
TEST(getStaInfoByMacAddrTest, MatchingMac) {
	struct hostapd_data iface_hapd = {};
	struct sta_info sta0 = {};
	struct sta_info sta1 = {};
	struct sta_info sta2 = {};
	iface_hapd.sta_list = &sta0;
	sta0.next = &sta1;
	sta1.next = &sta2;
	u8 sta0_addr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xD0, 0x0C};  // off by 1 bit
	std::memcpy(sta0.addr, sta0_addr, ETH_ALEN);
	u8 sta1_addr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xD0, 0x0D};
	std::memcpy(sta1.addr, sta1_addr, ETH_ALEN);
	u8 mac_addr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xD0, 0x0D};
	auto sta_ptr_optional = getStaInfoByMacAddr(&iface_hapd, mac_addr);
	EXPECT_TRUE(sta_ptr_optional.has_value());
	EXPECT_EQ(0, std::memcmp(sta_ptr_optional.value()->addr, sta1_addr, ETH_ALEN));
}


TEST_F(HostapdConfigTest, tetheredApConfig) {
	// instance name, config string, br_name, usesMlo
	std::string config_path = WriteHostapdConfig("wlan42", mWlan42_tethered_config, "", false);
	std::string expected_path = "/data/vendor/wifi/hostapd/hostapd_wlan42.conf";
	EXPECT_EQ(expected_path, config_path);
	EXPECT_EQ(mWlan42_tethered_config, hostapd_unittest_config_output);
}

TEST_F(HostapdConfigTest, tetheredApConfigStatFails) {
	hostapd_unittest_WriteStringToFileRet = false;
	hostapd_unittest_stat_ret = -1;
	// instance name, config string, br_name, usesMlo
	std::string config_path = WriteHostapdConfig("wlan42", mWlan42_tethered_config, "", false);
	std::string expected_path = "";
	EXPECT_EQ(expected_path, config_path);
}

TEST_F(HostapdConfigTest, tetheredApConfigWriteFails) {
	hostapd_unittest_WriteStringToFileRet = false;
	// instance name, config string, br_name, usesMlo
	std::string config_path = WriteHostapdConfig("wlan42", mWlan42_tethered_config, "", false);
	std::string expected_path = "";
	EXPECT_EQ(expected_path, config_path);
}

TEST_F(HostapdConfigTest, tetheredAp) {
	std::string config_string = CreateHostapdConfig(mIface_params, mChannel_params, mNetwork_params,
			mBr_name, mOwe_transition_ifname);
	EXPECT_EQ(mWlan42_tethered_config, config_string);
}

TEST_F(HostapdConfigTest, lohsAp) {
	mNetwork_params.isMetered = false;
	hostapd_unittest_overlay_content =
			"invalid_key=this_should_not_be_here\n"
			"dtim_period=2   \n"
			"   ap_max_inactivity=300\n"
			"another_invalid_key_dtim_period=-10000\n"
			"skip_inactivity_poll = 1";
	std::string config_string = CreateHostapdConfig(mIface_params, mChannel_params, mNetwork_params,
			mBr_name, mOwe_transition_ifname);
	EXPECT_EQ(mWlan42_lohs_config, config_string);
}

TEST_F(HostapdConfigTest, lohsApAccessFails) {
	mNetwork_params.isMetered = false;
	hostapd_unittest_accessRet = -1;
	std::string config_string = CreateHostapdConfig(mIface_params, mChannel_params, mNetwork_params,
			mBr_name, mOwe_transition_ifname);
	EXPECT_EQ(mWlan42_lohs_config_no_overlay, config_string);
}

TEST_F(HostapdConfigTest, lohsApReadFails) {
	mNetwork_params.isMetered = false;
	hostapd_unittest_ReadFileToStringRet = false;
	std::string config_string = CreateHostapdConfig(mIface_params, mChannel_params, mNetwork_params,
			mBr_name, mOwe_transition_ifname);
	EXPECT_EQ("", config_string);
}

}  // namespace aidl::android::hardware::wifi::hostapd
