[AWARE] Test multiple requests for same NDP

Issue multiple identical requests for an NDP using different
network requests which resolve to the same underlying (canonic)
NDP specification.

Bug: 64125969
Test: new test passes, all existing integration test pass
Change-Id: I2dfa9f833c39e623db6364374095a43c1979ba53
diff --git a/acts/tests/google/wifi/aware/functional/DataPathTest.py b/acts/tests/google/wifi/aware/functional/DataPathTest.py
index 8414c1d..dbe52e8 100644
--- a/acts/tests/google/wifi/aware/functional/DataPathTest.py
+++ b/acts/tests/google/wifi/aware/functional/DataPathTest.py
@@ -16,6 +16,7 @@
 
 import time
 
+from acts import asserts
 from acts.test_utils.net import connectivity_const as cconsts
 from acts.test_utils.wifi.aware import aware_const as aconsts
 from acts.test_utils.wifi.aware import aware_test_utils as autils
@@ -334,8 +335,10 @@
 
     # Publisher & Subscriber: fail on network formation
     time.sleep(autils.EVENT_NDP_TIMEOUT)
-    autils.fail_on_event(p_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
-    autils.fail_on_event(s_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+    autils.fail_on_event_with_keys(p_dut, cconsts.EVENT_NETWORK_CALLBACK, 0,
+                                   (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+    autils.fail_on_event_with_keys(s_dut, cconsts.EVENT_NETWORK_CALLBACK, 0,
+                                   (cconsts.NETWORK_CB_KEY_ID, s_req_key))
 
     # clean-up
     p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
@@ -414,8 +417,10 @@
 
     # Initiator & Responder: fail on network formation
     time.sleep(autils.EVENT_NDP_TIMEOUT)
-    autils.fail_on_event(init_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
-    autils.fail_on_event(resp_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+    autils.fail_on_event_with_keys(init_dut, cconsts.EVENT_NETWORK_CALLBACK, 0,
+                                   (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+    autils.fail_on_event_with_keys(resp_dut, cconsts.EVENT_NETWORK_CALLBACK, 0,
+                                   (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
 
     # clean-up
     resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
@@ -727,3 +732,162 @@
     self.run_mismatched_oob_data_path_test(
         init_encr_type=self.ENCR_TYPE_PASSPHRASE,
         resp_encr_type=self.ENCR_TYPE_OPEN)
+
+
+  ##########################################################################
+
+  def attach_with_identity(self, dut):
+    """Start an Aware session (attach) and wait for confirmation and identity
+    information (mac address).
+
+    Args:
+      dut: Device under test
+    Returns:
+      id: Aware session ID.
+      mac: Discovery MAC address of this device.
+    """
+    id = dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+    event = autils.wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    mac = event["data"]["mac"]
+
+    return id, mac
+
+  def wait_for_request_responses(self, dut, req_keys, aware_ifs):
+    """Wait for network request confirmation for all request keys.
+
+    Args:
+      dut: Device under test
+      req_keys: (in) A list of the network requests
+      aware_ifs: (out) A list into which to append the network interface
+    """
+    num_events = 0
+    while num_events != len(req_keys):
+      event = autils.wait_for_event(dut, cconsts.EVENT_NETWORK_CALLBACK)
+      if (event["data"][cconsts.NETWORK_CB_KEY_EVENT] ==
+          cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+        if event["data"][cconsts.NETWORK_CB_KEY_ID] in req_keys:
+          num_events = num_events + 1
+          aware_ifs.append(event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
+        else:
+          self.log.info("Received an unexpected connectivity, the revoked "
+                        "network request probably went through -- %s", event)
+
+  def test_multiple_identical_networks(self):
+    """Validate that creating multiple networks between 2 devices, each network
+    with identical configuration is supported over a single NDP.
+
+    Verify that the interface and IPv6 address is the same for all networks.
+    """
+    init_dut = self.android_devices[0]
+    init_dut.pretty_name = "Initiator"
+    resp_dut = self.android_devices[1]
+    resp_dut.pretty_name = "Responder"
+
+    N = 2 # first iteration (must be 2 to give us a chance to cancel the first)
+    M = 5 # second iteration
+
+    init_ids = []
+    resp_ids = []
+
+    # Initiator+Responder: attach and wait for confirmation & identity
+    # create 10 sessions to be used in the different (but identical) NDPs
+    for i in range(N + M):
+      id, init_mac = self.attach_with_identity(init_dut)
+      init_ids.append(id)
+      id, resp_mac = self.attach_with_identity(resp_dut)
+      resp_ids.append(id)
+
+    # 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 = []
+
+    # issue N quick requests for identical NDPs - without waiting for result
+    # tests whether pre-setup multiple NDP procedure
+    for i in range(N):
+      # Responder: request network
+      resp_req_keys.append(autils.request_network(
+          resp_dut,
+          resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+              resp_ids[i], aconsts.DATA_PATH_RESPONDER, init_mac, None)))
+
+      # Initiator: request network
+      init_req_keys.append(autils.request_network(
+          init_dut,
+          init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+              init_ids[i], aconsts.DATA_PATH_INITIATOR, resp_mac, None)))
+
+    # remove the first request (hopefully before completed) testing that NDP
+    # is still created
+    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_keys[0])
+    resp_req_keys.remove(resp_req_keys[0])
+    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_keys[0])
+    init_req_keys.remove(init_req_keys[0])
+
+    # wait for network formation for all initial requests
+    self.wait_for_request_responses(resp_dut, resp_req_keys, resp_aware_ifs)
+    self.wait_for_request_responses(init_dut, init_req_keys, init_aware_ifs)
+
+    # issue N more requests for the same NDPs - tests post-setup multiple NDP
+    for i in range(M):
+      # Responder: request network
+      resp_req_keys.append(autils.request_network(
+          resp_dut,
+          resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+              resp_ids[N + i], aconsts.DATA_PATH_RESPONDER, init_mac, None)))
+
+      # Initiator: request network
+      init_req_keys.append(autils.request_network(
+          init_dut,
+          init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+              init_ids[N + i], aconsts.DATA_PATH_INITIATOR, resp_mac, None)))
+
+    # wait for network formation for all subsequent requests
+    self.wait_for_request_responses(resp_dut, resp_req_keys[N - 1:],
+                                    resp_aware_ifs)
+    self.wait_for_request_responses(init_dut, init_req_keys[N - 1:],
+                                    init_aware_ifs)
+
+    # determine whether all interfaces are identical (single NDP) - can't really
+    # test the IPv6 address since it is not part of the callback event - it is
+    # simply obtained from the system (so we'll always get the same for the same
+    # interface)
+    init_aware_ifs = list(set(init_aware_ifs))
+    resp_aware_ifs = list(set(resp_aware_ifs))
+
+    self.log.info("Interface names: I=%s, R=%s", init_aware_ifs, resp_aware_ifs)
+    self.log.info("Initiator requests: %s", init_req_keys)
+    self.log.info("Responder requests: %s", resp_req_keys)
+
+    asserts.assert_equal(
+        len(init_aware_ifs), 1, "Multiple initiator interfaces")
+    asserts.assert_equal(
+        len(resp_aware_ifs), 1, "Multiple responder interfaces")
+
+    self.log.info("Interface IPv6 (using ifconfig): I=%s, R=%s",
+                  autils.get_ipv6_addr(init_dut, init_aware_ifs[0]),
+                  autils.get_ipv6_addr(resp_dut, resp_aware_ifs[0]))
+
+    for i in range(init_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]):
+      if_name = "%s%d" % (aconsts.AWARE_NDI_PREFIX, i)
+      init_ipv6 = autils.get_ipv6_addr(init_dut, if_name)
+      resp_ipv6 = autils.get_ipv6_addr(resp_dut, if_name)
+
+      asserts.assert_equal(
+          init_ipv6 is None, if_name not in init_aware_ifs,
+          "Initiator interface %s in unexpected state" % if_name)
+      asserts.assert_equal(
+          resp_ipv6 is None, if_name not in resp_aware_ifs,
+          "Responder interface %s in unexpected state" % if_name)
+
+    # 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)