/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * supplicant_ctrl.c - CLI to wpa_supplicant
 *
 * Copyright (C) 2020-2024 Iopsys Software Solutions AB. All rights reserved.
 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <net/if.h>
#include <time.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <endian.h>
#include <dirent.h>

#include <easy/easy.h>
#include "wifiutils.h"
#include "wifi.h"
#include "debug.h"
#include "wpactrl.h"

int supplicant_cli_get_ssid(const char *ifname, char *ssid, size_t ssid_size)
{
	char buf[1024];
	int ret;

	if (!ssid || ssid_size == 0)
		return -EINVAL;

	memset(ssid, 0, ssid_size);

	ret = wpa_cli_get(ifname, "status", buf, sizeof(buf));
	if (WARN_ON(ret))
		return ret;

	ret = wpa_ctrl_get_param(buf, "ssid", ssid, ssid_size);
	if (ret) {
		libwifi_warn("%s ssid parameter not found in buffer [%s]!\n",
				__func__, buf);
		return ret;
	}

	return 0;
}

static int supplicant_cli_get_param_hex(const char *buf, const char *param, uint8_t *ie, size_t *ie_len) {
	char val[256] = { 0 };
	int len;

	if (WARN_ON(!ie))
		return -EINVAL;

	if (wpa_ctrl_get_param(buf, param, val, sizeof(val))) {
		libwifi_warn("%s %s parameter not found in buffer [%s]!\n",
		__func__, param, buf);
		return -1;
	}

	len = strlen(val);
	if (len % 2)
		return -1;

	if (len > *ie_len * 2)
		return -1;

	strtob(val, len, ie);
	*ie_len = len/2;

	return 0;
}

int supplicant_cli_get_caps(const char *ifname, struct wifi_sta *info)
{
	char buf[2048] = { 0 };
	uint8_t ie[128] = {};
	size_t ie_len = 128;

	if (WARN_ON(wpa_cli_get(ifname, "status", buf, sizeof(buf))))
		return -1;

	ie_len = sizeof(ie);
	if (!supplicant_cli_get_param_hex(buf, "ht_capab", ie, &ie_len)) {
		info->caps.valid |= WIFI_CAP_HT_VALID;
		wifi_cap_set_from_ht_capabilities_info(info->cbitmap, ie, ie_len);

		memcpy(info->caps.ht.supp_mcs, &ie[3], sizeof(info->caps.ht.supp_mcs));
	}

	ie_len = sizeof(ie);
	if (!supplicant_cli_get_param_hex(buf, "vht_capab", ie, &ie_len)) {
		wifi_cap_set_from_vht_capabilities_info(info->cbitmap, ie, ie_len);
		memcpy(&info->caps.vht, ie, ie_len < sizeof(info->caps.vht) ? ie_len : sizeof(info->caps.vht));
		info->caps.valid |= WIFI_CAP_VHT_VALID;
	}

	ie_len = sizeof(ie);
	if (!supplicant_cli_get_param_hex(buf, "he_capab", ie, &ie_len)) {
		uint8_t ext_ie[1 + sizeof(ie)];

		wifi_cap_set_from_he(info->cbitmap, ie, ie_len);
		ext_ie[0] = IE_EXT_HE_CAP;
		memcpy(&ext_ie[1], ie, ie_len);
		wifi_cap_from_ext_ie_mode(ext_ie, ie_len + 1, &info->caps, WIFI_MODE_STA);
	}

	ie_len = sizeof(ie);
	if (!supplicant_cli_get_param_hex(buf, "eht_capab", ie, &ie_len)) {
		uint8_t ext_ie[1 + sizeof(ie)];

		wifi_cap_set_from_eht(info->cbitmap, ie, ie_len);
		ext_ie[0] = IE_EXT_EHT_CAP;
		memcpy(&ext_ie[1], ie, ie_len);
		wifi_cap_from_ext_ie_mode(ext_ie, ie_len + 1, &info->caps, WIFI_MODE_STA);
	}

	return 0;
}

int supplicant_cli_get_oper_std(const char *ifname, uint8_t *std)
{
	char buf[2048] = { 0 };
	char val[256] = { 0 };
	uint32_t freq = 0;
	uint8_t n_state = 0;
	uint8_t ac_state = 0;
	uint8_t wifi_generation = 0;

	if (WARN_ON(!std))
		return -EINVAL;

	if (WARN_ON(wpa_cli_get(ifname, "status", buf, sizeof(buf))))
		return -1;

	if (wpa_ctrl_get_param(buf, "freq", val, sizeof(val))) {
		libwifi_warn("%s freq parameter not found in buffer [%s]!\n",
		__func__, buf);
	}
	if (WARN_ON(sscanf(val, "%u", &freq) != 1)) {
		libwifi_warn("Failed to parse freq parameter\n");
	}

	if (wpa_ctrl_get_param(buf, "ieee80211n", val, sizeof(val))) {
		libwifi_warn("%s ieee80211n parameter not found in buffer [%s]!\n",
		__func__, buf);
	}
	if (WARN_ON(sscanf(val, "%1hhu", &n_state) != 1)) {
		libwifi_warn("Failed to parse ieee80211n parameter\n");
	}

	if (wpa_ctrl_get_param(buf, "ieee80211ac", val, sizeof(val))) {
		libwifi_warn("%s ieee80211ac parameter not found in buffer [%s]!\n",
		__func__, buf);
	}
	if (WARN_ON(sscanf(val, "%1hhu", &ac_state) != 1)) {
		libwifi_warn("Failed to parse ieee80211ac parameter\n");
	}

	if (wpa_ctrl_get_param(buf, "wifi_generation", val, sizeof(val))) {
		libwifi_warn("%s wifi_generation parameter not found in buffer [%s]!\n",
		__func__, buf);
	}
	if (WARN_ON(sscanf(val, "%1hhu", &wifi_generation) != 1)) {
		libwifi_warn("Failed to parse wifi_generation parameter\n");
	}
	*std = 0;

	switch (wifi_generation) {
	case 4:
		*std |= WIFI_N;
		break;
	case 5:
		*std |= WIFI_AC;
		break;
	case 6:
		*std |= WIFI_AX;
		break;
	case 7:
		*std |= WIFI_BE;
		break;
	default:
		break;
	}

	if (ac_state) {
		*std |= WIFI_AC;
	}

	if (n_state) {
		*std |= WIFI_N;
		if (freq >= 5180)
			*std |= WIFI_A;
		return 0;
	}

	if (freq >= 5180) {
		*std |= WIFI_A;
		return 0;
	}

	if (freq <= 2484) {
		*std |= WIFI_B | WIFI_G;
		return 0;
	}

	return 0;
}

int supplicant_cli_get_sta_supported_security(const char *ifname, uint32_t *sec)
{
	char proto[512] = { 0 };
	char key_mgmt[512] = { 0 };
	char auth_alg[512] = { 0 };
	char group[512] = { 0 };
	char buf[32] = { 0 };
	int buildin_wep = 0;
	int buildin_sae = 0;
	int buildin_ft = 0;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	*sec = 0;

	if (WARN_ON(wpa_cli_get(ifname, "get_capability proto", proto, sizeof(proto))))
		return -1;
	if (WARN_ON(wpa_cli_get(ifname, "get_capability key_mgmt", key_mgmt, sizeof(key_mgmt))))
		return -1;
	if (WARN_ON(wpa_cli_get(ifname, "get_capability auth_alg", auth_alg, sizeof(auth_alg))))
		return -1;
	if (WARN_ON(wpa_cli_get(ifname, "get_capability group", group, sizeof(group))))
		return -1;

	if (Cmd(buf, sizeof(buf), "wpa_supplicant -vwep") == 0)
		buildin_wep = 1;

	if (Cmd(buf, sizeof(buf), "wpa_supplicant -vsae") == 0)
		buildin_sae = 1;

	if (Cmd(buf, sizeof(buf), "wpa_supplicant -v11r") == 0)
		buildin_ft = 1;

	if (strstr(key_mgmt, "WPA-PSK")) {
		if (strstr(proto, "WPA"))
			*sec |= BIT(WIFI_SECURITY_WPAPSK);
		if (strstr(proto, "RSN")) {
			*sec |= BIT(WIFI_SECURITY_WPA2PSK);
			if (buildin_ft)
				*sec |= BIT(WIFI_SECURITY_FT_WPA2PSK);
		}
		if (strstr(auth_alg, "SAE") && buildin_sae == 1) {
			*sec |= BIT(WIFI_SECURITY_WPA3PSK);
			if (buildin_ft)
				*sec |= BIT(WIFI_SECURITY_FT_WPA2PSK);
		}
	}

	if (strstr(key_mgmt, "WPA-EAP")) {
		if (strstr(proto, "WPA"))
			*sec |= BIT(WIFI_SECURITY_WPA);
		if (strstr(proto, "RSN"))
			*sec |= BIT(WIFI_SECURITY_WPA2);
		if (strstr(auth_alg, "SAE") && buildin_sae == 1)
			*sec |= BIT(WIFI_SECURITY_WPA3);
	}

	if (strstr(group, "WEP40") && buildin_wep == 1)
		*sec |= BIT(WIFI_SECURITY_WEP64);
	if (strstr(group, "WEP104") && buildin_wep == 1)
		*sec |= BIT(WIFI_SECURITY_WEP128);

	*sec |= BIT(WIFI_SECURITY_NONE);  /* always supported */

	return 0;
}

static int parse_wpa_cipher(const char *cipher_text, uint32_t *cipher)
{
	if (WARN_ON(!cipher))
		return -EINVAL;

	if (strstr(cipher_text, "WEP")) {
		*cipher |= CIPHER_WEP;
	}

	if (strstr(cipher_text, "TKIP")) {
		*cipher |= CIPHER_TKIP;
	}

	if (strstr(cipher_text, "CCMP-256")) {
		*cipher |= CIPHER_CCMP256;
	} else if (strstr(cipher_text, "CCMP")) {
		*cipher |= CIPHER_AES;
	}

	if (strstr(cipher_text, "GCMP-256")) {
		*cipher |= CIPHER_GCMP256;
	} else if (strstr(cipher_text, "GCMP")) {
		*cipher |= CIPHER_GCMP128;
	}

	if(*cipher == 0) {
		*cipher |= CIPHER_NONE;
	}

	return 0;
}

int supplicant_cli_get_sta_security(const char *ifname, struct wifi_sta_security *sec)
{
	char buf[2048] = { 0 };
	char key_mgmt[256] = { 0 };
	char cipher[256] = { 0 };

	if (WARN_ON(!sec))
		return -EINVAL;

	sec->curr_mode = 0;
	sec->group_cipher = 0;
	sec->pair_ciphers =0;

	if (WARN_ON(wpa_cli_get(ifname, "status", buf, sizeof(buf))))
		return -1;

	if (wpa_ctrl_get_param(buf, "key_mgmt", key_mgmt, sizeof(key_mgmt))) {
		libwifi_warn("%s key_mgmt parameter not found in buffer [%s]!\n",
		__func__, buf);
	}

	if (strstr(key_mgmt, "WPA-PSK")) {
		sec->curr_mode |= BIT(WIFI_SECURITY_WPAPSK);
	}

	if (strstr(key_mgmt, "WPA2-PSK")) {
		sec->curr_mode |= BIT(WIFI_SECURITY_WPA2PSK);
	}

	if (strstr(key_mgmt, "WPA-EAP")) {
		sec->curr_mode |= BIT(WIFI_SECURITY_WPA);
	}

	if (strstr(key_mgmt, "SAE")) {
		sec->curr_mode |= BIT(WIFI_SECURITY_WPA3PSK);
	}

	if (strstr(key_mgmt, "FT-SAE")) {
		sec->curr_mode |= BIT(WIFI_SECURITY_FT_WPA3PSK);
	}

	if (sec->curr_mode == 0) {
		sec->curr_mode |= BIT(WIFI_SECURITY_NONE);
	}

	if (wpa_ctrl_get_param(buf, "pairwise_cipher", cipher, sizeof(cipher))) {
		libwifi_warn("%s pairwise_cipher parameter not found in buffer [%s]!\n",
		__func__, buf);
	}

	parse_wpa_cipher(cipher, &sec->pair_ciphers);

	if (wpa_ctrl_get_param(buf, "group_cipher", cipher, sizeof(cipher))) {
		libwifi_warn("%s group_cipher parameter not found in buffer [%s]!\n",
		__func__, buf);
	}

	parse_wpa_cipher(cipher, &sec->group_cipher);

	return 0;
}

int supplicant_sta_info(const char *ifname, struct wifi_sta *info) {
	libwifi_warn("info->caps.valid = 0x%x\n",info->caps.valid);
	WARN_ON(supplicant_cli_get_oper_std(ifname, &info->oper_std));
	WARN_ON(supplicant_cli_get_sta_supported_security(ifname, &info->sec.supp_modes));
	WARN_ON(supplicant_cli_get_sta_security(ifname, &info->sec));
	WARN_ON(supplicant_cli_get_ssid(ifname, (char *)info->ssid, sizeof(info->ssid)));
	info->ssidlen = strlen((char *)info->ssid);
	WARN_ON(supplicant_cli_get_caps(ifname, info));

	return 0;
}
