1#!/usr/bin/env python3.4
2#
3#   Copyright 2018 - 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
17import itertools
18import time
19
20import acts.base_test
21import acts.signals as signals
22from acts_contrib.test_utils.tel.tel_wifi_utils import WIFI_CONFIG_APBAND_2G
23import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
24import acts.utils as utils
25
26from acts import asserts
27from acts.test_decorators import test_tracker_info
28from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
29
30WifiEnums = wutils.WifiEnums
31# Default timeout used for reboot, toggle WiFi and Airplane mode,
32# for the system to settle down after the operation.
33DEFAULT_TIMEOUT = 10
34GET_MAC_ADDRESS = ("ip addr show wlan0"
35                   "| grep 'link/ether'"
36                   "| cut -d ' ' -f6")
37MAC_SETTING = "wifi_connected_mac_randomization_enabled"
38GET_MAC_RANDOMIZATION_STATUS = "settings get global {}".format(MAC_SETTING)
39TURN_ON_MAC_RANDOMIZATION = "settings put global {} 1".format(MAC_SETTING)
40TURN_OFF_MAC_RANDOMIZATION = "settings put global {} 0".format(MAC_SETTING)
41LOG_CLEAR = "logcat -c"
42LOG_GREP = "logcat -d | grep {}"
43
44
45class WifiConnectedMacRandomizationTest(WifiBaseTest):
46    """Tests for Connected MAC Randomization.
47
48    Test Bed Requirement:
49    * Two Android devices with the first one supporting MAC randomization.
50    * At least two Wi-Fi networks to connect to.
51    """
52
53    def setup_class(self):
54        super().setup_class()
55
56        self.dut = self.android_devices[0]
57        self.dut_softap = self.android_devices[1]
58        wutils.wifi_test_device_init(self.dut)
59        wutils.wifi_test_device_init(self.dut_softap)
60
61        self.reset_mac_address_to_factory_mac()
62        self.dut.adb.shell(TURN_ON_MAC_RANDOMIZATION)
63        asserts.assert_equal(
64            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "1",
65            "Failed to enable Connected MAC Randomization on dut.")
66
67        req_params = ["reference_networks"]
68        opt_param = []
69        self.unpack_userparams(req_param_names=req_params,
70                               opt_param_names=opt_param)
71
72        if "AccessPoint" in self.user_params:
73            self.legacy_configure_ap_and_start()
74
75        asserts.assert_true(
76            self.reference_networks[0]["2g"],
77            "Need at least 1 2.4Ghz reference network with psk.")
78        asserts.assert_true(
79            self.reference_networks[0]["5g"],
80            "Need at least 1 5Ghz reference network with psk.")
81        self.wpapsk_2g = self.reference_networks[0]["2g"]
82        self.wpapsk_5g = self.reference_networks[0]["5g"]
83
84    def setup_test(self):
85        super().setup_test()
86        self.dut.droid.wakeLockAcquireBright()
87        self.dut.droid.wakeUpNow()
88        wutils.wifi_toggle_state(self.dut, True)
89        wutils.wifi_toggle_state(self.dut_softap, False)
90
91    def teardown_test(self):
92        super().teardown_test()
93        self.dut.droid.wakeLockRelease()
94        self.dut.droid.goToSleepNow()
95        wutils.reset_wifi(self.dut)
96        wutils.reset_wifi(self.dut_softap)
97
98    def teardown_class(self):
99        wutils.stop_wifi_tethering(self.dut_softap)
100        self.reset_mac_address_to_factory_mac()
101        if "AccessPoint" in self.user_params:
102            del self.user_params["reference_networks"]
103            del self.user_params["open_network"]
104
105    """Helper Functions"""
106
107    def get_current_mac_address(self, ad):
108        """Get the device's wlan0 MAC address.
109
110        Args:
111            ad: AndroidDevice to get MAC address of.
112
113        Returns:
114            A MAC address string in the format of "12:34:56:78:90:12".
115        """
116        return ad.adb.shell(GET_MAC_ADDRESS)
117
118    def is_valid_randomized_mac_address(self, mac):
119        """Check if the given MAC address is a valid randomized MAC address.
120
121        Args:
122            mac: MAC address to check in the format of "12:34:56:78:90:12".
123        """
124        asserts.assert_true(
125            mac != self.dut_factory_mac,
126            "Randomized MAC address is same as factory MAC address.")
127        first_byte = int(mac[:2], 16)
128        asserts.assert_equal(first_byte & 1, 0, "MAC address is not unicast.")
129        asserts.assert_equal(first_byte & 2, 2, "MAC address is not local.")
130
131    def reset_mac_address_to_factory_mac(self):
132        """Reset dut to and store factory MAC address by turning off
133        Connected MAC Randomization and rebooting dut.
134        """
135        self.dut.adb.shell(TURN_OFF_MAC_RANDOMIZATION)
136        asserts.assert_equal(
137            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "0",
138            "Failed to disable Connected MAC Randomization on dut.")
139        self.dut.reboot()
140        time.sleep(DEFAULT_TIMEOUT)
141        self.dut_factory_mac = self.get_current_mac_address(self.dut)
142
143    def get_connection_data(self, ad, network):
144        """Connect and get network id and ssid info from connection data.
145
146        Args:
147            ad: AndroidDevice to use for connection
148            network: network info of the network to connect to
149
150        Returns:
151            A convenience dict with the connected network's ID and SSID.
152        """
153        wutils.connect_to_wifi_network(ad, network)
154        connect_data = ad.droid.wifiGetConnectionInfo()
155        ssid_id_dict = dict()
156        ssid_id_dict[WifiEnums.NETID_KEY] = connect_data[WifiEnums.NETID_KEY]
157        ssid_id_dict[WifiEnums.SSID_KEY] = connect_data[WifiEnums.SSID_KEY]
158        return ssid_id_dict
159
160    """Tests"""
161
162    @test_tracker_info(uuid="")
163    def test_wifi_connection_2G_with_mac_randomization(self):
164        """Tests connection to 2G network with Connected MAC Randomization.
165        """
166        wutils.connect_to_wifi_network(self.dut, self.wpapsk_2g)
167        mac = self.get_current_mac_address(self.dut)
168        self.is_valid_randomized_mac_address(mac)
169
170    @test_tracker_info(uuid="")
171    def test_wifi_connection_5G_with_mac_randomization(self):
172        """Tests connection to 5G network with Connected MAC Randomization.
173        """
174        wutils.connect_to_wifi_network(self.dut, self.wpapsk_5g)
175        mac = self.get_current_mac_address(self.dut)
176        self.is_valid_randomized_mac_address(mac)
177
178    @test_tracker_info(uuid="")
179    def test_randomized_mac_persistent_between_connections(self):
180        """Tests that randomized MAC address assigned to each network is
181        persistent between connections.
182
183        Steps:
184        1. Connect to a 2GHz network.
185        2. Connect to a 5GHz network.
186        3. Reconnect to the 2GHz network using its network id.
187        4. Verify that MAC addresses in Steps 1 and 3 are equal.
188        5. Reconnect to the 5GHz network using its network id.
189        6. Verify that MAC addresses in Steps 2 and 5 are equal.
190        """
191        connect_data_2g = self.get_connection_data(self.dut, self.wpapsk_2g)
192        old_mac_2g = self.get_current_mac_address(self.dut)
193        self.is_valid_randomized_mac_address(old_mac_2g)
194
195        connect_data_5g = self.get_connection_data(self.dut, self.wpapsk_5g)
196        old_mac_5g = self.get_current_mac_address(self.dut)
197        self.is_valid_randomized_mac_address(old_mac_5g)
198
199        asserts.assert_true(
200            old_mac_2g != old_mac_5g,
201            "Randomized MAC addresses for 2G and 5G networks are equal.")
202
203        reconnect_2g = wutils.connect_to_wifi_network_with_id(
204            self.dut, connect_data_2g[WifiEnums.NETID_KEY],
205            connect_data_2g[WifiEnums.SSID_KEY])
206        if not reconnect_2g:
207            raise signals.TestFailure("Device did not connect to the correct"
208                                      " 2GHz network.")
209        new_mac_2g = self.get_current_mac_address(self.dut)
210        asserts.assert_equal(
211            old_mac_2g, new_mac_2g,
212            "Randomized MAC for 2G is not persistent between connections.")
213
214        reconnect_5g = wutils.connect_to_wifi_network_with_id(
215            self.dut, connect_data_5g[WifiEnums.NETID_KEY],
216            connect_data_5g[WifiEnums.SSID_KEY])
217        if not reconnect_5g:
218            raise signals.TestFailure("Device did not connect to the correct"
219                                      " 5GHz network.")
220        new_mac_5g = self.get_current_mac_address(self.dut)
221        asserts.assert_equal(
222            old_mac_5g, new_mac_5g,
223            "Randomized MAC for 5G is not persistent between connections.")
224
225    @test_tracker_info(uuid="")
226    def test_randomized_mac_used_during_connection(self):
227        """Verify that the randomized MAC address and not the factory
228        MAC address is used during connection by checking the softap logs.
229
230        Steps:
231        1. Set up softAP on dut_softap.
232        2. Have dut connect to the softAp.
233        3. Verify that only randomized MAC appears in softAp logs.
234        """
235        self.dut_softap.adb.shell(LOG_CLEAR)
236        config = wutils.create_softap_config()
237        wutils.start_wifi_tethering(self.dut_softap,
238                                    config[wutils.WifiEnums.SSID_KEY],
239                                    config[wutils.WifiEnums.PWD_KEY],
240                                    WIFI_CONFIG_APBAND_2G)
241
242        # Internet validation fails when dut_softap does not have a valid sim
243        # supporting softap. Since this test is not checking for internet
244        # validation, we suppress failure signals.
245        wutils.connect_to_wifi_network(self.dut, config, assert_on_fail=False)
246        mac = self.get_current_mac_address(self.dut)
247        wutils.stop_wifi_tethering(self.dut_softap)
248
249        self.is_valid_randomized_mac_address(mac)
250        log = self.dut_softap.adb.shell(LOG_GREP.format(mac))
251        asserts.assert_true(len(log) > 0, "Randomized MAC not in log.")
252        log = self.dut_softap.adb.shell(LOG_GREP.format(self.dut_factory_mac))
253        asserts.assert_true(len(log) == 0, "Factory MAC is in log.")
254