1#
2#   Copyright 2017 - The Android Open Source Project
3#
4#   Licensed under the Apache License, Version 2.0 (the "License");
5#   you may not use this file except in compliance with the License.
6#   You may obtain a copy of the License at
7#
8#       http://www.apache.org/licenses/LICENSE-2.0
9#
10#   Unless required by applicable law or agreed to in writing, software
11#   distributed under the License is distributed on an "AS IS" BASIS,
12#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#   See the License for the specific language governing permissions and
14#   limitations under the License.
15
16import pprint
17import random
18import time
19from acts import context
20from scapy.all import *
21
22from acts import asserts
23from acts import base_test
24from acts import signals
25from acts.test_decorators import test_tracker_info
26from acts.test_utils.wifi import wifi_test_utils as wutils
27from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
28
29WifiEnums = wutils.WifiEnums
30DEF_ATTN = 60
31MAX_ATTN = 95
32ROAM_DBM = -75
33WAIT_AFTER_ATTN = 12
34ATTN_STEP = 5
35
36class WifiRoamingTest(WifiBaseTest):
37
38    def setup_class(self):
39        """Setup required dependencies from config file and configure
40           the required networks for testing roaming.
41
42        Returns:
43            True if successfully configured the requirements for testing.
44        """
45        super().setup_class()
46
47        self.dut = self.android_devices[0]
48        wutils.wifi_test_device_init(self.dut)
49        req_params = ["roaming_attn", "roam_interval", "ping_addr",
50                      "max_bugreports"]
51        opt_param = ["open_network", "reference_networks",]
52        self.unpack_userparams(
53            req_param_names=req_params, opt_param_names=opt_param)
54
55        if "AccessPoint" in self.user_params:
56            self.legacy_configure_ap_and_start(ap_count=2)
57
58        asserts.assert_true(
59            len(self.reference_networks) > 1,
60            "Need at least two psk networks for roaming.")
61        asserts.assert_true(
62            len(self.open_network) > 1,
63            "Need at least two open networks for roaming")
64
65
66        self.configure_packet_capture()
67
68    def teardown_class(self):
69        self.dut.ed.clear_all_events()
70        if "AccessPoint" in self.user_params:
71            del self.user_params["reference_networks"]
72            del self.user_params["open_network"]
73
74    def setup_test(self):
75        self.dut.ed.clear_all_events()
76        self.dut.droid.wakeLockAcquireBright()
77        self.dut.droid.wakeUpNow()
78
79    def teardown_test(self):
80        self.dut.droid.wakeLockRelease()
81        self.dut.droid.goToSleepNow()
82        wutils.reset_wifi(self.dut)
83        for a in self.attenuators:
84            a.set_atten(0)
85
86    def on_fail(self, test_name, begin_time):
87        self.dut.cat_adb_log(test_name, begin_time)
88        self.dut.take_bug_report(test_name, begin_time)
89
90    def roaming_from_AP1_and_AP2(self, AP1_network, AP2_network):
91        """Test roaming between two APs.
92
93        Args:
94            AP1_network: AP-1's network information.
95            AP2_network: AP-2's network information.
96
97        Steps:
98        1. Make AP1 visible, AP2 not visible.
99        2. Connect to AP1's ssid.
100        3. Make AP1 not visible, AP2 visible.
101        4. Expect DUT to roam to AP2.
102        5. Validate connection information and ping.
103        """
104        wutils.set_attns(self.attenuators, "AP1_on_AP2_off")
105        wifi_config = AP1_network.copy()
106        wifi_config.pop("bssid")
107        wutils.connect_to_wifi_network(self.dut, wifi_config)
108        self.log.info("Roaming from %s to %s", AP1_network, AP2_network)
109        wutils.trigger_roaming_and_validate(
110            self.dut, self.attenuators, "AP1_off_AP2_on", AP2_network)
111
112    def get_rssi(self, pcap_file, expected_bssid):
113        """Get signal strength of the wifi network attenuated.
114
115        Args:
116            pcap_file: PCAP file path.
117            expected_bssid: BSSID of the wifi network attenuated.
118        """
119        packets = []
120        try:
121            packets = rdpcap(pcap_file)
122        except Scapy_Exception:
123            self.log.error("Failed to read pcap file")
124        if not packets:
125            return 0
126
127        dbm = -100
128        for pkt in packets:
129            if pkt and hasattr(pkt, 'type') and pkt.type == 0 and \
130                pkt.subtype == 8 and hasattr(pkt, 'info'):
131                  bssid = pkt.addr3
132                  if expected_bssid == bssid:
133                      dbm = int(pkt.dBm_AntSignal)
134        self.log.info("RSSI: %s" % dbm)
135        return dbm
136
137    def trigger_roaming_and_verify_attenuation(self, network):
138        """Trigger roaming and verify signal strength is below roaming limit.
139
140        Args:
141            network: Wifi network that is being attenuated.
142        """
143        wutils.set_attns_steps(self.attenuators, "AP1_off_AP2_on")
144        band = '5G' if network['SSID'].startswith('5g_') else '2G'
145        attn = DEF_ATTN + ATTN_STEP
146        while attn <= MAX_ATTN:
147            self.pcap_procs = wutils.start_pcap(
148                self.packet_capture, 'dual', self.test_name)
149            time.sleep(WAIT_AFTER_ATTN/3)
150            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
151            pcap_file = os.path.join(
152                context.get_current_context().get_full_output_path(),
153                'PacketCapture',
154                '%s_%s.pcap' % (self.test_name, band))
155
156            rssi = self.get_rssi(pcap_file, network["bssid"])
157            if rssi == 0:
158                self.log.error("Failed to verify signal strength")
159                break
160            if self.get_rssi(pcap_file, network["bssid"]) < ROAM_DBM:
161                break
162
163            self.attenuators[0].set_atten(attn)
164            self.attenuators[1].set_atten(attn)
165            time.sleep(WAIT_AFTER_ATTN) # allow some time for attenuation
166            attn += 5
167
168    def validate_roaming(self, expected_con):
169        """Validate roaming.
170
171        Args:
172            expected_con: Expected wifi network after roaming.
173        """
174        expected_con = {
175            WifiEnums.SSID_KEY: expected_con[WifiEnums.SSID_KEY],
176            WifiEnums.BSSID_KEY: expected_con["bssid"],
177        }
178        curr_con = self.dut.droid.wifiGetConnectionInfo()
179        for key in expected_con:
180            if expected_con[key] != curr_con[key]:
181                asserts.fail("Expected '%s' to be %s, actual is %s." %
182                             (key, expected_con[key], curr_con[key]))
183        self.log.info("Roamed to %s successfully",
184                      expected_con[WifiEnums.BSSID_KEY])
185        if not wutils.validate_connection(self.dut):
186            raise signals.TestFailure("Fail to connect to internet on %s" %
187                                      expected_con[WifiEnums.BSSID_KEY])
188
189    """ Tests Begin.
190
191        The following tests are designed to test inter-SSID Roaming only.
192
193        """
194    @test_tracker_info(uuid="db8a46f9-713f-4b98-8d9f-d36319905b0a")
195    def test_roaming_between_AP1_to_AP2_open_2g(self):
196        AP1_network = self.open_network[0]["2g"]
197        AP2_network = self.open_network[1]["2g"]
198        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
199
200    @test_tracker_info(uuid="0db67d9b-6ea9-4f40-acf2-155c4ecf9dc5")
201    def test_roaming_between_AP1_to_AP2_open_5g(self):
202        AP1_network = self.open_network[0]["5g"]
203        AP2_network = self.open_network[1]["5g"]
204        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
205
206    @test_tracker_info(uuid="eabc7319-d962-4bef-b679-725e9ff00420")
207    def test_roaming_between_AP1_to_AP2_psk_2g(self):
208        AP1_network = self.reference_networks[0]["2g"]
209        AP2_network = self.reference_networks[1]["2g"]
210        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
211
212    @test_tracker_info(uuid="1cf9c681-4ff0-45c1-9719-f01629f6a7f7")
213    def test_roaming_between_AP1_to_AP2_psk_5g(self):
214        AP1_network = self.reference_networks[0]["5g"]
215        AP2_network = self.reference_networks[1]["5g"]
216        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
217
218    @test_tracker_info(uuid="3114d625-5cdd-4205-bb46-5a9d057dc80d")
219    def test_roaming_fail_psk_2g(self):
220        network = {'SSID':'test_roaming_fail', 'password':'roam123456@'}
221        # AP2 network with incorrect password.
222        network_fail = {'SSID':'test_roaming_fail', 'password':'roam123456@#$%^'}
223        # Setup AP1 with the correct password.
224        wutils.ap_setup(self, 0, self.access_points[0], network)
225        network_bssid = self.access_points[0].get_bssid_from_ssid(
226                network["SSID"], '2g')
227        # Setup AP2 with the incorrect password.
228        wutils.ap_setup(self, 1, self.access_points[1], network_fail)
229        network_fail_bssid = self.access_points[1].get_bssid_from_ssid(
230                network_fail["SSID"], '2g')
231        network['bssid'] = network_bssid
232        network_fail['bssid'] = network_fail_bssid
233        try:
234            # Initiate roaming with AP2 configured with incorrect password.
235            self.roaming_from_AP1_and_AP2(network, network_fail)
236        except:
237            self.log.info("Roaming failed to AP2 with incorrect password.")
238            # Re-configure AP2 after roaming failed, with correct password.
239            self.log.info("Re-configuring AP2 with correct password.")
240            wutils.ap_setup(self, 1, self.access_points[1], network)
241        self.roaming_from_AP1_and_AP2(network, network_fail)
242
243    """ Tests End """
244