/**
 * suppRsnFsmProcessEvent
 *
 * FUNCTION
 * Passes an event to the RSN key FSM instance for immediate processing.
 * 
 * @param fsm the RSN Key FSM instance
 * @param eventId the AAG event to process
 * @param arg an optional argument for this event
 *
 * @return ANI_OK if the operation succeeds
 */
int
suppRsnFsmProcessEvent(tSuppRsnFsm *fsm, tRsnFsmEvent eventId, void *arg)
{

    switch (eventId) 
    {
    case RSN_FSM_TIMER_EXPIRED:
        // Proceed straight to checkTransition
        break;
    case RSN_FSM_AUTH_START:
        fsm->authReq = eANI_BOOLEAN_TRUE;
        suppRsnAuthStartEventHandler(fsm);
        break;
    case RSN_FSM_EAPOL_FRAME_AVAILABLE:
        fsm->eapolAvail = eANI_BOOLEAN_TRUE;
        break;
    case RSN_FSM_INTEG_FAILED:
        fsm->integFailed = eANI_BOOLEAN_TRUE;
        break;
    default:
        VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, "Supp unknown event for SuppFsm: %d\n",
                      eventId);
        VOS_ASSERT( 0 );
        return ANI_E_ILLEGAL_ARG;
        break;
    }

    checkTransition(fsm, arg);

    return ANI_OK;
}
static int
gotoStateIntegFailure(tAuthRsnFsm *fsm, tSirMicFailureInfo *micFailureInfo)
{
    fsm->currentState = INTEG_FAILURE;

    fsm->integFailed = eANI_BOOLEAN_FALSE;

    checkTransition(fsm, NULL); // UCT

    return ANI_OK;
}
static int
gotoStateGetPsk(tAuthRsnFsm *fsm)
{
    //This is simply a transaction because we already have the PMK. We always do.
    fsm->currentState = GET_PSK;

    fsm->numTries = 0;
    
    checkTransition(fsm, NULL);
    
    return ANI_OK;
}
static int
gotoStateAuthentication(tAuthRsnFsm *fsm)
{
    fsm->currentState = AUTHENTICATION;

    zeroOutPtk(fsm);
    fsm->authReq = eANI_BOOLEAN_FALSE;

    checkTransition(fsm, NULL); // UCT rule

    return ANI_OK;
}
static int
gotoStateUpdateKeysReq(tAuthRsnFsm *fsm, tAniEapolKeyAvailEventData *data)
{
    tAniEapolRsnKeyDesc *rxDesc;

    fsm->currentState = UPDATE_KEYS_REQ;

    rxDesc = data->keyDesc;

    aniSsmReplayCtrUpdate(fsm->staCtx->peerReplayCtr, rxDesc->replayCounter);

    checkTransition(fsm, data);

    return ANI_OK;
}
static int
gotoStatePtkInitNego(tAuthRsnFsm *fsm, void *arg)
{
    fsm->currentState = PTK_INIT_NEGO;

    // Replay counter will be automatically updated when we create a
    // new packet

    fsm->numTries = 0;
    aniAsfPacketEmptyExplicit(fsm->lastEapol, 
                              EAPOL_TX_HEADER_SIZE);

    checkTransition(fsm, arg);

    return ANI_OK;
}
static int
gotoStateKeyUpdate(tAuthRsnFsm *fsm)
{
    fsm->currentState = KEY_UPDATE;

    if( VOS_IS_STATUS_SUCCESS( vos_rand_get_bytes(fsm->cryptHandle, fsm->aNonce, ANI_EAPOL_KEY_RSN_NONCE_SIZE) ) )
    {

        // Replay counter will be automatically updated when we create a
        // new packet
        
        checkTransition(fsm, NULL); // UCT

        return ANI_OK;
    }
    return ANI_ERROR;
}
static int
gotoStateAuthentication2(tAuthRsnFsm *fsm)
{
    fsm->currentState = AUTHENTICATION_2;

    if( !VOS_IS_STATUS_SUCCESS( vos_rand_get_bytes( fsm->cryptHandle, fsm->aNonce, ANI_EAPOL_KEY_RSN_NONCE_SIZE ) ) )
    {
        VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR,
            "gotoStateAuthentication2 fail to get random number. Disconnect\n" );
        bapAuthDisconnect( fsm->ctx );
        return ANI_ERROR;
    }
    fsm->numTries = 0;

    checkTransition(fsm, NULL); // UCT rule

    return ANI_OK;
}
static 
int checkTransition(tSuppRsnFsm *fsm, void *arg)
{
    tAniEapolKeyAvailEventData *data;
    tAniEapolRsnKeyDesc *rxDesc;
    v_BOOL_t retransmit;
    int retVal;

    if (fsm->authReq) 
    {
        gotoStateAuthentication(fsm);
        return ANI_OK;
    }

    switch (fsm->currentState) 
    {
    case INITIALIZE:
        break;
    case AUTHENTICATION:
        gotoStateGotPmk(fsm);
        checkTransition(fsm, arg);
        break;
    case GOT_PMK:
        if (fsm->eapolAvail) {

            fsm->eapolAvail = eANI_BOOLEAN_FALSE;
            data = (tAniEapolKeyAvailEventData *) arg;
            rxDesc = (tAniEapolRsnKeyDesc *) data->keyDesc;

            if (rxDesc->info.ackFlag) 
            {

                aniSsmReplayCtrUpdate(fsm->peerReplayCtr,
                                      rxDesc->replayCounter);

                // Going from one state to another cannot be a retransmit
                retVal = gotoStateStaKeyStart(fsm, data, eANI_BOOLEAN_FALSE);

            }
        }
        break;
    case STA_KEY_START:
        if (fsm->eapolAvail) {

            fsm->eapolAvail = eANI_BOOLEAN_FALSE;
            data = (tAniEapolKeyAvailEventData *) arg;
            rxDesc = (tAniEapolRsnKeyDesc *) data->keyDesc;

            if (rxDesc->info.ackFlag) {

                retVal = checkPeerReplayCounter(
                        fsm, 
                        data, 
                        &retransmit, 
                        rxDesc->info.micFlag,
                        0);  // MIC not set means check for re-Tx M1.
                if (retVal != ANI_OK)
                    return ANI_OK; // Caller should not fail

                if (retransmit) {

                    VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR,
                                    "Resending EAPOL-Key Msg2 from "
                                  "supplicant to AP" );
                    retVal = gotoStateStaKeyStart(fsm, data, eANI_BOOLEAN_TRUE);

                } 
                else {
                    retVal = checkMic(fsm, data, rxDesc->info.unicastFlag);
                    if (retVal != ANI_OK) 
                    {
                        bapSuppDisconnect( fsm->ctx );
                        return retVal;
                    }

                    aniSsmReplayCtrUpdate(fsm->peerReplayCtr,
                                          rxDesc->replayCounter);

                    gotoStateStaKeySet(fsm, data, eANI_BOOLEAN_FALSE);

                }
            }
        }
        break;
    case STA_KEY_SET:
        if (fsm->eapolAvail) 
        {
            fsm->eapolAvail = eANI_BOOLEAN_FALSE;
            data = (tAniEapolKeyAvailEventData *) arg;
            rxDesc = (tAniEapolRsnKeyDesc *) data->keyDesc;

            retVal = checkPeerReplayCounter(
                        fsm, 
                        data, 
                        &retransmit, 
                        rxDesc->info.micFlag,
                        1);  // MIC set means check for re-Tx M3.
            if (retVal != ANI_OK)
                return ANI_OK; // Caller should not fail

            if (!retransmit) 
            {
                retVal = checkMic(fsm, data, rxDesc->info.unicastFlag);
                if (retVal != ANI_OK) 
                {
                    bapSuppDisconnect( fsm->ctx );
                    return retVal;
                }
                aniSsmReplayCtrUpdate(fsm->peerReplayCtr,
                                      rxDesc->replayCounter);

            }

            if (rxDesc->info.unicastFlag) 
            {
                /* 
                 * Handle pairwise key message...in this state
                 * pairwise key messages can only be for retransmissions.
                 */
                if (retransmit) 
                {
                    VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, 
                                   "Resending EAPOL-Key Msg4 from "
                                  "supplicant \n" );
                    retVal = gotoStateStaKeySet(fsm, data, eANI_BOOLEAN_TRUE);
                }
            }
            else 
            {
                /*
                 * Handle group key message...with group key messages,
                 * the replay counter has to change on
                 * retransmissions.
                 */
                if (!retransmit) 
                {
                    retVal = gotoStateGroupKeySet(fsm, data);
                    if( !ANI_IS_STATUS_SUCCESS( retVal ) )
                    {
                        bapSuppDisconnect( fsm->ctx );
                        return retVal;
                    }
                }
            } 
        } 
        else {
            if (fsm->integFailed) 
            {
                gotoStateKeyUpdate(fsm, arg);
            }
        }
        break;
    case GROUP_KEY_SET:
        gotoStateStaKeySet(fsm, NULL, eANI_BOOLEAN_FALSE);
        break;
    case KEY_UPDATE:
        gotoStateRekeyMsg(fsm, arg);
        break;
    default:
        VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, "Illegal state for SuppRsnFsm: %d",
                      fsm->currentState);
        VOS_ASSERT( 0 );
        return ANI_E_FAILED;
    }

    return ANI_OK;
}
static int
gotoStateGroupKeySet(tSuppRsnFsm *fsm, 
                     tAniEapolKeyAvailEventData *data)
{
    int retVal;
    tAniEapolRsnKeyDesc txDesc;
    tAniEapolRsnKeyDesc *rxDesc;

    int groupKeyLen;
    
    fsm->currentState = GROUP_KEY_SET;
    
    do
    {
        rxDesc = (tAniEapolRsnKeyDesc *) data->keyDesc;
        if( NULL == rxDesc) 
        {
            retVal = ANI_E_NULL_VALUE;
            break;
        }

        if (rxDesc->keyDataLen == 0 || rxDesc->keyData == NULL) 
        {
            VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, 
                "Supp: AP sent no group key in group EAPOL-Key message!\n" );
            retVal = ANI_E_ILLEGAL_ARG;
            break;
        }

        if ( rxDesc->info.keyDescVers == ANI_EAPOL_KEY_DESC_VERS_AES ) 
        {
            groupKeyLen = rxDesc->keyDataLen - ANI_SSM_AES_KEY_WRAP_BLOCK_SIZE;
            if( groupKeyLen <= 0 )
            {
                VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, 
                    "Supp: AP sent GTK too short\n" );
                retVal = ANI_E_ILLEGAL_ARG;
                break;
            }
        } 
        else
        {
            VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, 
                "Supp: AP sent unsupported keyDescVer %d!\n", rxDesc->info.keyDescVers );
            retVal = ANI_E_ILLEGAL_ARG;
            break;
        }

        // Always create a new EAPOL frame

        aniAsfPacketEmptyExplicit( fsm->lastEapol, 
                                  EAPOL_TX_HEADER_SIZE );
        
        vos_mem_zero( &txDesc, sizeof(txDesc) );

        // The Key Information bits...
        if (fsm->suppCtx->grpCipherType == eCSR_ENCRYPT_TYPE_AES) 
        {
            txDesc.info.keyDescVers = ANI_EAPOL_KEY_DESC_VERS_AES;
        }

        txDesc.info.unicastFlag = eANI_BOOLEAN_FALSE;
        txDesc.info.keyId = rxDesc->info.keyId;
        txDesc.info.micFlag = eANI_BOOLEAN_TRUE;
        txDesc.info.secureFlag = eANI_BOOLEAN_TRUE;
        txDesc.keyLen = RSN_80211_KEY_LEN;

        // Send back the same replayCtr that the authenticator sent
        vos_mem_copy(txDesc.replayCounter, 
               rxDesc->replayCounter, 
               sizeof(txDesc.replayCounter));

        retVal = aniEapolWriteKey(fsm->cryptHandle,
                                  fsm->lastEapol,
                                  fsm->suppCtx->authMac,
                                  fsm->suppCtx->suppMac,
                                  ANI_EAPOL_KEY_DESC_TYPE_RSN_NEW,
                                  &txDesc,
                                  fsm->suppCtx->ptk,
                                  CSR_AES_KEY_LEN);
        if( !ANI_IS_STATUS_SUCCESS( retVal ) ) break;    

        if( !VOS_IS_STATUS_SUCCESS( bapRsnSendEapolFrame( fsm->ctx->pvosGCtx, fsm->lastEapol ) ) )
        {
            retVal = ANI_ERROR;
            VOS_TRACE( VOS_MODULE_ID_BAP, VOS_TRACE_LEVEL_ERROR, "Supp could not send eapol. Disconnect\n" );
            break;
        }

        //FIX_RSN there is no need to set GTK retVal = setGtk(fsm->suppCtx, rxDesc->keyRecvSeqCounter);

        // This is never retransmitted
        aniAsfPacketEmptyExplicit( fsm->lastEapol, 
                                  EAPOL_TX_HEADER_SIZE );

        checkTransition(fsm, NULL); // UCT rule
    }while( 0 );

    return retVal;
}