/** @file
  The implementation of a dispatch routine for processing TCP requests.

  (C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2018, 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"

/**
  Add or remove a route entry in the IP route table associated with this TCP instance.

  @param[in]  Tcb               Pointer to the TCP_CB of this TCP instance.
  @param[in]  RouteInfo         Pointer to the route information to be processed.

  @retval EFI_SUCCESS           The operation completed successfully.
  @retval EFI_NOT_STARTED       The driver instance has not been started.
  @retval EFI_NO_MAPPING        When using the default address, configuration(DHCP,
                                BOOTP, RARP, etc.) is not finished yet.
  @retval EFI_OUT_OF_RESOURCES  Could not add the entry to the routing table.
  @retval EFI_NOT_FOUND         This route is not in the routing table
                                (when RouteInfo->DeleteRoute is TRUE).
  @retval EFI_ACCESS_DENIED     The route is already defined in the routing table
                                (when RouteInfo->DeleteRoute is FALSE).
**/
EFI_STATUS
Tcp4Route (
  IN TCP_CB           *Tcb,
  IN TCP4_ROUTE_INFO  *RouteInfo
  )
{
  IP_IO_IP_PROTOCOL   Ip;

  Ip = Tcb->IpInfo->Ip;

  ASSERT (Ip.Ip4!= NULL);

  return Ip.Ip4->Routes (
                   Ip.Ip4,
                   RouteInfo->DeleteRoute,
                   RouteInfo->SubnetAddress,
                   RouteInfo->SubnetMask,
                   RouteInfo->GatewayAddress
                   );

}

/**
  Get the operational settings of this TCPv4 instance.

  @param[in]       Tcb           Pointer to the TCP_CB of this TCP instance.
  @param[in, out]  Mode          Pointer to the buffer to store the operational
                                 settings.

  @retval EFI_SUCCESS            The mode data was read.
  @retval EFI_NOT_STARTED        No configuration data is available because this
                                 instance hasn't been started.

**/
EFI_STATUS
Tcp4GetMode (
  IN     TCP_CB         *Tcb,
  IN OUT TCP4_MODE_DATA *Mode
  )
{
  SOCKET                *Sock;
  EFI_TCP4_CONFIG_DATA  *ConfigData;
  EFI_TCP4_ACCESS_POINT *AccessPoint;
  EFI_TCP4_OPTION       *Option;
  EFI_IP4_PROTOCOL      *Ip;

  Sock = Tcb->Sk;

  if (!SOCK_IS_CONFIGURED (Sock) && (Mode->Tcp4ConfigData != NULL)) {
    return EFI_NOT_STARTED;
  }

  if (Mode->Tcp4State != NULL) {
    *(Mode->Tcp4State) = (EFI_TCP4_CONNECTION_STATE) Tcb->State;
  }

  if (Mode->Tcp4ConfigData != NULL) {

    ConfigData                       = Mode->Tcp4ConfigData;
    AccessPoint                      = &(ConfigData->AccessPoint);
    Option                           = ConfigData->ControlOption;

    ConfigData->TypeOfService        = Tcb->Tos;
    ConfigData->TimeToLive           = Tcb->Ttl;

    AccessPoint->UseDefaultAddress   = Tcb->UseDefaultAddr;

    IP4_COPY_ADDRESS (&AccessPoint->StationAddress, &Tcb->LocalEnd.Ip);

    IP4_COPY_ADDRESS (&AccessPoint->SubnetMask, &Tcb->SubnetMask);
    AccessPoint->StationPort         = NTOHS (Tcb->LocalEnd.Port);

    IP4_COPY_ADDRESS (&AccessPoint->RemoteAddress, &Tcb->RemoteEnd.Ip);

    AccessPoint->RemotePort          = NTOHS (Tcb->RemoteEnd.Port);
    AccessPoint->ActiveFlag          = (BOOLEAN) (Tcb->State != TCP_LISTEN);

    if (Option != NULL) {
      Option->ReceiveBufferSize      = GET_RCV_BUFFSIZE (Tcb->Sk);
      Option->SendBufferSize         = GET_SND_BUFFSIZE (Tcb->Sk);
      Option->MaxSynBackLog          = GET_BACKLOG (Tcb->Sk);

      Option->ConnectionTimeout      = Tcb->ConnectTimeout / TCP_TICK_HZ;
      Option->DataRetries            = Tcb->MaxRexmit;
      Option->FinTimeout             = Tcb->FinWait2Timeout / TCP_TICK_HZ;
      Option->TimeWaitTimeout        = Tcb->TimeWaitTimeout / TCP_TICK_HZ;
      Option->KeepAliveProbes        = Tcb->MaxKeepAlive;
      Option->KeepAliveTime          = Tcb->KeepAliveIdle / TCP_TICK_HZ;
      Option->KeepAliveInterval      = Tcb->KeepAlivePeriod / TCP_TICK_HZ;

      Option->EnableNagle            = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE));
      Option->EnableTimeStamp        = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS));
      Option->EnableWindowScaling    = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS));

      Option->EnableSelectiveAck     = FALSE;
      Option->EnablePathMtuDiscovery = FALSE;
    }
  }

  Ip = Tcb->IpInfo->Ip.Ip4;
  ASSERT (Ip != NULL);

  return Ip->GetModeData (Ip, Mode->Ip4ModeData, Mode->MnpConfigData, Mode->SnpModeData);
}

/**
  Get the operational settings of this TCPv6 instance.

  @param[in]       Tcb           Pointer to the TCP_CB of this TCP instance.
  @param[in, out]  Mode          Pointer to the buffer to store the operational
                                 settings.

  @retval EFI_SUCCESS            The mode data was read.
  @retval EFI_NOT_STARTED        No configuration data is available because this
                                 instance hasn't been started.

**/
EFI_STATUS
Tcp6GetMode (
  IN     TCP_CB         *Tcb,
  IN OUT TCP6_MODE_DATA *Mode
  )
{
  SOCKET                *Sock;
  EFI_TCP6_CONFIG_DATA  *ConfigData;
  EFI_TCP6_ACCESS_POINT *AccessPoint;
  EFI_TCP6_OPTION       *Option;
  EFI_IP6_PROTOCOL      *Ip;

  Sock = Tcb->Sk;

  if (!SOCK_IS_CONFIGURED (Sock) && (Mode->Tcp6ConfigData != NULL)) {
    return EFI_NOT_STARTED;
  }

  if (Mode->Tcp6State != NULL) {
    *(Mode->Tcp6State) = (EFI_TCP6_CONNECTION_STATE) (Tcb->State);
  }

  if (Mode->Tcp6ConfigData != NULL) {

    ConfigData                       = Mode->Tcp6ConfigData;
    AccessPoint                      = &(ConfigData->AccessPoint);
    Option                           = ConfigData->ControlOption;

    ConfigData->TrafficClass         = Tcb->Tos;
    ConfigData->HopLimit             = Tcb->Ttl;

    AccessPoint->StationPort         = NTOHS (Tcb->LocalEnd.Port);
    AccessPoint->RemotePort          = NTOHS (Tcb->RemoteEnd.Port);
    AccessPoint->ActiveFlag          = (BOOLEAN) (Tcb->State != TCP_LISTEN);

    IP6_COPY_ADDRESS (&AccessPoint->StationAddress, &Tcb->LocalEnd.Ip);
    IP6_COPY_ADDRESS (&AccessPoint->RemoteAddress, &Tcb->RemoteEnd.Ip);

    if (Option != NULL) {
      Option->ReceiveBufferSize      = GET_RCV_BUFFSIZE (Tcb->Sk);
      Option->SendBufferSize         = GET_SND_BUFFSIZE (Tcb->Sk);
      Option->MaxSynBackLog          = GET_BACKLOG (Tcb->Sk);

      Option->ConnectionTimeout      = Tcb->ConnectTimeout / TCP_TICK_HZ;
      Option->DataRetries            = Tcb->MaxRexmit;
      Option->FinTimeout             = Tcb->FinWait2Timeout / TCP_TICK_HZ;
      Option->TimeWaitTimeout        = Tcb->TimeWaitTimeout / TCP_TICK_HZ;
      Option->KeepAliveProbes        = Tcb->MaxKeepAlive;
      Option->KeepAliveTime          = Tcb->KeepAliveIdle / TCP_TICK_HZ;
      Option->KeepAliveInterval      = Tcb->KeepAlivePeriod / TCP_TICK_HZ;

      Option->EnableNagle            = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE));
      Option->EnableTimeStamp        = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS));
      Option->EnableWindowScaling    = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS));

      Option->EnableSelectiveAck     = FALSE;
      Option->EnablePathMtuDiscovery = FALSE;
    }
  }

  Ip = Tcb->IpInfo->Ip.Ip6;
  ASSERT (Ip != NULL);

  return Ip->GetModeData (Ip, Mode->Ip6ModeData, Mode->MnpConfigData, Mode->SnpModeData);
}

/**
  If TcpAp->StationPort isn't zero, check whether the access point
  is registered, else generate a random station port for this
  access point.

  @param[in]  TcpAp              Pointer to the access point.
  @param[in]  IpVersion          IP_VERSION_4 or IP_VERSION_6

  @retval EFI_SUCCESS            The check passed or the port is assigned.
  @retval EFI_INVALID_PARAMETER  The non-zero station port is already used.
  @retval EFI_OUT_OF_RESOURCES   No port can be allocated.

**/
EFI_STATUS
TcpBind (
  IN TCP_ACCESS_POINT  *TcpAp,
  IN UINT8             IpVersion
  )
{
  BOOLEAN         Cycle;
  EFI_IP_ADDRESS  Local;
  UINT16          *Port;
  UINT16          *RandomPort;

  if (IpVersion == IP_VERSION_4) {
    IP4_COPY_ADDRESS (&Local, &TcpAp->Tcp4Ap.StationAddress);
    Port       = &TcpAp->Tcp4Ap.StationPort;
    RandomPort = &mTcp4RandomPort;
  } else {
    IP6_COPY_ADDRESS (&Local, &TcpAp->Tcp6Ap.StationAddress);
    Port       = &TcpAp->Tcp6Ap.StationPort;
    RandomPort = &mTcp6RandomPort;
  }

  if (0 != *Port) {
    //
    // Check if a same endpoing is bound.
    //
    if (TcpFindTcbByPeer (&Local, *Port, IpVersion)) {

      return EFI_INVALID_PARAMETER;
    }
  } else {
    //
    // generate a random port
    //
    Cycle = FALSE;

    if (TCP_PORT_USER_RESERVED == *RandomPort) {
      *RandomPort = TCP_PORT_KNOWN;
    }

    (*RandomPort)++;

    while (TcpFindTcbByPeer (&Local, *RandomPort, IpVersion)) {
      (*RandomPort)++;

      if (*RandomPort <= TCP_PORT_KNOWN) {
        if (Cycle) {
          DEBUG (
            (EFI_D_ERROR,
            "TcpBind: no port can be allocated for this pcb\n")
            );
          return EFI_OUT_OF_RESOURCES;
        }

        *RandomPort = TCP_PORT_KNOWN + 1;

        Cycle       = TRUE;
      }
    }

    *Port = *RandomPort;
  }

  return EFI_SUCCESS;
}

/**
  Flush the Tcb add its associated protocols.

  @param[in, out]  Tcb      Pointer to the TCP_CB to be flushed.

**/
VOID
TcpFlushPcb (
  IN OUT TCP_CB *Tcb
  )
{
  SOCKET                    *Sock;

  IpIoConfigIp (Tcb->IpInfo, NULL);

  Sock     = Tcb->Sk;

  if (SOCK_IS_CONFIGURED (Sock)) {
    RemoveEntryList (&Tcb->List);

    if (Sock->DevicePath != NULL) {
      //
      // Uninstall the device path protocl.
      //
      gBS->UninstallProtocolInterface (
             Sock->SockHandle,
             &gEfiDevicePathProtocolGuid,
             Sock->DevicePath
             );

      FreePool (Sock->DevicePath);
      Sock->DevicePath = NULL;
    }
  }

  NetbufFreeList (&Tcb->SndQue);
  NetbufFreeList (&Tcb->RcvQue);
  Tcb->State = TCP_CLOSED;
  Tcb->RemoteIpZero = FALSE;
}

/**
  Attach a Pcb to the socket.

  @param[in]  Sk                 Pointer to the socket of this TCP instance.

  @retval EFI_SUCCESS            The operation completed successfully.
  @retval EFI_OUT_OF_RESOURCES   Failed due to resource limits.

**/
EFI_STATUS
TcpAttachPcb (
  IN SOCKET  *Sk
  )
{
  TCP_CB          *Tcb;
  TCP_PROTO_DATA  *ProtoData;
  IP_IO           *IpIo;
  EFI_STATUS      Status;
  VOID            *Ip;
  EFI_GUID        *IpProtocolGuid;

  if (Sk->IpVersion == IP_VERSION_4) {
    IpProtocolGuid = &gEfiIp4ProtocolGuid;
  } else {
    IpProtocolGuid = &gEfiIp6ProtocolGuid;
  }

  Tcb = AllocateZeroPool (sizeof (TCP_CB));

  if (Tcb == NULL) {

    DEBUG ((EFI_D_ERROR, "TcpConfigurePcb: failed to allocate a TCB\n"));

    return EFI_OUT_OF_RESOURCES;
  }

  ProtoData = (TCP_PROTO_DATA *) Sk->ProtoReserved;
  IpIo      = ProtoData->TcpService->IpIo;

  //
  // Create an IpInfo for this Tcb.
  //
  Tcb->IpInfo = IpIoAddIp (IpIo);
  if (Tcb->IpInfo == NULL) {

    FreePool (Tcb);
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Open the new created IP instance BY_CHILD.
  //
  Status = gBS->OpenProtocol (
                  Tcb->IpInfo->ChildHandle,
                  IpProtocolGuid,
                  &Ip,
                  IpIo->Image,
                  Sk->SockHandle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
  if (EFI_ERROR (Status)) {
    IpIoRemoveIp (IpIo, Tcb->IpInfo);
    return Status;
  }

  InitializeListHead (&Tcb->List);
  InitializeListHead (&Tcb->SndQue);
  InitializeListHead (&Tcb->RcvQue);

  Tcb->State        = TCP_CLOSED;
  Tcb->Sk           = Sk;
  ProtoData->TcpPcb = Tcb;

  return EFI_SUCCESS;
}

/**
  Detach the Pcb of the socket.

  @param[in, out]  Sk           Pointer to the socket of this TCP instance.

**/
VOID
TcpDetachPcb (
  IN OUT SOCKET    *Sk
  )
{
  TCP_PROTO_DATA   *ProtoData;
  TCP_CB           *Tcb;

  ProtoData = (TCP_PROTO_DATA *) Sk->ProtoReserved;
  Tcb       = ProtoData->TcpPcb;

  ASSERT (Tcb != NULL);

  TcpFlushPcb (Tcb);

  IpIoRemoveIp (ProtoData->TcpService->IpIo, Tcb->IpInfo);

  FreePool (Tcb);

  ProtoData->TcpPcb = NULL;
}

/**
  Configure the Pcb using CfgData.

  @param[in]  Sk                 Pointer to the socket of this TCP instance.
  @param[in]  CfgData            Pointer to the TCP configuration data.

  @retval EFI_SUCCESS            The operation completed successfully.
  @retval EFI_INVALID_PARAMETER  A same access point has been configured in
                                 another TCP instance.
  @retval EFI_OUT_OF_RESOURCES   Failed due to resource limits.

**/
EFI_STATUS
TcpConfigurePcb (
  IN SOCKET           *Sk,
  IN TCP_CONFIG_DATA  *CfgData
  )
{
  IP_IO_IP_CONFIG_DATA IpCfgData;
  EFI_STATUS           Status;
  EFI_TCP4_OPTION      *Option;
  TCP_PROTO_DATA       *TcpProto;
  TCP_CB               *Tcb;
  TCP_ACCESS_POINT     *TcpAp;

  ASSERT ((CfgData != NULL) && (Sk != NULL) && (Sk->SockHandle != NULL));

  TcpProto = (TCP_PROTO_DATA *) Sk->ProtoReserved;
  Tcb      = TcpProto->TcpPcb;

  ASSERT (Tcb != NULL);

  if (Sk->IpVersion == IP_VERSION_4) {
    //
    // Add Ip for send pkt to the peer
    //
    CopyMem (&IpCfgData.Ip4CfgData, &mIp4IoDefaultIpConfigData, sizeof (EFI_IP4_CONFIG_DATA));
    IpCfgData.Ip4CfgData.DefaultProtocol    = EFI_IP_PROTO_TCP;
    IpCfgData.Ip4CfgData.TypeOfService      = CfgData->Tcp4CfgData.TypeOfService;
    IpCfgData.Ip4CfgData.TimeToLive         = CfgData->Tcp4CfgData.TimeToLive;
    IpCfgData.Ip4CfgData.UseDefaultAddress  = CfgData->Tcp4CfgData.AccessPoint.UseDefaultAddress;
    IP4_COPY_ADDRESS (
      &IpCfgData.Ip4CfgData.SubnetMask,
      &CfgData->Tcp4CfgData.AccessPoint.SubnetMask
      );
    IpCfgData.Ip4CfgData.ReceiveTimeout     = (UINT32) (-1);
    IP4_COPY_ADDRESS (
      &IpCfgData.Ip4CfgData.StationAddress,
      &CfgData->Tcp4CfgData.AccessPoint.StationAddress
      );

  } else {
    ASSERT (Sk->IpVersion == IP_VERSION_6);

    CopyMem (&IpCfgData.Ip6CfgData, &mIp6IoDefaultIpConfigData, sizeof (EFI_IP6_CONFIG_DATA));
    IpCfgData.Ip6CfgData.DefaultProtocol    = EFI_IP_PROTO_TCP;
    IpCfgData.Ip6CfgData.TrafficClass       = CfgData->Tcp6CfgData.TrafficClass;
    IpCfgData.Ip6CfgData.HopLimit           = CfgData->Tcp6CfgData.HopLimit;
    IpCfgData.Ip6CfgData.ReceiveTimeout     = (UINT32) (-1);
    IP6_COPY_ADDRESS (
      &IpCfgData.Ip6CfgData.StationAddress,
      &CfgData->Tcp6CfgData.AccessPoint.StationAddress
      );
    IP6_COPY_ADDRESS (
      &IpCfgData.Ip6CfgData.DestinationAddress,
      &CfgData->Tcp6CfgData.AccessPoint.RemoteAddress
      );
  }

  //
  // Configure the IP instance this Tcb consumes.
  //
  Status = IpIoConfigIp (Tcb->IpInfo, &IpCfgData);
  if (EFI_ERROR (Status)) {
    goto OnExit;
  }

  if (Sk->IpVersion == IP_VERSION_4) {
    //
    // Get the default address information if the instance is configured to use default address.
    //
    IP4_COPY_ADDRESS (
      &CfgData->Tcp4CfgData.AccessPoint.StationAddress,
      &IpCfgData.Ip4CfgData.StationAddress
      );
    IP4_COPY_ADDRESS (
      &CfgData->Tcp4CfgData.AccessPoint.SubnetMask,
      &IpCfgData.Ip4CfgData.SubnetMask
      );

    TcpAp = (TCP_ACCESS_POINT *) &CfgData->Tcp4CfgData.AccessPoint;
  } else {
    IP6_COPY_ADDRESS (
      &CfgData->Tcp6CfgData.AccessPoint.StationAddress,
      &IpCfgData.Ip6CfgData.StationAddress
      );

    TcpAp = (TCP_ACCESS_POINT *) &CfgData->Tcp6CfgData.AccessPoint;
  }

  //
  // check if we can bind this endpoint in CfgData
  //
  Status = TcpBind (TcpAp, Sk->IpVersion);

  if (EFI_ERROR (Status)) {
    DEBUG (
      (EFI_D_ERROR,
      "TcpConfigurePcb: Bind endpoint failed with %r\n",
      Status)
      );

    goto OnExit;
  }

  //
  // Initalize the operating information in this Tcb
  //
  ASSERT (Tcb->State == TCP_CLOSED &&
    IsListEmpty (&Tcb->SndQue) &&
    IsListEmpty (&Tcb->RcvQue));

  TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_KEEPALIVE);
  Tcb->State            = TCP_CLOSED;

  Tcb->SndMss           = 536;
  Tcb->RcvMss           = TcpGetRcvMss (Sk);

  Tcb->SRtt             = 0;
  Tcb->Rto              = 3 * TCP_TICK_HZ;

  Tcb->CWnd             = Tcb->SndMss;
  Tcb->Ssthresh         = 0xffffffff;

  Tcb->CongestState     = TCP_CONGEST_OPEN;

  Tcb->KeepAliveIdle    = TCP_KEEPALIVE_IDLE_MIN;
  Tcb->KeepAlivePeriod  = TCP_KEEPALIVE_PERIOD;
  Tcb->MaxKeepAlive     = TCP_MAX_KEEPALIVE;
  Tcb->MaxRexmit        = TCP_MAX_LOSS;
  Tcb->FinWait2Timeout  = TCP_FIN_WAIT2_TIME;
  Tcb->TimeWaitTimeout  = TCP_TIME_WAIT_TIME;
  Tcb->ConnectTimeout   = TCP_CONNECT_TIME;

  if (Sk->IpVersion == IP_VERSION_4) {
    //
    // initialize Tcb in the light of CfgData
    //
    Tcb->Ttl            = CfgData->Tcp4CfgData.TimeToLive;
    Tcb->Tos            = CfgData->Tcp4CfgData.TypeOfService;

    Tcb->UseDefaultAddr = CfgData->Tcp4CfgData.AccessPoint.UseDefaultAddress;

    CopyMem (&Tcb->LocalEnd.Ip, &CfgData->Tcp4CfgData.AccessPoint.StationAddress, sizeof (IP4_ADDR));
    Tcb->LocalEnd.Port  = HTONS (CfgData->Tcp4CfgData.AccessPoint.StationPort);
    IP4_COPY_ADDRESS (&Tcb->SubnetMask, &CfgData->Tcp4CfgData.AccessPoint.SubnetMask);

    CopyMem (&Tcb->RemoteEnd.Ip, &CfgData->Tcp4CfgData.AccessPoint.RemoteAddress, sizeof (IP4_ADDR));
    Tcb->RemoteEnd.Port = HTONS (CfgData->Tcp4CfgData.AccessPoint.RemotePort);

    Option              = CfgData->Tcp4CfgData.ControlOption;
  } else {
    Tcb->Ttl            = CfgData->Tcp6CfgData.HopLimit;
    Tcb->Tos            = CfgData->Tcp6CfgData.TrafficClass;

    IP6_COPY_ADDRESS (&Tcb->LocalEnd.Ip, &CfgData->Tcp6CfgData.AccessPoint.StationAddress);
    Tcb->LocalEnd.Port  = HTONS (CfgData->Tcp6CfgData.AccessPoint.StationPort);

    IP6_COPY_ADDRESS (&Tcb->RemoteEnd.Ip, &CfgData->Tcp6CfgData.AccessPoint.RemoteAddress);
    Tcb->RemoteEnd.Port = HTONS (CfgData->Tcp6CfgData.AccessPoint.RemotePort);

    //
    // Type EFI_TCP4_OPTION and EFI_TCP6_OPTION are the same.
    //
    Option              = (EFI_TCP4_OPTION *) CfgData->Tcp6CfgData.ControlOption;
  }

  if (Option != NULL) {
    SET_RCV_BUFFSIZE (
      Sk,
      (UINT32) (TCP_COMP_VAL (
                  TCP_RCV_BUF_SIZE_MIN,
                  TCP_RCV_BUF_SIZE,
                  TCP_RCV_BUF_SIZE,
                  Option->ReceiveBufferSize
                  )
               )
      );
    SET_SND_BUFFSIZE (
      Sk,
      (UINT32) (TCP_COMP_VAL (
                  TCP_SND_BUF_SIZE_MIN,
                  TCP_SND_BUF_SIZE,
                  TCP_SND_BUF_SIZE,
                  Option->SendBufferSize
                  )
               )
      );

    SET_BACKLOG (
      Sk,
      (UINT32) (TCP_COMP_VAL (
                  TCP_BACKLOG_MIN,
                  TCP_BACKLOG,
                  TCP_BACKLOG,
                  Option->MaxSynBackLog
                  )
               )
      );

    Tcb->MaxRexmit = (UINT16) TCP_COMP_VAL (
                                TCP_MAX_LOSS_MIN,
                                TCP_MAX_LOSS,
                                TCP_MAX_LOSS,
                                Option->DataRetries
                                );
    Tcb->FinWait2Timeout = TCP_COMP_VAL (
                              TCP_FIN_WAIT2_TIME,
                              TCP_FIN_WAIT2_TIME_MAX,
                              TCP_FIN_WAIT2_TIME,
                              (UINT32) (Option->FinTimeout * TCP_TICK_HZ)
                              );

    if (Option->TimeWaitTimeout != 0) {
      Tcb->TimeWaitTimeout = TCP_COMP_VAL (
                               TCP_TIME_WAIT_TIME,
                               TCP_TIME_WAIT_TIME_MAX,
                               TCP_TIME_WAIT_TIME,
                               (UINT32) (Option->TimeWaitTimeout * TCP_TICK_HZ)
                               );
    } else {
      Tcb->TimeWaitTimeout = 0;
    }

    if (Option->KeepAliveProbes != 0) {
      TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_KEEPALIVE);

      Tcb->MaxKeepAlive = (UINT8) TCP_COMP_VAL (
                                    TCP_MAX_KEEPALIVE_MIN,
                                    TCP_MAX_KEEPALIVE,
                                    TCP_MAX_KEEPALIVE,
                                    Option->KeepAliveProbes
                                    );
      Tcb->KeepAliveIdle = TCP_COMP_VAL (
                             TCP_KEEPALIVE_IDLE_MIN,
                             TCP_KEEPALIVE_IDLE_MAX,
                             TCP_KEEPALIVE_IDLE_MIN,
                             (UINT32) (Option->KeepAliveTime * TCP_TICK_HZ)
                             );
      Tcb->KeepAlivePeriod = TCP_COMP_VAL (
                               TCP_KEEPALIVE_PERIOD_MIN,
                               TCP_KEEPALIVE_PERIOD,
                               TCP_KEEPALIVE_PERIOD,
                               (UINT32) (Option->KeepAliveInterval * TCP_TICK_HZ)
                               );
    }

    Tcb->ConnectTimeout = TCP_COMP_VAL (
                            TCP_CONNECT_TIME_MIN,
                            TCP_CONNECT_TIME,
                            TCP_CONNECT_TIME,
                            (UINT32) (Option->ConnectionTimeout * TCP_TICK_HZ)
                            );

    if (!Option->EnableNagle) {
      TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE);
    }

    if (!Option->EnableTimeStamp) {
      TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_TS);
    }

    if (!Option->EnableWindowScaling) {
      TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_WS);
    }
  }

  //
  // The socket is bound, the <SrcIp, SrcPort, DstIp, DstPort> is
  // determined, construct the IP device path and install it.
  //
  Status = TcpInstallDevicePath (Sk);
  if (EFI_ERROR (Status)) {
    goto OnExit;
  }

  //
  // update state of Tcb and socket
  //
  if (((Sk->IpVersion == IP_VERSION_4) && !CfgData->Tcp4CfgData.AccessPoint.ActiveFlag) ||
      ((Sk->IpVersion == IP_VERSION_6) && !CfgData->Tcp6CfgData.AccessPoint.ActiveFlag)
      ) {

    TcpSetState (Tcb, TCP_LISTEN);
    SockSetState (Sk, SO_LISTENING);

    Sk->ConfigureState = SO_CONFIGURED_PASSIVE;
  } else {

    Sk->ConfigureState = SO_CONFIGURED_ACTIVE;
  }

  if (Sk->IpVersion == IP_VERSION_6) {
    Tcb->Tick          = TCP6_REFRESH_NEIGHBOR_TICK;

    if (NetIp6IsUnspecifiedAddr (&Tcb->RemoteEnd.Ip.v6)) {
      Tcb->RemoteIpZero = TRUE;
    }
  }

  TcpInsertTcb (Tcb);

OnExit:

  return Status;
}

/**
  The procotol handler provided to the socket layer, which is used to
  dispatch the socket level requests by calling the corresponding
  TCP layer functions.

  @param[in]  Sock               Pointer to the socket of this TCP instance.
  @param[in]  Request            The code of this operation request.
  @param[in]  Data               Pointer to the operation specific data passed in
                                 together with the operation request. This is an
                                 optional parameter that may be NULL.

  @retval EFI_SUCCESS            The socket request completed successfully.
  @retval other                  The error status returned by the corresponding TCP
                                 layer function.

**/
EFI_STATUS
TcpDispatcher (
  IN SOCKET                  *Sock,
  IN UINT8                   Request,
  IN VOID                    *Data    OPTIONAL
  )
{
  TCP_CB          *Tcb;
  TCP_PROTO_DATA  *ProtoData;

  ProtoData = (TCP_PROTO_DATA *) Sock->ProtoReserved;
  Tcb       = ProtoData->TcpPcb;

  switch (Request) {
  case SOCK_POLL:
    if (Tcb->Sk->IpVersion == IP_VERSION_4) {
      ProtoData->TcpService->IpIo->Ip.Ip4->Poll (ProtoData->TcpService->IpIo->Ip.Ip4);
    } else {
      ProtoData->TcpService->IpIo->Ip.Ip6->Poll (ProtoData->TcpService->IpIo->Ip.Ip6);
    }

    break;

  case SOCK_CONSUMED:
    //
    // After user received data from socket buffer, socket will
    // notify TCP using this message to give it a chance to send out
    // window update information
    //
    ASSERT (Tcb != NULL);
    TcpOnAppConsume (Tcb);
    break;

  case SOCK_SND:

    ASSERT (Tcb != NULL);
    TcpOnAppSend (Tcb);
    break;

  case SOCK_CLOSE:

    TcpOnAppClose (Tcb);

    break;

  case SOCK_ABORT:

    TcpOnAppAbort (Tcb);

    break;

  case SOCK_SNDPUSH:
    Tcb->SndPsh = TcpGetMaxSndNxt (Tcb) + GET_SND_DATASIZE (Tcb->Sk);
    TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_PSH);

    break;

  case SOCK_SNDURG:
    Tcb->SndUp = TcpGetMaxSndNxt (Tcb) + GET_SND_DATASIZE (Tcb->Sk) - 1;
    TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_URG);

    break;

  case SOCK_CONNECT:

    TcpOnAppConnect (Tcb);

    break;

  case SOCK_ATTACH:

    return TcpAttachPcb (Sock);

    break;

  case SOCK_FLUSH:

    TcpFlushPcb (Tcb);

    break;

  case SOCK_DETACH:

    TcpDetachPcb (Sock);

    break;

  case SOCK_CONFIGURE:

    return TcpConfigurePcb (
            Sock,
            (TCP_CONFIG_DATA *) Data
            );

    break;

  case SOCK_MODE:

    ASSERT ((Data != NULL) && (Tcb != NULL));

    if (Tcb->Sk->IpVersion == IP_VERSION_4) {

      return Tcp4GetMode (Tcb, (TCP4_MODE_DATA *) Data);
    } else {

      return Tcp6GetMode (Tcb, (TCP6_MODE_DATA *) Data);
    }

    break;

  case SOCK_ROUTE:

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

    return Tcp4Route (Tcb, (TCP4_ROUTE_INFO *) Data);

  default:

    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}
