• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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