Merge "[AWARE] Support multiple NAN data interfaces (NDI)" into oc-mr1-dev
am: bad0137ff3

Change-Id: I8dc0e337b858f5d05d696d13dc3bc10a7ea93b45
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index 16888c8..efeca65 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -60,10 +60,11 @@
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 /**
  * Manages Aware data-path lifetime: interface creation/deletion, data-path setup and tear-down.
@@ -323,6 +324,8 @@
              * - The discovery session (pub/sub ID) must match.
              * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC ==
              *   accept (otherwise matching) requests from any peer MAC.
+             * - The request must be pending (i.e. we could have completed requests for the same
+             *   parameters)
              */
             if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) {
                 continue;
@@ -333,6 +336,11 @@
                 continue;
             }
 
+            if (entry.getValue().state
+                    != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+                continue;
+            }
+
             networkSpecifier = entry.getKey();
             nnri = entry.getValue();
             break;
@@ -884,18 +892,43 @@
     }
 
     /**
-     * Select one of the existing interfaces for the new network request.
+     * Select one of the existing interfaces for the new network request. A request is canonical
+     * (otherwise it wouldn't be executed).
      *
-     * TODO: for now there is only a single interface - simply pick it.
+     * Construct a list of all interfaces currently used to communicate to the peer. The remaining
+     * interfaces are available for use for this request - if none are left then the request should
+     * fail (signaled to the caller by returning a null).
      */
     private String selectInterfaceForRequest(AwareNetworkRequestInformation req) {
-        Iterator<String> it = mInterfaces.iterator();
-        if (it.hasNext()) {
-            return it.next();
+        SortedSet<String> potential = new TreeSet<>(mInterfaces);
+        Set<String> used = new HashSet<>();
+
+        if (VDBG) {
+            Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache="
+                    + mNetworkRequestsCache);
         }
 
-        Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - but no interfaces available!");
+        for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) {
+            if (nnri == req) {
+                continue;
+            }
 
+            if (Arrays.equals(req.peerDiscoveryMac, nnri.peerDiscoveryMac)) {
+                used.add(nnri.interfaceName);
+            }
+        }
+
+        if (VDBG) {
+            Log.v(TAG, "selectInterfaceForRequest: potential=" + potential + ", used=" + used);
+        }
+
+        for (String ifName: potential) {
+            if (!used.contains(ifName)) {
+                return ifName;
+            }
+        }
+
+        Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!");
         return null;
     }
 
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 2f424db..2121160 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -167,10 +167,6 @@
                     capabilities.maxSubscribeInterfaceAddresses;
             frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites;
 
-            // TODO (b/63635857): enable framework support of >1 NDI
-            // Until then: force corresponding capability to 1.
-            frameworkCapabilities.maxNdiInterfaces = 1;
-
             mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities);
         } else {
             Log.e(TAG, "notifyCapabilitiesResponse: error code=" + status.status + " ("
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java b/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
index 41eef69..a2f6046 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
@@ -55,6 +55,7 @@
                 for (DelegatedShellCommand dsc: mDelegatedCommands.values()) {
                     dsc.onReset();
                 }
+                return 0;
             } else {
                 DelegatedShellCommand delegatedCmd = null;
                 if (!TextUtils.isEmpty(cmd)) {
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
index 227a196..61143d9 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -19,6 +19,7 @@
 import static android.hardware.wifi.V1_0.NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED;
 
 import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -84,6 +85,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Unit test harness for WifiAwareDataPathStateManager class.
@@ -359,8 +362,7 @@
         final byte pubSubId = 58;
         final int requestorId = 1341234;
         final int ndpId = 2;
-        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
-        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+
         final int[] startOrder = {0, 1, 2};
         final int[] endOrder = {1, 0, 2};
         int networkRequestId = 0;
@@ -378,8 +380,10 @@
         boolean first = true;
         for (int i : startOrder) {
             networkRequestId += 1;
-            peerDiscoveryMac[5] = (byte) (peerDiscoveryMac[5] + 1);
-            peerDataPathMac[5] = (byte) (peerDataPathMac[5] + 1);
+            byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+            byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+            peerDiscoveryMac[5] = (byte) (peerDiscoveryMac[5] + i);
+            peerDataPathMac[5] = (byte) (peerDataPathMac[5] + i);
 
             // (0) initialize
             ress[i] = initDataPathEndPoint(first, clientId, (byte) (pubSubId + i),
@@ -474,16 +478,16 @@
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (1) initialize all clients
-        Messenger messenger = initOobDataPathEndPoint(true, clientId, inOrder, inOrderM);
+        Messenger messenger = initOobDataPathEndPoint(true, 1, clientId, inOrder, inOrderM);
         for (int i = 1; i < numRequestsPre + numRequestsPost; ++i) {
-            initOobDataPathEndPoint(false, clientId + i, inOrder, inOrderM);
+            initOobDataPathEndPoint(false, 1, clientId + i, inOrder, inOrderM);
         }
 
         // (2) make 3 network requests (all identical under the hood)
         for (int i = 0; i < numRequestsPre; ++i) {
             nrs[i] = getDirectNetworkRequest(clientId + i,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, null,
-                    null);
+                    null, i);
 
             Message reqNetworkMsg = Message.obtain();
             reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
@@ -532,7 +536,7 @@
         for (int i = numRequestsPre; i < numRequestsPre + numRequestsPost; ++i) {
             nrs[i] = getDirectNetworkRequest(clientId + i,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, null,
-                    null);
+                    null, i);
 
             Message reqNetworkMsg = Message.obtain();
             reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
@@ -570,6 +574,74 @@
                 mAwareMetricsMock, mMockNwMgt);
     }
 
+    /**
+     * Validate that multiple NDP requests to the same peer target different NDIs.
+     */
+    @Test
+    public void testMultipleNdi() throws Exception {
+        final int numNdis = 5;
+        final int clientId = 123;
+        final int ndpId = 5;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> ifNameCaptor = ArgumentCaptor.forClass(String.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNwMgt);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) initialize all clients
+        Messenger messenger = initOobDataPathEndPoint(true, numNdis, clientId, inOrder, inOrderM);
+        for (int i = 1; i < numNdis + 3; ++i) {
+            initOobDataPathEndPoint(false, numNdis, clientId + i, inOrder, inOrderM);
+        }
+
+        // (2) make N network requests: each unique
+        Set<String> interfaces = new HashSet<>();
+        for (int i = 0; i < numNdis + 1; ++i) {
+            byte[] pmk = new byte[32];
+            pmk[0] = (byte) i;
+
+            NetworkRequest nr = getDirectNetworkRequest(clientId + i,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, pmk,
+                    null, i);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nr;
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+            mMockLooper.dispatchAll();
+
+            if (i < numNdis) {
+                inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
+                        eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                        ifNameCaptor.capture(), eq(pmk), eq(null), eq(true), any());
+                interfaces.add(ifNameCaptor.getValue());
+
+                mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId + i);
+                mDut.onDataPathConfirmNotification(ndpId + i, peerDataPathMac, true, 0, null);
+                mMockLooper.dispatchAll();
+
+                inOrder.verify(mMockNwMgt).setInterfaceUp(anyString());
+                inOrder.verify(mMockNwMgt).enableIpv6(anyString());
+                inOrder.verify(mMockCm).registerNetworkAgent(any(), any(), any(), any(), anyInt(),
+                        any());
+                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                        eq(true), anyLong());
+                inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any());
+            }
+        }
+
+        // verify that each interface name is unique
+        assertEquals("Number of unique interface names", numNdis, interfaces.size());
+
+        verifyNoMoreInteractions(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mAwareMetricsMock, mMockNwMgt);
+    }
+
     /*
      * Initiator tests
      */
@@ -929,7 +1001,7 @@
             nr = getDirectNetworkRequest(clientId,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR,
                     provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null,
-                    providePassphrase ? passphrase : null);
+                    providePassphrase ? passphrase : null, 0);
         } else {
             nr = getSessionNetworkRequest(clientId, res.mSessionId,
                     provideMac ? res.mPeerHandle : null, providePmk ? pmk : null,
@@ -1038,7 +1110,7 @@
             nr = getDirectNetworkRequest(clientId,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
                     provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null,
-                    providePassphrase ? passphrase : null);
+                    providePassphrase ? passphrase : null, 0);
         } else {
             nr = getSessionNetworkRequest(clientId, res.mSessionId,
                     provideMac ? res.mPeerHandle : null, providePmk ? pmk : null,
@@ -1183,7 +1255,7 @@
     }
 
     private NetworkRequest getDirectNetworkRequest(int clientId, int role, byte[] peer,
-            byte[] pmk, String passphrase) throws Exception {
+            byte[] pmk, String passphrase, int requestId) throws Exception {
         final IWifiAwareManager mockAwareService = mock(IWifiAwareManager.class);
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mockAwareService);
@@ -1220,7 +1292,7 @@
         nc.setLinkDownstreamBandwidthKbps(1);
         nc.setSignalStrength(1);
 
-        return new NetworkRequest(nc, 0, 0, NetworkRequest.Type.REQUEST);
+        return new NetworkRequest(nc, 0, requestId, NetworkRequest.Type.REQUEST);
     }
 
     private DataPathEndPointInfo initDataPathEndPoint(boolean isFirstIteration, int clientId,
@@ -1237,7 +1309,7 @@
 
         Messenger messenger = null;
         if (isFirstIteration) {
-            messenger = initOobDataPathEndPoint(true, clientId, inOrder, inOrderM);
+            messenger = initOobDataPathEndPoint(true, 1, clientId, inOrder, inOrderM);
         }
 
         if (doPublish) {
@@ -1271,8 +1343,8 @@
                 isFirstIteration ? messenger : null);
     }
 
-    private Messenger initOobDataPathEndPoint(boolean startUpSequence, int clientId,
-            InOrder inOrder, InOrder inOrderM) throws Exception {
+    private Messenger initOobDataPathEndPoint(boolean startUpSequence, int maxNdiInterfaces,
+            int clientId, InOrder inOrder, InOrder inOrderM) throws Exception {
         final int pid = 2000;
         final String callingPackage = "com.android.somePackage";
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
@@ -1282,7 +1354,7 @@
         ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
 
         Capabilities capabilities = new Capabilities();
-        capabilities.maxNdiInterfaces = 1;
+        capabilities.maxNdiInterfaces = maxNdiInterfaces;
 
         if (startUpSequence) {
             // (0) start/registrations
@@ -1322,12 +1394,14 @@
                 any());
 
         if (startUpSequence) {
-            inOrder.verify(mMockNative).createAwareNetworkInterface(transactionId.capture(),
-                    strCaptor.capture());
-            collector.checkThat("interface created -- 0", sAwareInterfacePrefix + 0,
-                    equalTo(strCaptor.getValue()));
-            mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
-            mMockLooper.dispatchAll();
+            for (int i = 0; i < maxNdiInterfaces; ++i) {
+                inOrder.verify(mMockNative).createAwareNetworkInterface(transactionId.capture(),
+                        strCaptor.capture());
+                collector.checkThat("interface created -- " + i, sAwareInterfacePrefix + i,
+                        equalTo(strCaptor.getValue()));
+                mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+                mMockLooper.dispatchAll();
+            }
             return messengerCaptor.getValue();
         }