Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2d7af45e authored by Peter Qiu's avatar Peter Qiu
Browse files

hotspot2: add support for complete PerProviderSubscription/Policy subtree

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
parent 815fb547
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -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
+452 −1
Original line number Diff line number Diff line
@@ -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,12 +170,39 @@ 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.
     */
@@ -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());
            }
@@ -998,6 +1030,425 @@ public final class PPSMOParser {
        return simCred;
    }

    /**
     * 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.
     *
+19 −0
Original line number Diff line number Diff line
/**
 * 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;
Loading