blob: ff1663775a5d191b1b537b9c1d3edc5e51ee9f2e [file] [log] [blame]
# Lint as: python3
#!/usr/bin/env python3
#
# Copyright 2022 The Fuchsia Authors
#
# 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.
#
# This class provides pipeline betweem python tests and WLAN policy facade.
from enum import StrEnum
from honeydew import errors
from honeydew.interfaces.device_classes.fuchsia_device import (
FuchsiaDevice as HdFuchsiaDevice,
)
from honeydew.typing.wlan import (
ClientStateSummary,
ConnectionState,
DisconnectStatus,
NetworkConfig,
NetworkIdentifier,
NetworkState,
RequestStatus,
SecurityType,
WlanClientState,
)
from antlion.controllers.ap_lib.hostapd_security import FuchsiaSecurityType
from antlion.controllers.fuchsia_lib.base_lib import BaseLib
from antlion.validation import MapValidator
DEFAULT_UPDATE_TIMEOUT_SEC: float = 30.0
class WlanPolicyError(Exception):
"""Exception for SL4F commands executed by WLAN Policy."""
class Command(StrEnum):
"""Sl4f Server Commands."""
CONNECT = "wlan_policy.connect"
CREATE_CLIENT_CONTROLLER = "wlan_policy.create_client_controller"
GET_SAVED_NETWORKS = "wlan_policy.get_saved_networks"
GET_UPDATE = "wlan_policy.get_update"
REMOVE_ALL_NETWORKS = "wlan_policy.remove_all_networks"
REMOVE_NETWORK = "wlan_policy.remove_network"
SAVE_NETWORK = "wlan_policy.save_network"
SCAN_FOR_NETWORKS = "wlan_policy.scan_for_networks"
SET_NEW_UPDATE_LISTENER = "wlan_policy.set_new_update_listener"
START_CLIENT_CONNECTIONS = "wlan_policy.start_client_connections"
STOP_CLIENT_CONNECTIONS = "wlan_policy.stop_client_connections"
class FuchsiaWlanPolicyLib(BaseLib):
def __init__(self, addr: str, honeydew_fd: HdFuchsiaDevice | None = None) -> None:
super().__init__(addr, "wlan_policy")
self.honeydew_fd = honeydew_fd
def _check_response_error(
self, cmd: Command, response_json: dict[str, object]
) -> object | None:
"""Helper method to process errors from SL4F calls.
Args:
cmd: SL4F command sent.
response_json: Response from SL4F server.
Returns:
Response json or None if error.
Raises:
WlanPolicyError if the response_json has something in the 'error' field.
"""
resp = MapValidator(response_json)
error = resp.get(str, "error", None)
if error:
# We sometimes expect to catch WlanPolicyError so we include a log here for
# when we do retries.
self.log.debug(f"SL4F call: {cmd} failed with Error: '{error}'.")
raise WlanPolicyError(f"SL4F call: {cmd} failed with Error: '{error}'.")
else:
return response_json.get("result")
def connect(
self, target_ssid: str, security_type: SecurityType | FuchsiaSecurityType
) -> RequestStatus:
"""Triggers connection to a network.
Args:
target_ssid: The network to connect to. Must have been previously
saved in order for a successful connection to happen.
security_type: The security protocol of the network.
Returns:
A RequestStatus response to the connect request.
Raises:
WlanPolicyError: Sl4f run command failed.
TypeError: Return value not a string.
"""
# TODO(b/308807691): Change other uses of FuchsiaSecurityType to Honeydew's
# SecurityType
hd_security_type = SecurityType(security_type.value)
method_params = {
"target_ssid": target_ssid,
"security_type": str(hd_security_type),
}
if self.honeydew_fd:
try:
return self.honeydew_fd.wlan_policy.connect(
target_ssid, hd_security_type
)
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.CONNECT, method_params)
result = self._check_response_error(Command.CONNECT, resp)
if not isinstance(result, str):
raise TypeError(f'Expected "result" to be str, got {type(result)}')
return RequestStatus(result)
def create_client_controller(self) -> None:
"""Initializes the client controller.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.create_client_controller()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.CREATE_CLIENT_CONTROLLER)
self._check_response_error(Command.CREATE_CLIENT_CONTROLLER, resp)
def get_saved_networks(self) -> list[NetworkConfig]:
"""Gets networks saved on device.
Returns:
A list of NetworkConfigs.
Raises:
WlanPolicyError: Sl4f run command failed.
TypeError: Return values not correct types.
"""
if self.honeydew_fd:
try:
return self.honeydew_fd.wlan_policy.get_saved_networks()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.GET_SAVED_NETWORKS)
result = self._check_response_error(Command.GET_SAVED_NETWORKS, resp)
if not isinstance(result, list):
raise TypeError(f'Expected "result" to be list, got {type(result)}')
networks: list[NetworkConfig] = []
for n in result:
if not isinstance(n, dict):
raise TypeError(f'Expected "network" to be dict, got {type(n)}')
network = MapValidator(n)
security_type = network.get(str, "security_type", "None")
networks.append(
NetworkConfig(
ssid=network.get(str, "ssid"),
security_type=SecurityType(security_type.lower()),
credential_type=network.get(str, "credential_type"),
credential_value=network.get(str, "credential_value"),
)
)
return networks
def get_update(
self, timeout: float = DEFAULT_UPDATE_TIMEOUT_SEC
) -> ClientStateSummary:
"""Gets one client listener update.
This call will return with an update immediately the
first time the update listener is initialized by setting a new listener
or by creating a client controller before setting a new listener.
Subsequent calls will hang until there is a change since the last
update call.
Args:
timeout: Timeout in seconds to wait for the get_update command to
return.
Returns:
An update of connection status. If there is no error, the result is
a WlanPolicyUpdate with a structure that matches the FIDL
ClientStateSummary struct given for updates.
Raises:
WlanPolicyError: Sl4f run command failed.
TypeError: Return values not correct types.
"""
if self.honeydew_fd:
try:
return self.honeydew_fd.wlan_policy.get_update(response_timeout=timeout)
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.GET_UPDATE, response_timeout=timeout)
result_raw = self._check_response_error(Command.GET_UPDATE, resp)
if not isinstance(result_raw, dict):
raise TypeError(f'Expected "result" to be dict, got {type(result_raw)}')
result = MapValidator(result_raw)
networks = result.get(list, "networks", [])
network_states: list[NetworkState] = []
for n in networks:
network = MapValidator(n)
state = network.get(str, "state")
status = network.get(str, "status", None)
if status is None:
status = DisconnectStatus.CONNECTION_STOPPED
id = MapValidator(network.get(dict, "id"))
ssid = id.get(str, "ssid")
security_type = id.get(str, "type_")
network_states.append(
NetworkState(
network_identifier=NetworkIdentifier(
ssid=ssid,
security_type=SecurityType(security_type.lower()),
),
connection_state=ConnectionState(state),
disconnect_status=DisconnectStatus(status),
)
)
client_state = result.get(str, "state")
return ClientStateSummary(
state=WlanClientState(client_state), networks=network_states
)
def remove_all_networks(self) -> None:
"""Deletes all saved networks on the device.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.remove_all_networks()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.REMOVE_ALL_NETWORKS)
self._check_response_error(Command.REMOVE_ALL_NETWORKS, resp)
def remove_network(
self,
target_ssid: str,
security_type: SecurityType,
target_pwd: str | None = None,
) -> None:
"""Removes or "forgets" a network from saved networks.
Args:
target_ssid: The network to remove.
security_type: The security protocol of the network.
target_pwd: The credential being saved with the network. No password
is equivalent to an empty string.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
if not target_pwd:
target_pwd = ""
method_params = {
"target_ssid": target_ssid,
"security_type": str(security_type),
"target_pwd": target_pwd,
}
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.remove_network(
target_ssid, security_type, target_pwd
)
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.REMOVE_NETWORK, method_params)
self._check_response_error(Command.REMOVE_NETWORK, resp)
def save_network(
self,
target_ssid: str,
security_type: SecurityType | FuchsiaSecurityType,
target_pwd: str | None = None,
) -> None:
"""Saves a network to the device.
Args:
target_ssid: The network to save.
security_type: The security protocol of the network.
target_pwd: The credential being saved with the network. No password
is equivalent to an empty string.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
# TODO(b/308807691): Change other uses of FuchsiaSecurityType to Honeydew's
# SecurityType
hd_security_type = SecurityType(security_type.value)
if not target_pwd:
target_pwd = ""
method_params = {
"target_ssid": target_ssid,
"security_type": str(hd_security_type.value),
"target_pwd": target_pwd,
}
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.save_network(
target_ssid, hd_security_type, target_pwd
)
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.SAVE_NETWORK, method_params)
self._check_response_error(Command.SAVE_NETWORK, resp)
def scan_for_networks(self) -> list[str]:
"""Scans for networks.
Returns:
A list of network SSIDs that can be connected to.
Raises:
WlanPolicyError: Sl4f run command failed.
TypeError: Return value not a list.
"""
if self.honeydew_fd:
try:
return self.honeydew_fd.wlan_policy.scan_for_network()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.SCAN_FOR_NETWORKS)
result = self._check_response_error(Command.SCAN_FOR_NETWORKS, resp)
if not isinstance(result, list):
raise TypeError(f'Expected "result" to be list, got {type(result)}')
return result
def set_new_update_listener(self) -> None:
"""Sets the update listener stream of the facade to a new stream.
This causes updates to be reset. Intended to be used between tests so
that the behaviour of updates in a test is independent from previous
tests.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.set_new_update_listener()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.SET_NEW_UPDATE_LISTENER)
self._check_response_error(Command.SET_NEW_UPDATE_LISTENER, resp)
def start_client_connections(self) -> None:
"""Enables device to initiate connections to networks.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.start_client_connections()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.START_CLIENT_CONNECTIONS)
self._check_response_error(Command.START_CLIENT_CONNECTIONS, resp)
def stop_client_connections(self) -> None:
"""Disables device for initiating connections to networks.
Raises:
WlanPolicyError: Sl4f run command failed.
"""
if self.honeydew_fd:
try:
self.honeydew_fd.wlan_policy.stop_client_connections()
except errors.Sl4fError as e:
raise WlanPolicyError from e
else:
resp = self.send_command(Command.STOP_CLIENT_CONNECTIONS)
self._check_response_error(Command.STOP_CLIENT_CONNECTIONS, resp)