[AWARE] Support multiple NDI

Add tests to validate that multiple NDIs are supported. This is tested
by requesting NDPs to the same peer but with varying security configs.

Bug: 63635857
Test: Integrated test suite passes
Change-Id: I5c369fbaf60e38eff61ee4d80c65dd82a05630c4
diff --git a/acts/tests/google/wifi/aware/performance/ThroughputTest.py b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
index 5b59d92..6cf1046 100644
--- a/acts/tests/google/wifi/aware/performance/ThroughputTest.py
+++ b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
@@ -32,6 +32,9 @@
 
   SERVICE_NAME = "GoogleTestServiceXYZ"
 
+  PASSPHRASE = "This is some random passphrase - very very secure!!"
+  PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
+
   def __init__(self, controllers):
     AwareBaseTest.__init__(self, controllers)
 
@@ -209,7 +212,7 @@
       self.log.info("iPerf3: Sent = %d bps Received = %d bps",
                     results[i]["tx_rate"], results[i]["rx_rate"])
 
-    ########################################################################
+  ########################################################################
 
   def test_iperf_single_ndp_aware_only_ib(self):
     """Measure throughput using iperf on a single NDP, with Aware enabled and
@@ -235,3 +238,141 @@
     self.run_iperf_max_ndp_aware_only(results=results)
     asserts.explicit_pass(
         "test_iperf_max_ndp_aware_only_oob passes", extras=results)
+
+  ########################################################################
+
+  def run_iperf_max_ndi_aware_only(self, sec_configs, results):
+    """Measure iperf performance on multiple NDPs between 2 devices using
+    different security configurations (and hence different NDIs). Test with
+    Aware enabled and no infrastructure connection - i.e. device is not
+    associated to an AP.
+
+    The security configuration can be:
+    - None: open
+    - String: passphrase
+    - otherwise: PMK (byte array)
+
+    Args:
+      sec_configs: list of security configurations
+      results: Dictionary into which to place test results.
+    """
+    init_dut = self.android_devices[0]
+    init_dut.pretty_name = "Initiator"
+    resp_dut = self.android_devices[1]
+    resp_dut.pretty_name = "Responder"
+
+    asserts.skip_if(init_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]
+                    < len(sec_configs) or
+                    resp_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]
+                    < len(sec_configs),
+                    "Initiator or Responder do not support multiple NDIs")
+
+
+    init_id, init_mac = autils.attach_with_identity(init_dut)
+    resp_id, resp_mac = autils.attach_with_identity(resp_dut)
+
+    # wait for for devices to synchronize with each other - there are no other
+    # mechanisms to make sure this happens for OOB discovery (except retrying
+    # to execute the data-path request)
+    time.sleep(autils.WAIT_FOR_CLUSTER)
+
+    resp_req_keys = []
+    init_req_keys = []
+    resp_aware_ifs = []
+    init_aware_ifs = []
+    resp_aware_ipv6s = []
+    init_aware_ipv6s = []
+
+    for sec in sec_configs:
+      # Responder: request network
+      resp_req_key = autils.request_network(resp_dut,
+                                            autils.get_network_specifier(
+                                                resp_dut, resp_id,
+                                                aconsts.DATA_PATH_RESPONDER,
+                                                init_mac, sec))
+      resp_req_keys.append(resp_req_key)
+
+      # Initiator: request network
+      init_req_key = autils.request_network(init_dut,
+                                            autils.get_network_specifier(
+                                                init_dut, init_id,
+                                                aconsts.DATA_PATH_INITIATOR,
+                                                resp_mac, sec))
+      init_req_keys.append(init_req_key)
+
+      # Wait for network
+      init_net_event = autils.wait_for_event_with_keys(
+          init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_TIMEOUT,
+          (cconsts.NETWORK_CB_KEY_EVENT,
+           cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+          (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+      resp_net_event = autils.wait_for_event_with_keys(
+          resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_TIMEOUT,
+          (cconsts.NETWORK_CB_KEY_EVENT,
+           cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+          (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+      resp_aware_ifs.append(
+          resp_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
+      init_aware_ifs.append(
+          init_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
+
+      resp_aware_ipv6s.append(
+          autils.get_ipv6_addr(resp_dut, resp_aware_ifs[-1]))
+      init_aware_ipv6s.append(
+          autils.get_ipv6_addr(init_dut, init_aware_ifs[-1]))
+
+    self.log.info("Initiator interfaces/ipv6: %s / %s", init_aware_ifs,
+                  init_aware_ipv6s)
+    self.log.info("Responder interfaces/ipv6: %s / %s", resp_aware_ifs,
+                  resp_aware_ipv6s)
+
+    # create threads, start them, and wait for all to finish
+    base_port = 5000
+    q = queue.Queue()
+    threads = []
+    for i in range(len(sec_configs)):
+      threads.append(
+          threading.Thread(
+              target=self.run_iperf,
+              args=(q, init_dut, resp_dut, resp_aware_ifs[i], init_aware_ipv6s[
+                  i], base_port + i)))
+
+    for thread in threads:
+      thread.start()
+
+    for thread in threads:
+      thread.join()
+
+    # release requests
+    for resp_req_key in resp_req_keys:
+      resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+    for init_req_key in init_req_keys:
+      init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+
+    # collect data
+    for i in range(len(sec_configs)):
+      results[i] = {}
+      result, data = q.get()
+      asserts.assert_true(result,
+                          "Failure starting/running iperf3 in client mode")
+      self.log.debug(pprint.pformat(data))
+      data_json = json.loads("".join(data))
+      if "error" in data_json:
+        asserts.fail(
+            "iperf run failed: %s" % data_json["error"], extras=data_json)
+      results[i]["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
+      results[i]["rx_rate"] = data_json["end"]["sum_received"][
+        "bits_per_second"]
+      self.log.info("iPerf3: Sent = %d bps Received = %d bps",
+                    results[i]["tx_rate"], results[i]["rx_rate"])
+
+  def test_iperf_max_ndi_aware_only_passphrases(self):
+    """Test throughput for multiple NDIs configured with different passphrases.
+    """
+    results = {}
+    self.run_iperf_max_ndi_aware_only(
+        [self.PASSPHRASE, self.PASSPHRASE2], results=results)
+    asserts.explicit_pass(
+        "test_iperf_max_ndi_aware_only_passphrases passes", extras=results)