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