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