1# Copyright (C) 2024 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Constants for Nearby Connection.""" 16 17import ast 18import dataclasses 19import datetime 20import enum 21import logging 22from typing import Any 23 24SUCCESS_RATE_TARGET = 0.98 25MCC_PERFORMANCE_TEST_COUNT = 100 26MCC_PERFORMANCE_TEST_MAX_CONSECUTIVE_ERROR = 5 27SCC_PERFORMANCE_TEST_COUNT = 10 28SCC_PERFORMANCE_TEST_MAX_CONSECUTIVE_ERROR = 2 29BT_PERFORMANCE_TEST_COUNT = 100 30BT_PERFORMANCE_TEST_MAX_CONSECUTIVE_ERROR = 5 31 32TARGET_POST_WIFI_CONNECTION_IDLE_TIME_SEC = 10 33 34NEARBY_RESET_WAIT_TIME = datetime.timedelta(seconds=2) 35WIFI_DISCONNECTION_DELAY = datetime.timedelta(seconds=1) 36 37FIRST_DISCOVERY_TIMEOUT = datetime.timedelta(seconds=30) 38FIRST_CONNECTION_INIT_TIMEOUT = datetime.timedelta(seconds=30) 39FIRST_CONNECTION_RESULT_TIMEOUT = datetime.timedelta(seconds=35) 40BT_1K_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=20) 41BT_500K_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=25) 42BLE_500K_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=25) 43SECOND_DISCOVERY_TIMEOUT = datetime.timedelta(seconds=35) 44SECOND_CONNECTION_INIT_TIMEOUT = datetime.timedelta(seconds=10) 45SECOND_CONNECTION_RESULT_TIMEOUT = datetime.timedelta(seconds=25) 46CONNECTION_BANDWIDTH_CHANGED_TIMEOUT = datetime.timedelta(seconds=25) 47WIFI_1K_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=20) 48WIFI_2G_20M_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=20) 49WIFI_200M_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=100) 50WIFI_500M_PAYLOAD_TRANSFER_TIMEOUT = datetime.timedelta(seconds=250) 51WIFI_STA_CONNECTING_TIME_OUT = datetime.timedelta(seconds=25) 52DISCONNECTION_TIMEOUT = datetime.timedelta(seconds=15) 53 54MAX_PHY_RATE_PER_STREAM_AC_80_MBPS = 433 55MAX_PHY_RATE_PER_STREAM_AC_40_MBPS = 200 56MAX_PHY_RATE_PER_STREAM_N_20_MBPS = 72 57 58MCC_THROUGHPUT_MULTIPLIER = 0.25 59MAX_PHY_RATE_TO_MIN_THROUGHPUT_RATIO_5G = 0.37 60MAX_PHY_RATE_TO_MIN_THROUGHPUT_RATIO_2G = 0.10 61WLAN_THROUGHPUT_CAP_MBPS = 20 # cap for WLAN medium due to encryption overhead 62 63CLASSIC_BT_MEDIUM_THROUGHPUT_BENCHMARK_MBPS = 0.02 64BLE_MEDIUM_THROUGHPUT_BENCHMARK_MBPS = 0.02 65 66KEEP_ALIVE_TIMEOUT_BT_MS = 30000 67KEEP_ALIVE_INTERVAL_BT_MS = 5000 68 69KEEP_ALIVE_TIMEOUT_WIFI_MS = 10000 70KEEP_ALIVE_INTERVAL_WIFI_MS = 3000 71 72PERCENTILE_50_FACTOR = 0.5 73LATENCY_PRECISION_DIGITS = 1 74 75UNSET_LATENCY = datetime.timedelta.max 76UNSET_THROUGHPUT_KBPS = -1.0 77MAX_NUM_BUG_REPORT = 5 78INVALID_INT = -1 79 80TRANSFER_FILE_SIZE_500MB = 500 * 1024 # kB 81TRANSFER_FILE_SIZE_200MB = 200 * 1024 # kB 82TRANSFER_FILE_SIZE_20MB = 20 * 1024 # kB 83TRANSFER_FILE_SIZE_1MB = 1024 # kB 84TRANSFER_FILE_SIZE_500KB = 512 # kB 85TRANSFER_FILE_SIZE_1KB = 1 # kB 86 87TARGET_CUJ_QUICK_START = 'quick_start' 88TARGET_CUJ_ESIM = 'setting_based_esim_transfer' 89TARGET_CUJ_QUICK_SHARE = 'quick_share' 90 91 92@enum.unique 93class PayloadType(enum.IntEnum): 94 FILE = 2 95 STREAM = 3 96 97 98@enum.unique 99class NearbyMedium(enum.IntEnum): 100 """Medium options for discovery, advertising, connection and upgrade.""" 101 102 AUTO = 0 103 BT_ONLY = 1 104 BLE_ONLY = 2 105 WIFILAN_ONLY = 3 106 WIFIAWARE_ONLY = 4 107 UPGRADE_TO_WEBRTC = 5 108 UPGRADE_TO_WIFIHOTSPOT = 6 109 UPGRADE_TO_WIFIDIRECT = 7 110 BLE_L2CAP_ONLY = 8 111 # including WIFI_LAN, WIFI_HOTSPOT, WIFI_DIRECT 112 UPGRADE_TO_ALL_WIFI = 9 113 114 115@dataclasses.dataclass(frozen=False) 116class TestParameters: 117 """Test parameters to be customized for Nearby Connection.""" 118 119 target_cuj_name: str = 'unspecified' 120 requires_bt_multiplex: bool = False 121 requires_3p_api_test: bool = False 122 abort_all_tests_on_function_tests_fail: bool = True 123 fast_fail_on_any_error: bool = False 124 use_auto_controlled_wifi_ap: bool = False 125 wifi_2g_ssid: str = '' 126 wifi_2g_password: str = '' 127 wifi_5g_ssid: str = '' 128 wifi_5g_password: str = '' 129 wifi_dfs_5g_ssid: str = '' 130 wifi_dfs_5g_password: str = '' 131 wifi_ssid: str = '' # optional, for tests which can use any wifi 132 wifi_password: str = '' 133 advertising_discovery_medium: NearbyMedium = NearbyMedium.BLE_ONLY 134 connection_medium: NearbyMedium = NearbyMedium.BT_ONLY 135 toggle_airplane_mode_target_side: bool = False 136 reset_wifi_connection: bool = True 137 disconnect_bt_after_test: bool = False 138 disconnect_wifi_after_test: bool = False 139 payload_type: PayloadType = PayloadType.FILE 140 allow_unrooted_device: bool = False 141 keep_alive_timeout_ms: int = KEEP_ALIVE_TIMEOUT_WIFI_MS 142 keep_alive_interval_ms: int = KEEP_ALIVE_INTERVAL_WIFI_MS 143 enable_2g_ble_scan_throttling: bool = True 144 target_post_wifi_connection_idle_time_sec: int = ( 145 TARGET_POST_WIFI_CONNECTION_IDLE_TIME_SEC 146 ) 147 148 run_function_tests_with_performance_tests: bool = True 149 run_bt_performance_test: bool = True 150 run_ble_performance_test: bool = False 151 run_bt_coex_test: bool = True 152 run_directed_test: bool = True 153 run_compound_test: bool = True 154 run_aware_test: bool = False 155 run_iperf_test: bool = True 156 run_nearby_connections_function_tests: bool = False 157 skip_test_if_wifi_chipset_is_empty: bool = True 158 skip_bug_report: bool = False 159 160 @classmethod 161 def from_user_params( 162 cls, 163 user_params: dict[str, Any]) -> 'TestParameters': 164 """convert the parameters from the testbed to the test parameter.""" 165 166 # Convert G3 user int parameter in str format to int. 167 for key, value in user_params.items(): 168 if key == 'mh_files': 169 continue 170 logging.info('[Test Parameters] %s: %s', key, value) 171 if value in ('true', 'True'): 172 user_params[key] = True 173 elif value in ('false', 'False'): 174 user_params[key] = False 175 elif isinstance(value, bool): 176 user_params[key] = value 177 elif isinstance(value, str) and value.isdigit(): 178 user_params[key] = ast.literal_eval(value) 179 180 test_parameters_names = { 181 field.name for field in dataclasses.fields(cls) 182 } 183 test_parameters = cls( 184 **{ 185 key: val 186 for key, val in user_params.items() 187 if key in test_parameters_names 188 } 189 ) 190 191 if test_parameters.target_cuj_name == TARGET_CUJ_QUICK_START: 192 test_parameters.requires_bt_multiplex = True 193 194 return test_parameters 195 196 197@enum.unique 198class NearbyConnectionMedium(enum.IntEnum): 199 """The final connection medium selected, see BandWidthInfo.Medium.""" 200 UNKNOWN = 0 201 # reserved 1, it's Medium.MDNS, not used now 202 BLUETOOTH = 2 203 WIFI_HOTSPOT = 3 204 BLE = 4 205 WIFI_LAN = 5 206 WIFI_AWARE = 6 207 NFC = 7 208 WIFI_DIRECT = 8 209 WEB_RTC = 9 210 # 10 is reserved. 211 USB = 11 212 213 214def is_high_quality_medium(medium: NearbyMedium) -> bool: 215 return medium in { 216 NearbyMedium.WIFILAN_ONLY, 217 NearbyMedium.WIFIAWARE_ONLY, 218 NearbyMedium.UPGRADE_TO_WEBRTC, 219 NearbyMedium.UPGRADE_TO_WIFIHOTSPOT, 220 NearbyMedium.UPGRADE_TO_WIFIDIRECT, 221 NearbyMedium.UPGRADE_TO_ALL_WIFI, 222 } 223 224 225@enum.unique 226class MediumUpgradeType(enum.IntEnum): 227 DEFAULT = 0 228 DISRUPTIVE = 1 229 NON_DISRUPTIVE = 2 230 231 232@enum.unique 233class WifiD2DType(enum.IntEnum): 234 SCC_2G = 0 235 SCC_5G = 1 236 MCC_2G_WFD_5G_STA = 2 237 MCC_2G_WFD_5G_INDOOR_STA = 3 238 MCC_5G_WFD_5G_DFS_STA = 4 239 MCC_5G_HS_5G_DFS_STA = 5 240 241 242@enum.unique 243class SingleTestFailureReason(enum.IntEnum): 244 """The failure reasons for a nearby connect connection test.""" 245 UNINITIALIZED = 0 246 SOURCE_START_DISCOVERY = 1 247 TARGET_START_ADVERTISING = 2 248 SOURCE_REQUEST_CONNECTION = 3 249 TARGET_ACCEPT_CONNECTION = 4 250 WIFI_MEDIUM_UPGRADE = 5 251 FILE_TRANSFER_FAIL = 6 252 FILE_TRANSFER_THROUGHPUT_LOW = 7 253 SOURCE_WIFI_CONNECTION = 8 254 TARGET_WIFI_CONNECTION = 9 255 AP_IS_NOT_CONFIGURED = 10 256 DISCONNECTED_FROM_AP = 11 257 WRONG_AP_FREQUENCY = 12 258 WRONG_P2P_FREQUENCY = 13 259 DEVICE_CONFIG_ERROR = 14 260 SUCCESS = 15 261 262 263COMMON_WIFI_CONNECTION_FAILURE_REASONS = ( 264 ' 1) Check if the wifi ssid or password is correct;\n', 265 ' 2) Try to remove any saved wifi network from wifi settings;\n', 266 ' 3) Check if other device can connect to the same AP\n', 267 ' 4) Check the wifi connection related log on the device.\n', 268) 269 270COMMON_TRIAGE_TIP: dict[SingleTestFailureReason, str] = { 271 SingleTestFailureReason.UNINITIALIZED: ( 272 'not executed, the whole test was exited earlier; the devices may be' 273 ' disconnected from the host, abnormal things, such as system crash, ' 274 ' mobly snippet was killed; Or something wrong with the script, check' 275 ' the test running log and the corresponding bugreport log.' 276 ), 277 SingleTestFailureReason.SUCCESS: 'success!', 278 SingleTestFailureReason.SOURCE_START_DISCOVERY: ( 279 'The source device fails to discover the target device.' 280 ), 281 SingleTestFailureReason.TARGET_START_ADVERTISING: ( 282 'The target device can not start advertising.' 283 ), 284 SingleTestFailureReason.SOURCE_REQUEST_CONNECTION: ( 285 'The source device fails to connect to the target device' 286 ), 287 SingleTestFailureReason.TARGET_ACCEPT_CONNECTION: ( 288 'The target device fails to accept the connection.' 289 ), 290 SingleTestFailureReason.SOURCE_WIFI_CONNECTION: ( 291 'The source device can not connect to the wifi AP.\n' 292 f'{COMMON_WIFI_CONNECTION_FAILURE_REASONS}' 293 ), 294 SingleTestFailureReason.TARGET_WIFI_CONNECTION: ( 295 'The target device can not connect to the wifi AP.\n' 296 f'{COMMON_WIFI_CONNECTION_FAILURE_REASONS}' 297 ), 298 SingleTestFailureReason.AP_IS_NOT_CONFIGURED: ( 299 'The test AP is not set correctly in the test configuration file.' 300 ), 301 SingleTestFailureReason.DISCONNECTED_FROM_AP: ( 302 'The STA is disconnected from the AP. Check AP DHCP config. Check if' 303 ' other devices can connect to the same AP.' 304 ), 305 SingleTestFailureReason.WRONG_AP_FREQUENCY: ( 306 'Check if the test AP is set to the expected frequency.' 307 ), 308 SingleTestFailureReason.WRONG_P2P_FREQUENCY: '\n'.join([ 309 'The test P2P frequency is not set to the expected value.', 310 ' Check if device capabilities are set correctly in the config file.', 311 ' If it is SCC DBS test case, check if the device does support DBS;', 312 ( 313 ' If it is the SCC indoor or DFS test case, check if the device' 314 ' does support indoor/DFS channels in WFD mode;' 315 ), 316 ( 317 ' If it is a MCC test, check if devices actually supports DBS,' 318 ' indoor or DFS feature and set device capabilities correctly.' 319 ), 320 ]), 321 SingleTestFailureReason.DEVICE_CONFIG_ERROR: ( 322 'Check if device capabilities are set correctly in the config file.' 323 ), 324} 325 326COMMON_WFD_UPGRADE_FAILURE_REASONS = '\n'.join([ 327 'If WFD GO fails to start, check your factory build to ensure that', 328 ( 329 ' 1) includes the wpa_supplicant patch to avoid scan before starting GO' 330 ' https://w1.fi/cgit/hostap/commit/?id=b18d95759375834b6ca6f864c898f27d161b14ca.' 331 ), 332 ( 333 ' 2) includes WiFi mainline module 34.11.10.06.0 or later version which' 334 ' fixes the out-of-order message issue between P2P and tethering' 335 ' modules' 336 ), 337 ( 338 ' 3) HAL getUsableChannels() returns the correct channel list. Run "adb' 339 ' shell cmd wifi get-allowed-channel" and ensure it does not include' 340 ' DFS channels unless config_wifiEnableStaDfsChannelForPeerNetwork is' 341 ' set to true. DFS channels can be found from' 342 ' https://en.wikipedia.org/wiki/List_of_WLAN_channels.' 343 ), 344 ( 345 'Also check if BT socket is still connected and read/write is normal' 346 ' when the upgrade failure happens' 347 ), 348]) 349 350MEDIUM_UPGRADE_FAIL_TRIAGE_TIPS: dict[NearbyMedium, str] = { 351 NearbyMedium.WIFILAN_ONLY: ( 352 ' WLAN, check if AP blocks the mDNS traffic. Check if STA is connected' 353 ' to AP during WiFi upgrade.' 354 ), 355 NearbyMedium.UPGRADE_TO_WIFIHOTSPOT: ( 356 ' HOTSPOT, check the related wifip2p and NearbyConnections logs to see' 357 ' if the WFD group owner fails to start on' 358 ' the target side or the STA fails to connect on the source side.\n' 359 f' {COMMON_WFD_UPGRADE_FAILURE_REASONS}' 360 ), 361 NearbyMedium.UPGRADE_TO_WIFIDIRECT: ( 362 ' WFD, check the related wifip2p and NearbyConnections logs if the WFD' 363 ' group owner fails to start on the target side or WFD group client' 364 ' fails to connect on the source side. \n' 365 f' {COMMON_WFD_UPGRADE_FAILURE_REASONS}' 366 ), 367 NearbyMedium.UPGRADE_TO_ALL_WIFI: ( 368 ' all WiFI mediums, check NearbyConnections logs to see if WFD, WLAN' 369 ' and HOTSPOT mediums are tried and if the failure is on the target or' 370 ' source side. Check directed test results to see which medium fails.' 371 ), 372} 373 374 375@dataclasses.dataclass(frozen=True) 376class ConnectionSetupTimeouts: 377 """The timeouts of the nearby connection setup.""" 378 discovery_timeout: datetime.timedelta | None = None 379 connection_init_timeout: datetime.timedelta | None = None 380 connection_result_timeout: datetime.timedelta | None = None 381 382 383@dataclasses.dataclass(frozen=False) 384class ConnectionSetupQualityInfo: 385 """The quality information of the nearby connection setup.""" 386 discovery_latency: datetime.timedelta = UNSET_LATENCY 387 connection_latency: datetime.timedelta = UNSET_LATENCY 388 medium_upgrade_latency: datetime.timedelta = UNSET_LATENCY 389 medium_upgrade_expected: bool = False 390 upgrade_medium: NearbyConnectionMedium | None = None 391 medium_frequency: int = INVALID_INT 392 393 def get_dict(self) -> dict[str, str]: 394 dict_repr = { 395 'discovery': f'{round(self.discovery_latency.total_seconds(), 1)}s', 396 'connection': f'{round(self.connection_latency.total_seconds(), 1)}s' 397 } 398 if self.medium_upgrade_expected: 399 dict_repr['upgrade'] = ( 400 f'{round(self.medium_upgrade_latency.total_seconds(), 1)}s' 401 ) 402 if self.upgrade_medium: 403 dict_repr['medium'] = self.upgrade_medium.name 404 return dict_repr 405 406 def get_medium_name(self) -> str: 407 if self.upgrade_medium: 408 return self.upgrade_medium.name 409 return 'na' 410 411 412@dataclasses.dataclass(frozen=False) 413class SingleTestResult: 414 """The test result of a single iteration.""" 415 416 test_iteration: int = 0 417 is_failed_with_prior_bt: bool = False 418 failure_reason: SingleTestFailureReason = ( 419 SingleTestFailureReason.UNINITIALIZED 420 ) 421 result_message: str = '' 422 prior_nc_quality_info: ConnectionSetupQualityInfo = dataclasses.field( 423 default_factory=ConnectionSetupQualityInfo 424 ) 425 discoverer_sta_latency: datetime.timedelta = UNSET_LATENCY 426 quality_info: ConnectionSetupQualityInfo = ( 427 dataclasses.field(default_factory=ConnectionSetupQualityInfo) 428 ) 429 file_transfer_throughput_kbps: float = UNSET_THROUGHPUT_KBPS 430 iperf_throughput_kbps: float = UNSET_THROUGHPUT_KBPS 431 advertiser_sta_latency: datetime.timedelta = UNSET_LATENCY 432 discoverer_sta_expected: bool = False 433 advertiser_wifi_expected: bool = False 434 sta_frequency: int = INVALID_INT 435 max_sta_link_speed_mbps: int = INVALID_INT 436 start_time: datetime.datetime = datetime.datetime.now() 437 438 439@dataclasses.dataclass(frozen=False) 440class NcPerformanceTestMetrics: 441 """Metrics data for quick start test.""" 442 443 prior_bt_discovery_latencies: list[datetime.timedelta] = dataclasses.field( 444 default_factory=list[datetime.timedelta] 445 ) 446 prior_bt_connection_latencies: list[datetime.timedelta] = dataclasses.field( 447 default_factory=list[datetime.timedelta] 448 ) 449 discoverer_wifi_sta_latencies: list[datetime.timedelta] = dataclasses.field( 450 default_factory=list[datetime.timedelta] 451 ) 452 file_transfer_discovery_latencies: list[datetime.timedelta] = ( 453 dataclasses.field(default_factory=list[datetime.timedelta]) 454 ) 455 file_transfer_connection_latencies: list[datetime.timedelta] = ( 456 dataclasses.field(default_factory=list[datetime.timedelta]) 457 ) 458 medium_upgrade_latencies: list[datetime.timedelta] = dataclasses.field( 459 default_factory=list[datetime.timedelta]) 460 advertiser_wifi_sta_latencies: list[datetime.timedelta] = dataclasses.field( 461 default_factory=list[datetime.timedelta]) 462 file_transfer_throughputs_kbps: list[float] = dataclasses.field( 463 default_factory=list[float]) 464 iperf_throughputs_kbps: list[float] = dataclasses.field( 465 default_factory=list[float]) 466 upgraded_wifi_transfer_mediums: list[NearbyConnectionMedium] = ( 467 dataclasses.field(default_factory=list[NearbyConnectionMedium])) 468 469 470@dataclasses.dataclass(frozen=True) 471class TestResultStats: 472 """The test result stats.""" 473 success_count: int | None = None 474 min_val: float | None = None 475 median_val: float | None = None 476 max_val: float | None = None 477