/** @file
  TCP timer related functions.

  Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "TcpMain.h"

UINT32    mTcpTick = 1000;

/**
  Connect timeout handler.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpConnectTimeout (
  IN OUT TCP_CB *Tcb
  );

/**
  Timeout handler for TCP retransmission timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpRexmitTimeout (
  IN OUT TCP_CB *Tcb
  );

/**
  Timeout handler for window probe timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpProbeTimeout (
  IN OUT TCP_CB *Tcb
  );

/**
  Timeout handler for keepalive timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpKeepaliveTimeout (
  IN OUT TCP_CB *Tcb
  );

/**
  Timeout handler for FIN_WAIT_2 timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpFinwait2Timeout (
  IN OUT TCP_CB *Tcb
  );

/**
  Timeout handler for 2MSL timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
Tcp2MSLTimeout (
  IN OUT TCP_CB *Tcb
  );

TCP_TIMER_HANDLER mTcpTimerHandler[TCP_TIMER_NUMBER] = {
  TcpConnectTimeout,
  TcpRexmitTimeout,
  TcpProbeTimeout,
  TcpKeepaliveTimeout,
  TcpFinwait2Timeout,
  Tcp2MSLTimeout,
};

/**
  Close the TCP connection.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpClose (
  IN OUT TCP_CB *Tcb
  )
{
  NetbufFreeList (&Tcb->SndQue);
  NetbufFreeList (&Tcb->RcvQue);

  TcpSetState (Tcb, TCP_CLOSED);
}

/**
  Backoff the RTO.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpBackoffRto (
  IN OUT TCP_CB *Tcb
  )
{
  //
  // Fold the RTT estimate if too many times, the estimate
  // may be wrong, fold it. So the next time a valid
  // measurement is sampled, we can start fresh.
  //
  if ((Tcb->LossTimes >= TCP_FOLD_RTT) && (Tcb->SRtt != 0)) {
    Tcb->RttVar += Tcb->SRtt >> 2;
    Tcb->SRtt = 0;
  }

  Tcb->Rto <<= 1;

  if (Tcb->Rto < TCP_RTO_MIN) {

    Tcb->Rto = TCP_RTO_MIN;
  } else if (Tcb->Rto > TCP_RTO_MAX) {

    Tcb->Rto = TCP_RTO_MAX;
  }
}

/**
  Connect timeout handler.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpConnectTimeout (
  IN OUT TCP_CB *Tcb
  )
{
  if (!TCP_CONNECTED (Tcb->State)) {
    DEBUG (
      (EFI_D_ERROR,
      "TcpConnectTimeout: connection closed because conenction timer timeout for TCB %p\n",
      Tcb)
      );

    if (EFI_ABORTED == Tcb->Sk->SockError) {
      SOCK_ERROR (Tcb->Sk, EFI_TIMEOUT);
    }

    if (TCP_SYN_RCVD == Tcb->State) {
      DEBUG (
        (EFI_D_WARN,
        "TcpConnectTimeout: send reset because connection timer timeout for TCB %p\n",
        Tcb)
        );

      TcpResetConnection (Tcb);

    }

    TcpClose (Tcb);
  }
}


/**
  Timeout handler for TCP retransmission timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpRexmitTimeout (
  IN OUT TCP_CB *Tcb
  )
{
  UINT32  FlightSize;

  DEBUG (
    (EFI_D_WARN,
    "TcpRexmitTimeout: transmission timeout for TCB %p\n",
    Tcb)
    );

  //
  // Set the congestion window. FlightSize is the
  // amount of data that has been sent but not
  // yet ACKed.
  //
  FlightSize        = TCP_SUB_SEQ (Tcb->SndNxt, Tcb->SndUna);
  Tcb->Ssthresh     = MAX ((UINT32) (2 * Tcb->SndMss), FlightSize / 2);

  Tcb->CWnd         = Tcb->SndMss;
  Tcb->LossRecover  = Tcb->SndNxt;

  Tcb->LossTimes++;
  if ((Tcb->LossTimes > Tcb->MaxRexmit) && !TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_CONNECT)) {

    DEBUG (
      (EFI_D_ERROR,
      "TcpRexmitTimeout: connection closed because too many timeouts for TCB %p\n",
      Tcb)
      );

    if (EFI_ABORTED == Tcb->Sk->SockError) {
      SOCK_ERROR (Tcb->Sk, EFI_TIMEOUT);
    }

    TcpClose (Tcb);
    return ;
  }

  TcpBackoffRto (Tcb);
  TcpRetransmit (Tcb, Tcb->SndUna);
  TcpSetTimer (Tcb, TCP_TIMER_REXMIT, Tcb->Rto);

  Tcb->CongestState = TCP_CONGEST_LOSS;

  TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_RTT_ON);
}

/**
  Timeout handler for window probe timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpProbeTimeout (
  IN OUT TCP_CB *Tcb
  )
{
  //
  // This is the timer for sender's SWSA. RFC1122 requires
  // a timer set for sender's SWSA, and suggest combine it
  // with window probe timer. If data is sent, don't set
  // the probe timer, since retransmit timer is on.
  //
  if ((TcpDataToSend (Tcb, 1) != 0) && (TcpToSendData (Tcb, 1) > 0)) {

    ASSERT (TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_REXMIT) != 0);
    Tcb->ProbeTimerOn = FALSE;
    return ;
  }

  TcpSendZeroProbe (Tcb);
  TcpSetProbeTimer (Tcb);
}

/**
  Timeout handler for keepalive timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpKeepaliveTimeout (
  IN OUT TCP_CB *Tcb
  )
{
  Tcb->KeepAliveProbes++;

  //
  // Too many Keep-alive probes, drop the connection
  //
  if (Tcb->KeepAliveProbes > Tcb->MaxKeepAlive) {

    if (EFI_ABORTED == Tcb->Sk->SockError) {
      SOCK_ERROR (Tcb->Sk, EFI_TIMEOUT);
    }

    TcpClose (Tcb);
    return ;
  }

  TcpSendZeroProbe (Tcb);
  TcpSetKeepaliveTimer (Tcb);
}

/**
  Timeout handler for FIN_WAIT_2 timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpFinwait2Timeout (
  IN OUT TCP_CB *Tcb
  )
{
  DEBUG (
    (EFI_D_WARN,
    "TcpFinwait2Timeout: connection closed because FIN_WAIT2 timer timeouts for TCB %p\n",
    Tcb)
    );

  TcpClose (Tcb);
}

/**
  Timeout handler for 2MSL timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
Tcp2MSLTimeout (
  IN OUT TCP_CB *Tcb
  )
{
  DEBUG (
    (EFI_D_WARN,
    "Tcp2MSLTimeout: connection closed because TIME_WAIT timer timeouts for TCB %p\n",
    Tcb)
    );

  TcpClose (Tcb);
}

/**
  Update the timer status and the next expire time according to the timers
  to expire in a specific future time slot.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpUpdateTimer (
  IN OUT TCP_CB *Tcb
  )
{
  UINT16  Index;

  //
  // Don't use a too large value to init NextExpire
  // since mTcpTick wraps around as sequence no does.
  //
  Tcb->NextExpire = TCP_EXPIRE_TIME;
  TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_TIMER_ON);

  for (Index = 0; Index < TCP_TIMER_NUMBER; Index++) {

    if (TCP_TIMER_ON (Tcb->EnabledTimer, Index) &&
        TCP_TIME_LT (Tcb->Timer[Index], mTcpTick + Tcb->NextExpire)
        ) {

      Tcb->NextExpire = TCP_SUB_TIME (Tcb->Timer[Index], mTcpTick);
      TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_TIMER_ON);
    }
  }
}

/**
  Enable a TCP timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.
  @param[in]       Timer    The index of the timer to be enabled.
  @param[in]       TimeOut  The timeout value of this timer.

**/
VOID
TcpSetTimer (
  IN OUT TCP_CB *Tcb,
  IN     UINT16 Timer,
  IN     UINT32 TimeOut
  )
{
  TCP_SET_TIMER (Tcb->EnabledTimer, Timer);
  Tcb->Timer[Timer] = mTcpTick + TimeOut;

  TcpUpdateTimer (Tcb);
}

/**
  Clear one TCP timer.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.
  @param[in]       Timer    The index of the timer to be cleared.

**/
VOID
TcpClearTimer (
  IN OUT TCP_CB *Tcb,
  IN     UINT16 Timer
  )
{
  TCP_CLEAR_TIMER (Tcb->EnabledTimer, Timer);
  TcpUpdateTimer (Tcb);
}

/**
  Clear all TCP timers.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpClearAllTimer (
  IN OUT TCP_CB *Tcb
  )
{
  Tcb->EnabledTimer = 0;
  TcpUpdateTimer (Tcb);
}

/**
  Enable the window prober timer and set the timeout value.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpSetProbeTimer (
  IN OUT TCP_CB *Tcb
  )
{
  if (!Tcb->ProbeTimerOn) {
    Tcb->ProbeTime    = Tcb->Rto;
    Tcb->ProbeTimerOn = TRUE;

  } else {
    Tcb->ProbeTime <<= 1;
  }

  if (Tcb->ProbeTime < TCP_RTO_MIN) {

    Tcb->ProbeTime = TCP_RTO_MIN;
  } else if (Tcb->ProbeTime > TCP_RTO_MAX) {

    Tcb->ProbeTime = TCP_RTO_MAX;
  }

  TcpSetTimer (Tcb, TCP_TIMER_PROBE, Tcb->ProbeTime);
}

/**
  Enable the keepalive timer and set the timeout value.

  @param[in, out]  Tcb      Pointer to the TCP_CB of this TCP instance.

**/
VOID
TcpSetKeepaliveTimer (
  IN OUT TCP_CB *Tcb
  )
{
  if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_KEEPALIVE)) {
    return ;

  }

  //
  // Set the timer to KeepAliveIdle if either
  // 1. the keepalive timer is off
  // 2. The keepalive timer is on, but the idle
  // is less than KeepAliveIdle, that means the
  // connection is alive since our last probe.
  //
  if (!TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_KEEPALIVE) ||
      (Tcb->Idle < Tcb->KeepAliveIdle)
      ) {

    TcpSetTimer (Tcb, TCP_TIMER_KEEPALIVE, Tcb->KeepAliveIdle);
    Tcb->KeepAliveProbes = 0;

  } else {

    TcpSetTimer (Tcb, TCP_TIMER_KEEPALIVE, Tcb->KeepAlivePeriod);
  }
}

/**
  Heart beat timer handler.

  @param[in]  Context        Context of the timer event, ignored.

**/
VOID
EFIAPI
TcpTickingDpc (
  IN VOID       *Context
  )
{
  LIST_ENTRY      *Entry;
  LIST_ENTRY      *Next;
  TCP_CB          *Tcb;
  INT16           Index;

  mTcpTick++;
  mTcpGlobalIss += TCP_ISS_INCREMENT_2;

  //
  // Don't use LIST_FOR_EACH, which isn't delete safe.
  //
  for (Entry = mTcpRunQue.ForwardLink; Entry != &mTcpRunQue; Entry = Next) {

    Next  = Entry->ForwardLink;

    Tcb   = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    if (Tcb->State == TCP_CLOSED) {
      continue;
    }
    //
    // The connection is doing RTT measurement.
    //
    if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RTT_ON)) {
      Tcb->RttMeasure++;
    }

    Tcb->Idle++;

    if (Tcb->DelayedAck != 0) {
      TcpSendAck (Tcb);
    }

    if (Tcb->IpInfo->IpVersion == IP_VERSION_6 && Tcb->Tick > 0) {
      Tcb->Tick--;
    }

    //
    // No timer is active or no timer expired
    //
    if (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_TIMER_ON) || ((--Tcb->NextExpire) > 0)) {

      continue;
    }

    //
    // Call the timeout handler for each expired timer.
    //
    for (Index = 0; Index < TCP_TIMER_NUMBER; Index++) {

      if (TCP_TIMER_ON (Tcb->EnabledTimer, Index) && TCP_TIME_LEQ (Tcb->Timer[Index], mTcpTick)) {
        //
        // disable the timer before calling the handler
        // in case the handler enables it again.
        //
        TCP_CLEAR_TIMER (Tcb->EnabledTimer, Index);
        mTcpTimerHandler[Index](Tcb);

        //
        // The Tcb may have been deleted by the timer, or
        // no other timer is set.
        //
        if ((Next->BackLink != Entry) || (Tcb->EnabledTimer == 0)) {
          break;
        }
      }
    }

    //
    // If the Tcb still exist or some timer is set, update the timer
    //
    if (Index == TCP_TIMER_NUMBER) {
      TcpUpdateTimer (Tcb);
    }
  }
}

/**
  Heart beat timer handler, queues the DPC at TPL_CALLBACK.

  @param[in]  Event    Timer event signaled, ignored.
  @param[in]  Context  Context of the timer event, ignored.

**/
VOID
EFIAPI
TcpTicking (
  IN EFI_EVENT Event,
  IN VOID      *Context
  )
{
  QueueDpc (TPL_CALLBACK, TcpTickingDpc, Context);
}