summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Qiu <zqiu@google.com>2016-11-21 15:03:40 -0800
committerPeter Qiu <zqiu@google.com>2016-12-06 09:32:37 -0800
commitb40ba9e6ef82ac6c82869d1b562701483b8f1fc2 (patch)
tree1cafae6721d0028efb299d28767daa13ff6355fb
parentb8da1eaf580f53fec4c744fc2dc1c5822c105caf (diff)
hotspot2: cleanup DomainMatcher
- Cleanup the implementation - Add documentation - Add unit tests Bug: 33050774 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I694ab0fc24ed9e8bcd9cd24a4d22208e123dcc5f
-rw-r--r--service/java/com/android/server/wifi/hotspot2/DomainMatcher.java199
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java5
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java3
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.java203
4 files changed, 339 insertions, 71 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java b/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java
index 3f93101ba..ce60c55b0 100644
--- a/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java
+++ b/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java
@@ -1,45 +1,99 @@
+/*
+ * 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 com.android.server.wifi.hotspot2;
+import android.text.TextUtils;
+
import com.android.server.wifi.hotspot2.Utils;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+/**
+ * Utility class for matching domain names.
+ */
public class DomainMatcher {
+ public static final int MATCH_NONE = 0;
+ public static final int MATCH_PRIMARY = 1;
+ public static final int MATCH_SECONDARY = 2;
- public enum Match {None, Primary, Secondary}
-
+ /**
+ * The root of the Label tree.
+ */
private final Label mRoot;
+ /**
+ * Label tree representation for the domain name. Labels are delimited by "." in the domain
+ * name.
+ *
+ * For example, the tree representation of "android.google.com" as a primary domain:
+ * [com, None] -> [google, None] -> [android, Primary]
+ *
+ */
private static class Label {
private final Map<String, Label> mSubDomains;
- private final Match mMatch;
+ private int mMatch;
- private Label(Match match) {
+ Label(int match) {
mMatch = match;
- mSubDomains = match == Match.None ? new HashMap<String, Label>() : null;
+ mSubDomains = new HashMap<String, Label>();
}
- private void addDomain(Iterator<String> labels, Match match) {
+ /**
+ * Add sub-domains to this label.
+ *
+ * @param labels The iterator of domain label strings
+ * @param match The match status of the domain
+ */
+ public void addDomain(Iterator<String> labels, int match) {
String labelName = labels.next();
- if (labels.hasNext()) {
- Label subLabel = new Label(Match.None);
+ // Create the Label object if it doesn't exist yet.
+ Label subLabel = mSubDomains.get(labelName);
+ if (subLabel == null) {
+ subLabel = new Label(MATCH_NONE);
mSubDomains.put(labelName, subLabel);
+ }
+
+ if (labels.hasNext()) {
+ // Adding sub-domain.
subLabel.addDomain(labels, match);
} else {
- mSubDomains.put(labelName, new Label(match));
+ // End of the domain, update the match status.
+ subLabel.mMatch = match;
}
}
- private Label getSubLabel(String labelString) {
+ /**
+ * Return the Label for the give label string.
+ * @param labelString The label string to look for
+ * @return {@link Label}
+ */
+ public Label getSubLabel(String labelString) {
return mSubDomains.get(labelString);
}
- public Match getMatch() {
+ /**
+ * Return the match status
+ *
+ * @return The match status
+ */
+ public int getMatch() {
return mMatch;
}
@@ -64,46 +118,90 @@ public class DomainMatcher {
}
}
- public DomainMatcher(List<String> primary, List<List<String>> secondary) {
- mRoot = new Label(Match.None);
- for (List<String> secondaryLabel : secondary) {
- mRoot.addDomain(secondaryLabel.iterator(), Match.Secondary);
+ public DomainMatcher(String primaryDomain, List<String> secondaryDomains) {
+ // Create the root label.
+ mRoot = new Label(MATCH_NONE);
+
+ // Add secondary domains.
+ if (secondaryDomains != null) {
+ for (String domain : secondaryDomains) {
+ if (!TextUtils.isEmpty(domain)) {
+ List<String> secondaryLabel = Utils.splitDomain(domain);
+ mRoot.addDomain(secondaryLabel.iterator(), MATCH_SECONDARY);
+ }
+ }
+ }
+
+ // Add primary domain, primary overwrites secondary.
+ if (!TextUtils.isEmpty(primaryDomain)) {
+ List<String> primaryLabel = Utils.splitDomain(primaryDomain);
+ mRoot.addDomain(primaryLabel.iterator(), MATCH_PRIMARY);
}
- // Primary overwrites secondary.
- mRoot.addDomain(primary.iterator(), Match.Primary);
}
/**
- * Check if domain is either a the same or a sub-domain of any of the domains in the domain tree
- * in this matcher, i.e. all or or a sub-set of the labels in domain matches a path in the tree.
- * @param domain Domain to be checked.
- * @return None if domain is not a sub-domain, Primary if it matched one of the primary domains
- * or Secondary if it matched on of the secondary domains.
+ * Check if domain is either the same or a sub-domain of any of the domains in the
+ * domain tree in this matcher, i.e. all or a sub-set of the labels in domain matches
+ * a path in the tree.
+ *
+ * This will have precedence for matching primary domain over secondary domain if both
+ * are found.
+ *
+ * For example, with primary domain set to "test.google.com" and secondary domain set to
+ * "google.com":
+ * "test2.test.google.com" -> Match.Primary
+ * "test1.google.com" -> Match.Secondary
+ *
+ * @param domainName Domain name to be checked.
+ * @return The match status
*/
- public Match isSubDomain(List<String> domain) {
+ public int isSubDomain(String domainName) {
+ if (TextUtils.isEmpty(domainName)) {
+ return MATCH_NONE;
+ }
+ List<String> domainLabels = Utils.splitDomain(domainName);
Label label = mRoot;
- for (String labelString : domain) {
+ int match = MATCH_NONE;
+ for (String labelString : domainLabels) {
label = label.getSubLabel(labelString);
if (label == null) {
- return Match.None;
- } else if (label.getMatch() != Match.None) {
- return label.getMatch();
+ break;
+ } else if (label.getMatch() != MATCH_NONE) {
+ match = label.getMatch();
+ if (match == MATCH_PRIMARY) {
+ break;
+ }
}
}
- return Match.None; // Domain is a super domain
+ return match;
}
- public static boolean arg2SubdomainOfArg1(List<String> arg1, List<String> arg2) {
- if (arg2.size() < arg1.size()) {
+ /**
+ * Check if domain2 is a sub-domain of domain1.
+ *
+ * @param domain1 The string of the first domain
+ * @param domain2 The string of the second domain
+ * @return true if the second domain is the sub-domain of the first
+ */
+ public static boolean arg2SubdomainOfArg1(String domain1, String domain2) {
+ if (TextUtils.isEmpty(domain1) || TextUtils.isEmpty(domain2)) {
+ return false;
+ }
+
+ List<String> labels1 = Utils.splitDomain(domain1);
+ List<String> labels2 = Utils.splitDomain(domain2);
+
+ // domain2 must be the same or longer than domain1 in order to be a sub-domain.
+ if (labels2.size() < labels1.size()) {
return false;
}
- Iterator<String> l1 = arg1.iterator();
- Iterator<String> l2 = arg2.iterator();
+ Iterator<String> l1 = labels1.iterator();
+ Iterator<String> l2 = labels2.iterator();
while(l1.hasNext()) {
- if (!l1.next().equals(l2.next())) {
+ if (!TextUtils.equals(l1.next(), l2.next())) {
return false;
}
}
@@ -114,35 +212,4 @@ public class DomainMatcher {
public String toString() {
return "Domain matcher " + mRoot;
}
-
- private static final String[] TestDomains = {
- "garbage.apple.com",
- "apple.com",
- "com",
- "jan.android.google.com.",
- "jan.android.google.com",
- "android.google.com",
- "google.com",
- "jan.android.google.net.",
- "jan.android.google.net",
- "android.google.net",
- "google.net",
- "net.",
- "."
- };
-
- public static void main(String[] args) {
- DomainMatcher dm1 = new DomainMatcher(Utils.splitDomain("android.google.com"),
- Collections.<List<String>>emptyList());
- for (String domain : TestDomains) {
- System.out.println(domain + ": " + dm1.isSubDomain(Utils.splitDomain(domain)));
- }
- List<List<String>> secondaries = new ArrayList<List<String>>();
- secondaries.add(Utils.splitDomain("apple.com"));
- secondaries.add(Utils.splitDomain("net"));
- DomainMatcher dm2 = new DomainMatcher(Utils.splitDomain("android.google.com"), secondaries);
- for (String domain : TestDomains) {
- System.out.println(domain + ": " + dm2.isSubDomain(Utils.splitDomain(domain)));
- }
- }
}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java
index 58c4913e5..95fbc893c 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java
@@ -59,12 +59,11 @@ public class NAIRealmData {
// TODO(b/32714185): revisit this when integrating the new Passpoint implementation and add
// unit tests for this.
- public int match(List<String> credLabels, EAPMethod credMethod) {
+ public int match(String realmToMatch, EAPMethod credMethod) {
int realmMatch = AuthMatch.None;
if (!mRealms.isEmpty()) {
for (String realm : mRealms) {
- List<String> labels = Utils.splitDomain(realm);
- if (DomainMatcher.arg2SubdomainOfArg1(credLabels, labels)) {
+ if (DomainMatcher.arg2SubdomainOfArg1(realmToMatch, realm)) {
realmMatch = AuthMatch.Realm;
break;
}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java
index de80f585a..e1bafa85f 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java
@@ -51,10 +51,9 @@ public class NAIRealmElement extends ANQPElement {
if (mRealmData.isEmpty())
return AuthMatch.Indeterminate;
- List<String> credLabels = Utils.splitDomain(realm);
int best = AuthMatch.None;
for (NAIRealmData realmData : mRealmData) {
- int match = realmData.match(credLabels, credMethod);
+ int match = realmData.match(realm, credMethod);
if (match > best) {
best = match;
if (best == AuthMatch.Exact) {
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.java
new file mode 100644
index 000000000..c0159217d
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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 com.android.server.wifi.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.DomainMatcher}.
+ */
+@SmallTest
+public class DomainMatcherTest {
+ private static final String PRIMARY_DOMAIN = "google.com";
+ private static final String SECONDARY_DOMAIN1 = "android.com";
+ private static final String SECONDARY_DOMAIN2 = "testing.test.com";
+
+ /**
+ * Test data for isSubDomain function.
+ */
+ private static final Map<String, Integer> TEST_DOMAIN_MAP = new HashMap<>();
+ static {
+ TEST_DOMAIN_MAP.put("", DomainMatcher.MATCH_NONE);
+ TEST_DOMAIN_MAP.put("com", DomainMatcher.MATCH_NONE);
+ TEST_DOMAIN_MAP.put("test.com", DomainMatcher.MATCH_NONE);
+ TEST_DOMAIN_MAP.put("google.com", DomainMatcher.MATCH_PRIMARY);
+ TEST_DOMAIN_MAP.put("test.google.com", DomainMatcher.MATCH_PRIMARY);
+ TEST_DOMAIN_MAP.put("android.com", DomainMatcher.MATCH_SECONDARY);
+ TEST_DOMAIN_MAP.put("test.android.com", DomainMatcher.MATCH_SECONDARY);
+ TEST_DOMAIN_MAP.put("testing.test.com", DomainMatcher.MATCH_SECONDARY);
+ TEST_DOMAIN_MAP.put("adbcd.testing.test.com", DomainMatcher.MATCH_SECONDARY);
+ }
+
+ /**
+ * Test data for arg2SubdomainOfArg1 function.
+ */
+ private static final Map<Pair<String, String>, Boolean> TEST_ARG_DOMAIN_MAP = new HashMap<>();
+ static {
+ TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test.com", "abc.test.com"), true);
+ TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test.com", "ad.abc.test.com"), true);
+ TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("com", "test.com"), true);
+ TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("abc.test.com", "test.com"), false);
+ TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test1.com", "test.com"), false);
+ TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test.com", "com"), false);
+ }
+
+ /**
+ * Verify that creating a matcher with empty domains doesn't cause any exceptions.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void createMatcherWithEmptyDomains() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher(null, null);
+ assertEquals(DomainMatcher.MATCH_NONE, domainMatcher.isSubDomain("google.com"));
+ }
+
+ /**
+ * Verify that matching a null domain doesn't cause any exceptions.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void matchNullDomain() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher(PRIMARY_DOMAIN,
+ Arrays.asList(SECONDARY_DOMAIN1, SECONDARY_DOMAIN2));
+ assertEquals(DomainMatcher.MATCH_NONE, domainMatcher.isSubDomain(null));
+ }
+
+ /**
+ * Verify the domain matching expectations based on the predefined {@link #TEST_DOMAIN_MAP}.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void matchTestDomains() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher(PRIMARY_DOMAIN,
+ Arrays.asList(SECONDARY_DOMAIN1, SECONDARY_DOMAIN2));
+ for (Map.Entry<String, Integer> entry : TEST_DOMAIN_MAP.entrySet()) {
+ assertEquals(entry.getValue().intValue(), domainMatcher.isSubDomain(entry.getKey()));
+ }
+ }
+
+ /**
+ * Verify that the correct match status is returned when a domain matches both primary
+ * and secondary domain (primary domain have precedence over secondary).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void matchDomainWithBothPrimaryAndSecondary() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher(PRIMARY_DOMAIN,
+ Arrays.asList(PRIMARY_DOMAIN));
+ assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain(PRIMARY_DOMAIN));
+ }
+
+ /**
+ * Verify domain matching expectation when the secondary domain is a sub-domain of the
+ * primary domain.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void matchDomainWhenSecondaryIsSubdomainOfPrimary() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher("google.com",
+ Arrays.asList("test.google.com"));
+ assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("google.com"));
+ assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("test.google.com"));
+ assertEquals(DomainMatcher.MATCH_PRIMARY,
+ domainMatcher.isSubDomain("abcd.test.google.com"));
+ }
+
+ /**
+ * Verify domain matching expectations when the secondary domain is a sub-domain of the
+ * primary domain.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void matchDomainWhenPrimaryIsSubdomainOfSecondary() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher("test.google.com",
+ Arrays.asList("google.com"));
+ assertEquals(DomainMatcher.MATCH_SECONDARY, domainMatcher.isSubDomain("google.com"));
+ assertEquals(DomainMatcher.MATCH_SECONDARY, domainMatcher.isSubDomain("test2.google.com"));
+ assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("test.google.com"));
+ assertEquals(DomainMatcher.MATCH_PRIMARY,
+ domainMatcher.isSubDomain("adcd.test.google.com"));
+ }
+
+ /**
+ * Verify domain matching expectations when the domain names contained empty label (domain
+ * name that contained "..").
+ *
+ * @throws Exception
+ */
+ @Test
+ public void matchDomainWithEmptyLabel() throws Exception {
+ DomainMatcher domainMatcher = new DomainMatcher("test.google..com",
+ Arrays.asList("google..com"));
+ assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("test.google..com"));
+ assertEquals(DomainMatcher.MATCH_SECONDARY, domainMatcher.isSubDomain("google..com"));
+ }
+
+ /**
+ * Verify domain matching expectation for arg2SubdomainOfArg1 based on predefined
+ * {@link #TEST_ARG_DOMAIN_MAP}.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyArg2SubdomainOfArg1() throws Exception {
+ for (Map.Entry<Pair<String, String>, Boolean> entry : TEST_ARG_DOMAIN_MAP.entrySet()) {
+ assertEquals(entry.getValue().booleanValue(),
+ DomainMatcher.arg2SubdomainOfArg1(entry.getKey().first, entry.getKey().second));
+ }
+ }
+
+ /**
+ * Verify that arg2SubdomainOfArg1 works as expected when pass in null domains.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void arg2SubdomainOfArg1WithNullDomain() throws Exception {
+ assertFalse(DomainMatcher.arg2SubdomainOfArg1(null, "test.com"));
+ assertFalse(DomainMatcher.arg2SubdomainOfArg1("test.com", null));
+ assertFalse(DomainMatcher.arg2SubdomainOfArg1(null, null));
+ }
+
+ /**
+ * Verify that arg2SubdomainOfArg1 works as expected when domain contains empty label.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void arg2SubdomainOfArg1WithEmptyLabel() throws Exception {
+ assertTrue(DomainMatcher.arg2SubdomainOfArg1("test..com", "adsf.test..com"));
+ }
+
+}