1#!/usr/bin/env python3
2#
3#   Copyright 2020 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17from mobly import signals
18import multiprocessing as mp
19import time
20
21from acts import utils
22from acts import asserts
23from acts.base_test import BaseTestClass
24from acts.controllers import iperf_server
25from acts.controllers import iperf_client
26from acts.controllers.ap_lib import hostapd_constants
27from acts.controllers.ap_lib import hostapd_security
28from acts.test_utils.abstract_devices.utils_lib import wlan_utils
29from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
30from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate
31from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
32
33ANDROID_DEFAULT_WLAN_PORT = 'wlan0'
34CONNECTIVITY_MODE_LOCAL = 'local_only'
35CONNECTIVITY_MODE_UNRESTRICTED = 'unrestricted'
36DEFAULT_IPERF_PORT = 5201
37INTERFACE_ROLE_AP = 'Ap'
38INTERFACE_ROLE_CLIENT = 'Client'
39INTERFACE_ROLES = {INTERFACE_ROLE_AP, INTERFACE_ROLE_CLIENT}
40OPERATING_BAND_2G = 'only_2_4_ghz'
41OPERATING_BAND_5G = 'only_5_ghz'
42OPERATING_BAND_ANY = 'any'
43SECURITY_OPEN = 'none'
44SECURITY_WEP = 'wep'
45SECURITY_WPA = 'wpa'
46SECURITY_WPA2 = 'wpa2'
47SECURITY_WPA3 = 'wpa3'
48TEST_TYPE_ASSOCIATE_ONLY = 'associate_only'
49TEST_TYPE_ASSOCIATE_AND_PING = 'associate_and_ping'
50TEST_TYPE_ASSOCIATE_AND_PASS_TRAFFIC = 'associate_and_pass_traffic'
51TEST_TYPES = {
52    TEST_TYPE_ASSOCIATE_ONLY, TEST_TYPE_ASSOCIATE_AND_PING,
53    TEST_TYPE_ASSOCIATE_AND_PASS_TRAFFIC
54}
55
56
57def generate_test_name(settings):
58    """Generates a string test name based on the channel and band.
59
60    Args:
61        settings A dict with the soft ap config parameteres.
62
63    Returns:
64        A string test case name.
65    """
66    return 'test_soft_ap_band_%s_security_%s_mode_%s_loops_%s' % (
67        settings['operating_band'], settings['security_type'],
68        settings['connectivity_mode'], settings['reconnect_loops'])
69
70
71class SoftApClient(object):
72    def __init__(self, device):
73        self.w_device = create_wlan_device(device)
74        self.ip_client = iperf_client.IPerfClientOverAdb(device.serial)
75
76
77class WlanInterface(object):
78    def __init__(self):
79        self.name = None
80        self.mac_addr = None
81        self.ipv4 = None
82
83
84class SoftApTest(BaseTestClass):
85    """Tests for Fuchsia SoftAP
86
87    Testbed requirement:
88    * One Fuchsia Device
89    * One Client (Android) Device
90    """
91    def setup_class(self):
92        self.dut = create_wlan_device(self.fuchsia_devices[0])
93        self.dut.device.netstack_lib.init()
94
95        # TODO(fxb/51313): Add in device agnosticity for clients
96        self.clients = []
97        for device in self.android_devices:
98            self.clients.append(SoftApClient(device))
99        self.primary_client = self.clients[0]
100
101        self.iperf_server_config = {
102            'user': self.dut.device.ssh_username,
103            'host': self.dut.device.ip,
104            'ssh_config': self.dut.device.ssh_config
105        }
106        self.iperf_server = iperf_server.IPerfServerOverSsh(
107            self.iperf_server_config, DEFAULT_IPERF_PORT, use_killall=True)
108        self.iperf_server.start()
109
110        try:
111            self.access_point = self.access_points[0]
112        except AttributeError:
113            self.access_point = None
114
115    def teardown_class(self):
116        # Because this is using killall, it will stop all iperf processes,
117        # making it a great teardown cleanup
118        self.iperf_server.stop()
119
120    def setup_test(self):
121        for ad in self.android_devices:
122            ad.droid.wakeLockAcquireBright()
123            ad.droid.wakeUpNow()
124        for client in self.clients:
125            client.w_device.disconnect()
126            client.w_device.reset_wifi()
127            client.w_device.wifi_toggle_state(True)
128        self.dut.device.wlan_ap_policy_lib.wlanStopAllAccessPoint()
129        if self.access_point:
130            self.access_point.stop_all_aps()
131        self.dut.disconnect()
132
133    def teardown_test(self):
134        for client in self.clients:
135            client.w_device.disconnect()
136        for ad in self.android_devices:
137            ad.droid.wakeLockRelease()
138            ad.droid.goToSleepNow()
139        self.dut.device.wlan_ap_policy_lib.wlanStopAllAccessPoint()
140        if self.access_point:
141            self.access_point.stop_all_aps()
142        self.dut.disconnect()
143
144    def on_fail(self, test_name, begin_time):
145        self.dut.take_bug_report(test_name, begin_time)
146        self.dut.get_log(test_name, begin_time)
147
148    def start_soft_ap(self, settings):
149        """Starts a softAP on Fuchsia device.
150
151        Args:
152            settings: a dict containing softAP configuration params
153                ssid: string, SSID of softAP network
154                security_type: string, security type of softAP network
155                    - 'none', 'wep', 'wpa', 'wpa2', 'wpa3'
156                password: string, password if applicable
157                connectivity_mode: string, connecitivity_mode for softAP
158                    - 'local_only', 'unrestricted'
159                operating_band: string, band for softAP network
160                    - 'any', 'only_5_ghz', 'only_2_4_ghz'
161        """
162        ssid = settings['ssid']
163        security_type = settings['security_type']
164        password = settings.get('password', '')
165        connectivity_mode = settings['connectivity_mode']
166        operating_band = settings['operating_band']
167
168        self.log.info('Attempting to start SoftAP on DUT with settings: %s' %
169                      settings)
170
171        response = self.dut.device.wlan_ap_policy_lib.wlanStartAccessPoint(
172            ssid, security_type, password, connectivity_mode, operating_band)
173        if response.get('error'):
174            raise EnvironmentError('Failed to setup SoftAP. Err: %s' %
175                                   response['error'])
176
177        self.log.info('SoftAp network (%s) is up.' % ssid)
178
179    def associate_with_soft_ap(self, w_device, settings):
180        """Associates client device with softAP on Fuchsia device.
181
182        Args:
183            w_device: wlan_device to associate with the softAP
184            settings: a dict containing softAP config params (see start_soft_ap)
185                for details
186
187        Raises:
188            TestFailure, if association fails
189        """
190        self.log.info(
191            'Attempting to associate client %s with SoftAP on FuchsiaDevice '
192            '(%s).' % (w_device.device.serial, self.dut.device.ip))
193
194        check_connectivity = settings[
195            'connectivity_mode'] == CONNECTIVITY_MODE_UNRESTRICTED
196        associated = wlan_utils.associate(
197            w_device,
198            settings['ssid'],
199            password=settings.get('password'),
200            check_connectivity=check_connectivity)
201
202        if not associated:
203            asserts.fail('Failed to connect to SoftAP.')
204
205        self.log.info('Client successfully associated with SoftAP.')
206
207    def disconnect_from_soft_ap(self, w_device):
208        """Disconnects client device from SoftAP.
209
210        Args:
211            w_device: wlan_device to disconnect from SoftAP
212        """
213        self.log.info('Disconnecting device %s from SoftAP.' %
214                      w_device.device.serial)
215        w_device.disconnect()
216
217    def get_dut_interface_by_role(self, role):
218        """Retrieves interface information from the FuchsiaDevice DUT based
219        on the role.
220
221        Args:
222            role: string, the role of the interface to seek (e.g. Client or Ap)
223
224        Raises:
225            ConnectionError, if SL4F calls fail
226            AttributeError, if device does not have an interface matching role
227
228        Returns:
229            WlanInterface object representing the interface matching role
230        """
231        if not role in INTERFACE_ROLES:
232            raise ValueError('Unsupported interface role %s' % role)
233
234        self.log.info('Getting %s interface info from DUT.' % role)
235        interface = WlanInterface()
236
237        # Determine WLAN interface with role
238        wlan_ifaces = self.dut.device.wlan_lib.wlanGetIfaceIdList()
239        if wlan_ifaces.get('error'):
240            raise ConnectionError('Failed to get wlan interface IDs: %s' %
241                                  wlan_ifaces['error'])
242
243        for wlan_iface in wlan_ifaces['result']:
244            iface_info = self.dut.device.wlan_lib.wlanQueryInterface(
245                wlan_iface)
246            if iface_info.get('error'):
247                raise ConnectionError('Failed to query wlan iface: %s' %
248                                      iface_info['error'])
249
250            if iface_info['result']['role'] == role:
251                interface.mac_addr = iface_info['result']['mac_addr']
252                break
253        else:
254            raise AttributeError('Failed to find a %s interface.' % role)
255
256        # Retrieve interface info from netstack
257        netstack_ifaces = self.dut.device.netstack_lib.netstackListInterfaces()
258        if netstack_ifaces.get('error'):
259            raise ConnectionError('Failed to get netstack ifaces: %s' %
260                                  netstack_ifaces['error'])
261
262        # TODO(fxb/51315): Once subnet information is available in
263        # netstackListInterfaces store it to verify the clients ip address.
264        for netstack_iface in netstack_ifaces['result']:
265            if netstack_iface['mac'] == interface.mac_addr:
266                interface.name = netstack_iface['name']
267                if len(netstack_iface['ipv4_addresses']) > 0:
268                    interface.ipv4 = '.'.join(
269                        str(byte)
270                        for byte in netstack_iface['ipv4_addresses'][0])
271                else:
272                    interface.ipv4 = self.wait_for_ipv4_address(
273                        self.dut, interface.name)
274        self.log.info('DUT %s interface: %s. Has ipv4 address %s' %
275                      (role, interface.name, interface.ipv4))
276        return interface
277
278    def wait_for_ipv4_address(self, w_device, interface_name, timeout=10):
279        # TODO(fxb/51315): Once subnet information is available in netstack, add a
280        # subnet verification here.
281        """ Waits for interface on a wlan_device to get an ipv4 address.
282
283        Args:
284            w_device: wlan_device to check interface
285            interface_name: name of the interface to check
286            timeout: seconds to wait before raising an error
287
288        Raises:
289            ValueError, if interface does not have an ipv4 address after timeout
290        """
291        self.log.info(
292            'Checking if device %s interface %s has an ipv4 address. '
293            'Will retrying for %s seconds.' %
294            (w_device.device.serial, interface_name, timeout))
295
296        end_time = time.time() + timeout
297        while time.time() < end_time:
298            ips = w_device.get_interface_ip_addresses(interface_name)
299            if len(ips['ipv4_private']) > 0:
300                self.log.info('Device %s interface %s has ipv4 address %s' %
301                              (w_device.device.serial, interface_name,
302                               ips['ipv4_private'][0]))
303                return ips['ipv4_private'][0]
304            else:
305                time.sleep(1)
306        raise ValueError(
307            'After %s seconds, device %s still doesn not have an ipv4 address '
308            'on interface %s.' %
309            (timeout, w_device.device.serial, interface_name))
310
311    def verify_ping(self, w_device, dest_ip):
312        """ Verify wlan_device can ping a destination ip.
313
314        Args:
315            w_device: wlan_device to initiate ping
316            dest_ip: ip to ping from wlan_device
317
318        Raises:
319            TestFailure, if ping fails
320        """
321        self.log.info('Attempting to ping from device %s to dest ip %s' %
322                      (w_device.device.serial, dest_ip))
323        if not w_device.ping(dest_ip):
324            asserts.fail('Device %s could not ping dest ip %s' %
325                         (w_device.device.serial, dest_ip))
326        self.log.info('Ping successful.')
327
328    def run_iperf_traffic(self, ip_client, server_address, server_port=5201):
329        """Runs traffic between client and ap an verifies throughput.
330
331        Args:
332            ip_client: iperf client to use
333            server_address: ipv4 address of the iperf server to use
334            server_port: port of the iperf server
335
336        Raises:
337            TestFailure, if no traffic passes in either direction
338        """
339        ip_client_identifier = self.get_iperf_client_identifier(ip_client)
340        self.log.info(
341            'Running traffic from iperf client %s to iperf server %s.' %
342            (ip_client_identifier, server_address))
343        client_to_ap_path = ip_client.start(
344            server_address, '-i 1 -t 10 -J -p %s' % server_port,
345            'client_to_soft_ap')
346
347        self.log.info(
348            'Running traffic from iperf server %s to iperf client %s.' %
349            (server_address, ip_client_identifier))
350        ap_to_client_path = ip_client.start(
351            server_address, '-i 1 -t 10 -R -J -p %s' % server_port,
352            'soft_ap_to_client')
353        self.log.info('Getting iperf results')
354
355        client_to_ap_result = iperf_server.IPerfResult(client_to_ap_path)
356        ap_to_client_result = iperf_server.IPerfResult(ap_to_client_path)
357
358        if (not client_to_ap_result.avg_receive_rate):
359            asserts.fail(
360                'Failed to pass traffic from iperf client %s to iperf server %s.'
361                % (ip_client_identifier, server_address))
362
363        self.log.info(
364            'Passed traffic from iperf client %s to iperf server %s with avg '
365            'rate of %s MB/s.' % (ip_client_identifier, server_address,
366                                  client_to_ap_result.avg_receive_rate))
367
368        if (not ap_to_client_result.avg_receive_rate):
369            asserts.fail(
370                'Failed to pass traffic from iperf server %s to iperf client %s.'
371                % (server_address, ip_client_identifier))
372
373        self.log.info(
374            'Passed traffic from iperf server %s to iperf client %s with avg '
375            'rate of %s MB/s.' % (server_address, ip_client_identifier,
376                                  ap_to_client_result.avg_receive_rate))
377
378    def run_iperf_traffic_parallel_process(self,
379                                           ip_client,
380                                           server_address,
381                                           error_queue,
382                                           server_port=5201):
383        """ Executes run_iperf_traffic using a queue to capture errors. Used
384        when running iperf in a parallel process.
385
386        Args:
387            ip_client: iperf client to use
388            server_address: ipv4 address of the iperf server to use
389            error_queue: multiprocessing queue to capture errors
390            server_port: port of the iperf server
391        """
392        try:
393            self.run_iperf_traffic(ip_client,
394                                   server_address,
395                                   server_port=server_port)
396        except Exception as err:
397            error_queue.put('In iperf process from %s to %s: %s' %
398                            (self.get_iperf_client_identifier(ip_client),
399                             server_address, err))
400
401    def get_iperf_client_identifier(self, ip_client):
402        """ Retrieves an indentifer string from iperf client, for logging.
403
404        Args:
405            ip_client: iperf client to grab identifier from
406        """
407        if type(ip_client) == iperf_client.IPerfClientOverAdb:
408            return ip_client._android_device_or_serial
409        return ip_client._ssh_settings.hostname
410
411    def run_config_stress_test(self, settings):
412        """Runs test based on config parameters.
413
414        Args:
415            settings: test configuration settings, see
416                test_soft_ap_stress_from_config for details
417        """
418        client = settings['client']
419        test_type = settings['test_type']
420        if not test_type in TEST_TYPES:
421            raise ValueError('Unrecognized test type %s' % test_type)
422        reconnect_loops = settings['reconnect_loops']
423        self.log.info('Running test type %s in loop %s times' %
424                      (test_type, reconnect_loops))
425
426        self.start_soft_ap(settings)
427
428        passed_count = 0
429        for run in range(reconnect_loops):
430            try:
431                # Associate with SoftAp
432                self.log.info('Starting SoftApTest run %s' % str(run + 1))
433                self.associate_with_soft_ap(client.w_device, settings)
434
435                if test_type != TEST_TYPE_ASSOCIATE_ONLY:
436                    # Verify client and SoftAP can ping
437                    dut_ap_interface = self.get_dut_interface_by_role(
438                        INTERFACE_ROLE_AP)
439                    client_ipv4 = self.wait_for_ipv4_address(
440                        client.w_device, ANDROID_DEFAULT_WLAN_PORT)
441                    self.verify_ping(client.w_device, dut_ap_interface.ipv4)
442                    self.verify_ping(self.dut, client_ipv4)
443
444                    if test_type != TEST_TYPE_ASSOCIATE_AND_PING:
445                        # Run traffic between client and SoftAp
446                        self.run_iperf_traffic(client.ip_client,
447                                               dut_ap_interface.ipv4)
448                # Disconnect
449                self.disconnect_from_soft_ap(client.w_device)
450
451            except signals.TestFailure as err:
452                self.log.error('SoftApTest run %s failed. Err: %s' %
453                               (str(run + 1), err.details))
454            else:
455                self.log.info('SoftApTest run %s successful.' % run)
456                passed_count += 1
457
458        if passed_count < reconnect_loops:
459            asserts.fail('SoftAp reconnect test passed on %s/%s runs.' %
460                         (passed_count, reconnect_loops))
461
462        asserts.explicit_pass('SoftAp reconnect test passed on %s/%s runs.' %
463                              (passed_count, reconnect_loops))
464
465    # Test helper functions
466    def verify_soft_ap_associate_only(self, client, settings):
467        self.start_soft_ap(settings)
468        self.associate_with_soft_ap(client.w_device, settings)
469
470    def verify_soft_ap_associate_and_ping(self, client, settings):
471        self.start_soft_ap(settings)
472        self.associate_with_soft_ap(client.w_device, settings)
473        dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP)
474        client_ipv4 = self.wait_for_ipv4_address(self.primary_client.w_device,
475                                                 ANDROID_DEFAULT_WLAN_PORT)
476        self.verify_ping(client.w_device, dut_ap_interface.ipv4)
477        self.verify_ping(self.dut, client_ipv4)
478
479    def verify_soft_ap_associate_and_pass_traffic(self, client, settings):
480        self.start_soft_ap(settings)
481        self.associate_with_soft_ap(client.w_device, settings)
482        dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP)
483        client_ipv4 = self.wait_for_ipv4_address(self.primary_client.w_device,
484                                                 ANDROID_DEFAULT_WLAN_PORT)
485        self.verify_ping(client.w_device, dut_ap_interface.ipv4)
486        self.verify_ping(self.dut, client_ipv4)
487        self.run_iperf_traffic(client.ip_client, dut_ap_interface.ipv4)
488
489
490# Test Cases
491
492    def test_soft_ap_2g_open_local(self):
493        self.verify_soft_ap_associate_and_pass_traffic(
494            self.primary_client, {
495                'ssid': utils.rand_ascii_str(
496                    hostapd_constants.AP_SSID_LENGTH_2G),
497                'security_type': SECURITY_OPEN,
498                'connectivity_mode': CONNECTIVITY_MODE_LOCAL,
499                'operating_band': OPERATING_BAND_2G
500            })
501
502    def test_soft_ap_5g_open_local(self):
503        self.verify_soft_ap_associate_and_pass_traffic(
504            self.primary_client, {
505                'ssid': utils.rand_ascii_str(
506                    hostapd_constants.AP_SSID_LENGTH_5G),
507                'security_type': SECURITY_OPEN,
508                'connectivity_mode': CONNECTIVITY_MODE_LOCAL,
509                'operating_band': OPERATING_BAND_5G
510            })
511
512    def test_soft_ap_any_open_local(self):
513        self.verify_soft_ap_associate_and_pass_traffic(
514            self.primary_client, {
515                'ssid': utils.rand_ascii_str(
516                    hostapd_constants.AP_SSID_LENGTH_5G),
517                'security_type': SECURITY_OPEN,
518                'connectivity_mode': CONNECTIVITY_MODE_LOCAL,
519                'operating_band': OPERATING_BAND_ANY
520            })
521
522    def test_soft_ap_2g_wep_local(self):
523        self.verify_soft_ap_associate_and_pass_traffic(
524            self.primary_client, {
525                'ssid':
526                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
527                'security_type':
528                SECURITY_WEP,
529                'password':
530                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
531                'connectivity_mode':
532                CONNECTIVITY_MODE_LOCAL,
533                'operating_band':
534                OPERATING_BAND_2G
535            })
536
537    def test_soft_ap_5g_wep_local(self):
538        self.verify_soft_ap_associate_and_pass_traffic(
539            self.primary_client, {
540                'ssid':
541                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
542                'security_type':
543                SECURITY_WEP,
544                'password':
545                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
546                'connectivity_mode':
547                CONNECTIVITY_MODE_LOCAL,
548                'operating_band':
549                OPERATING_BAND_5G
550            })
551
552    def test_soft_ap_any_wep_local(self):
553        self.verify_soft_ap_associate_and_pass_traffic(
554            self.primary_client, {
555                'ssid':
556                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
557                'security_type':
558                SECURITY_WEP,
559                'password':
560                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
561                'connectivity_mode':
562                CONNECTIVITY_MODE_LOCAL,
563                'operating_band':
564                OPERATING_BAND_ANY
565            })
566
567    def test_soft_ap_2g_wpa_local(self):
568        self.verify_soft_ap_associate_and_pass_traffic(
569            self.primary_client, {
570                'ssid':
571                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
572                'security_type':
573                SECURITY_WPA,
574                'password':
575                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
576                'connectivity_mode':
577                CONNECTIVITY_MODE_LOCAL,
578                'operating_band':
579                OPERATING_BAND_2G
580            })
581
582    def test_soft_ap_5g_wpa_local(self):
583        self.verify_soft_ap_associate_and_pass_traffic(
584            self.primary_client, {
585                'ssid':
586                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
587                'security_type':
588                SECURITY_WPA,
589                'password':
590                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
591                'connectivity_mode':
592                CONNECTIVITY_MODE_LOCAL,
593                'operating_band':
594                OPERATING_BAND_5G
595            })
596
597    def test_soft_ap_any_wpa_local(self):
598        self.verify_soft_ap_associate_and_pass_traffic(
599            self.primary_client, {
600                'ssid':
601                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
602                'security_type':
603                SECURITY_WPA,
604                'password':
605                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
606                'connectivity_mode':
607                CONNECTIVITY_MODE_LOCAL,
608                'operating_band':
609                OPERATING_BAND_ANY
610            })
611
612    def test_soft_ap_2g_wpa2_local(self):
613        self.verify_soft_ap_associate_and_pass_traffic(
614            self.primary_client, {
615                'ssid':
616                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
617                'security_type':
618                SECURITY_WPA2,
619                'password':
620                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
621                'connectivity_mode':
622                CONNECTIVITY_MODE_LOCAL,
623                'operating_band':
624                OPERATING_BAND_2G
625            })
626
627    def test_soft_ap_5g_wpa2_local(self):
628        self.verify_soft_ap_associate_and_pass_traffic(
629            self.primary_client, {
630                'ssid':
631                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
632                'security_type':
633                SECURITY_WPA2,
634                'password':
635                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
636                'connectivity_mode':
637                CONNECTIVITY_MODE_LOCAL,
638                'operating_band':
639                OPERATING_BAND_5G
640            })
641
642    def test_soft_ap_any_wpa2_local(self):
643        self.verify_soft_ap_associate_and_pass_traffic(
644            self.primary_client, {
645                'ssid':
646                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
647                'security_type':
648                SECURITY_WPA2,
649                'password':
650                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
651                'connectivity_mode':
652                CONNECTIVITY_MODE_LOCAL,
653                'operating_band':
654                OPERATING_BAND_ANY
655            })
656
657    def test_soft_ap_2g_wpa3_local(self):
658        self.verify_soft_ap_associate_and_pass_traffic(
659            self.primary_client, {
660                'ssid':
661                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
662                'security_type':
663                SECURITY_WPA3,
664                'password':
665                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
666                'connectivity_mode':
667                CONNECTIVITY_MODE_LOCAL,
668                'operating_band':
669                OPERATING_BAND_2G
670            })
671
672    def test_soft_ap_5g_wpa3_local(self):
673        self.verify_soft_ap_associate_and_pass_traffic(
674            self.primary_client, {
675                'ssid':
676                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
677                'security_type':
678                SECURITY_WPA3,
679                'password':
680                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
681                'connectivity_mode':
682                CONNECTIVITY_MODE_LOCAL,
683                'operating_band':
684                OPERATING_BAND_ANY
685            })
686
687    def test_soft_ap_any_wpa3_local(self):
688        self.verify_soft_ap_associate_and_pass_traffic(
689            self.primary_client, {
690                'ssid':
691                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
692                'security_type':
693                SECURITY_WPA3,
694                'password':
695                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
696                'connectivity_mode':
697                CONNECTIVITY_MODE_LOCAL,
698                'operating_band':
699                OPERATING_BAND_ANY
700            })
701
702    def test_soft_ap_2g_open_unrestricted(self):
703        self.verify_soft_ap_associate_and_pass_traffic(
704            self.primary_client, {
705                'ssid': utils.rand_ascii_str(
706                    hostapd_constants.AP_SSID_LENGTH_2G),
707                'security_type': SECURITY_OPEN,
708                'connectivity_mode': CONNECTIVITY_MODE_UNRESTRICTED,
709                'operating_band': OPERATING_BAND_2G
710            })
711
712    def test_soft_ap_5g_open_unrestricted(self):
713        self.verify_soft_ap_associate_and_pass_traffic(
714            self.primary_client, {
715                'ssid': utils.rand_ascii_str(
716                    hostapd_constants.AP_SSID_LENGTH_5G),
717                'security_type': SECURITY_OPEN,
718                'connectivity_mode': CONNECTIVITY_MODE_UNRESTRICTED,
719                'operating_band': OPERATING_BAND_5G
720            })
721
722    def test_soft_ap_any_open_unrestricted(self):
723        self.verify_soft_ap_associate_and_pass_traffic(
724            self.primary_client, {
725                'ssid': utils.rand_ascii_str(
726                    hostapd_constants.AP_SSID_LENGTH_5G),
727                'security_type': SECURITY_OPEN,
728                'connectivity_mode': CONNECTIVITY_MODE_UNRESTRICTED,
729                'operating_band': OPERATING_BAND_ANY
730            })
731
732    def test_soft_ap_2g_wep_unrestricted(self):
733        self.verify_soft_ap_associate_and_pass_traffic(
734            self.primary_client, {
735                'ssid':
736                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
737                'security_type':
738                SECURITY_WEP,
739                'password':
740                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
741                'connectivity_mode':
742                CONNECTIVITY_MODE_UNRESTRICTED,
743                'operating_band':
744                OPERATING_BAND_2G
745            })
746
747    def test_soft_ap_5g_wep_unrestricted(self):
748        self.verify_soft_ap_associate_and_pass_traffic(
749            self.primary_client, {
750                'ssid':
751                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
752                'security_type':
753                SECURITY_WEP,
754                'password':
755                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
756                'connectivity_mode':
757                CONNECTIVITY_MODE_UNRESTRICTED,
758                'operating_band':
759                OPERATING_BAND_5G
760            })
761
762    def test_soft_ap_any_wep_unrestricted(self):
763        self.verify_soft_ap_associate_and_pass_traffic(
764            self.primary_client, {
765                'ssid':
766                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
767                'security_type':
768                SECURITY_WEP,
769                'password':
770                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
771                'connectivity_mode':
772                CONNECTIVITY_MODE_UNRESTRICTED,
773                'operating_band':
774                OPERATING_BAND_ANY
775            })
776
777    def test_soft_ap_2g_wpa_unrestricted(self):
778        self.verify_soft_ap_associate_and_pass_traffic(
779            self.primary_client, {
780                'ssid':
781                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
782                'security_type':
783                SECURITY_WPA,
784                'password':
785                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
786                'connectivity_mode':
787                CONNECTIVITY_MODE_UNRESTRICTED,
788                'operating_band':
789                OPERATING_BAND_2G
790            })
791
792    def test_soft_ap_5g_wpa_unrestricted(self):
793        self.verify_soft_ap_associate_and_pass_traffic(
794            self.primary_client, {
795                'ssid':
796                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
797                'security_type':
798                SECURITY_WPA,
799                'password':
800                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
801                'connectivity_mode':
802                CONNECTIVITY_MODE_UNRESTRICTED,
803                'operating_band':
804                OPERATING_BAND_5G
805            })
806
807    def test_soft_ap_any_wpa_unrestricted(self):
808        self.verify_soft_ap_associate_and_pass_traffic(
809            self.primary_client, {
810                'ssid':
811                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
812                'security_type':
813                SECURITY_WPA,
814                'password':
815                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
816                'connectivity_mode':
817                CONNECTIVITY_MODE_UNRESTRICTED,
818                'operating_band':
819                OPERATING_BAND_ANY
820            })
821
822    def test_soft_ap_2g_wpa2_unrestricted(self):
823        self.verify_soft_ap_associate_and_pass_traffic(
824            self.primary_client, {
825                'ssid':
826                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
827                'security_type':
828                SECURITY_WPA2,
829                'password':
830                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
831                'connectivity_mode':
832                CONNECTIVITY_MODE_UNRESTRICTED,
833                'operating_band':
834                OPERATING_BAND_2G
835            })
836
837    def test_soft_ap_5g_wpa2_unrestricted(self):
838        self.verify_soft_ap_associate_and_pass_traffic(
839            self.primary_client, {
840                'ssid':
841                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
842                'security_type':
843                SECURITY_WPA2,
844                'password':
845                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
846                'connectivity_mode':
847                CONNECTIVITY_MODE_UNRESTRICTED,
848                'operating_band':
849                OPERATING_BAND_5G
850            })
851
852    def test_soft_ap_any_wpa2_unrestricted(self):
853        self.verify_soft_ap_associate_and_pass_traffic(
854            self.primary_client, {
855                'ssid':
856                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
857                'security_type':
858                SECURITY_WPA2,
859                'password':
860                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
861                'connectivity_mode':
862                CONNECTIVITY_MODE_UNRESTRICTED,
863                'operating_band':
864                OPERATING_BAND_ANY
865            })
866
867    def test_soft_ap_2g_wpa3_unrestricted(self):
868        self.verify_soft_ap_associate_and_pass_traffic(
869            self.primary_client, {
870                'ssid':
871                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
872                'security_type':
873                SECURITY_WPA3,
874                'password':
875                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
876                'connectivity_mode':
877                CONNECTIVITY_MODE_UNRESTRICTED,
878                'operating_band':
879                OPERATING_BAND_2G
880            })
881
882    def test_soft_ap_5g_wpa3_unrestricted(self):
883        self.verify_soft_ap_associate_and_pass_traffic(
884            self.primary_client, {
885                'ssid':
886                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
887                'security_type':
888                SECURITY_WPA3,
889                'password':
890                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
891                'connectivity_mode':
892                CONNECTIVITY_MODE_UNRESTRICTED,
893                'operating_band':
894                OPERATING_BAND_ANY
895            })
896
897    def test_soft_ap_any_wpa3_unrestricted(self):
898        self.verify_soft_ap_associate_and_pass_traffic(
899            self.primary_client, {
900                'ssid':
901                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G),
902                'security_type':
903                SECURITY_WPA3,
904                'password':
905                utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH),
906                'connectivity_mode':
907                CONNECTIVITY_MODE_UNRESTRICTED,
908                'operating_band':
909                OPERATING_BAND_ANY
910            })
911
912    def test_multi_client_open(self):
913        """Tests multi-client association with a single soft AP network.
914
915        This tests associates a variable length list of clients, verfying it can
916        can ping the SoftAP and pass traffic, and then verfies all previously
917        associated clients can still ping and pass traffic.
918
919        The same occurs in reverse for disassocations.
920        """
921        asserts.skip_if(
922            len(self.clients) < 2, 'Test requires at least 2 SoftAPClients')
923
924        settings = {
925            'ssid': utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
926            'security_type': SECURITY_OPEN,
927            'connectivity_mode': CONNECTIVITY_MODE_LOCAL,
928            'operating_band': OPERATING_BAND_ANY
929        }
930        self.start_soft_ap(settings)
931
932        dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP)
933        associated = []
934
935        for client in self.clients:
936            # Associate new client
937            self.associate_with_soft_ap(client.w_device, settings)
938            client_ipv4 = self.wait_for_ipv4_address(
939                client.w_device, ANDROID_DEFAULT_WLAN_PORT)
940            self.run_iperf_traffic(client.ip_client, dut_ap_interface.ipv4)
941
942            # Verify previously associated clients still behave as expected
943            for client_map in associated:
944                associated_client = client_map['client']
945                associated_client_ipv4 = client_map['ipv4']
946                self.log.info(
947                    'Verifying previously associated client %s still functions correctly.'
948                    % associated_client.w_device.device.serial)
949                try:
950                    self.verify_ping(self.dut, associated_client_ipv4)
951                    self.verify_ping(associated_client.w_device,
952                                     dut_ap_interface.ipv4)
953                    self.run_iperf_traffic(associated_client.ip_client,
954                                           dut_ap_interface.ipv4)
955                except signals.TestFailure as err:
956                    asserts.fail(
957                        'Previously associated client %s failed checks after '
958                        'client %s associated. Error: %s' %
959                        (associated_client.w_device.device.serial,
960                         client.w_device.device.serial, err))
961
962            associated.append({'client': client, 'ipv4': client_ipv4})
963
964        self.log.info(
965            'All devices successfully associated. Beginning disassociations.')
966
967        while len(associated) > 0:
968            # Disassociate client
969            client = associated.pop()['client']
970            self.disconnect_from_soft_ap(client.w_device)
971
972            # Verify still connected clients still behave as expected
973            for client_map in associated:
974                associated_client = client_map['client']
975                associated_client_ipv4 = client_map['ipv4']
976                try:
977                    self.log.info(
978                        'Verifying still associated client %s still functions '
979                        'correctly.' %
980                        associated_client.w_device.device.serial)
981                    self.verify_ping(self.dut, associated_client_ipv4)
982                    self.verify_ping(associated_client.w_device,
983                                     dut_ap_interface.ipv4)
984                    self.run_iperf_traffic(associated_client.ip_client,
985                                           dut_ap_interface.ipv4)
986                except signals.TestFailure as err:
987                    asserts.fail(
988                        'Previously associated client %s failed checks after'
989                        ' client %s disassociated. Error: %s' %
990                        (associated_client.w_device.device.serial,
991                         client.w_device.device.serial, err))
992
993        self.log.info('All disassociations occurred smoothly.')
994
995    def test_soft_ap_and_client(self):
996        """ Tests FuchsiaDevice DUT can act as a client and a SoftAP
997        simultaneously.
998
999        Raises:
1000            ConnectionError: if DUT fails to connect as client
1001            RuntimeError: if parallel processes fail to join
1002            TestFailure: if DUT fails to pass traffic as either a client or an
1003                AP
1004        """
1005        asserts.skip_if(not self.access_point, 'No access point provided.')
1006
1007        self.log.info('Setting up AP using hostapd.')
1008
1009        # Configure AP
1010        ap_params = self.user_params.get('soft_ap_test_params',
1011                                         {}).get('ap_params', {})
1012        channel = ap_params.get('channel', 11)
1013        ssid = ap_params.get('ssid', 'apnet')
1014        security_mode = ap_params.get('security_mode', None)
1015        password = ap_params.get('password', None)
1016        if security_mode:
1017            security = hostapd_security.Security(security_mode, password)
1018        else:
1019            security = None
1020
1021        # Setup AP and associate DUT
1022        if not setup_ap_and_associate(access_point=self.access_point,
1023                                      client=self.dut,
1024                                      profile_name='whirlwind',
1025                                      channel=channel,
1026                                      security=security,
1027                                      password=password,
1028                                      ssid=ssid):
1029            raise ConnectionError(
1030                'FuchsiaDevice DUT failed to connect as client to AP.')
1031        self.log.info('DUT successfully associated to AP network.')
1032
1033        # Verify FuchsiaDevice's client interface has an ip address from AP
1034        dut_client_interface = self.get_dut_interface_by_role(
1035            INTERFACE_ROLE_CLIENT)
1036
1037        # Verify FuchsiaDevice can ping AP
1038        lowest_5ghz_channel = 36
1039        if channel < lowest_5ghz_channel:
1040            ap_interface = self.access_point.wlan_2g
1041        else:
1042            ap_interface = self.access_point.wlan_5g
1043        ap_ipv4 = utils.get_interface_ip_addresses(
1044            self.access_point.ssh, ap_interface)['ipv4_private'][0]
1045
1046        self.verify_ping(self.dut, ap_ipv4)
1047
1048        # Setup SoftAP
1049        soft_ap_settings = {
1050            'ssid': utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G),
1051            'security_type': SECURITY_OPEN,
1052            'connectivity_mode': CONNECTIVITY_MODE_LOCAL,
1053            'operating_band': OPERATING_BAND_2G
1054        }
1055        self.start_soft_ap(soft_ap_settings)
1056
1057        # Get FuchsiaDevice's AP interface info
1058        dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP)
1059
1060        # Associate primary client with SoftAP
1061        self.associate_with_soft_ap(self.primary_client.w_device,
1062                                    soft_ap_settings)
1063
1064        # Verify primary client has an ip address from SoftAP
1065        client_ipv4 = self.wait_for_ipv4_address(self.primary_client.w_device,
1066                                                 ANDROID_DEFAULT_WLAN_PORT)
1067
1068        # Verify primary client can ping SoftAP, and reverse
1069        self.verify_ping(self.primary_client.w_device, dut_ap_interface.ipv4)
1070        self.verify_ping(self.dut, client_ipv4)
1071
1072        # Set up secondary iperf server of FuchsiaDevice
1073        self.log.info('Setting up second iperf server on FuchsiaDevice DUT.')
1074        secondary_iperf_server = iperf_server.IPerfServerOverSsh(
1075            self.iperf_server_config, DEFAULT_IPERF_PORT + 1, use_killall=True)
1076        secondary_iperf_server.start()
1077
1078        # Set up iperf client on AP
1079        self.log.info('Setting up iperf client on AP.')
1080        ap_iperf_client = iperf_client.IPerfClientOverSsh(
1081            self.user_params['AccessPoint'][0]['ssh_config'])
1082
1083        # Setup iperf processes:
1084        #     Primary client <-> SoftAP interface on FuchsiaDevice
1085        #     AP <-> Client interface on FuchsiaDevice
1086        process_errors = mp.Queue()
1087        iperf_soft_ap = mp.Process(
1088            target=self.run_iperf_traffic_parallel_process,
1089            args=[
1090                self.primary_client.ip_client, dut_ap_interface.ipv4,
1091                process_errors
1092            ])
1093
1094        iperf_fuchsia_client = mp.Process(
1095            target=self.run_iperf_traffic_parallel_process,
1096            args=[ap_iperf_client, dut_client_interface.ipv4, process_errors],
1097            kwargs={'server_port': 5202})
1098
1099        # Run iperf processes simultaneously
1100        self.log.info('Running simultaneous iperf traffic: between AP and DUT '
1101                      'client interface, and DUT AP interface and client.')
1102        iperf_soft_ap.start()
1103        iperf_fuchsia_client.start()
1104
1105        # Block until processes can join or timeout
1106        for proc in [iperf_soft_ap, iperf_fuchsia_client]:
1107            proc.join(timeout=30)
1108            if proc.is_alive():
1109                raise RuntimeError('Failed to join process %s' % proc)
1110
1111        # Stop iperf server (also stopped in teardown class as failsafe)
1112        secondary_iperf_server.stop()
1113
1114        # Check errors from parallel processes
1115        if process_errors.empty():
1116            asserts.explicit_pass(
1117                'FuchsiaDevice was successfully able to pass traffic as a '
1118                'client and an AP simultaneously.')
1119        else:
1120            while not process_errors.empty():
1121                self.log.error('Error in iperf process: %s' %
1122                               process_errors.get())
1123            asserts.fail(
1124                'FuchsiaDevice failed to pass traffic as a client and an AP '
1125                'simultaneously.')
1126
1127    def test_soft_ap_stress_from_config(self):
1128        """ Runs tests from ACTS config file.
1129
1130        Example Config
1131        "soft_ap_test_params" : {
1132            "soft_ap_tests": [
1133                {
1134                    "ssid": "test_network",
1135                    "security_type": "wpa2",
1136                    "password": "password",
1137                    "connectivity_mode": "local_only",
1138                    "operating_band": "only_2_4_ghz",
1139                    "reconnect_loops": 10
1140                }
1141            ]
1142        }
1143        """
1144        tests = self.user_params.get('soft_ap_test_params',
1145                                     {}).get('soft_ap_tests')
1146        asserts.skip_if(not tests, 'No soft ap tests in the ACTS config.')
1147
1148        test_settings_list = []
1149        for config_settings in self.user_params['soft_ap_test_params'][
1150                'soft_ap_tests']:
1151            ssid = config_settings.get(
1152                'ssid',
1153                utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G))
1154            security_type = config_settings.get('security_type', SECURITY_OPEN)
1155            password = config_settings.get('password', '')
1156            connectivity_mode = config_settings.get('connectivity_mode',
1157                                                    CONNECTIVITY_MODE_LOCAL)
1158            operating_band = config_settings.get('operating_band',
1159                                                 OPERATING_BAND_ANY)
1160            test_type = config_settings.get('test_type',
1161                                            'associate_and_pass_traffic')
1162            reconnect_loops = config_settings.get('reconnect_loops', 1)
1163            test_settings = {
1164                'client': self.primary_client,
1165                'ssid': ssid,
1166                'security_type': security_type,
1167                'password': password,
1168                'connectivity_mode': connectivity_mode,
1169                'operating_band': operating_band,
1170                'test_type': test_type,
1171                'reconnect_loops': reconnect_loops
1172            }
1173            test_settings_list.append(test_settings)
1174
1175        self.run_generated_testcases(self.run_config_stress_test,
1176                                     test_settings_list,
1177                                     name_func=generate_test_name)