blob: 0c8336e5b034d70d0fdc18fb36631493fb07859b [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2022 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import time
from datetime import datetime, timedelta, timezone
from typing import FrozenSet
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
from acts import asserts
from acts import signals
from acts import utils
from acts.controllers.access_point import setup_ap
from acts.controllers.ap_lib import hostapd_constants
from acts.controllers.ap_lib.radio_measurement import BssidInformation, BssidInformationCapabilities, NeighborReportElement, PhyType
from acts.controllers.ap_lib.wireless_network_management import BssTransitionManagementRequest
# TODO(fxbug.dev/103440) WNM support should be visible/controllable in ACTS.
# When ACTS can see WNM features that are enabled (through ACTS config) or
# ACTS can enable WNM features (through new APIs), additional tests should be
# added to this suite that check that features function properly when the DUT is
# configured to support those features.
class WlanWirelessNetworkManagementTest(WifiBaseTest):
"""Tests Fuchsia's Wireless Network Management (AKA 802.11v) support.
Testbed Requirements:
* One Fuchsia device
* One Whirlwind access point
Existing Fuchsia drivers do not yet support WNM features out-of-the-box, so these
tests check that WNM features are not enabled.
"""
def setup_class(self):
if 'dut' in self.user_params and self.user_params[
'dut'] != 'fuchsia_devices':
raise AttributeError(
'WlanWirelessNetworkManagementTest is only relevant for Fuchsia devices.'
)
self.dut = create_wlan_device(self.fuchsia_devices[0])
if self.dut.device.association_mechanism != 'policy':
raise AttributeError('Must use WLAN policy layer to test WNM.')
self.access_point = self.access_points[0]
def teardown_class(self):
self.dut.disconnect()
self.access_point.stop_all_aps()
def teardown_test(self):
self.dut.disconnect()
self.download_ap_logs()
self.access_point.stop_all_aps()
def on_fail(self, test_name: str, begin_time: str):
super().on_fail(test_name, begin_time)
self.access_point.stop_all_aps()
def on_exception(self, test_name: str, begin_time: str):
super().on_exception(test_name, begin_time)
self.dut.disconnect()
self.access_point.stop_all_aps()
def setup_ap(self,
ssid: str,
channel: int = hostapd_constants.AP_DEFAULT_CHANNEL_2G,
wnm_features: FrozenSet[
hostapd_constants.WnmFeature] = frozenset()):
"""Sets up an AP using the provided parameters.
Args:
ssid: SSID for the AP.
channel: which channel number to set the AP to (default is
AP_DEFAULT_CHANNEL_2G).
wnm_features: Wireless Network Management features to enable
(default is no WNM features).
"""
setup_ap(access_point=self.access_point,
profile_name='whirlwind',
channel=channel,
ssid=ssid,
security=None,
wnm_features=wnm_features)
def _get_client_mac(self) -> str:
"""Get the MAC address of the DUT client interface.
Returns:
str, MAC address of the DUT client interface.
Raises:
ValueError if there is no DUT client interface.
ConnectionError if the DUT interface query fails.
"""
wlan_ifaces = self.dut.device.wlan_lib.wlanGetIfaceIdList()
if wlan_ifaces.get('error'):
raise ConnectionError('Failed to get wlan interface IDs: %s' %
wlan_ifaces['error'])
for wlan_iface in wlan_ifaces['result']:
iface_info = self.dut.device.wlan_lib.wlanQueryInterface(
wlan_iface)
if iface_info.get('error'):
raise ConnectionError('Failed to query wlan iface: %s' %
iface_info['error'])
if iface_info['result']['role'] == 'Client':
return utils.mac_address_list_to_str(
iface_info['result']['sta_addr'])
raise ValueError(
'Failed to get client interface mac address. No client interface found.'
)
def test_bss_transition_ap_supported_dut_unsupported(self):
ssid = utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G)
wnm_features = frozenset(
[hostapd_constants.WnmFeature.BSS_TRANSITION_MANAGEMENT])
self.setup_ap(ssid, wnm_features=wnm_features)
asserts.assert_true(self.dut.associate(ssid), 'Failed to associate.')
asserts.assert_true(self.dut.is_connected(), 'Failed to connect.')
client_mac = self._get_client_mac()
ext_capabilities = self.access_point.get_sta_extended_capabilities(
self.access_point.wlan_2g, client_mac)
asserts.assert_false(
ext_capabilities.bss_transition,
'DUT is incorrectly advertising BSS Transition Management support')
def test_wnm_sleep_mode_ap_supported_dut_unsupported(self):
ssid = utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G)
wnm_features = frozenset([hostapd_constants.WnmFeature.WNM_SLEEP_MODE])
self.setup_ap(ssid, wnm_features=wnm_features)
asserts.assert_true(self.dut.associate(ssid), 'Failed to associate.')
asserts.assert_true(self.dut.is_connected(), 'Failed to connect.')
client_mac = self._get_client_mac()
ext_capabilities = self.access_point.get_sta_extended_capabilities(
self.access_point.wlan_2g, client_mac)
asserts.assert_false(
ext_capabilities.wnm_sleep_mode,
'DUT is incorrectly advertising WNM Sleep Mode support')
def test_btm_req_ignored_dut_unsupported(self):
ssid = utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G)
wnm_features = frozenset(
[hostapd_constants.WnmFeature.BSS_TRANSITION_MANAGEMENT])
# Setup 2.4 GHz AP.
self.setup_ap(ssid,
channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
wnm_features=wnm_features)
asserts.assert_true(self.dut.associate(ssid), 'Failed to associate.')
# Verify that DUT is actually associated (as seen from AP).
client_mac = self._get_client_mac()
asserts.assert_true(
client_mac in self.access_point.get_stas(
self.access_point.wlan_2g),
'Client MAC not included in list of associated STAs on the 2.4GHz band'
)
# Setup 5 GHz AP with same SSID.
self.setup_ap(ssid,
channel=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
wnm_features=wnm_features)
# Construct a BTM request.
dest_bssid = self.access_point.get_bssid_from_ssid(
ssid, self.access_point.wlan_5g)
dest_bssid_info = BssidInformation(
security=True, capabilities=BssidInformationCapabilities())
neighbor_5g_ap = NeighborReportElement(
dest_bssid,
dest_bssid_info,
operating_class=126,
channel_number=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
phy_type=PhyType.VHT)
btm_req = BssTransitionManagementRequest(
disassociation_imminent=True, candidate_list=[neighbor_5g_ap])
# Send BTM request from 2.4 GHz AP to DUT
self.access_point.send_bss_transition_management_req(
self.access_point.wlan_2g, client_mac, btm_req)
# Check that DUT has not reassociated.
REASSOC_DEADLINE = datetime.now(timezone.utc) + timedelta(seconds=2)
while datetime.now(timezone.utc) < REASSOC_DEADLINE:
# Fail if DUT has reassociated to 5 GHz AP (as seen from AP).
if client_mac in self.access_point.get_stas(
self.access_point.wlan_5g):
raise signals.TestFailure(
'DUT unexpectedly roamed to target BSS after BTM request')
else:
time.sleep(0.25)
# DUT should have stayed associated to original AP.
asserts.assert_true(
client_mac in self.access_point.get_stas(
self.access_point.wlan_2g),
'DUT lost association on the 2.4GHz band after BTM request')