1#!/usr/bin/env python3
2#
3# Copyright (C) 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# 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, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
16"""
17Script for testing WiFi recovery after rebooting the AP.
18
19Override default number of iterations using the following
20parameter in the test config file.
21
22"beacon_loss_test_iterations": "5"
23"""
24
25import time
26
27from acts import asserts
28from acts import signals
29from acts import utils
30from acts.controllers.access_point import setup_ap
31from acts.controllers.ap_lib import hostapd_constants
32
33from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
34from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
35from acts.utils import rand_ascii_str
36
37
38class BeaconLossTest(WifiBaseTest):
39    # Default number of test iterations here.
40    # Override using parameter in config file.
41    # Eg: "beacon_loss_test_iterations": "10"
42    num_of_iterations = 5
43
44    # Time to wait for AP to startup
45    wait_ap_startup_s = 15
46
47    # Default wait time in seconds for the AP radio to turn back on
48    wait_to_connect_after_ap_txon_s = 5
49
50    # Time to wait for device to disconnect after AP radio of
51    wait_after_ap_txoff_s = 15
52
53    # Time to wait for device to complete connection setup after
54    # given an associate command
55    wait_client_connection_setup_s = 15
56
57    def setup_class(self):
58        super().setup_class()
59        self.ssid = rand_ascii_str(10)
60        if 'dut' in self.user_params:
61            if self.user_params['dut'] == 'fuchsia_devices':
62                self.dut = create_wlan_device(self.fuchsia_devices[0])
63            elif self.user_params['dut'] == 'android_devices':
64                self.dut = create_wlan_device(self.android_devices[0])
65            else:
66                raise ValueError('Invalid DUT specified in config. (%s)' %
67                                 self.user_params['dut'])
68        else:
69            # Default is an android device, just like the other tests
70            self.dut = create_wlan_device(self.android_devices[0])
71        self.access_point = self.access_points[0]
72        self.num_of_iterations = int(
73            self.user_params.get("beacon_loss_test_iterations",
74                                 self.num_of_iterations))
75        self.in_use_interface = None
76
77    def teardown_test(self):
78        self.dut.disconnect()
79        self.dut.reset_wifi()
80        # ensure radio is on, in case the test failed while the radio was off
81        self.access_point.iwconfig.ap_iwconfig(self.in_use_interface,
82                                               "txpower on")
83        self.download_ap_logs()
84        self.access_point.stop_all_aps()
85
86    def on_fail(self, test_name, begin_time):
87        super().on_fail(test_name, begin_time)
88        self.access_point.stop_all_aps()
89
90    def beacon_loss(self, channel):
91        setup_ap(access_point=self.access_point,
92                 profile_name='whirlwind',
93                 channel=channel,
94                 ssid=self.ssid)
95        time.sleep(self.wait_ap_startup_s)
96        if channel > 14:
97            self.in_use_interface = self.access_point.wlan_5g
98        else:
99            self.in_use_interface = self.access_point.wlan_2g
100
101        # TODO(b/144505723): [ACTS] update BeaconLossTest.py to handle client
102        # roaming, saved networks, etc.
103        self.log.info("sending associate command for ssid %s", self.ssid)
104        self.dut.associate(target_ssid=self.ssid)
105
106        asserts.assert_true(self.dut.is_connected(), 'Failed to connect.')
107
108        time.sleep(self.wait_client_connection_setup_s)
109
110        for _ in range(0, self.num_of_iterations):
111            # Turn off AP radio
112            self.log.info("turning off radio")
113            self.access_point.iwconfig.ap_iwconfig(self.in_use_interface,
114                                                   "txpower off")
115            time.sleep(self.wait_after_ap_txoff_s)
116
117            # Did we disconnect from AP?
118            asserts.assert_false(self.dut.is_connected(),
119                                 'Failed to disconnect.')
120
121            # Turn on AP radio
122            self.log.info("turning on radio")
123            self.access_point.iwconfig.ap_iwconfig(self.in_use_interface,
124                                                   "txpower on")
125            time.sleep(self.wait_to_connect_after_ap_txon_s)
126
127            # Tell the client to connect
128            self.log.info("sending associate command for ssid %s" % self.ssid)
129            self.dut.associate(target_ssid=self.ssid)
130            time.sleep(self.wait_client_connection_setup_s)
131
132            # Did we connect back to WiFi?
133            asserts.assert_true(self.dut.is_connected(),
134                                'Failed to connect back.')
135
136        return True
137
138    def test_beacon_loss_2g(self):
139        self.beacon_loss(channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G)
140
141    def test_beacon_loss_5g(self):
142        self.beacon_loss(channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G)
143