/** @file
  Misc support routines for tcp.

Copyright (c) 2005 - 2006, Intel Corporation<BR>
All rights reserved. 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<BR>

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 "Tcp4Main.h"

#include <Library/DevicePathLib.h>

LIST_ENTRY      mTcpRunQue = {
  &mTcpRunQue,
  &mTcpRunQue
};

LIST_ENTRY      mTcpListenQue = {
  &mTcpListenQue,
  &mTcpListenQue
};

TCP_SEQNO       mTcpGlobalIss = 0x4d7e980b;

CHAR16   *mTcpStateName[] = {
  L"TCP_CLOSED",
  L"TCP_LISTEN",
  L"TCP_SYN_SENT",
  L"TCP_SYN_RCVD",
  L"TCP_ESTABLISHED",
  L"TCP_FIN_WAIT_1",
  L"TCP_FIN_WAIT_2",
  L"TCP_CLOSING",
  L"TCP_TIME_WAIT",
  L"TCP_CLOSE_WAIT",
  L"TCP_LAST_ACK"
};


/**
  Initialize the Tcb local related members.

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

**/
VOID
TcpInitTcbLocal (
  IN OUT TCP_CB *Tcb
  )
{
  //
  // Compute the checksum of the fixed parts of pseudo header
  //
  Tcb->HeadSum = NetPseudoHeadChecksum (
                  Tcb->LocalEnd.Ip,
                  Tcb->RemoteEnd.Ip,
                  0x06,
                  0
                  );

  Tcb->Iss    = TcpGetIss ();
  Tcb->SndUna = Tcb->Iss;
  Tcb->SndNxt = Tcb->Iss;

  Tcb->SndWl2 = Tcb->Iss;
  Tcb->SndWnd = 536;

  Tcb->RcvWnd = GET_RCV_BUFFSIZE (Tcb->Sk);

  //
  // First window size is never scaled
  //
  Tcb->RcvWndScale = 0;
}


/**
  Initialize the peer related members.

  @param  Tcb                   Pointer to the TCP_CB of this TCP instance.
  @param  Seg                   Pointer to the segment that contains the peer's
                                intial info.
  @param  Opt                   Pointer to the options announced by the peer.

**/
VOID
TcpInitTcbPeer (
  IN OUT TCP_CB     *Tcb,
  IN     TCP_SEG    *Seg,
  IN     TCP_OPTION *Opt
  )
{
  UINT16  RcvMss;

  ASSERT ((Tcb != NULL) && (Seg != NULL) && (Opt != NULL));
  ASSERT (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN));

  Tcb->SndWnd     = Seg->Wnd;
  Tcb->SndWndMax  = Tcb->SndWnd;
  Tcb->SndWl1     = Seg->Seq;

  if (TCP_FLG_ON (Seg->Flag, TCP_FLG_ACK)) {
    Tcb->SndWl2 = Seg->Ack;
  } else {
    Tcb->SndWl2 = Tcb->Iss + 1;
  }

  if (TCP_FLG_ON (Opt->Flag, TCP_OPTION_RCVD_MSS)) {
    Tcb->SndMss = (UINT16) MAX (64, Opt->Mss);

    RcvMss = TcpGetRcvMss (Tcb->Sk);
    if (Tcb->SndMss > RcvMss) {
      Tcb->SndMss = RcvMss;
    }

  } else {
    //
    // One end doesn't support MSS option, use default.
    //
    Tcb->RcvMss = 536;
  }

  Tcb->CWnd   = Tcb->SndMss;

  Tcb->Irs    = Seg->Seq;
  Tcb->RcvNxt = Tcb->Irs + 1;

  Tcb->RcvWl2 = Tcb->RcvNxt;

  if (TCP_FLG_ON (Opt->Flag, TCP_OPTION_RCVD_WS) &&
      !TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS)) {

    Tcb->SndWndScale  = Opt->WndScale;

    Tcb->RcvWndScale  = TcpComputeScale (Tcb);
    TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_RCVD_WS);

  } else {
    //
    // One end doesn't support window scale option. use zero.
    //
    Tcb->RcvWndScale = 0;
  }

  if (TCP_FLG_ON (Opt->Flag, TCP_OPTION_RCVD_TS) &&
      !TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS)) {

    TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_TS);
    TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_RCVD_TS);

    //
    // Compute the effective SndMss per RFC1122
    // section 4.2.2.6. If timestamp option is
    // enabled, it will always occupy 12 bytes.
    //
    Tcb->SndMss -= TCP_OPTION_TS_ALIGNED_LEN;
  }
}


/**
  Locate a listen TCB that matchs the Local and Remote.

  @param  Local                 Pointer to the local (IP, Port).
  @param  Remote                Pointer to the remote (IP, Port).

  @return  Pointer to the TCP_CB with the least number of wildcard, 
           if NULL no match is found.

**/
TCP_CB *
TcpLocateListenTcb (
  IN TCP_PEER *Local,
  IN TCP_PEER *Remote
  )
{
  LIST_ENTRY      *Entry;
  TCP_CB          *Node;
  TCP_CB          *Match;
  INTN            Last;
  INTN            Cur;

  Last  = 4;
  Match = NULL;

  NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
    Node = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    if ((Local->Port != Node->LocalEnd.Port) ||
        !TCP_PEER_MATCH (Remote, &Node->RemoteEnd) ||
        !TCP_PEER_MATCH (Local, &Node->LocalEnd)) {

      continue;
    }

    //
    // Compute the number of wildcard
    //
    Cur = 0;
    if (Node->RemoteEnd.Ip == 0) {
      Cur++;
    }

    if (Node->RemoteEnd.Port == 0) {
      Cur++;
    }

    if (Node->LocalEnd.Ip == 0) {
      Cur++;
    }

    if (Cur < Last) {
      if (Cur == 0) {
        return Node;
      }

      Last  = Cur;
      Match = Node;
    }
  }

  return Match;
}


/**
  Try to find one Tcb whose <Ip, Port> equals to <IN Addr, IN Port>.

  @param  Addr                  Pointer to the IP address needs to match.
  @param  Port                  The port number needs to match.

  @return  The Tcb which matches the <Addr Port> paire exists or not.

**/
BOOLEAN
TcpFindTcbByPeer (
  IN EFI_IPv4_ADDRESS  *Addr,
  IN TCP_PORTNO        Port
  )
{
  TCP_PORTNO      LocalPort;
  LIST_ENTRY      *Entry;
  TCP_CB          *Tcb;

  ASSERT ((Addr != NULL) && (Port != 0));

  LocalPort = HTONS (Port);

  NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
    Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    if (EFI_IP4_EQUAL (Addr, &Tcb->LocalEnd.Ip) &&
      (LocalPort == Tcb->LocalEnd.Port)) {

      return TRUE;
    }
  }

  NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
    Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    if (EFI_IP4_EQUAL (Addr, &Tcb->LocalEnd.Ip) &&
      (LocalPort == Tcb->LocalEnd.Port)) {

      return TRUE;
    }
  }

  return FALSE;
}


/**
  Locate the TCP_CB related to the socket pair.

  @param  LocalPort             The local port number.
  @param  LocalIp               The local IP address.
  @param  RemotePort            The remote port number.
  @param  RemoteIp              The remote IP address.
  @param  Syn                   Whether to search the listen sockets, if TRUE, the
                                listen sockets are searched.

  @return  Pointer to the related TCP_CB, if NULL no match is found.

**/
TCP_CB *
TcpLocateTcb (
  IN TCP_PORTNO  LocalPort,
  IN UINT32      LocalIp,
  IN TCP_PORTNO  RemotePort,
  IN UINT32      RemoteIp,
  IN BOOLEAN     Syn
  )
{
  TCP_PEER        Local;
  TCP_PEER        Remote;
  LIST_ENTRY      *Entry;
  TCP_CB          *Tcb;

  Local.Port  = LocalPort;
  Local.Ip    = LocalIp;

  Remote.Port = RemotePort;
  Remote.Ip   = RemoteIp;

  //
  // First check for exact match.
  //
  NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
    Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    if (TCP_PEER_EQUAL (&Remote, &Tcb->RemoteEnd) &&
        TCP_PEER_EQUAL (&Local, &Tcb->LocalEnd)) {

      RemoveEntryList (&Tcb->List);
      InsertHeadList (&mTcpRunQue, &Tcb->List);

      return Tcb;
    }
  }

  //
  // Only check listen queue when SYN flag is on
  //
  if (Syn) {
    return TcpLocateListenTcb (&Local, &Remote);
  }

  return NULL;
}


/**
  Insert a Tcb into the proper queue.

  @param  Tcb                   Pointer to the TCP_CB to be inserted.

  @retval 0                     The Tcb is inserted successfully.
  @retval -1                    Error condition occurred.

**/
INTN
TcpInsertTcb (
  IN TCP_CB *Tcb
  )
{
  LIST_ENTRY       *Entry;
  LIST_ENTRY       *Head;
  TCP_CB           *Node;
  TCP4_PROTO_DATA  *TcpProto;

  ASSERT (
    (Tcb != NULL) &&
    ((Tcb->State == TCP_LISTEN) ||
     (Tcb->State == TCP_SYN_SENT) ||
     (Tcb->State == TCP_SYN_RCVD) ||
     (Tcb->State == TCP_CLOSED))
    );

  if (Tcb->LocalEnd.Port == 0) {
    return -1;
  }

  Head = &mTcpRunQue;

  if (Tcb->State == TCP_LISTEN) {
    Head = &mTcpListenQue;
  }

  //
  // Check that Tcb isn't already on the list.
  //
  NET_LIST_FOR_EACH (Entry, Head) {
    Node = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    if (TCP_PEER_EQUAL (&Tcb->LocalEnd, &Node->LocalEnd) &&
        TCP_PEER_EQUAL (&Tcb->RemoteEnd, &Node->RemoteEnd)) {

      return -1;
    }
  }

  InsertHeadList (Head, &Tcb->List);

  TcpProto = (TCP4_PROTO_DATA *) Tcb->Sk->ProtoReserved;
  TcpSetVariableData (TcpProto->TcpService);

  return 0;
}


/**
  Clone a TCB_CB from Tcb.

  @param  Tcb                   Pointer to the TCP_CB to be cloned.

  @return  Pointer to the new cloned TCP_CB, if NULL error condition occurred.

**/
TCP_CB *
TcpCloneTcb (
  IN TCP_CB *Tcb
  )
{
  TCP_CB               *Clone;

  Clone = AllocatePool (sizeof (TCP_CB));

  if (Clone == NULL) {
    return NULL;

  }

  CopyMem (Clone, Tcb, sizeof (TCP_CB));

  //
  // Increate the reference count of the shared IpInfo.
  //
  NET_GET_REF (Tcb->IpInfo);

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

  Clone->Sk = SockClone (Tcb->Sk);
  if (Clone->Sk == NULL) {
    DEBUG ((EFI_D_ERROR, "TcpCloneTcb: failed to clone a sock\n"));
    gBS->FreePool (Clone);
    return NULL;
  }

  ((TCP4_PROTO_DATA *) (Clone->Sk->ProtoReserved))->TcpPcb = Clone;

  return Clone;
}


/**
  Compute an ISS to be used by a new connection.

  @return  The result ISS.

**/
TCP_SEQNO
TcpGetIss (
  VOID
  )
{
  mTcpGlobalIss += 2048;
  return mTcpGlobalIss;
}


/**
  Get the local mss.

  @param  Sock        Pointer to the socket to get mss

  @return  The mss size.

**/
UINT16
TcpGetRcvMss (
  IN SOCKET  *Sock
  )
{
  EFI_SIMPLE_NETWORK_MODE SnpMode;
  TCP4_PROTO_DATA         *TcpProto;
  EFI_IP4_PROTOCOL        *Ip;

  ASSERT (Sock != NULL);

  TcpProto = (TCP4_PROTO_DATA *) Sock->ProtoReserved;
  Ip       = TcpProto->TcpService->IpIo->Ip;
  ASSERT (Ip != NULL);

  Ip->GetModeData (Ip, NULL, NULL, &SnpMode);

  return (UINT16) (SnpMode.MaxPacketSize - 40);
}


/**
  Set the Tcb's state.

  @param  Tcb                   Pointer to the TCP_CB of this TCP instance.
  @param  State                 The state to be set.

**/
VOID
TcpSetState (
  IN OUT TCP_CB  *Tcb,
  IN     UINT8   State
  )
{
  DEBUG (
    (EFI_D_INFO,
    "Tcb (%p) state %s --> %s\n",
    Tcb,
    mTcpStateName[Tcb->State],
    mTcpStateName[State])
    );

  Tcb->State = State;

  switch (State) {
  case TCP_ESTABLISHED:

    SockConnEstablished (Tcb->Sk);

    if (Tcb->Parent != NULL) {
      //
      // A new connection is accepted by a listening socket, install
      // the device path.
      //
      TcpInstallDevicePath (Tcb->Sk);
    }

    break;

  case TCP_CLOSED:

    SockConnClosed (Tcb->Sk);

    break;
  default:
    break;
  }
}


/**
  Compute the TCP segment's checksum.

  @param  Nbuf                  Pointer to the buffer that contains the TCP
                                segment.
  @param  HeadSum               The checksum value of the fixed part of pseudo
                                header.

  @return  The checksum value.

**/
UINT16
TcpChecksum (
  IN NET_BUF *Nbuf,
  IN UINT16  HeadSum
  )
{
  UINT16  Checksum;

  Checksum  = NetbufChecksum (Nbuf);
  Checksum  = NetAddChecksum (Checksum, HeadSum);

  Checksum  = NetAddChecksum (
                Checksum,
                HTONS ((UINT16) Nbuf->TotalSize)
                );

  return (UINT16) ~Checksum;
}

/**
  Translate the information from the head of the received TCP
  segment Nbuf contains and fill it into a TCP_SEG structure.

  @param  Tcb                   Pointer to the TCP_CB of this TCP instance.
  @param  Nbuf                  Pointer to the buffer contains the TCP segment.

  @return  Pointer to the TCP_SEG that contains the translated TCP head information.

**/
TCP_SEG *
TcpFormatNetbuf (
  IN     TCP_CB  *Tcb,
  IN OUT NET_BUF *Nbuf
  )
{
  TCP_SEG   *Seg;
  TCP_HEAD  *Head;

  Seg       = TCPSEG_NETBUF (Nbuf);
  Head      = (TCP_HEAD *) NetbufGetByte (Nbuf, 0, NULL);
  Nbuf->Tcp = Head;

  Seg->Seq  = NTOHL (Head->Seq);
  Seg->Ack  = NTOHL (Head->Ack);
  Seg->End  = Seg->Seq + (Nbuf->TotalSize - (Head->HeadLen << 2));

  Seg->Urg  = NTOHS (Head->Urg);
  Seg->Wnd  = (NTOHS (Head->Wnd) << Tcb->SndWndScale);
  Seg->Flag = Head->Flag;

  //
  // SYN and FIN flag occupy one sequence space each.
  //
  if (TCP_FLG_ON (Seg->Flag, TCP_FLG_SYN)) {
    //
    // RFC requires that initial window not be scaled
    //
    Seg->Wnd = NTOHS (Head->Wnd);
    Seg->End++;
  }

  if (TCP_FLG_ON (Seg->Flag, TCP_FLG_FIN)) {
    Seg->End++;
  }

  return Seg;
}


/**
  Reset the connection related with Tcb.

  @param  Tcb                   Pointer to the TCP_CB of the connection to be
                                reset.

**/
VOID
TcpResetConnection (
  IN TCP_CB *Tcb
  )
{
  NET_BUF   *Nbuf;
  TCP_HEAD  *Nhead;

  Nbuf = NetbufAlloc (TCP_MAX_HEAD);

  if (Nbuf == NULL) {
    return ;
  }

  Nhead = (TCP_HEAD *) NetbufAllocSpace (
                        Nbuf,
                        sizeof (TCP_HEAD),
                        NET_BUF_TAIL
                        );

  ASSERT (Nhead != NULL);

  Nbuf->Tcp       = Nhead;

  Nhead->Flag     = TCP_FLG_RST;
  Nhead->Seq      = HTONL (Tcb->SndNxt);
  Nhead->Ack      = HTONL (Tcb->RcvNxt);
  Nhead->SrcPort  = Tcb->LocalEnd.Port;
  Nhead->DstPort  = Tcb->RemoteEnd.Port;
  Nhead->HeadLen  = (sizeof (TCP_HEAD) >> 2);
  Nhead->Res      = 0;
  Nhead->Wnd      = HTONS (0xFFFF);
  Nhead->Checksum = 0;
  Nhead->Urg      = 0;
  Nhead->Checksum = TcpChecksum (Nbuf, Tcb->HeadSum);

  TcpSendIpPacket (Tcb, Nbuf, Tcb->LocalEnd.Ip, Tcb->RemoteEnd.Ip);

  NetbufFree (Nbuf);
}


/**
  Initialize an active connection.

  @param  Tcb                   Pointer to the TCP_CB that wants to initiate a
                                connection.

**/
VOID
TcpOnAppConnect (
  IN OUT TCP_CB  *Tcb
  )
{
  TcpInitTcbLocal (Tcb);
  TcpSetState (Tcb, TCP_SYN_SENT);

  TcpSetTimer (Tcb, TCP_TIMER_CONNECT, Tcb->ConnectTimeout);
  TcpToSendData (Tcb, 1);
}


/**
  Initiate the connection close procedure, called when
  applications want to close the connection.

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

**/
VOID
TcpOnAppClose (
  IN OUT TCP_CB *Tcb
  )
{
  ASSERT (Tcb != NULL);

  if (!IsListEmpty (&Tcb->RcvQue) || GET_RCV_DATASIZE (Tcb->Sk) != 0) {

    DEBUG ((EFI_D_WARN, "TcpOnAppClose: connection reset "
      "because data is lost for TCB %p\n", Tcb));

    TcpResetConnection (Tcb);
    TcpClose (Tcb);
    return;
  }

  switch (Tcb->State) {
  case TCP_CLOSED:
  case TCP_LISTEN:
  case TCP_SYN_SENT:
    TcpSetState (Tcb, TCP_CLOSED);
    break;

  case TCP_SYN_RCVD:
  case TCP_ESTABLISHED:
    TcpSetState (Tcb, TCP_FIN_WAIT_1);
    break;

  case TCP_CLOSE_WAIT:
    TcpSetState (Tcb, TCP_LAST_ACK);
    break;
  default:
    break;
  }

  TcpToSendData (Tcb, 1);
}


/**
  Check whether the application's newly delivered data can be sent out.

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

  @retval 0                     Whether the data is sent out or is buffered for
                                further sending.
  @retval -1                    The Tcb is not in a state that data is permitted to
                                be sent out.

**/
INTN
TcpOnAppSend (
  IN OUT TCP_CB *Tcb
  )
{

  switch (Tcb->State) {
  case TCP_CLOSED:
    return -1;

  case TCP_LISTEN:
    return -1;

  case TCP_SYN_SENT:
  case TCP_SYN_RCVD:
    return 0;

  case TCP_ESTABLISHED:
  case TCP_CLOSE_WAIT:
    TcpToSendData (Tcb, 0);
    return 0;

  case TCP_FIN_WAIT_1:
  case TCP_FIN_WAIT_2:
  case TCP_CLOSING:
  case TCP_LAST_ACK:
  case TCP_TIME_WAIT:
    return -1;

  default:
    break;
  }

  return 0;
}


/**
  Application has consumed some data, check whether
  to send a window updata ack or a delayed ack.

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

**/
VOID
TcpOnAppConsume (
  IN TCP_CB *Tcb
  )
{
  UINT32 TcpOld;

  switch (Tcb->State) {
  case TCP_CLOSED:
    return;

  case TCP_LISTEN:
    return;

  case TCP_SYN_SENT:
  case TCP_SYN_RCVD:
    return;

  case TCP_ESTABLISHED:
    TcpOld = TcpRcvWinOld (Tcb);
    if (TcpRcvWinNow (Tcb) > TcpOld) {

      if (TcpOld < Tcb->RcvMss) {

        DEBUG ((EFI_D_INFO, "TcpOnAppConsume: send a window"
          " update for a window closed Tcb %p\n", Tcb));

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

        DEBUG ((EFI_D_INFO, "TcpOnAppConsume: scheduled a delayed"
          " ACK to update window for Tcb %p\n", Tcb));

        Tcb->DelayedAck = 1;
      }
    }

    break;

  case TCP_CLOSE_WAIT:
    return;

  case TCP_FIN_WAIT_1:
  case TCP_FIN_WAIT_2:
  case TCP_CLOSING:
  case TCP_LAST_ACK:
  case TCP_TIME_WAIT:
    return;

  default:
    break;
  }
}


/**
  Abort the connection by sending a reset segment, called
  when the application wants to abort the connection.

  @param  Tcb                   Pointer to the TCP_CB of the TCP instance.

**/
VOID
TcpOnAppAbort (
  IN TCP_CB *Tcb
  )
{
  DEBUG ((EFI_D_WARN, "TcpOnAppAbort: connection reset "
    "issued by application for TCB %p\n", Tcb));

  switch (Tcb->State) {
  case TCP_SYN_RCVD:
  case TCP_ESTABLISHED:
  case TCP_FIN_WAIT_1:
  case TCP_FIN_WAIT_2:
  case TCP_CLOSE_WAIT:
    TcpResetConnection (Tcb);
    break;
  default:
    break;
  }

  TcpSetState (Tcb, TCP_CLOSED);
}


/**
  Set the Tdp4 variable data.

  @param  Tcp4Service           Pointer to Tcp4 service data.

  @retval EFI_OUT_OF_RESOURCES  There are not enough resources to set the variable.
  @retval other                 Set variable failed.

**/
EFI_STATUS
TcpSetVariableData (
  IN TCP4_SERVICE_DATA  *Tcp4Service
  )
{
  UINT32                  NumConfiguredInstance;
  LIST_ENTRY              *Entry;
  TCP_CB                  *TcpPcb;
  TCP4_PROTO_DATA         *TcpProto;
  UINTN                   VariableDataSize;
  EFI_TCP4_VARIABLE_DATA  *Tcp4VariableData;
  EFI_TCP4_SERVICE_POINT  *Tcp4ServicePoint;
  CHAR16                  *NewMacString;
  EFI_STATUS              Status;

  NumConfiguredInstance = 0;

  //
  // Go through the running queue to count the instances.
  //
  NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
    TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    TcpProto = (TCP4_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;

    if (TcpProto->TcpService == Tcp4Service) {
      //
      // This tcp instance belongs to the Tcp4Service.
      //
      NumConfiguredInstance++;
    }
  }

  //
  // Go through the listening queue to count the instances.
  //
  NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
    TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    TcpProto = (TCP4_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;

    if (TcpProto->TcpService == Tcp4Service) {
      //
      // This tcp instance belongs to the Tcp4Service.
      //
      NumConfiguredInstance++;
    }
  }

  //
  // Calculate the size of the Tcp4VariableData. As there may be no Tcp4 child,
  // we should add extra buffer for the service points only if the number of configured
  // children is more than 1.
  //
  VariableDataSize = sizeof (EFI_TCP4_VARIABLE_DATA);

  if (NumConfiguredInstance > 1) {
    VariableDataSize += sizeof (EFI_TCP4_SERVICE_POINT) * (NumConfiguredInstance - 1);
  }

  Tcp4VariableData = AllocatePool (VariableDataSize);
  if (Tcp4VariableData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Tcp4VariableData->DriverHandle = Tcp4Service->DriverBindingHandle;
  Tcp4VariableData->ServiceCount = NumConfiguredInstance;

  Tcp4ServicePoint = &Tcp4VariableData->Services[0];

  //
  // Go through the running queue to fill the service points.
  //
  NET_LIST_FOR_EACH (Entry, &mTcpRunQue) {
    TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    TcpProto = (TCP4_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;

    if (TcpProto->TcpService == Tcp4Service) {
      //
      // This tcp instance belongs to the Tcp4Service.
      //
      Tcp4ServicePoint->InstanceHandle          = TcpPcb->Sk->SockHandle;
      CopyMem (&Tcp4ServicePoint->LocalAddress, &TcpPcb->LocalEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
      Tcp4ServicePoint->LocalPort               = NTOHS (TcpPcb->LocalEnd.Port);
      CopyMem (&Tcp4ServicePoint->RemoteAddress, &TcpPcb->RemoteEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
      Tcp4ServicePoint->RemotePort              = NTOHS (TcpPcb->RemoteEnd.Port);

      Tcp4ServicePoint++;
    }
  }

  //
  // Go through the listening queue to fill the service points.
  //
  NET_LIST_FOR_EACH (Entry, &mTcpListenQue) {
    TcpPcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List);

    TcpProto = (TCP4_PROTO_DATA *) TcpPcb->Sk->ProtoReserved;

    if (TcpProto->TcpService == Tcp4Service) {
      //
      // This tcp instance belongs to the Tcp4Service.
      //
      Tcp4ServicePoint->InstanceHandle          = TcpPcb->Sk->SockHandle;
      CopyMem (&Tcp4ServicePoint->LocalAddress, &TcpPcb->LocalEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
      Tcp4ServicePoint->LocalPort               = NTOHS (TcpPcb->LocalEnd.Port);
      CopyMem (&Tcp4ServicePoint->RemoteAddress, &TcpPcb->RemoteEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
      Tcp4ServicePoint->RemotePort              = NTOHS (TcpPcb->RemoteEnd.Port);

      Tcp4ServicePoint++;
    }
  }

  //
  // Get the mac string.
  //
  Status = NetLibGetMacString (
             Tcp4Service->ControllerHandle,
             Tcp4Service->DriverBindingHandle,
             &NewMacString
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  if (Tcp4Service->MacString != NULL) {
    //
    // The variable is set already, we're going to update it.
    //
    if (StrCmp (Tcp4Service->MacString, NewMacString) != 0) {
      //
      // The mac address is changed, delete the previous variable first.
      //
      gRT->SetVariable (
             Tcp4Service->MacString,
             &gEfiTcp4ServiceBindingProtocolGuid,
             EFI_VARIABLE_BOOTSERVICE_ACCESS,
             0,
             NULL
             );
    }

    gBS->FreePool (Tcp4Service->MacString);
  }

  Tcp4Service->MacString = NewMacString;

  Status = gRT->SetVariable (
                  Tcp4Service->MacString,
                  &gEfiTcp4ServiceBindingProtocolGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  VariableDataSize,
                  (VOID *) Tcp4VariableData
                  );

ON_ERROR:

  gBS->FreePool (Tcp4VariableData);

  return Status;
}


/**
  Clear the variable and free the resource.

  @param  Tcp4Service           Pointer to Tcp4 service data.

**/
VOID
TcpClearVariableData (
  IN TCP4_SERVICE_DATA  *Tcp4Service
  )
{
  ASSERT (Tcp4Service->MacString != NULL);

  gRT->SetVariable (
         Tcp4Service->MacString,
         &gEfiTcp4ServiceBindingProtocolGuid,
         EFI_VARIABLE_BOOTSERVICE_ACCESS,
         0,
         NULL
         );

  gBS->FreePool (Tcp4Service->MacString);
  Tcp4Service->MacString = NULL;
}

/**
  Install the device path protocol on the TCP instance.

  @param  Sock             Pointer to the socket representing the TCP instance.

  @retval  EFI_SUCCESS     The device path protocol is installed.
  @retval  other           Failed to install the device path protocol.

**/
EFI_STATUS
TcpInstallDevicePath (
  IN SOCKET *Sock
  )
{
  TCP4_PROTO_DATA    *TcpProto;
  TCP4_SERVICE_DATA  *TcpService;
  TCP_CB             *Tcb;
  IPv4_DEVICE_PATH   Ip4DPathNode;
  EFI_STATUS         Status;

  TcpProto   = (TCP4_PROTO_DATA *) Sock->ProtoReserved;
  TcpService = TcpProto->TcpService;
  Tcb        = TcpProto->TcpPcb;

  NetLibCreateIPv4DPathNode (
    &Ip4DPathNode,
    TcpService->ControllerHandle,
    Tcb->LocalEnd.Ip,
    NTOHS (Tcb->LocalEnd.Port),
    Tcb->RemoteEnd.Ip,
    NTOHS (Tcb->RemoteEnd.Port),
    EFI_IP_PROTO_TCP,
    Tcb->UseDefaultAddr
    );

  Sock->DevicePath = AppendDevicePathNode (
                       Sock->ParentDevicePath,
                       (EFI_DEVICE_PATH_PROTOCOL *) &Ip4DPathNode
                       );
  if (Sock->DevicePath == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = gBS->InstallProtocolInterface (
                  &Sock->SockHandle,
                  &gEfiDevicePathProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  Sock->DevicePath
                  );
  if (EFI_ERROR (Status)) {
    gBS->FreePool (Sock->DevicePath);
  }

  return Status;
}
