OSDN Git Service

hotspot2: add support for complete PerProviderSubscription/Policy subtree
authorPeter Qiu <zqiu@google.com>
Tue, 17 Jan 2017 19:41:27 +0000 (11:41 -0800)
committerPeter Qiu <zqiu@google.com>
Wed, 25 Jan 2017 18:14:37 +0000 (10:14 -0800)
Added Policy to PasspointConfiguration and the corresponding parser
support in PPSMOParser.

While there, fix a typo in node name "CertSHA256Fingerprint" under
CertificateCredential.

Bug: 34198926
Test: frameworks/base/wifi/test/runtests.sh
Change-Id: Iabe27cd83b6658ed7d4f895d7fe2255fe2094ebb

13 files changed:
wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl [new file with mode: 0644]
wifi/java/android/net/wifi/hotspot2/pps/Policy.java [new file with mode: 0644]
wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl [new file with mode: 0644]
wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java [new file with mode: 0644]
wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
wifi/tests/assets/pps/PerProviderSubscription.xml
wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java [new file with mode: 0644]
wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java [new file with mode: 0644]

index 643753a..ad7477c 100644 (file)
@@ -18,6 +18,7 @@ package android.net.wifi.hotspot2;
 
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
 import android.os.Parcelable;
 import android.os.Parcel;
 
@@ -28,13 +29,12 @@ import android.os.Parcel;
  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
  * Release 2 Technical Specification.
  *
- * Currently, only HomeSP and Credential subtrees are supported.
- *
  * @hide
  */
 public final class PasspointConfiguration implements Parcelable {
     public HomeSP homeSp = null;
     public Credential credential = null;
+    public Policy policy = null;
 
     /**
      * Constructor for creating PasspointConfiguration with default values.
@@ -54,6 +54,9 @@ public final class PasspointConfiguration implements Parcelable {
             if (source.credential != null) {
                 credential = new Credential(source.credential);
             }
+            if (source.policy != null) {
+                policy = new Policy(source.policy);
+            }
         }
     }
 
@@ -66,6 +69,7 @@ public final class PasspointConfiguration implements Parcelable {
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(homeSp, flags);
         dest.writeParcelable(credential, flags);
+        dest.writeParcelable(policy, flags);
     }
 
     @Override
@@ -77,9 +81,10 @@ public final class PasspointConfiguration implements Parcelable {
             return false;
         }
         PasspointConfiguration that = (PasspointConfiguration) thatObject;
-        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
-                (credential == null ? that.credential == null :
-                    credential.equals(that.credential));
+        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
+                && (credential == null ? that.credential == null :
+                    credential.equals(that.credential))
+                && (policy == null) ? that.policy == null : policy.equals(that.policy);
     }
 
     /**
@@ -94,6 +99,9 @@ public final class PasspointConfiguration implements Parcelable {
         if (credential == null || !credential.validate()) {
             return false;
         }
+        if (policy != null && !policy.validate()) {
+            return false;
+        }
         return true;
     }
 
@@ -104,6 +112,7 @@ public final class PasspointConfiguration implements Parcelable {
                 PasspointConfiguration config = new PasspointConfiguration();
                 config.homeSp = in.readParcelable(null);
                 config.credential = in.readParcelable(null);
+                config.policy = in.readParcelable(null);
                 return config;
             }
             @Override
index 98fd0f3..e557c64 100644 (file)
@@ -19,6 +19,8 @@ package android.net.wifi.hotspot2.omadm;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -168,13 +170,40 @@ public final class PPSMOParser {
     private static final String NODE_INNER_METHOD = "InnerMethod";
     private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
     private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
-    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
+    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
     private static final String NODE_REALM = "Realm";
     private static final String NODE_SIM = "SIM";
     private static final String NODE_SIM_IMSI = "IMSI";
     private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
 
     /**
+     * Fields under Policy subtree.
+     */
+    private static final String NODE_POLICY = "Policy";
+    private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+            "PreferredRoamingPartnerList";
+    private static final String NODE_FQDN_MATCH = "FQDN_Match";
+    private static final String NODE_PRIORITY = "Priority";
+    private static final String NODE_COUNTRY = "Country";
+    private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+    private static final String NODE_NETWORK_TYPE = "NetworkType";
+    private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+    private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+    private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+    private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+    private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+    private static final String NODE_RESTRICTION = "Restriction";
+    private static final String NODE_URI = "URI";
+    private static final String NODE_TRUST_ROOT = "TrustRoot";
+    private static final String NODE_CERT_URL = "CertURL";
+    private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+    private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+    private static final String NODE_IP_PROTOCOL = "IPProtocol";
+    private static final String NODE_PORT_NUMBER = "PortNumber";
+    private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+    private static final String NODE_OTHER = "Other";
+
+    /**
      * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
      */
     private static final String PPS_MO_URN =
@@ -551,6 +580,9 @@ public final class PPSMOParser {
                 case NODE_CREDENTIAL:
                     config.credential = parseCredential(child);
                     break;
+                case NODE_POLICY:
+                    config.policy = parsePolicy(child);
+                    break;
                 default:
                     throw new ParsingException("Unknown node: " + child.getName());
             }
@@ -999,6 +1031,425 @@ public final class PPSMOParser {
     }
 
     /**
+     * Parse configurations under PerProviderSubscription/Policy subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+     * @return {@link Policy}
+     * @throws ParsingException
+     */
+    private static Policy parsePolicy(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Policy");
+        }
+
+        Policy policy = new Policy();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+                    policy.preferredRoamingPartnerList = parsePreferredRoamingPartnerList(child);
+                    break;
+                case NODE_MIN_BACKHAUL_THRESHOLD:
+                    parseMinBackhaulThreshold(child, policy);
+                    break;
+                case NODE_POLICY_UPDATE:
+                    policy.policyUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SP_EXCLUSION_LIST:
+                    policy.excludedSsidList = parseSpExclusionList(child);
+                    break;
+                case NODE_REQUIRED_PROTO_PORT_TUPLE:
+                    policy.requiredProtoPortMap = parseRequiredProtoPortTuple(child);
+                    break;
+                case NODE_MAXIMUM_BSS_LOAD_VALUE:
+                    policy.maximumBssLoadValue = parseInteger(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Policy: " + child.getName());
+            }
+        }
+        return policy;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+     * @return List of {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+        }
+        List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            partnerList.add(parsePreferredRoamingPartner(child));
+        }
+        return partnerList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+     * @return {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+                    + "instance");
+        }
+
+        Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN_MATCH:
+                    // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+                    // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+                    // matching all FQDNs with the same sub-domain.
+                    String fqdnMatch = getPpsNodeValue(child);
+                    String[] fqdnMatchArray = fqdnMatch.split(",");
+                    if (fqdnMatchArray.length != 2) {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    roamingPartner.fqdn = fqdnMatchArray[0];
+                    if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+                        roamingPartner.fqdnExactMatch = true;
+                    } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+                        roamingPartner.fqdnExactMatch = false;
+                    } else {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    break;
+                case NODE_PRIORITY:
+                    roamingPartner.priority = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_COUNTRY:
+                    roamingPartner.countries = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+                            + "instance " + child.getName());
+            }
+        }
+        return roamingPartner;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * @param policy The policy to store the MinBackhualThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+        }
+        for (PPSNode child : node.getChildren()) {
+            parseMinBackhaulThresholdInstance(child, policy);
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * @param policy The policy to store the MinBackhaulThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+        }
+        String networkType = null;
+        long downlinkBandwidth = Long.MIN_VALUE;
+        long uplinkBandwidth = Long.MIN_VALUE;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_NETWORK_TYPE:
+                    networkType = getPpsNodeValue(child);
+                    break;
+                case NODE_DOWNLINK_BANDWIDTH:
+                    try {
+                        downlinkBandwidth = Long.parseLong(getPpsNodeValue(child));
+                    } catch (NumberFormatException e) {
+                        throw new ParsingException("Invalid value for downlink bandwidth: "
+                                + getPpsNodeValue(child));
+                    }
+                    break;
+                case NODE_UPLINK_BANDWIDTH:
+                    try {
+                        uplinkBandwidth = Long.parseLong(getPpsNodeValue(child));
+                    } catch (NumberFormatException e) {
+                        throw new ParsingException("Invalid value for downlink bandwidth: "
+                                + getPpsNodeValue(child));
+                    }
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+                            + child.getName());
+            }
+        }
+        if (networkType == null) {
+            throw new ParsingException("Missing NetworkType field");
+        }
+
+        if (TextUtils.equals(networkType, "home")) {
+            policy.minHomeDownlinkBandwidth = downlinkBandwidth;
+            policy.minHomeUplinkBandwidth = uplinkBandwidth;
+        } else if (TextUtils.equals(networkType, "roaming")) {
+            policy.minRoamingDownlinkBandwidth = downlinkBandwidth;
+            policy.minRoamingUplinkBandwidth = uplinkBandwidth;
+        } else {
+            throw new ParsingException("Invalid network type: " + networkType);
+        }
+    }
+
+    /**
+     * Parse update parameters. This contained configurations from either
+     * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+     *             or PerProviderSubscription/SubscriptionUpdate subtree
+     * @return {@link UpdateParameter}
+     * @throws ParsingException
+     */
+    private static UpdateParameter parseUpdateParameter(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Update Parameters");
+        }
+
+        UpdateParameter updateParam = new UpdateParameter();
+        for (PPSNode child : node.getChildren()) {
+            switch(child.getName()) {
+                case NODE_UPDATE_INTERVAL:
+                    try {
+                        updateParam.updateIntervalInMinutes =
+                                Long.parseLong(getPpsNodeValue(child));
+                    } catch (NumberFormatException e) {
+                        throw new ParsingException("Invalid value for update interval: "
+                                + getPpsNodeValue(child));
+                    }
+                    break;
+                case NODE_UPDATE_METHOD:
+                    updateParam.updateMethod = getPpsNodeValue(child);
+                    break;
+                case NODE_RESTRICTION:
+                    updateParam.restriction = getPpsNodeValue(child);
+                    break;
+                case NODE_URI:
+                    updateParam.serverUri = getPpsNodeValue(child);
+                    break;
+                case NODE_USERNAME_PASSWORD:
+                    Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+                    updateParam.username = usernamePassword.first;
+                    updateParam.base64EncodedPassword = usernamePassword.second;
+                    break;
+                case NODE_TRUST_ROOT:
+                    Pair<String, byte[]> trustRoot = parseUpdateTrustRoot(child);
+                    updateParam.trustRootCertUrl = trustRoot.first;
+                    updateParam.trustRootCertSha256Fingerprint = trustRoot.second;
+                    break;
+                case NODE_OTHER:
+                    Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Update Parameters: "
+                            + child.getName());
+            }
+        }
+        return updateParam;
+    }
+
+    /**
+     * Parse username and password parameters associated with policy or subscription update.
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+     * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+     *
+     * @param node PPSNode representing the root of the UsernamePassword subtree
+     * @return Pair of username and password
+     * @throws ParsingException
+     */
+    private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsernamePassword");
+        }
+
+        String username = null;
+        String password = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_USERNAME:
+                    username = getPpsNodeValue(child);
+                    break;
+                case NODE_PASSWORD:
+                    password = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsernamePassword: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(username, password);
+    }
+
+    /**
+     * Parse the trust root parameters associated with policy or subscription update.
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+     * PerProviderSubscription/SubscriptionUpdate/TrustRoot subtree.
+     *
+     * @param node PPSNode representing the root of the TrustRoot subtree
+     * @return Pair of Certificate URL and fingerprint
+     * @throws ParsingException
+     */
+    private static Pair<String, byte[]> parseUpdateTrustRoot(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for TrustRoot");
+        }
+
+        String certUrl = null;
+        byte[] certFingerprint = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CERT_URL:
+                    certUrl = getPpsNodeValue(child);
+                    break;
+                case NODE_CERT_SHA256_FINGERPRINT:
+                    certFingerprint = parseHexString(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under TrustRoot: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(certUrl, certFingerprint);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList subtree
+     * @return Array of excluded SSIDs
+     * @throws ParsingException
+     */
+    private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusionList");
+        }
+        List<String> ssidList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            ssidList.add(parseSpExclusionInstance(child));
+        }
+        return ssidList.toArray(new String[ssidList.size()]);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+     * @return String
+     * @throws ParsingException
+     */
+    private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusion instance");
+        }
+        String ssid = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SSID:
+                    ssid = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SPExclusion instance");
+            }
+        }
+        return ssid;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+     * @return Map of IP Protocol to Port Number tuples
+     * @throws ParsingException
+     */
+    private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+        }
+        Map<Integer, String> protoPortTupleMap = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+            protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+        }
+        return protoPortTupleMap;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+     * @return Pair of IP Protocol to Port Number tuple
+     * @throws ParsingException
+     */
+    private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+                    + "instance");
+        }
+        int proto = Integer.MIN_VALUE;
+        String ports = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_IP_PROTOCOL:
+                    proto = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_PORT_NUMBER:
+                    ports = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+                            + child.getName());
+            }
+        }
+        if (proto == Integer.MIN_VALUE) {
+            throw new ParsingException("Missing IPProtocol field");
+        }
+        if (ports == null) {
+            throw new ParsingException("Missing PortNumber field");
+        }
+        return Pair.create(proto, ports);
+    }
+
+    /**
      * Convert a hex string to a byte array.
      *
      * @param str String containing hex values
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
new file mode 100644 (file)
index 0000000..e923f1f
--- /dev/null
@@ -0,0 +1,19 @@
+/**
+ * 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.hotspot2.pps;
+
+parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644 (file)
index 0000000..b2583d3
--- /dev/null
@@ -0,0 +1,452 @@
+/**
+ * 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.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+    private static final String TAG = "Policy";
+
+    /**
+     * Default priority for preferred roaming partner.
+     */
+    public static final int PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY = 128;
+
+    /**
+     * Maximum number of SSIDs in the exclusion list.
+     */
+    private static final int MAX_EXCLUSION_SSIDS = 128;
+
+    /**
+     * Maximum byte for SSID.
+     */
+    private static final int MAX_SSID_BYTES = 32;
+
+    /**
+     * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+     */
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from home providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 â€“ LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long minHomeDownlinkBandwidth = Long.MIN_VALUE;
+    public long minHomeUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from roaming providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 â€“ LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long minRoamingDownlinkBandwidth = Long.MIN_VALUE;
+    public long minRoamingUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * List of SSIDs that are not preferred by the Home SP.
+     */
+    public String[] excludedSsidList = null;
+
+    /**
+     * List of IP protocol and port number required by one or more operator supported application.
+     * The port string contained one or more port numbers delimited by ",".
+     */
+    public Map<Integer, String> requiredProtoPortMap = null;
+
+    /**
+     * This specifies the maximum acceptable BSS load policy.  This is used to prevent device
+     * from joining an AP whose channel is overly congested with traffic.
+     * Using Integer.MIN_VALUE to indicate unset value.
+     */
+    public int maximumBssLoadValue = Integer.MIN_VALUE;
+
+    /**
+     * Policy associated with a roaming provider.  This specifies a priority associated
+     * with a roaming provider for given list of countries.
+     *
+     * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+     */
+    public static final class RoamingPartner implements Parcelable {
+        /**
+         * FQDN of the roaming partner.
+         */
+        public String fqdn = null;
+
+        /**
+         * Flag indicating the exact match of FQDN is required for FQDN matching.
+         *
+         * When this flag is set to false, sub-domain matching is used.  For example, when
+         * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+         */
+        public boolean fqdnExactMatch = false;
+
+        /**
+         * Priority associated with this roaming partner policy.
+         */
+        public int priority = PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY;
+
+        /**
+         * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+         * character country strings or the country-independent value, "*".
+         */
+        public String countries = null;
+
+        public RoamingPartner() {}
+
+        public RoamingPartner(RoamingPartner source) {
+            if (source != null) {
+                fqdn = source.fqdn;
+                fqdnExactMatch = source.fqdnExactMatch;
+                priority = source.priority;
+                countries = source.countries;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(fqdn);
+            dest.writeInt(fqdnExactMatch ? 1 : 0);
+            dest.writeInt(priority);
+            dest.writeString(countries);
+        }
+
+        @Override
+        public boolean equals(Object thatObject) {
+            if (this == thatObject) {
+                return true;
+            }
+            if (!(thatObject instanceof RoamingPartner)) {
+                return false;
+            }
+
+            RoamingPartner that = (RoamingPartner) thatObject;
+            return TextUtils.equals(fqdn, that.fqdn)
+                    && fqdnExactMatch == that.fqdnExactMatch
+                    && priority == that.priority
+                    && TextUtils.equals(countries, that.countries);
+        }
+
+        /**
+         * Validate RoamingParnter data.
+         *
+         * @return true on success
+         */
+        public boolean validate() {
+            if (TextUtils.isEmpty(fqdn)) {
+                Log.d(TAG, "Missing FQDN");
+                return false;
+            }
+            if (TextUtils.isEmpty(countries)) {
+                Log.d(TAG, "Missing countries");
+                return false;
+            }
+            return true;
+        }
+
+        public static final Creator<RoamingPartner> CREATOR =
+            new Creator<RoamingPartner>() {
+                @Override
+                public RoamingPartner createFromParcel(Parcel in) {
+                    RoamingPartner roamingPartner = new RoamingPartner();
+                    roamingPartner.fqdn = in.readString();
+                    roamingPartner.fqdnExactMatch = in.readInt() != 0;
+                    roamingPartner.priority = in.readInt();
+                    roamingPartner.countries = in.readString();
+                    return roamingPartner;
+                }
+
+                @Override
+                public RoamingPartner[] newArray(int size) {
+                    return new RoamingPartner[size];
+                }
+            };
+    }
+    public List<RoamingPartner> preferredRoamingPartnerList = null;
+
+    /**
+     * Meta data used for policy update.
+     */
+    public UpdateParameter policyUpdate = null;
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public Policy() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public Policy(Policy source) {
+        if (source == null) {
+            return;
+        }
+        minHomeDownlinkBandwidth = source.minHomeDownlinkBandwidth;
+        minHomeUplinkBandwidth = source.minHomeUplinkBandwidth;
+        minRoamingDownlinkBandwidth = source.minRoamingDownlinkBandwidth;
+        minRoamingUplinkBandwidth = source.minRoamingUplinkBandwidth;
+        maximumBssLoadValue = source.maximumBssLoadValue;
+        if (source.excludedSsidList != null) {
+            excludedSsidList = Arrays.copyOf(source.excludedSsidList,
+                    source.excludedSsidList.length);
+        }
+        if (source.requiredProtoPortMap != null) {
+            requiredProtoPortMap = Collections.unmodifiableMap(source.requiredProtoPortMap);
+        }
+        if (source.preferredRoamingPartnerList != null) {
+            preferredRoamingPartnerList = Collections.unmodifiableList(
+                    source.preferredRoamingPartnerList);
+        }
+        if (source.policyUpdate != null) {
+            policyUpdate = new UpdateParameter(source.policyUpdate);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(minHomeDownlinkBandwidth);
+        dest.writeLong(minHomeUplinkBandwidth);
+        dest.writeLong(minRoamingDownlinkBandwidth);
+        dest.writeLong(minRoamingUplinkBandwidth);
+        dest.writeStringArray(excludedSsidList);
+        writeProtoPortMap(dest, requiredProtoPortMap);
+        dest.writeInt(maximumBssLoadValue);
+        writeRoamingPartnerList(dest, flags, preferredRoamingPartnerList);
+        dest.writeParcelable(policyUpdate, flags);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof Policy)) {
+            return false;
+        }
+        Policy that = (Policy) thatObject;
+
+        return minHomeDownlinkBandwidth == that.minHomeDownlinkBandwidth
+                && minHomeUplinkBandwidth == that.minHomeUplinkBandwidth
+                && minRoamingDownlinkBandwidth == that.minRoamingDownlinkBandwidth
+                && minRoamingUplinkBandwidth == that.minRoamingUplinkBandwidth
+                && Arrays.equals(excludedSsidList, that.excludedSsidList)
+                && (requiredProtoPortMap == null) ? that.requiredProtoPortMap == null
+                        : requiredProtoPortMap.equals(that.requiredProtoPortMap)
+                && maximumBssLoadValue == that.maximumBssLoadValue
+                && (preferredRoamingPartnerList == null) ? that.preferredRoamingPartnerList == null
+                        : preferredRoamingPartnerList.equals(that.preferredRoamingPartnerList)
+                && (policyUpdate == null) ? that.policyUpdate == null
+                        : policyUpdate.equals(that.policyUpdate);
+    }
+
+    /**
+     * Validate Policy data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (policyUpdate == null) {
+            Log.d(TAG, "PolicyUpdate not specified");
+            return false;
+        }
+        if (!policyUpdate.validate()) {
+            return false;
+        }
+
+        // Validate SSID exclusion list.
+        if (excludedSsidList != null) {
+            if (excludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+                Log.d(TAG, "SSID exclusion list size exceeded the max: "
+                        + excludedSsidList.length);
+                return false;
+            }
+            for (String ssid : excludedSsidList) {
+                if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+                    Log.d(TAG, "Invalid SSID: " + ssid);
+                    return false;
+                }
+            }
+        }
+        // Validate required protocol to port map.
+        if (requiredProtoPortMap != null) {
+            for (Map.Entry<Integer, String> entry : requiredProtoPortMap.entrySet()) {
+                String portNumber = entry.getValue();
+                if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+                    Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+                    return false;
+                }
+            }
+        }
+        // Validate preferred roaming partner list.
+        if (preferredRoamingPartnerList != null) {
+            for (RoamingPartner partner : preferredRoamingPartnerList) {
+                if (!partner.validate()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static final Creator<Policy> CREATOR =
+        new Creator<Policy>() {
+            @Override
+            public Policy createFromParcel(Parcel in) {
+                Policy policy = new Policy();
+                policy.minHomeDownlinkBandwidth = in.readLong();
+                policy.minHomeUplinkBandwidth = in.readLong();
+                policy.minRoamingDownlinkBandwidth = in.readLong();
+                policy.minRoamingUplinkBandwidth = in.readLong();
+                policy.excludedSsidList = in.createStringArray();
+                policy.requiredProtoPortMap = readProtoPortMap(in);
+                policy.maximumBssLoadValue = in.readInt();
+                policy.preferredRoamingPartnerList = readRoamingPartnerList(in);
+                policy.policyUpdate = in.readParcelable(null);
+                return policy;
+            }
+
+            @Override
+            public Policy[] newArray(int size) {
+                return new Policy[size];
+            }
+
+            /**
+             * Helper function for reading IP Protocol to Port Number map from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return Map of IP protocol to port number
+             */
+            private Map<Integer, String> readProtoPortMap(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<Integer, String> protoPortMap = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    int key = in.readInt();
+                    String value = in.readString();
+                    protoPortMap.put(key, value);
+                }
+                return protoPortMap;
+            }
+
+            /**
+             * Helper function for reading roaming partner list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return List of roaming partners
+             */
+            private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                List<RoamingPartner> partnerList = new ArrayList<>();
+                for (int i = 0; i < size; i++) {
+                    partnerList.add(in.readParcelable(null));
+                }
+                return partnerList;
+            }
+
+        };
+
+    /**
+     * Helper function for writing IP Protocol to Port Number map to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param protoPortMap The map to write
+     */
+    private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+        if (protoPortMap == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(protoPortMap.size());
+        for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+            dest.writeInt(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for writing roaming partner list to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param flags The flag about how the object should be written
+     * @param partnerList The partner list to write
+     */
+    private static void writeRoamingPartnerList(Parcel dest, int flags,
+            List<RoamingPartner> partnerList) {
+        if (partnerList == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(partnerList.size());
+        for (RoamingPartner partner : partnerList) {
+            dest.writeParcelable(partner, flags);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
new file mode 100644 (file)
index 0000000..701db47
--- /dev/null
@@ -0,0 +1,19 @@
+/**
+ * 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.hotspot2.pps;
+
+parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644 (file)
index 0000000..a390df7
--- /dev/null
@@ -0,0 +1,303 @@
+/**
+ * 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.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree.  This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class UpdateParameter implements Parcelable {
+    private static final String TAG = "UpdateParameter";
+
+    /**
+     * Value indicating policy update is not applicable.  Thus, never check with policy server
+     * for updates.
+     */
+    public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+    /**
+     * Valid string for UpdateMethod.
+     */
+    public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+    /**
+     * Valid string for Restriction.
+     */
+    public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URI_BYTES = 1023;
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Maximum bytes for username.
+     */
+    private static final int MAX_USERNAME_BYTES = 63;
+
+    /**
+     * Maximum bytes for password.
+     */
+    private static final int MAX_PASSWORD_BYTES = 255;
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * This specifies how often the mobile device shall check with policy server for updates.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long updateIntervalInMinutes = Long.MIN_VALUE;
+
+    /**
+     * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
+     * and "SPP-ClientInitiated".
+     */
+    public String updateMethod = null;
+
+    /**
+     * This specifies the hotspots at which the subscription update is permitted.  Permitted
+     * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+     */
+    public String restriction = null;
+
+    /**
+     * The URI of the update server.
+     */
+    public String serverUri = null;
+
+    /**
+     * Username used to authenticate with the policy server.
+     */
+    public String username = null;
+
+    /**
+     * Base64 encoded password used to authenticate with the policy server.
+     */
+    public String base64EncodedPassword = null;
+
+    /**
+     * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
+     * policy server's identity.
+     */
+    public String trustRootCertUrl = null;
+
+    /**
+     * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+     */
+    public byte[] trustRootCertSha256Fingerprint = null;
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public UpdateParameter() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public UpdateParameter(UpdateParameter source) {
+        if (source == null) {
+            return;
+        }
+        updateIntervalInMinutes = source.updateIntervalInMinutes;
+        updateMethod = source.updateMethod;
+        restriction = source.restriction;
+        serverUri = source.serverUri;
+        username = source.username;
+        base64EncodedPassword = source.base64EncodedPassword;
+        trustRootCertUrl = source.trustRootCertUrl;
+        if (source.trustRootCertSha256Fingerprint != null) {
+            trustRootCertSha256Fingerprint = Arrays.copyOf(source.trustRootCertSha256Fingerprint,
+                    source.trustRootCertSha256Fingerprint.length);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(updateIntervalInMinutes);
+        dest.writeString(updateMethod);
+        dest.writeString(restriction);
+        dest.writeString(serverUri);
+        dest.writeString(username);
+        dest.writeString(base64EncodedPassword);
+        dest.writeString(trustRootCertUrl);
+        dest.writeByteArray(trustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof UpdateParameter)) {
+            return false;
+        }
+        UpdateParameter that = (UpdateParameter) thatObject;
+
+        return updateIntervalInMinutes == that.updateIntervalInMinutes
+                && TextUtils.equals(updateMethod, that.updateMethod)
+                && TextUtils.equals(restriction, that.restriction)
+                && TextUtils.equals(serverUri, that.serverUri)
+                && TextUtils.equals(username, that.username)
+                && TextUtils.equals(base64EncodedPassword, that.base64EncodedPassword)
+                && TextUtils.equals(trustRootCertUrl, that.trustRootCertUrl)
+                && Arrays.equals(trustRootCertSha256Fingerprint,
+                        that.trustRootCertSha256Fingerprint);
+    }
+
+    /**
+     * Validate UpdateParameter data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (updateIntervalInMinutes == Long.MIN_VALUE) {
+            Log.d(TAG, "Update interval not specified");
+            return false;
+        }
+        // Update not applicable.
+        if (updateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+            return true;
+        }
+
+        if (!TextUtils.equals(updateMethod, UPDATE_METHOD_OMADM)
+                && !TextUtils.equals(updateMethod, UPDATE_METHOD_SSP)) {
+            Log.d(TAG, "Unknown update method: " + updateMethod);
+            return false;
+        }
+
+        if (!TextUtils.equals(restriction, UPDATE_RESTRICTION_HOMESP)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+            Log.d(TAG, "Unknown restriction: " + restriction);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(serverUri)) {
+            Log.d(TAG, "Missing update server URI");
+            return false;
+        }
+        if (serverUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+            Log.d(TAG, "URI bytes exceeded the max: "
+                    + serverUri.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(username)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+            Log.d(TAG, "Username bytes exceeded the max: "
+                    + username.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(base64EncodedPassword)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+            Log.d(TAG, "Password bytes exceeded the max: "
+                    + base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+        try {
+            Base64.decode(base64EncodedPassword, Base64.DEFAULT);
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Invalid encoding for password: " + base64EncodedPassword);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(trustRootCertUrl)) {
+            Log.d(TAG, "Missing trust root certificate URL");
+            return false;
+        }
+        if (trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+            Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+                    + trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (trustRootCertSha256Fingerprint == null) {
+            Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+            return false;
+        }
+        if (trustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+            Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                    + trustRootCertSha256Fingerprint.length);
+            return false;
+        }
+        return true;
+    }
+
+    public static final Creator<UpdateParameter> CREATOR =
+        new Creator<UpdateParameter>() {
+            @Override
+            public UpdateParameter createFromParcel(Parcel in) {
+                UpdateParameter updateParam = new UpdateParameter();
+                updateParam.updateIntervalInMinutes = in.readLong();
+                updateParam.updateMethod = in.readString();
+                updateParam.restriction = in.readString();
+                updateParam.serverUri = in.readString();
+                updateParam.username = in.readString();
+                updateParam.base64EncodedPassword = in.readString();
+                updateParam.trustRootCertUrl = in.readString();
+                updateParam.trustRootCertSha256Fingerprint = in.createByteArray();
+                return updateParam;
+            }
+
+            @Override
+            public UpdateParameter[] newArray(int size) {
+                return new UpdateParameter[size];
+            }
+        };
+}
index 8c1eb08..995963d 100644 (file)
@@ -42,7 +42,7 @@ V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
 a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
 eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
 QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
 KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
 bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
 OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
index 6d86dd5..3ddd09f 100644 (file)
@@ -35,7 +35,7 @@ ICAgPC9Ob2RlPgogICAgICAgIDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPkRpZ2l0YWxDZXJ0
 aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
 PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
 bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
-TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VycHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
 MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
 ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
 IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
index 3969f69..7f36cdb 100644 (file)
             <Value>x509v3</Value>
           </Node>
           <Node>
-            <NodeName>CertSHA256FingerPrint</NodeName>
+            <NodeName>CertSHA256Fingerprint</NodeName>
             <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
           </Node>
         </Node>
           </Node>
         </Node>
       </Node>
+      <Node>
+        <NodeName>Policy</NodeName>
+        <Node>
+          <NodeName>PreferredRoamingPartnerList</NodeName>
+          <Node>
+            <NodeName>p001</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test1.fqdn.com,exactMatch</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>127</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>us,fr</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>p002</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test2.fqdn.com,includeSubdomains</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>200</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>*</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MinBackhaulThreshold</NodeName>
+          <Node>
+            <NodeName>m001</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>home</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>23412</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>9823</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>m002</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>roaming</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>9271</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>2315</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>PolicyUpdate</NodeName>
+          <Node>
+            <NodeName>UpdateInterval</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UpdateMethod</NodeName>
+            <Value>OMA-DM-ClientInitiated</Value>
+          </Node>
+          <Node>
+            <NodeName>Restriction</NodeName>
+            <Value>HomeSP</Value>
+          </Node>
+          <Node>
+            <NodeName>URI</NodeName>
+            <Value>policy.update.com</Value>
+          </Node>
+          <Node>
+            <NodeName>UsernamePassword</NodeName>
+            <Node>
+              <NodeName>Username</NodeName>
+              <Value>updateUser</Value>
+            </Node>
+            <Node>
+              <NodeName>Password</NodeName>
+              <Value>updatePass</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>TrustRoot</NodeName>
+            <Node>
+              <NodeName>CertURL</NodeName>
+              <Value>update.cert.com</Value>
+            </Node>
+            <Node>
+              <NodeName>CertSHA256Fingerprint</NodeName>
+              <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>SPExclusionList</NodeName>
+          <Node>
+            <NodeName>s001</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>excludeSSID</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>RequiredProtoPortTuple</NodeName>
+          <Node>
+            <NodeName>r001</NodeName>
+            <Node>
+              <NodeName>IPProtocol</NodeName>
+              <Value>12</Value>
+            </Node>
+            <Node>
+              <NodeName>PortNumber</NodeName>
+              <Value>34,92,234</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MaximumBSSLoadValue</NodeName>
+          <Value>23</Value>
+        </Node>
+      </Node>
     </Node>
   </Node>
 </MgmtTree>
index 2350d32..a386d2a 100644 (file)
@@ -22,11 +22,17 @@ import static org.junit.Assert.assertTrue;
 import android.net.wifi.EAPConstants;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
  */
@@ -66,6 +72,64 @@ public class PasspointConfigurationTest {
     }
 
     /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.maximumBssLoadValue = 12;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+        return policy;
+    }
+
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createConfig() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.homeSp = createHomeSp();
+        config.credential = createCredential();
+        config.policy = createPolicy();
+        return config;
+    }
+
+    /**
      * Verify parcel write and read consistency for the given configuration.
      *
      * @param writeConfig The configuration to verify
@@ -92,39 +156,48 @@ public class PasspointConfigurationTest {
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+     * Verify parcel read/write for a configuration that contained the full configuration.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPAndCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void verifyParcelWithFullConfiguration() throws Exception {
+        verifyParcel(createConfig());
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain HomeSP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutHomeSP() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.homeSp = null;
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only HomeSP.
+     * Verify parcel read/write for a configuration that doesn't contain Credential.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+    public void verifyParcelWithoutCredential() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.credential = null;
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only Credential.
+     * Verify parcel read/write for a configuration that doesn't contain Policy.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithCredentialOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+    public void verifyParcelWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.policy = null;
         verifyParcel(config);
     }
 
@@ -140,39 +213,50 @@ public class PasspointConfigurationTest {
     }
 
     /**
+     * Verify that a configuration contained all fields is valid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullConfig() throws Exception {
+        PasspointConfiguration config = createConfig();
+        assertTrue(config.validate());
+    }
+
+    /**
      * Verify that a configuration without Credential is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+        PasspointConfiguration config = createConfig();
+        config.credential = null;
         assertFalse(config.validate());
     }
 
     /**
-     * Verify that a configuration without HomeSP is invalid.
+     * Verify that a configuration without HomeSP is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutHomeSp() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+        PasspointConfiguration config = createConfig();
+        config.homeSp = null;
         assertFalse(config.validate());
     }
 
     /**
-     * Verify a valid configuration.
+     * Verify that a configuration without Policy is valid, since Policy configurations
+     * are optional (applied for Hotspot 2.0 Release only).
      *
      * @throws Exception
      */
     @Test
-    public void validateValidConfig() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void validateConfigWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.policy = null;
         assertTrue(config.validate());
     }
 
@@ -195,9 +279,7 @@ public class PasspointConfigurationTest {
      */
     @Test
     public void validateCopyConstructorWithValidSource() throws Exception {
-        PasspointConfiguration sourceConfig = new PasspointConfiguration();
-        sourceConfig.homeSp = createHomeSp();
-        sourceConfig.credential = createCredential();
+        PasspointConfiguration sourceConfig = createConfig();
         PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
         assertTrue(copyConfig.equals(sourceConfig));
     }
index 1c7508e..e9b41a9 100644 (file)
@@ -23,7 +23,10 @@ import android.net.wifi.hotspot2.omadm.PPSMOParser;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 
 import org.junit.Test;
 
@@ -33,6 +36,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
@@ -118,6 +122,41 @@ public class PPSMOParserTest {
         config.credential.simCredential = new Credential.SimCredential();
         config.credential.simCredential.imsi = "imsi";
         config.credential.simCredential.eapType = 24;
+
+        // Policy configuration.
+        config.policy = new Policy();
+        config.policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "test1.fqdn.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 127;
+        partner1.countries = "us,fr";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "test2.fqdn.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 200;
+        partner2.countries = "*";
+        config.policy.preferredRoamingPartnerList.add(partner1);
+        config.policy.preferredRoamingPartnerList.add(partner2);
+        config.policy.minHomeDownlinkBandwidth = 23412;
+        config.policy.minHomeUplinkBandwidth = 9823;
+        config.policy.minRoamingDownlinkBandwidth = 9271;
+        config.policy.minRoamingUplinkBandwidth = 2315;
+        config.policy.excludedSsidList = new String[] {"excludeSSID"};
+        config.policy.requiredProtoPortMap = new HashMap<>();
+        config.policy.requiredProtoPortMap.put(12, "34,92,234");
+        config.policy.maximumBssLoadValue = 23;
+        config.policy.policyUpdate = new UpdateParameter();
+        config.policy.policyUpdate.updateIntervalInMinutes = 120;
+        config.policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        config.policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        config.policy.policyUpdate.serverUri = "policy.update.com";
+        config.policy.policyUpdate.username = "updateUser";
+        config.policy.policyUpdate.base64EncodedPassword = "updatePass";
+        config.policy.policyUpdate.trustRootCertUrl = "update.cert.com";
+        config.policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.policy.policyUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
         return config;
     }
 
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
new file mode 100644 (file)
index 0000000..c371c49
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * 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.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.Policy}.
+ */
+@SmallTest
+public class PolicyTest {
+    private static final int MAX_NUMBER_OF_EXCLUDED_SSIDS = 128;
+    private static final int MAX_SSID_BYTES = 32;
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+        policy.maximumBssLoadValue = 12;
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+        return policy;
+    }
+
+    /**
+     * Helper function for verifying Policy after parcel write then read.
+     * @param policyToWrite
+     * @throws Exception
+     */
+    private static void verifyParcel(Policy policyToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        policyToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        Policy policyFromRead = Policy.CREATOR.createFromParcel(parcel);
+        assertTrue(policyFromRead.equals(policyToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty Policy.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyPolicy() throws Exception {
+        verifyParcel(new Policy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullPolicy() throws Exception {
+        verifyParcel(createPolicy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without protocol port map.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        policy.requiredProtoPortMap = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without preferred roaming partner list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPreferredRoamingPartnerList() throws Exception {
+        Policy policy = createPolicy();
+        policy.preferredRoamingPartnerList = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without policy update parameters.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify that policy created using copy constructor with null source should be the same
+     * as the policy created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        Policy copyPolicy = new Policy(null);
+        Policy defaultPolicy = new Policy();
+        assertTrue(defaultPolicy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that policy created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullPolicy() throws Exception {
+        Policy policy = createPolicy();
+        Policy copyPolicy = new Policy(policy);
+        assertTrue(policy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that a default policy (with no informatio) is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithDefault() throws Exception {
+        Policy policy = new Policy();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy created using {@link #createPolicy} is valid, since all fields are
+     * filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithFullPolicy() throws Exception {
+        assertTrue(createPolicy().validate());
+    }
+
+    /**
+     * Verify that a policy without policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = null;
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with invalid policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = new UpdateParameter();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with FQDN not specified is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutFQDN() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        partner.countries = "us,jp";
+        policy.preferredRoamingPartnerList.add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with countries not specified is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutCountries() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.fqdn = "test.com";
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        policy.preferredRoamingPartnerList.add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a proto-port tuple that contains an invalid port string is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPortStringInProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawPortBytes = new byte[MAX_PORT_STRING_BYTES + 1];
+        policy.requiredProtoPortMap.put(324, new String(rawPortBytes, StandardCharsets.UTF_8));
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with number of excluded SSIDs exceeded the max is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithSsidExclusionListSizeExceededMax() throws Exception {
+        Policy policy = createPolicy();
+        policy.excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+        Arrays.fill(policy.excludedSsidList, "ssid");
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with an invalid SSID in the excluded SSID list is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidSsid() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawSsidBytes = new byte[MAX_SSID_BYTES + 1];
+        Arrays.fill(rawSsidBytes, (byte) 'a');
+        policy.excludedSsidList = new String[] {new String(rawSsidBytes, StandardCharsets.UTF_8)};
+        assertFalse(policy.validate());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
new file mode 100644 (file)
index 0000000..6bf0db1
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * 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.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.UpdateParameter}.
+ */
+@SmallTest
+public class UpdateParameterTest {
+    private static final int MAX_URI_BYTES = 1023;
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int MAX_USERNAME_BYTES = 63;
+    private static final int MAX_PASSWORD_BYTES = 255;
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Helper function for creating a {@link UpdateParameter} for testing.
+     *
+     * @return {@link UpdateParameter}
+     */
+    private static UpdateParameter createUpdateParameter() {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.updateIntervalInMinutes = 1712;
+        updateParam.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        updateParam.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        updateParam.serverUri = "server.pdate.com";
+        updateParam.username = "username";
+        updateParam.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        updateParam.trustRootCertUrl = "trust.cert.com";
+        updateParam.trustRootCertSha256Fingerprint = new byte[32];
+        return updateParam;
+    }
+
+    /**
+     * Helper function for verifying UpdateParameter after parcel write then read.
+     * @param paramToWrite The UpdateParamter to verify
+     * @throws Exception
+     */
+    private static void verifyParcel(UpdateParameter paramToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        paramToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        UpdateParameter paramFromRead = UpdateParameter.CREATOR.createFromParcel(parcel);
+        assertTrue(paramFromRead.equals(paramToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty UpdateParameter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyUpdateParameter() throws Exception {
+        verifyParcel(new UpdateParameter());
+    }
+
+    /**
+     * Verify parcel read/write for a UpdateParameter with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullUpdateParameter() throws Exception {
+        verifyParcel(createUpdateParameter());
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with null source should be the
+     * same as the UpdateParameter created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        UpdateParameter copyParam = new UpdateParameter(null);
+        UpdateParameter defaultParam = new UpdateParameter();
+        assertTrue(defaultParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullUpdateParameter() throws Exception {
+        UpdateParameter origParam = createUpdateParameter();
+        UpdateParameter copyParam = new UpdateParameter(origParam);
+        assertTrue(origParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that a default UpdateParameter is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithDefault() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter created using {@link #createUpdateParameter} is valid,
+     * since all fields are filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithFullPolicy() throws Exception {
+        assertTrue(createUpdateParameter().validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown update method is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowMethod() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.updateMethod = "adsfasd";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown restriction is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowRestriction() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.restriction = "adsfasd";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an username exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUsernameExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUsernameBytes = new byte[MAX_USERNAME_BYTES + 1];
+        Arrays.fill(rawUsernameBytes, (byte) 'a');
+        updateParam.username = new String(rawUsernameBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty username is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyUsername() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.username = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a password exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawPasswordBytes = new byte[MAX_PASSWORD_BYTES + 1];
+        Arrays.fill(rawPasswordBytes, (byte) 'a');
+        updateParam.base64EncodedPassword = new String(rawPasswordBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty password is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyPassword() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.base64EncodedPassword = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a Base64 encoded password that contained invalid padding
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordContainedInvalidPadding() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.base64EncodedPassword = updateParam.base64EncodedPassword + "=";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertUrl = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with invalid trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        updateParam.trustRootCertUrl = new String(rawUrlBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate SHA-256 fingerprint is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithouttrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertSha256Fingerprint = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an incorrect size trust root certificate SHA-256
+     * fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidtrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES + 1];
+        assertFalse(updateParam.validate());
+
+        updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES - 1];
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.serverUri = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an invalid server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUriBytes = new byte[MAX_URI_BYTES + 1];
+        Arrays.fill(rawUriBytes, (byte) 'a');
+        updateParam.serverUri = new String(rawUriBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with update interval set to "never" will not perform
+     * validation on other parameters, since update is not applicable in this case.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithNoServerCheck() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.updateIntervalInMinutes = UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER;
+        updateParam.username = null;
+        updateParam.base64EncodedPassword = null;
+        updateParam.updateMethod = null;
+        updateParam.restriction = null;
+        updateParam.serverUri = null;
+        updateParam.trustRootCertUrl = null;
+        updateParam.trustRootCertSha256Fingerprint = null;
+        assertTrue(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with unset update interval is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutUpdateInterval() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.updateIntervalInMinutes = Long.MIN_VALUE;
+        assertFalse(updateParam.validate());
+    }
+}