1# FST tests related classes
2# Copyright (c) 2015, Qualcomm Atheros, Inc.
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import logging
8import os
9import signal
10import time
11import re
12
13import hostapd
14import wpaspy
15import utils
16from wpasupplicant import WpaSupplicant
17
18import fst_test_common
19
20logger = logging.getLogger()
21
22def parse_fst_iface_event(ev):
23    """Parses FST iface event that comes as a string, e.g.
24    "<3>FST-EVENT-IFACE attached ifname=wlan9 group=fstg0"
25    Returns a dictionary with parsed "event_type", "ifname", and "group"; or
26    None if not an FST event or can't be parsed."""
27    event = {}
28    if ev.find("FST-EVENT-IFACE") == -1:
29        return None
30    if ev.find("attached") != -1:
31        event['event_type'] = 'attached'
32    elif ev.find("detached") != -1:
33        event['event_type'] = 'detached'
34    else:
35        return None
36    f = re.search("ifname=(\S+)", ev)
37    if f is not None:
38        event['ifname'] = f.group(1)
39    f = re.search("group=(\S+)", ev)
40    if f is not None:
41        event['group'] = f.group(1)
42    return event
43
44def parse_fst_session_event(ev):
45    """Parses FST session event that comes as a string, e.g.
46    "<3>FST-EVENT-SESSION event_type=EVENT_FST_SESSION_STATE session_id=0 reason=REASON_STT"
47    Returns a dictionary with parsed "type", "id", and "reason"; or None if not
48    a FST event or can't be parsed"""
49    event = {}
50    if ev.find("FST-EVENT-SESSION") == -1:
51        return None
52    event['new_state'] = '' # The field always exists in the dictionary
53    f = re.search("event_type=(\S+)", ev)
54    if f is None:
55        return None
56    event['type'] = f.group(1)
57    f = re.search("session_id=(\d+)", ev)
58    if f is not None:
59        event['id'] = f.group(1)
60    f = re.search("old_state=(\S+)", ev)
61    if f is not None:
62        event['old_state'] = f.group(1)
63    f = re.search("new_state=(\S+)", ev)
64    if f is not None:
65        event['new_state'] = f.group(1)
66    f = re.search("reason=(\S+)", ev)
67    if f is not None:
68        event['reason'] = f.group(1)
69    return event
70
71def start_two_ap_sta_pairs(apdev, rsn=False):
72    """auxiliary function that creates two pairs of APs and STAs"""
73    ap1 = FstAP(apdev[0]['ifname'], 'fst_11a', 'a',
74                fst_test_common.fst_test_def_chan_a,
75                fst_test_common.fst_test_def_group,
76                fst_test_common.fst_test_def_prio_low,
77                fst_test_common.fst_test_def_llt, rsn=rsn)
78    ap1.start()
79    ap2 = FstAP(apdev[1]['ifname'], 'fst_11g', 'g',
80                fst_test_common.fst_test_def_chan_g,
81                fst_test_common.fst_test_def_group,
82                fst_test_common.fst_test_def_prio_high,
83                fst_test_common.fst_test_def_llt, rsn=rsn)
84    ap2.start()
85
86    sta1 = FstSTA('wlan5',
87                  fst_test_common.fst_test_def_group,
88                  fst_test_common.fst_test_def_prio_low,
89                  fst_test_common.fst_test_def_llt, rsn=rsn)
90    sta1.start()
91    sta2 = FstSTA('wlan6',
92                  fst_test_common.fst_test_def_group,
93                  fst_test_common.fst_test_def_prio_high,
94                  fst_test_common.fst_test_def_llt, rsn=rsn)
95    sta2.start()
96
97    return ap1, ap2, sta1, sta2
98
99def stop_two_ap_sta_pairs(ap1, ap2, sta1, sta2):
100    sta1.stop()
101    sta2.stop()
102    ap1.stop()
103    ap2.stop()
104    fst_test_common.fst_clear_regdom()
105
106def connect_two_ap_sta_pairs(ap1, ap2, dev1, dev2, rsn=False):
107    """Connects a pair of stations, each one to a separate AP"""
108    dev1.scan(freq=fst_test_common.fst_test_def_freq_a)
109    dev2.scan(freq=fst_test_common.fst_test_def_freq_g)
110
111    if rsn:
112        dev1.connect(ap1, psk="12345678",
113                     scan_freq=fst_test_common.fst_test_def_freq_a)
114        dev2.connect(ap2, psk="12345678",
115                     scan_freq=fst_test_common.fst_test_def_freq_g)
116    else:
117        dev1.connect(ap1, key_mgmt="NONE",
118                     scan_freq=fst_test_common.fst_test_def_freq_a)
119        dev2.connect(ap2, key_mgmt="NONE",
120                     scan_freq=fst_test_common.fst_test_def_freq_g)
121
122def disconnect_two_ap_sta_pairs(ap1, ap2, dev1, dev2):
123    dev1.disconnect()
124    dev2.disconnect()
125
126def external_sta_connect(sta, ap, **kwargs):
127    """Connects the external station to the given AP"""
128    if not isinstance(sta, WpaSupplicant):
129        raise Exception("Bad STA object")
130    if not isinstance(ap, FstAP):
131        raise Exception("Bad AP object to connect to")
132    hap = ap.get_instance()
133    sta.connect(ap.get_ssid(), **kwargs)
134
135def disconnect_external_sta(sta, ap, check_disconnect=True):
136    """Disconnects the external station from the AP"""
137    if not isinstance(sta, WpaSupplicant):
138        raise Exception("Bad STA object")
139    if not isinstance(ap, FstAP):
140        raise Exception("Bad AP object to connect to")
141    sta.request("DISCONNECT")
142    if check_disconnect:
143        hap = ap.get_instance()
144        ev = hap.wait_event(["AP-STA-DISCONNECTED"], timeout=10)
145        if ev is None:
146            raise Exception("No disconnection event received from %s" % ap.get_ssid())
147
148#
149# FstDevice class
150# This is the parent class for the AP (FstAP) and STA (FstSTA) that implements
151# FST functionality.
152#
153class FstDevice:
154    def __init__(self, iface, fst_group, fst_pri, fst_llt=None, rsn=False):
155        self.iface = iface
156        self.fst_group = fst_group
157        self.fst_pri = fst_pri
158        self.fst_llt = fst_llt  # None llt means no llt parameter will be set
159        self.instance = None    # Hostapd/WpaSupplicant instance
160        self.peer_obj = None    # Peer object, must be a FstDevice child object
161        self.new_peer_addr = None # Peer MAC address for new session iface
162        self.old_peer_addr = None # Peer MAC address for old session iface
163        self.role = 'initiator' # Role: initiator/responder
164        s = self.grequest("FST-MANAGER TEST_REQUEST IS_SUPPORTED")
165        if not s.startswith('OK'):
166            raise utils.HwsimSkip("FST not supported")
167        self.rsn = rsn
168
169    def ifname(self):
170        return self.iface
171
172    def get_instance(self):
173        """Gets the Hostapd/WpaSupplicant instance"""
174        raise Exception("Virtual get_instance() called!")
175
176    def get_own_mac_address(self):
177        """Gets the device's own MAC address"""
178        raise Exception("Virtual get_own_mac_address() called!")
179
180    def get_new_peer_addr(self):
181        return self.new_peer_addr
182
183    def get_old_peer_addr(self):
184        return self.old_peer_addr
185
186    def get_actual_peer_addr(self):
187        """Gets the peer address. A connected AP/station address is returned."""
188        raise Exception("Virtual get_actual_peer_addr() called!")
189
190    def grequest(self, req):
191        """Send request on the global control interface"""
192        raise Exception("Virtual grequest() called!")
193
194    def wait_gevent(self, events, timeout=None):
195        """Wait for a list of events on the global interface"""
196        raise Exception("Virtual wait_gevent() called!")
197
198    def request(self, req):
199        """Issue a request to the control interface"""
200        h = self.get_instance()
201        return h.request(req)
202
203    def wait_event(self, events, timeout=None):
204        """Wait for an event from the control interface"""
205        h = self.get_instance()
206        if timeout is not None:
207            return h.wait_event(events, timeout=timeout)
208        else:
209            return h.wait_event(events)
210
211    def set_old_peer_addr(self, peer_addr=None):
212        """Sets the peer address"""
213        if peer_addr is not None:
214            self.old_peer_addr = peer_addr
215        else:
216            self.old_peer_addr = self.get_actual_peer_addr()
217
218    def set_new_peer_addr(self, peer_addr=None):
219        """Sets the peer address"""
220        if peer_addr is not None:
221            self.new_peer_addr = peer_addr
222        else:
223            self.new_peer_addr = self.get_actual_peer_addr()
224
225    def add_peer(self, obj, old_peer_addr=None, new_peer_addr=None):
226        """Add peer for FST session(s). 'obj' is a FstDevice subclass object.
227        The method must be called before add_session().
228        If peer_addr is not specified, the address of the currently connected
229        station is used."""
230        if not isinstance(obj, FstDevice):
231            raise Exception("Peer must be a FstDevice object")
232        self.peer_obj = obj
233        self.set_old_peer_addr(old_peer_addr)
234        self.set_new_peer_addr(new_peer_addr)
235
236    def get_peer(self):
237        """Returns peer object"""
238        return self.peer_obj
239
240    def set_fst_parameters(self, group_id=None, pri=None, llt=None):
241        """Change/set new FST parameters. Can be used to start FST sessions with
242        different FST parameters than defined in the configuration file."""
243        if group_id is not None:
244            self.fst_group = group_id
245        if pri is not None:
246            self.fst_pri = pri
247        if llt is not None:
248            self.fst_llt = llt
249
250    def get_local_mbies(self, ifname=None):
251        if_name = ifname if ifname is not None else self.iface
252        return self.grequest("FST-MANAGER TEST_REQUEST GET_LOCAL_MBIES " + if_name)
253
254    def add_session(self):
255        """Adds an FST session. add_peer() must be called calling this
256        function"""
257        if self.peer_obj is None:
258            raise Exception("Peer wasn't added before starting session")
259        self.dump_monitor()
260        grp = ' ' + self.fst_group if self.fst_group != '' else ''
261        sid = self.grequest("FST-MANAGER SESSION_ADD" + grp)
262        sid = sid.strip()
263        if sid.startswith("FAIL"):
264            raise Exception("Cannot add FST session with groupid ==" + grp)
265        self.dump_monitor()
266        return sid
267
268    def set_session_param(self, params):
269        request = "FST-MANAGER SESSION_SET"
270        if params is not None and params != '':
271            request = request + ' ' + params
272        return self.grequest(request)
273
274    def get_session_params(self, sid):
275        request = "FST-MANAGER SESSION_GET " + sid
276        res = self.grequest(request)
277        if res.startswith("FAIL"):
278            return None
279        params = {}
280        for i in res.splitlines():
281            p = i.split('=')
282            params[p[0]] = p[1]
283        return params
284
285    def iface_peers(self, ifname):
286        grp = self.fst_group if self.fst_group != '' else ''
287        res = self.grequest("FST-MANAGER IFACE_PEERS " + grp + ' ' + ifname)
288        if res.startswith("FAIL"):
289            return None
290        return res.splitlines()
291
292    def get_peer_mbies(self, ifname, peer_addr):
293        return self.grequest("FST-MANAGER GET_PEER_MBIES %s %s" % (ifname, peer_addr))
294
295    def list_ifaces(self):
296        grp = self.fst_group if self.fst_group != '' else ''
297        res = self.grequest("FST-MANAGER LIST_IFACES " + grp)
298        if res.startswith("FAIL"):
299            return None
300        ifaces = []
301        for i in res.splitlines():
302            p = i.split(':')
303            iface = {}
304            iface['name'] = p[0]
305            iface['priority'] = p[1]
306            iface['llt'] = p[2]
307            ifaces.append(iface)
308        return ifaces
309
310    def list_groups(self):
311        res = self.grequest("FST-MANAGER LIST_GROUPS")
312        if res.startswith("FAIL"):
313            return None
314        return res.splitlines()
315
316    def configure_session(self, sid, new_iface, old_iface=None):
317        """Calls session_set for a number of parameters some of which are stored
318        in "self" while others are passed to this function explicitly. If
319        old_iface is None, current iface is used; if old_iface is an empty
320        string."""
321        self.dump_monitor()
322        oldiface = old_iface if old_iface is not None else self.iface
323        s = self.set_session_param(sid + ' old_ifname=' + oldiface)
324        if not s.startswith("OK"):
325            raise Exception("Cannot set FST session old_ifname: " + s)
326        if new_iface is not None:
327            s = self.set_session_param(sid + " new_ifname=" + new_iface)
328            if not s.startswith("OK"):
329                raise Exception("Cannot set FST session new_ifname:" + s)
330        if self.new_peer_addr is not None and self.new_peer_addr != '':
331            s = self.set_session_param(sid + " new_peer_addr=" + self.new_peer_addr)
332            if not s.startswith("OK"):
333                raise Exception("Cannot set FST session peer address:" + s + " (new)")
334        if self.old_peer_addr is not None and self.old_peer_addr != '':
335            s = self.set_session_param(sid + " old_peer_addr=" + self.old_peer_addr)
336            if not s.startswith("OK"):
337                raise Exception("Cannot set FST session peer address:" + s + " (old)")
338        if self.fst_llt is not None and self.fst_llt != '':
339            s = self.set_session_param(sid + " llt=" + self.fst_llt)
340            if not s.startswith("OK"):
341                raise Exception("Cannot set FST session llt:" + s)
342        self.dump_monitor()
343
344    def send_iface_attach_request(self, ifname, group, llt, priority):
345        request = "FST-ATTACH " + ifname + ' ' + group
346        if llt is not None:
347            request += " llt=" + llt
348        if priority is not None:
349            request += " priority=" + priority
350        res = self.grequest(request)
351        if not res.startswith("OK"):
352            raise Exception("Cannot attach FST iface: " + res)
353
354    def send_iface_detach_request(self, ifname):
355        res = self.grequest("FST-DETACH " + ifname)
356        if not res.startswith("OK"):
357            raise Exception("Cannot detach FST iface: " + res)
358
359    def send_session_setup_request(self, sid):
360        s = self.grequest("FST-MANAGER SESSION_INITIATE " + sid)
361        if not s.startswith('OK'):
362            raise Exception("Cannot send setup request: %s" % s)
363        return s
364
365    def send_session_setup_response(self, sid, response):
366        request = "FST-MANAGER SESSION_RESPOND " + sid + " " + response
367        s = self.grequest(request)
368        if not s.startswith('OK'):
369            raise Exception("Cannot send setup response: %s" % s)
370        return s
371
372    def send_test_session_setup_request(self, fsts_id,
373                                        additional_parameter=None):
374        request = "FST-MANAGER TEST_REQUEST SEND_SETUP_REQUEST " + fsts_id
375        if additional_parameter is not None:
376            request += " " + additional_parameter
377        s = self.grequest(request)
378        if not s.startswith('OK'):
379            raise Exception("Cannot send FST setup request: %s" % s)
380        return s
381
382    def send_test_session_setup_response(self, fsts_id,
383                                         response, additional_parameter=None):
384        request = "FST-MANAGER TEST_REQUEST SEND_SETUP_RESPONSE " + fsts_id + " " + response
385        if additional_parameter is not None:
386            request += " " + additional_parameter
387        s = self.grequest(request)
388        if not s.startswith('OK'):
389            raise Exception("Cannot send FST setup response: %s" % s)
390        return s
391
392    def send_test_ack_request(self, fsts_id):
393        s = self.grequest("FST-MANAGER TEST_REQUEST SEND_ACK_REQUEST " + fsts_id)
394        if not s.startswith('OK'):
395            raise Exception("Cannot send FST ack request: %s" % s)
396        return s
397
398    def send_test_ack_response(self, fsts_id):
399        s = self.grequest("FST-MANAGER TEST_REQUEST SEND_ACK_RESPONSE " + fsts_id)
400        if not s.startswith('OK'):
401            raise Exception("Cannot send FST ack response: %s" % s)
402        return s
403
404    def send_test_tear_down(self, fsts_id):
405        s = self.grequest("FST-MANAGER TEST_REQUEST SEND_TEAR_DOWN " + fsts_id)
406        if not s.startswith('OK'):
407            raise Exception("Cannot send FST tear down: %s" % s)
408        return s
409
410    def get_fsts_id_by_sid(self, sid):
411        s = self.grequest("FST-MANAGER TEST_REQUEST GET_FSTS_ID " + sid)
412        if s == ' ' or s.startswith('FAIL'):
413            raise Exception("Cannot get fsts_id for sid == %s" % sid)
414        return int(s)
415
416    def wait_for_iface_event(self, timeout):
417        while True:
418            ev = self.wait_gevent(["FST-EVENT-IFACE"], timeout)
419            if ev is None:
420                raise Exception("No FST-EVENT-IFACE received")
421            event = parse_fst_iface_event(ev)
422            if event is None:
423                # We can't parse so it's not our event, wait for next one
424                continue
425            return event
426
427    def wait_for_session_event(self, timeout, events_to_ignore=[],
428                               events_to_count=[]):
429        while True:
430            ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout)
431            if ev is None:
432                raise Exception("No FST-EVENT-SESSION received")
433            event = parse_fst_session_event(ev)
434            if event is None:
435                # We can't parse so it's not our event, wait for next one
436                continue
437            if len(events_to_ignore) > 0:
438                if event['type'] in events_to_ignore:
439                    continue
440            elif len(events_to_count) > 0:
441                if event['type'] not in events_to_count:
442                    continue
443            return event
444
445    def initiate_session(self, sid, response="accept"):
446        """Initiates FST session with given session id 'sid'.
447        'response' is the session respond answer: "accept", "reject", or a
448        special "timeout" value to skip the response in order to test session
449        timeouts.
450        Returns: "OK" - session has been initiated, otherwise the reason for the
451        reset: REASON_REJECT, REASON_STT."""
452        strsid = ' ' + sid if sid != '' else ''
453        s = self.grequest("FST-MANAGER SESSION_INITIATE"+ strsid)
454        if not s.startswith('OK'):
455            raise Exception("Cannot initiate fst session: %s" % s)
456        ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
457        if ev is None:
458            raise Exception("No FST-EVENT-SESSION received")
459        # We got FST event
460        event = parse_fst_session_event(ev)
461        if event == None:
462            raise Exception("Unrecognized FST event: " % ev)
463        if event['type'] != 'EVENT_FST_SETUP':
464            raise Exception("Expected FST_SETUP event, got: " + event['type'])
465        ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
466        if ev is None:
467            raise Exception("No FST-EVENT-SESSION received")
468        event = parse_fst_session_event(ev)
469        if event == None:
470            raise Exception("Unrecognized FST event: " % ev)
471        if event['type'] != 'EVENT_FST_SESSION_STATE':
472            raise Exception("Expected EVENT_FST_SESSION_STATE event, got: " + event['type'])
473        if event['new_state'] != "SETUP_COMPLETION":
474            raise Exception("Expected new state SETUP_COMPLETION, got: " + event['new_state'])
475        if response == '':
476            return 'OK'
477        if response != "timeout":
478            s = self.peer_obj.grequest("FST-MANAGER SESSION_RESPOND "+ event['id'] + " " + response)  # Or reject
479            if not s.startswith('OK'):
480                raise Exception("Error session_respond: %s" % s)
481        # Wait for EVENT_FST_SESSION_STATE events. We should get at least 2
482        # events. The 1st event will be EVENT_FST_SESSION_STATE
483        # old_state=INITIAL new_state=SETUP_COMPLETED. The 2nd event will be
484        # either EVENT_FST_ESTABLISHED with the session id or
485        # EVENT_FST_SESSION_STATE with new_state=INITIAL if the session was
486        # reset, the reason field will tell why.
487        result = ''
488        while result == '':
489            ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
490            if ev is None:
491                break # No session event received
492            event = parse_fst_session_event(ev)
493            if event == None:
494                # We can't parse so it's not our event, wait for next one
495                continue
496            if event['type'] == 'EVENT_FST_ESTABLISHED':
497                result = "OK"
498                break
499            elif event['type'] == "EVENT_FST_SESSION_STATE":
500                if event['new_state'] == "INITIAL":
501                    # Session was reset, the only reason to get back to initial
502                    # state.
503                    result = event['reason']
504                    break
505        if result == '':
506            raise Exception("No event for session respond")
507        return result
508
509    def transfer_session(self, sid):
510        """Transfers the session. 'sid' is the session id. 'hsta' is the
511        station-responder object.
512        Returns: REASON_SWITCH - the session has been transferred successfully
513        or a REASON_... reported by the reset event."""
514        request = "FST-MANAGER SESSION_TRANSFER"
515        self.dump_monitor()
516        if sid != '':
517            request += ' ' + sid
518        s = self.grequest(request)
519        if not s.startswith('OK'):
520            raise Exception("Cannot transfer fst session: %s" % s)
521        result = ''
522        while result == '':
523            ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
524            if ev is None:
525                raise Exception("Missing session transfer event")
526            # We got FST event. We expect TRANSITION_CONFIRMED state and then
527            # INITIAL (reset) with the reason (e.g. "REASON_SWITCH").
528            # Right now we'll be waiting for the reset event and record the
529            # reason.
530            event = parse_fst_session_event(ev)
531            if event == None:
532                raise Exception("Unrecognized FST event: " % ev)
533            if event['new_state'] == 'INITIAL':
534                result = event['reason']
535        self.dump_monitor()
536        return result
537
538    def wait_for_tear_down(self):
539        ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
540        if ev is None:
541            raise Exception("No FST-EVENT-SESSION received")
542        # We got FST event
543        event = parse_fst_session_event(ev)
544        if event == None:
545            raise Exception("Unrecognized FST event: " % ev)
546        if event['type'] != 'EVENT_FST_SESSION_STATE':
547            raise Exception("Expected EVENT_FST_SESSION_STATE event, got: " + event['type'])
548        if event['new_state'] != "INITIAL":
549            raise Exception("Expected new state INITIAL, got: " + event['new_state'])
550        if event['reason'] != 'REASON_TEARDOWN':
551            raise Exception("Expected reason REASON_TEARDOWN, got: " + event['reason'])
552
553    def teardown_session(self, sid):
554        """Tears down FST session with a given session id ('sid')"""
555        strsid = ' ' + sid if sid != '' else ''
556        s = self.grequest("FST-MANAGER SESSION_TEARDOWN" + strsid)
557        if not s.startswith('OK'):
558            raise Exception("Cannot tear down fst session: %s" % s)
559        self.peer_obj.wait_for_tear_down()
560
561
562    def remove_session(self, sid, wait_for_tear_down=True):
563        """Removes FST session with a given session id ('sid')"""
564        strsid = ' ' + sid if sid != '' else ''
565        s = self.grequest("FST-MANAGER SESSION_REMOVE" + strsid)
566        if not s.startswith('OK'):
567            raise Exception("Cannot remove fst session: %s" % s)
568        if wait_for_tear_down == True:
569            self.peer_obj.wait_for_tear_down()
570
571    def remove_all_sessions(self):
572        """Removes FST session with a given session id ('sid')"""
573        grp = ' ' + self.fst_group if self.fst_group != '' else ''
574        s = self.grequest("FST-MANAGER LIST_SESSIONS" + grp)
575        if not s.startswith('FAIL'):
576            for sid in s.splitlines():
577                sid = sid.strip()
578                if len(sid) != 0:
579                    self.remove_session(sid, wait_for_tear_down=False)
580
581
582#
583# FstAP class
584#
585class FstAP(FstDevice):
586    def __init__(self, iface, ssid, mode, chan, fst_group, fst_pri,
587                 fst_llt=None, rsn=False):
588        """If fst_group is empty, then FST parameters will not be set
589        If fst_llt is empty, the parameter will not be set and the default value
590        is expected to be configured."""
591        self.ssid = ssid
592        self.mode = mode
593        self.chan = chan
594        self.reg_ctrl = fst_test_common.HapdRegCtrl()
595        self.reg_ctrl.add_ap(iface, self.chan)
596        self.global_instance = hostapd.HostapdGlobal()
597        FstDevice.__init__(self, iface, fst_group, fst_pri, fst_llt, rsn)
598
599    def start(self, return_early=False):
600        """Starts AP the "standard" way as it was intended by hostapd tests.
601        This will work only when FST supports fully dynamically loading
602        parameters in hostapd."""
603        params = {}
604        params['ssid'] = self.ssid
605        params['hw_mode'] = self.mode
606        params['channel'] = self.chan
607        params['country_code'] = 'US'
608        if self.rsn:
609            params['wpa'] = '2'
610            params['wpa_key_mgmt'] = 'WPA-PSK'
611            params['rsn_pairwise'] = 'CCMP'
612            params['wpa_passphrase'] = '12345678'
613        self.hapd = hostapd.add_ap(self.iface, params)
614        if not self.hapd.ping():
615            raise Exception("Could not ping FST hostapd")
616        self.reg_ctrl.start()
617        self.get_global_instance()
618        if return_early:
619            return self.hapd
620        if len(self.fst_group) != 0:
621            self.send_iface_attach_request(self.iface, self.fst_group,
622                                           self.fst_llt, self.fst_pri)
623        return self.hapd
624
625    def stop(self):
626        """Removes the AP, To be used when dynamic fst APs are implemented in
627        hostapd."""
628        if len(self.fst_group) != 0:
629            self.remove_all_sessions()
630            try:
631                self.send_iface_detach_request(self.iface)
632            except Exception as e:
633                logger.info(str(e))
634        self.reg_ctrl.stop()
635        del self.global_instance
636        self.global_instance = None
637
638    def get_instance(self):
639        """Return the Hostapd/WpaSupplicant instance"""
640        if self.instance is None:
641            self.instance = hostapd.Hostapd(self.iface)
642        return self.instance
643
644    def get_global_instance(self):
645        return self.global_instance
646
647    def get_own_mac_address(self):
648        """Gets the device's own MAC address"""
649        h = self.get_instance()
650        status = h.get_status()
651        return status['bssid[0]']
652
653    def get_actual_peer_addr(self):
654        """Gets the peer address. A connected station address is returned."""
655        # Use the device instance, the global control interface doesn't have
656        # station address
657        h = self.get_instance()
658        sta = h.get_sta(None)
659        if sta is None or 'addr' not in sta:
660            # Maybe station is not connected?
661            addr = None
662        else:
663            addr = sta['addr']
664        return addr
665
666    def grequest(self, req):
667        """Send request on the global control interface"""
668        logger.debug("FstAP::grequest: " + req)
669        h = self.get_global_instance()
670        return h.request(req)
671
672    def wait_gevent(self, events, timeout=None):
673        """Wait for a list of events on the global interface"""
674        h = self.get_global_instance()
675        if timeout is not None:
676            return h.wait_event(events, timeout=timeout)
677        else:
678            return h.wait_event(events)
679
680    def get_ssid(self):
681        return self.ssid
682
683    def dump_monitor(self):
684        """Dump control interface monitor events"""
685        if self.instance:
686            self.instance.dump_monitor()
687
688#
689# FstSTA class
690#
691class FstSTA(FstDevice):
692    def __init__(self, iface, fst_group, fst_pri, fst_llt=None, rsn=False):
693        """If fst_group is empty, then FST parameters will not be set
694        If fst_llt is empty, the parameter will not be set and the default value
695        is expected to be configured."""
696        FstDevice.__init__(self, iface, fst_group, fst_pri, fst_llt, rsn)
697        self.connected = None # FstAP object the station is connected to
698
699    def start(self):
700        """Current implementation involves running another instance of
701        wpa_supplicant with fixed FST STAs configurations. When any type of
702        dynamic STA loading is implemented, rewrite the function similarly to
703        FstAP."""
704        h = self.get_instance()
705        h.interface_add(self.iface, drv_params="force_connect_cmd=1")
706        if not h.global_ping():
707            raise Exception("Could not ping FST wpa_supplicant")
708        if len(self.fst_group) != 0:
709            self.send_iface_attach_request(self.iface, self.fst_group,
710                                           self.fst_llt, self.fst_pri)
711        return None
712
713    def stop(self):
714        """Removes the STA. In a static (temporary) implementation does nothing,
715        the STA will be removed when the fst wpa_supplicant process is killed by
716        fstap.cleanup()."""
717        h = self.get_instance()
718        h.dump_monitor()
719        if len(self.fst_group) != 0:
720            self.remove_all_sessions()
721            self.send_iface_detach_request(self.iface)
722            h.dump_monitor()
723        h.interface_remove(self.iface)
724        h.close_ctrl()
725        del h
726        self.instance = None
727
728    def get_instance(self):
729        """Return the Hostapd/WpaSupplicant instance"""
730        if self.instance is None:
731             self.instance = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
732        return self.instance
733
734    def get_own_mac_address(self):
735        """Gets the device's own MAC address"""
736        h = self.get_instance()
737        status = h.get_status()
738        return status['address']
739
740    def get_actual_peer_addr(self):
741        """Gets the peer address. A connected station address is returned"""
742        h = self.get_instance()
743        status = h.get_status()
744        return status['bssid']
745
746    def grequest(self, req):
747        """Send request on the global control interface"""
748        logger.debug("FstSTA::grequest: " + req)
749        h = self.get_instance()
750        return h.global_request(req)
751
752    def wait_gevent(self, events, timeout=None):
753        """Wait for a list of events on the global interface"""
754        h = self.get_instance()
755        if timeout is not None:
756            return h.wait_global_event(events, timeout=timeout)
757        else:
758            return h.wait_global_event(events)
759
760    def scan(self, freq=None, no_wait=False, only_new=False):
761        """Issue Scan with given parameters. Returns the BSS dictionary for the
762        AP found (the 1st BSS found. TODO: What if the AP required is not the
763        1st in list?) or None if no BSS found. None call be also a result of
764        no_wait=True. Note, request("SCAN_RESULTS") can be used to get all the
765        results at once."""
766        h = self.get_instance()
767        h.dump_monitor()
768        h.scan(None, freq, no_wait, only_new)
769        r = h.get_bss('0')
770        h.dump_monitor()
771        return r
772
773    def connect(self, ap, **kwargs):
774        """Connects to the given AP"""
775        if not isinstance(ap, FstAP):
776            raise Exception("Bad AP object to connect to")
777        h = self.get_instance()
778        hap = ap.get_instance()
779        h.dump_monitor()
780        h.connect(ap.get_ssid(), **kwargs)
781        h.dump_monitor()
782        self.connected = ap
783
784    def connect_to_external_ap(self, ap, ssid, check_connection=True, **kwargs):
785        """Connects to the given external AP"""
786        if not isinstance(ap, hostapd.Hostapd):
787            raise Exception("Bad AP object to connect to")
788        h = self.get_instance()
789        h.dump_monitor()
790        h.connect(ssid, **kwargs)
791        self.connected = ap
792        if check_connection:
793            ev = ap.wait_event(["AP-STA-CONNECTED"], timeout=10)
794            if ev is None:
795                self.connected = None
796                raise Exception("No connection event received from %s" % ssid)
797            h.dump_monitor()
798
799    def disconnect(self, check_disconnect=True):
800        """Disconnects from the AP the station is currently connected to"""
801        if self.connected is not None:
802            h = self.get_instance()
803            h.dump_monitor()
804            h.request("DISCONNECT")
805            if check_disconnect:
806                hap = self.connected.get_instance()
807                ev = hap.wait_event(["AP-STA-DISCONNECTED"], timeout=10)
808                if ev is None:
809                    raise Exception("No disconnection event received from %s" % self.connected.get_ssid())
810                h.dump_monitor()
811            self.connected = None
812
813
814    def disconnect_from_external_ap(self, check_disconnect=True):
815        """Disconnects from the external AP the station is currently connected
816        to"""
817        if self.connected is not None:
818            h = self.get_instance()
819            h.dump_monitor()
820            h.request("DISCONNECT")
821            if check_disconnect:
822                hap = self.connected
823                ev = hap.wait_event(["AP-STA-DISCONNECTED"], timeout=10)
824                if ev is None:
825                    raise Exception("No disconnection event received from AP")
826                h.dump_monitor()
827            self.connected = None
828
829    def dump_monitor(self):
830        """Dump control interface monitor events"""
831        if self.instance:
832            self.instance.dump_monitor()
833