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
471#########################################################
472# Aware primitives
473#########################################################
474
475
476def request_network(dut, ns):
477    """Request a Wi-Fi Aware network.
478
479  Args:
480    dut: Device
481    ns: Network specifier
482  Returns: the request key
483  """
484    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
485    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
486
487
488def get_network_specifier(dut, id, dev_type, peer_mac, sec):
489    """Create a network specifier for the device based on the security
490  configuration.
491
492  Args:
493    dut: device
494    id: session ID
495    dev_type: device type - Initiator or Responder
496    peer_mac: the discovery MAC address of the peer
497    sec: security configuration
498  """
499    if sec is None:
500        return dut.droid.wifiAwareCreateNetworkSpecifierOob(
501            id, dev_type, peer_mac)
502    if isinstance(sec, str):
503        return dut.droid.wifiAwareCreateNetworkSpecifierOob(
504            id, dev_type, peer_mac, sec)
505    return dut.droid.wifiAwareCreateNetworkSpecifierOob(
506        id, dev_type, peer_mac, None, sec)
507
508
509def configure_power_setting(device, mode, name, value):
510    """Use the command-line API to configure the power setting
511
512  Args:
513    device: Device on which to perform configuration
514    mode: The power mode being set, should be "default", "inactive", or "idle"
515    name: One of the power settings from 'wifiaware set-power'.
516    value: An integer.
517  """
518    device.adb.shell(
519        "cmd wifiaware native_api set-power %s %s %d" % (mode, name, value))
520
521
522def configure_mac_random_interval(device, interval_sec):
523    """Use the command-line API to configure the MAC address randomization
524  interval.
525
526  Args:
527    device: Device on which to perform configuration
528    interval_sec: The MAC randomization interval in seconds. A value of 0
529                  disables all randomization.
530  """
531    device.adb.shell("cmd wifiaware native_api set mac_random_interval_sec %d"
532                     % interval_sec)
533
534
535def configure_ndp_allow_any_override(device, override_api_check):
536    """Use the command-line API to configure whether an NDP Responder may be
537  configured to accept an NDP request from ANY peer.
538
539  By default the target API level of the requesting app determines whether such
540  configuration is permitted. This allows overriding the API check and allowing
541  it.
542
543  Args:
544    device: Device on which to perform configuration.
545    override_api_check: True to allow a Responder to ANY configuration, False to
546                        perform the API level check.
547  """
548    device.adb.shell("cmd wifiaware state_mgr allow_ndp_any %s" %
549                     ("true" if override_api_check else "false"))
550
551
552def config_settings_high_power(device):
553    """Configure device's power settings values to high power mode -
554  whether device is in interactive or non-interactive modes"""
555    configure_power_setting(device, "default", "dw_24ghz",
556                            aconsts.POWER_DW_24_INTERACTIVE)
557    configure_power_setting(device, "default", "dw_5ghz",
558                            aconsts.POWER_DW_5_INTERACTIVE)
559    configure_power_setting(device, "default", "disc_beacon_interval_ms",
560                            aconsts.POWER_DISC_BEACON_INTERVAL_INTERACTIVE)
561    configure_power_setting(device, "default", "num_ss_in_discovery",
562                            aconsts.POWER_NUM_SS_IN_DISC_INTERACTIVE)
563    configure_power_setting(device, "default", "enable_dw_early_term",
564                            aconsts.POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE)
565
566    configure_power_setting(device, "inactive", "dw_24ghz",
567                            aconsts.POWER_DW_24_INTERACTIVE)
568    configure_power_setting(device, "inactive", "dw_5ghz",
569                            aconsts.POWER_DW_5_INTERACTIVE)
570    configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
571                            aconsts.POWER_DISC_BEACON_INTERVAL_INTERACTIVE)
572    configure_power_setting(device, "inactive", "num_ss_in_discovery",
573                            aconsts.POWER_NUM_SS_IN_DISC_INTERACTIVE)
574    configure_power_setting(device, "inactive", "enable_dw_early_term",
575                            aconsts.POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE)
576
577
578def config_settings_low_power(device):
579    """Configure device's power settings values to low power mode - whether
580  device is in interactive or non-interactive modes"""
581    configure_power_setting(device, "default", "dw_24ghz",
582                            aconsts.POWER_DW_24_NON_INTERACTIVE)
583    configure_power_setting(device, "default", "dw_5ghz",
584                            aconsts.POWER_DW_5_NON_INTERACTIVE)
585    configure_power_setting(device, "default", "disc_beacon_interval_ms",
586                            aconsts.POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE)
587    configure_power_setting(device, "default", "num_ss_in_discovery",
588                            aconsts.POWER_NUM_SS_IN_DISC_NON_INTERACTIVE)
589    configure_power_setting(device, "default", "enable_dw_early_term",
590                            aconsts.POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE)
591
592    configure_power_setting(device, "inactive", "dw_24ghz",
593                            aconsts.POWER_DW_24_NON_INTERACTIVE)
594    configure_power_setting(device, "inactive", "dw_5ghz",
595                            aconsts.POWER_DW_5_NON_INTERACTIVE)
596    configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
597                            aconsts.POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE)
598    configure_power_setting(device, "inactive", "num_ss_in_discovery",
599                            aconsts.POWER_NUM_SS_IN_DISC_NON_INTERACTIVE)
600    configure_power_setting(device, "inactive", "enable_dw_early_term",
601                            aconsts.POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE)
602
603
604def config_power_settings(device,
605                          dw_24ghz,
606                          dw_5ghz,
607                          disc_beacon_interval=None,
608                          num_ss_in_disc=None,
609                          enable_dw_early_term=None):
610    """Configure device's discovery window (DW) values to the specified values -
611  whether the device is in interactive or non-interactive mode.
612
613  Args:
614    dw_24ghz: DW interval in the 2.4GHz band.
615    dw_5ghz: DW interval in the 5GHz band.
616    disc_beacon_interval: The discovery beacon interval (in ms). If None then
617                          not set.
618    num_ss_in_disc: Number of spatial streams to use for discovery. If None then
619                    not set.
620    enable_dw_early_term: If True then enable early termination of the DW. If
621                          None then not set.
622  """
623    configure_power_setting(device, "default", "dw_24ghz", dw_24ghz)
624    configure_power_setting(device, "default", "dw_5ghz", dw_5ghz)
625    configure_power_setting(device, "inactive", "dw_24ghz", dw_24ghz)
626    configure_power_setting(device, "inactive", "dw_5ghz", dw_5ghz)
627
628    if disc_beacon_interval is not None:
629        configure_power_setting(device, "default", "disc_beacon_interval_ms",
630                                disc_beacon_interval)
631        configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
632                                disc_beacon_interval)
633
634    if num_ss_in_disc is not None:
635        configure_power_setting(device, "default", "num_ss_in_discovery",
636                                num_ss_in_disc)
637        configure_power_setting(device, "inactive", "num_ss_in_discovery",
638                                num_ss_in_disc)
639
640    if enable_dw_early_term is not None:
641        configure_power_setting(device, "default", "enable_dw_early_term",
642                                enable_dw_early_term)
643        configure_power_setting(device, "inactive", "enable_dw_early_term",
644                                enable_dw_early_term)
645
646
647def create_discovery_config(service_name,
648                            d_type,
649                            ssi=None,
650                            match_filter=None,
651                            match_filter_list=None,
652                            ttl=0,
653                            term_cb_enable=True):
654    """Create a publish discovery configuration based on input parameters.
655
656  Args:
657    service_name: Service name - required
658    d_type: Discovery type (publish or subscribe constants)
659    ssi: Supplemental information - defaults to None
660    match_filter, match_filter_list: The match_filter, only one mechanism can
661                                     be used to specify. Defaults to None.
662    ttl: Time-to-live - defaults to 0 (i.e. non-self terminating)
663    term_cb_enable: True (default) to enable callback on termination, False
664                    means that no callback is called when session terminates.
665  Returns:
666    publish discovery configuration object.
667  """
668    config = {}
669    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
670    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = d_type
671    if ssi is not None:
672        config[aconsts.DISCOVERY_KEY_SSI] = ssi
673    if match_filter is not None:
674        config[aconsts.DISCOVERY_KEY_MATCH_FILTER] = match_filter
675    if match_filter_list is not None:
676        config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = match_filter_list
677    config[aconsts.DISCOVERY_KEY_TTL] = ttl
678    config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_cb_enable
679    return config
680
681
682def add_ranging_to_pub(p_config, enable_ranging):
683    """Add ranging enabled configuration to a publish configuration (only relevant
684  for publish configuration).
685
686  Args:
687    p_config: The Publish discovery configuration.
688    enable_ranging: True to enable ranging, False to disable.
689  Returns:
690    The modified publish configuration.
691  """
692    p_config[aconsts.DISCOVERY_KEY_RANGING_ENABLED] = enable_ranging
693    return p_config
694
695
696def add_ranging_to_sub(s_config, min_distance_mm, max_distance_mm):
697    """Add ranging distance configuration to a subscribe configuration (only
698  relevant to a subscribe configuration).
699
700  Args:
701    s_config: The Subscribe discovery configuration.
702    min_distance_mm, max_distance_mm: The min and max distance specification.
703                                      Used if not None.
704  Returns:
705    The modified subscribe configuration.
706  """
707    if min_distance_mm is not None:
708        s_config[aconsts.DISCOVERY_KEY_MIN_DISTANCE_MM] = min_distance_mm
709    if max_distance_mm is not None:
710        s_config[aconsts.DISCOVERY_KEY_MAX_DISTANCE_MM] = max_distance_mm
711    return s_config
712
713
714def attach_with_identity(dut):
715    """Start an Aware session (attach) and wait for confirmation and identity
716  information (mac address).
717
718  Args:
719    dut: Device under test
720  Returns:
721    id: Aware session ID.
722    mac: Discovery MAC address of this device.
723  """
724    id = dut.droid.wifiAwareAttach(True)
725    wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
726    event = wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
727    mac = event["data"]["mac"]
728
729    return id, mac
730
731
732def create_discovery_pair(p_dut,
733                          s_dut,
734                          p_config,
735                          s_config,
736                          device_startup_offset,
737                          msg_id=None):
738    """Creates a discovery session (publish and subscribe), and waits for
739  service discovery - at that point the sessions are connected and ready for
740  further messaging of data-path setup.
741
742  Args:
743    p_dut: Device to use as publisher.
744    s_dut: Device to use as subscriber.
745    p_config: Publish configuration.
746    s_config: Subscribe configuration.
747    device_startup_offset: Number of seconds to offset the enabling of NAN on
748                           the two devices.
749    msg_id: Controls whether a message is sent from Subscriber to Publisher
750            (so that publisher has the sub's peer ID). If None then not sent,
751            otherwise should be an int for the message id.
752  Returns: variable size list of:
753    p_id: Publisher attach session id
754    s_id: Subscriber attach session id
755    p_disc_id: Publisher discovery session id
756    s_disc_id: Subscriber discovery session id
757    peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
758    peer_id_on_pub: Peer ID of the Subscriber as seen on the Publisher. Only
759                    included if |msg_id| is not None.
760  """
761    p_dut.pretty_name = 'Publisher'
762    s_dut.pretty_name = 'Subscriber'
763
764    # Publisher+Subscriber: attach and wait for confirmation
765    p_id = p_dut.droid.wifiAwareAttach()
766    wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
767    time.sleep(device_startup_offset)
768    s_id = s_dut.droid.wifiAwareAttach()
769    wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
770
771    # Publisher: start publish and wait for confirmation
772    p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
773    wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
774
775    # Subscriber: start subscribe and wait for confirmation
776    s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
777    wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
778
779    # Subscriber: wait for service discovery
780    discovery_event = wait_for_event(s_dut,
781                                     aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
782    peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
783
784    # Optionally send a message from Subscriber to Publisher
785    if msg_id is not None:
786        ping_msg = 'PING'
787
788        # Subscriber: send message to peer (Publisher)
789        s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
790                                         ping_msg, aconsts.MAX_TX_RETRIES)
791        sub_tx_msg_event = wait_for_event(s_dut,
792                                          aconsts.SESSION_CB_ON_MESSAGE_SENT)
793        asserts.assert_equal(
794            msg_id,
795            sub_tx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_ID],
796            'Subscriber -> Publisher message ID corrupted')
797
798        # Publisher: wait for received message
799        pub_rx_msg_event = wait_for_event(
800            p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
801        peer_id_on_pub = pub_rx_msg_event['data'][
802            aconsts.SESSION_CB_KEY_PEER_ID]
803        asserts.assert_equal(
804            ping_msg,
805            pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
806            'Subscriber -> Publisher message corrupted')
807        return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub, peer_id_on_pub
808
809    return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub
810
811
812def create_ib_ndp(p_dut, s_dut, p_config, s_config, device_startup_offset):
813    """Create an NDP (using in-band discovery)
814
815  Args:
816    p_dut: Device to use as publisher.
817    s_dut: Device to use as subscriber.
818    p_config: Publish configuration.
819    s_config: Subscribe configuration.
820    device_startup_offset: Number of seconds to offset the enabling of NAN on
821                           the two devices.
822  """
823    (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
824     peer_id_on_pub) = create_discovery_pair(
825         p_dut, s_dut, p_config, s_config, device_startup_offset, msg_id=9999)
826
827    # Publisher: request network
828    p_req_key = request_network(
829        p_dut,
830        p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub,
831                                                    None))
832
833    # Subscriber: request network
834    s_req_key = request_network(
835        s_dut,
836        s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
837                                                    None))
838
839    # Publisher & Subscriber: wait for network formation
840    p_net_event_nc = wait_for_event_with_keys(
841        p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
842        (cconsts.NETWORK_CB_KEY_EVENT,
843         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
844        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
845    s_net_event_nc = wait_for_event_with_keys(
846        s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
847        (cconsts.NETWORK_CB_KEY_EVENT,
848         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
849        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
850
851    # validate no leak of information
852    asserts.assert_false(
853        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in p_net_event_nc["data"],
854        "Network specifier leak!")
855    asserts.assert_false(
856        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in s_net_event_nc["data"],
857        "Network specifier leak!")
858
859    # note that Pub <-> Sub since IPv6 are of peer's!
860    p_ipv6 = s_net_event_nc["data"][aconsts.NET_CAP_IPV6]
861    s_ipv6 = p_net_event_nc["data"][aconsts.NET_CAP_IPV6]
862
863    p_net_event_lp = wait_for_event_with_keys(
864        p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
865        (cconsts.NETWORK_CB_KEY_EVENT,
866         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
867        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
868    s_net_event_lp = wait_for_event_with_keys(
869        s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
870        (cconsts.NETWORK_CB_KEY_EVENT,
871         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
872        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
873
874    p_aware_if = p_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
875    s_aware_if = s_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
876
877    return p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6, s_ipv6
878
879
880def create_oob_ndp_on_sessions(init_dut, resp_dut, init_id, init_mac, resp_id,
881                               resp_mac):
882    """Create an NDP on top of existing Aware sessions (using OOB discovery)
883
884  Args:
885    init_dut: Initiator device
886    resp_dut: Responder device
887    init_id: Initiator attach session id
888    init_mac: Initiator discovery MAC address
889    resp_id: Responder attach session id
890    resp_mac: Responder discovery MAC address
891  Returns:
892    init_req_key: Initiator network request
893    resp_req_key: Responder network request
894    init_aware_if: Initiator Aware data interface
895    resp_aware_if: Responder Aware data interface
896    init_ipv6: Initiator IPv6 address
897    resp_ipv6: Responder IPv6 address
898  """
899    # Responder: request network
900    resp_req_key = request_network(
901        resp_dut,
902        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
903            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
904
905    # Initiator: request network
906    init_req_key = request_network(
907        init_dut,
908        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
909            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
910
911    # Initiator & Responder: wait for network formation
912    init_net_event_nc = wait_for_event_with_keys(
913        init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
914        (cconsts.NETWORK_CB_KEY_EVENT,
915         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
916        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
917    resp_net_event_nc = wait_for_event_with_keys(
918        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
919        (cconsts.NETWORK_CB_KEY_EVENT,
920         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
921        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
922
923    # validate no leak of information
924    asserts.assert_false(
925        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in init_net_event_nc["data"],
926        "Network specifier leak!")
927    asserts.assert_false(
928        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in resp_net_event_nc["data"],
929        "Network specifier leak!")
930
931    # note that Init <-> Resp since IPv6 are of peer's!
932    resp_ipv6 = init_net_event_nc["data"][aconsts.NET_CAP_IPV6]
933    init_ipv6 = resp_net_event_nc["data"][aconsts.NET_CAP_IPV6]
934
935    init_net_event_lp = wait_for_event_with_keys(
936        init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
937        (cconsts.NETWORK_CB_KEY_EVENT,
938         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
939        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
940    resp_net_event_lp = wait_for_event_with_keys(
941        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
942        (cconsts.NETWORK_CB_KEY_EVENT,
943         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
944        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
945
946    init_aware_if = init_net_event_lp['data'][
947        cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
948    resp_aware_if = resp_net_event_lp['data'][
949        cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
950
951    return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
952            init_ipv6, resp_ipv6)
953
954
955def create_oob_ndp(init_dut, resp_dut):
956    """Create an NDP (using OOB discovery)
957
958  Args:
959    init_dut: Initiator device
960    resp_dut: Responder device
961  """
962    init_dut.pretty_name = 'Initiator'
963    resp_dut.pretty_name = 'Responder'
964
965    # Initiator+Responder: attach and wait for confirmation & identity
966    init_id = init_dut.droid.wifiAwareAttach(True)
967    wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
968    init_ident_event = wait_for_event(init_dut,
969                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
970    init_mac = init_ident_event['data']['mac']
971    resp_id = resp_dut.droid.wifiAwareAttach(True)
972    wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
973    resp_ident_event = wait_for_event(resp_dut,
974                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
975    resp_mac = resp_ident_event['data']['mac']
976
977    # wait for for devices to synchronize with each other - there are no other
978    # mechanisms to make sure this happens for OOB discovery (except retrying
979    # to execute the data-path request)
980    time.sleep(WAIT_FOR_CLUSTER)
981
982    (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
983     init_ipv6, resp_ipv6) = create_oob_ndp_on_sessions(
984         init_dut, resp_dut, init_id, init_mac, resp_id, resp_mac)
985
986    return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
987            init_ipv6, resp_ipv6)
988