/** @file
  IP6 option support functions and routines.

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

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

**/

#include "Ip6Impl.h"

/**
  Validate the IP6 option format for both the packets we received
  and that we will transmit. It will compute the ICMPv6 error message fields
  if the option is malformatted.

  @param[in]  IpSb              The IP6 service data.
  @param[in]  Packet            The to be validated packet.
  @param[in]  Option            The first byte of the option.
  @param[in]  OptionLen         The length of the whole option.
  @param[in]  Pointer           Identifies the octet offset within
                                the invoking packet where the error was detected.


  @retval TRUE     The option is properly formatted.
  @retval FALSE    The option is malformatted.

**/
BOOLEAN
Ip6IsOptionValid (
  IN IP6_SERVICE  *IpSb,
  IN NET_BUF      *Packet,
  IN UINT8        *Option,
  IN UINT8        OptionLen,
  IN UINT32       Pointer
  )
{
  UINT8  Offset;
  UINT8  OptionType;

  Offset = 0;

  while (Offset < OptionLen) {
    OptionType = *(Option + Offset);

    switch (OptionType) {
      case Ip6OptionPad1:
        //
        // It is a Pad1 option
        //
        Offset++;
        break;
      case Ip6OptionPadN:
        //
        // It is a PadN option
        //
        Offset = (UINT8)(Offset + *(Option + Offset + 1) + 2);
        break;
      case Ip6OptionRouterAlert:
        //
        // It is a Router Alert Option
        //
        Offset += 4;
        break;
      default:
        //
        // The highest-order two bits specify the action must be taken if
        // the processing IPv6 node does not recognize the option type.
        //
        switch (OptionType & Ip6OptionMask) {
          case Ip6OptionSkip:
            Offset = (UINT8)(Offset + *(Option + Offset + 1));
            break;
          case Ip6OptionDiscard:
            return FALSE;
          case Ip6OptionParameterProblem:
            Pointer = Pointer + Offset + sizeof (EFI_IP6_HEADER);
            Ip6SendIcmpError (
              IpSb,
              Packet,
              NULL,
              &Packet->Ip.Ip6->SourceAddress,
              ICMP_V6_PARAMETER_PROBLEM,
              2,
              &Pointer
              );
            return FALSE;
          case Ip6OptionMask:
            if (!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) {
              Pointer = Pointer + Offset + sizeof (EFI_IP6_HEADER);
              Ip6SendIcmpError (
                IpSb,
                Packet,
                NULL,
                &Packet->Ip.Ip6->SourceAddress,
                ICMP_V6_PARAMETER_PROBLEM,
                2,
                &Pointer
                );
            }

            return FALSE;
            break;
        }

        break;
    }
  }

  return TRUE;
}

/**
  Validate the IP6 option format for both the packets we received
  and that we will transmit. It supports the defined options in Neighbor
  Discovery messages.

  @param[in]  Option            The first byte of the option.
  @param[in]  OptionLen         The length of the whole option.

  @retval TRUE     The option is properly formatted.
  @retval FALSE    The option is malformatted.

**/
BOOLEAN
Ip6IsNDOptionValid (
  IN UINT8   *Option,
  IN UINT16  OptionLen
  )
{
  UINT32             Offset;
  UINT16             Length;
  IP6_OPTION_HEADER  *OptionHeader;

  if (Option == NULL) {
    ASSERT (Option != NULL);
    return FALSE;
  }

  Offset = 0;

  //
  // RFC 4861 states that Neighbor Discovery packet can contain zero or more
  // options. Start processing the options if at least Type + Length fields
  // fit within the input buffer.
  //
  while (Offset + sizeof (IP6_OPTION_HEADER) - 1 < OptionLen) {
    OptionHeader = (IP6_OPTION_HEADER *)(Option + Offset);
    Length       = (UINT16)OptionHeader->Length * 8;

    switch (OptionHeader->Type) {
      case Ip6OptionPrefixInfo:
        if (Length != 32) {
          return FALSE;
        }

        break;

      case Ip6OptionMtu:
        if (Length != 8) {
          return FALSE;
        }

        break;

      default:
        // RFC 4861 states that Length field cannot be 0.
        if (Length == 0) {
          return FALSE;
        }

        break;
    }

    //
    // Check whether recognized options are within the input buffer's scope.
    //
    switch (OptionHeader->Type) {
      case Ip6OptionEtherSource:
      case Ip6OptionEtherTarget:
      case Ip6OptionPrefixInfo:
      case Ip6OptionRedirected:
      case Ip6OptionMtu:
        if (Offset + Length > (UINT32)OptionLen) {
          return FALSE;
        }

        break;

      default:
        //
        // Unrecognized options can be either valid (but unused) or invalid
        // (garbage in between or right after valid options). Silently ignore.
        //
        break;
    }

    //
    // Advance to the next option.
    // Length already considers option header's Type + Length.
    //
    Offset += Length;
  }

  return TRUE;
}

/**
  Validate whether the NextHeader is a known valid protocol or one of the user configured
  protocols from the upper layer.

  @param[in]  IpSb          The IP6 service instance.
  @param[in]  NextHeader    The next header field.

  @retval TRUE              The NextHeader is a known valid protocol or user configured.
  @retval FALSE             The NextHeader is not a known valid protocol.

**/
BOOLEAN
Ip6IsValidProtocol (
  IN IP6_SERVICE  *IpSb,
  IN UINT8        NextHeader
  )
{
  LIST_ENTRY    *Entry;
  IP6_PROTOCOL  *IpInstance;

  if ((NextHeader == EFI_IP_PROTO_TCP) ||
      (NextHeader == EFI_IP_PROTO_UDP) ||
      (NextHeader == IP6_ICMP) ||
      (NextHeader == IP6_ESP)
      )
  {
    return TRUE;
  }

  if (IpSb == NULL) {
    return FALSE;
  }

  if (IpSb->Signature != IP6_SERVICE_SIGNATURE) {
    return FALSE;
  }

  NET_LIST_FOR_EACH (Entry, &IpSb->Children) {
    IpInstance = NET_LIST_USER_STRUCT_S (Entry, IP6_PROTOCOL, Link, IP6_PROTOCOL_SIGNATURE);
    if (IpInstance->State == IP6_STATE_CONFIGED) {
      if (IpInstance->ConfigData.DefaultProtocol == NextHeader) {
        return TRUE;
      }
    }
  }

  return FALSE;
}

/**
  Validate the IP6 extension header format for both the packets we received
  and that we will transmit. It will compute the ICMPv6 error message fields
  if the option is mal-formatted.

  @param[in]  IpSb          The IP6 service instance. This is an optional parameter.
  @param[in]  Packet        The data of the packet. Ignored if NULL.
  @param[in]  NextHeader    The next header field in IPv6 basic header.
  @param[in]  ExtHdrs       The first byte of the option.
  @param[in]  ExtHdrsLen    The length of the whole option.
  @param[in]  Rcvd          The option is from the packet we received if TRUE,
                            otherwise, the option we want to transmit.
  @param[out] FormerHeader  The offset of NextHeader which points to Fragment
                            Header when we received, of the ExtHdrs.
                            Ignored if we transmit.
  @param[out] LastHeader    The pointer of NextHeader of the last extension
                            header processed by IP6.
  @param[out] RealExtsLen   The length of extension headers processed by IP6 layer.
                            This is an optional parameter that may be NULL.
  @param[out] UnFragmentLen The length of unfragmented length of extension headers.
                            This is an optional parameter that may be NULL.
  @param[out] Fragmented    Indicate whether the packet is fragmented.
                            This is an optional parameter that may be NULL.

  @retval     TRUE          The option is properly formatted.
  @retval     FALSE         The option is malformatted.

**/
BOOLEAN
Ip6IsExtsValid (
  IN IP6_SERVICE  *IpSb           OPTIONAL,
  IN NET_BUF      *Packet         OPTIONAL,
  IN UINT8        *NextHeader,
  IN UINT8        *ExtHdrs,
  IN UINT32       ExtHdrsLen,
  IN BOOLEAN      Rcvd,
  OUT UINT32      *FormerHeader   OPTIONAL,
  OUT UINT8       **LastHeader,
  OUT UINT32      *RealExtsLen    OPTIONAL,
  OUT UINT32      *UnFragmentLen  OPTIONAL,
  OUT BOOLEAN     *Fragmented     OPTIONAL
  )
{
  UINT32               Pointer;
  UINT32               Offset;
  UINT8                *Option;
  UINT8                OptionLen;
  BOOLEAN              Flag;
  UINT8                CountD;
  UINT8                CountA;
  IP6_FRAGMENT_HEADER  *FragmentHead;
  UINT16               FragmentOffset;
  IP6_ROUTING_HEADER   *RoutingHead;

  if (RealExtsLen != NULL) {
    *RealExtsLen = 0;
  }

  if (UnFragmentLen != NULL) {
    *UnFragmentLen = 0;
  }

  if (Fragmented != NULL) {
    *Fragmented = FALSE;
  }

  *LastHeader = NextHeader;

  if ((ExtHdrs == NULL) && (ExtHdrsLen == 0)) {
    return TRUE;
  }

  if (((ExtHdrs == NULL) && (ExtHdrsLen != 0)) || ((ExtHdrs != NULL) && (ExtHdrsLen == 0))) {
    return FALSE;
  }

  Pointer = 0;
  Offset  = 0;
  Flag    = FALSE;
  CountD  = 0;
  CountA  = 0;

  while (Offset <= ExtHdrsLen) {
    switch (*NextHeader) {
      case IP6_HOP_BY_HOP:
        if (Offset != 0) {
          if (!Rcvd) {
            return FALSE;
          }

          //
          // Hop-by-Hop Options header is restricted to appear immediately after an IPv6 header only.
          // If not, generate a ICMP parameter problem message with code value of 1.
          //
          if (Pointer == 0) {
            Pointer = sizeof (EFI_IP6_HEADER);
          } else {
            Pointer = Offset + sizeof (EFI_IP6_HEADER);
          }

          if ((IpSb != NULL) && (Packet != NULL) &&
              !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress))
          {
            Ip6SendIcmpError (
              IpSb,
              Packet,
              NULL,
              &Packet->Ip.Ip6->SourceAddress,
              ICMP_V6_PARAMETER_PROBLEM,
              1,
              &Pointer
              );
          }

          return FALSE;
        }

        Flag = TRUE;

      //
      // Fall through
      //
      case IP6_DESTINATION:
        if (*NextHeader == IP6_DESTINATION) {
          CountD++;
        }

        if (CountD > 2) {
          return FALSE;
        }

        NextHeader = ExtHdrs + Offset;
        Pointer    = Offset;

        Offset++;
        Option    = ExtHdrs + Offset;
        OptionLen = (UINT8)((*Option + 1) * 8 - 2);
        Option++;
        Offset++;

        if ((IpSb != NULL) && (Packet != NULL) && !Ip6IsOptionValid (IpSb, Packet, Option, OptionLen, Offset)) {
          return FALSE;
        }

        Offset = Offset + OptionLen;

        if (Flag) {
          if (UnFragmentLen != NULL) {
            *UnFragmentLen = Offset;
          }

          Flag = FALSE;
        }

        break;

      case IP6_ROUTING:
        NextHeader  = ExtHdrs + Offset;
        RoutingHead = (IP6_ROUTING_HEADER *)NextHeader;

        //
        // Type 0 routing header is defined in RFC2460 and deprecated in RFC5095.
        // Thus all routing types are processed as unrecognized.
        //
        if (RoutingHead->SegmentsLeft == 0) {
          //
          // Ignore the routing header and proceed to process the next header.
          //
          Offset = Offset + (RoutingHead->HeaderLen + 1) * 8;

          if (UnFragmentLen != NULL) {
            *UnFragmentLen = Offset;
          }
        } else {
          //
          // Discard the packet and send an ICMP Parameter Problem, Code 0, message
          // to the packet's source address, pointing to the unrecognized routing
          // type.
          //
          Pointer = Offset + 2 + sizeof (EFI_IP6_HEADER);
          if ((IpSb != NULL) && (Packet != NULL) &&
              !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress))
          {
            Ip6SendIcmpError (
              IpSb,
              Packet,
              NULL,
              &Packet->Ip.Ip6->SourceAddress,
              ICMP_V6_PARAMETER_PROBLEM,
              0,
              &Pointer
              );
          }

          return FALSE;
        }

        break;

      case IP6_FRAGMENT:

        //
        // RFC2402, AH header should after fragment header.
        //
        if (CountA > 1) {
          return FALSE;
        }

        //
        // RFC2460, ICMP Parameter Problem message with code 0 should be sent
        // if the length of a fragment is not a multiple of 8 octets and the M
        // flag of that fragment is 1, pointing to the Payload length field of the
        // fragment packet.
        //
        if ((IpSb != NULL) && (Packet != NULL) && ((ExtHdrsLen % 8) != 0)) {
          //
          // Check whether it is the last fragment.
          //
          FragmentHead = (IP6_FRAGMENT_HEADER *)(ExtHdrs + Offset);
          if (FragmentHead == NULL) {
            return FALSE;
          }

          FragmentOffset = NTOHS (FragmentHead->FragmentOffset);

          if (((FragmentOffset & 0x1) == 0x1) &&
              !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress))
          {
            Pointer = sizeof (UINT32);
            Ip6SendIcmpError (
              IpSb,
              Packet,
              NULL,
              &Packet->Ip.Ip6->SourceAddress,
              ICMP_V6_PARAMETER_PROBLEM,
              0,
              &Pointer
              );
            return FALSE;
          }
        }

        if (Fragmented != NULL) {
          *Fragmented = TRUE;
        }

        if (Rcvd && (FormerHeader != NULL)) {
          *FormerHeader = (UINT32)(NextHeader - ExtHdrs);
        }

        NextHeader = ExtHdrs + Offset;
        Offset     = Offset + 8;
        break;

      case IP6_AH:
        if (++CountA > 1) {
          return FALSE;
        }

        Option     = ExtHdrs + Offset;
        NextHeader = Option;
        Option++;
        //
        // RFC2402, Payload length is specified in 32-bit words, minus "2".
        //
        OptionLen = (UINT8)((*Option + 2) * 4);
        Offset    = Offset + OptionLen;
        break;

      case IP6_NO_NEXT_HEADER:
        *LastHeader = NextHeader;
        return FALSE;
        break;

      default:
        if (Ip6IsValidProtocol (IpSb, *NextHeader)) {
          *LastHeader = NextHeader;

          if (RealExtsLen != NULL) {
            *RealExtsLen = Offset;
          }

          return TRUE;
        }

        //
        // The Next Header value is unrecognized by the node, discard the packet and
        // send an ICMP parameter problem message with code value of 1.
        //
        if (Offset == 0) {
          //
          // The Next Header directly follows IPv6 basic header.
          //
          Pointer = 6;
        } else {
          if (Pointer == 0) {
            Pointer = sizeof (EFI_IP6_HEADER);
          } else {
            Pointer = Offset + sizeof (EFI_IP6_HEADER);
          }
        }

        if ((IpSb != NULL) && (Packet != NULL) &&
            !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress))
        {
          Ip6SendIcmpError (
            IpSb,
            Packet,
            NULL,
            &Packet->Ip.Ip6->SourceAddress,
            ICMP_V6_PARAMETER_PROBLEM,
            1,
            &Pointer
            );
        }

        return FALSE;
    }
  }

  *LastHeader = NextHeader;

  if (RealExtsLen != NULL) {
    *RealExtsLen = Offset;
  }

  return TRUE;
}

/**
  Generate an IPv6 router alert option in network order and output it through Buffer.

  @param[out]     Buffer         Points to a buffer to record the generated option.
  @param[in, out] BufferLen      The length of Buffer, in bytes.
  @param[in]      NextHeader     The 8-bit selector indicates the type of header
                                 immediately following the Hop-by-Hop Options header.

  @retval EFI_BUFFER_TOO_SMALL   The Buffer is too small to contain the generated
                                 option. BufferLen is updated for the required size.

  @retval EFI_SUCCESS            The option is generated and filled in to Buffer.

**/
EFI_STATUS
Ip6FillHopByHop (
  OUT UINT8     *Buffer,
  IN OUT UINTN  *BufferLen,
  IN UINT8      NextHeader
  )
{
  UINT8  BufferArray[8];

  if (*BufferLen < 8) {
    *BufferLen = 8;
    return EFI_BUFFER_TOO_SMALL;
  }

  //
  // Form the Hop-By-Hop option in network order.
  // NextHeader (1 octet) + HdrExtLen (1 octet) + RouterAlertOption(4 octets) + PadN
  // The Hdr Ext Len is the length in 8-octet units, and does not including the first 8 octets.
  //
  ZeroMem (BufferArray, sizeof (BufferArray));
  BufferArray[0] = NextHeader;
  BufferArray[2] = 0x5;
  BufferArray[3] = 0x2;
  BufferArray[6] = 1;

  CopyMem (Buffer, BufferArray, sizeof (BufferArray));
  return EFI_SUCCESS;
}

/**
  Insert a Fragment Header to the Extension headers and output it in UpdatedExtHdrs.

  @param[in]  IpSb             The IP6 service instance to transmit the packet.
  @param[in]  NextHeader       The extension header type of first extension header.
  @param[in]  LastHeader       The extension header type of last extension header.
  @param[in]  ExtHdrs          The length of the original extension header.
  @param[in]  ExtHdrsLen       The length of the extension headers.
  @param[in]  FragmentOffset   The fragment offset of the data following the header.
  @param[out] UpdatedExtHdrs   The updated ExtHdrs with Fragment header inserted.
                               It's caller's responsibility to free this buffer.

  @retval EFI_OUT_OF_RESOURCES Failed to finish the operation due to lake of
                               resource.
  @retval EFI_UNSUPPORTED      The extension header specified in ExtHdrs is not
                               supported currently.
  @retval EFI_SUCCESS          The operation performed successfully.

**/
EFI_STATUS
Ip6FillFragmentHeader (
  IN  IP6_SERVICE  *IpSb,
  IN  UINT8        NextHeader,
  IN  UINT8        LastHeader,
  IN  UINT8        *ExtHdrs,
  IN  UINT32       ExtHdrsLen,
  IN  UINT16       FragmentOffset,
  OUT UINT8        **UpdatedExtHdrs
  )
{
  UINT32               Length;
  UINT8                *Buffer;
  UINT32               FormerHeader;
  UINT32               Offset;
  UINT32               Part1Len;
  UINT32               HeaderLen;
  UINT8                Current;
  IP6_FRAGMENT_HEADER  FragmentHead;

  if (UpdatedExtHdrs == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Length = ExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER);
  Buffer = AllocatePool (Length);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Offset       = 0;
  Part1Len     = 0;
  FormerHeader = 0;
  Current      = NextHeader;

  while ((ExtHdrs != NULL) && (Offset <= ExtHdrsLen)) {
    switch (NextHeader) {
      case IP6_ROUTING:
      case IP6_HOP_BY_HOP:
      case IP6_DESTINATION:
        Current    = NextHeader;
        NextHeader = *(ExtHdrs + Offset);

        if ((Current == IP6_DESTINATION) && (NextHeader != IP6_ROUTING)) {
          //
          // Destination Options header should occur at most twice, once before
          // a Routing header and once before the upper-layer header. Here we
          // find the one before the upper-layer header. Insert the Fragment
          // Header before it.
          //
          CopyMem (Buffer, ExtHdrs, Part1Len);
          *(Buffer + FormerHeader) = IP6_FRAGMENT;
          //
          // Exit the loop.
          //
          Offset = ExtHdrsLen + 1;
          break;
        }

        FormerHeader = Offset;
        HeaderLen    = (*(ExtHdrs + Offset + 1) + 1) * 8;
        Part1Len     = Part1Len + HeaderLen;
        Offset       = Offset + HeaderLen;
        break;

      case IP6_FRAGMENT:
        Current = NextHeader;

        if (Part1Len != 0) {
          CopyMem (Buffer, ExtHdrs, Part1Len);
        }

        *(Buffer + FormerHeader) = IP6_FRAGMENT;

        //
        // Exit the loop.
        //
        Offset = ExtHdrsLen + 1;
        break;

      case IP6_AH:
        Current    = NextHeader;
        NextHeader = *(ExtHdrs + Offset);
        //
        // RFC2402, Payload length is specified in 32-bit words, minus "2".
        //
        HeaderLen = (*(ExtHdrs + Offset + 1) + 2) * 4;
        Part1Len  = Part1Len + HeaderLen;
        Offset    = Offset + HeaderLen;
        break;

      default:
        if (Ip6IsValidProtocol (IpSb, NextHeader)) {
          Current = NextHeader;
          CopyMem (Buffer, ExtHdrs, Part1Len);
          *(Buffer + FormerHeader) = IP6_FRAGMENT;
          //
          // Exit the loop.
          //
          Offset = ExtHdrsLen + 1;
          break;
        }

        FreePool (Buffer);
        return EFI_UNSUPPORTED;
    }
  }

  //
  // Append the Fragment header. If the fragment offset indicates the fragment
  // is the first fragment.
  //
  if ((FragmentOffset & IP6_FRAGMENT_OFFSET_MASK) == 0) {
    FragmentHead.NextHeader = Current;
  } else {
    FragmentHead.NextHeader = LastHeader;
  }

  FragmentHead.Reserved       = 0;
  FragmentHead.FragmentOffset = HTONS (FragmentOffset);
  FragmentHead.Identification = mIp6Id;

  CopyMem (Buffer + Part1Len, &FragmentHead, sizeof (IP6_FRAGMENT_HEADER));

  if ((ExtHdrs != NULL) && (Part1Len < ExtHdrsLen)) {
    //
    // Append the part2 (fragmentable part) of Extension headers
    //
    CopyMem (
      Buffer + Part1Len + sizeof (IP6_FRAGMENT_HEADER),
      ExtHdrs + Part1Len,
      ExtHdrsLen - Part1Len
      );
  }

  *UpdatedExtHdrs = Buffer;

  return EFI_SUCCESS;
}