/* Agere-specific driver implementation file for wpa_supplicant */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>

#include "wireless_copy.h"
#include "common.h"
#include "driver.h"
#include "driver_wext.h"
#include "eloop.h"
#include "hostap_common.h"
#include "wpa_supplicant.h"


/* Includes for Agere Hermes-I/II device access */
#include "hcfcfg.h"
#include "hcf.h"
#include "hcfdef.h"
#include "wireless/wl_if.h"


/* Enumeration for supported Hermes Types */
enum
{
    WL_HERMES_UNKNOWN = 0,
    WL_HERMES_1       = 1,
    WL_HERMES_2       = 2,
    WL_HERMES_25      = 3
};


/* Function prototpyes for Agere Hermes-I/II device access */
IFBP wl_connect( int wl_sock, const char *ifname );
void wl_disconnect( int wl_sock, IFBP ifbp, const char *ifname );
int wl_get_info( int wl_sock, ltv_t *ltv, const char *ifname );
int wl_put_info( int wl_sock, ltv_t *ltv, const char *ifname );
int wl_wpa_key_auth_mode( char *wpa_ie, int wpa_ie_len );
int wl_detect_hermes_type( const char *ifname );


/* Global variables needed for Hermes-I/II device access */
static int wl_hermes_type;




static int wpa_driver_hermes_set_wpa_ie(const char *ifname, const char *wpa_ie,
					size_t wpa_ie_len)
{
    int     ret = -1;
    int     sock = 0;
    ltv_t   ltv;
    //hcf_16  key_mgmt = 0;

    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {
        /* Check the WPA-IE to see if we're using PSK or not, and set the
           Hermes RID accordingly */
        
        //key_mgmt = wl_wpa_key_auth_mode( wpa_ie, wpa_ie_len );

        //wpa_printf( MSG_DEBUG, "key_mgmt: %d\n", key_mgmt );

        /* Set CFG_SET_WPA_AUTH_KEY_MGMT_SUITE; Is there a way to determine
           if PSK or EAP is being used and should be set? */
        ltv.len = 2;
        ltv.typ = CFG_SET_WPA_AUTH_KEY_MGMT_SUITE;

        switch( wl_hermes_type )
        {
        case WL_HERMES_1:
            ltv.u.u16[0] = 2;
            break;

        case WL_HERMES_2:
        case WL_HERMES_25:
            ltv.u.u16[0] = 4;
            break;

        default:
            ltv.u.u16[0] = 0;
            break;
        }

        ret = wl_put_info( sock, &ltv, ifname );

        close( sock );
    }

    return ret;
}


static int wpa_driver_hermes_set_wpa(const char *ifname, int enabled)
{
    int     ret = -1;
    int     val = 0;
    int     sock = 0;
    ltv_t   ltv;

    wpa_printf(MSG_DEBUG, "%s: enabled=%d", __FUNCTION__, enabled);

    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {

        /* Detect and store the Hermes Type */
        if( enabled )
        {
            wl_hermes_type = wl_detect_hermes_type( ifname );
        }

        /* Setting CFG_CNF_ENCRYPTION to 2 sets WPA: TKIP or better */
        val = enabled ? 2 : 0;
    
        ltv.len = 2;
        ltv.typ = CFG_CNF_ENCRYPTION;
        ltv.u.u16[0] = val;

        ret = wl_put_info( sock, &ltv, ifname );
        
        close( sock );
    }

    return ret;
}


static int wpa_driver_hermes_set_key(const char *ifname, wpa_alg alg,
				     unsigned char *addr, int key_idx,
				     int set_tx, u8 *seq, size_t seq_len,
				     u8 *key, size_t key_len)
{
	int ret = 0;
	char *alg_name;
    int   sock;
    ltv_t ltv;
    int   count = 0;
    int   buf_idx = 0;
    hcf_8 tsc[] = { 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00 };
    hcf_8 rsc[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };


	switch (alg) {
	case WPA_ALG_NONE:
		alg_name = "none";
		break;
	case WPA_ALG_WEP:
		alg_name = "WEP";
		break;
	case WPA_ALG_TKIP:
		alg_name = "TKIP";
		break;
	case WPA_ALG_CCMP:
		alg_name = "CCMP";
		break;
	default:
		return -1;
	}

    wpa_printf(MSG_DEBUG, "%s: alg=%s key_idx=%d set_tx=%d seq_len=%d "
		   "key_len=%d", __FUNCTION__, alg_name, key_idx, set_tx,
		   seq_len, key_len);

    if (seq_len > 8)
		return -2;

    /* Check the key index here; if 0, load as Pairwise Key, otherwise, load as
       a group key. Note that for the Hermes, the RIDs for group/pariwise keys
       are different from each other and different than the default WEP keys as
       well. */
    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {
        switch( alg )
        {
        case WPA_ALG_TKIP:
            /* Make sure that there is no data queued up in the firmware before
               setting the TKIP keys. If this check is not performed, some data
               may be sent out with incorrect MIC and cause synchronizarion
               errors with the AP */
            /* Check every 1ms for 100ms */
            for( count = 0; count < 100; count++ )
            {
                usleep( 1000 );

                ltv.len = 2;
                ltv.typ = 0xFD91;  // This RID not defined in HCF yet!!!
                ltv.u.u16[0] = 0;

                wl_get_info( sock, &ltv, ifname );
                
                if( ltv.u.u16[0] == 0 )
                {
                    break;
                }
            }

            if( count == 100 )
            {
                wpa_printf( MSG_DEBUG, "Timed out waiting for TxQ!" );
            }
            

            switch( key_idx )
            {
            case 0:

                /* Only load key as pairwise key for Hermes-II and II.5. For 
                   Hermes-I, fall through to the next case and load the pairwise
                   key as a Group Key at index 0. */
                if( wl_hermes_type == WL_HERMES_2 || 
                    wl_hermes_type == WL_HERMES_25 )
                {
                    ltv.len = 28;
                    ltv.typ = CFG_ADD_TKIP_MAPPED_KEY;

                    /* Load the BSSID */
                    memcpy( &ltv.u.u8[buf_idx], addr, ETH_ALEN );
                    buf_idx += ETH_ALEN;

                    /* Load the TKIP key */
                    memcpy( &ltv.u.u8[buf_idx], &key[0], 16 );
                    buf_idx += 16;

                    /* Load the TSC */
                    memcpy( &ltv.u.u8[buf_idx], tsc, 8 );
                    buf_idx += 8;

                    /* Load the RSC */
                    /* Copy the RSC from the supplicant to a local buffer, because
                    the RSC doesn't always contain the padding needed */
                    memcpy( rsc, seq, seq_len );
                    memcpy( &ltv.u.u8[buf_idx], rsc, 8 );
                    buf_idx += 8;

                    /* Load the TxMIC key */
                    memcpy( &ltv.u.u8[buf_idx], &key[16], 8 );
                    buf_idx += 8;

                    /* Load the RxMIC key */
                    memcpy( &ltv.u.u8[buf_idx], &key[24], 8 );

                    /* Send the request to the Hermes */
                    wl_put_info( sock, &ltv, ifname );
                    break;
                }

            case 1:
            case 2:
            case 3:
                ltv.len = 26;
                ltv.typ = CFG_ADD_TKIP_DEFAULT_KEY;

                /* Load the key Index */
                ltv.u.u16[buf_idx] = key_idx;

                /* If this is a Tx Key, set bit 8000 */
                if( set_tx )
                {
                    ltv.u.u16[buf_idx] |= 0x8000;
                }

                buf_idx += 2;

                /* Load the RSC */
                /* Copy the RSC from the supplicant to a local buffer, because
                   the RSC doesn't always contain the padding needed */
                memcpy( rsc, seq, seq_len );
                memcpy( &ltv.u.u8[buf_idx], rsc, 8 );
                buf_idx += 8;

                /* Load the TKIP, TxMIC, and RxMIC keys in one shot, because in
                   CFG_ADD_TKIP_DEFAULT_KEY they are back-to-back */
                memcpy( &ltv.u.u8[buf_idx], key, key_len );
                buf_idx += key_len;

                /* Load the TSC */
                memcpy( &ltv.u.u8[buf_idx], tsc, 8 );

                /* Send the request to the Hermes */
                wl_put_info( sock, &ltv, ifname );
                break;

            default:
                break;
            }

            break;

        case WPA_ALG_WEP:
        case WPA_ALG_CCMP:
            break;

        case WPA_ALG_NONE:
            switch( key_idx )
            {
            case 0:
                if( wl_hermes_type == WL_HERMES_2 || 
                    wl_hermes_type == WL_HERMES_25 )
                {

                    /* Only clear a pairwise key for Hermes-II. For Hermes-I,
                       fall through to the next case and clear the key as a
                       Group Key at index 0. */
                    if( addr )
                    {
                        ltv.len = 7;
                        ltv.typ = CFG_REMOVE_TKIP_MAPPED_KEY;

                        memcpy( &ltv.u.u8[0], addr, ETH_ALEN );
                        
                        wl_put_info( sock, &ltv, ifname );
                    }

                    break;
                }

            case 1:
            case 2:
            case 3:
                /* Clear the Group TKIP keys by index */
                ltv.len = 2;
                ltv.typ = CFG_REMOVE_TKIP_DEFAULT_KEY;

                ltv.u.u16[0] = key_idx;
                
                wl_put_info( sock, &ltv, ifname );
                break;

            default:
                break;
            }
            break;

        default:
            break;
        }

        close( sock );
    }

    return ret;
}


static int wpa_driver_hermes_set_countermeasures(const char *ifname,
						 int enabled)
{
    int     ret = -1;
    int     sock = 0;
    ltv_t   ltv;

    /* The supplicant handles all the timers related to MIC failure and 
       countermeasures. When countermeasures are enabled, shut down the card; 
       when disable, re-enable the card. Make sure that the EAPOL message 
       is getting out before card disable */
    wpa_printf(MSG_DEBUG, "%s: enabled=%d", __FUNCTION__, enabled);

    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {
        ltv.len      = 2;
        ltv.typ      = CFG_DRIVER_ENABLE;
        ltv.u.u16[0] = enabled ? 0 : 1;

        ret = wl_put_info( sock, &ltv, ifname );
        close( sock );
    }

    return ret;
}


static int wpa_driver_hermes_set_drop_unencrypted(const char *ifname,
						  int enabled)
{
    int     ret = -1;
    int     sock = 0;
    ltv_t   ltv;

    wpa_printf(MSG_DEBUG, "%s: enabled=%d", __FUNCTION__, enabled);

    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {
        ltv.len = 2;
        ltv.typ = CFG_CNF_EXCL_UNENCRYPTED;
        ltv.u.u16[0] = enabled;

        ret = wl_put_info( sock, &ltv, ifname );
        close( sock );
    }

    return ret;
}


static int wpa_driver_hermes_deauthenticate(const char *ifname, u8 *addr,
					    int reason_code)
{
	wpa_printf(MSG_DEBUG, "%s", __FUNCTION__);
	
    return 0;
}


static int wpa_driver_hermes_disassociate(const char *ifname, u8 *addr,
					  int reason_code)
{
    int     ret = -1;
    int     sock = 0;
    ltv_t   ltv;

    wpa_printf(MSG_DEBUG, "%s", __FUNCTION__);

    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {
        ltv.len = 2;
        ltv.typ = 0xFCC8;   // This RID not defined in HCF yet!!!
        
        memcpy( &ltv.u.u8[0], addr, ETH_ALEN );
        ltv.u.u16[ETH_ALEN / 2] = reason_code;

        ret = wl_put_info( sock, &ltv, ifname );
        close( sock );
    }

    return ret;
	
}


static int wpa_driver_hermes_associate(const char *ifname, const char *bssid,
				       const char *ssid, size_t ssid_len,
				       int freq,
				       const char *wpa_ie, size_t wpa_ie_len,
				       wpa_cipher pairwise_suite,
				       wpa_cipher group_suite,
				       wpa_key_mgmt key_mgmt_suite)
{
	int ret = 0;

	wpa_printf(MSG_DEBUG, "%s", __FUNCTION__);

	if (wpa_driver_hermes_set_wpa_ie(ifname, wpa_ie, wpa_ie_len) < 0)
		ret = -1;
	if (wpa_driver_wext_set_freq(ifname, freq) < 0)
		ret = -1;
	if (wpa_driver_wext_set_ssid(ifname, ssid, ssid_len) < 0)
		ret = -1;
	if (wpa_driver_wext_set_bssid(ifname, bssid) < 0)
		ret = -1;

	return ret;
}


int wpa_driver_hermes_scan(const char *ifname, void *ctx, u8 *ssid,
			   size_t ssid_len)
{
    int ret = -1;

	if (ssid == NULL) {
		/* Use standard Linux Wireless Extensions ioctl if possible
		 * because some drivers using hostap code in wpa_supplicant
		 * might not support Host AP specific scan request (with SSID
		 * info). */
		return wpa_driver_wext_scan(ifname, ctx, ssid, ssid_len);
	}

	return ret;
}


struct wpa_driver_ops wpa_driver_hermes_ops = {
	.get_bssid = wpa_driver_wext_get_bssid,
	.get_ssid = wpa_driver_wext_get_ssid,
	.set_wpa = wpa_driver_hermes_set_wpa,
	.set_key = wpa_driver_hermes_set_key,
	.events_init = wpa_driver_wext_events_init,
	.events_deinit = wpa_driver_wext_events_deinit,
	.set_countermeasures = wpa_driver_hermes_set_countermeasures,
	.set_drop_unencrypted = wpa_driver_hermes_set_drop_unencrypted,
	.scan = wpa_driver_hermes_scan,
	.get_scan_results = wpa_driver_wext_get_scan_results,
	.deauthenticate = wpa_driver_hermes_deauthenticate,
	.disassociate = wpa_driver_hermes_disassociate,
	.associate = wpa_driver_hermes_associate,
};


/* Routines for basic device access to Agere Hermes-I/Hermes-II via the UIL */
IFBP wl_connect( int wl_sock, const char *ifname )
{
    int             result = 0;
    IFBP            ifbp = NULL;
    struct uilreq   urq;

    memset( &urq, 0, sizeof( struct uilreq ));

    strcpy( urq.ifr_name, ifname );
    urq.command = UIL_FUN_CONNECT;
    
    result = ioctl( wl_sock, WVLAN2_IOCTL_UIL, &urq );
    if( result == 0 && urq.result == HCF_SUCCESS )
    {
        ifbp = urq.hcfCtx;
    }
    
    return ifbp;
}


void wl_disconnect( int wl_sock, IFBP ifbp, const char *ifname )
{
    int             result = 0;
    struct uilreq   urq;


    if( ifbp != NULL )
    {
        memset( &urq, 0, sizeof( struct uilreq ));

        strcpy( urq.ifr_name, ifname );
        urq.command = UIL_FUN_DISCONNECT;
        urq.hcfCtx = ifbp;

        result = ioctl( wl_sock, WVLAN2_IOCTL_UIL, &urq );
    
        if( result != 0 || urq.result != HCF_SUCCESS )
        {
            wpa_printf( MSG_WARNING, 
                        "wl_disconnect(): ioctl() failed, errno: %d\n", errno );
            wpa_printf( MSG_WARNING, 
                        "wl_disconnect(): urq.result: %d\n", urq.result );
        }

    }
    else
    {
        wpa_printf( MSG_WARNING, "wl_disconnect(): called with NULL ifbp\n" );
    }

    return;
}

int wl_get_info( int wl_sock, ltv_t *ltv, const char *ifname )
{
    int             result = 0;
    IFBP            ifbp = NULL;
    struct uilreq   urq;


    /* First, connect to the device */
    ifbp = wl_connect( wl_sock, ifname );
    if( ifbp != NULL && ltv != NULL )
    {
        memset( &urq, 0, sizeof( struct uilreq ));

        strcpy( urq.ifr_name, ifname );
        urq.hcfCtx = ifbp;
        urq.command = UIL_FUN_GET_INFO;
        urq.len = sizeof( ltv_t );
        urq.data = ltv;
        
        result = ioctl( wl_sock, WVLAN2_IOCTL_UIL, &urq );

        if( result != 0 || urq.result != HCF_SUCCESS )
        {
            wpa_printf( MSG_WARNING, 
                        "wl_disconnect(): ioctl() failed, errno: %d\n", errno );
            wpa_printf( MSG_WARNING, 
                        "wl_disconnect(): urq.result: %d\n", urq.result );
        }

        wl_disconnect( wl_sock, ifbp, ifname );
    }
    else
    {
        wpa_printf( MSG_WARNING,
                    "Could not connect to the device, or LTV NULL\n" );
        result = -1;
    }
    
    return result;
}

int wl_put_info( int wl_sock, ltv_t *ltv, const char *ifname )
{
    int             result = 0;
    IFBP            ifbp = NULL;
    struct uilreq   urq;


    /* First, connect to the device */
    ifbp = wl_connect( wl_sock, ifname );
    if( ifbp != NULL && ltv != NULL )
    {
        memset( &urq, 0, sizeof( struct uilreq ));

        strcpy( urq.ifr_name, ifname );
        urq.hcfCtx = ifbp;
        urq.command = UIL_FUN_PUT_INFO;
        urq.len = sizeof( ltv_t );
        urq.data = ltv;
        
        result = ioctl( wl_sock, WVLAN2_IOCTL_UIL, &urq );
        
        if( result != 0 || urq.result != HCF_SUCCESS )
        {
            wpa_printf( MSG_WARNING, 
                        "wl_disconnect(): ioctl() failed, errno: %d\n", errno );
            wpa_printf( MSG_WARNING, 
                        "wl_disconnect(): urq.result: %d\n", urq.result );
        }

        wl_disconnect( wl_sock, ifbp, ifname );
    }
    else
    {
        wpa_printf( MSG_WARNING, 
                    "Could not connect to the device, or LTV NULL\n" );
        result = -1;
    }
    
    return result;
}

int wl_detect_hermes_type( const char *ifname )
{
    int                    result;
    int                    sock = 0;
    int                    ret = WL_HERMES_UNKNOWN;
    ltv_t                  ltv;
    CFG_FW_IDENTITY_STRCT  *fw_id;
    
    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sock >= 0 )
    {
        fw_id = (CFG_FW_IDENTITY_STRCT *)&ltv;
        fw_id->len = ( sizeof( CFG_FW_IDENTITY_STRCT ) / sizeof( hcf_16 )) - 1;
        fw_id->typ = CFG_FW_IDENTITY;

        result = wl_get_info( sock, (ltv_t *)fw_id, ifname );

        if( result == HCF_SUCCESS )
        {
            wpa_printf( MSG_DEBUG, "PRI CompID  : %d", fw_id->comp_id );
            wpa_printf( MSG_DEBUG, "PRI Variant : %d", fw_id->variant );
            wpa_printf( MSG_DEBUG, "PRI Version : %d.%02d", fw_id->version_major,
                        fw_id->version_minor );

            switch( fw_id->comp_id )
            {
            case COMP_ID_FW_STA:
                switch( fw_id->variant )
                {
                case 1:
                case 2:
                    wpa_printf( MSG_DEBUG, "Found Hermes-1" );
                    ret = WL_HERMES_1;
                    break;

                case 3:
                    wpa_printf( MSG_DEBUG, "Found Hermes-2" );
                    ret = WL_HERMES_2;
                    break;
                case 4:
                    wpa_printf( MSG_DEBUG, "Found Hermes-2.5" );
                    ret = WL_HERMES_25;
                    break;
                }
                break;

            case COMP_ID_FW_AP:
                switch( fw_id->variant )
                {
                case 1:
                    wpa_printf( MSG_DEBUG, "Found Hermes-1" );
                    ret = WL_HERMES_1;
                    break;

                case 2:
                    wpa_printf( MSG_DEBUG, "Found Hermes-2" );
                    ret = WL_HERMES_2;
                    break;
                }
                break;

            default:
                wpa_printf( MSG_WARNING, "Could not detect Hermes type!" );
                ret = WL_HERMES_UNKNOWN;
                break;
            }
        }

        close( sock );
    }

    return ret;
}
