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