/** @file

Copyright (c) 2004, Intel Corporation
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

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

Module Name:
  pxe_bc_ip.c

Abstract:


**/

#include "Bc.h"

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**
  Check if two IP addresses are on the same subnet.

  @param  IpLength     Length of IP address in bytes.
  @param  Ip1          IP address to check.
  @param  Ip2          IP address to check.
  @param  SubnetMask   Subnet mask to check with.

  @retval TRUE         IP addresses are on the same subnet.
  @retval FALSE        IP addresses are on different subnets.

**/
BOOLEAN
OnSameSubnet (
  IN UINTN           IpLength,
  IN EFI_IP_ADDRESS  *Ip1,
  IN EFI_IP_ADDRESS  *Ip2,
  IN EFI_IP_ADDRESS  *SubnetMask
  )
{
  if (IpLength == 0 || Ip1 == NULL || Ip2 == NULL || SubnetMask == NULL) {
    return FALSE;
  }

  while (IpLength-- != 0) {
    if ((Ip1->v6.Addr[IpLength] ^ Ip2->v6.Addr[IpLength]) & SubnetMask->v6.Addr[IpLength]) {
      return FALSE;
    }
  }

  return TRUE;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**
  Add router to router table.

  @param  Private      Pointer PxeBc instance data.
  @param  RouterIpPtr  Pointer to router IP address.

  @return Nothing

**/
VOID
IpAddRouter (
  IN PXE_BASECODE_DEVICE *Private,
  IN EFI_IP_ADDRESS      *RouterIpPtr
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  UINTN                   Index;

  if (Private == NULL || RouterIpPtr == NULL) {
    return ;
  }

  PxeBcMode = Private->EfiBc.Mode;

  //
  // if we are filled up or this is not on the same subnet, forget it
  //
  if ((PxeBcMode->RouteTableEntries == PXE_ROUTER_TABLE_SIZE) ||
    !OnSameSubnet(Private->IpLength, &PxeBcMode->StationIp, RouterIpPtr, &PxeBcMode->SubnetMask)) {
    return ;
  }
  //
  // make sure we don't already have it
  //
  for (Index = 0; Index < PxeBcMode->RouteTableEntries; ++Index) {
    if (!CompareMem (
          &PxeBcMode->RouteTable[Index].GwAddr,
          RouterIpPtr,
          Private->IpLength
          )) {
      return ;
    }
  }
  //
  // keep it
  //
  ZeroMem (
    &PxeBcMode->RouteTable[PxeBcMode->RouteTableEntries],
    sizeof (EFI_PXE_BASE_CODE_ROUTE_ENTRY)
    );

  CopyMem (
    &PxeBcMode->RouteTable[PxeBcMode->RouteTableEntries++].GwAddr,
    RouterIpPtr,
    Private->IpLength
    );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// return router ip to use for DestIp (0 if none)
//
EFI_IP_ADDRESS *
GetRouterIp (
  PXE_BASECODE_DEVICE *Private,
  EFI_IP_ADDRESS      *DestIpPtr
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  UINTN                   Index;

  if (Private == NULL || DestIpPtr == NULL) {
    return NULL;
  }

  PxeBcMode = Private->EfiBc.Mode;

  for (Index = 0; Index < PxeBcMode->RouteTableEntries; ++Index) {
    if (OnSameSubnet (
          Private->IpLength,
          &PxeBcMode->RouteTable[Index].IpAddr,
          DestIpPtr,
          &PxeBcMode->RouteTable[Index].SubnetMask
          )) {
      return &PxeBcMode->RouteTable[Index].GwAddr;
    }
  }

  return NULL;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// routine to send ipv4 packet
// ipv4 header of length HdrLth in TransmitBufferPtr
// routine fills in ipv4hdr Ver_Hdl, TotalLength, and Checksum, moves in Data
// and gets dest MAC address
//
#define IP_TX_BUFFER  ((IPV4_BUFFER *) Private->TransmitBufferPtr)
#define IP_TX_HEADER  IP_TX_BUFFER->IpHeader

EFI_STATUS
Ipv4Xmt (
  PXE_BASECODE_DEVICE         *Private,
  UINT32                      GatewayIp,
  UINTN                       IpHeaderLength,
  UINTN                       TotalHeaderLength,
  VOID                        *Data,
  UINTN                       DataLength,
  EFI_PXE_BASE_CODE_FUNCTION  Function
  )
{
  EFI_MAC_ADDRESS             DestMac;
  EFI_SIMPLE_NETWORK_PROTOCOL *Snp;
  EFI_PXE_BASE_CODE_MODE      *PxeBcMode;
  EFI_STATUS                  StatCode;
  UINTN                       PacketLength;

  Snp           = Private->SimpleNetwork;
  PxeBcMode     = Private->EfiBc.Mode;
  StatCode      = EFI_SUCCESS;
  PacketLength  = TotalHeaderLength + DataLength;

  //
  // get dest MAC address
  // multicast - convert to hw equiv
  // unicast on same net, use arp
  // on different net, arp for router
  //
  if (IP_TX_HEADER.DestAddr.L == BROADCAST_IPv4) {
    CopyMem (&DestMac, &Snp->Mode->BroadcastAddress, sizeof (DestMac));
  } else if (IS_MULTICAST (&IP_TX_HEADER.DestAddr)) {
    StatCode = (*Snp->MCastIpToMac) (Snp, PxeBcMode->UsingIpv6, (EFI_IP_ADDRESS *) &IP_TX_HEADER.DestAddr, &DestMac);
  } else {
    UINT32  Ip;

    if (OnSameSubnet (
          Private->IpLength,
          &PxeBcMode->StationIp,
          (EFI_IP_ADDRESS *) &IP_TX_HEADER.DestAddr,
          &PxeBcMode->SubnetMask
          )) {
      Ip = IP_TX_HEADER.DestAddr.L;
    } else if (GatewayIp != 0) {
      Ip = GatewayIp;
    } else {
      EFI_IP_ADDRESS  *TmpIp;

      TmpIp = GetRouterIp (Private, (EFI_IP_ADDRESS *) &IP_TX_HEADER.DestAddr);

      if (TmpIp == NULL) {
        DEBUG (
          (DEBUG_WARN,
          "\nIpv4Xmit()  Exit #1  %xh (%r)",
          EFI_NO_RESPONSE,
          EFI_NO_RESPONSE)
          );

        return EFI_NO_RESPONSE;
        //
        // no router
        //
      }

      Ip = TmpIp->Addr[0];
    }

    if (!GetHwAddr (
          Private,
          (EFI_IP_ADDRESS *) &Ip,
          (EFI_MAC_ADDRESS *) &DestMac
          )) {
      if (!PxeBcMode->AutoArp) {
        DEBUG (
          (DEBUG_WARN,
          "\nIpv4Xmit()  Exit #2  %xh (%r)",
          EFI_DEVICE_ERROR,
          EFI_DEVICE_ERROR)
          );

        return EFI_DEVICE_ERROR;
      } else {
        StatCode = DoArp (
                    Private,
                    (EFI_IP_ADDRESS *) &Ip,
                    (EFI_MAC_ADDRESS *) &DestMac
                    );
      }
    }
  }

  if (EFI_ERROR (StatCode)) {
    DEBUG ((DEBUG_WARN, "\nIpv4Xmit()  Exit #3  %xh (%r)", StatCode, StatCode));
    return StatCode;
  }
  //
  // fill in packet info
  //
  SET_IPV4_VER_HDL (&IP_TX_HEADER, IpHeaderLength);
  IP_TX_HEADER.TotalLength    = HTONS (PacketLength);
  IP_TX_HEADER.HeaderChecksum = IpChecksum ((UINT16 *) &IP_TX_HEADER, IpHeaderLength);
  CopyMem (((UINT8 *) &IP_TX_HEADER) + TotalHeaderLength, Data, DataLength);

  //
  // send it
  //
  return SendPacket (
          Private,
          (UINT8 *) &IP_TX_HEADER - Snp->Mode->MediaHeaderSize,
          &IP_TX_HEADER,
          PacketLength,
          &DestMac,
          PXE_PROTOCOL_ETHERNET_IP,
          Function
          );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// send ipv4 packet with option
//
EFI_STATUS
Ipv4SendWOp (
  PXE_BASECODE_DEVICE         *Private,
  UINT32                      GatewayIp,
  UINT8                       *Msg,
  UINTN                       MessageLength,
  UINT8                       Prot,
  UINT8                       *Option,
  UINTN                       OptionLength,
  UINT32                      DestIp,
  EFI_PXE_BASE_CODE_FUNCTION  Function
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  UINTN                   HdrLth;

  PxeBcMode = Private->EfiBc.Mode;
  HdrLth    = sizeof (IPV4_HEADER) + OptionLength;

  ZeroMem ((VOID *) &IP_TX_HEADER, sizeof (IPV4_HEADER));
  IP_TX_HEADER.TimeToLive     = PxeBcMode->TTL;
  IP_TX_HEADER.TypeOfService  = PxeBcMode->ToS;
  IP_TX_HEADER.Protocol       = Prot;
  IP_TX_HEADER.SrcAddr.L      = *(UINT32 *) &PxeBcMode->StationIp;
  IP_TX_HEADER.DestAddr.L     = DestIp;
  IP_TX_HEADER.Id             = Random (Private);
  CopyMem (IP_TX_BUFFER->u.Data, Option, OptionLength);
  return Ipv4Xmt (
          Private,
          GatewayIp,
          HdrLth,
          HdrLth,
          Msg,
          MessageLength,
          Function
          );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// send MessageLength message at MessagePtr - higher level protocol header already in TransmitBufferPtr, length HdrSize
//
EFI_STATUS
Ip4Send (
  PXE_BASECODE_DEVICE                          *Private,  // pointer to instance data
  UINTN               MayFrag,                            //
  UINT8                                    Prot,          // protocol
  UINT32                          SrcIp,                  // Source IP address
  UINT32                 DestIp,                          // Destination IP address
  UINT32              GatewayIp,                          // used if not NULL and needed
  UINTN               HdrSize,                            // protocol header byte length
  UINT8               *MessagePtr,                        // pointer to data
  UINTN               MessageLength                       // data byte length
  )
{
  EFI_STATUS  StatCode;
  UINTN       TotDataLength;

  TotDataLength = HdrSize + MessageLength;

  if (TotDataLength > MAX_IPV4_DATA_SIZE) {
    DEBUG (
      (DEBUG_WARN,
      "\nIp4Send()  Exit #1  %xh (%r)",
      EFI_BAD_BUFFER_SIZE,
      EFI_BAD_BUFFER_SIZE)
      );

    return EFI_BAD_BUFFER_SIZE;
  }

  ZeroMem ((VOID *) &IP_TX_HEADER, sizeof (IPV4_HEADER));
  IP_TX_HEADER.TimeToLive = DEFAULT_TTL;
  IP_TX_HEADER.Protocol   = Prot;
  IP_TX_HEADER.SrcAddr.L  = SrcIp;
  IP_TX_HEADER.DestAddr.L = DestIp;
  IP_TX_HEADER.Id         = Random (Private);

  if (!MayFrag) {
    *(UINT8 *) (&IP_TX_HEADER.FragmentFields) = IP_NO_FRAG >> 8;
  }
  //
  // check for need to fragment
  //
  if (TotDataLength > MAX_IPV4_FRAME_DATA_SIZE) {
    UINTN   DataLengthSent;
    UINT16  FragmentOffset;

    FragmentOffset = IP_MORE_FRAG;
    //
    // frag offset field
    //
    if (!MayFrag) {
      DEBUG (
        (DEBUG_WARN,
        "\nIp4Send()  Exit #2  %xh (%r)",
        EFI_BAD_BUFFER_SIZE,
        EFI_BAD_BUFFER_SIZE)
        );

      return EFI_BAD_BUFFER_SIZE;
    }
    //
    // send out in fragments - first includes upper level header
    // all are max and include more frag bit except last
    //
    * (UINT8 *) (&IP_TX_HEADER.FragmentFields) = IP_MORE_FRAG >> 8;

#define IPV4_FRAG_SIZE    (MAX_IPV4_FRAME_DATA_SIZE & 0xfff8)
#define IPV4_FRAG_OFF_INC (IPV4_FRAG_SIZE >> 3)

    DataLengthSent = IPV4_FRAG_SIZE - HdrSize;

    StatCode = Ipv4Xmt (
                Private,
                GatewayIp,
                sizeof (IPV4_HEADER),
                sizeof (IPV4_HEADER) + HdrSize,
                MessagePtr,
                DataLengthSent,
                Private->Function
                );

    if (EFI_ERROR (StatCode)) {
      DEBUG (
        (DEBUG_WARN,
        "\nIp4Send()  Exit #3  %xh (%r)",
        StatCode,
        StatCode)
        );

      return StatCode;
    }

    MessagePtr += DataLengthSent;
    MessageLength -= DataLengthSent;
    FragmentOffset += IPV4_FRAG_OFF_INC;
    IP_TX_HEADER.FragmentFields = HTONS (FragmentOffset);

    while (MessageLength > IPV4_FRAG_SIZE) {
      StatCode = Ipv4Xmt (
                  Private,
                  GatewayIp,
                  sizeof (IPV4_HEADER),
                  sizeof (IPV4_HEADER),
                  MessagePtr,
                  IPV4_FRAG_SIZE,
                  Private->Function
                  );

      if (EFI_ERROR (StatCode)) {
        DEBUG (
          (DEBUG_WARN,
          "\nIp4Send()  Exit #3  %xh (%r)",
          StatCode,
          StatCode)
          );

        return StatCode;
      }

      MessagePtr += IPV4_FRAG_SIZE;
      MessageLength -= IPV4_FRAG_SIZE;
      FragmentOffset += IPV4_FRAG_OFF_INC;
      IP_TX_HEADER.FragmentFields = HTONS (FragmentOffset);
    }

    * (UINT8 *) (&IP_TX_HEADER.FragmentFields) &= ~(IP_MORE_FRAG >> 8);
    HdrSize = 0;
  }
  //
  // transmit
  //
  return Ipv4Xmt (
          Private,
          GatewayIp,
          sizeof (IPV4_HEADER),
          sizeof (IPV4_HEADER) + HdrSize,
          MessagePtr,
          MessageLength,
          Private->Function
          );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// return true if dst IP in receive header matched with what's enabled
//
BOOLEAN
IPgood (
  PXE_BASECODE_DEVICE *Private,
  IPV4_HEADER         *IpHeader
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  UINTN                   Index;

  PxeBcMode = Private->EfiBc.Mode;

  if (PxeBcMode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS) {
    return TRUE;
  }

  if ((PxeBcMode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST) &&
      IS_MULTICAST (&IpHeader->DestAddr)
        ) {
    return TRUE;
  }

  if ((PxeBcMode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP) &&
      PxeBcMode->StationIp.Addr[0] == IpHeader->DestAddr.L
      ) {
    return TRUE;
  }

  if ((PxeBcMode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST) && IpHeader->DestAddr.L == BROADCAST_IPv4) {
    return TRUE;
  }

  for (Index = 0; Index < PxeBcMode->IpFilter.IpCnt; ++Index) {
    if (IpHeader->DestAddr.L == PxeBcMode->IpFilter.IpList[Index].Addr[0]) {
      return TRUE;
    }
  }

  return FALSE;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// receive up to MessageLength message into MessagePtr for protocol Prot
// return message length, src/dest ips if select any, and pointer to protocol
// header routine will filter based on source and/or dest ip if OpFlags set.
//
EFI_STATUS
IpReceive (
  PXE_BASECODE_DEVICE *Private,
  PXE_OPFLAGS         OpFlags,
  EFI_IP_ADDRESS      *SrcIpPtr,
  EFI_IP_ADDRESS      *DestIpPtr,
  UINT8               Prot,
  VOID                *HeaderPtr,
  UINTN               HdrSize,
  UINT8               *MessagePtr,
  UINTN               *MessageLengthPtr,
  EFI_EVENT           TimeoutEvent
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  EFI_STATUS              StatCode;
  UINTN                   ByteCount;
  UINTN                   FragmentCount;
  UINTN                   ExpectedPacketLength;
  UINTN                   Id;
  BOOLEAN                 GotFirstFragment;
  BOOLEAN                 GotLastFragment;

  DEBUG (
    (DEBUG_NET,
    "\nIpReceive()  Hdr=%Xh  HdrSz=%d  Data=%Xh  DataSz=%d",
    HeaderPtr,
    HdrSize,
    MessagePtr,
    *MessageLengthPtr)
    );

  PxeBcMode                     = Private->EfiBc.Mode;
  PxeBcMode->IcmpErrorReceived  = FALSE;

  ExpectedPacketLength          = 0;
  GotFirstFragment              = FALSE;
  GotLastFragment               = FALSE;
  FragmentCount                 = 0;
  ByteCount                     = 0;
  Id = 0;

  for (;;) {
    IPV4_HEADER IpHdr;
    UINTN       FFlds;
    UINTN       TotalLength;
    UINTN       FragmentOffset;
    UINTN       HeaderSize;
    UINTN       BufferSize;
    UINTN       IpHeaderLength;
    UINTN       DataLength;
    UINT16      Protocol;
    UINT8       *NextHdrPtr;
    UINT8       *PacketPtr;

    StatCode = WaitForReceive (
                Private,
                Private->Function,
                TimeoutEvent,
                &HeaderSize,
                &BufferSize,
                &Protocol
                );

    if (EFI_ERROR (StatCode)) {
      return StatCode;
    }

    PacketPtr = Private->ReceiveBufferPtr + HeaderSize;

    if (Protocol == PXE_PROTOCOL_ETHERNET_ARP) {
      HandleArpReceive (
        Private,
        (ARP_PACKET *) PacketPtr,
        Private->ReceiveBufferPtr
        );

      continue;
    }

    if (Protocol != PXE_PROTOCOL_ETHERNET_IP) {
      continue;
    }

#define IpRxHeader  ((IPV4_HEADER *) PacketPtr)

    //
    // filter for version & check sum
    //
    IpHeaderLength = IPV4_HEADER_LENGTH (IpRxHeader);

    if ((IpRxHeader->VersionIhl >> 4) != IPVER4) {
      continue;
    }

    if (IpChecksum ((UINT16 *) IpRxHeader, IpHeaderLength)) {
      continue;
    }

    CopyMem (&IpHdr, IpRxHeader, sizeof (IpHdr));
    TotalLength = NTOHS (IpHdr.TotalLength);

    if (IpHdr.Protocol == PROT_TCP) {
      //
      // The NextHdrPtr is used to seed the header buffer we are passing back.
      // That being the case, we want to see everything in pPkt which contains
      // everything but the ethernet (or whatever) frame.  IP + TCP in this case.
      //
      DataLength  = TotalLength;
      NextHdrPtr  = PacketPtr;
    } else {
      DataLength  = TotalLength - IpHeaderLength;
      NextHdrPtr  = PacketPtr + IpHeaderLength;
    }
    //
    // If this is an ICMP, it might not be for us.
    // Double check the state of the IP stack and the
    // packet fields before assuming it is an ICMP
    // error.  ICMP requests are not supported by the
    // PxeBc IP stack and should be ignored.
    //
    if (IpHdr.Protocol == PROT_ICMP) {
      ICMPV4_HEADER *Icmpv4;

      Icmpv4 = (ICMPV4_HEADER *) NextHdrPtr;

      //
      // For now only obvious ICMP error replies will be accepted by
      // this stack.  This still makes us vulnerable to DoS attacks.
      // But at least we will not be killed by DHCP daemons.
      //
      switch (Icmpv4->Type) {
      case ICMP_REDIRECT:
      case ICMP_ECHO:
      case ICMP_ROUTER_ADV:
      case ICMP_ROUTER_SOLICIT:
      case ICMP_TIMESTAMP:
      case ICMP_TIMESTAMP_REPLY:
      case ICMP_INFO_REQ:
      case ICMP_INFO_REQ_REPLY:
      case ICMP_SUBNET_MASK_REQ:
      case ICMP_SUBNET_MASK_REPLY:
      default:
        continue;

      //
      // %%TBD - This should be implemented.
      //
      case ICMP_ECHO_REPLY:
        continue;

      case ICMP_DEST_UNREACHABLE:
      case ICMP_TIME_EXCEEDED:
      case ICMP_PARAMETER_PROBLEM:
      case ICMP_SOURCE_QUENCH:
        PxeBcMode->IcmpErrorReceived = TRUE;

        CopyMem (
          &PxeBcMode->IcmpError,
          NextHdrPtr,
          sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)
          );

        DEBUG (
          (DEBUG_NET,
          "\nIpReceive()  Exit #1  %Xh (%r)",
          EFI_ICMP_ERROR,
          EFI_ICMP_ERROR)
          );
      }

      return EFI_ICMP_ERROR;
    }

    if (IpHdr.Protocol == PROT_IGMP) {
      HandleIgmp (Private, (IGMPV2_MESSAGE *) NextHdrPtr, DataLength);

      DEBUG ((DEBUG_NET, "\n  IGMP"));
      continue;
    }
    //
    // check for protocol
    //
    if (IpHdr.Protocol != Prot) {
      continue;
    }
    //
    // do filtering
    //
    if (!(OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP) && SrcIpPtr && SrcIpPtr->Addr[0] != IpHdr.SrcAddr.L) {
      DEBUG ((DEBUG_NET, "\n  Not expected source IP address."));
      continue;
    }

    if (OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER) {
      if (!IPgood (Private, &IpHdr)) {
        continue;
      }
    } else if (!(OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP)) {
      if (DestIpPtr == NULL) {
        if (PxeBcMode->StationIp.Addr[0] != IpHdr.DestAddr.L) {
          continue;
        }
      } else if (DestIpPtr->Addr[0] != IpHdr.DestAddr.L) {
        continue;
      }
    }
    //
    // get some data we need
    //
    FFlds           = NTOHS (IpHdr.FragmentFields);
    FragmentOffset  = ((FFlds & IP_FRAG_OFF_MSK) << 3);

    /* Keep count of fragments that belong to this session.
     * If we get packets with a different IP ID number,
     * ignore them.  Ignored packets should be handled
     * by the upper level protocol.
     */
    if (FragmentCount == 0) {
      Id = IpHdr.Id;

      if (DestIpPtr != NULL) {
        DestIpPtr->Addr[0] = IpHdr.DestAddr.L;
      }

      if (SrcIpPtr != NULL) {
        SrcIpPtr->Addr[0] = IpHdr.SrcAddr.L;
      }
    } else {
      if (IpHdr.Id != Id) {
        continue;
      }
    }

    ++FragmentCount;

    /* Fragment management.
     */
    if (FragmentOffset == 0) {
      /* This is the first fragment (may also be the
       * only fragment).
       */
      GotFirstFragment = TRUE;

      /* If there is a separate protocol header buffer,
       * copy the header, adjust the data pointer and
       * the data length.
       */
      if (HdrSize != 0) {
        CopyMem (HeaderPtr, NextHdrPtr, HdrSize);

        NextHdrPtr += HdrSize;
        DataLength -= HdrSize;
      }
    } else {
      /* If there is a separate protocol header buffer,
       * adjust the fragment offset.
       */
      FragmentOffset -= HdrSize;
    }

    /* See if this is the last fragment.
     */
    if (!(FFlds & IP_MORE_FRAG)) {
      //
      // This is the last fragment (may also be the only fragment).
      //
      GotLastFragment = TRUE;

      /* Compute the expected length of the assembled
       * packet.  This will be used to decide if we
       * have gotten all of the fragments.
       */
      ExpectedPacketLength = FragmentOffset + DataLength;
    }

    DEBUG (
      (DEBUG_NET,
      "\n  ID = %Xh  Off = %d  Len = %d",
      Id,
      FragmentOffset,
      DataLength)
      );

    /* Check for receive buffer overflow.
     */
    if (FragmentOffset + DataLength > *MessageLengthPtr) {
      /* There is not enough space in the receive
       * buffer for the fragment.
       */
      DEBUG (
        (DEBUG_NET,
        "\nIpReceive()  Exit #3  %Xh (%r)",
        EFI_BUFFER_TOO_SMALL,
        EFI_BUFFER_TOO_SMALL)
        );

      return EFI_BUFFER_TOO_SMALL;
    }

    /* Copy data into receive buffer.
     */
    if (DataLength != 0) {
      DEBUG ((DEBUG_NET, "  To = %Xh", MessagePtr + FragmentOffset));

      CopyMem (MessagePtr + FragmentOffset, NextHdrPtr, DataLength);
      ByteCount += DataLength;
    }

    /* If we have seen the first and last fragments and
     * the receive byte count is at least as large as the
     * expected byte count, return SUCCESS.
     *
     * We could be tricked by receiving a fragment twice
     * but the upper level protocol should figure this
     * out.
     */
    if (GotFirstFragment && GotLastFragment && ByteCount >= ExpectedPacketLength) {
      *MessageLengthPtr = ExpectedPacketLength;
      return EFI_SUCCESS;
    }
  }
}

/* eof - pxe_bc_ip.c */
