/*
 * hostapd / Test method for vendor specific (expanded) EAP type
 * Copyright (c) 2005-2007, Jouni Malinen <j@w1.fi>
 *
 * This software may be distributed under the terms of the BSD license.
 * See README for more details.
 */

#include "includes.h"

#include "common.h"
#include "eap_i.h"


#define EAP_VENDOR_ID EAP_VENDOR_HOSTAP
#define EAP_VENDOR_TYPE 0xfcfbfaf9


struct eap_vendor_test_data {
	enum { INIT, CONFIRM, SUCCESS, FAILURE } state;
};


static const char * eap_vendor_test_state_txt(int state)
{
	switch (state) {
	case INIT:
		return "INIT";
	case CONFIRM:
		return "CONFIRM";
	case SUCCESS:
		return "SUCCESS";
	case FAILURE:
		return "FAILURE";
	default:
		return "?";
	}
}


static void eap_vendor_test_state(struct eap_vendor_test_data *data,
				  int state)
{
	wpa_printf(MSG_DEBUG, "EAP-VENDOR-TEST: %s -> %s",
		   eap_vendor_test_state_txt(data->state),
		   eap_vendor_test_state_txt(state));
	data->state = state;
}


static void * eap_vendor_test_init(struct eap_sm *sm)
{
	struct eap_vendor_test_data *data;

	data = os_zalloc(sizeof(*data));
	if (data == NULL)
		return NULL;
	data->state = INIT;

	return data;
}


static void eap_vendor_test_reset(struct eap_sm *sm, void *priv)
{
	struct eap_vendor_test_data *data = priv;
	os_free(data);
}


static struct wpabuf * eap_vendor_test_buildReq(struct eap_sm *sm, void *priv,
						u8 id)
{
	struct eap_vendor_test_data *data = priv;
	struct wpabuf *req;

	req = eap_msg_alloc(EAP_VENDOR_ID, EAP_VENDOR_TYPE, 1,
			    EAP_CODE_REQUEST, id);
	if (req == NULL) {
		wpa_printf(MSG_ERROR, "EAP-VENDOR-TEST: Failed to allocate "
			   "memory for request");
		return NULL;
	}

	wpabuf_put_u8(req, data->state == INIT ? 1 : 3);

	return req;
}


static Boolean eap_vendor_test_check(struct eap_sm *sm, void *priv,
				     struct wpabuf *respData)
{
	const u8 *pos;
	size_t len;

	pos = eap_hdr_validate(EAP_VENDOR_ID, EAP_VENDOR_TYPE, respData, &len);
	if (pos == NULL || len < 1) {
		wpa_printf(MSG_INFO, "EAP-VENDOR-TEST: Invalid frame");
		return TRUE;
	}

	return FALSE;
}


static void eap_vendor_test_process(struct eap_sm *sm, void *priv,
				    struct wpabuf *respData)
{
	struct eap_vendor_test_data *data = priv;
	const u8 *pos;
	size_t len;

	pos = eap_hdr_validate(EAP_VENDOR_ID, EAP_VENDOR_TYPE, respData, &len);
	if (pos == NULL || len < 1)
		return;

	if (data->state == INIT) {
		if (*pos == 2)
			eap_vendor_test_state(data, CONFIRM);
		else
			eap_vendor_test_state(data, FAILURE);
	} else if (data->state == CONFIRM) {
		if (*pos == 4)
			eap_vendor_test_state(data, SUCCESS);
		else
			eap_vendor_test_state(data, FAILURE);
	} else
		eap_vendor_test_state(data, FAILURE);
}


static Boolean eap_vendor_test_isDone(struct eap_sm *sm, void *priv)
{
	struct eap_vendor_test_data *data = priv;
	return data->state == SUCCESS;
}


static u8 * eap_vendor_test_getKey(struct eap_sm *sm, void *priv, size_t *len)
{
	struct eap_vendor_test_data *data = priv;
	u8 *key;
	const int key_len = 64;

	if (data->state != SUCCESS)
		return NULL;

	key = os_malloc(key_len);
	if (key == NULL)
		return NULL;

	os_memset(key, 0x11, key_len / 2);
	os_memset(key + key_len / 2, 0x22, key_len / 2);
	*len = key_len;

	return key;
}


static Boolean eap_vendor_test_isSuccess(struct eap_sm *sm, void *priv)
{
	struct eap_vendor_test_data *data = priv;
	return data->state == SUCCESS;
}


int eap_server_vendor_test_register(void)
{
	struct eap_method *eap;
	int ret;

	eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
				      EAP_VENDOR_ID, EAP_VENDOR_TYPE,
				      "VENDOR-TEST");
	if (eap == NULL)
		return -1;

	eap->init = eap_vendor_test_init;
	eap->reset = eap_vendor_test_reset;
	eap->buildReq = eap_vendor_test_buildReq;
	eap->check = eap_vendor_test_check;
	eap->process = eap_vendor_test_process;
	eap->isDone = eap_vendor_test_isDone;
	eap->getKey = eap_vendor_test_getKey;
	eap->isSuccess = eap_vendor_test_isSuccess;

	ret = eap_server_method_register(eap);
	if (ret)
		eap_server_method_free(eap);
	return ret;
}
