Merge "[AWARE] Add a group network specifier allowing matches to multiple requests" into oc-mr1-dev
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
new file mode 100644
index 0000000..7c99c49
--- /dev/null
+++ b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * A network specifier object used to represent the capabilities of an network agent. A collection
+ * of multiple WifiAwareNetworkSpecifier objects whose matching critiera (satisfiedBy) is an OR:
+ * a match on any of the network specifiers in the collection is a match.
+ *
+ * This class is not intended for use in network requests.
+ *
+ * @hide
+ */
+public class WifiAwareAgentNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    private static final String TAG = "WifiAwareAgentNs";
+
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private Set<ByteArrayWrapper> mNetworkSpecifiers = new HashSet<>();
+    private MessageDigest mDigester;
+
+    public WifiAwareAgentNetworkSpecifier() {
+        // do nothing, already initialized to empty
+    }
+
+    public WifiAwareAgentNetworkSpecifier(WifiAwareNetworkSpecifier ns) {
+        initialize();
+        mNetworkSpecifiers.add(convert(ns));
+    }
+
+    public WifiAwareAgentNetworkSpecifier(WifiAwareNetworkSpecifier[] nss) {
+        initialize();
+        for (WifiAwareNetworkSpecifier ns : nss) {
+            mNetworkSpecifiers.add(convert(ns));
+        }
+    }
+
+    public boolean isEmpty() {
+        return mNetworkSpecifiers.isEmpty();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeArray(mNetworkSpecifiers.toArray());
+    }
+
+    public static final Creator<WifiAwareAgentNetworkSpecifier> CREATOR =
+            new Creator<WifiAwareAgentNetworkSpecifier>() {
+                @Override
+                public WifiAwareAgentNetworkSpecifier createFromParcel(Parcel in) {
+                    WifiAwareAgentNetworkSpecifier agentNs = new WifiAwareAgentNetworkSpecifier();
+                    Object[] objs = in.readArray(null);
+                    for (Object obj : objs) {
+                        agentNs.mNetworkSpecifiers.add((ByteArrayWrapper) obj);
+                    }
+                    return agentNs;
+                }
+
+                @Override
+                public WifiAwareAgentNetworkSpecifier[] newArray(int size) {
+                    return new WifiAwareAgentNetworkSpecifier[size];
+                }
+            };
+
+    @Override
+    public int hashCode() {
+        return mNetworkSpecifiers.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof WifiAwareAgentNetworkSpecifier)) {
+            return false;
+        }
+        return mNetworkSpecifiers.equals(((WifiAwareAgentNetworkSpecifier) obj).mNetworkSpecifiers);
+    }
+
+    @Override
+    public String toString() {
+        StringJoiner sj = new StringJoiner(",");
+        for (ByteArrayWrapper baw: mNetworkSpecifiers) {
+            sj.add(baw.toString());
+        }
+        return sj.toString();
+    }
+
+    @Override
+    public boolean satisfiedBy(NetworkSpecifier other) {
+        if (!(other instanceof WifiAwareAgentNetworkSpecifier)) {
+            return false;
+        }
+        WifiAwareAgentNetworkSpecifier otherNs = (WifiAwareAgentNetworkSpecifier) other;
+
+        // called as old.satifiedBy(new): satisfied if old contained in new
+        for (ByteArrayWrapper baw: mNetworkSpecifiers) {
+            if (!otherNs.mNetworkSpecifiers.contains(baw)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public boolean satisfiesAwareNetworkSpecifier(WifiAwareNetworkSpecifier ns) {
+        if (VDBG) Log.v(TAG, "satisfiesAwareNetworkSpecifier: ns=" + ns);
+        ByteArrayWrapper nsBytes = convert(ns);
+        return mNetworkSpecifiers.contains(nsBytes);
+    }
+
+    @Override
+    public void assertValidFromUid(int requestorUid) {
+        throw new SecurityException(
+                "WifiAwareAgentNetworkSpecifier should not be used in network requests");
+    }
+
+    private void initialize() {
+        try {
+            mDigester = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            Log.e(TAG, "Can not instantiate a SHA-256 digester!? Will match nothing.");
+            return;
+        }
+    }
+
+    private ByteArrayWrapper convert(WifiAwareNetworkSpecifier ns) {
+        if (mDigester == null) {
+            return null;
+        }
+
+        Parcel parcel = Parcel.obtain();
+        ns.writeToParcel(parcel, 0);
+        byte[] bytes = parcel.marshall();
+
+        mDigester.reset();
+        mDigester.update(bytes);
+        return new ByteArrayWrapper(mDigester.digest());
+    }
+
+    private static class ByteArrayWrapper implements Parcelable {
+        private byte[] mData;
+
+        ByteArrayWrapper(byte[] data) {
+            mData = data;
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(mData);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof ByteArrayWrapper)) {
+                return false;
+            }
+            return Arrays.equals(((ByteArrayWrapper) obj).mData, mData);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBlob(mData);
+        }
+
+        public static final Creator<ByteArrayWrapper> CREATOR =
+                new Creator<ByteArrayWrapper>() {
+                    @Override
+                    public ByteArrayWrapper createFromParcel(Parcel in) {
+                        return new ByteArrayWrapper(in.readBlob());
+                    }
+
+                    @Override
+                    public ByteArrayWrapper[] newArray(int size) {
+                        return new ByteArrayWrapper[size];
+                    }
+                };
+
+        @Override
+        public String toString() {
+            return new String(HexEncoding.encode(mData));
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
index eeabbfa..6e37fcf 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -193,6 +193,9 @@
     @Override
     public boolean satisfiedBy(NetworkSpecifier other) {
         // MatchAllNetworkSpecifier is taken care in NetworkCapabilities#satisfiedBySpecifier.
+        if (other instanceof WifiAwareAgentNetworkSpecifier) {
+            return ((WifiAwareAgentNetworkSpecifier) other).satisfiesAwareNetworkSpecifier(this);
+        }
         return equals(other);
     }
 
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
new file mode 100644
index 0000000..2dd0537
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit test harness for WifiAwareAgentNetworkSpecifier class.
+ */
+@SmallTest
+public class WifiAwareAgentNetworkSpecifierTest {
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    @Test
+    public void testParcel() {
+        final int numNs = 10;
+
+        Set<WifiAwareNetworkSpecifier> nsSet = new HashSet<>();
+        for (int i = 0; i < numNs; ++i) {
+            nsSet.add(getDummyNetworkSpecifier(10 + i));
+        }
+        WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier(
+                nsSet.toArray(new WifiAwareNetworkSpecifier[numNs]));
+
+        Parcel parcelW = Parcel.obtain();
+        dut.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        WifiAwareAgentNetworkSpecifier rereadDut =
+                WifiAwareAgentNetworkSpecifier.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(dut, rereadDut);
+    }
+
+    /**
+     * Validate that an empty agent network specifier doesn't match any base network specifier.
+     */
+    @Test
+    public void testEmptyDoesntMatchAnything() {
+        WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier();
+        WifiAwareNetworkSpecifier ns = getDummyNetworkSpecifier(6);
+        collector.checkThat("No match expected", ns.satisfiedBy(dut), equalTo(false));
+    }
+
+    /**
+     * Validate that an agent network specifier constructed with a single entry matches that entry,
+     * and only that entry.
+     */
+    @Test
+    public void testSingleMatch() {
+        WifiAwareNetworkSpecifier nsThis = getDummyNetworkSpecifier(6);
+        WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier(nsThis);
+        WifiAwareNetworkSpecifier nsOther = getDummyNetworkSpecifier(8);
+        collector.checkThat("Match expected", nsThis.satisfiedBy(dut), equalTo(true));
+        collector.checkThat("No match expected", nsOther.satisfiedBy(dut), equalTo(false));
+    }
+
+    /**
+     * Validate that an agent network specifier constructed with multiple entries matches all those
+     * entries - but none other.
+     */
+    @Test
+    public void testMultipleMatchesAllMembers() {
+        final int numNs = 10;
+
+        Set<WifiAwareNetworkSpecifier> nsSet = new HashSet<>();
+        for (int i = 0; i < numNs; ++i) {
+            nsSet.add(getDummyNetworkSpecifier(10 + i));
+        }
+
+        WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier(
+                nsSet.toArray(new WifiAwareNetworkSpecifier[numNs]));
+        WifiAwareNetworkSpecifier nsOther = getDummyNetworkSpecifier(10000);
+
+        for (WifiAwareNetworkSpecifier nsThis: nsSet) {
+            collector.checkThat("Match expected", nsThis.satisfiedBy(dut), equalTo(true));
+        }
+        collector.checkThat("No match expected", nsOther.satisfiedBy(dut), equalTo(false));
+    }
+
+    /**
+     * Validate that agent network specifier matches against a super-set.
+     */
+    @Test
+    public void testMatchSuperset() {
+        final int numNs = 10;
+
+        Set<WifiAwareNetworkSpecifier> nsSet = new HashSet<>();
+        for (int i = 0; i < numNs; ++i) {
+            nsSet.add(getDummyNetworkSpecifier(10 + i));
+        }
+
+        WifiAwareAgentNetworkSpecifier oldNs = new WifiAwareAgentNetworkSpecifier(
+                nsSet.toArray(new WifiAwareNetworkSpecifier[nsSet.size()]));
+
+        nsSet.add(getDummyNetworkSpecifier(100 + numNs));
+        WifiAwareAgentNetworkSpecifier newNs = new WifiAwareAgentNetworkSpecifier(
+                nsSet.toArray(new WifiAwareNetworkSpecifier[nsSet.size()]));
+
+        collector.checkThat("Match expected", oldNs.satisfiedBy(newNs), equalTo(true));
+    }
+
+    /**
+     * Validate that agent network specifier does not match against a sub-set.
+     */
+    @Test
+    public void testNoMatchSubset() {
+        final int numNs = 10;
+
+        Set<WifiAwareNetworkSpecifier> nsSet = new HashSet<>();
+        for (int i = 0; i < numNs; ++i) {
+            nsSet.add(getDummyNetworkSpecifier(10 + i));
+        }
+
+        WifiAwareAgentNetworkSpecifier newNs = new WifiAwareAgentNetworkSpecifier(
+                nsSet.toArray(new WifiAwareNetworkSpecifier[nsSet.size()]));
+
+        nsSet.add(getDummyNetworkSpecifier(100 + numNs));
+        WifiAwareAgentNetworkSpecifier oldNs = new WifiAwareAgentNetworkSpecifier(
+                nsSet.toArray(new WifiAwareNetworkSpecifier[nsSet.size()]));
+
+        collector.checkThat("Match unexpected", oldNs.satisfiedBy(newNs), equalTo(false));
+    }
+
+    /**
+     * Validate that agent network specifier cannot be used as in network requests - i.e. that
+     * throws an exception when queried for UID validity.
+     */
+    @Test(expected = SecurityException.class)
+    public void testNoUsageInRequest() {
+        WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier();
+
+        dut.assertValidFromUid(0);
+    }
+
+    // utilities
+
+    /**
+     * Returns a WifiAwareNetworkSpecifier with dummy (but valid) entries. Each can be
+     * differentiated (made unique) by specifying a different client ID.
+     */
+    WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) {
+        return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
+                WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6],
+                null, null, 0);
+    }
+}