/*
 * Copyright (c) 2001-2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/* 
 * Modification History
 *
 * November 1, 2001	Dieter Siegmund (dieter@apple.com)
 * - created
 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/queue.h>
#include <string.h>


#include "EAPClientModule.h"

/*
 * Type: EAPClientInfo
 * Purpose:
 *   A structure containing EAPClientInfo member functions.
 * Fields:
 *   type		the IANA/IEEE assigned EAP type
 */
typedef struct {
    EAPType					eap_type;
    const char *				eap_name;

    EAPClientPluginFuncIntrospect *		introspect;
    EAPClientPluginFuncVersion *		version;
    EAPClientPluginFuncInit *			init;
    EAPClientPluginFuncFree *			free;
    EAPClientPluginFuncProcess *		process;
    EAPClientPluginFuncFreePacket *		free_packet;
    EAPClientPluginFuncFailureString *		failure_string;
    EAPClientPluginFuncSessionKey *		session_key;
    EAPClientPluginFuncSessionKey *		server_key;
    EAPClientPluginFuncRequireProperties *	require_properties;
    EAPClientPluginFuncPublishProperties *	publish_properties;
    EAPClientPluginFuncPacketDump *		packet_dump;
    EAPClientPluginFuncUserName *		user_name;
} EAPClientInfo, *EAPClientInfoRef;

struct EAPClientModule_s {
    TAILQ_ENTRY(EAPClientModule_s)	link;

    char *				name;
    EAPClientInfoRef			info;
};

static TAILQ_HEAD(EAPClientModuleHead, EAPClientModule_s)	S_head;
static struct EAPClientModuleHead * S_EAPClientModuleHead_p = &S_head;

EAPClientModuleRef
EAPClientModuleLookup(EAPType type)
{
    EAPClientModuleRef	scan;

    TAILQ_FOREACH(scan, S_EAPClientModuleHead_p, link) {
	if (scan->info->eap_type == type)
	    return (scan);
    }
    return (NULL);
}

EAPType
EAPClientModulePluginEAPType(EAPClientModuleRef module)
{
    return (module->info->eap_type);
}

const char *
EAPClientModulePluginEAPName(EAPClientModuleRef module)
{
    return (module->info->eap_name);
}

EAPType
EAPClientModuleDefaultType(void)
{
    return (TAILQ_FIRST(S_EAPClientModuleHead_p)->info->eap_type);
}

static EAPClientModuleStatus
EAPClientModuleValidatePlugin(const EAPClientInfoRef info)
{
    if (info->init == NULL
	|| info->version == NULL
	|| info->free == NULL
	|| info->process == NULL
	|| info->free_packet == NULL) {
	return (kEAPClientModuleStatusPluginIncomplete);
    }
    if ((*info->version)() != kEAPClientPluginVersion) {
	return (kEAPClientModuleStatusPluginInvalidVersion);
    }
    return (kEAPClientModuleStatusOK);
}

static EAPClientModuleStatus
EAPClientModuleAdd(const EAPClientInfoRef info)
{
    EAPClientModuleRef	module;

    module = malloc(sizeof(*module));
    if (module == NULL) {
	return (kEAPClientModuleStatusAllocationFailed);
    }
    bzero(module, sizeof(*module));
    module->info = info;
    TAILQ_INSERT_TAIL(S_EAPClientModuleHead_p, module, link);
    return (kEAPClientModuleStatusOK);
}

static void
EAPClientModuleInit(void)
{
    static int first;

    if (first == TRUE) {
	return;
    }
    first = TRUE;
    TAILQ_INIT(S_EAPClientModuleHead_p);
    return;
}

EAPClientModuleStatus
EAPClientModuleAddBuiltinModule(EAPClientPluginFuncIntrospect * func)
{
    EAPType			eap_type;
    const char *		eap_name;
    EAPClientInfoRef		info = NULL;
    EAPClientPluginFuncEAPName *name_func;
    EAPClientModuleStatus	status = kEAPClientModuleStatusOK;
    EAPClientPluginFuncEAPType *type_func;

    EAPClientModuleInit();
    type_func = (EAPClientPluginFuncEAPType *)
	(*func)(kEAPClientPluginFuncNameEAPType);
    if (type_func == NULL) {
	status = kEAPClientModuleStatusPluginIncomplete;
	goto failed;
    }
    name_func = (EAPClientPluginFuncEAPName *)
	(*func)(kEAPClientPluginFuncNameEAPName);
    if (name_func == NULL) {
	status = kEAPClientModuleStatusPluginIncomplete;
	goto failed;
    }
    eap_type = (*type_func)();
    if (eap_type <= 0 || eap_type > 255) {
	status = kEAPClientModuleStatusInvalidType;
	goto failed;
    }
    eap_name = (*name_func)();
    if (eap_name == NULL) {
	status = kEAPClientModuleStatusPluginIncomplete;
	goto failed;
    }
    if (EAPClientModuleLookup(eap_type)) {
	status = kEAPClientModuleStatusTypeAlreadyLoaded;
	goto failed;
    }
    info = malloc(sizeof(*info));
    if (info == NULL) {
	status = kEAPClientModuleStatusAllocationFailed;
	goto failed;
    }
    bzero(info, sizeof(*info));
    info->eap_type = eap_type;
    info->eap_name = eap_name;
    info->introspect = (EAPClientPluginFuncIntrospect *)
	(*func)(kEAPClientPluginFuncNameIntrospect);
    info->init = (EAPClientPluginFuncInit *)
	(*func)(kEAPClientPluginFuncNameInit);
    info->version = (EAPClientPluginFuncVersion *)
	(*func)(kEAPClientPluginFuncNameVersion);
    info->free = (EAPClientPluginFuncFree *)
	(*func)(kEAPClientPluginFuncNameFree);
    info->process = (EAPClientPluginFuncProcess *)
	(*func)(kEAPClientPluginFuncNameProcess);
    info->free_packet = (EAPClientPluginFuncFreePacket *)
	(*func)(kEAPClientPluginFuncNameFreePacket);
    info->failure_string = (EAPClientPluginFuncFailureString *)
	(*func)(kEAPClientPluginFuncNameFailureString);
    info->session_key = (EAPClientPluginFuncSessionKey *)
	(*func)(kEAPClientPluginFuncNameSessionKey);
    info->server_key = (EAPClientPluginFuncServerKey *)
	(*func)(kEAPClientPluginFuncNameServerKey);
    info->require_properties = (EAPClientPluginFuncRequireProperties *)
	(*func)(kEAPClientPluginFuncNameRequireProperties);
    info->publish_properties = (EAPClientPluginFuncPublishProperties *)
	(*func)(kEAPClientPluginFuncNamePublishProperties);
    info->packet_dump = (EAPClientPluginFuncPacketDump *)
	(*func)(kEAPClientPluginFuncNamePacketDump);
    info->user_name = (EAPClientPluginFuncUserName *)
	(*func)(kEAPClientPluginFuncNameUserName);
    status = EAPClientModuleValidatePlugin(info);
    if (status != kEAPClientModuleStatusOK) {
	goto failed;
    }
    status = EAPClientModuleAdd(info);
    if (status != kEAPClientModuleStatusOK) {
	goto failed;
    }
    return (status);

 failed:
    if (info != NULL) {
	free (info);
    }
    return (status);
}

/*
 * Functions to call the plug-in, given a module
 */

EAPClientPluginFuncRef
EAPClientModulePluginIntrospect(EAPClientModuleRef module,
				EAPClientPluginFuncName name)
{
    const EAPClientInfoRef	info = module->info;

    if (info->introspect == NULL) {
	return (NULL);
    }
    return ((*info->introspect)(name));
}

EAPClientStatus
EAPClientModulePluginInit(EAPClientModuleRef module, 
			  EAPClientPluginDataRef plugin, 
			  CFArrayRef * require_props, int * error)
{
    EAPClientPluginFuncInit *	init = module->info->init;

    if (init == NULL) {
	return (kEAPClientStatusFailed);
    }
    return (*init)(plugin, require_props, error);
}

void 
EAPClientModulePluginFree(EAPClientModuleRef module,
			  EAPClientPluginDataRef plugin)
{
    EAPClientPluginFuncFree *	free = module->info->free;

    if (free == NULL) {
	return;
    }
    return (*free)(plugin);
}

void 
EAPClientModulePluginFreePacket(EAPClientModuleRef module,
				EAPClientPluginDataRef plugin,
				EAPPacketRef pkt_p)
{
    EAPClientPluginFuncFreePacket *	free_packet = module->info->free_packet;

    if (free_packet == NULL) {
	return;
    }
    return (*free_packet)(plugin, pkt_p);
}

EAPClientState 
EAPClientModulePluginProcess(EAPClientModuleRef module,
			     EAPClientPluginDataRef plugin,
			     const EAPPacketRef in_pkt,
			     EAPPacketRef * out_pkt_p,
			     EAPClientStatus * status, 
			     EAPClientDomainSpecificError * error)
{
    EAPClientPluginFuncProcess *	process = module->info->process;

    if (process == NULL) {
	return (kEAPClientStateFailure);
    }
    return (*process)(plugin, in_pkt, out_pkt_p, status, error);
}

const char * 
EAPClientModulePluginFailureString(EAPClientModuleRef module,
				   EAPClientPluginDataRef plugin)
{
    EAPClientPluginFuncFailureString *	failure_string;

    failure_string = module->info->failure_string;
    if (failure_string == NULL) {
	return (NULL);
    }
    return (*failure_string)(plugin);
}


void * 
EAPClientModulePluginSessionKey(EAPClientModuleRef module,
				EAPClientPluginDataRef plugin, int * key_length)
{
    EAPClientPluginFuncSessionKey *	session_key;

    session_key = module->info->session_key;
    if (session_key == NULL) {
	return (NULL);
    }
    return (*session_key)(plugin, key_length);
}

void * 
EAPClientModulePluginServerKey(EAPClientModuleRef module,
			       EAPClientPluginDataRef plugin, int * key_length)
{
    EAPClientPluginFuncServerKey *	server_key;

    server_key = module->info->server_key;
    if (server_key == NULL) {
	return (NULL);
    }
    return (*server_key)(plugin, key_length);
}

CFArrayRef
EAPClientModulePluginRequireProperties(EAPClientModuleRef module,
				       EAPClientPluginDataRef plugin)
{
    EAPClientPluginFuncRequireProperties *	require_properties;

    require_properties = module->info->require_properties;
    if (require_properties == NULL) {
	return (NULL);
    }
    return (*require_properties)(plugin);
}

CFDictionaryRef
EAPClientModulePluginPublishProperties(EAPClientModuleRef module,
				       EAPClientPluginDataRef plugin)
{
    EAPClientPluginFuncPublishProperties *	publish_properties;

    publish_properties = module->info->publish_properties;
    if (publish_properties == NULL) {
	return (NULL);
    }
    return (*publish_properties)(plugin);
}

bool
EAPClientModulePluginPacketDump(EAPClientModuleRef module,
				FILE * out_f, const EAPPacketRef packet)
{
    EAPClientPluginFuncPacketDump *	packet_dump;

    packet_dump = module->info->packet_dump;
    if (packet_dump == NULL) {
	return (FALSE);
    }
    if (out_f == NULL || packet == NULL) {
	/* just testing for existence of packet dump routine */
	return (TRUE);
    }
    return (*packet_dump)(out_f, packet);
}

CFStringRef
EAPClientModulePluginUserName(EAPClientModuleRef module,
			      CFDictionaryRef properties)
{
    EAPClientPluginFuncUserName *	user_name;

    user_name = module->info->user_name;
    if (user_name == NULL) {
	return (NULL);
    }
    return (*user_name)(properties);
}


