diff options
author | Peter Qiu <zqiu@google.com> | 2016-11-21 15:03:40 -0800 |
---|---|---|
committer | Peter Qiu <zqiu@google.com> | 2016-12-06 09:32:37 -0800 |
commit | b40ba9e6ef82ac6c82869d1b562701483b8f1fc2 (patch) | |
tree | 1cafae6721d0028efb299d28767daa13ff6355fb | |
parent | b8da1eaf580f53fec4c744fc2dc1c5822c105caf (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
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")); + } + +} |