/*++

Copyright (c) 2000 Microsoft Corporation

    receive.cpp

Abstract:

    Handle received packets.

Revision History:

    07-18-00    vadimg      created

--*/

#include "precomp.h"

VOID (*gReceive[])(DWORD CpIndex, PPP_CONFIG *pRecvConfig) = {
        NULL,
        ReceiveConfigReq,
        ReceiveConfigAck,
        ReceiveConfigNakRej,
        ReceiveConfigNakRej,
        ReceiveTermReq,
        ReceiveTermAck,
        ReceiveCodeRej,
        NULL,
        ReceiveEchoReq,
        ReceiveEchoReply,
        ReceiveDiscardReq
};

/***************************************************************************\
* ReceiveConfigReq
*
\***************************************************************************/

VOID ReceiveConfigReq(DWORD CpIndex, PPP_CONFIG *pRecvConfig)
{
    PPP_PACKET *pSendPacket = GetSendPacket();
    PPP_CONFIG *pSendConfig = (PPP_CONFIG*)pSendPacket->Data;
    BOOL fAcked;

    switch (gInfo.State) {
    case FSM_OPENED:            
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }
        if (!FsmSendConfigReq(CpIndex)) {
            return;
        }
        if (!FsmSendConfigResult(CpIndex, pRecvConfig, &fAcked)) {
            return;
        }

        gInfo.State = fAcked ? FSM_ACK_SENT : FSM_REQ_SENT;
        break;

    case FSM_STOPPED:

        InitRestartCounters();

        if (!FsmSendConfigReq(CpIndex)) {
            return;
        }

        // fall through

    case FSM_REQ_SENT:
    case FSM_ACK_SENT:          
        if (!FsmSendConfigResult(CpIndex, pRecvConfig, &fAcked)) {
            return;
        }

        gInfo.State = fAcked ? FSM_ACK_SENT : FSM_REQ_SENT;
        break;

    case FSM_ACK_RCVD:
        if (!FsmSendConfigResult(CpIndex, pRecvConfig, &fAcked)) {
            return;
        }

        if (fAcked) {
            gInfo.State = FSM_OPENED;
            FsmThisLayerUp(CpIndex);
        }
        break;

    case FSM_CLOSED:
        FsmSendTermAck(CpIndex, pRecvConfig);
        break;

    case FSM_CLOSING:
    case FSM_STOPPING:
        break;
    }
}

/***************************************************************************\
* ReceiveConfigAck
*
\***************************************************************************/

VOID ReceiveConfigAck(DWORD CpIndex, PPP_CONFIG *pRecvConfig)
{
    // The Id of the Ack HAS to match the Id of the last request sent
    // If it is different, then we should silently discard it.
    if (pRecvConfig->Id != gInfo.LastId) {
        return;
    }

    ClearTimeout();

    switch (gInfo.State) {
    case FSM_REQ_SENT:
        if (!FsmConfigResultReceived(CpIndex, pRecvConfig)) {
            return;
        }

        InitRestartCounters();
        gInfo.State = FSM_ACK_RCVD;
        break;

    case FSM_ACK_SENT:
        if (!FsmConfigResultReceived(CpIndex, pRecvConfig)) {
            return;
        }

        InitRestartCounters();
        gInfo.State = FSM_OPENED;
        FsmThisLayerUp(CpIndex);
        break;

    case FSM_OPENED:            
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }

        // fall through

    case FSM_ACK_RCVD:  
        if (!FsmSendConfigReq(CpIndex)) {
            return;
        }

        gInfo.State = FSM_REQ_SENT;
        break;

    case FSM_CLOSED:
    case FSM_STOPPED:
        // Out of Sync; kill the remote 
        FsmSendTermAck(CpIndex, pRecvConfig);
        break;

    case FSM_CLOSING:
    case FSM_STOPPING:
        break;
    }
}

/***************************************************************************\
* ReceiveConfigNakRej
*
\***************************************************************************/

VOID ReceiveConfigNakRej(DWORD CpIndex, PPP_CONFIG *pRecvConfig)
{
    // The Id of the Nak/Rej HAS to match the Id of the last request sent
    // If it is different, then we should silently discard it.
    if (pRecvConfig->Id != gInfo.LastId) {
        return;
    }

    ClearTimeout();

    switch (gInfo.State) {
    case FSM_REQ_SENT:
    case FSM_ACK_SENT:
        if (!FsmConfigResultReceived(CpIndex, pRecvConfig)) {
            return;
        }

        InitRestartCounters();

        if (!FsmSendConfigReq(CpIndex)) {
            return;
        }
        break;

    case FSM_OPENED:            
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }

        // fall through

    case FSM_ACK_RCVD:          
        if (!FsmSendConfigReq(CpIndex)) {
            return;
        }

        gInfo.State = FSM_REQ_SENT;
        break;

    case FSM_CLOSED:
    case FSM_STOPPED:
        // Out of Sync; kill the remote 
        FsmSendTermAck(CpIndex, pRecvConfig);
        break;

    case FSM_CLOSING:
    case FSM_STOPPING:
        break;
    }
}

/***************************************************************************\
* ReceiveTermReq
*
\***************************************************************************/

VOID ReceiveTermReq(DWORD CpIndex, PPP_CONFIG *pConfig)
{
    switch (gInfo.State) {
    case FSM_OPENED:
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }

        FsmSendTermAck(CpIndex, pConfig);
        gInfo.State = FSM_STOPPING;
        break;

    case FSM_ACK_RCVD:
    case FSM_ACK_SENT:
    case FSM_REQ_SENT:
        FsmSendTermAck(CpIndex, pConfig);
        gInfo.State = FSM_REQ_SENT;
        break;

    case FSM_CLOSED:
    case FSM_CLOSING:
    case FSM_STOPPED:
    case FSM_STOPPING:
        FsmSendTermAck(CpIndex, pConfig);
        break;
    }

    FsmThisLayerFinished(CpIndex);
}

/***************************************************************************\
* ReceiveTermAck
*
\***************************************************************************/

VOID ReceiveTermAck(DWORD CpIndex, PPP_CONFIG *pRecvConfig)
{
    // The Id of the Term Ack HAS to match the Id of the last request sent
    // If it is different, then we should silently discard it.
    if (pRecvConfig->Id != gInfo.LastId) {
        return;
    }

    switch (gInfo.State) {
    case FSM_OPENED:
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }

        if (!FsmSendConfigReq(CpIndex)) {
            return;
        }

        gInfo.State = FSM_REQ_SENT;
        break;

    case FSM_ACK_RCVD:
        gInfo.State = FSM_REQ_SENT;
        break;

    case FSM_CLOSING:
    case FSM_STOPPING:
        if (!FsmThisLayerFinished(CpIndex)) {
            return;
        }

        gInfo.State = (gInfo.State == FSM_CLOSING) ? FSM_CLOSED : FSM_STOPPED;
        break;

    case FSM_REQ_SENT:
    case FSM_ACK_SENT:
    case FSM_CLOSED:
    case FSM_STOPPED:
        break;
    }
}

/***************************************************************************\
* ReceiveUnknownCode 
*
\***************************************************************************/

VOID ReceiveUnknownCode(DWORD CpIndex, PPP_CONFIG *pConfig)
{
    switch (gInfo.State) {
    case FSM_STOPPED:
    case FSM_STOPPING:
    case FSM_OPENED:
    case FSM_ACK_SENT:
    case FSM_ACK_RCVD:
    case FSM_REQ_SENT:
    case FSM_CLOSING:
    case FSM_CLOSED:
        FsmSendCodeReject(CpIndex, pConfig);
        break;
    }
}

/***************************************************************************\
* ReceiveDiscardReq
*
\***************************************************************************/

VOID ReceiveDiscardReq(DWORD CpIndex, PPP_CONFIG *pConfig)
{
}

/***************************************************************************\
* ReceiveEchoReq
*
\***************************************************************************/

VOID ReceiveEchoReq(DWORD CpIndex, PPP_CONFIG *pConfig)
{
    // Silently discard this packet if LCP is not in an opened state
    if (!IsLcpOpened()) {
        return;
    }

    switch (gInfo.State) {
    case FSM_STOPPED:
    case FSM_STOPPING:
    case FSM_ACK_SENT:
    case FSM_ACK_RCVD:
    case FSM_REQ_SENT:
    case FSM_CLOSING:
    case FSM_CLOSED:
    case FSM_STARTING:
    case FSM_INITIAL:
        break;

    case FSM_OPENED:
        FsmSendEchoReply(CpIndex, pConfig);
        break;
    }
}

/***************************************************************************\
* ReceiveEchoReply
*
\***************************************************************************/

VOID ReceiveEchoReply(DWORD CpIndex, PPP_CONFIG *pConfig)
{
}

/***************************************************************************\
* ReceiveCodeRej
*
\***************************************************************************/

VOID ReceiveCodeRej(DWORD CpIndex, PPP_CONFIG *pConfig)
{
    pConfig = (PPP_CONFIG*)pConfig->Data;

    // First check to see if these codes may be rejected without 
    // affecting implementation. Permitted code rejects
    if (CpIndex == CP_LCP) {
        switch (pConfig->Code) {
        case CONFIG_REQ:
        case CONFIG_ACK:
        case CONFIG_NAK:
        case CONFIG_REJ:
        case TERM_REQ:
        case TERM_ACK:
        case CODE_REJ:
        case PROT_REJ:
        case ECHO_REQ:
        case ECHO_REPLY:
        case DISCARD_REQ:
            // Unpermitted code rejects.
            break;

        default:
            // Permitted code rejects, we can still work.
            switch (gInfo.State) {
            case FSM_ACK_RCVD:
                gInfo.State = FSM_REQ_SENT;
                break;
            }

            return;
        }
    }
    // Actually the remote side did not reject the protocol, it rejected
    // the code. But for all practical purposes we cannot talk with
    // the corresponding CP on the remote side. This is actually an
    // implementation error in the remote side.
    gInfo.dwError = ERROR_PPP_NOT_CONVERGING;

    switch (gInfo.State) {
    case FSM_CLOSING:
        if (!FsmThisLayerFinished(CpIndex)) {
            return;
        }
        gInfo.State = FSM_CLOSED;
        break;

    case FSM_REQ_SENT:
    case FSM_ACK_RCVD:
    case FSM_ACK_SENT:
    case FSM_STOPPING:
        if (!FsmThisLayerFinished(CpIndex)) {
            return;
        }
        gInfo.State = FSM_STOPPED;
        break;

    case FSM_OPENED:
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }

        InitRestartCounters();
        FsmSendTermReq(CpIndex);
        gInfo.State = FSM_STOPPING;
        break;

    case FSM_CLOSED:
    case FSM_STOPPED:
        break;
    }
}

/***************************************************************************\
* ReceiveProtocolRej
*
\***************************************************************************/

VOID ReceiveProtocolRej(PPP_PACKET *pPacket)
{
    PPP_CONFIG *pRecvConfig = (PPP_CONFIG*)pPacket->Data;
    DWORD dwProtocol  = WireToHostFormat16(pRecvConfig->Data);
    DWORD CpIndex;

    CpIndex = GetCpIndexFromProtocol(dwProtocol);

    if (CpIndex == (DWORD)-1) {
        return;
    }

    if (CpIndex == CP_LCP) {
        gInfo.dwError = ERROR_PPP_NOT_CONVERGING;
        return;
    }

    // If LCP is not in the opened state we silently discard this packet 
    if (!IsLcpOpened()) {
        return;
    }

    gInfo.dwError = ERROR_PPP_CP_REJECTED;

    switch (gInfo.State) {
    case FSM_CLOSING:
        if (!FsmThisLayerFinished(CpIndex)) {
            return;
        }

        gInfo.State = FSM_CLOSED;
        break;

    case FSM_REQ_SENT:
    case FSM_ACK_RCVD:
    case FSM_ACK_SENT:
    case FSM_STOPPING:
        if (!FsmThisLayerFinished(CpIndex)) {
            return;
        }

        gInfo.State = FSM_STOPPED;
        break;

    case FSM_OPENED:
        if (!FsmThisLayerDown(CpIndex)) {
            return;
        }

        InitRestartCounters();
        FsmSendTermReq(CpIndex);
        gInfo.State = FSM_STOPPING;
        break;

    case FSM_CLOSED:
    case FSM_STOPPED:
        break;
    }
}

/***************************************************************************\
* FsmConfigResultReceived
*
\***************************************************************************/

BOOL FsmConfigResultReceived(DWORD CpIndex, PPP_CONFIG *pRecvConfig)
{
    DWORD dwRetCode; 

    switch (pRecvConfig->Code) {
    case CONFIG_NAK:
        dwRetCode = gCpTable[CpIndex].CP_ConfigNakReceived(pRecvConfig);
        break;

    case CONFIG_ACK:
        dwRetCode = gCpTable[CpIndex].CP_ConfigAckReceived(pRecvConfig);
        break;

    case CONFIG_REJ:
        dwRetCode = gCpTable[CpIndex].CP_ConfigRejReceived(pRecvConfig);
        break;

    default:
        return FALSE;
    }

    if (dwRetCode != NO_ERROR) {
        if (dwRetCode == ERROR_PPP_INVALID_PACKET) {
            return TRUE;
        } else {
            FsmClose(CpIndex);
            return FALSE;
        }
    }

    return TRUE;
}

/***************************************************************************\
* FsmReceive
*
\***************************************************************************/

VOID FsmReceive(PPP_PACKET *pPacket, DWORD dwPacketLength)
{
    DWORD dwProtocol;
    DWORD CpIndex;
    PPP_CONFIG *pRecvConfig;
    DWORD dwLength;
    BOOL fAuth = FALSE;
    
    // Validate length of packet
    if (dwPacketLength < (PPP_PACKET_HDR_LEN + PPP_CONFIG_HDR_LEN)) {
        return;
    }

    dwProtocol = WireToHostFormat16(pPacket->Protocol);
    CpIndex = GetCpIndexFromProtocol(dwProtocol);
    if (CpIndex == (DWORD)-1) {
        DbgPrint("Unknown protocol in PPP packet\n");
        return;
    }

    switch (gInfo.Phase) {
    case PPP_AP:
        if (CpIndex == GetCpIndexFromProtocol(gLcp.Remote.Work.AP)) {
            fAuth = TRUE;
            break;
        }

        // fall through

    case PPP_LCP:
        if (CpIndex == CP_LCP) {
            break;
        }

        return;
    }

    pRecvConfig = (PPP_CONFIG*)pPacket->Data;

    dwLength = WireToHostFormat16(pRecvConfig->Length);

    if (dwLength > (dwPacketLength - PPP_PACKET_HDR_LEN) || 
            (dwLength < PPP_CONFIG_HDR_LEN)) {
        return;
    }

    // Not in ProcessPacket table since parameters to this are different.
    if (CpIndex == CP_LCP && pRecvConfig->Code == PROT_REJ) {
        ReceiveProtocolRej(pPacket);
        return;
    }

    // Make sure that the protocol can handle the config code sent.
    if (pRecvConfig->Code == 0 || 
            pRecvConfig->Code > gCpTable[CpIndex].Recognize) {
        ReceiveUnknownCode(CpIndex, pRecvConfig);
        return;
    }

    if (fAuth) {
        ApWork(CpIndex, pRecvConfig);
    } else {
        gReceive[pRecvConfig->Code](CpIndex, pRecvConfig);
    }
}

