Home | History | Annotate | Download | only in chaos_lib
      1 #!/usr/bin/python
      2 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import collections
      7 import pyshark
      8 import os
      9 
     10 class PacketCapture(object):
     11     """ Class to manage the packet capture file access from a chaos test. """
     12 
     13     def __init__(self, file_name):
     14         self._file_name = file_name
     15 
     16     def get_output(self, display_filter=None, summaries=True, decryption=None):
     17         """
     18         Gets the packets from a trace file as Pyshark packet objects for
     19         further analysis.
     20 
     21         @param display_filer: Tshark filter to be used for extracting the
     22                               relevant packets.
     23         @param summaries: Flag to indicate whether to extract only the summaries
     24                           of packet or not.
     25         @param decryption: Decryption key to be used on the trace file.
     26         @returns List of pyshark packet objects.
     27 
     28         """
     29         capture = pyshark.FileCapture(self._file_name,
     30                                       display_filter=display_filter,
     31                                       only_summaries=summaries,
     32                                       decryption_key=decryption,
     33                                       encryption_type='wpa-pwd')
     34         capture.load_packets()
     35         return capture
     36 
     37     def get_packet_number(self, index, summary):
     38         """
     39         Gets the packet that appears index |index| in the capture file.
     40 
     41         @param index: Extract this index from the capture file.
     42         @param summary: Flag to indicate whether to extract only the summary
     43                         of the packet or not.
     44 
     45         @returns pyshark packet object or None.
     46 
     47         """
     48         display_filter = "frame.number == %d" % index
     49         capture = pyshark.FileCapture(self._file_name,
     50                                       display_filter=display_filter,
     51                                       only_summaries=summary)
     52         capture.load_packets()
     53         if not capture:
     54             return None
     55         return capture[0]
     56 
     57     def get_packet_after(self, packet):
     58         """
     59         Gets the packet that appears next in the capture file.
     60 
     61         @param packet: Reference packet -- the packet after this one will
     62                        be retrieved.
     63 
     64         @returns pyshark packet object or None.
     65 
     66         """
     67         return self.get_packet_number(int(packet.number) + 1, summary=False)
     68 
     69     def count_packets_with_display_filter(self, display_filter):
     70         """
     71         Counts the number of packets which match the provided display filter.
     72 
     73         @param display_filer: Tshark filter to be used for extracting the
     74                               relevant packets.
     75         @returns Number of packets which match the filter.
     76 
     77         """
     78         output = self.get_output(display_filter=display_filter)
     79         return len(output)
     80 
     81     def count_packets_from(self, mac_addresses):
     82         """
     83         Counts the number of packets sent from a given entity using MAC address.
     84 
     85         @param mac_address: Mac address of the entity.
     86         @returns Number of packets which matched the MAC address filter.
     87 
     88         """
     89         filter = ' or '.join(['wlan.ta==%s' % addr for addr in mac_addresses])
     90         return self.count_packets_with_display_filter(filter)
     91 
     92     def count_packets_to(self, mac_addresses):
     93         """
     94         Counts the number of packets sent to a given entity using MAC address.
     95 
     96         @param mac_address: Mac address of the entity.
     97         @returns Number of packets which matched the MAC address filter.
     98 
     99         """
    100         filter = ' or '.join(['wlan.ra==%s' % addr for addr in mac_addresses])
    101         return self.count_packets_with_display_filter(filter)
    102 
    103     def count_packets_from_or_to(self, mac_addresses):
    104         """
    105         Counts the number of packets sent to/from a given entity using MAC
    106         address.
    107 
    108         @param mac_address: Mac address of the entity.
    109         @returns Number of packets which matched the MAC address filter.
    110 
    111         """
    112         filter = ' or '.join(['wlan.addr==%s' % addr for addr in mac_addresses])
    113         return self.count_packets_with_display_filter(filter)
    114 
    115     def count_beacons_from(self, mac_addresses):
    116         """
    117         Counts the number of beacon packets sent from a AP using MAC address.
    118 
    119         @param mac_address: Mac address of the AP.
    120         @returns Number of packets which matched the MAC address filter.
    121 
    122         """
    123         filter = ' or '.join(['wlan.ta==%s' % addr for addr in mac_addresses])
    124         filter = '(%s) and wlan.fc.type_subtype == 0x0008' % (filter)
    125         return self.count_packets_with_display_filter(filter)
    126 
    127     def get_filtered_packets(self, ap, dut, summaries, decryption):
    128         """
    129         Gets the packets sent to/from the DUT from a trace file as Pyshark
    130         packet objects for further analysis.
    131 
    132         @param summaries: Flag to indicate whether to extract only the summaries
    133                           of packet or not.
    134         @param dut: Mac address of the DUT.
    135         @param ap: Mac address of the AP.
    136         @param decryption: Decryption key to be used on the trace file.
    137         @returns List of pyshark packet objects.
    138 
    139         """
    140         filter = 'wlan.addr==%s' % dut
    141         packets = self.get_output(display_filter=filter, summaries=summaries,
    142                                   decryption=decryption)
    143         return packets
    144 
    145 
    146 class WifiStateMachineAnalyzer(object):
    147     """ Class to analyze the Wifi Protocol exhcange from a chaos test. """
    148 
    149     STATE_INIT = "INIT"
    150     STATE_PROBE_REQ = "PROBE_REQ"
    151     STATE_PROBE_RESP = "PROBE_RESP"
    152     STATE_AUTH_REQ = "AUTH_REQ"
    153     STATE_AUTH_RESP = "AUTH_RESP"
    154     STATE_ASSOC_REQ = "ASSOC_REQ"
    155     STATE_ASSOC_RESP = "ASSOC_RESP"
    156     STATE_KEY_MESSAGE_1 = "KEY_MESSAGE_1"
    157     STATE_KEY_MESSAGE_2 = "KEY_MESSAGE_2"
    158     STATE_KEY_MESSAGE_3 = "KEY_MESSAGE_3"
    159     STATE_KEY_MESSAGE_4 = "KEY_MESSAGE_4"
    160     STATE_DHCP_DISCOVER = "DHCP_DISCOVER"
    161     STATE_DHCP_OFFER = "DHCP_OFFER"
    162     STATE_DHCP_REQ = "DHCP_REQ"
    163     STATE_DHCP_REQ_ACK = "DHCP_REQ_ACK"
    164     STATE_END = "END"
    165 
    166 
    167     PACKET_MATCH_WLAN_FRAME_TYPE = "wlan.fc_type_subtype"
    168     PACKET_MATCH_WLAN_FRAME_RETRY_FLAG = "wlan.fc_retry"
    169     PACKET_MATCH_WLAN_MANAGEMENT_REASON_CODE = "wlan_mgt.fixed_reason_code"
    170     PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE = "wlan_mgt.fixed_status_code"
    171     PACKET_MATCH_WLAN_TRANSMITTER = "wlan.ta"
    172     PACKET_MATCH_LLC_TYPE = "llc.type"
    173     PACKET_MATCH_EAP_TYPE = "eapol.type"
    174     PACKET_MATCH_EAP_KEY_INFO_INSTALL = "eapol.keydes_key_info_install"
    175     PACKET_MATCH_EAP_KEY_INFO_ACK = "eapol.keydes_key_info_key_ack"
    176     PACKET_MATCH_EAP_KEY_INFO_MIC = "eapol.keydes_key_info_key_mic"
    177     PACKET_MATCH_EAP_KEY_INFO_SECURE = "eapol.keydes_key_info_secure"
    178     PACKET_MATCH_IP_PROTOCOL_TYPE = "ip.proto"
    179     PACKET_MATCH_DHCP_MESSAGE_TYPE = "bootp.option_dhcp"
    180     PACKET_MATCH_RADIOTAP_DATA_RATE = "radiotap.datarate"
    181 
    182     WLAN_PROBE_REQ_FRAME_TYPE = '0x04'
    183     WLAN_PROBE_RESP_FRAME_TYPE = '0x05'
    184     WLAN_AUTH_REQ_FRAME_TYPE = '0x0b'
    185     WLAN_AUTH_RESP_FRAME_TYPE = '0x0b'
    186     WLAN_ASSOC_REQ_FRAME_TYPE = '0x00'
    187     WLAN_ASSOC_RESP_FRAME_TYPE = '0x01'
    188     WLAN_ACK_FRAME_TYPE = '0x1d'
    189     WLAN_DEAUTH_REQ_FRAME_TYPE = '0x0c'
    190     WLAN_DISASSOC_REQ_FRAME_TYPE = '0x0a'
    191     WLAN_QOS_DATA_FRAME_TYPE = '0x28'
    192     WLAN_MANAGEMENT_STATUS_CODE_SUCCESS = '0x0000'
    193     WLAN_BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff'
    194     WLAN_FRAME_CONTROL_TYPE_MANAGEMENT = '0'
    195 
    196     WLAN_FRAME_RETRY = '1'
    197 
    198     LLC_AUTH_TYPE = '0x888e'
    199 
    200     EAP_KEY_TYPE = '0x03'
    201 
    202     IP_UDP_PROTOCOL_TYPE = '17'
    203 
    204     DHCP_DISCOVER_MESSAGE_TYPE = '1'
    205     DHCP_OFFER_MESSAGE_TYPE = '2'
    206     DHCP_REQUEST_MESSAGE_TYPE = '3'
    207     DHCP_ACK_MESSAGE_TYPE = '5'
    208 
    209     DIR_TO_DUT = 0
    210     DIR_FROM_DUT = 1
    211     DIR_DUT_TO_AP = 2
    212     DIR_AP_TO_DUT = 3
    213     DIR_ACK = 4
    214 
    215     # State Info Tuples (Name, Direction, Match fields, Next State)
    216     StateInfo = collections.namedtuple(
    217             'StateInfo', ['name', 'direction', 'match_fields', 'next_state'])
    218     STATE_INFO_INIT = StateInfo("INIT", 0, {}, STATE_PROBE_REQ)
    219     STATE_INFO_PROBE_REQ = StateInfo("WLAN PROBE REQUEST",
    220                                      DIR_FROM_DUT,
    221                                      { PACKET_MATCH_WLAN_FRAME_TYPE:
    222                                        WLAN_PROBE_REQ_FRAME_TYPE },
    223                                      STATE_PROBE_RESP)
    224     STATE_INFO_PROBE_RESP = StateInfo("WLAN PROBE RESPONSE",
    225                                       DIR_AP_TO_DUT,
    226                                       { PACKET_MATCH_WLAN_FRAME_TYPE:
    227                                         WLAN_PROBE_RESP_FRAME_TYPE },
    228                                       STATE_AUTH_REQ)
    229     STATE_INFO_AUTH_REQ = StateInfo("WLAN AUTH REQUEST",
    230                                     DIR_DUT_TO_AP,
    231                                     { PACKET_MATCH_WLAN_FRAME_TYPE:
    232                                       WLAN_AUTH_REQ_FRAME_TYPE },
    233                                     STATE_AUTH_RESP)
    234     STATE_INFO_AUTH_RESP = StateInfo(
    235             "WLAN AUTH RESPONSE",
    236             DIR_AP_TO_DUT,
    237             { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_AUTH_REQ_FRAME_TYPE,
    238               PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE:
    239               WLAN_MANAGEMENT_STATUS_CODE_SUCCESS },
    240             STATE_ASSOC_REQ)
    241     STATE_INFO_ASSOC_REQ = StateInfo("WLAN ASSOC REQUEST",
    242                                      DIR_DUT_TO_AP,
    243                                      { PACKET_MATCH_WLAN_FRAME_TYPE:
    244                                        WLAN_ASSOC_REQ_FRAME_TYPE },
    245                                      STATE_ASSOC_RESP)
    246     STATE_INFO_ASSOC_RESP = StateInfo(
    247               "WLAN ASSOC RESPONSE",
    248               DIR_AP_TO_DUT,
    249               { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_ASSOC_RESP_FRAME_TYPE,
    250                 PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE:
    251                 WLAN_MANAGEMENT_STATUS_CODE_SUCCESS },
    252               STATE_KEY_MESSAGE_1)
    253     STATE_INFO_KEY_MESSAGE_1 = StateInfo("WPA KEY MESSAGE 1",
    254                                          DIR_AP_TO_DUT,
    255                                          { PACKET_MATCH_LLC_TYPE:
    256                                            LLC_AUTH_TYPE,
    257                                            PACKET_MATCH_EAP_KEY_INFO_INSTALL:
    258                                            '0',
    259                                            PACKET_MATCH_EAP_KEY_INFO_ACK:
    260                                            '1',
    261                                            PACKET_MATCH_EAP_KEY_INFO_MIC:
    262                                            '0',
    263                                            PACKET_MATCH_EAP_KEY_INFO_SECURE:
    264                                            '0' },
    265                                          STATE_KEY_MESSAGE_2)
    266     STATE_INFO_KEY_MESSAGE_2 = StateInfo("WPA KEY MESSAGE 2",
    267                                          DIR_DUT_TO_AP,
    268                                          { PACKET_MATCH_LLC_TYPE:
    269                                            LLC_AUTH_TYPE,
    270                                            PACKET_MATCH_EAP_KEY_INFO_INSTALL:
    271                                            '0',
    272                                            PACKET_MATCH_EAP_KEY_INFO_ACK:
    273                                            '0',
    274                                            PACKET_MATCH_EAP_KEY_INFO_MIC:
    275                                            '1',
    276                                            PACKET_MATCH_EAP_KEY_INFO_SECURE:
    277                                            '0' },
    278                                          STATE_KEY_MESSAGE_3)
    279     STATE_INFO_KEY_MESSAGE_3 = StateInfo("WPA KEY MESSAGE 3",
    280                                          DIR_AP_TO_DUT,
    281                                          { PACKET_MATCH_LLC_TYPE:
    282                                            LLC_AUTH_TYPE,
    283                                            PACKET_MATCH_EAP_KEY_INFO_INSTALL:
    284                                            '1',
    285                                            PACKET_MATCH_EAP_KEY_INFO_ACK:
    286                                            '1',
    287                                            PACKET_MATCH_EAP_KEY_INFO_MIC:
    288                                            '1',
    289                                            PACKET_MATCH_EAP_KEY_INFO_SECURE:
    290                                            '1' },
    291                                          STATE_KEY_MESSAGE_4)
    292     STATE_INFO_KEY_MESSAGE_4 = StateInfo("WPA KEY MESSAGE 4",
    293                                          DIR_DUT_TO_AP,
    294                                          { PACKET_MATCH_LLC_TYPE:
    295                                            LLC_AUTH_TYPE,
    296                                            PACKET_MATCH_EAP_KEY_INFO_INSTALL:
    297                                            '0',
    298                                            PACKET_MATCH_EAP_KEY_INFO_ACK:
    299                                            '0',
    300                                            PACKET_MATCH_EAP_KEY_INFO_MIC:
    301                                            '1',
    302                                            PACKET_MATCH_EAP_KEY_INFO_SECURE:
    303                                            '1' },
    304                                          STATE_DHCP_DISCOVER)
    305     STATE_INFO_DHCP_DISCOVER = StateInfo("DHCP DISCOVER",
    306                                          DIR_DUT_TO_AP,
    307                                          { PACKET_MATCH_IP_PROTOCOL_TYPE:
    308                                            IP_UDP_PROTOCOL_TYPE,
    309                                            PACKET_MATCH_DHCP_MESSAGE_TYPE:
    310                                            DHCP_DISCOVER_MESSAGE_TYPE },
    311                                          STATE_DHCP_OFFER)
    312     STATE_INFO_DHCP_OFFER = StateInfo("DHCP OFFER",
    313                                       DIR_AP_TO_DUT,
    314                                       { PACKET_MATCH_IP_PROTOCOL_TYPE:
    315                                         IP_UDP_PROTOCOL_TYPE,
    316                                         PACKET_MATCH_DHCP_MESSAGE_TYPE:
    317                                         DHCP_OFFER_MESSAGE_TYPE },
    318                                       STATE_DHCP_REQ)
    319     STATE_INFO_DHCP_REQ = StateInfo("DHCP REQUEST",
    320                                     DIR_DUT_TO_AP,
    321                                     { PACKET_MATCH_IP_PROTOCOL_TYPE:
    322                                       IP_UDP_PROTOCOL_TYPE,
    323                                       PACKET_MATCH_DHCP_MESSAGE_TYPE:
    324                                       DHCP_REQUEST_MESSAGE_TYPE },
    325                                     STATE_DHCP_REQ_ACK)
    326     STATE_INFO_DHCP_REQ_ACK = StateInfo("DHCP ACK",
    327                                         DIR_AP_TO_DUT,
    328                                         { PACKET_MATCH_IP_PROTOCOL_TYPE:
    329                                           IP_UDP_PROTOCOL_TYPE,
    330                                           PACKET_MATCH_DHCP_MESSAGE_TYPE:
    331                                           DHCP_ACK_MESSAGE_TYPE },
    332                                         STATE_END)
    333     STATE_INFO_END = StateInfo("END", 0, {}, STATE_END)
    334     # Master State Table Map of State Infos
    335     STATE_INFO_MAP = {STATE_INIT:         STATE_INFO_INIT,
    336                       STATE_PROBE_REQ:    STATE_INFO_PROBE_REQ,
    337                       STATE_PROBE_RESP:   STATE_INFO_PROBE_RESP,
    338                       STATE_AUTH_REQ:     STATE_INFO_AUTH_REQ,
    339                       STATE_AUTH_RESP:    STATE_INFO_AUTH_RESP,
    340                       STATE_ASSOC_REQ:    STATE_INFO_ASSOC_REQ,
    341                       STATE_ASSOC_RESP:   STATE_INFO_ASSOC_RESP,
    342                       STATE_KEY_MESSAGE_1:STATE_INFO_KEY_MESSAGE_1,
    343                       STATE_KEY_MESSAGE_2:STATE_INFO_KEY_MESSAGE_2,
    344                       STATE_KEY_MESSAGE_3:STATE_INFO_KEY_MESSAGE_3,
    345                       STATE_KEY_MESSAGE_4:STATE_INFO_KEY_MESSAGE_4,
    346                       STATE_DHCP_DISCOVER:STATE_INFO_DHCP_DISCOVER,
    347                       STATE_DHCP_OFFER:   STATE_INFO_DHCP_OFFER,
    348                       STATE_DHCP_REQ:     STATE_INFO_DHCP_REQ,
    349                       STATE_DHCP_REQ_ACK: STATE_INFO_DHCP_REQ_ACK,
    350                       STATE_END:          STATE_INFO_END}
    351 
    352     # Packet Details Tuples (User friendly name, Field name)
    353     PacketDetail = collections.namedtuple(
    354             "PacketDetail", ["friendly_name", "field_name"])
    355     PACKET_DETAIL_REASON_CODE = PacketDetail(
    356             "Reason Code",
    357             PACKET_MATCH_WLAN_MANAGEMENT_REASON_CODE)
    358     PACKET_DETAIL_STATUS_CODE = PacketDetail(
    359             "Status Code",
    360             PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE)
    361     PACKET_DETAIL_SENDER = PacketDetail(
    362             "Sender", PACKET_MATCH_WLAN_TRANSMITTER)
    363 
    364     # Error State Info Tuples (Name, Match fields)
    365     ErrorStateInfo = collections.namedtuple(
    366             'ErrorStateInfo', ['name', 'match_fields', 'details'])
    367     ERROR_STATE_INFO_DEAUTH = ErrorStateInfo("WLAN DEAUTH REQUEST",
    368                                              { PACKET_MATCH_WLAN_FRAME_TYPE:
    369                                                WLAN_DEAUTH_REQ_FRAME_TYPE },
    370                                              [ PACKET_DETAIL_SENDER,
    371                                                PACKET_DETAIL_REASON_CODE ])
    372     ERROR_STATE_INFO_DEASSOC = ErrorStateInfo("WLAN DISASSOC REQUEST",
    373                                             { PACKET_MATCH_WLAN_FRAME_TYPE:
    374                                               WLAN_DISASSOC_REQ_FRAME_TYPE },
    375                                             [ PACKET_DETAIL_SENDER,
    376                                               PACKET_DETAIL_REASON_CODE ])
    377     # Master State Table Tuple of Error State Infos
    378     ERROR_STATE_INFO_TUPLE = (ERROR_STATE_INFO_DEAUTH, ERROR_STATE_INFO_DEASSOC)
    379 
    380     # These warnings actually match successful states, but since the we
    381     # check forwards and backwards through the state machine for the successful
    382     # version of these packets, they can only match a failure.
    383     WARNING_INFO_AUTH_REJ = ErrorStateInfo(
    384             "WLAN AUTH REJECTED",
    385             { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_AUTH_REQ_FRAME_TYPE },
    386             [ PACKET_DETAIL_STATUS_CODE ])
    387     WARNING_INFO_ASSOC_REJ = ErrorStateInfo(
    388             "WLAN ASSOC REJECTED",
    389             { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_ASSOC_RESP_FRAME_TYPE },
    390             [ PACKET_DETAIL_STATUS_CODE ])
    391 
    392     # Master Table Tuple of warning information.
    393     WARNING_INFO_TUPLE = (WARNING_INFO_AUTH_REJ, WARNING_INFO_ASSOC_REJ)
    394 
    395 
    396     def __init__(self, ap_macs, dut_mac, filtered_packets, capture, logger):
    397         self._current_state = self._get_state(self.STATE_INIT)
    398         self._reached_states = []
    399         self._skipped_states = []
    400         self._packets = filtered_packets
    401         self._capture = capture
    402         self._dut_mac = dut_mac
    403         self._ap_macs = ap_macs
    404         self._log = logger
    405         self._acks = []
    406 
    407     @property
    408     def acks(self):
    409         return self._acks
    410 
    411     def _get_state(self, state):
    412         return self.STATE_INFO_MAP[state]
    413 
    414     def _get_next_state(self, state):
    415         return self._get_state(state.next_state)
    416 
    417     def _get_curr_next_state(self):
    418         return self._get_next_state(self._current_state)
    419 
    420     def _fetch_packet_field_value(self, packet, field):
    421         layer_object = packet
    422         for layer in field.split('.'):
    423             try:
    424                 layer_object = getattr(layer_object, layer)
    425             except AttributeError:
    426                 return None
    427         return layer_object
    428 
    429     def _match_packet_fields(self, packet, fields):
    430         for field, exp_value in fields.items():
    431             value = self._fetch_packet_field_value(packet, field)
    432             if exp_value != value:
    433                 return False
    434         return True
    435 
    436     def _fetch_packet_data_rate(self, packet):
    437         return self._fetch_packet_field_value(packet,
    438                 self.PACKET_MATCH_RADIOTAP_DATA_RATE)
    439 
    440     def _does_packet_match_state(self, state, packet):
    441         fields = state.match_fields
    442         if self._match_packet_fields(packet, fields):
    443             if state.direction == self.DIR_TO_DUT:
    444                 # This should have receiver addr of DUT
    445                 if packet.wlan.ra == self._dut_mac:
    446                     return True
    447             elif state.direction == self.DIR_FROM_DUT:
    448                 # This should have transmitter addr of DUT
    449                 if packet.wlan.ta == self._dut_mac:
    450                     return True
    451             elif state.direction == self.DIR_AP_TO_DUT:
    452                 # This should have receiver addr of DUT &
    453                 # transmitter addr of AP's
    454                 if ((packet.wlan.ra == self._dut_mac) and
    455                     (packet.wlan.ta in self._ap_macs)):
    456                     return True
    457             elif state.direction == self.DIR_DUT_TO_AP:
    458                 # This should have transmitter addr of DUT &
    459                 # receiver addr of AP's
    460                 if ((packet.wlan.ta == self._dut_mac) and
    461                     (packet.wlan.ra in self._ap_macs)):
    462                     return True
    463         return False
    464 
    465     def _does_packet_match_error_state(self, state, packet):
    466         fields = state.match_fields
    467         return self._match_packet_fields(packet, fields)
    468 
    469     def _get_packet_detail(self, details, packet):
    470         attributes = []
    471         attributes.append("Packet number: %s" % packet.number)
    472         for detail in details:
    473             value = self._fetch_packet_field_value(packet, detail.field_name)
    474             attributes.append("%s: %s" % (detail.friendly_name, value))
    475         return attributes
    476 
    477     def _does_packet_match_ack_state(self, packet):
    478         fields = { self.PACKET_MATCH_WLAN_FRAME_TYPE: self.WLAN_ACK_FRAME_TYPE }
    479         return self._match_packet_fields(packet, fields)
    480 
    481     def _does_packet_contain_retry_flag(self, packet):
    482         fields = { self.PACKET_MATCH_WLAN_FRAME_RETRY_FLAG:
    483                    self.WLAN_FRAME_RETRY }
    484         return self._match_packet_fields(packet, fields)
    485 
    486     def _check_for_ack(self, state, packet):
    487         if (packet.wlan.da == self.WLAN_BROADCAST_ADDRESS and
    488             packet.wlan.fc_type == self.WLAN_FRAME_CONTROL_TYPE_MANAGEMENT):
    489             # Broadcast management frames are not ACKed.
    490             return True
    491         next_packet = self._capture.get_packet_after(packet)
    492         if not next_packet or not (
    493                 (self._does_packet_match_ack_state(next_packet)) and
    494                 (next_packet.wlan.addr == packet.wlan.ta)):
    495             msg = "WARNING! Missing ACK for state: " + \
    496                   state.name + "."
    497             self._log.log_to_output_file(msg)
    498             return False
    499         self._acks.append(int(next_packet.number))
    500         return True
    501 
    502     def _check_for_error(self, packet):
    503         for error_state in self.ERROR_STATE_INFO_TUPLE:
    504             if self._does_packet_match_error_state(error_state, packet):
    505                 error_attributes = self._get_packet_detail(error_state.details,
    506                                                            packet)
    507                 msg = "ERROR! State Machine encountered error due to " + \
    508                       error_state.name + ", " + \
    509                       ", ".join(error_attributes) + "."
    510                 self._log.log_to_output_file(msg)
    511                 return True
    512         return False
    513 
    514     def _check_for_warning(self, packet):
    515         for warning in self.WARNING_INFO_TUPLE:
    516             if self._does_packet_match_error_state(warning, packet):
    517                 error_attributes = self._get_packet_detail(warning.details,
    518                                                            packet)
    519                 msg = "WARNING! " + warning.name + " found, " + \
    520                       ", ".join(error_attributes) + "."
    521                 self._log.log_to_output_file(msg)
    522                 return True
    523         return False
    524 
    525     def _check_for_repeated_state(self, packet):
    526         for state in self._reached_states:
    527             if self._does_packet_match_state(state, packet):
    528                 msg = "WARNING! Repeated State: " + \
    529                       state.name + ", Packet number: " + \
    530                       str(packet.number)
    531                 if self._does_packet_contain_retry_flag(packet):
    532                     msg += " due to retransmission."
    533                 else:
    534                     msg +=  "."
    535                 self._log.log_to_output_file(msg)
    536 
    537     def _is_from_previous_state(self, packet):
    538         for state in self._reached_states + self._skipped_states:
    539             if self._does_packet_match_state(state, packet):
    540                 return True
    541         return False
    542 
    543     def _step(self, reached_state, packet):
    544         # We missed a few packets in between
    545         if self._current_state != reached_state:
    546             msg = "WARNING! Missed states: "
    547             skipped_state = self._current_state
    548             while skipped_state != reached_state:
    549                 msg += skipped_state.name + ", "
    550                 self._skipped_states.append(skipped_state)
    551                 skipped_state = self._get_next_state(skipped_state)
    552             msg = msg[:-2]
    553             msg += "."
    554             self._log.log_to_output_file(msg)
    555         msg = "Found state: " + reached_state.name
    556         if packet:
    557             msg += ", Packet number: " + str(packet.number) + \
    558                    ", Data rate: " + str(self._fetch_packet_data_rate(packet))+\
    559                    "Mbps."
    560         else:
    561             msg += "."
    562         self._log.log_to_output_file(msg)
    563         # Ignore the Init state in the reached states
    564         if packet:
    565             self._reached_states.append(reached_state)
    566         self._current_state = self._get_next_state(reached_state)
    567 
    568     def _step_init(self):
    569         #self.log_to_output_file("Starting Analysis")
    570         self._current_state = self._get_curr_next_state()
    571 
    572     def analyze(self):
    573         """ Starts the analysis of the Wifi Protocol Exchange. """
    574 
    575         # Start the state machine iteration
    576         self._step_init()
    577         packet_iterator = iter(self._packets)
    578         for packet in packet_iterator:
    579             self._check_for_repeated_state(packet)
    580             # Try to look ahead in the state machine to account for occasional
    581             # packet capture misses.
    582             next_state = self._current_state
    583             while next_state != self.STATE_INFO_END:
    584                 if self._does_packet_match_state(next_state, packet):
    585                     self._step(next_state, packet)
    586                     self._check_for_ack(next_state, packet)
    587                     break
    588                 next_state = self._get_next_state(next_state)
    589             if self._current_state == self.STATE_INFO_END:
    590                 self._log.log_to_output_file("State Machine completed!")
    591                 return True
    592             if self._check_for_error(packet):
    593                 return False
    594             if not self._is_from_previous_state(packet):
    595                 self._check_for_warning(packet)
    596         msg = "ERROR! State Machine halted at " + self._current_state.name + \
    597               " state."
    598         self._log.log_to_output_file(msg)
    599         return False
    600 
    601 
    602 class ChaosCaptureAnalyzer(object):
    603     """ Class to analyze the packet capture from a chaos test . """
    604 
    605     def __init__(self, ap_bssids, ap_ssid, dut_mac, logger):
    606         self._ap_bssids = ap_bssids
    607         self._ap_ssid = ap_ssid
    608         self._dut_mac = dut_mac
    609         self._log = logger
    610 
    611     def _validate_ap_presence(self, capture, bssids, ssid):
    612         beacon_count = capture.count_beacons_from(bssids)
    613         if not beacon_count:
    614             packet_count = capture.count_packets_from(bssids)
    615             if not packet_count:
    616                 self._log.log_to_output_file(
    617                         "No packets at all from AP BSSIDs %r!" % bssids)
    618             else:
    619                 self._log.log_to_output_file(
    620                         "No beacons from AP BSSIDs %r but %d packets!" %
    621                         (bssids, packet_count))
    622             return False
    623         self._log.log_to_output_file("AP BSSIDs: %s, SSID: %s." %
    624                                      (bssids, ssid))
    625         self._log.log_to_output_file("AP beacon count: %d." % beacon_count)
    626         return True
    627 
    628     def _validate_dut_presence(self, capture, dut_mac):
    629         tx_count = capture.count_packets_from([dut_mac])
    630         if not tx_count:
    631             self._log.log_to_output_file(
    632                     "No packets Tx at all from DUT MAC %r!" % dut_mac)
    633             return False
    634         rx_count = capture.count_packets_to([dut_mac])
    635         self._log.log_to_output_file("DUT MAC: %s." % dut_mac)
    636         self._log.log_to_output_file(
    637                 "DUT packet count Tx: %d, Rx: %d." % (tx_count, rx_count))
    638         return True
    639 
    640     def _ack_interleave(self, packets, capture, acks):
    641         """Generator that interleaves packets with their associated ACKs."""
    642         for packet in packets:
    643             packet_number = int(packet.no)
    644             while acks and acks[0] < packet_number:
    645                 # ACK packet does not appear in the filtered capture.
    646                 yield capture.get_packet_number(acks.pop(0), summary=True)
    647             if acks and acks[0] == packet_number:
    648                 # ACK packet also appears in the capture.
    649                 acks.pop(0)
    650             yield packet
    651 
    652     def analyze(self, trace):
    653         """
    654         Starts the analysis of the Chaos capture.
    655 
    656         @param trace: Packet capture file path to analyze.
    657 
    658         """
    659         basename = os.path.basename(trace)
    660         self._log.log_start_section("Packet Capture File: %s" % basename)
    661         capture = PacketCapture(trace)
    662         bssids = self._ap_bssids
    663         ssid =  self._ap_ssid
    664         if not self._validate_ap_presence(capture, bssids, ssid):
    665             return
    666         dut_mac = self._dut_mac
    667         if not self._validate_dut_presence(capture, dut_mac):
    668             return
    669         decryption = 'chromeos:%s' % ssid
    670         self._log.log_start_section("WLAN Protocol Verification")
    671         filtered_packets = capture.get_filtered_packets(
    672                bssids, dut_mac, False, decryption)
    673         wifi_state_machine = WifiStateMachineAnalyzer(
    674                bssids, dut_mac, filtered_packets, capture, self._log)
    675         wifi_state_machine.analyze()
    676         self._log.log_start_section("Filtered Packet Capture Summary")
    677         filtered_packets = capture.get_filtered_packets(
    678                bssids, dut_mac, True, decryption)
    679         for packet in self._ack_interleave(
    680                filtered_packets, capture, wifi_state_machine.acks):
    681             self._log.log_to_output_file("%s" % (packet))
    682