/*
 *
 *  Wireless daemon for Linux
 *
 *  Copyright (C) 2020  Intel Corporation. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <unistd.h>

#include <ell/ell.h>

#include "linux/nl80211.h"

#include "src/mpdu.h"
#include "src/nl80211util.h"

static struct l_genl *genl;
static struct l_genl_family *nl80211;
static int exit_status;
static uint64_t wdev_id;
static uint8_t wdev_addr[6];
static uint32_t freq;

static const uint8_t probe_req_body[] = {
	/* SSID */
	0x00, 0x07, 'D', 'I', 'R', 'E', 'C', 'T', '-',
	/* Supported Rates */
	0x01, 0x08, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c,
	/* DS Parameter Set */
	0x03, 0x01, 0x00,
	/* HT Capabilities */
	0x2d, 0x1a, 0xef, 0x11, 0x17, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x2c, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,
	/* WPS */
	0xdd, 0x6c, 0x00, 0x50, 0xf2, 0x04,
	/* > Version */
	0x10, 0x4a, 0x00, 0x01, 0x10,
	/* > Request Type */
	0x10, 0x3a, 0x00, 0x01, 0x00,
	/* > Config Methods */
	0x10, 0x08, 0x00, 0x02, 0x13, 0x80,
	/* > UUID E */
	0x10, 0x47, 0x00, 0x10, 0x46, 0x92, 0x49, 0x6f, 0xce, 0x1e, 0x5f, 0xd1,
	0xa5, 0x45, 0x9b, 0x1c, 0xa5, 0xde, 0xb9, 0x41,
	/* > Primary Device Type */
	0x10, 0x54, 0x00, 0x08, 0x00, 0x01, 0x00, 0x50, 0xf2, 0x04, 0x00, 0x01,
	/* > RF Bands */
	0x10, 0x3c, 0x00, 0x01, 0x01,
	/* > Association State */
	0x10, 0x02, 0x00, 0x02, 0x00, 0x00,
	/* > Configuration Error */
	0x10, 0x09, 0x00, 0x02, 0x00, 0x00,
	/* > Device Password ID */
	0x10, 0x12, 0x00, 0x02, 0x00, 0x00,
	/* > Manufacturer */
	0x10, 0x21, 0x00, 0x01, 0x20,
	/* > Model Name */
	0x10, 0x23, 0x00, 0x01, 0x20,
	/* > Model Numbers */
	0x10, 0x24, 0x00, 0x01, 0x20,
	/* > Device Name */
	0x10, 0x11, 0x00, 0x04, 't', 'e', 's', 't',
	/* > Vendor Extension > Version2 */
	0x10, 0x49, 0x00, 0x06, 0x00, 0x37, 0x2a, 0x00, 0x01, 0x20,
	/* P2P */
	0xdd, 0x11, 0x50, 0x6f, 0x9a, 0x09,
	/* > P2P Capability */
	0x02, 0x02, 0x00, 0x04, 0x00,
	/* > Listen Channel */
	0x06, 0x05, 0x00, 'X', 'X', 0x04, 0x51, 0x01,
};

static void frame_cb(struct l_genl_msg *msg, void *user_data)
{
	int err = l_genl_msg_get_error(msg);

	if (err < 0) {
		l_error("CMD_FRAME failed: %s (%i)", strerror(-err), -err);
		exit_status = EXIT_FAILURE;
	} else {
		l_info("Frame queued");
		exit_status = EXIT_SUCCESS;
	}

	l_main_quit();
}

static void get_interface_callback(struct l_genl_msg *msg, void *user_data)
{
	uint32_t ifindex;
	uint32_t iftype;
	const char *ifname;
	const uint8_t *ifaddr;
	uint64_t cur_wdev_id;
	struct ifreq ifr;
	int sock;
	int r;

	/*
	 * For now hoose the first interface with iftype station, require it
	 * to be UP and have an ifindex.
	 */

	if (wdev_id)
		return;

	if (nl80211_parse_attrs(msg, NL80211_ATTR_IFINDEX, &ifindex,
					NL80211_ATTR_WDEV, &cur_wdev_id,
					NL80211_ATTR_IFTYPE, &iftype,
					NL80211_ATTR_IFNAME, &ifname,
					NL80211_ATTR_MAC, &ifaddr,
					NL80211_ATTR_UNSPEC) < 0)
		return;

	if (iftype != NL80211_IFTYPE_STATION)
		return;

	sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
	if (sock == -1)
		return;

	memset(&ifr, 0, sizeof(ifr));
	l_strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	r = ioctl(sock, SIOCGIFFLAGS, &ifr);
	close(sock);

	/* IFF_RUNNING not required */
	if (r == -1 || !(ifr.ifr_flags & IFF_UP))
		return;

	l_info("Selected interface %s", ifname);
	wdev_id = cur_wdev_id;
	memcpy(wdev_addr, ifaddr, 6);
}

static void get_interface_done(void *user_data)
{
	struct l_genl_msg *msg;
	uint8_t frame_buf[256] __attribute__ ((aligned));
	struct mmpdu_header *hdr = (void *) frame_buf;
	static const uint8_t bcast_addr[6] =
		{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
	size_t frame_len;

	if (!wdev_id) {
		l_error("No suitable interface found");
		exit_status = EXIT_FAILURE;
		l_main_quit();
		return;
	}

	memset(frame_buf, 0, sizeof(*hdr));
	hdr->fc.protocol_version = 0;
	hdr->fc.type = MPDU_TYPE_MANAGEMENT;
	hdr->fc.subtype = MPDU_MANAGEMENT_SUBTYPE_PROBE_REQUEST;
	memcpy(hdr->address_1, bcast_addr, 6);	/* DA */
	memcpy(hdr->address_2, wdev_addr, 6);	/* SA */
	memcpy(hdr->address_3, bcast_addr, 6);	/* BSSID */
	frame_len = (uint8_t *) mmpdu_body(hdr) - (uint8_t *) hdr;

	memcpy((void *) mmpdu_body(hdr), probe_req_body, sizeof(probe_req_body));
	frame_len += sizeof(probe_req_body);

	msg = l_genl_msg_new_sized(NL80211_CMD_FRAME, 128 + frame_len);
	l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &wdev_id);

	if (freq) {
		l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq);
		l_genl_msg_append_attr(msg, NL80211_ATTR_OFFCHANNEL_TX_OK, 0,
					NULL);
	}

	l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME, frame_len, frame_buf);
	l_genl_msg_append_attr(msg, NL80211_ATTR_DONT_WAIT_FOR_ACK, 0, NULL);
	l_genl_msg_append_attr(msg, NL80211_ATTR_TX_NO_CCK_RATE, 0, NULL);

	if (!l_genl_family_send(nl80211, msg, frame_cb, user_data, NULL)) {
		l_error("l_genl_family_send failed");
		exit_status = EXIT_FAILURE;
		l_main_quit();
		return;
	}
}

static void dump_interfaces(void)
{
	struct l_genl_msg *msg;

	msg = l_genl_msg_new(NL80211_CMD_GET_INTERFACE);
	if (!l_genl_family_dump(nl80211, msg, get_interface_callback,
				NULL, get_interface_done)) {
		l_genl_msg_unref(msg);
		l_error("Getting nl80211 interface information failed");
		exit_status = EXIT_FAILURE;
		l_main_quit();
		return;
	}
}

static void family_discovered(const struct l_genl_family_info *info,
							void *user_data)
{
	if (!strcmp(l_genl_family_info_get_name(info), NL80211_GENL_NAME))
		nl80211 = l_genl_family_new(genl, NL80211_GENL_NAME);
}

static void discovery_done(void *user_data)
{
	if (!nl80211) {
		l_error("nl80211 doesn't exist.\n"
			"Load it manually using modprobe cfg80211");
		goto quit;
	}

	dump_interfaces();
	return;

quit:
	exit_status = EXIT_FAILURE;
	l_main_quit();
}

int main(int argc, char *argv[])
{
	if (argc >= 2) {
		char *endp;

		if (!strcmp(argv[1], "-h")) {
			fprintf(stderr,
				"Usage: %s [<frequency>]\n\n"
				"Send out a broadcast Probe Request frame.  "
				"A wireless interface must be UP.  If a "
				"frequency is not given, the frame is "
				"transmitted on the current channel.\n",
				argv[0]);
			return EXIT_SUCCESS;
		}

		freq = strtol(argv[1], &endp, 0);

		if (*endp != '\0') {
			fprintf(stderr, "Can't parse '%s'\n", endp);
			return EXIT_FAILURE;
		}
	}

	if (!l_main_init())
		return EXIT_FAILURE;

	l_log_set_stderr();
	exit_status = EXIT_FAILURE;

	genl = l_genl_new();
	if (!genl) {
		l_error("Failed to initialize generic netlink");
		goto done;
	}

	if (!l_genl_discover_families(genl, family_discovered, NULL,
						discovery_done)) {
		l_error("Unable to start family discovery");
		l_genl_unref(genl);
		goto done;
	}

	l_main_run();

	l_genl_family_free(nl80211);
	l_genl_unref(genl);

done:
	l_main_exit();

	return exit_status;
}