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
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    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
148class 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
604class 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