• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
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
6import collections
7import pyshark
8import os
9
10class 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
146class 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    # 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    # 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    # 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
602class 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