/*
 *
 *  Wireless daemon for Linux
 *
 *  Copyright (C) 2019  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 <ell/ell.h>

#include "anqputil.h"
#include "ie.h"
#include "util.h"

void anqp_iter_init(struct anqp_iter *iter, const unsigned char *anqp,
			unsigned int len)
{
	iter->anqp = anqp;
	iter->max = len;
	iter->pos = 0;
}

bool anqp_iter_next(struct anqp_iter *iter, uint16_t *id, uint16_t *len,
			const void **data)
{
	const unsigned char *anqp = iter->anqp + iter->pos;
	const unsigned char *end = iter->anqp + iter->max;

	if (iter->pos + 4 >= iter->max)
		return false;

	if (anqp + l_get_le16(anqp + 2) > end)
		return false;

	*id = l_get_le16(anqp);
	anqp += 2;

	*len = l_get_le16(anqp);
	anqp += 2;

	*data = anqp;

	iter->id = *id;
	iter->len = *len;
	iter->data = *data;

	iter->pos = anqp + *len - iter->anqp;

	return true;
}

bool anqp_hs20_parse_osu_provider_nai(const unsigned char *anqp,
					unsigned int len, const char **nai_out)
{
	uint8_t nai_len;
	static char nai[256] = { 0 };

	if (len < 1)
		return false;

	nai_len = *anqp++;
	len--;

	if (len < nai_len)
		return false;

	memcpy(nai, anqp, nai_len);

	*nai_out = nai;

	return true;
}

bool anqp_iter_is_hs20(const struct anqp_iter *iter, uint8_t *stype,
			unsigned int *len, const unsigned char **data)
{
	const unsigned char *anqp = iter->data;
	unsigned int anqp_len = iter->len;
	uint8_t type;

	if (iter->len < 6)
		return false;

	if (memcmp(anqp, wifi_alliance_oui, 3))
		return false;

	anqp += 3;
	anqp_len -= 3;

	type = *anqp++;
	anqp_len--;

	if (type != 0x11)
		return false;

	*stype = *anqp++;
	anqp_len--;

	/* reserved byte */
	anqp++;
	anqp_len--;

	*data = anqp;
	*len = anqp_len;

	return true;
}

char **anqp_parse_nai_realms(const unsigned char *anqp, unsigned int len)
{
	char **realms = NULL;
	uint16_t count;

	if (len < 2)
		return false;

	count = l_get_le16(anqp);

	anqp += 2;
	len -= 2;

	l_debug("");

	while (count--) {
		uint16_t realm_len;
		uint8_t encoding;
		uint8_t nai_len;
		char nai_realm[256] = { 0 };

		/*
		 * The method list is a variable field, so the only way to
		 * reliably increment anqp is by realm_len at the very end since
		 * we dont know how many bytes parse_eap advanced (it does
		 * internal length checking so it should not overflow). We
		 * cant incrementally advance anqp/len, hence the hardcoded
		 * length and pointer adjustments.
		 */

		if (len < 4)
			goto failed;

		realm_len = l_get_le16(anqp);
		anqp += 2;
		len -= 2;

		encoding = anqp[0];

		nai_len = anqp[1];

		if (len - 2 < nai_len)
			goto failed;

		memcpy(nai_realm, anqp + 2, nai_len);

		/*
		 * TODO: Verify NAI encoding in accordance with RFC 4282 ?
		 *
		 * The encoding in RFC 4282 seems to only limit which characters
		 * can be used in an NAI. Since these come in from public
		 * action frames it could have been spoofed, but ultimately if
		 * its bogus the AP won't allow us to connect.
		 */
		if (!util_is_bit_set(encoding, 0))
			l_warn("Not verifying NAI encoding");
		else if (!l_utf8_validate(nai_realm, nai_len, NULL)) {
			l_warn("NAI is not UTF-8");
			goto failed;
		}

		realms = l_strv_append(realms, nai_realm);

		if (len < realm_len)
			goto failed;

		anqp += realm_len;
		len -= realm_len;
	}

	return realms;

failed:
	l_strv_free(realms);
	return NULL;
}
