/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * wps.c - provides "wifi.wps" ubus object
 *
 * Copyright (C) 2018-2024 Iopsys Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 */

#include <stdio.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <uci.h>

#include <easy/easy.h>
#include <wifi.h>
#include "wifimngr.h"

#include "policy.c"

char *wifi_get_main_interface(struct wifimngr *w, int freq_band, char *ifmain)
{
	enum wifi_bw bw;
	int i;

	for (i = 0; i < w->num_wifi_device; i++) {
		uint32_t ch = 0;

		if (!wifi_get_channel(w->wdev[i].phy, &ch, &bw)) {
			if (freq_band == 5 && (ch >= 36 && ch < 200)) {
				strncpy(ifmain, w->wdev[i].phy, 15);
				return ifmain;
			} else if (freq_band == 2 && (ch > 0 && ch <= 14)) {
				strncpy(ifmain, w->wdev[i].phy, 15);
				return ifmain;
			}
		}
	}

	return NULL;
}

static int wps_status(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifimngr *w = container_of(obj, struct wifimngr, wifi_wps_obj);
	struct blob_attr *tb[__ATTR_IFNAME_ONLY];
	enum wps_status code = 0;
	char ifname[16] = {0};
	struct blob_buf bb;
	char status[16];

	memset(&bb, 0, sizeof(bb));
	blobmsg_parse(ifname_policy, __ATTR_IFNAME_ONLY, tb, blob_data(msg),
								blob_len(msg));

	if (!(tb[WPS_ATTR_IFNAME])) {
		if (wifi_get_main_interface(w, 5, ifname) == NULL)
			return UBUS_STATUS_UNKNOWN_ERROR;
	} else {
		strncpy(ifname, blobmsg_data(tb[WPS_ATTR_IFNAME]), 15);
	}

	if (wifi_get_wps_status(ifname, &code))
		return UBUS_STATUS_UNKNOWN_ERROR;

	switch (code) {
		case WPS_STATUS_INIT:
			strcpy(status, "init");
			break;
		case WPS_STATUS_PROCESSING:
			strcpy(status, "processing");
			break;
		case WPS_STATUS_SUCCESS:
			strcpy(status, "success");
			break;
		case WPS_STATUS_FAIL:
			strcpy(status, "fail");
			break;
		case WPS_STATUS_TIMEOUT:
			strcpy(status, "timeout");
			break;
		default:
			strcpy(status, "unknown");
			break;
	}

	blob_buf_init(&bb, 0);
	blobmsg_add_u32(&bb, "code", code);
	blobmsg_add_string(&bb, "status", status);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wps_start(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifimngr *w = container_of(obj, struct wifimngr, wifi_wps_obj);
	struct blob_attr *tb[__WPS_START_ATTR_MAX];
	struct wps_param wps = {
		.role = WPS_REGISTRAR,
		.method = WPS_METHOD_PBC
	};
	char ifname[16] = {0};
	char role[16] = {0};
	char mode[8] = {0};
	unsigned long pin;

	blobmsg_parse(wps_start_policy, __WPS_START_ATTR_MAX,
					tb, blob_data(msg), blob_len(msg));

	if (!(tb[WPS_START_ATTR_IFNAME])) {
		/* if interface not provided, assume 5Ghz main interface */
		if (wifi_get_main_interface(w, 5, ifname) == NULL)
			return UBUS_STATUS_UNKNOWN_ERROR;
	} else {
		strncpy(ifname, blobmsg_data(tb[WPS_START_ATTR_IFNAME]), 15);
	}

	if (!(tb[WPS_START_ATTR_MODE])) {
		wps.method = WPS_METHOD_PBC;
	} else {
		strncpy(mode, blobmsg_data(tb[WPS_START_ATTR_MODE]), 7);
		if (!strcasecmp(mode, "pin"))
			wps.method = WPS_METHOD_PIN;
		else if (!strcasecmp(mode, "pbc"))
			wps.method = WPS_METHOD_PBC;
		else
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (!(tb[WPS_START_ATTR_ROLE])) {
		wps.role = WPS_REGISTRAR;
	} else {
		strncpy(role, blobmsg_data(tb[WPS_START_ATTR_ROLE]), 10);
		if (!strcasecmp(role, "registrar"))
			wps.role = WPS_REGISTRAR;
		else if (!strcasecmp(role, "enrollee"))
			wps.role = WPS_ENROLLEE;
		else if (!strcasecmp(role, "bsta"))
			wps.role = WPS_ENROLLEE_BSTA;
		else
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (wps.method == WPS_METHOD_PIN) {
		if (!(tb[WPS_START_ATTR_STA_PIN])) {
			return UBUS_STATUS_INVALID_ARGUMENT;
		} else {
			pin = strtoul(blobmsg_data(tb[WPS_START_ATTR_STA_PIN]), NULL, 10);
			if (!wifi_is_wps_pin_valid(pin))
				return UBUS_STATUS_INVALID_ARGUMENT;
			wps.pin = pin;
		}
	}

	if ((wifi_start_wps(ifname, wps) == 0))
		return UBUS_STATUS_OK;

	return UBUS_STATUS_UNKNOWN_ERROR;
}

static int wps_genpin(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	unsigned long PIN;
	char local_devPwd[32];
	struct blob_buf bb;

	memset(&bb, 0, sizeof(bb));

	wifi_generate_wps_pin(&PIN);
	sprintf(local_devPwd, "%08u", (unsigned int)PIN);
	local_devPwd[8] = '\0';

	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "pin", local_devPwd);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

static int wps_checkpin(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	unsigned long pin;
	bool valid = false;
	struct blob_attr *tb[__ATTR_PIN_MAX];
	struct blob_buf bb;

	memset(&bb, 0, sizeof(bb));
	blobmsg_parse(pin_policy, __ATTR_PIN_MAX, tb, blob_data(msg),
								blob_len(msg));

	if (!(tb[ATTR_PIN]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	pin = strtoul(blobmsg_get_string(tb[ATTR_PIN]), NULL, 10);
	valid = wifi_is_wps_pin_valid(pin);

	blob_buf_init(&bb, 0);
	blobmsg_add_u8(&bb, "valid", valid);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wps_set_ap_pin(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifimngr *w = container_of(obj, struct wifimngr, wifi_wps_obj);
	struct blob_attr *tb[__PIN_SET_MAX];
	char ifname[16] = {0};
	unsigned long pin;

	blobmsg_parse(pin_set_policy, __PIN_SET_MAX, tb, blob_data(msg),
								blob_len(msg));

	if (!(tb[PIN_SET_IFNAME])) {
		if (wifi_get_main_interface(w, 5, ifname) == NULL)
			return UBUS_STATUS_UNKNOWN_ERROR;
	} else {
		strncpy(ifname, blobmsg_data(tb[PIN_SET_IFNAME]), 15);
	}

	if (!(tb[PIN_SET_PIN]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	pin = strtoul(blobmsg_data(tb[PIN_SET_PIN]), NULL, 10);
	if (!wifi_is_wps_pin_valid(pin))
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (wifi_set_wps_pin(ifname, pin))
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

static int wps_show_ap_pin(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifimngr *w = container_of(obj, struct wifimngr, wifi_wps_obj);
	struct blob_attr *tb[__ATTR_IFNAME_ONLY];
	char ifname[16] = {0};
	char pinstr[9] = {0};
	struct blob_buf bb;
	unsigned long pin;

	memset(&bb, 0, sizeof(bb));
	blobmsg_parse(ifname_policy, __ATTR_IFNAME_ONLY, tb, blob_data(msg),
								blob_len(msg));

	if (!(tb[WPS_ATTR_IFNAME])) {
		if (wifi_get_main_interface(w, 5, ifname) == NULL)
			return UBUS_STATUS_UNKNOWN_ERROR;
	} else {
		strncpy(ifname, blobmsg_data(tb[WPS_ATTR_IFNAME]), 15);
	}

	if (wifi_get_wps_pin(ifname, &pin))
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);
	sprintf(pinstr, "%08lu", pin);
	blobmsg_add_string(&bb, "pin", pinstr);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wps_stop(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifimngr *w = container_of(obj, struct wifimngr, wifi_wps_obj);
	struct blob_attr *tb[__ATTR_IFNAME_ONLY];
	char ifname[16] = {0};
	int i;

	blobmsg_parse(ifname_policy, __ATTR_IFNAME_ONLY, tb, blob_data(msg),
								blob_len(msg));

	if (tb[WPS_ATTR_IFNAME]) {
		strncpy(ifname, blobmsg_data(tb[WPS_ATTR_IFNAME]), 15);
		wifi_stop_wps(ifname);
	} else {
		for (i = 0; i < w->num_wifi_iface; i++) {
			wifi_stop_wps(w->ifs[i].iface);
		}
	}

	return 0;
}

int wl_wps_help(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wl_help_command(ctx, obj, req, method, msg, "wifi.wps");
}

#define MAX_WPS_METHODS		10
int add_wps_methods(struct wifimngr *w, struct ubus_object *wifi_wps_obj)
{
	struct ubus_method *wps_methods;
	int n_methods = 0;

	wps_methods = calloc(MAX_WPS_METHODS, sizeof(struct ubus_method));
	if (!wps_methods)
		return -ENOMEM;


	UBUS_METHOD_ADD(wps_methods, n_methods,
		UBUS_METHOD_NOARG("help", wl_wps_help));

	/* Usage -
	 * start {"mode":"pbc|pin", "role":"enrollee|registrar", "pin":"pin"}
	 * where default mode = pbc and default role = registrar.
	 * If role = registrar, and mode = pin, then enrollee's pin
	 * should be provided through the 'pin' attribute.
	 */
	UBUS_METHOD_ADD(wps_methods, n_methods,
		UBUS_METHOD("start", wps_start, wps_start_policy));

	UBUS_METHOD_ADD(wps_methods, n_methods,
		UBUS_METHOD_NOARG("stop", wps_stop));


	UBUS_METHOD_ADD(wps_methods, n_methods,
			UBUS_METHOD("status", wps_status, ifname_policy));

	UBUS_METHOD_ADD(wps_methods, n_methods,
			UBUS_METHOD_NOARG("generate_pin", wps_genpin));

	UBUS_METHOD_ADD(wps_methods, n_methods,
			UBUS_METHOD("validate_pin", wps_checkpin, pin_policy));

	UBUS_METHOD_ADD(wps_methods, n_methods,
		UBUS_METHOD("showpin", wps_show_ap_pin, ifname_policy));

	UBUS_METHOD_ADD(wps_methods, n_methods,
		UBUS_METHOD("setpin", wps_set_ap_pin, pin_set_policy));


	wifi_wps_obj->methods = wps_methods;
	wifi_wps_obj->n_methods = n_methods;

	return 0;
}
