1#!/usr/bin/env python3
2#
3#   Copyright 2017 - Google
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
17import base64
18import json
19import queue
20import re
21import statistics
22import time
23from acts import asserts
24
25from acts.test_utils.net import connectivity_const as cconsts
26from acts.test_utils.net import socket_test_utils as sutils
27from acts.test_utils.wifi.aware import aware_const as aconsts
28
29# arbitrary timeout for events
30EVENT_TIMEOUT = 10
31
32# semi-arbitrary timeout for network formation events. Based on framework
33# timeout for NDP (NAN data-path) negotiation to be completed.
34EVENT_NDP_TIMEOUT = 20
35
36# number of second to 'reasonably' wait to make sure that devices synchronize
37# with each other - useful for OOB test cases, where the OOB discovery would
38# take some time
39WAIT_FOR_CLUSTER = 5
40
41
42def decorate_event(event_name, id):
43    return '%s_%d' % (event_name, id)
44
45
46def wait_for_event(ad, event_name, timeout=EVENT_TIMEOUT):
47    """Wait for the specified event or timeout.
48
49  Args:
50    ad: The android device
51    event_name: The event to wait on
52    timeout: Number of seconds to wait
53  Returns:
54    The event (if available)
55  """
56    prefix = ''
57    if hasattr(ad, 'pretty_name'):
58        prefix = '[%s] ' % ad.pretty_name
59    try:
60        event = ad.ed.pop_event(event_name, timeout)
61        ad.log.info('%s%s: %s', prefix, event_name, event['data'])
62        return event
63    except queue.Empty:
64        ad.log.info('%sTimed out while waiting for %s', prefix, event_name)
65        asserts.fail(event_name)
66
67
68def wait_for_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT,
69                             *keyvalues):
70    """Wait for the specified event contain the key/value pairs or timeout
71
72  Args:
73    ad: The android device
74    event_name: The event to wait on
75    timeout: Number of seconds to wait
76    keyvalues: (kay, value) pairs
77  Returns:
78    The event (if available)
79  """
80
81    def filter_callbacks(event, keyvalues):
82        for keyvalue in keyvalues:
83            key, value = keyvalue
84            if event['data'][key] != value:
85                return False
86        return True
87
88    prefix = ''
89    if hasattr(ad, 'pretty_name'):
90        prefix = '[%s] ' % ad.pretty_name
91    try:
92        event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
93                                     keyvalues)
94        ad.log.info('%s%s: %s', prefix, event_name, event['data'])
95        return event
96    except queue.Empty:
97        ad.log.info('%sTimed out while waiting for %s (%s)', prefix,
98                    event_name, keyvalues)
99        asserts.fail(event_name)
100
101
102def fail_on_event(ad, event_name, timeout=EVENT_TIMEOUT):
103    """Wait for a timeout period and looks for the specified event - fails if it
104  is observed.
105
106  Args:
107    ad: The android device
108    event_name: The event to wait for (and fail on its appearance)
109  """
110    prefix = ''
111    if hasattr(ad, 'pretty_name'):
112        prefix = '[%s] ' % ad.pretty_name
113    try:
114        event = ad.ed.pop_event(event_name, timeout)
115        ad.log.info('%sReceived unwanted %s: %s', prefix, event_name,
116                    event['data'])
117        asserts.fail(event_name, extras=event)
118    except queue.Empty:
119        ad.log.info('%s%s not seen (as expected)', prefix, event_name)
120        return
121
122
123def fail_on_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT, *keyvalues):
124    """Wait for a timeout period and looks for the specified event which contains
125  the key/value pairs - fails if it is observed.
126
127  Args:
128    ad: The android device
129    event_name: The event to wait on
130    timeout: Number of seconds to wait
131    keyvalues: (kay, value) pairs
132  """
133
134    def filter_callbacks(event, keyvalues):
135        for keyvalue in keyvalues:
136            key, value = keyvalue
137            if event['data'][key] != value:
138                return False
139        return True
140
141    prefix = ''
142    if hasattr(ad, 'pretty_name'):
143        prefix = '[%s] ' % ad.pretty_name
144    try:
145        event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
146                                     keyvalues)
147        ad.log.info('%sReceived unwanted %s: %s', prefix, event_name,
148                    event['data'])
149        asserts.fail(event_name, extras=event)
150    except queue.Empty:
151        ad.log.info('%s%s (%s) not seen (as expected)', prefix, event_name,
152                    keyvalues)
153        return
154
155
156def verify_no_more_events(ad, timeout=EVENT_TIMEOUT):
157    """Verify that there are no more events in the queue.
158  """
159    prefix = ''
160    if hasattr(ad, 'pretty_name'):
161        prefix = '[%s] ' % ad.pretty_name
162    should_fail = False
163    try:
164        while True:
165            event = ad.ed.pop_events('.*', timeout, freq=0)
166            ad.log.info('%sQueue contains %s', prefix, event)
167            should_fail = True
168    except queue.Empty:
169        if should_fail:
170            asserts.fail('%sEvent queue not empty' % prefix)
171        ad.log.info('%sNo events in the queue (as expected)', prefix)
172        return
173
174
175def encode_list(list_of_objects):
176    """Converts the list of strings or bytearrays to a list of b64 encoded
177  bytearrays.
178
179  A None object is treated as a zero-length bytearray.
180
181  Args:
182    list_of_objects: A list of strings or bytearray objects
183  Returns: A list of the same objects, converted to bytes and b64 encoded.
184  """
185    encoded_list = []
186    for obj in list_of_objects:
187        if obj is None:
188            obj = bytes()
189        if isinstance(obj, str):
190            encoded_list.append(
191                base64.b64encode(bytes(obj, 'utf-8')).decode('utf-8'))
192        else:
193            encoded_list.append(base64.b64encode(obj).decode('utf-8'))
194    return encoded_list
195
196
197def decode_list(list_of_b64_strings):
198    """Converts the list of b64 encoded strings to a list of bytearray.
199
200  Args:
201    list_of_b64_strings: list of strings, each of which is b64 encoded array
202  Returns: a list of bytearrays.
203  """
204    decoded_list = []
205    for str in list_of_b64_strings:
206        decoded_list.append(base64.b64decode(str))
207    return decoded_list
208
209
210def construct_max_match_filter(max_size):
211    """Constructs a maximum size match filter that fits into the 'max_size' bytes.
212
213  Match filters are a set of LVs (Length, Value pairs) where L is 1 byte. The
214  maximum size match filter will contain max_size/2 LVs with all Vs (except
215  possibly the last one) of 1 byte, the last V may be 2 bytes for odd max_size.
216
217  Args:
218    max_size: Maximum size of the match filter.
219  Returns: an array of bytearrays.
220  """
221    mf_list = []
222    num_lvs = max_size // 2
223    for i in range(num_lvs - 1):
224        mf_list.append(bytes([i]))
225    if (max_size % 2 == 0):
226        mf_list.append(bytes([255]))
227    else:
228        mf_list.append(bytes([254, 255]))
229    return mf_list
230
231
232def assert_equal_strings(first, second, msg=None, extras=None):
233    """Assert equality of the string operands - where None is treated as equal to
234  an empty string (''), otherwise fail the test.
235
236  Error message is "first != second" by default. Additional explanation can
237  be supplied in the message.
238
239  Args:
240      first, seconds: The strings that are evaluated for equality.
241      msg: A string that adds additional info about the failure.
242      extras: An optional field for extra information to be included in
243              test result.
244  """
245    if first == None:
246        first = ''
247    if second == None:
248        second = ''
249    asserts.assert_equal(first, second, msg, extras)
250
251
252def get_aware_capabilities(ad):
253    """Get the Wi-Fi Aware capabilities from the specified device. The
254  capabilities are a dictionary keyed by aware_const.CAP_* keys.
255
256  Args:
257    ad: the Android device
258  Returns: the capability dictionary.
259  """
260    return json.loads(ad.adb.shell('cmd wifiaware state_mgr get_capabilities'))
261
262
263def get_wifi_mac_address(ad):
264    """Get the Wi-Fi interface MAC address as a upper-case string of hex digits
265  without any separators (e.g. ':').
266
267  Args:
268    ad: Device on which to run.
269  """
270    return ad.droid.wifiGetConnectionInfo()['mac_address'].upper().replace(
271        ':', '')
272
273
274def validate_forbidden_callbacks(ad, limited_cb=None):
275    """Validate that the specified callbacks have not been called more then permitted.
276
277  In addition to the input configuration also validates that forbidden callbacks
278  have never been called.
279
280  Args:
281    ad: Device on which to run.
282    limited_cb: Dictionary of CB_EV_* ids and maximum permitted calls (0
283                meaning never).
284  """
285    cb_data = json.loads(ad.adb.shell('cmd wifiaware native_cb get_cb_count'))
286
287    if limited_cb is None:
288        limited_cb = {}
289    # add callbacks which should never be called
290    limited_cb[aconsts.CB_EV_MATCH_EXPIRED] = 0
291
292    fail = False
293    for cb_event in limited_cb.keys():
294        if cb_event in cb_data:
295            if cb_data[cb_event] > limited_cb[cb_event]:
296                fail = True
297                ad.log.info(
298                    'Callback %s observed %d times: more then permitted %d times',
299                    cb_event, cb_data[cb_event], limited_cb[cb_event])
300
301    asserts.assert_false(fail, 'Forbidden callbacks observed', extras=cb_data)
302
303
304def extract_stats(ad, data, results, key_prefix, log_prefix):
305    """Extract statistics from the data, store in the results dictionary, and
306  output to the info log.
307
308  Args:
309    ad: Android device (for logging)
310    data: A list containing the data to be analyzed.
311    results: A dictionary into which to place the statistics.
312    key_prefix: A string prefix to use for the dict keys storing the
313                extracted stats.
314    log_prefix: A string prefix to use for the info log.
315    include_data: If True includes the raw data in the dictionary,
316                  otherwise just the stats.
317  """
318    num_samples = len(data)
319    results['%snum_samples' % key_prefix] = num_samples
320
321    if not data:
322        return
323
324    data_min = min(data)
325    data_max = max(data)
326    data_mean = statistics.mean(data)
327    data_cdf = extract_cdf(data)
328    data_cdf_decile = extract_cdf_decile(data_cdf)
329
330    results['%smin' % key_prefix] = data_min
331    results['%smax' % key_prefix] = data_max
332    results['%smean' % key_prefix] = data_mean
333    results['%scdf' % key_prefix] = data_cdf
334    results['%scdf_decile' % key_prefix] = data_cdf_decile
335    results['%sraw_data' % key_prefix] = data
336
337    if num_samples > 1:
338        data_stdev = statistics.stdev(data)
339        results['%sstdev' % key_prefix] = data_stdev
340        ad.log.info(
341            '%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, stdev=%.2f, cdf_decile=%s',
342            log_prefix, num_samples, data_min, data_max, data_mean, data_stdev,
343            data_cdf_decile)
344    else:
345        ad.log.info(
346            '%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, cdf_decile=%s',
347            log_prefix, num_samples, data_min, data_max, data_mean,
348            data_cdf_decile)
349
350
351def extract_cdf_decile(cdf):
352    """Extracts the 10%, 20%, ..., 90% points from the CDF and returns their
353  value (a list of 9 values).
354
355  Since CDF may not (will not) have exact x% value picks the value >= x%.
356
357  Args:
358    cdf: a list of 2 lists, the X and Y of the CDF.
359  """
360    decades = []
361    next_decade = 10
362    for x, y in zip(cdf[0], cdf[1]):
363        while 100 * y >= next_decade:
364            decades.append(x)
365            next_decade = next_decade + 10
366        if next_decade == 100:
367            break
368    return decades
369
370
371def extract_cdf(data):
372    """Calculates the Cumulative Distribution Function (CDF) of the data.
373
374  Args:
375      data: A list containing data (does not have to be sorted).
376
377  Returns: a list of 2 lists: the X and Y axis of the CDF.
378  """
379    x = []
380    cdf = []
381    if not data:
382        return (x, cdf)
383
384    all_values = sorted(data)
385    for val in all_values:
386        if not x:
387            x.append(val)
388            cdf.append(1)
389        else:
390            if x[-1] == val:
391                cdf[-1] += 1
392            else:
393                x.append(val)
394                cdf.append(cdf[-1] + 1)
395
396    scale = 1.0 / len(all_values)
397    for i in range(len(cdf)):
398        cdf[i] = cdf[i] * scale
399
400    return (x, cdf)
401
402
403def get_mac_addr(device, interface):
404    """Get the MAC address of the specified interface. Uses ifconfig and parses
405  its output. Normalizes string to remove ':' and upper case.
406
407  Args:
408    device: Device on which to query the interface MAC address.
409    interface: Name of the interface for which to obtain the MAC address.
410  """
411    out = device.adb.shell("ifconfig %s" % interface)
412    res = re.match(".* HWaddr (\S+).*", out, re.S)
413    asserts.assert_true(
414        res,
415        'Unable to obtain MAC address for interface %s' % interface,
416        extras=out)
417    return res.group(1).upper().replace(':', '')
418
419
420def get_ipv6_addr(device, interface):
421    """Get the IPv6 address of the specified interface. Uses ifconfig and parses
422  its output. Returns a None if the interface does not have an IPv6 address
423  (indicating it is not UP).
424
425  Args:
426    device: Device on which to query the interface IPv6 address.
427    interface: Name of the interface for which to obtain the IPv6 address.
428  """
429    out = device.adb.shell("ifconfig %s" % interface)
430    res = re.match(".*inet6 addr: (\S+)/.*", out, re.S)
431    if not res:
432        return None
433    return res.group(1)
434
435
436def verify_socket_connect(dut_s, dut_c, ipv6_s, ipv6_c, port):
437    """Verify the socket connection between server (dut_s) and client (dut_c)
438    using the given IPv6 addresses.
439
440    Opens a ServerSocket on the server and tries to connect to it
441    from the client.
442
443    Args:
444        dut_s, dut_c: the server and client devices under test (DUTs)
445        ipv6_s, ipv6_c: the scoped link-local addresses of the server and client.
446        port: the port to use
447    Return: True on success, False otherwise
448    """
449    server_sock = None
450    sock_c = None
451    sock_s = None
452    try:
453        server_sock = sutils.open_server_socket(dut_s, ipv6_s, port)
454        port_to_use = port
455        if port == 0:
456            port_to_use = dut_s.droid.getTcpServerSocketPort(server_sock)
457        sock_c, sock_s = sutils.open_connect_socket(
458            dut_c, dut_s, ipv6_c, ipv6_s, 0, port_to_use, server_sock)
459    except:
460        return False
461    finally:
462        if sock_c is not None:
463            sutils.close_socket(dut_c, sock_c)
464        if sock_s is not None:
465            sutils.close_socket(dut_s, sock_s)
466        if server_sock is not None:
467            sutils.close_server_socket(dut_s, server_sock)
468    return True
469
470
471def run_ping6(dut, target_ip, duration=60):
472    """Run ping test and return the latency result
473
474    Args:
475        dut: the dut which run the ping cmd
476        target_ip: target IP Address for ping
477        duration: the duration time of the ping
478
479    return: dict contains "min/avg/max/mdev" result
480    """
481    cmd = "ping6 -w %d %s" % (duration, target_ip)
482    ping_result = dut.adb.shell(cmd, timeout=duration + 1)
483    res = re.match(".*mdev = (\S+) .*", ping_result, re.S)
484    asserts.assert_true(res, "Cannot reach the IP address %s", target_ip)
485    title = ["min", "avg", "max", "mdev"]
486    result = res.group(1).split("/")
487    latency_result = {}
488    for i in range(len(title)):
489        latency_result[title[i]] = result[i]
490    return latency_result
491
492
493#########################################################
494# Aware primitives
495#########################################################
496
497
498def request_network(dut, ns):
499    """Request a Wi-Fi Aware network.
500
501  Args:
502    dut: Device
503    ns: Network specifier
504  Returns: the request key
505  """
506    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
507    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
508
509
510def get_network_specifier(dut, id, dev_type, peer_mac, sec):
511    """Create a network specifier for the device based on the security
512  configuration.
513
514  Args:
515    dut: device
516    id: session ID
517    dev_type: device type - Initiator or Responder
518    peer_mac: the discovery MAC address of the peer
519    sec: security configuration
520  """
521    if sec is None:
522        return dut.droid.wifiAwareCreateNetworkSpecifierOob(
523            id, dev_type, peer_mac)
524    if isinstance(sec, str):
525        return dut.droid.wifiAwareCreateNetworkSpecifierOob(
526            id, dev_type, peer_mac, sec)
527    return dut.droid.wifiAwareCreateNetworkSpecifierOob(
528        id, dev_type, peer_mac, None, sec)
529
530
531def configure_power_setting(device, mode, name, value):
532    """Use the command-line API to configure the power setting
533
534  Args:
535    device: Device on which to perform configuration
536    mode: The power mode being set, should be "default", "inactive", or "idle"
537    name: One of the power settings from 'wifiaware set-power'.
538    value: An integer.
539  """
540    device.adb.shell(
541        "cmd wifiaware native_api set-power %s %s %d" % (mode, name, value))
542
543
544def configure_mac_random_interval(device, interval_sec):
545    """Use the command-line API to configure the MAC address randomization
546  interval.
547
548  Args:
549    device: Device on which to perform configuration
550    interval_sec: The MAC randomization interval in seconds. A value of 0
551                  disables all randomization.
552  """
553    device.adb.shell("cmd wifiaware native_api set mac_random_interval_sec %d"
554                     % interval_sec)
555
556
557def configure_ndp_allow_any_override(device, override_api_check):
558    """Use the command-line API to configure whether an NDP Responder may be
559  configured to accept an NDP request from ANY peer.
560
561  By default the target API level of the requesting app determines whether such
562  configuration is permitted. This allows overriding the API check and allowing
563  it.
564
565  Args:
566    device: Device on which to perform configuration.
567    override_api_check: True to allow a Responder to ANY configuration, False to
568                        perform the API level check.
569  """
570    device.adb.shell("cmd wifiaware state_mgr allow_ndp_any %s" %
571                     ("true" if override_api_check else "false"))
572
573
574def config_settings_high_power(device):
575    """Configure device's power settings values to high power mode -
576  whether device is in interactive or non-interactive modes"""
577    configure_power_setting(device, "default", "dw_24ghz",
578                            aconsts.POWER_DW_24_INTERACTIVE)
579    configure_power_setting(device, "default", "dw_5ghz",
580                            aconsts.POWER_DW_5_INTERACTIVE)
581    configure_power_setting(device, "default", "disc_beacon_interval_ms",
582                            aconsts.POWER_DISC_BEACON_INTERVAL_INTERACTIVE)
583    configure_power_setting(device, "default", "num_ss_in_discovery",
584                            aconsts.POWER_NUM_SS_IN_DISC_INTERACTIVE)
585    configure_power_setting(device, "default", "enable_dw_early_term",
586                            aconsts.POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE)
587
588    configure_power_setting(device, "inactive", "dw_24ghz",
589                            aconsts.POWER_DW_24_INTERACTIVE)
590    configure_power_setting(device, "inactive", "dw_5ghz",
591                            aconsts.POWER_DW_5_INTERACTIVE)
592    configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
593                            aconsts.POWER_DISC_BEACON_INTERVAL_INTERACTIVE)
594    configure_power_setting(device, "inactive", "num_ss_in_discovery",
595                            aconsts.POWER_NUM_SS_IN_DISC_INTERACTIVE)
596    configure_power_setting(device, "inactive", "enable_dw_early_term",
597                            aconsts.POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE)
598
599
600def config_settings_low_power(device):
601    """Configure device's power settings values to low power mode - whether
602  device is in interactive or non-interactive modes"""
603    configure_power_setting(device, "default", "dw_24ghz",
604                            aconsts.POWER_DW_24_NON_INTERACTIVE)
605    configure_power_setting(device, "default", "dw_5ghz",
606                            aconsts.POWER_DW_5_NON_INTERACTIVE)
607    configure_power_setting(device, "default", "disc_beacon_interval_ms",
608                            aconsts.POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE)
609    configure_power_setting(device, "default", "num_ss_in_discovery",
610                            aconsts.POWER_NUM_SS_IN_DISC_NON_INTERACTIVE)
611    configure_power_setting(device, "default", "enable_dw_early_term",
612                            aconsts.POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE)
613
614    configure_power_setting(device, "inactive", "dw_24ghz",
615                            aconsts.POWER_DW_24_NON_INTERACTIVE)
616    configure_power_setting(device, "inactive", "dw_5ghz",
617                            aconsts.POWER_DW_5_NON_INTERACTIVE)
618    configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
619                            aconsts.POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE)
620    configure_power_setting(device, "inactive", "num_ss_in_discovery",
621                            aconsts.POWER_NUM_SS_IN_DISC_NON_INTERACTIVE)
622    configure_power_setting(device, "inactive", "enable_dw_early_term",
623                            aconsts.POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE)
624
625
626def config_power_settings(device,
627                          dw_24ghz,
628                          dw_5ghz,
629                          disc_beacon_interval=None,
630                          num_ss_in_disc=None,
631                          enable_dw_early_term=None):
632    """Configure device's discovery window (DW) values to the specified values -
633  whether the device is in interactive or non-interactive mode.
634
635  Args:
636    dw_24ghz: DW interval in the 2.4GHz band.
637    dw_5ghz: DW interval in the 5GHz band.
638    disc_beacon_interval: The discovery beacon interval (in ms). If None then
639                          not set.
640    num_ss_in_disc: Number of spatial streams to use for discovery. If None then
641                    not set.
642    enable_dw_early_term: If True then enable early termination of the DW. If
643                          None then not set.
644  """
645    configure_power_setting(device, "default", "dw_24ghz", dw_24ghz)
646    configure_power_setting(device, "default", "dw_5ghz", dw_5ghz)
647    configure_power_setting(device, "inactive", "dw_24ghz", dw_24ghz)
648    configure_power_setting(device, "inactive", "dw_5ghz", dw_5ghz)
649
650    if disc_beacon_interval is not None:
651        configure_power_setting(device, "default", "disc_beacon_interval_ms",
652                                disc_beacon_interval)
653        configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
654                                disc_beacon_interval)
655
656    if num_ss_in_disc is not None:
657        configure_power_setting(device, "default", "num_ss_in_discovery",
658                                num_ss_in_disc)
659        configure_power_setting(device, "inactive", "num_ss_in_discovery",
660                                num_ss_in_disc)
661
662    if enable_dw_early_term is not None:
663        configure_power_setting(device, "default", "enable_dw_early_term",
664                                enable_dw_early_term)
665        configure_power_setting(device, "inactive", "enable_dw_early_term",
666                                enable_dw_early_term)
667
668
669def create_discovery_config(service_name,
670                            d_type,
671                            ssi=None,
672                            match_filter=None,
673                            match_filter_list=None,
674                            ttl=0,
675                            term_cb_enable=True):
676    """Create a publish discovery configuration based on input parameters.
677
678  Args:
679    service_name: Service name - required
680    d_type: Discovery type (publish or subscribe constants)
681    ssi: Supplemental information - defaults to None
682    match_filter, match_filter_list: The match_filter, only one mechanism can
683                                     be used to specify. Defaults to None.
684    ttl: Time-to-live - defaults to 0 (i.e. non-self terminating)
685    term_cb_enable: True (default) to enable callback on termination, False
686                    means that no callback is called when session terminates.
687  Returns:
688    publish discovery configuration object.
689  """
690    config = {}
691    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
692    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = d_type
693    if ssi is not None:
694        config[aconsts.DISCOVERY_KEY_SSI] = ssi
695    if match_filter is not None:
696        config[aconsts.DISCOVERY_KEY_MATCH_FILTER] = match_filter
697    if match_filter_list is not None:
698        config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = match_filter_list
699    config[aconsts.DISCOVERY_KEY_TTL] = ttl
700    config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_cb_enable
701    return config
702
703
704def add_ranging_to_pub(p_config, enable_ranging):
705    """Add ranging enabled configuration to a publish configuration (only relevant
706  for publish configuration).
707
708  Args:
709    p_config: The Publish discovery configuration.
710    enable_ranging: True to enable ranging, False to disable.
711  Returns:
712    The modified publish configuration.
713  """
714    p_config[aconsts.DISCOVERY_KEY_RANGING_ENABLED] = enable_ranging
715    return p_config
716
717
718def add_ranging_to_sub(s_config, min_distance_mm, max_distance_mm):
719    """Add ranging distance configuration to a subscribe configuration (only
720  relevant to a subscribe configuration).
721
722  Args:
723    s_config: The Subscribe discovery configuration.
724    min_distance_mm, max_distance_mm: The min and max distance specification.
725                                      Used if not None.
726  Returns:
727    The modified subscribe configuration.
728  """
729    if min_distance_mm is not None:
730        s_config[aconsts.DISCOVERY_KEY_MIN_DISTANCE_MM] = min_distance_mm
731    if max_distance_mm is not None:
732        s_config[aconsts.DISCOVERY_KEY_MAX_DISTANCE_MM] = max_distance_mm
733    return s_config
734
735
736def attach_with_identity(dut):
737    """Start an Aware session (attach) and wait for confirmation and identity
738  information (mac address).
739
740  Args:
741    dut: Device under test
742  Returns:
743    id: Aware session ID.
744    mac: Discovery MAC address of this device.
745  """
746    id = dut.droid.wifiAwareAttach(True)
747    wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
748    event = wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
749    mac = event["data"]["mac"]
750
751    return id, mac
752
753
754def create_discovery_pair(p_dut,
755                          s_dut,
756                          p_config,
757                          s_config,
758                          device_startup_offset,
759                          msg_id=None):
760    """Creates a discovery session (publish and subscribe), and waits for
761  service discovery - at that point the sessions are connected and ready for
762  further messaging of data-path setup.
763
764  Args:
765    p_dut: Device to use as publisher.
766    s_dut: Device to use as subscriber.
767    p_config: Publish configuration.
768    s_config: Subscribe configuration.
769    device_startup_offset: Number of seconds to offset the enabling of NAN on
770                           the two devices.
771    msg_id: Controls whether a message is sent from Subscriber to Publisher
772            (so that publisher has the sub's peer ID). If None then not sent,
773            otherwise should be an int for the message id.
774  Returns: variable size list of:
775    p_id: Publisher attach session id
776    s_id: Subscriber attach session id
777    p_disc_id: Publisher discovery session id
778    s_disc_id: Subscriber discovery session id
779    peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
780    peer_id_on_pub: Peer ID of the Subscriber as seen on the Publisher. Only
781                    included if |msg_id| is not None.
782  """
783    p_dut.pretty_name = 'Publisher'
784    s_dut.pretty_name = 'Subscriber'
785
786    # Publisher+Subscriber: attach and wait for confirmation
787    p_id = p_dut.droid.wifiAwareAttach()
788    wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
789    time.sleep(device_startup_offset)
790    s_id = s_dut.droid.wifiAwareAttach()
791    wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
792
793    # Publisher: start publish and wait for confirmation
794    p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
795    wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
796
797    # Subscriber: start subscribe and wait for confirmation
798    s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
799    wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
800
801    # Subscriber: wait for service discovery
802    discovery_event = wait_for_event(s_dut,
803                                     aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
804    peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
805
806    # Optionally send a message from Subscriber to Publisher
807    if msg_id is not None:
808        ping_msg = 'PING'
809
810        # Subscriber: send message to peer (Publisher)
811        s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
812                                         ping_msg, aconsts.MAX_TX_RETRIES)
813        sub_tx_msg_event = wait_for_event(s_dut,
814                                          aconsts.SESSION_CB_ON_MESSAGE_SENT)
815        asserts.assert_equal(
816            msg_id,
817            sub_tx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_ID],
818            'Subscriber -> Publisher message ID corrupted')
819
820        # Publisher: wait for received message
821        pub_rx_msg_event = wait_for_event(
822            p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
823        peer_id_on_pub = pub_rx_msg_event['data'][
824            aconsts.SESSION_CB_KEY_PEER_ID]
825        asserts.assert_equal(
826            ping_msg,
827            pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
828            'Subscriber -> Publisher message corrupted')
829        return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub, peer_id_on_pub
830
831    return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub
832
833
834def create_ib_ndp(p_dut, s_dut, p_config, s_config, device_startup_offset):
835    """Create an NDP (using in-band discovery)
836
837  Args:
838    p_dut: Device to use as publisher.
839    s_dut: Device to use as subscriber.
840    p_config: Publish configuration.
841    s_config: Subscribe configuration.
842    device_startup_offset: Number of seconds to offset the enabling of NAN on
843                           the two devices.
844  """
845    (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
846     peer_id_on_pub) = create_discovery_pair(
847         p_dut, s_dut, p_config, s_config, device_startup_offset, msg_id=9999)
848
849    # Publisher: request network
850    p_req_key = request_network(
851        p_dut,
852        p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub,
853                                                    None))
854
855    # Subscriber: request network
856    s_req_key = request_network(
857        s_dut,
858        s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
859                                                    None))
860
861    # Publisher & Subscriber: wait for network formation
862    p_net_event_nc = wait_for_event_with_keys(
863        p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
864        (cconsts.NETWORK_CB_KEY_EVENT,
865         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
866        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
867    s_net_event_nc = wait_for_event_with_keys(
868        s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
869        (cconsts.NETWORK_CB_KEY_EVENT,
870         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
871        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
872
873    # validate no leak of information
874    asserts.assert_false(
875        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in p_net_event_nc["data"],
876        "Network specifier leak!")
877    asserts.assert_false(
878        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in s_net_event_nc["data"],
879        "Network specifier leak!")
880
881    # note that Pub <-> Sub since IPv6 are of peer's!
882    p_ipv6 = s_net_event_nc["data"][aconsts.NET_CAP_IPV6]
883    s_ipv6 = p_net_event_nc["data"][aconsts.NET_CAP_IPV6]
884
885    p_net_event_lp = wait_for_event_with_keys(
886        p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
887        (cconsts.NETWORK_CB_KEY_EVENT,
888         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
889        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
890    s_net_event_lp = wait_for_event_with_keys(
891        s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
892        (cconsts.NETWORK_CB_KEY_EVENT,
893         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
894        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
895
896    p_aware_if = p_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
897    s_aware_if = s_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
898
899    return p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6, s_ipv6
900
901
902def create_oob_ndp_on_sessions(init_dut, resp_dut, init_id, init_mac, resp_id,
903                               resp_mac):
904    """Create an NDP on top of existing Aware sessions (using OOB discovery)
905
906  Args:
907    init_dut: Initiator device
908    resp_dut: Responder device
909    init_id: Initiator attach session id
910    init_mac: Initiator discovery MAC address
911    resp_id: Responder attach session id
912    resp_mac: Responder discovery MAC address
913  Returns:
914    init_req_key: Initiator network request
915    resp_req_key: Responder network request
916    init_aware_if: Initiator Aware data interface
917    resp_aware_if: Responder Aware data interface
918    init_ipv6: Initiator IPv6 address
919    resp_ipv6: Responder IPv6 address
920  """
921    # Responder: request network
922    resp_req_key = request_network(
923        resp_dut,
924        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
925            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
926
927    # Initiator: request network
928    init_req_key = request_network(
929        init_dut,
930        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
931            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
932
933    # Initiator & Responder: wait for network formation
934    init_net_event_nc = wait_for_event_with_keys(
935        init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
936        (cconsts.NETWORK_CB_KEY_EVENT,
937         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
938        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
939    resp_net_event_nc = wait_for_event_with_keys(
940        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
941        (cconsts.NETWORK_CB_KEY_EVENT,
942         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
943        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
944
945    # validate no leak of information
946    asserts.assert_false(
947        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in init_net_event_nc["data"],
948        "Network specifier leak!")
949    asserts.assert_false(
950        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in resp_net_event_nc["data"],
951        "Network specifier leak!")
952
953    # note that Init <-> Resp since IPv6 are of peer's!
954    resp_ipv6 = init_net_event_nc["data"][aconsts.NET_CAP_IPV6]
955    init_ipv6 = resp_net_event_nc["data"][aconsts.NET_CAP_IPV6]
956
957    init_net_event_lp = wait_for_event_with_keys(
958        init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
959        (cconsts.NETWORK_CB_KEY_EVENT,
960         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
961        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
962    resp_net_event_lp = wait_for_event_with_keys(
963        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
964        (cconsts.NETWORK_CB_KEY_EVENT,
965         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
966        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
967
968    init_aware_if = init_net_event_lp['data'][
969        cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
970    resp_aware_if = resp_net_event_lp['data'][
971        cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
972
973    return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
974            init_ipv6, resp_ipv6)
975
976
977def create_oob_ndp(init_dut, resp_dut):
978    """Create an NDP (using OOB discovery)
979
980  Args:
981    init_dut: Initiator device
982    resp_dut: Responder device
983  """
984    init_dut.pretty_name = 'Initiator'
985    resp_dut.pretty_name = 'Responder'
986
987    # Initiator+Responder: attach and wait for confirmation & identity
988    init_id = init_dut.droid.wifiAwareAttach(True)
989    wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
990    init_ident_event = wait_for_event(init_dut,
991                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
992    init_mac = init_ident_event['data']['mac']
993    resp_id = resp_dut.droid.wifiAwareAttach(True)
994    wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
995    resp_ident_event = wait_for_event(resp_dut,
996                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
997    resp_mac = resp_ident_event['data']['mac']
998
999    # wait for for devices to synchronize with each other - there are no other
1000    # mechanisms to make sure this happens for OOB discovery (except retrying
1001    # to execute the data-path request)
1002    time.sleep(WAIT_FOR_CLUSTER)
1003
1004    (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
1005     init_ipv6, resp_ipv6) = create_oob_ndp_on_sessions(
1006         init_dut, resp_dut, init_id, init_mac, resp_id, resp_mac)
1007
1008    return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
1009            init_ipv6, resp_ipv6)
1010