/*
 * Copyright (c) 2012-2018 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

/*
 * This file limLinkMonitoringAlgo.cc contains the code for
 * Link monitoring algorithm on AP and heart beat failure
 * handling on STA.
 * Author:        Chandra Modumudi
 * Date:          03/01/02
 * History:-
 * Date           Modified by    Modification Information
 * --------------------------------------------------------------------
 *
 */

#include "aniGlobal.h"
#include "wni_cfg.h"
#include "cfgApi.h"


#include "schApi.h"
#include "pmmApi.h"
#include "utilsApi.h"
#include "limAssocUtils.h"
#include "limTypes.h"
#include "limUtils.h"
#include "limPropExtsUtils.h"

#ifdef FEATURE_WLAN_DIAG_SUPPORT_LIM //FEATURE_WLAN_DIAG_SUPPORT
#include "vos_diag_core_log.h"
#endif //FEATURE_WLAN_DIAG_SUPPORT
#ifdef WLAN_FEATURE_VOWIFI_11R
#include "limFTDefs.h"
#endif
#include "limSession.h"
#include "limSerDesUtils.h"


/**
 * limSendKeepAliveToPeer()
 *
 *FUNCTION:
 * This function is called to send Keep alive message to peer
 *
 *LOGIC:
 *
 *ASSUMPTIONS:
 *
 *NOTE:
 * NA
 *
 * @param  pMac        - Pointer to Global MAC structure
 * @return None
 */

void
limSendKeepAliveToPeer(tpAniSirGlobal pMac)
{

} /*** limSendKeepAliveToPeer() ***/


/** ---------------------------------------------------------
\fn      limDeleteStaContext
\brief   This function handles the message from HAL:
\        WDA_DELETE_STA_CONTEXT_IND. This function
\        validates that the given station id exist, and if so,
\        deletes the station by calling limTriggerSTAdeletion.
\param   tpAniSirGlobal pMac
\param   tpSirMsgQ      limMsg
\return  none
  -----------------------------------------------------------*/
void
limDeleteStaContext(tpAniSirGlobal pMac, tpSirMsgQ limMsg)
{
    tpDeleteStaContext  pMsg = (tpDeleteStaContext)limMsg->bodyptr;
    tpDphHashNode       pStaDs;
    tpPESession psessionEntry ;

    if(NULL == pMsg)
    {
        PELOGE(limLog(pMac, LOGE,FL("Invalid body pointer in message"));)
        return;
    }
    psessionEntry = pe_find_session_by_sme_session_id(pMac, pMsg->vdev_id);
    if(NULL == psessionEntry)
    {
        limLog(pMac, LOGE, FL("session not found for given sme session"));
        vos_mem_free(pMsg);
        return;
    }

    switch(pMsg->reasonCode)
    {
        case HAL_DEL_STA_REASON_CODE_KEEP_ALIVE:
             pStaDs = dphLookupAssocId(pMac, pMsg->staId, &pMsg->assocId,
                            &psessionEntry->dph.dphHashTable);

             if (!pStaDs) {
                 PELOGE(limLog(pMac, LOGE,
                               FL("Skip STA deletion (invalid STA) limSystemRole=%d"),
                               GET_LIM_SYSTEM_ROLE(psessionEntry));)
                 vos_mem_free(pMsg);
                 return;
             }

             /*
              * check and see if same staId. This is to avoid the scenario
              * where we're trying to delete a staId we just added.
              */
             if (pStaDs->staIndex != pMsg->staId) {
                 PELOGE(limLog(pMac, LOGE,
                               FL("staid mismatch: %d vs %d "), pStaDs->staIndex, pMsg->staId);)
                 vos_mem_free(pMsg);
                 return;
             }

             /*
              * Check if Deauth/Disassoc is triggered from Host.
              * If mlmState is in some transient state then
              * don't trigger STA deletion to avoid the race
              * condition.
              */
             if ((pStaDs &&
                 ((pStaDs->mlmStaContext.mlmState !=
                   eLIM_MLM_LINK_ESTABLISHED_STATE) &&
                  (pStaDs->mlmStaContext.mlmState !=
                   eLIM_MLM_WT_ASSOC_CNF_STATE) &&
                  (pStaDs->mlmStaContext.mlmState !=
                   eLIM_MLM_ASSOCIATED_STATE)))) {
                   PELOGE(limLog(pMac, LOGE,
                              FL("received Del STA context in some transit state(staId: %d, assocId: %d)"),
                              pMsg->staId, pMsg->assocId);)
                vos_mem_free(pMsg);
                return;
             }

             if (LIM_IS_STA_ROLE(psessionEntry) && !pMsg->is_tdls) {
                 /*
                  * If roaming is in progress, then ignore the STA kick out
                  * and let the connection happen. The roaming_in_progress
                  * flag is set whenever a candidate found indication is
                  * received. It is enabled on the PE session for which
                  * the indication is received. There is really no need to
                  * re-set the flag, since the PE session on which it was
                  * set will be deleted, even if roaming is success or failure.
                  * When roaming is a success, the PE session for AP1 is
                  * deleted. When we get a candidate indication, it would be
                  * on the PE session of the AP1. AP2 to which we are about to
                  * roam will have a new PE session ID.If roaming fails for
                  * any reason, then it will anyways delete the PE session of
                  * of the AP1.
                  */
                 if (psessionEntry->roaming_in_progress ||
                      limIsReassocInProgress(pMac, psessionEntry)) {
                     limLog(pMac, LOGE,
                        FL("roam_progress=%d, reassoc=%d. Not disconnecting"),
                           psessionEntry->roaming_in_progress,
                           limIsReassocInProgress(pMac, psessionEntry));
                     vos_mem_free(pMsg);
                     return;
                 }
                 if (!((psessionEntry->limMlmState ==
                        eLIM_MLM_LINK_ESTABLISHED_STATE) &&
                       (psessionEntry->limSmeState !=
                        eLIM_SME_WT_DISASSOC_STATE) &&
                       (psessionEntry->limSmeState !=
                        eLIM_SME_WT_DEAUTH_STATE))) {
                        limLog(pMac, LOGE, FL("Do not process in limMlmState %s(%x) limSmeState (%x)"),
                          limMlmStateStr(psessionEntry->limMlmState),
                          psessionEntry->limMlmState,
                          psessionEntry->limSmeState);
                        vos_mem_free(pMsg);
                        return;
                 }
                 pStaDs = dphGetHashEntry(pMac,
                                          DPH_STA_HASH_INDEX_PEER,
                                          &psessionEntry->dph.dphHashTable);
                 if (NULL == pStaDs) {
                     limLog(pMac, LOGE, FL("Dph entry not found."));
                     vos_mem_free(pMsg);
                     return;
                 }
                 pStaDs->del_sta_ctx_rssi = pMsg->rssi;
                 limSendDeauthMgmtFrame(pMac,
                                   eSIR_MAC_DISASSOC_DUE_TO_INACTIVITY_REASON,
                                   pMsg->addr2, psessionEntry, FALSE);
                 limTearDownLinkWithAp(pMac, psessionEntry->peSessionId,
                                 eSIR_MAC_DISASSOC_DUE_TO_INACTIVITY_REASON);
                 /* only break for STA role (non TDLS) */
                 break;
             }

             limLog(pMac, LOGE, FL("Deleting sta: staId %d, reasonCode %d"),
                             pMsg->staId, pMsg->reasonCode);
             if (LIM_IS_IBSS_ROLE(psessionEntry)) {
                 vos_mem_free(pMsg);
                 return;
             }
             if (LIM_IS_BT_AMP_AP_ROLE(psessionEntry) ||
                 LIM_IS_AP_ROLE(psessionEntry)) {
                 PELOG1(limLog(pMac, LOG1, FL("SAP:lim Delete Station Context (staId: %d, assocId: %d) "),
                             pMsg->staId, pMsg->assocId);)
                 limSendDisassocMgmtFrame(pMac,
                                     eSIR_MAC_DISASSOC_DUE_TO_INACTIVITY_REASON,
                                     pStaDs->staAddr, psessionEntry, FALSE);
                 limTriggerSTAdeletion(pMac, pStaDs, psessionEntry);
             }
#ifdef FEATURE_WLAN_TDLS
             else
             {
                if (LIM_IS_STA_ROLE(psessionEntry) &&
                    STA_ENTRY_TDLS_PEER == pStaDs->staType) {
                    //TeardownLink with PEER
                    //Reason code HAL_DEL_STA_REASON_CODE_KEEP_ALIVE means
                    //eSIR_MAC_TDLS_TEARDOWN_PEER_UNREACHABLE
                    limSendSmeTDLSDelStaInd(pMac, pStaDs, psessionEntry,
                                       eSIR_MAC_TDLS_TEARDOWN_PEER_UNREACHABLE);
                }
             }
#endif
             break;

        case HAL_DEL_STA_REASON_CODE_UNKNOWN_A2:
             PELOGE(limLog(pMac, LOGE, FL(" Deleting Unknown station "));)
             limPrintMacAddr(pMac, pMsg->addr2, LOGE);
             limSendDeauthMgmtFrame( pMac, eSIR_MAC_CLASS3_FRAME_FROM_NON_ASSOC_STA_REASON, pMsg->addr2, psessionEntry, FALSE);
             break;

        default:
             PELOGE(limLog(pMac, LOGE, FL(" Unknown reason code "));)
             break;

    }
    vos_mem_free(pMsg);
    return;
}


/**
 * limTriggerSTAdeletion()
 *
 *FUNCTION:
 * This function is called to trigger STA context deletion
 *
 *LOGIC:
 *
 *ASSUMPTIONS:
 *
 *NOTE:
 * NA
 *
 * @param  pMac   - Pointer to global MAC structure
 * @param  pStaDs - Pointer to internal STA Datastructure
 * @return None
 */
void
limTriggerSTAdeletion(tpAniSirGlobal pMac, tpDphHashNode pStaDs, tpPESession psessionEntry)
{
    tLimMlmDisassocInd mlmDisassocInd;

    if (!pStaDs)
    {
        PELOGW(limLog(pMac, LOGW, FL("Skip STA deletion (invalid STA)"));)
        return;
    }

    if ((pStaDs->mlmStaContext.mlmState == eLIM_MLM_WT_DEL_STA_RSP_STATE) ||
        (pStaDs->mlmStaContext.mlmState == eLIM_MLM_WT_DEL_BSS_RSP_STATE) ||
        pStaDs->sta_deletion_in_progress) {
        /* Already in the process of deleting context for the peer */
        limLog(pMac, LOG1,
            FL("Deletion is in progress (%d) for peer:%pK in mlmState %d"),
            pStaDs->sta_deletion_in_progress, pStaDs->staAddr,
            pStaDs->mlmStaContext.mlmState);
        return;
    }
    pStaDs->sta_deletion_in_progress = true;
    pStaDs->mlmStaContext.disassocReason =
             eSIR_MAC_DISASSOC_DUE_TO_INACTIVITY_REASON;
    pStaDs->mlmStaContext.cleanupTrigger = eLIM_LINK_MONITORING_DISASSOC;
    vos_mem_copy(&mlmDisassocInd.peerMacAddr, pStaDs->staAddr,
                 sizeof(tSirMacAddr));
    mlmDisassocInd.reasonCode = eSIR_MAC_DISASSOC_DUE_TO_INACTIVITY_REASON;
    mlmDisassocInd.disassocTrigger = eLIM_LINK_MONITORING_DISASSOC;

    /* Update PE session Id  */
    mlmDisassocInd.sessionId = psessionEntry->peSessionId;
    limPostSmeMessage(pMac, LIM_MLM_DISASSOC_IND,
                      (tANI_U32 *) &mlmDisassocInd);
    // Issue Disassoc Indication to SME.
    limSendSmeDisassocInd(pMac, pStaDs, psessionEntry);
} /*** end limTriggerSTAdeletion() ***/



/**
 * limTearDownLinkWithAp()
 *
 *FUNCTION:
 * This function is called when heartbeat (beacon reception)
 * fails on STA
 *
 *LOGIC:
 *
 *ASSUMPTIONS:
 *
 *NOTE:
 *
 * @param  pMac - Pointer to Global MAC structure
 * @return None
 */

void
limTearDownLinkWithAp(tpAniSirGlobal pMac, tANI_U8 sessionId, tSirMacReasonCodes reasonCode)
{
    tpDphHashNode pStaDs = NULL;

    //tear down the following sessionEntry
    tpPESession psessionEntry;

    if((psessionEntry = peFindSessionBySessionId(pMac, sessionId))== NULL)
    {
        limLog(pMac, LOGP,FL("Session Does not exist for given sessionID"));
        return;
    }
    /**
     * Heart beat failed for upto threshold value
     * and AP did not respond for Probe request.
     * Trigger link tear down.
     */
    if(pMac->psOffloadEnabled)
        psessionEntry->pmmOffloadInfo.bcnmiss = FALSE;
    else
        pMac->pmm.inMissedBeaconScenario = FALSE;

    limLog(pMac, LOGW,
       FL("No ProbeRsp from AP after HB failure. Tearing down link"));

    // Deactivate heartbeat timer
    limHeartBeatDeactivateAndChangeTimer(pMac, psessionEntry);

    // Announce loss of link to Roaming algorithm
    // and cleanup by sending SME_DISASSOC_REQ to SME

    pStaDs = dphGetHashEntry(pMac, DPH_STA_HASH_INDEX_PEER, &psessionEntry->dph.dphHashTable);


    if (pStaDs != NULL)
    {
        tLimMlmDeauthInd  mlmDeauthInd;

#ifdef FEATURE_WLAN_TDLS
        /* Delete all TDLS peers connected before leaving BSS*/
        limDeleteTDLSPeers(pMac, psessionEntry);
#endif

        pStaDs->mlmStaContext.disassocReason = reasonCode;
        pStaDs->mlmStaContext.cleanupTrigger = eLIM_LINK_MONITORING_DEAUTH;

        /// Issue Deauth Indication to SME.
        vos_mem_copy((tANI_U8 *) &mlmDeauthInd.peerMacAddr,
                      pStaDs->staAddr,
                      sizeof(tSirMacAddr));

        /*
         * if sendDeauthBeforeCon is enabled and reasoncode is
         * Beacon Missed Store the MAC of AP in the flip flop
         * buffer. This MAC will be used to send Deauth before
         * connection, if we connect to same AP after HB failure.
        */
        if (pMac->roam.configParam.sendDeauthBeforeCon &&
                          eSIR_BEACON_MISSED == reasonCode)
        {
            int apCount = pMac->lim.gLimHeartBeatApMacIndex;

            if (pMac->lim.gLimHeartBeatApMacIndex)
                pMac->lim.gLimHeartBeatApMacIndex = 0;
            else
                pMac->lim.gLimHeartBeatApMacIndex = 1;

            limLog(pMac, LOGE, FL("HB Failure on MAC "
                   MAC_ADDRESS_STR" Store it on Index %d"),
                   MAC_ADDR_ARRAY(pStaDs->staAddr),apCount);

            sirCopyMacAddr(pMac->lim.gLimHeartBeatApMac[apCount],
                                                    pStaDs->staAddr);
        }

        mlmDeauthInd.reasonCode    = (tANI_U8) pStaDs->mlmStaContext.disassocReason;
        mlmDeauthInd.deauthTrigger =  pStaDs->mlmStaContext.cleanupTrigger;

        if (GET_LIM_SYSTEM_ROLE(psessionEntry) == eLIM_STA_ROLE)
            limPostSmeMessage(pMac, LIM_MLM_DEAUTH_IND,
                                    (tANI_U32 *) &mlmDeauthInd);

        limSendSmeDeauthInd(pMac, pStaDs, psessionEntry);
        limReInitScanResults(pMac);
    }
} /*** limTearDownLinkWithAp() ***/




/**
 * limHandleHeartBeatFailure()
 *
 *FUNCTION:
 * This function is called when heartbeat (beacon reception)
 * fails on STA
 *
 *LOGIC:
 *
 *ASSUMPTIONS:
 *
 *NOTE:
 *
 * @param  pMac - Pointer to Global MAC structure
 * @return None
 */

void limHandleHeartBeatFailure(tpAniSirGlobal pMac,tpPESession psessionEntry)
{

#ifdef FEATURE_WLAN_DIAG_SUPPORT_LIM //FEATURE_WLAN_DIAG_SUPPORT
    vos_log_beacon_update_pkt_type *log_ptr = NULL;
#endif //FEATURE_WLAN_DIAG_SUPPORT

    /* If gLimHeartBeatTimer fires between the interval of sending WDA_ENTER_BMPS_REQUEST
     * to the HAL and receiving WDA_ENTER_BMPS_RSP from the HAL, then LIM (PE) tries to Process the
     * SIR_LIM_HEAR_BEAT_TIMEOUT message but The PE state is ePMM_STATE_BMPS_SLEEP so PE dont
     * want to handle heartbeat timeout in the BMPS, because Firmware handles it in BMPS.
     * So just return from heartbeatfailure handler
     */
    if(!pMac->psOffloadEnabled)
    {
        if(!IS_ACTIVEMODE_OFFLOAD_FEATURE_ENABLE && (!limIsSystemInActiveState(pMac)))
           return;
    }

#ifdef FEATURE_WLAN_DIAG_SUPPORT_LIM //FEATURE_WLAN_DIAG_SUPPORT
    WLAN_VOS_DIAG_LOG_ALLOC(log_ptr, vos_log_beacon_update_pkt_type, LOG_WLAN_BEACON_UPDATE_C);
    if(log_ptr)
        log_ptr->bcn_rx_cnt = psessionEntry->LimRxedBeaconCntDuringHB;
    WLAN_VOS_DIAG_LOG_REPORT(log_ptr);
#endif //FEATURE_WLAN_DIAG_SUPPORT

    /* Ensure HB Status for the session has been reseted */
    psessionEntry->LimHBFailureStatus = eANI_BOOLEAN_FALSE;

    if ((LIM_IS_STA_ROLE(psessionEntry) ||
         LIM_IS_BT_AMP_STA_ROLE(psessionEntry)) &&
         (psessionEntry->limMlmState == eLIM_MLM_LINK_ESTABLISHED_STATE) &&
         (psessionEntry->limSmeState != eLIM_SME_WT_DISASSOC_STATE) &&
         (psessionEntry->limSmeState != eLIM_SME_WT_DEAUTH_STATE))
    {
        if (!pMac->sys.gSysEnableLinkMonitorMode)
            return;
        /* Ignore HB if channel switch is in progress */
        if (psessionEntry->gLimSpecMgmt.dot11hChanSwState ==
                          eLIM_11H_CHANSW_RUNNING) {
           limLog(pMac, LOGE,
               FL("Ignore Heartbeat failure as Channel switch is in progress"));
           pMac->pmm.inMissedBeaconScenario = false;
           return;
        }

        /**
         * Beacon frame not received within heartbeat timeout.
         */
        limLog(pMac, LOGW, FL("Heartbeat Failure"));
        pMac->lim.gLimHBfailureCntInLinkEstState++;

        /**
         * Check if connected on the DFS channel, if not connected on
         * DFS channel then only send the probe request otherwise tear down the link
         */
        if(!limIsconnectedOnDFSChannel(psessionEntry->currentOperChannel))
        {
            /*** Detected continuous Beacon Misses ***/
            psessionEntry->LimHBFailureStatus= eANI_BOOLEAN_TRUE;

            /*Reset the HB packet count before sending probe*/
            limResetHBPktCount(psessionEntry);
            /**
             * Send Probe Request frame to AP to see if
             * it is still around. Wait until certain
             * timeout for Probe Response from AP.
             */
            PELOGW(limLog(pMac, LOGW, FL("Heart Beat missed from AP. Sending Probe Req"));)
            /* for searching AP, we don't include any additional IE */
            limSendProbeReqMgmtFrame(pMac, &psessionEntry->ssId, psessionEntry->bssId,
                                      psessionEntry->currentOperChannel,psessionEntry->selfMacAddr,
                                      psessionEntry->dot11mode, 0, NULL);
        }
        else
        {
            PELOGW(limLog(pMac, LOGW,
              FL("Heart Beat missed from AP on DFS chanel moving to passive"));)
            if (psessionEntry->currentOperChannel < SIR_MAX_24G_5G_CHANNEL_RANGE){
               limCovertChannelScanType(pMac, psessionEntry->currentOperChannel, false);
               pMac->lim.dfschannelList.timeStamp[psessionEntry->currentOperChannel] = 0;
            }
            /* Connected on DFS channel so should not send the probe request
            * tear down the link directly */
            limTearDownLinkWithAp(pMac, psessionEntry->peSessionId,
                                                       eSIR_BEACON_MISSED);
        }
    }
    else
    {
        /**
             * Heartbeat timer may have timed out
            * while we're doing background scanning/learning
            * or in states other than link-established state.
            * Log error.
            */
        PELOG1(limLog(pMac, LOG1, FL("received heartbeat timeout in state %X"),
               psessionEntry->limMlmState);)
        limPrintMlmState(pMac, LOG1, psessionEntry->limMlmState);
        pMac->lim.gLimHBfailureCntInOtherStates++;
        limReactivateHeartBeatTimer(pMac, psessionEntry);
    }
} /*** limHandleHeartBeatFailure() ***/
