1#!/usr/bin/python3.4
2#
3#   Copyright 2017 - 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
17import json
18import pprint
19import queue
20import threading
21import time
22
23from acts import asserts
24from acts.test_utils.net import connectivity_const as cconsts
25from acts.test_utils.wifi.aware import aware_const as aconsts
26from acts.test_utils.wifi.aware import aware_test_utils as autils
27from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
28
29
30class ThroughputTest(AwareBaseTest):
31  """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
32
33  SERVICE_NAME = "GoogleTestServiceXYZ"
34
35  PASSPHRASE = "This is some random passphrase - very very secure!!"
36  PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
37
38  def __init__(self, controllers):
39    super(ThroughputTest, self).__init__(controllers)
40
41  def request_network(self, dut, ns):
42    """Request a Wi-Fi Aware network.
43
44    Args:
45      dut: Device
46      ns: Network specifier
47    Returns: the request key
48    """
49    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
50    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
51
52  def run_iperf_single_ndp_aware_only(self, use_ib, results):
53    """Measure iperf performance on a single NDP, with Aware enabled and no
54    infrastructure connection - i.e. device is not associated to an AP.
55
56    Args:
57      use_ib: True to use in-band discovery, False to use out-of-band discovery.
58      results: Dictionary into which to place test results.
59    """
60    init_dut = self.android_devices[0]
61    resp_dut = self.android_devices[1]
62
63    if use_ib:
64      # note: Publisher = Responder, Subscribe = Initiator
65      (resp_req_key, init_req_key, resp_aware_if,
66       init_aware_if, resp_ipv6, init_ipv6) = autils.create_ib_ndp(
67           resp_dut, init_dut,
68           autils.create_discovery_config(self.SERVICE_NAME,
69                                          aconsts.PUBLISH_TYPE_UNSOLICITED),
70           autils.create_discovery_config(self.SERVICE_NAME,
71                                          aconsts.SUBSCRIBE_TYPE_PASSIVE),
72           self.device_startup_offset)
73    else:
74      (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
75      resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
76    self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
77    self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
78                  resp_ipv6)
79
80    # Run iperf3
81    result, data = init_dut.run_iperf_server("-D")
82    asserts.assert_true(result, "Can't start iperf3 server")
83
84    result, data = resp_dut.run_iperf_client(
85        "%s%%%s" % (init_ipv6, resp_aware_if), "-6 -J")
86    self.log.debug(data)
87    asserts.assert_true(result,
88                        "Failure starting/running iperf3 in client mode")
89    self.log.debug(pprint.pformat(data))
90
91    # clean-up
92    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
93    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
94
95    # Collect results
96    data_json = json.loads("".join(data))
97    if "error" in data_json:
98      asserts.fail(
99          "iperf run failed: %s" % data_json["error"], extras=data_json)
100    results["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
101    results["rx_rate"] = data_json["end"]["sum_received"]["bits_per_second"]
102    self.log.info("iPerf3: Sent = %d bps Received = %d bps", results["tx_rate"],
103                  results["rx_rate"])
104
105  def run_iperf(self, q, dut, peer_dut, peer_aware_if, dut_ipv6, port):
106    """Runs iperf and places results in the queue.
107
108    Args:
109      q: The queue into which to place the results
110      dut: The DUT on which to run the iperf server command.
111      peer_dut: The DUT on which to run the iperf client command.
112      peer_aware_if: The interface on the DUT.
113      dut_ipv6: The IPv6 address of the server.
114      port: The port to use for the server and client.
115    """
116    result, data = dut.run_iperf_server("-D -p %d" % port)
117    asserts.assert_true(result, "Can't start iperf3 server")
118
119    result, data = peer_dut.run_iperf_client(
120        "%s%%%s" % (dut_ipv6, peer_aware_if), "-6 -J -p %d" % port)
121    self.log.debug(data)
122    q.put((result, data))
123
124  def run_iperf_max_ndp_aware_only(self, results):
125    """Measure iperf performance on the max number of concurrent OOB NDPs, with
126    Aware enabled and no infrastructure connection - i.e. device is not
127    associated to an AP.
128
129    Note: the test requires MAX_NDP + 1 devices to be validated. If these are
130    not available the test will fail.
131
132    Args:
133      results: Dictionary into which to place test results.
134    """
135    dut = self.android_devices[0]
136
137    # get max NDP: using first available device (assumes all devices are the
138    # same)
139    max_ndp = dut.aware_capabilities[aconsts.CAP_MAX_NDP_SESSIONS]
140    asserts.assert_true(len(self.android_devices) > max_ndp,
141                        'Needed %d devices to run the test, have %d' %
142                        (max_ndp + 1, len(self.android_devices)))
143
144    # create all NDPs
145    dut_aware_if = None
146    dut_ipv6 = None
147    peers_aware_ifs = []
148    peers_ipv6s = []
149    dut_requests = []
150    peers_requests = []
151    for i in range(max_ndp):
152      (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
153       resp_ipv6) = autils.create_oob_ndp(dut, self.android_devices[i + 1])
154      self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
155      self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
156                    resp_ipv6)
157
158      dut_requests.append(init_req_key)
159      peers_requests.append(resp_req_key)
160      if dut_aware_if is None:
161        dut_aware_if = init_aware_if
162      else:
163        asserts.assert_equal(
164            dut_aware_if, init_aware_if,
165            "DUT (Initiator) interface changed on subsequent NDPs!?")
166      if dut_ipv6 is None:
167        dut_ipv6 = init_ipv6
168      else:
169        asserts.assert_equal(
170            dut_ipv6, init_ipv6,
171            "DUT (Initiator) IPv6 changed on subsequent NDPs!?")
172      peers_aware_ifs.append(resp_aware_if)
173      peers_ipv6s.append(resp_ipv6)
174
175    # create threads, start them, and wait for all to finish
176    base_port = 5000
177    q = queue.Queue()
178    threads = []
179    for i in range(max_ndp):
180      threads.append(
181          threading.Thread(
182              target=self.run_iperf,
183              args=(q, dut, self.android_devices[i + 1], peers_aware_ifs[i],
184                    dut_ipv6, base_port + i)))
185
186    for thread in threads:
187      thread.start()
188
189    for thread in threads:
190      thread.join()
191
192    # cleanup
193    for i in range(max_ndp):
194      dut.droid.connectivityUnregisterNetworkCallback(dut_requests[i])
195      self.android_devices[i + 1].droid.connectivityUnregisterNetworkCallback(
196          peers_requests[i])
197
198    # collect data
199    for i in range(max_ndp):
200      results[i] = {}
201      result, data = q.get()
202      asserts.assert_true(result,
203                          "Failure starting/running iperf3 in client mode")
204      self.log.debug(pprint.pformat(data))
205      data_json = json.loads("".join(data))
206      if "error" in data_json:
207        asserts.fail(
208            "iperf run failed: %s" % data_json["error"], extras=data_json)
209      results[i]["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
210      results[i]["rx_rate"] = data_json["end"]["sum_received"][
211          "bits_per_second"]
212      self.log.info("iPerf3: Sent = %d bps Received = %d bps",
213                    results[i]["tx_rate"], results[i]["rx_rate"])
214
215  ########################################################################
216
217  def test_iperf_single_ndp_aware_only_ib(self):
218    """Measure throughput using iperf on a single NDP, with Aware enabled and
219    no infrastructure connection. Use in-band discovery."""
220    results = {}
221    self.run_iperf_single_ndp_aware_only(use_ib=True, results=results)
222    asserts.explicit_pass(
223        "test_iperf_single_ndp_aware_only_ib passes", extras=results)
224
225  def test_iperf_single_ndp_aware_only_oob(self):
226    """Measure throughput using iperf on a single NDP, with Aware enabled and
227    no infrastructure connection. Use out-of-band discovery."""
228    results = {}
229    self.run_iperf_single_ndp_aware_only(use_ib=False, results=results)
230    asserts.explicit_pass(
231        "test_iperf_single_ndp_aware_only_oob passes", extras=results)
232
233  def test_iperf_max_ndp_aware_only_oob(self):
234    """Measure throughput using iperf on all possible concurrent NDPs, with
235    Aware enabled and no infrastructure connection. Use out-of-band discovery.
236    """
237    results = {}
238    self.run_iperf_max_ndp_aware_only(results=results)
239    asserts.explicit_pass(
240        "test_iperf_max_ndp_aware_only_oob passes", extras=results)
241
242  ########################################################################
243
244  def run_iperf_max_ndi_aware_only(self, sec_configs, results):
245    """Measure iperf performance on multiple NDPs between 2 devices using
246    different security configurations (and hence different NDIs). Test with
247    Aware enabled and no infrastructure connection - i.e. device is not
248    associated to an AP.
249
250    The security configuration can be:
251    - None: open
252    - String: passphrase
253    - otherwise: PMK (byte array)
254
255    Args:
256      sec_configs: list of security configurations
257      results: Dictionary into which to place test results.
258    """
259    init_dut = self.android_devices[0]
260    init_dut.pretty_name = "Initiator"
261    resp_dut = self.android_devices[1]
262    resp_dut.pretty_name = "Responder"
263
264    asserts.skip_if(init_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]
265                    < len(sec_configs) or
266                    resp_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]
267                    < len(sec_configs),
268                    "Initiator or Responder do not support multiple NDIs")
269
270
271    init_id, init_mac = autils.attach_with_identity(init_dut)
272    resp_id, resp_mac = autils.attach_with_identity(resp_dut)
273
274    # wait for for devices to synchronize with each other - there are no other
275    # mechanisms to make sure this happens for OOB discovery (except retrying
276    # to execute the data-path request)
277    time.sleep(autils.WAIT_FOR_CLUSTER)
278
279    resp_req_keys = []
280    init_req_keys = []
281    resp_aware_ifs = []
282    init_aware_ifs = []
283    resp_aware_ipv6s = []
284    init_aware_ipv6s = []
285
286    for sec in sec_configs:
287      # Responder: request network
288      resp_req_key = autils.request_network(resp_dut,
289                                            autils.get_network_specifier(
290                                                resp_dut, resp_id,
291                                                aconsts.DATA_PATH_RESPONDER,
292                                                init_mac, sec))
293      resp_req_keys.append(resp_req_key)
294
295      # Initiator: request network
296      init_req_key = autils.request_network(init_dut,
297                                            autils.get_network_specifier(
298                                                init_dut, init_id,
299                                                aconsts.DATA_PATH_INITIATOR,
300                                                resp_mac, sec))
301      init_req_keys.append(init_req_key)
302
303      # Wait for network
304      init_net_event = autils.wait_for_event_with_keys(
305          init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
306          (cconsts.NETWORK_CB_KEY_EVENT,
307           cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
308          (cconsts.NETWORK_CB_KEY_ID, init_req_key))
309      resp_net_event = autils.wait_for_event_with_keys(
310          resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
311          (cconsts.NETWORK_CB_KEY_EVENT,
312           cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
313          (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
314
315      resp_aware_ifs.append(
316          resp_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
317      init_aware_ifs.append(
318          init_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
319
320      resp_aware_ipv6s.append(
321          autils.get_ipv6_addr(resp_dut, resp_aware_ifs[-1]))
322      init_aware_ipv6s.append(
323          autils.get_ipv6_addr(init_dut, init_aware_ifs[-1]))
324
325    self.log.info("Initiator interfaces/ipv6: %s / %s", init_aware_ifs,
326                  init_aware_ipv6s)
327    self.log.info("Responder interfaces/ipv6: %s / %s", resp_aware_ifs,
328                  resp_aware_ipv6s)
329
330    # create threads, start them, and wait for all to finish
331    base_port = 5000
332    q = queue.Queue()
333    threads = []
334    for i in range(len(sec_configs)):
335      threads.append(
336          threading.Thread(
337              target=self.run_iperf,
338              args=(q, init_dut, resp_dut, resp_aware_ifs[i], init_aware_ipv6s[
339                  i], base_port + i)))
340
341    for thread in threads:
342      thread.start()
343
344    for thread in threads:
345      thread.join()
346
347    # release requests
348    for resp_req_key in resp_req_keys:
349      resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
350    for init_req_key in init_req_keys:
351      init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
352
353
354    # collect data
355    for i in range(len(sec_configs)):
356      results[i] = {}
357      result, data = q.get()
358      asserts.assert_true(result,
359                          "Failure starting/running iperf3 in client mode")
360      self.log.debug(pprint.pformat(data))
361      data_json = json.loads("".join(data))
362      if "error" in data_json:
363        asserts.fail(
364            "iperf run failed: %s" % data_json["error"], extras=data_json)
365      results[i]["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
366      results[i]["rx_rate"] = data_json["end"]["sum_received"][
367        "bits_per_second"]
368      self.log.info("iPerf3: Sent = %d bps Received = %d bps",
369                    results[i]["tx_rate"], results[i]["rx_rate"])
370
371  def test_iperf_max_ndi_aware_only_passphrases(self):
372    """Test throughput for multiple NDIs configured with different passphrases.
373    """
374    results = {}
375    self.run_iperf_max_ndi_aware_only(
376        [self.PASSPHRASE, self.PASSPHRASE2], results=results)
377    asserts.explicit_pass(
378        "test_iperf_max_ndi_aware_only_passphrases passes", extras=results)
379