content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.android.server.wifi.util;
  
  import android.util.Log;
  
  import com.android.server.wifi.WifiLoggerHal;
  
  import java.nio.BufferUnderflowException;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.HashSet;
  import java.util.Set;
  
  /**
   * This class parses the raw bytes of a network frame, and stores the parsed information in its
   * public fields.
   */
  public class FrameParser {
      /**
       * Note: When adding constants derived from network protocol specifications, please encode
       * these constants the same way as the relevant specification, for ease of comparison.
       */
  
      private static final String TAG = "FrameParser";
  
      /* These fields hold the information parsed from this frame. */
      public String mMostSpecificProtocolString = "N/A";
      public String mTypeString = "N/A";
      public String mResultString = "N/A";
  
      /**
       * Parses the contents of a given network frame.
       *
       * @param frameType The type of the frame, as defined in
       * {@link com.android.server.wifi.WifiLoggerHal}.
       * @param frameBytes The raw bytes of the frame to be parsed.
       */
      public FrameParser(byte frameType, byte[] frameBytes) {
          try {
              ByteBuffer frameBuffer = ByteBuffer.wrap(frameBytes);
              frameBuffer.order(ByteOrder.BIG_ENDIAN);
              if (frameType == WifiLoggerHal.FRAME_TYPE_ETHERNET_II) {
                  parseEthernetFrame(frameBuffer);
              } else if (frameType == WifiLoggerHal.FRAME_TYPE_80211_MGMT) {
                  parseManagementFrame(frameBuffer);
              }
          } catch (BufferUnderflowException | IllegalArgumentException e) {
              Log.e(TAG, "Dissection aborted mid-frame: " + e);
          }
      }
  
      /**
       * Read one byte into a form that can easily be compared against, or output as, an integer
       * in the range (0, 255).
       */
      private static short getUnsignedByte(ByteBuffer data) {
          return (short) (data.get() & 0x00ff);
      }
      /**
       * Read two bytes into a form that can easily be compared against, or output as, an integer
       * in the range (0, 65535).
       */
      private static int getUnsignedShort(ByteBuffer data) {
          return (data.getShort() & 0xffff);
      }
  
      private static final int ETHERNET_SRC_MAC_ADDR_LEN = 6;
      private static final int ETHERNET_DST_MAC_ADDR_LEN = 6;
      private static final short ETHERTYPE_IP_V4 = (short) 0x0800;
      private static final short ETHERTYPE_ARP = (short) 0x0806;
      private static final short ETHERTYPE_IP_V6 = (short) 0x86dd;
      private static final short ETHERTYPE_EAPOL = (short) 0x888e;
  
      private void parseEthernetFrame(ByteBuffer data) {
          mMostSpecificProtocolString = "Ethernet";
          data.position(data.position() + ETHERNET_SRC_MAC_ADDR_LEN + ETHERNET_DST_MAC_ADDR_LEN);
          short etherType = data.getShort();
          switch (etherType) {
              case ETHERTYPE_IP_V4:
                  parseIpv4Packet(data);
                  return;
              case ETHERTYPE_ARP:
                  parseArpPacket(data);
                  return;
              case ETHERTYPE_IP_V6:
                  parseIpv6Packet(data);
                  return;
              case ETHERTYPE_EAPOL:
                  parseEapolPacket(data);
                  return;
              default:
                  return;
          }
      }
  
      private static final byte IP_V4_VERSION_BYTE_MASK = (byte) 0b11110000;
      private static final byte IP_V4_IHL_BYTE_MASK = (byte) 0b00001111;
      private static final byte IP_V4_ADDR_LEN = 4;
      private static final byte IP_V4_DSCP_AND_ECN_LEN = 1;
      private static final byte IP_V4_TOTAL_LEN_LEN = 2;
      private static final byte IP_V4_ID_LEN = 2;
      private static final byte IP_V4_FLAGS_AND_FRAG_OFFSET_LEN = 2;
      private static final byte IP_V4_TTL_LEN = 1;
      private static final byte IP_V4_HEADER_CHECKSUM_LEN = 2;
      private static final byte IP_V4_SRC_ADDR_LEN = 4;
      private static final byte IP_V4_DST_ADDR_LEN = 4;
      private static final byte IP_PROTO_ICMP = 1;
      private static final byte IP_PROTO_TCP = 6;
      private static final byte IP_PROTO_UDP = 17;
      private static final byte BYTES_PER_QUAD = 4;
  
      private void parseIpv4Packet(ByteBuffer data) {
          mMostSpecificProtocolString = "IPv4";
          data.mark();
          byte versionAndHeaderLen = data.get();
          int version = (versionAndHeaderLen & IP_V4_VERSION_BYTE_MASK) >> 4;
          if (version != 4) {
              Log.e(TAG, "IPv4 header: Unrecognized protocol version " + version);
              return;
          }
  
          data.position(data.position() + IP_V4_DSCP_AND_ECN_LEN + IP_V4_TOTAL_LEN_LEN
                  + IP_V4_ID_LEN + IP_V4_FLAGS_AND_FRAG_OFFSET_LEN + IP_V4_TTL_LEN);
          short protocolNumber = getUnsignedByte(data);
          data.position(data.position() + IP_V4_HEADER_CHECKSUM_LEN + IP_V4_SRC_ADDR_LEN
                  + IP_V4_DST_ADDR_LEN);
  
          int headerLen = (versionAndHeaderLen & IP_V4_IHL_BYTE_MASK) * BYTES_PER_QUAD;
          data.reset();  // back to start of IPv4 header
          data.position(data.position() + headerLen);
  
          switch (protocolNumber) {
              case IP_PROTO_ICMP:
                  parseIcmpPacket(data);
                  break;
              case IP_PROTO_TCP:
                  parseTcpPacket(data);
                  break;
              case IP_PROTO_UDP:
                  parseUdpPacket(data);
                  break;
              default:
                  break;
          }
      }
  
      private static final byte TCP_SRC_PORT_LEN = 2;
      private static final int HTTPS_PORT = 443;
      private static final Set<Integer> HTTP_PORTS = new HashSet<>();
      static {
          HTTP_PORTS.add(80);
          HTTP_PORTS.add(3128);
          HTTP_PORTS.add(3132);
          HTTP_PORTS.add(5985);
          HTTP_PORTS.add(8080);
          HTTP_PORTS.add(8088);
          HTTP_PORTS.add(11371);
          HTTP_PORTS.add(1900);
          HTTP_PORTS.add(2869);
          HTTP_PORTS.add(2710);
      }
  
      private void parseTcpPacket(ByteBuffer data) {
          mMostSpecificProtocolString = "TCP";
          data.position(data.position() + TCP_SRC_PORT_LEN);
          int dstPort = getUnsignedShort(data);
  
          if (dstPort == HTTPS_PORT) {
              mTypeString = "HTTPS";
          } else if (HTTP_PORTS.contains(dstPort)) {
              mTypeString = "HTTP";
          }
      }
  
      private static final byte UDP_PORT_BOOTPS = 67;
      private static final byte UDP_PORT_BOOTPC = 68;
      private static final byte UDP_PORT_NTP = 123;
      private static final byte UDP_CHECKSUM_LEN = 2;
  
      private void parseUdpPacket(ByteBuffer data) {
          mMostSpecificProtocolString = "UDP";
          int srcPort = getUnsignedShort(data);
          int dstPort = getUnsignedShort(data);
          int length = getUnsignedShort(data);
  
          data.position(data.position() + UDP_CHECKSUM_LEN);
          if ((srcPort == UDP_PORT_BOOTPC && dstPort == UDP_PORT_BOOTPS)
                  || (srcPort == UDP_PORT_BOOTPS && dstPort == UDP_PORT_BOOTPC)) {
              parseDhcpPacket(data);
              return;
          }
          if (srcPort == UDP_PORT_NTP || dstPort == UDP_PORT_NTP) {
              mMostSpecificProtocolString = "NTP";
              return;
          }
      }
  
      private static final byte BOOTP_OPCODE_LEN = 1;
      private static final byte BOOTP_HWTYPE_LEN = 1;
      private static final byte BOOTP_HWADDR_LEN_LEN = 1;
      private static final byte BOOTP_HOPCOUNT_LEN = 1;
      private static final byte BOOTP_TRANSACTION_ID_LEN = 4;
      private static final byte BOOTP_ELAPSED_SECONDS_LEN = 2;
      private static final byte BOOTP_FLAGS_LEN = 2;
      private static final byte BOOTP_CLIENT_HWADDR_LEN = 16;
      private static final byte BOOTP_SERVER_HOSTNAME_LEN = 64;
      private static final short BOOTP_BOOT_FILENAME_LEN = 128;
      private static final byte BOOTP_MAGIC_COOKIE_LEN = 4;
      private static final short DHCP_OPTION_TAG_PAD = 0;
      private static final short DHCP_OPTION_TAG_MESSAGE_TYPE = 53;
      private static final short DHCP_OPTION_TAG_END = 255;
  
      private void parseDhcpPacket(ByteBuffer data) {
          mMostSpecificProtocolString = "DHCP";
          data.position(data.position() + BOOTP_OPCODE_LEN + BOOTP_HWTYPE_LEN + BOOTP_HWADDR_LEN_LEN
                  + BOOTP_HOPCOUNT_LEN + BOOTP_TRANSACTION_ID_LEN + BOOTP_ELAPSED_SECONDS_LEN
                  + BOOTP_FLAGS_LEN + IP_V4_ADDR_LEN * 4 + BOOTP_CLIENT_HWADDR_LEN
                  + BOOTP_SERVER_HOSTNAME_LEN + BOOTP_BOOT_FILENAME_LEN + BOOTP_MAGIC_COOKIE_LEN);
          while (data.remaining() > 0) {
              short dhcpOptionTag = getUnsignedByte(data);
              if (dhcpOptionTag == DHCP_OPTION_TAG_PAD) {
                  continue;
              }
              if (dhcpOptionTag == DHCP_OPTION_TAG_END) {
                  break;
              }
              short dhcpOptionLen = getUnsignedByte(data);
              switch (dhcpOptionTag) {
                  case DHCP_OPTION_TAG_MESSAGE_TYPE:
                      if (dhcpOptionLen != 1) {
                          Log.e(TAG, "DHCP option len: " + dhcpOptionLen  + " (expected |1|)");
                          return;
                      }
                      mTypeString = decodeDhcpMessageType(getUnsignedByte(data));
                      return;
                  default:
                      data.position(data.position() + dhcpOptionLen);
              }
          }
      }
  
      private static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1;
      private static final byte DHCP_MESSAGE_TYPE_OFFER = 2;
      private static final byte DHCP_MESSAGE_TYPE_REQUEST = 3;
      private static final byte DHCP_MESSAGE_TYPE_DECLINE = 4;
      private static final byte DHCP_MESSAGE_TYPE_ACK = 5;
      private static final byte DHCP_MESSAGE_TYPE_NAK = 6;
      private static final byte DHCP_MESSAGE_TYPE_RELEASE = 7;
      private static final byte DHCP_MESSAGE_TYPE_INFORM = 8;
  
      private static String decodeDhcpMessageType(short messageType) {
          switch (messageType) {
              case DHCP_MESSAGE_TYPE_DISCOVER:
                  return "Discover";
              case DHCP_MESSAGE_TYPE_OFFER:
                  return "Offer";
              case DHCP_MESSAGE_TYPE_REQUEST:
                  return "Request";
              case DHCP_MESSAGE_TYPE_DECLINE:
                  return "Decline";
              case DHCP_MESSAGE_TYPE_ACK:
                  return "Ack";
              case DHCP_MESSAGE_TYPE_NAK:
                  return "Nak";
              case DHCP_MESSAGE_TYPE_RELEASE:
                  return "Release";
              case DHCP_MESSAGE_TYPE_INFORM:
                  return "Inform";
              default:
                  return "Unknown type " + messageType;
          }
      }
  
      private static final byte ICMP_TYPE_ECHO_REPLY = 0;
      private static final byte ICMP_TYPE_DEST_UNREACHABLE = 3;
      private static final byte ICMP_TYPE_REDIRECT = 5;
      private static final byte ICMP_TYPE_ECHO_REQUEST = 8;
  
      private void parseIcmpPacket(ByteBuffer data) {
          mMostSpecificProtocolString = "ICMP";
          short messageType = getUnsignedByte(data);
          switch (messageType) {
              case ICMP_TYPE_ECHO_REPLY:
                  mTypeString = "Echo Reply";
                  return;
              case ICMP_TYPE_DEST_UNREACHABLE:
                  mTypeString = "Destination Unreachable";
                  return;
              case ICMP_TYPE_REDIRECT:
                  mTypeString = "Redirect";
                  return;
              case ICMP_TYPE_ECHO_REQUEST:
                  mTypeString = "Echo Request";
                  return;
              default:
                  mTypeString = "Type " + messageType;
                  return;
          }
      }
  
      private static final byte ARP_HWTYPE_LEN = 2;
      private static final byte ARP_PROTOTYPE_LEN = 2;
      private static final byte ARP_HWADDR_LEN_LEN = 1;
      private static final byte ARP_PROTOADDR_LEN_LEN = 1;
      private static final byte ARP_OPCODE_REQUEST = 1;
      private static final byte ARP_OPCODE_REPLY = 2;
  
      private void parseArpPacket(ByteBuffer data) {
          mMostSpecificProtocolString = "ARP";
          data.position(data.position() + ARP_HWTYPE_LEN + ARP_PROTOTYPE_LEN + ARP_HWADDR_LEN_LEN
                  + ARP_PROTOADDR_LEN_LEN);
          int opCode = getUnsignedShort(data);
          switch (opCode) {
              case ARP_OPCODE_REQUEST:
                  mTypeString = "Request";
                  break;
              case ARP_OPCODE_REPLY:
                  mTypeString = "Reply";
                  break;
              default:
                  mTypeString = "Operation " + opCode;
          }
      }
  
      private static final byte IP_V6_PAYLOAD_LENGTH_LEN = 2;
      private static final byte IP_V6_HOP_LIMIT_LEN = 1;
      private static final byte IP_V6_ADDR_LEN = 16;
      private static final byte IP_V6_HEADER_TYPE_HOP_BY_HOP_OPTION = 0;
      private static final byte IP_V6_HEADER_TYPE_ICMP_V6 = 58;
      private static final byte BYTES_PER_OCT = 8;
  
      private void parseIpv6Packet(ByteBuffer data) {
          mMostSpecificProtocolString = "IPv6";
          int versionClassAndLabel = data.getInt();
          int version = (versionClassAndLabel & 0xf0000000) >> 28;
          if (version != 6) {
              Log.e(TAG, "IPv6 header: invalid IP version " + version);
              return;
          }
          data.position(data.position() + IP_V6_PAYLOAD_LENGTH_LEN);
  
          short nextHeaderType = getUnsignedByte(data);
          data.position(data.position() + IP_V6_HOP_LIMIT_LEN + IP_V6_ADDR_LEN * 2);
          while (nextHeaderType == IP_V6_HEADER_TYPE_HOP_BY_HOP_OPTION) {
              int thisHeaderLen;
              data.mark();
              nextHeaderType = getUnsignedByte(data);
              thisHeaderLen = (getUnsignedByte(data) + 1) * BYTES_PER_OCT;
              data.reset();  // back to start of this header
              data.position(data.position() + thisHeaderLen);
          }
          switch (nextHeaderType) {
              case IP_V6_HEADER_TYPE_ICMP_V6:
                  parseIcmpV6Packet(data);
                  return;
              default:
                  mTypeString = "Option/Protocol " + nextHeaderType;
                  return;
          }
      }
  
      private static final short ICMP_V6_TYPE_ECHO_REQUEST = 128;
      private static final short ICMP_V6_TYPE_ECHO_REPLY = 129;
      private static final short ICMP_V6_TYPE_ROUTER_SOLICITATION = 133;
      private static final short ICMP_V6_TYPE_ROUTER_ADVERTISEMENT = 134;
      private static final short ICMP_V6_TYPE_NEIGHBOR_SOLICITATION = 135;
      private static final short ICMP_V6_TYPE_NEIGHBOR_ADVERTISEMENT = 136;
      private static final short ICMP_V6_TYPE_MULTICAST_LISTENER_DISCOVERY = 143;
  
      private void parseIcmpV6Packet(ByteBuffer data) {
          mMostSpecificProtocolString = "ICMPv6";
          short icmpV6Type = getUnsignedByte(data);
          switch (icmpV6Type) {
              case ICMP_V6_TYPE_ECHO_REQUEST:
                  mTypeString = "Echo Request";
                  return;
              case ICMP_V6_TYPE_ECHO_REPLY:
                  mTypeString = "Echo Reply";
                  return;
              case ICMP_V6_TYPE_ROUTER_SOLICITATION:
                  mTypeString = "Router Solicitation";
                  return;
              case ICMP_V6_TYPE_ROUTER_ADVERTISEMENT:
                  mTypeString = "Router Advertisement";
                  return;
              case ICMP_V6_TYPE_NEIGHBOR_SOLICITATION:
                  mTypeString = "Neighbor Solicitation";
                  return;
              case ICMP_V6_TYPE_NEIGHBOR_ADVERTISEMENT:
                  mTypeString = "Neighbor Advertisement";
                  return;
              case ICMP_V6_TYPE_MULTICAST_LISTENER_DISCOVERY:
                  mTypeString = "MLDv2 report";
                  return;
              default:
                  mTypeString = "Type " + icmpV6Type;
                  return;
          }
      }
  
      private static final byte EAPOL_TYPE_KEY = 3;
      private static final byte EAPOL_KEY_DESCRIPTOR_RSN_KEY = 2;
      private static final byte EAPOL_LENGTH_LEN = 2;
      private static final short WPA_KEY_INFO_FLAG_PAIRWISE = (short) 1 << 3;  // bit 4
      private static final short WPA_KEY_INFO_FLAG_INSTALL = (short) 1 << 6;  // bit 7
      private static final short WPA_KEY_INFO_FLAG_MIC = (short) 1 << 8;  // bit 9
      private static final byte WPA_KEYLEN_LEN = 2;
      private static final byte WPA_REPLAY_COUNTER_LEN = 8;
      private static final byte WPA_KEY_NONCE_LEN = 32;
      private static final byte WPA_KEY_IV_LEN = 16;
      private static final byte WPA_KEY_RECEIVE_SEQUENCE_COUNTER_LEN = 8;
      private static final byte WPA_KEY_IDENTIFIER_LEN = 8;
      private static final byte WPA_KEY_MIC_LEN = 16;
  
      private void parseEapolPacket(ByteBuffer data) {
          mMostSpecificProtocolString = "EAPOL";
          short eapolVersion = getUnsignedByte(data);
          if (eapolVersion < 1 || eapolVersion > 2) {
              Log.e(TAG, "Unrecognized EAPOL version " + eapolVersion);
              return;
          }
  
          short eapolType = getUnsignedByte(data);
          if (eapolType != EAPOL_TYPE_KEY) {
              Log.e(TAG, "Unrecognized EAPOL type " + eapolType);
              return;
          }
  
          data.position(data.position() + EAPOL_LENGTH_LEN);
          short eapolKeyDescriptorType = getUnsignedByte(data);
          if (eapolKeyDescriptorType != EAPOL_KEY_DESCRIPTOR_RSN_KEY) {
              Log.e(TAG, "Unrecognized key descriptor " + eapolKeyDescriptorType);
              return;
          }
  
          short wpaKeyInfo = data.getShort();
          if ((wpaKeyInfo & WPA_KEY_INFO_FLAG_PAIRWISE) == 0) {
              mTypeString = "Group Key";
          } else {
              mTypeString = "Pairwise Key";
          }
  
          // See goo.gl/tu8AQC for details.
          if ((wpaKeyInfo & WPA_KEY_INFO_FLAG_MIC) == 0) {
              mTypeString += " message 1/4";
              return;
          }
  
          if ((wpaKeyInfo & WPA_KEY_INFO_FLAG_INSTALL) != 0) {
              mTypeString += " message 3/4";
              return;
          }
  
          data.position(data.position() + WPA_KEYLEN_LEN + WPA_REPLAY_COUNTER_LEN
                  + WPA_KEY_NONCE_LEN + WPA_KEY_IV_LEN + WPA_KEY_RECEIVE_SEQUENCE_COUNTER_LEN
                  + WPA_KEY_IDENTIFIER_LEN + WPA_KEY_MIC_LEN);
          int wpaKeyDataLen = getUnsignedShort(data);
          if (wpaKeyDataLen > 0) {
              mTypeString += " message 2/4";
          } else {
              mTypeString += " message 4/4";
          }
      }
  
      private static final byte IEEE_80211_FRAME_TYPE_MGMT = 0b00;
      // Per 802.11-2016 Table 9-1
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_REQ = 0b0000;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_RESP = 0b0001;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_REQ = 0b0010;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_RESP = 0b0011;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_REQ = 0b0100;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_RESP = 0b0101;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_TIMING_AD = 0b0110;
      // 0b0111 reserved
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_BEACON = 0b1000;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ATIM = 0b1001;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DISASSOC = 0b1010;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_AUTH = 0b1011;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DEAUTH = 0b1100;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION = 0b1101;
      private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION_NO_ACK = 0b1110;
      // 0b1111 reserved
  
      private static final byte IEEE_80211_FRAME_FLAG_ORDER = (byte) (1 << 7); // bit 8
      private static final byte IEEE_80211_DURATION_LEN = 2;
      private static final byte IEEE_80211_ADDR1_LEN = 6;
      private static final byte IEEE_80211_ADDR2_LEN = 6;
      private static final byte IEEE_80211_ADDR3_LEN = 6;
      private static final byte IEEE_80211_SEQUENCE_CONTROL_LEN = 2;
      private static final byte IEEE_80211_HT_CONTROL_LEN = 4;
  
      private static byte parseIeee80211FrameCtrlVersion(byte b) {
          return (byte) (b & 0b00000011);
      }
      private static byte parseIeee80211FrameCtrlType(byte b) {
          return (byte) ((b & 0b00001100) >> 2);
      }
      private static byte parseIeee80211FrameCtrlSubtype(byte b) {
          return (byte) ((b & 0b11110000) >> 4);
      }
      private void parseManagementFrame(ByteBuffer data) {  // 802.11-2012 Sec 8.3.3.1
          data.order(ByteOrder.LITTLE_ENDIAN);
  
          mMostSpecificProtocolString = "802.11 Mgmt";
          byte frameControlVersionTypeSubtype = data.get();
          byte ieee80211Version = parseIeee80211FrameCtrlVersion(frameControlVersionTypeSubtype);
          if (ieee80211Version != 0) {
              Log.e(TAG, "Unrecognized 802.11 version " + ieee80211Version);
              return;
          }
  
          byte ieee80211FrameType = parseIeee80211FrameCtrlType(frameControlVersionTypeSubtype);
          if (ieee80211FrameType != IEEE_80211_FRAME_TYPE_MGMT) {
              Log.e(TAG, "Unexpected frame type " + ieee80211FrameType);
              return;
          }
  
          byte frameControlFlags = data.get();
  
          data.position(data.position() + IEEE_80211_DURATION_LEN + IEEE_80211_ADDR1_LEN
                  + IEEE_80211_ADDR2_LEN + IEEE_80211_ADDR3_LEN + IEEE_80211_SEQUENCE_CONTROL_LEN);
  
          if ((frameControlFlags & IEEE_80211_FRAME_FLAG_ORDER) != 0) {
              // Per 802.11-2012 Sec 8.2.4.1.10.
              data.position(data.position() + IEEE_80211_HT_CONTROL_LEN);
          }
  
          byte ieee80211FrameSubtype = parseIeee80211FrameCtrlSubtype(frameControlVersionTypeSubtype);
          switch (ieee80211FrameSubtype) {
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_REQ:
                  mTypeString = "Association Request";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_RESP:
                  mTypeString = "Association Response";
                  parseAssociationResponse(data);
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_REQ:
                  mTypeString = "Reassociation Request";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_RESP:
                  mTypeString = "Reassociation Response";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_REQ:
                  mTypeString = "Probe Request";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_RESP:
                  mTypeString = "Probe Response";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_TIMING_AD:
                  mTypeString = "Timing Advertisement";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_BEACON:
                  mTypeString = "Beacon";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ATIM:
                  mTypeString = "ATIM";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DISASSOC:
                  mTypeString = "Disassociation";
                  parseDisassociationFrame(data);
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_AUTH:
                  mTypeString = "Authentication";
                  parseAuthenticationFrame(data);
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DEAUTH:
                  mTypeString = "Deauthentication";
                  parseDeauthenticationFrame(data);
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION:
                  mTypeString = "Action";
                  return;
              case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION_NO_ACK:
                  mTypeString = "Action No Ack";
                  return;
              case 0b0111:
              case 0b1111:
                  mTypeString = "Reserved";
                  return;
              default:
                  mTypeString = "Unexpected subtype " + ieee80211FrameSubtype;
                  return;
          }
      }
  
      // Per 802.11-2012 Secs 8.3.3.6 and 8.4.1.
      private static final byte IEEE_80211_CAPABILITY_INFO_LEN = 2;
      private void parseAssociationResponse(ByteBuffer data) {
          data.position(data.position() + IEEE_80211_CAPABILITY_INFO_LEN);
          short resultCode = data.getShort();
          mResultString = String.format(
                  "%d: %s", resultCode, decodeIeee80211StatusCode(resultCode));
      }
  
      // Per 802.11-2016 Sec 9.3.3.5
      private void parseDisassociationFrame(ByteBuffer data) {
          short reasonCode = data.getShort();
          mResultString = String.format("%d: %s", reasonCode, decodeIeee80211ReasonCode(reasonCode));
      }
  
      // Per 802.11-2012 Secs 8.3.3.11 and 8.4.1.
      private static final short IEEE_80211_AUTH_ALG_OPEN = 0;
      private static final short IEEE_80211_AUTH_ALG_SHARED_KEY = 1;
      private static final short IEEE_80211_AUTH_ALG_FAST_BSS_TRANSITION = 2;
      private static final short IEEE_80211_AUTH_ALG_SIMUL_AUTH_OF_EQUALS = 3;
      private void parseAuthenticationFrame(ByteBuffer data) {
          short algorithm = data.getShort();
          short sequenceNum = data.getShort();
          boolean hasResultCode = false;
          switch (algorithm) {
              case IEEE_80211_AUTH_ALG_OPEN:
              case IEEE_80211_AUTH_ALG_SHARED_KEY:
                  if (sequenceNum == 2) {
                      hasResultCode = true;
                  }
                  break;
              case IEEE_80211_AUTH_ALG_FAST_BSS_TRANSITION:
                  if (sequenceNum == 2 || sequenceNum == 4) {
                      hasResultCode = true;
                  }
                  break;
              case IEEE_80211_AUTH_ALG_SIMUL_AUTH_OF_EQUALS:
                  hasResultCode = true;
                  break;
              default:
                  // Ignore unknown algorithm -- don't know which frames would have result codes.
          }
  
          if (hasResultCode) {
              short resultCode = data.getShort();
              mResultString = String.format(
                      "%d: %s", resultCode, decodeIeee80211StatusCode(resultCode));
          }
      }
  
      // Per 802.11-2016 Sec 9.3.3.13
      private void parseDeauthenticationFrame(ByteBuffer data) {
          short reasonCode = data.getShort();
          mResultString = String.format("%d: %s", reasonCode, decodeIeee80211ReasonCode(reasonCode));
      }
  
      // Per 802.11-2016 Table 9-45
      private String decodeIeee80211ReasonCode(short reasonCode) {
          switch (reasonCode) {
              case 0:
                  return "Reserved";
              case 1:
                  return "Unspecified reason";
              case 2:
                  return "Previous authentication no longer valid";
              case 3:
                  return "Deauthenticated because sending STA is leaving (or has left) IBSS or ESS";
              case 4:
                  return "Disassociated due to inactivity";
              case 5:
                  return "Disassociated because AP is unable to handle all currently associated STAs";
              case 6:
                  return "Class 2 frame received from nonauthenticated STA";
              case 7:
                  return "Class 3 frame received from nonassociated STA";
              case 8:
                  return "Disassociated because sending STA is leaving (or has left) BSS";
              case 9:
                  return "STA requesting (re)association is not authenticated with responding STA";
              case 10:
                  return "Disassociated because the information in the Power Capability element is "
                          + "unacceptable";
              case 11:
                  return "Disassociated because the information in the Supported Channels element "
                          + "is unacceptable";
              case 12:
                  return "Disassociated due to BSS transition management";
              case 13:
                  return "Invalid element, i.e., an element defined in this standard for which the "
                          + "content does not meet the specifications in Clause 9";
              case 14:
                  return "Message integrity code (MIC) failure";
              case 15:
                  return "4-way handshake timeout";
              case 16:
                  return "Group key handshake timeout";
              case 17:
                  return "Element in 4-way handshake different from (Re)Association Request/Probe "
                          + "Response/Beacon frame";
              case 18:
                  return "Invalid group cipher";
              case 19:
                  return "Invalid pairwise cipher";
              case 20:
                  return "Invalid AKMP";
              case 21:
                  return "Unsupported RSNE version";
              case 22:
                  return "Invalid RSNE capabilities";
              case 23:
                  return "IEEE 802.1X authentication failed";
              case 24:
                  return "Cipher suite rejected because of the security policy";
              case 25:
                  return "TDLS direct-link teardown due to TDLS peer STA unreachable via the TDLS "
                          + "direct link";
              case 26:
                  return "TDLS direct-link teardown for unspecified reason";
              case 27:
                  return "Disassociated because session terminated by SSP request";
              case 28:
                  return "Disassociated because of lack of SSP roaming agreement";
              case 29:
                  return "Requested service rejected because of SSP cipher suite or AKM requirement";
              case 30:
                  return "Requested service not authorized in this location";
              case 31:
                  return "TS deleted because QoS AP lacks sufficient bandwidth for this QoS STA due"
                          + " to a change in BSS service characteristics or operational mode (e.g.,"
                          + " an HT BSS change from 40 MHz channel to 20 MHz channel)";
              case 32:
                  return "Disassociated for unspecified, QoS-related reason";
              case 33:
                  return "Disassociated because QoS AP lacks sufficient bandwidth for this QoS STA";
              case 34:
                  return "Disassociated because excessive number of frames need to be acknowledged,"
                          + " but are not acknowledged due to AP transmissions and/or poor channel "
                          + "conditions";
              case 35:
                  return "Disassociated because STA is transmitting outside the limits of its TXOPs";
              case 36:
                  return "Requesting STA is leaving the BSS (or resetting)";
              case 37:
                  return "Requesting STA is no longer using the stream or session";
              case 38:
                  return "Requesting STA received frames using a mechanism for which a setup has "
                          + "not been completed";
              case 39:
                  return "Requested from peer STA due to timeout";
              case 40:
              case 41:
              case 42:
              case 43:
              case 44:
                  return "<unspecified>";
              case 45:
                  return "Peer STA does not support the requested cipher suite";
              case 46:
                  return "In a DLS Teardown frame: The teardown was initiated by the DLS peer. In a"
                          + " Disassociation frame: Disassociated because authorized access limit "
                          + "reached";
              case 47:
                  return "In a DLS Teardown frame: The teardown was initiated by the AP. In a "
                          + "Disassociation frame: Disassociated due to external service "
                          + "requirements";
              case 48:
                  return "Invalid FT Action frame count";
              case 49:
                  return "Invalid pairwise master key identifier (PMKID)";
              case 50:
                  return "Invalid MDE";
              case 51:
                  return "Invalid FTE";
              case 52:
                  return "Mesh peering canceled for unknown reasons";
              case 53:
                  return "The mesh STA has reached the supported maximum number of peer mesh STAs";
              case 54:
                  return "The received information violates the Mesh Configuration policy "
                          + "configured in the mesh STA profile";
              case 55:
                  return "The mesh STA has received a Mesh Peering Close frame requesting to close "
                          + "the mesh peering.";
              case 56:
                  return "The mesh STA has resent dot11MeshMaxRetries Mesh Peering Open frames, "
                          + "without receiving a Mesh Peering Confirm frame.";
              case 57:
                  return "The confirmTimer for the mesh peering instance times out.";
              case 58:
                  return "The mesh STA fails to unwrap the GTK or the values in the wrapped "
                          + "contents do not match";
              case 59:
                  return "The mesh STA receives inconsistent information about the mesh parameters "
                          + "between mesh peering Management frames";
              case 60:
                  return "The mesh STA fails the authenticated mesh peering exchange because due to"
                          + " failure in selecting either the pairwise ciphersuite or group "
                          + "ciphersuite";
              case 61:
                  return "The mesh STA does not have proxy information for this external "
                          + "destination.";
              case 62:
                  return "The mesh STA does not have forwarding information for this destination.";
              case 63:
                  return "The mesh STA determines that the link to the next hop of an active path "
                          + "in its forwarding information is no longer usable.";
              case 64:
                  return "The Deauthentication frame was sent because the MAC address of the STA "
                          + "already exists in the mesh BSS. See 11.3.6.";
              case 65:
                  return "The mesh STA performs channel switch to meet regulatory requirements.";
              case 66:
                  return "The mesh STA performs channel switching with unspecified reason.";
              default:
                  return "Reserved";
          }
      }
  
      // Per 802.11-2012 Table 8-37.
      private String decodeIeee80211StatusCode(short statusCode) {
          switch (statusCode) {
              case 0:
                  return "Success";
              case 1:
                  return "Unspecified failure";
              case 2:
                  return "TDLS wakeup schedule rejected; alternative provided";
              case 3:
                  return "TDLS wakeup schedule rejected";
              case 4:
                  return "Reserved";
              case 5:
                  return "Security disabled";
              case 6:
                  return "Unacceptable lifetime";
              case 7:
                  return "Not in same BSS";
              case 8:
              case 9:
                  return "Reserved";
              case 10:
                  return "Capabilities mismatch";
              case 11:
                  return "Reassociation denied; could not confirm association exists";
              case 12:
                  return "Association denied for reasons outside standard";
              case 13:
                  return "Unsupported authentication algorithm";
              case 14:
                  return "Authentication sequence number of of sequence";
              case 15:
                  return "Authentication challenge failure";
              case 16:
                  return "Authentication timeout";
              case 17:
                  return "Association denied; too many STAs";
              case 18:
                  return "Association denied; must support BSSBasicRateSet";
              case 19:
                  return "Association denied; must support short preamble";
              case 20:
                  return "Association denied; must support PBCC";
              case 21:
                  return "Association denied; must support channel agility";
              case 22:
                  return "Association rejected; must support spectrum management";
              case 23:
                  return "Association rejected; unacceptable power capability";
              case 24:
                  return "Association rejected; unacceptable supported channels";
              case 25:
                  return "Association denied; must support short slot time";
              case 26:
                  return "Association denied; must support DSSS-OFDM";
              case 27:
                  return "Association denied; must support HT";
              case 28:
                  return "R0 keyholder unreachable (802.11r)";
              case 29:
                  return "Association denied; must support PCO transition time";
              case 30:
                  return "Refused temporarily";
              case 31:
                  return "Robust management frame policy violation";
              case 32:
                  return "Unspecified QoS failure";
              case 33:
                  return "Association denied; insufficient bandwidth for QoS";
              case 34:
                  return "Association denied; poor channel";
              case 35:
                  return "Association denied; must support QoS";
              case 36:
                  return "Reserved";
              case 37:
                  return "Declined";
              case 38:
                  return "Invalid parameters";
              case 39:
                  return "TS cannot be honored; changes suggested";
              case 40:
                  return "Invalid element";
              case 41:
                  return "Invalid group cipher";
              case 42:
                  return "Invalid pairwise cipher";
              case 43:
                  return "Invalid auth/key mgmt proto (AKMP)";
              case 44:
                  return "Unsupported RSNE version";
              case 45:
                  return "Invalid RSNE capabilities";
              case 46:
                  return "Cipher suite rejected by policy";
              case 47:
                  return "TS cannot be honored now; try again later";
              case 48:
                  return "Direct link rejected by policy";
              case 49:
                  return "Destination STA not in BSS";
              case 50:
                  return "Destination STA not configured for QoS";
              case 51:
                  return "Association denied; listen interval too large";
              case 52:
                  return "Invalid fast transition action frame count";
              case 53:
                  return "Invalid PMKID";
              case 54:
                  return "Invalid MDE";
              case 55:
                  return "Invalid FTE";
              case 56:
                  return "Unsupported TCLAS";
              case 57:
                  return "Requested TCLAS exceeds resources";
              case 58:
                  return "TS cannot be honored; try another BSS";
              case 59:
                  return "GAS Advertisement not supported";
              case 60:
                  return "No outstanding GAS request";
              case 61:
                  return "No query response from GAS server";
              case 62:
                  return "GAS query timeout";
              case 63:
                  return "GAS response too large";
              case 64:
                  return "Home network does not support request";
              case 65:
                  return "Advertisement server unreachable";
              case 66:
                  return "Reserved";
              case 67:
                  return "Rejected for SSP permissions";
              case 68:
                  return "Authentication required";
              case 69:
              case 70:
              case 71:
                  return "Reserved";
              case 72:
                  return "Invalid RSNE contents";
              case 73:
                  return "U-APSD coexistence unsupported";
              case 74:
                  return "Requested U-APSD coex mode unsupported";
              case 75:
                  return "Requested parameter unsupported with U-APSD coex";
              case 76:
                  return "Auth rejected; anti-clogging token required";
              case 77:
                  return "Auth rejected; offered group is not supported";
              case 78:
                  return "Cannot find alternative TBTT";
              case 79:
                  return "Transmission failure";
              case 80:
                  return "Requested TCLAS not supported";
              case 81:
                  return "TCLAS resources exhausted";
              case 82:
                  return "Rejected with suggested BSS transition";
              case 83:
                  return "Reserved";
              case 84:
              case 85:
              case 86:
              case 87:
              case 88:
              case 89:
              case 90:
              case 91:
                  return "<unspecified>";
              case 92:
                  return "Refused due to external reason";
              case 93:
                  return "Refused; AP out of memory";
              case 94:
                  return "Refused; emergency services not supported";
              case 95:
                  return "GAS query response outstanding";
              case 96:
              case 97:
              case 98:
              case 99:
                  return "Reserved";
              case 100:
                  return "Failed; reservation conflict";
              case 101:
                  return "Failed; exceeded MAF limit";
              case 102:
                  return "Failed; exceeded MCCA track limit";
              default:
                  return "Reserved";
          }
      }
  }
  