summaryrefslogtreecommitdiff
path: root/service/java/com/android/server/wifi/util/FrameParser.java
blob: c48ca8e2e40af7d627986e8b828b41b89d160cad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
/*
 * 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";
        }
    }
}