/** @file
  Routines to process TCP option.

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

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "TcpMain.h"

/**
  Get a UINT16 value from buffer.

  @param[in] Buf              Pointer to input buffer.

  @return                     The UINT16 value obtained from the buffer.

**/
UINT16
TcpGetUint16 (
  IN UINT8  *Buf
  )
{
  UINT16  Value;

  CopyMem (&Value, Buf, sizeof (UINT16));
  return NTOHS (Value);
}

/**
  Get a UINT32 value from buffer.

  @param[in] Buf              Pointer to input buffer.

  @return                     The UINT32 value obtained from the buffer.

**/
UINT32
TcpGetUint32 (
  IN UINT8  *Buf
  )
{
  UINT32  Value;

  CopyMem (&Value, Buf, sizeof (UINT32));
  return NTOHL (Value);
}

/**
  Put a UINT32 value in buffer.

  @param[out] Buf             Pointer to the buffer.
  @param[in] Data             The UINT32 Date to put in the buffer.

**/
VOID
TcpPutUint32 (
  OUT UINT8      *Buf,
  IN     UINT32  Data
  )
{
  Data = HTONL (Data);
  CopyMem (Buf, &Data, sizeof (UINT32));
}

/**
  Compute the window scale value according to the given buffer size.

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

  @return         The scale value.

**/
UINT8
TcpComputeScale (
  IN TCP_CB  *Tcb
  )
{
  UINT8   Scale;
  UINT32  BufSize;

  ASSERT ((Tcb != NULL) && (Tcb->Sk != NULL));

  BufSize = GET_RCV_BUFFSIZE (Tcb->Sk);

  Scale = 0;
  while ((Scale < TCP_OPTION_MAX_WS) && ((UINT32)(TCP_OPTION_MAX_WIN << Scale) < BufSize)) {
    Scale++;
  }

  return Scale;
}

/**
  Build the TCP option in three-way handshake.

  @param[in]  Tcb     Pointer to the TCP_CB of this TCP instance.
  @param[in]  Nbuf    Pointer to the buffer to store the options.

  @return             The total length of the TCP option field.

**/
UINT16
TcpSynBuildOption (
  IN TCP_CB   *Tcb,
  IN NET_BUF  *Nbuf
  )
{
  UINT8   *Data;
  UINT16  Len;

  ASSERT ((Tcb != NULL) && (Nbuf != NULL) && (Nbuf->Tcp == NULL));

  Len = 0;

  //
  // Add a timestamp option if not disabled by the application
  // and it is the first SYN segment, or the peer has sent
  // us its timestamp.
  //
  if (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS) &&
      (!TCP_FLG_ON (TCPSEG_NETBUF (Nbuf)->Flag, TCP_FLG_ACK) ||
       TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RCVD_TS))
      )
  {
    Data = NetbufAllocSpace (
             Nbuf,
             TCP_OPTION_TS_ALIGNED_LEN,
             NET_BUF_HEAD
             );

    ASSERT (Data != NULL);
    Len += TCP_OPTION_TS_ALIGNED_LEN;

    TcpPutUint32 (Data, TCP_OPTION_TS_FAST);
    TcpPutUint32 (Data + 4, mTcpTick);
    TcpPutUint32 (Data + 8, 0);
  }

  //
  // Build window scale option, only when configured
  // to send WS option, and either we are doing active
  // open or we have received WS option from peer.
  //
  if (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS) &&
      (!TCP_FLG_ON (TCPSEG_NETBUF (Nbuf)->Flag, TCP_FLG_ACK) ||
       TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RCVD_WS))
      )
  {
    Data = NetbufAllocSpace (
             Nbuf,
             TCP_OPTION_WS_ALIGNED_LEN,
             NET_BUF_HEAD
             );

    ASSERT (Data != NULL);

    Len += TCP_OPTION_WS_ALIGNED_LEN;
    TcpPutUint32 (Data, TCP_OPTION_WS_FAST | TcpComputeScale (Tcb));
  }

  //
  // Build the MSS option.
  //
  Data = NetbufAllocSpace (Nbuf, TCP_OPTION_MSS_LEN, 1);
  ASSERT (Data != NULL);

  Len += TCP_OPTION_MSS_LEN;
  TcpPutUint32 (Data, TCP_OPTION_MSS_FAST | Tcb->RcvMss);

  return Len;
}

/**
  Build the TCP option in synchronized states.

  @param[in]  Tcb     Pointer to the TCP_CB of this TCP instance.
  @param[in]  Nbuf    Pointer to the buffer to store the options.

  @return             The total length of the TCP option field.

**/
UINT16
TcpBuildOption (
  IN TCP_CB   *Tcb,
  IN NET_BUF  *Nbuf
  )
{
  UINT8   *Data;
  UINT16  Len;

  ASSERT ((Tcb != NULL) && (Nbuf != NULL) && (Nbuf->Tcp == NULL));
  Len = 0;

  //
  // Build the Timestamp option.
  //
  if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_SND_TS) &&
      !TCP_FLG_ON (TCPSEG_NETBUF (Nbuf)->Flag, TCP_FLG_RST)
      )
  {
    Data = NetbufAllocSpace (
             Nbuf,
             TCP_OPTION_TS_ALIGNED_LEN,
             NET_BUF_HEAD
             );

    ASSERT (Data != NULL);
    Len += TCP_OPTION_TS_ALIGNED_LEN;

    TcpPutUint32 (Data, TCP_OPTION_TS_FAST);
    TcpPutUint32 (Data + 4, mTcpTick);
    TcpPutUint32 (Data + 8, Tcb->TsRecent);
  }

  return Len;
}

/**
  Parse the supported options.

  @param[in]       Tcp     Pointer to the TCP_CB of this TCP instance.
  @param[in, out]  Option  Pointer to the TCP_OPTION used to store the
                           successfully pasrsed options.

  @retval          0       The options are successfully pasrsed.
  @retval          -1      Illegal option was found.

**/
INTN
TcpParseOption (
  IN     TCP_HEAD    *Tcp,
  IN OUT TCP_OPTION  *Option
  )
{
  UINT8  *Head;
  UINT8  TotalLen;
  UINT8  Cur;
  UINT8  Type;
  UINT8  Len;

  ASSERT ((Tcp != NULL) && (Option != NULL));

  Option->Flag = 0;

  TotalLen = (UINT8)((Tcp->HeadLen << 2) - sizeof (TCP_HEAD));
  if (TotalLen <= 0) {
    return 0;
  }

  Head = (UINT8 *)(Tcp + 1);

  //
  // Fast process of the timestamp option.
  //
  if ((TotalLen == TCP_OPTION_TS_ALIGNED_LEN) && (TcpGetUint32 (Head) == TCP_OPTION_TS_FAST)) {
    Option->TSVal = TcpGetUint32 (Head + 4);
    Option->TSEcr = TcpGetUint32 (Head + 8);
    Option->Flag  = TCP_OPTION_RCVD_TS;

    return 0;
  }

  //
  // Slow path to process the options.
  //
  Cur = 0;

  while (Cur < TotalLen) {
    Type = Head[Cur];

    switch (Type) {
      case TCP_OPTION_MSS:
        Len = Head[Cur + 1];

        if ((Len != TCP_OPTION_MSS_LEN) || (TotalLen - Cur < TCP_OPTION_MSS_LEN)) {
          return -1;
        }

        Option->Mss = TcpGetUint16 (&Head[Cur + 2]);
        TCP_SET_FLG (Option->Flag, TCP_OPTION_RCVD_MSS);

        Cur += TCP_OPTION_MSS_LEN;
        break;

      case TCP_OPTION_WS:
        Len = Head[Cur + 1];

        if ((Len != TCP_OPTION_WS_LEN) || (TotalLen - Cur < TCP_OPTION_WS_LEN)) {
          return -1;
        }

        Option->WndScale = (UINT8)MIN (14, Head[Cur + 2]);
        TCP_SET_FLG (Option->Flag, TCP_OPTION_RCVD_WS);

        Cur += TCP_OPTION_WS_LEN;
        break;

      case TCP_OPTION_TS:
        Len = Head[Cur + 1];

        if ((Len != TCP_OPTION_TS_LEN) || (TotalLen - Cur < TCP_OPTION_TS_LEN)) {
          return -1;
        }

        Option->TSVal = TcpGetUint32 (&Head[Cur + 2]);
        Option->TSEcr = TcpGetUint32 (&Head[Cur + 6]);
        TCP_SET_FLG (Option->Flag, TCP_OPTION_RCVD_TS);

        Cur += TCP_OPTION_TS_LEN;
        break;

      case TCP_OPTION_NOP:
        Cur++;
        break;

      case TCP_OPTION_EOP:
        Cur = TotalLen;
        break;

      default:
        Len = Head[Cur + 1];

        if (((TotalLen - Cur) < Len) || (Len < 2)) {
          return -1;
        }

        Cur = (UINT8)(Cur + Len);
        break;
    }
  }

  return 0;
}
