1 2 3# Copyright (C) 2024 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"""Util for aware test.""" 17import base64 18import datetime 19import json 20import logging 21import time 22from typing import Any, Callable, Dict, List, Optional 23 24from aware import constants 25 26from mobly import asserts 27from mobly.controllers import android_device 28from mobly.controllers.android_device_lib import adb 29from mobly.controllers.android_device_lib import callback_handler_v2 30from mobly.snippet import callback_event 31from mobly.snippet import errors 32 33 34_WAIT_DOZE_MODE_IN_SEC = 5 35_TIMEOUT_INTERVAL_IN_SEC = 1 36_WAIT_WIFI_STATE_TIME_OUT = datetime.timedelta(seconds=10) 37_WAIT_TIME_SEC = 3 38_CONTROL_WIFI_TIMEOUT_SEC = 10 39_REQUEST_NETWORK_TIMEOUT_MS = 15 * 1000 40# arbitrary timeout for events 41_EVENT_TIMEOUT = 10 42 43# Alias variable. 44_CALLBACK_NAME = constants.DiscoverySessionCallbackParamsType.CALLBACK_NAME 45_DEFAULT_TIMEOUT = constants.WAIT_WIFI_STATE_TIME_OUT.total_seconds() 46_TRANSPORT_TYPE_WIFI_AWARE = ( 47 constants.NetworkCapabilities.Transport.TRANSPORT_WIFI_AWARE 48) 49# Definition for timeout and retries. 50_DEFAULT_TIMEOUT = constants.WAIT_WIFI_STATE_TIME_OUT.total_seconds() 51 52 53def callback_no_response( 54 callback: callback_handler_v2.CallbackHandlerV2, 55 event_name: str, 56 timeout: int = _WAIT_WIFI_STATE_TIME_OUT.total_seconds(), 57 use_callbackid: bool = False, 58 ): 59 """Makes a callback call and expects no response within a given timeout. 60 61 Args: 62 callback: Snippet callback object. 63 event_name: event name to wait. 64 timeout: Timeout in second. 65 use_callbackid: Using callbackid in eventname, default False. 66 67 Raises: 68 CallBackError: if receive response. 69 """ 70 if use_callbackid: 71 event_name += callback.callback_id 72 try: 73 data = callback.waitAndGet(event_name=event_name, timeout=timeout) 74 raise CallBackError(f' Unexpected response {data}') 75 except errors.CallbackHandlerTimeoutError: 76 return 77 78 79class CallBackError(Exception): 80 """Error raised when there is a problem to get callback response.""" 81 82def control_wifi( 83 ad: android_device.AndroidDevice, 84 wifi_state: bool, 85): 86 """Control Android Wi-Fi status. 87 88 Args: 89 ad: Android test device. 90 wifi_state: True if or Wi-Fi on False if Wi-Fi off. 91 timeout_seconds: Maximum wait time (seconds), default is 10 seconds. 92 93 Raises: 94 TimeoutError: If the Wi-Fi state cannot be set within the timeout (in seconds). 95 """ 96 if _check_wifi_status(ad) == wifi_state: 97 return 98 if wifi_state: 99 ad.adb.shell("svc wifi enable") 100 else: 101 ad.adb.shell("svc wifi disable") 102 start_time = time.time() 103 while True: 104 if _check_wifi_status(ad) == wifi_state: 105 return 106 # Check for timeout 107 if time.time() - start_time > _CONTROL_WIFI_TIMEOUT_SEC: 108 raise TimeoutError( 109 f"Failed to set Wi-Fi state to {wifi_state} within {_CONTROL_WIFI_TIMEOUT_SEC} seconds." 110 ) 111 112 time.sleep(1) # Wait for a second before checking again 113 114def _check_wifi_status(ad: android_device.AndroidDevice): 115 """Check Android Wi-Fi status. 116 117 Args: 118 ad: android device object. 119 120 Returns: 121 True if wifi on, False if wifi off. 122 """ 123 cmd = ad.adb.shell("cmd wifi status").decode("utf-8").strip() 124 first_line = cmd.split("\n")[0] 125 logging.info("device wifi status: %s", first_line) 126 if "enabled" in first_line: 127 return True 128 else: 129 return False 130 131 132def set_doze_mode(ad: android_device.AndroidDevice, state: bool) -> bool: 133 """Enables/Disables Android doze mode. 134 135 Args: 136 ad: android device object. 137 state: bool, True if intent to enable Android doze mode, False otherwise. 138 139 Returns: 140 True if doze mode is enabled, False otherwise. 141 142 Raises: 143 TimeoutError: If timeout is hit. 144 """ 145 if state: 146 ad.log.info("Enables Android doze mode") 147 _dumpsys(ad, "battery unplug") 148 _dumpsys(ad, "deviceidle enable") 149 _dumpsys(ad, "deviceidle force-idle") 150 time.sleep(_WAIT_DOZE_MODE_IN_SEC) 151 else: 152 ad.log.info("Disables Android doze mode") 153 _dumpsys(ad, "deviceidle disable") 154 _dumpsys(ad, "battery reset") 155 for _ in range(10 + 1): 156 adb_shell_result = _dumpsys(ad, "deviceidle get deep") 157 logging.info("dumpsys deviceidle get deep: %s", adb_shell_result) 158 if adb_shell_result.startswith(constants.DeviceidleState.IDLE.value): 159 return True 160 if adb_shell_result.startswith(constants.DeviceidleState.ACTIVE.value): 161 return False 162 time.sleep(_TIMEOUT_INTERVAL_IN_SEC) 163 # At this point, timeout must have occurred. 164 raise errors.CallbackHandlerTimeoutError( 165 ad, "Timed out after waiting for doze_mode set to {state}" 166 ) 167 168 169def _dumpsys(ad: android_device.AndroidDevice, command: str) -> str: 170 """Dumpsys device info. 171 172 Args: 173 ad: android device object. 174 command: adb command. 175 176 Returns: 177 Android dumsys info 178 """ 179 return ad.adb.shell(f"dumpsys {command}").decode().strip() 180 181 182def check_android_os_version( 183 ad: android_device.AndroidDevice, 184 operator_func: Callable[[Any, Any], bool], 185 android_version: constants.AndroidVersion, 186 ) -> bool: 187 """Compares device's Android OS version with the given one. 188 189 Args: 190 ad: Android devices. 191 operator_func: Operator used in the comparison. 192 android_version: The given Android OS version. 193 194 Returns: 195 bool: The comparison result. 196 """ 197 device_os_version = int(ad.adb.shell("getprop ro.build.version.release")) 198 result = False 199 if isinstance(operator_func, constants.Operator): 200 return operator_func.value(device_os_version, android_version) 201 return result 202 203 204def _get_airplane_mode(ad: android_device.AndroidDevice) -> bool: 205 """Gets the airplane mode. 206 207 Args: 208 ad: android device object. 209 210 Returns: 211 True if airplane mode On, False for Off. 212 """ 213 state = ad.adb.shell("settings get global airplane_mode_on") 214 return bool(int(state)) 215 216 217def set_airplane_mode(ad: android_device.AndroidDevice, state: bool): 218 """Sets the airplane mode to the given state. 219 220 Args: 221 ad: android device object. 222 state: bool, True for Airplane mode on, False for off. 223 """ 224 ad.adb.shell( 225 ["settings", "put", "global", "airplane_mode_on", str(int(state))] 226 ) 227 ad.adb.shell([ 228 "am", 229 "broadcast", 230 "-a", 231 "android.intent.action.AIRPLANE_MODE", 232 "--ez", 233 "state", 234 str(state), 235 ]) 236 start_time = time.time() 237 while _get_airplane_mode(ad) != state: 238 time.sleep(_TIMEOUT_INTERVAL_IN_SEC) 239 asserts.assert_greater( 240 time.time() - start_time > _WAIT_TIME_SEC, 241 f"Failed to set airplane mode to: {state}", 242 ) 243 244 245def decode_list(list_of_b64_strings: List[str]) -> List[bytes]: 246 """Converts the list of b64 encoded strings to a list of bytearray. 247 248 Args: 249 list_of_b64_strings: A list of strings, each of which is b64 encoded array. 250 251 Returns: 252 A list of bytearrays. 253 """ 254 decoded_list = [] 255 for string_item in list_of_b64_strings: 256 decoded_list.append(base64.b64decode(string_item)) 257 return decoded_list 258 259 260def encode_list( 261 list_of_objects: List[Any]) -> List[str]: 262 """Converts a list of strings/bytearrays to a list of b64 encoded bytearrays. 263 264 A None object is treated as a zero-length bytearray. 265 266 Args: 267 list_of_objects: A list of strings or bytearray objects. 268 Returns: 269 A list of the same objects, converted to bytes and b64 encoded. 270 """ 271 encoded_list = [] 272 for obj in list_of_objects: 273 if obj is None: 274 obj = bytes() 275 if isinstance(obj, str): 276 encoded_list.append(base64.b64encode(bytes(obj, "utf-8")).decode("utf-8")) 277 else: 278 encoded_list.append(base64.b64encode(bytes(obj)).decode("utf-8")) 279 return encoded_list 280 281 282def construct_max_match_filter(max_size: int)-> List[bytes]: 283 """Constructs a maximum size match filter that fits into the 'max_size' bytes. 284 285 Match filters are a set of LVs (Length, Value pairs) where L is 1 byte. The 286 maximum size match filter will contain max_size/2 LVs with all Vs (except 287 possibly the last one) of 1 byte, the last V may be 2 bytes for odd max_size. 288 289 Args: 290 max_size: Maximum size of the match filter. 291 Returns: 292 A list of bytearrays. 293 """ 294 mf_list = [] 295 num_lvs = max_size // 2 296 for i in range(num_lvs - 1): 297 mf_list.append(bytes([i])) 298 if max_size % 2 == 0: 299 mf_list.append(bytes([255])) 300 else: 301 mf_list.append(bytes([254, 255])) 302 return mf_list 303 304 305def validate_forbidden_callbacks(ad: android_device.AndroidDevice, 306 limited_cb: Optional[Dict[str, int]] = None 307 ) -> None: 308 """Validate the specified callbacks have not been called more than permitted. 309 310 In addition to the input configuration also validates that forbidden callbacks 311 have never been called. 312 313 Args: 314 ad: Device on which to run. 315 limited_cb: Dictionary of CB_EV_* ids and maximum permitted calls (0 316 meaning never). 317 Raises: 318 CallBackError: If forbidden callbacks are triggered. 319 """ 320 cb_data = json.loads(ad.adb.shell("cmd wifiaware native_cb get_cb_count")) 321 if limited_cb is None: 322 limited_cb = {} 323 # Add callbacks which should never be called. 324 limited_cb["5"] = 0 325 fail = False 326 for cb_event in limited_cb.keys(): 327 if cb_event in cb_data: 328 if cb_data[cb_event] > limited_cb[cb_event]: 329 fail = True 330 ad.log.info( 331 "Callback %s observed %d times: more than permitted %d times", 332 cb_event, cb_data[cb_event], limited_cb[cb_event]) 333 break 334 if fail: 335 raise CallBackError("Forbidden callbacks observed.") 336 337 338def reset_device_parameters(ad: android_device.AndroidDevice): 339 """Reset device configurations which may have been set by tests. 340 Should be done before tests start (in case previous one was killed 341 without tearing down) and after they end (to leave device in usable 342 state). 343 344 Args: 345 ad: device to be reset 346 """ 347 ad.adb.shell("cmd wifiaware reset") 348 349def aware_cap_str_to_dict(cap_string:str) -> dict: 350 idx = cap_string.find('[maxConcurrentAwareClusters') 351 # Remove the braces from the string. 352 new_string = cap_string[idx:-1].strip('[]') 353 # split the string into key-value pairs 354 pairs = new_string.split(', ') 355 # Converting the values to integer or bool into dictionary 356 capabilities = {} 357 for pair in pairs: 358 key, value = pair.split('=') 359 try: 360 capabilities[key] = int(value) 361 except ValueError: 362 capabilities[key] = bool(value) 363 return capabilities 364 365 366def reset_device_statistics(ad: android_device.AndroidDevice,): 367 """Reset device statistics. 368 369 Args: 370 ad: device to be reset 371 """ 372 ad.adb.shell("cmd wifiaware native_cb get_cb_count --reset") 373 374def get_aware_capabilities(ad: android_device.AndroidDevice): 375 """Get the Wi-Fi Aware capabilities from the specified device. The 376 capabilities are a dictionary keyed by aware_const.CAP_* keys. 377 378 Args: 379 ad: the Android device 380 Returns: the capability dictionary. 381 """ 382 try: 383 result = ad.adb.shell('cmd wifiaware state_mgr get_capabilities') 384 return json.loads(result) 385 except adb.AdbError: 386 ad.log.info('Another way to get capabilities- dumpsys and parse string.') 387 result = ad.adb.shell('dumpsys wifiaware |grep mCapabilities').decode() 388 pairs = aware_cap_str_to_dict(result) 389 ad.log.info(pairs) 390 return pairs 391 392 393def create_discovery_config(service_name, 394 p_type=None, 395 s_type=None, 396 ssi=None, 397 match_filter=None, 398 match_filter_list=None, 399 ttl=0, 400 term_cb_enable=True, 401 instant_mode=None): 402 """Create a publish discovery configuration based on input parameters. 403 404 Args: 405 service_name: Service name - required 406 d_type: Discovery type (publish or subscribe constants) 407 ssi: Supplemental information - defaults to None 408 match_filter, match_filter_list: The match_filter, only one mechanism can 409 be used to specify. Defaults to None. 410 ttl: Time-to-live - defaults to 0 (i.e. non-self terminating) 411 term_cb_enable: True (default) to enable callback on termination, False 412 means that no callback is called when session terminates. 413 instant_mode: set the band to use instant communication mode, 2G or 5G 414 Returns: 415 publish discovery configuration object. 416 """ 417 config = {} 418 config[constants.SERVICE_NAME] = service_name 419 if p_type is not None: 420 config[constants.PUBLISH_TYPE] = p_type 421 if s_type is not None: 422 config[constants.SUBSCRIBE_TYPE] = s_type 423 if ssi is not None: 424 config[constants.SERVICE_SPECIFIC_INFO] = ssi 425 if match_filter is not None: 426 config[constants.MATCH_FILTER] = match_filter 427 if match_filter_list is not None: 428 config[constants.MATCH_FILTER_LIST] = match_filter_list 429 if instant_mode is not None: 430 config[constants.INSTANTMODE_ENABLE] = instant_mode 431 config[constants.TTL_SEC] = ttl 432 config[constants.TERMINATE_NOTIFICATION_ENABLED] = term_cb_enable 433 return config 434 435def start_attach( 436 ad: android_device.AndroidDevice, 437 is_ranging_enabled: bool = True, 438) -> str: 439 """Starts the attach process on the provided device.""" 440 attach_handler = ad.wifi_aware_snippet.wifiAwareAttached( 441 is_ranging_enabled 442 ) 443 attach_event = attach_handler.waitAndGet( 444 event_name=constants.AttachCallBackMethodType.ATTACHED, 445 timeout=_DEFAULT_TIMEOUT, 446 ) 447 asserts.assert_true( 448 ad.wifi_aware_snippet.wifiAwareIsSessionAttached( 449 attach_event.callback_id 450 ), 451 f'{ad} attach succeeded, but Wi-Fi Aware session is still null.', 452 ) 453 mac_address = None 454 if is_ranging_enabled: 455 identity_changed_event = attach_handler.waitAndGet( 456 event_name=constants.AttachCallBackMethodType.ID_CHANGED, 457 timeout=_DEFAULT_TIMEOUT, 458 ) 459 mac_address = identity_changed_event.data.get('mac', None) 460 asserts.assert_true(bool(mac_address), 'Mac address should not be empty') 461 ad.log.info('Attach Wi-Fi Aware session succeeded.') 462 return attach_event.callback_id, mac_address 463 464def create_discovery_pair( 465 p_dut: android_device.AndroidDevice, 466 s_dut: android_device.AndroidDevice, 467 p_config: dict[str, any], 468 s_config: dict[str, any], 469 device_startup_delay: int=1, 470 msg_id=None, 471): 472 """Creates a discovery session (publish and subscribe), and pair each other. 473 474 wait for service discovery - at that point the sessions are connected and 475 ready for further messaging of data-path setup. 476 477 Args: 478 p_dut: Device to use as publisher. 479 s_dut: Device to use as subscriber. 480 p_config: Publish configuration. 481 s_config: Subscribe configuration. 482 device_startup_delay: Number of seconds to offset the enabling of NAN 483 on the two devices. 484 msg_id: Controls whether a message is sent from Subscriber to Publisher 485 (so that publisher has the sub's peer ID). If None then not sent, 486 otherwise should be an int for the message id. 487 488 Returns: 489 variable size list of: 490 p_id: Publisher attach session id 491 s_id: Subscriber attach session id 492 p_disc_id: Publisher discovery session id 493 s_disc_id: Subscriber discovery session id 494 peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber 495 peer_id_on_pub: Peer ID of the Subscriber as seen on the Publisher. Only 496 included if |msg_id| is not None. 497 """ 498 # attach and wait for confirmation 499 p_id, _ = start_attach(p_dut) 500 time.sleep(device_startup_delay) 501 s_id, _ = start_attach(s_dut) 502 p_disc_id = p_dut.wifi_aware_snippet.wifiAwarePublish( 503 p_id, p_config 504 ) 505 p_dut.log.info('Created the publish session.') 506 p_discovery = p_disc_id.waitAndGet( 507 constants.DiscoverySessionCallbackMethodType.DISCOVER_RESULT) 508 callback_name = p_discovery.data[_CALLBACK_NAME] 509 asserts.assert_equal( 510 constants.DiscoverySessionCallbackMethodType.PUBLISH_STARTED, 511 callback_name, 512 f'{p_dut} publish failed, got callback: {callback_name}.', 513 ) 514 time.sleep(device_startup_delay) 515 # Subscriber: start subscribe and wait for confirmation 516 s_disc_id = s_dut.wifi_aware_snippet.wifiAwareSubscribe( 517 s_id, s_config 518 ) 519 s_dut.log.info('Created the subscribe session.') 520 s_discovery = s_disc_id.waitAndGet( 521 constants.DiscoverySessionCallbackMethodType.DISCOVER_RESULT) 522 callback_name = s_discovery.data[_CALLBACK_NAME] 523 asserts.assert_equal( 524 constants.DiscoverySessionCallbackMethodType.SUBSCRIBE_STARTED, 525 callback_name, 526 f'{s_dut} subscribe failed, got callback: {callback_name}.', 527 ) 528 # Subscriber: wait for service discovery 529 discovery_event = s_disc_id.waitAndGet( 530 constants.DiscoverySessionCallbackMethodType.SERVICE_DISCOVERED) 531 peer_id_on_sub = discovery_event.data[ 532 constants.WifiAwareSnippetParams.PEER_ID 533 ] 534 # Optionally send a message from Subscriber to Publisher 535 if msg_id is not None: 536 ping_msg = 'PING' 537 # Subscriber: send message to peer (Publisher) 538 s_dut.wifi_aware_snippet.wifiAwareSendMessage( 539 s_disc_id.callback_id, peer_id_on_sub, msg_id, ping_msg 540 ) 541 message_send_result = s_disc_id.waitAndGet( 542 event_name= 543 constants.DiscoverySessionCallbackMethodType.MESSAGE_SEND_RESULT, 544 timeout=_DEFAULT_TIMEOUT, 545 ) 546 actual_send_message_id = message_send_result.data[ 547 constants.DiscoverySessionCallbackParamsType.MESSAGE_ID 548 ] 549 asserts.assert_equal( 550 actual_send_message_id, 551 msg_id, 552 f'{s_dut} send message succeeded but message ID mismatched.' 553 ) 554 pub_rx_msg_event = p_disc_id.waitAndGet( 555 event_name= 556 constants.DiscoverySessionCallbackMethodType.MESSAGE_RECEIVED, 557 timeout=_DEFAULT_TIMEOUT, 558 ) 559 peer_id_on_pub = pub_rx_msg_event.data[ 560 constants.WifiAwareSnippetParams.PEER_ID 561 ] 562 received_message_raw = pub_rx_msg_event.data[ 563 constants.WifiAwareSnippetParams.RECEIVED_MESSAGE 564 ] 565 received_message = bytes(received_message_raw).decode('utf-8') 566 asserts.assert_equal( 567 received_message, 568 ping_msg, 569 f'{p_dut} Subscriber -> Publisher message corrupted.' 570 ) 571 return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub, peer_id_on_pub 572 return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub 573 574def request_network( 575 ad: android_device.AndroidDevice, 576 discovery_session: str, 577 peer: int, 578 net_work_request_id: str, 579 network_specifier_params: ( 580 constants.WifiAwareNetworkSpecifier | None 581 ) = None, 582 is_accept_any_peer: bool = False, 583) -> callback_handler_v2.CallbackHandlerV2: 584 """Requests and configures a Wi-Fi Aware network connection.""" 585 network_specifier_parcel = ( 586 ad.wifi_aware_snippet.wifiAwareCreateNetworkSpecifier( 587 discovery_session, 588 peer, 589 is_accept_any_peer, 590 network_specifier_params.to_dict() 591 if network_specifier_params 592 else None, 593 ) 594 ) 595 network_request_dict = constants.NetworkRequest( 596 transport_type=_TRANSPORT_TYPE_WIFI_AWARE, 597 network_specifier_parcel=network_specifier_parcel, 598 ).to_dict() 599 ad.log.debug('Requesting Wi-Fi Aware network: %r', network_request_dict) 600 return ad.wifi_aware_snippet.connectivityRequestNetwork( 601 net_work_request_id, network_request_dict, _REQUEST_NETWORK_TIMEOUT_MS 602 ) 603 604def wait_for_network( 605 ad: android_device.AndroidDevice, 606 request_network_cb_handler: callback_handler_v2.CallbackHandlerV2, 607 expected_channel: str | None = None, 608) -> callback_event.CallbackEvent: 609 """Waits for and verifies the establishment of a Wi-Fi Aware network.""" 610 network_callback_event = request_network_cb_handler.waitAndGet( 611 event_name=constants.NetworkCbEventName.NETWORK_CALLBACK, 612 timeout=_DEFAULT_TIMEOUT, 613 ) 614 callback_name = network_callback_event.data[_CALLBACK_NAME] 615 if callback_name == constants.NetworkCbName.ON_UNAVAILABLE: 616 asserts.fail( 617 f'{ad} failed to request the network, got callback {callback_name}.' 618 ) 619 elif callback_name == constants.NetworkCbName.ON_CAPABILITIES_CHANGED: 620 # `network` is the network whose capabilities have changed. 621 network = network_callback_event.data[constants.NetworkCbEventKey.NETWORK] 622 network_capabilities = network_callback_event.data[ 623 constants.NetworkCbEventKey.NETWORK_CAPABILITIES 624 ] 625 asserts.assert_true( 626 network and network_capabilities, 627 f'{ad} received a null Network or NetworkCapabilities!?.', 628 ) 629 transport_info_class_name = network_callback_event.data[ 630 constants.NetworkCbEventKey.TRANSPORT_INFO_CLASS_NAME 631 ] 632 ad.log.info(f'got class_name {transport_info_class_name}') 633 asserts.assert_equal( 634 transport_info_class_name, 635 constants.AWARE_NETWORK_INFO_CLASS_NAME, 636 f'{ad} network capabilities changes but it is not a WiFi Aware' 637 ' network.', 638 ) 639 if expected_channel: 640 mhz_list = network_callback_event.data[ 641 constants.NetworkCbEventKey.CHANNEL_IN_MHZ 642 ] 643 asserts.assert_equal( 644 mhz_list, 645 [expected_channel], 646 f'{ad} Channel freq is not match the request.', 647 ) 648 elif callback_name == constants.NetworkCbName.ON_PROPERTIES_CHANGED: 649 iface_name = network_callback_event.data[ 650 constants.NetworkCbEventKey.NETWORK_INTERFACE_NAME 651 ] 652 ad.log.info('interface name = %s', iface_name) 653 else: 654 asserts.fail( 655 f'{ad} got unknown request network callback {callback_name}.' 656 ) 657 return network_callback_event 658 659def wait_for_link( 660 ad: android_device.AndroidDevice, 661 request_network_cb_handler: callback_handler_v2.CallbackHandlerV2, 662) -> callback_event.CallbackEvent: 663 """Waits for and verifies the establishment of a Wi-Fi Aware network.""" 664 network_callback_event = request_network_cb_handler.waitAndGet( 665 event_name=constants.NetworkCbEventName.NETWORK_CALLBACK, 666 timeout=_DEFAULT_TIMEOUT, 667 ) 668 callback_name = network_callback_event.data[_CALLBACK_NAME] 669 if callback_name == constants.NetworkCbName.ON_UNAVAILABLE: 670 asserts.fail( 671 f'{ad} failed to request the network, got callback {callback_name}.' 672 ) 673 elif callback_name == constants.NetworkCbName.ON_PROPERTIES_CHANGED: 674 iface_name = network_callback_event.data[ 675 constants.NetworkCbEventKey.NETWORK_INTERFACE_NAME 676 ] 677 ad.log.info('interface name = %s', iface_name) 678 else: 679 asserts.fail( 680 f'{ad} got unknown request network callback {callback_name}.' 681 ) 682 ad.log.info('type = %s', type(network_callback_event)) 683 return network_callback_event 684 685 686def _wait_accept_success( 687 pub_accept_handler: callback_handler_v2.CallbackHandlerV2 688) -> None: 689 pub_accept_event = pub_accept_handler.waitAndGet( 690 event_name=constants.SnippetEventNames.SERVER_SOCKET_ACCEPT, 691 timeout=_DEFAULT_TIMEOUT 692 ) 693 is_accept = pub_accept_event.data.get(constants.SnippetEventParams.IS_ACCEPT, False) 694 if not is_accept: 695 error = pub_accept_event.data[constants.SnippetEventParams.ERROR] 696 asserts.fail( 697 f'Publisher failed to accept the connection. Error: {error}' 698 ) 699 700 701def _send_socket_msg( 702 sender_ad: android_device.AndroidDevice, 703 receiver_ad: android_device.AndroidDevice, 704 msg: str, 705 send_callback_id: str, 706 receiver_callback_id: str, 707): 708 """Sends a message from one device to another and verifies receipt.""" 709 is_write_socket = sender_ad.wifi_aware_snippet.connectivityWriteSocket( 710 send_callback_id, msg 711 ) 712 asserts.assert_true( 713 is_write_socket, 714 f'{sender_ad} Failed to write data to the socket.' 715 ) 716 sender_ad.log.info('Wrote data to the socket.') 717 # Verify received message 718 received_message = receiver_ad.wifi_aware_snippet.connectivityReadSocket( 719 receiver_callback_id, len(msg) 720 ) 721 asserts.assert_equal( 722 received_message, 723 msg, 724 f'{receiver_ad} received message mismatched.Failure:Expected {msg} but got ' 725 f'{received_message}.' 726 ) 727 receiver_ad.log.info('Read data from the socket.') 728 729 730def establish_socket_and_send_msg( 731 publisher: android_device.AndroidDevice, 732 subscriber: android_device.AndroidDevice, 733 pub_accept_handler: callback_handler_v2.CallbackHandlerV2, 734 network_id: str, 735 pub_local_port: int 736): 737 """Handles socket-based communication between publisher and subscriber.""" 738 # Init socket 739 # Create a ServerSocket and makes it listen for client connections. 740 subscriber.wifi_aware_snippet.connectivityCreateSocketOverWiFiAware( 741 network_id, pub_local_port 742 ) 743 _wait_accept_success(pub_accept_handler) 744 # Subscriber Send socket data 745 subscriber.log.info('Subscriber create a socket.') 746 _send_socket_msg( 747 sender_ad=subscriber, 748 receiver_ad=publisher, 749 msg=constants.WifiAwareTestConstants.MSG_CLIENT_TO_SERVER, 750 send_callback_id=network_id, 751 receiver_callback_id=network_id 752 ) 753 _send_socket_msg( 754 sender_ad=publisher, 755 receiver_ad=subscriber, 756 msg=constants.WifiAwareTestConstants.MSG_SERVER_TO_CLIENT, 757 send_callback_id=network_id, 758 receiver_callback_id=network_id 759 ) 760 publisher.wifi_aware_snippet.connectivityCloseWrite(network_id) 761 subscriber.wifi_aware_snippet.connectivityCloseWrite(network_id) 762 publisher.wifi_aware_snippet.connectivityCloseRead(network_id) 763 subscriber.wifi_aware_snippet.connectivityCloseRead(network_id) 764 logging.info('Communicated through socket connection of Wi-Fi Aware network successfully.') 765 766 767def run_ping6(dut: android_device.AndroidDevice, peer_ipv6: str): 768 """Run a ping6 over the specified device/link. 769 770 Args: 771 dut: Device on which to execute ping6. 772 peer_ipv6: Scoped IPv6 address of the peer to ping. 773 """ 774 cmd = 'ping6 -c 3 -W 5 %s' % peer_ipv6 775 try: 776 dut.log.info(cmd) 777 results = dut.adb.shell(cmd) 778 except adb.AdbError: 779 time.sleep(1) 780 dut.log.info('CMD RETRY: %s', cmd) 781 results = dut.adb.shell(cmd) 782 783 dut.log.info("cmd='%s' -> '%s'", cmd, results) 784 if not results: 785 asserts.fail("ping6 empty results - seems like a failure") 786