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"""Utility functions for interacting with the Wi-Fi Aware snippet RPCs.""" 15 16import datetime 17import random 18 19from aware import constants 20from mobly import asserts 21from mobly.controllers import android_device 22from mobly.controllers.android_device_lib import callback_handler_v2 23from mobly.snippet import callback_event 24 25 26_DEFAULT_TIMEOUT = constants.WAIT_WIFI_STATE_TIME_OUT.total_seconds() 27_REQUEST_NETWORK_TIMEOUT = datetime.timedelta(seconds=15) 28_CALLBACK_NAME = constants.DiscoverySessionCallbackParamsType.CALLBACK_NAME 29_IS_SESSION_INIT = constants.DiscoverySessionCallbackParamsType.IS_SESSION_INIT 30_TRANSPORT_TYPE_WIFI_AWARE = ( 31 constants.NetworkCapabilities.Transport.TRANSPORT_WIFI_AWARE 32) 33 34 35def start_attach( 36 ad: android_device.AndroidDevice, 37 is_ranging_enabled: bool, 38) -> (str, str): 39 """Starts the attach process on the Android device. 40 41 Args: 42 ad: The Android device controller. 43 is_ranging_enabled: Whether to enable ranging. 44 45 Returns: 46 A tuple of the attach session ID and the mac address of the aware 47 interface. The mac address will be None if ranging is disabled. 48 """ 49 attach_handler = ad.wifi.wifiAwareAttached(is_ranging_enabled) 50 attach_event = attach_handler.waitAndGet( 51 event_name=constants.AttachCallBackMethodType.ATTACHED, 52 timeout=_DEFAULT_TIMEOUT, 53 ) 54 asserts.assert_true( 55 ad.wifi.wifiAwareIsSessionAttached(attach_event.callback_id), 56 f'{ad} attach succeeded, but Wi-Fi Aware session is still null.', 57 ) 58 mac_address = None 59 if is_ranging_enabled: 60 identity_changed_event = attach_handler.waitAndGet( 61 event_name=constants.AttachCallBackMethodType.ID_CHANGED, 62 timeout=_DEFAULT_TIMEOUT, 63 ) 64 mac_address = identity_changed_event.data.get('mac', None) 65 asserts.assert_true( 66 bool(mac_address), 'Mac address should not be empty' 67 ) 68 ad.log.info( 69 'Attached Wi-Fi Aware session with ID %s and mac address %s.', 70 attach_event.callback_id, 71 mac_address, 72 ) 73 return attach_event.callback_id, mac_address 74 75 76def publish_and_subscribe( 77 publisher: android_device.AndroidDevice, 78 pub_config: constants.PublishConfig, 79 pub_attach_session: str, 80 subscriber: android_device.AndroidDevice, 81 sub_config: constants.SubscribeConfig, 82 sub_attach_session: str, 83) -> tuple[ 84 str, 85 callback_handler_v2.CallbackHandlerV2, 86 str, 87 callback_handler_v2.CallbackHandlerV2, 88 int, 89]: 90 """Creates discovery sessions and waits for service discovery. 91 92 This publishes a discovery session on the publisher, and subscribes to it 93 on the subscriber. After this method returns, the sessions are connected 94 and ready for further messaging. 95 96 Args: 97 publisher: The publisher. 98 pub_config: The publish disocvery session configuration. 99 pub_attach_session: The attach session ID of the publisher. 100 subscriber: The subscriber. 101 sub_config: The subscribe disocvery session configuration. 102 sub_attach_session: The attach session ID of the subscriber. 103 104 Returns: 105 A tuple of the publish session ID, the publish session handler, the 106 subscribe session ID, the subscribe session handler, and the peer ID 107 of the subscriber. 108 """ 109 # Initialize discovery sessions (publish and subscribe). 110 pub_session_handler, pub_session = _start_publish( 111 publisher=publisher, 112 attach_session_id=pub_attach_session, 113 pub_config=pub_config, 114 ) 115 sub_session_handler, sub_session = _start_subscribe( 116 subscriber=subscriber, 117 attach_session_id=sub_attach_session, 118 sub_config=sub_config, 119 ) 120 # Wait for discovery. 121 subscriber_peer = _wait_for_discovery( 122 subscriber, 123 sub_session_handler, 124 pub_service_specific_info=pub_config.service_specific_info, 125 is_ranging_enabled=pub_config.ranging_enabled, 126 ) 127 subscriber.log.info('The subscriber discovered the published service.') 128 return ( 129 pub_session, 130 pub_session_handler, 131 sub_session, 132 sub_session_handler, 133 subscriber_peer, 134 ) 135 136 137def _start_publish( 138 publisher: android_device.AndroidDevice, 139 attach_session_id: str, 140 pub_config: constants.PublishConfig, 141) -> tuple[callback_handler_v2.CallbackHandlerV2, str]: 142 """Starts a publish session on the publisher device. 143 144 Args: 145 publisher: The Android device controller of the publisher. 146 attach_session_id: The attach session ID of the publisher. 147 pub_config: The publish configuration. 148 149 Returns: 150 A tuple of the callback handler for the publish session and the 151 publish session ID. 152 """ 153 # Start the publish session. 154 publish_handler = publisher.wifi.wifiAwarePublish( 155 attach_session_id, pub_config.to_dict() 156 ) 157 158 # Wait for session start result. 159 discovery_event = publish_handler.waitAndGet( 160 event_name=constants.DiscoverySessionCallbackMethodType.DISCOVER_RESULT, 161 timeout=_DEFAULT_TIMEOUT, 162 ) 163 callback_name = discovery_event.data[_CALLBACK_NAME] 164 asserts.assert_equal( 165 constants.DiscoverySessionCallbackMethodType.PUBLISH_STARTED, 166 callback_name, 167 f'{publisher} publish failed, got callback: {callback_name}.', 168 ) 169 170 is_session_init = discovery_event.data[_IS_SESSION_INIT] 171 asserts.assert_true( 172 is_session_init, 173 f'{publisher} publish succeeded, but null discovery session returned.', 174 ) 175 publisher.log.info('Created the publish session.') 176 return publish_handler, publish_handler.callback_id 177 178 179def _start_subscribe( 180 subscriber: android_device.AndroidDevice, 181 attach_session_id: str, 182 sub_config: constants.SubscribeConfig, 183) -> tuple[callback_handler_v2.CallbackHandlerV2, str]: 184 """Starts a subscribe session on the subscriber device. 185 186 Args: 187 subscriber: The Android device controller of the subscriber. 188 attach_session_id: The attach session ID of the subscriber. 189 sub_config: The subscribe configuration. 190 191 Returns: 192 A tuple of the callback handler for the subscribe session and the 193 subscribe session ID. 194 """ 195 # Start the subscribe session. 196 subscribe_handler = subscriber.wifi.wifiAwareSubscribe( 197 attach_session_id, sub_config.to_dict() 198 ) 199 200 # Wait for session start result. 201 discovery_event = subscribe_handler.waitAndGet( 202 event_name=constants.DiscoverySessionCallbackMethodType.DISCOVER_RESULT, 203 timeout=_DEFAULT_TIMEOUT, 204 ) 205 callback_name = discovery_event.data[_CALLBACK_NAME] 206 asserts.assert_equal( 207 constants.DiscoverySessionCallbackMethodType.SUBSCRIBE_STARTED, 208 callback_name, 209 f'{subscriber} subscribe failed, got callback: {callback_name}.', 210 ) 211 is_session_init = discovery_event.data[_IS_SESSION_INIT] 212 asserts.assert_true( 213 is_session_init, 214 f'{subscriber} subscribe succeeded, but null session returned.', 215 ) 216 subscriber.log.info('Created subscribe session.') 217 return subscribe_handler, subscribe_handler.callback_id 218 219 220def _wait_for_discovery( 221 subscriber: android_device.AndroidDevice, 222 sub_session_handler: callback_handler_v2.CallbackHandlerV2, 223 pub_service_specific_info: bytes, 224 is_ranging_enabled: bool, 225) -> int: 226 """Waits for discovery of the publisher's service by the subscriber. 227 228 Args: 229 subscriber: The Android device controller of the subscriber. 230 sub_session_handler: The callback handler for the subscribe session. 231 pub_service_specific_info: The service info set on the publisher. 232 is_ranging_enabled: Whether the publisher has ranging enabled. 233 234 Returns: 235 The peer ID of the publisher as seen on the subscriber. 236 """ 237 event_name = constants.DiscoverySessionCallbackMethodType.SERVICE_DISCOVERED 238 if is_ranging_enabled: 239 event_name = ( 240 constants.DiscoverySessionCallbackMethodType.SERVICE_DISCOVERED_WITHIN_RANGE 241 ) 242 discover_data = sub_session_handler.waitAndGet( 243 event_name=event_name, timeout=_DEFAULT_TIMEOUT 244 ) 245 246 service_info = bytes( 247 discover_data.data[ 248 constants.WifiAwareSnippetParams.SERVICE_SPECIFIC_INFO 249 ] 250 ) 251 asserts.assert_equal( 252 service_info, 253 pub_service_specific_info, 254 f'{subscriber} got unexpected service info in discovery' 255 f' callback event "{event_name}".', 256 ) 257 match_filters = discover_data.data[ 258 constants.WifiAwareSnippetParams.MATCH_FILTER 259 ] 260 match_filters = [ 261 bytes(filter[constants.WifiAwareSnippetParams.MATCH_FILTER_VALUE]) 262 for filter in match_filters 263 ] 264 asserts.assert_equal( 265 match_filters, 266 [constants.WifiAwareTestConstants.MATCH_FILTER_BYTES], 267 f'{subscriber} got unexpected match filter data in discovery' 268 f' callback event "{event_name}".', 269 ) 270 return discover_data.data[constants.WifiAwareSnippetParams.PEER_ID] 271 272 273def send_msg_through_discovery_session( 274 sender: android_device.AndroidDevice, 275 sender_discovery_session_handler: callback_handler_v2.CallbackHandlerV2, 276 receiver: android_device.AndroidDevice, 277 receiver_discovery_session_handler: callback_handler_v2.CallbackHandlerV2, 278 discovery_session: str, 279 peer_on_sender: int, 280 send_message: str, 281 send_message_id: int | None = None, 282) -> int: 283 """Sends a message through a discovery session and verifies receipt. 284 285 Args: 286 sender: The Android device controller of the sender. 287 sender_discovery_session_handler: The callback handler for the sender's 288 discovery session. 289 receiver: The Android device controller of the receiver. 290 receiver_discovery_session_handler: The callback handler for the 291 receiver's discovery session. 292 discovery_session: The discovery session ID. 293 peer_on_sender: The peer ID of the receiver as seen on the sender. 294 send_message: The message to send. 295 send_message_id: The message ID. If not provided, a random ID will be 296 generated. 297 298 Returns: 299 The peer ID of the sender as seen on the receiver. 300 """ 301 send_message_id = send_message_id or random.randint(1000, 5000) 302 sender.wifi.wifiAwareSendMessage( 303 discovery_session, peer_on_sender, send_message_id, send_message 304 ) 305 message_send_result = sender_discovery_session_handler.waitAndGet( 306 event_name=constants.DiscoverySessionCallbackMethodType.MESSAGE_SEND_RESULT, 307 timeout=_DEFAULT_TIMEOUT, 308 ) 309 callback_name = message_send_result.data[ 310 constants.DiscoverySessionCallbackParamsType.CALLBACK_NAME 311 ] 312 asserts.assert_equal( 313 callback_name, 314 constants.DiscoverySessionCallbackMethodType.MESSAGE_SEND_SUCCEEDED, 315 f'{sender} failed to send message with an unexpected callback.', 316 ) 317 actual_send_message_id = message_send_result.data[ 318 constants.DiscoverySessionCallbackParamsType.MESSAGE_ID 319 ] 320 asserts.assert_equal( 321 actual_send_message_id, 322 send_message_id, 323 f'{sender} send message succeeded but message ID mismatched.', 324 ) 325 receive_message_event = receiver_discovery_session_handler.waitAndGet( 326 event_name=constants.DiscoverySessionCallbackMethodType.MESSAGE_RECEIVED, 327 timeout=_DEFAULT_TIMEOUT, 328 ) 329 received_message_raw = receive_message_event.data[ 330 constants.WifiAwareSnippetParams.RECEIVED_MESSAGE 331 ] 332 received_message = bytes(received_message_raw).decode('utf-8') 333 asserts.assert_equal( 334 received_message, 335 send_message, 336 f'{receiver} received the message but message content mismatched.', 337 ) 338 return receive_message_event.data[constants.WifiAwareSnippetParams.PEER_ID] 339 340 341def create_server_socket( 342 publisher: android_device.AndroidDevice, 343): 344 """Creates a server socket listening on a local port.""" 345 server_accept_handler = publisher.wifi.connectivityServerSocketAccept() 346 network_id = server_accept_handler.callback_id 347 server_local_port = server_accept_handler.ret_value 348 return server_accept_handler, network_id, server_local_port 349 350 351def request_aware_network( 352 ad: android_device.AndroidDevice, 353 discovery_session: str, 354 peer: int, 355 network_id: str, 356 network_specifier_params: constants.WifiAwareNetworkSpecifier, 357 is_accept_any_peer: bool = False, 358) -> callback_handler_v2.CallbackHandlerV2: 359 """Sends the command to request a Wi-Fi Aware network. 360 361 This does not wait for the network to be established. 362 363 Args: 364 ad: The Android device controller. 365 discovery_session: The discovery session ID. 366 peer: The ID of the peer to establish a Wi-Fi Aware connection with. 367 network_id: The network ID. 368 network_specifier_params: The network specifier parameters. 369 is_accept_any_peer: Whether to accept any peer. If True, the argument 370 peer will be ignored. 371 372 Returns: 373 The callback handler for querying the network status. 374 """ 375 network_specifier_parcel = ad.wifi.wifiAwareCreateNetworkSpecifier( 376 discovery_session, 377 peer, 378 is_accept_any_peer, 379 network_specifier_params.to_dict(), 380 ) 381 network_request = constants.NetworkRequest( 382 transport_type=_TRANSPORT_TYPE_WIFI_AWARE, 383 network_specifier_parcel=network_specifier_parcel, 384 ) 385 ad.log.debug('Requesting Wi-Fi Aware network: %r', network_request) 386 return ad.wifi.connectivityRequestNetwork( 387 network_id, 388 network_request.to_dict(), 389 _REQUEST_NETWORK_TIMEOUT.total_seconds() * 1000, 390 ) 391 392 393def wait_for_aware_network( 394 ad: android_device.AndroidDevice, 395 request_network_handler: callback_handler_v2.CallbackHandlerV2, 396) -> callback_event.CallbackEvent: 397 """Waits for and verifies the establishment of a Wi-Fi Aware network. 398 399 Args: 400 ad: The Android device controller. 401 request_network_handler: The callback handler for requesting network. 402 403 Returns: 404 The callback event for network capabilities changed event, providing 405 information of the new network connection. 406 """ 407 network_callback_event = request_network_handler.waitAndGet( 408 event_name=constants.NetworkCbEventName.NETWORK_CALLBACK, 409 timeout=_DEFAULT_TIMEOUT, 410 ) 411 callback_name = network_callback_event.data[_CALLBACK_NAME] 412 if callback_name == constants.NetworkCbName.ON_UNAVAILABLE: 413 asserts.fail( 414 f'{ad} failed to request the network, got callback' 415 f' {callback_name}.' 416 ) 417 elif callback_name == constants.NetworkCbName.ON_CAPABILITIES_CHANGED: 418 # `network` is the network whose capabilities have changed. 419 network = network_callback_event.data[ 420 constants.NetworkCbEventKey.NETWORK 421 ] 422 network_capabilities = network_callback_event.data[ 423 constants.NetworkCbEventKey.NETWORK_CAPABILITIES 424 ] 425 asserts.assert_true( 426 network and network_capabilities, 427 f'{ad} received a null Network or NetworkCapabilities!?.', 428 ) 429 transport_info_class_name = network_callback_event.data[ 430 constants.NetworkCbEventKey.TRANSPORT_INFO_CLASS_NAME 431 ] 432 asserts.assert_equal( 433 transport_info_class_name, 434 constants.AWARE_NETWORK_INFO_CLASS_NAME, 435 f'{ad} network capabilities changes but it is not a WiFi Aware' 436 ' network.', 437 ) 438 return network_callback_event 439 else: 440 asserts.fail( 441 f'{ad} got unknown request network callback {callback_name}.' 442 ) 443 444 445def establish_socket_connection( 446 publisher: android_device.AndroidDevice, 447 subscriber: android_device.AndroidDevice, 448 pub_accept_handler: callback_handler_v2.CallbackHandlerV2, 449 network_id: str, 450 pub_local_port: int, 451): 452 """Establishes a socket connection between the publisher and subscriber. 453 454 Args: 455 publisher: The publisher. 456 subscriber: The subscriber. 457 pub_accept_handler: The callback handler returned when the publisher 458 called snippet RPC `connectivityServerSocketAccept`. 459 network_id: The network ID. 460 pub_local_port: The local port of the publisher's server socket. 461 """ 462 subscriber.wifi.connectivityCreateSocketOverWiFiAware( 463 network_id, pub_local_port 464 ) 465 pub_accept_event = pub_accept_handler.waitAndGet( 466 event_name=constants.SnippetEventNames.SERVER_SOCKET_ACCEPT, 467 timeout=_DEFAULT_TIMEOUT, 468 ) 469 is_accept = pub_accept_event.data.get( 470 constants.SnippetEventParams.IS_ACCEPT, False 471 ) 472 if not is_accept: 473 error = pub_accept_event.data[constants.SnippetEventParams.ERROR] 474 asserts.fail( 475 f'{publisher} Failed to accept the connection. Error: {error}' 476 ) 477 subscriber.log.info('Subscriber created a socket to the publisher.') 478 479 480def send_socket_msg( 481 sender_ad: android_device.AndroidDevice, 482 receiver_ad: android_device.AndroidDevice, 483 msg: str, 484 network_id: str, 485): 486 """Sends a message from one device to another and verifies receipt.""" 487 is_write_socket = sender_ad.wifi.connectivityWriteSocket(network_id, msg) 488 asserts.assert_true( 489 is_write_socket, f'{sender_ad} Failed to write data to the socket.' 490 ) 491 sender_ad.log.info('Wrote data to the socket.') 492 received_message = receiver_ad.wifi.connectivityReadSocket( 493 network_id, len(msg) 494 ) 495 asserts.assert_equal( 496 received_message, 497 msg, 498 f'{receiver_ad} received message mismatched.Failure:Expected {msg} but ' 499 f'got {received_message}.', 500 ) 501 receiver_ad.log.info('Read data from the socket.') 502