| /** |
| * Copyright (c) 2016, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net.wifi.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; |
| |
| import java.io.IOException; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Utility class for converting OMA-DM (Open Mobile Alliance's Device Management) |
| * PPS-MO (PerProviderSubscription Management Object) XML tree to a |
| * {@link PasspointConfiguration} object. |
| * |
| * Currently this only supports PerProviderSubscription/HomeSP and |
| * PerProviderSubscription/Credential subtree for Hotspot 2.0 Release 1 support. |
| * |
| * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0 |
| * Release 2 Technical Specification. |
| * |
| * Below is a sample XML string for a Release 1 PPS MO tree: |
| * |
| * <MgmtTree xmlns="syncml:dmddf1.2"> |
| * <VerDTD>1.2</VerDTD> |
| * <Node> |
| * <NodeName>PerProviderSubscription</NodeName> |
| * <RTProperties> |
| * <Type> |
| * <DDFName>urn:wfa:mo:hotspot2dot0perprovidersubscription:1.0</DDFName> |
| * </Type> |
| * </RTProperties> |
| * <Node> |
| * <NodeName>i001</NodeName> |
| * <Node> |
| * <NodeName>HomeSP</NodeName> |
| * <Node> |
| * <NodeName>FriendlyName</NodeName> |
| * <Value>Century House</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>FQDN</NodeName> |
| * <Value>mi6.co.uk</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>RoamingConsortiumOI</NodeName> |
| * <Value>112233,445566</Value> |
| * </Node> |
| * </Node> |
| * <Node> |
| * <NodeName>Credential</NodeName> |
| * <Node> |
| * <NodeName>Realm</NodeName> |
| * <Value>shaken.stirred.com</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>UsernamePassword</NodeName> |
| * <Node> |
| * <NodeName>Username</NodeName> |
| * <Value>james</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>Password</NodeName> |
| * <Value>Ym9uZDAwNw==</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>EAPMethod</NodeName> |
| * <Node> |
| * <NodeName>EAPType</NodeName> |
| * <Value>21</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>InnerMethod</NodeName> |
| * <Value>MS-CHAP-V2</Value> |
| * </Node> |
| * </Node> |
| * </Node> |
| * </Node> |
| * </Node> |
| * </Node> |
| * </MgmtTree> |
| */ |
| public final class PpsMoParser { |
| private static final String TAG = "PpsMoParser"; |
| |
| /** |
| * XML tags expected in the PPS MO (PerProviderSubscription Management Object) XML tree. |
| */ |
| private static final String TAG_MANAGEMENT_TREE = "MgmtTree"; |
| private static final String TAG_VER_DTD = "VerDTD"; |
| private static final String TAG_NODE = "Node"; |
| private static final String TAG_NODE_NAME = "NodeName"; |
| private static final String TAG_RT_PROPERTIES = "RTProperties"; |
| private static final String TAG_TYPE = "Type"; |
| private static final String TAG_DDF_NAME = "DDFName"; |
| private static final String TAG_VALUE = "Value"; |
| |
| /** |
| * Name for PerProviderSubscription node. |
| */ |
| private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription"; |
| |
| /** |
| * Fields under PerProviderSubscription. |
| */ |
| private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier"; |
| private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot"; |
| private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate"; |
| private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter"; |
| private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription"; |
| private static final String NODE_USAGE_LIMITS = "UsageLimits"; |
| private static final String NODE_DATA_LIMIT = "DataLimit"; |
| private static final String NODE_START_DATE = "StartDate"; |
| private static final String NODE_TIME_LIMIT = "TimeLimit"; |
| private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod"; |
| private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority"; |
| private static final String NODE_EXTENSION = "Extension"; |
| |
| /** |
| * Fields under HomeSP subtree. |
| */ |
| private static final String NODE_HOMESP = "HomeSP"; |
| private static final String NODE_FQDN = "FQDN"; |
| private static final String NODE_FRIENDLY_NAME = "FriendlyName"; |
| private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI"; |
| private static final String NODE_NETWORK_ID = "NetworkID"; |
| private static final String NODE_SSID = "SSID"; |
| private static final String NODE_HESSID = "HESSID"; |
| private static final String NODE_ICON_URL = "IconURL"; |
| private static final String NODE_HOME_OI_LIST = "HomeOIList"; |
| private static final String NODE_HOME_OI = "HomeOI"; |
| private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired"; |
| private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners"; |
| |
| /** |
| * Fields under Credential subtree. |
| */ |
| private static final String NODE_CREDENTIAL = "Credential"; |
| private static final String NODE_CREATION_DATE = "CreationDate"; |
| private static final String NODE_EXPIRATION_DATE = "ExpirationDate"; |
| private static final String NODE_USERNAME_PASSWORD = "UsernamePassword"; |
| private static final String NODE_USERNAME = "Username"; |
| private static final String NODE_PASSWORD = "Password"; |
| private static final String NODE_MACHINE_MANAGED = "MachineManaged"; |
| private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp"; |
| private static final String NODE_ABLE_TO_SHARE = "AbleToShare"; |
| private static final String NODE_EAP_METHOD = "EAPMethod"; |
| private static final String NODE_EAP_TYPE = "EAPType"; |
| private static final String NODE_VENDOR_ID = "VendorId"; |
| private static final String NODE_VENDOR_TYPE = "VendorType"; |
| private static final String NODE_INNER_EAP_TYPE = "InnerEAPType"; |
| private static final String NODE_INNER_VENDOR_ID = "InnerVendorID"; |
| private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType"; |
| 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_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 = |
| "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0"; |
| |
| /** |
| * Exception for generic parsing errors. |
| */ |
| private static class ParsingException extends Exception { |
| public ParsingException(String message) { |
| super(message); |
| } |
| } |
| |
| /** |
| * Class representing a node within the PerProviderSubscription tree. |
| * This is used to flatten out and eliminate the extra layering in the XMLNode tree, |
| * to make the data parsing easier and cleaner. |
| * |
| * A PPSNode can be an internal or a leaf node, but not both. |
| * |
| */ |
| private static abstract class PPSNode { |
| private final String mName; |
| public PPSNode(String name) { |
| mName = name; |
| } |
| |
| /** |
| * @return the name of the node |
| */ |
| public String getName() { |
| return mName; |
| } |
| |
| /** |
| * Applies for internal node only. |
| * |
| * @return the list of children nodes. |
| */ |
| public abstract List<PPSNode> getChildren(); |
| |
| /** |
| * Applies for leaf node only. |
| * |
| * @return the string value of the node |
| */ |
| public abstract String getValue(); |
| |
| /** |
| * @return a flag indicating if this is a leaf or an internal node |
| */ |
| public abstract boolean isLeaf(); |
| } |
| |
| /** |
| * Class representing a leaf node in a PPS (PerProviderSubscription) tree. |
| */ |
| private static class LeafNode extends PPSNode { |
| private final String mValue; |
| public LeafNode(String nodeName, String value) { |
| super(nodeName); |
| mValue = value; |
| } |
| |
| @Override |
| public String getValue() { |
| return mValue; |
| } |
| |
| @Override |
| public List<PPSNode> getChildren() { |
| return null; |
| } |
| |
| @Override |
| public boolean isLeaf() { |
| return true; |
| } |
| } |
| |
| /** |
| * Class representing an internal node in a PPS (PerProviderSubscription) tree. |
| */ |
| private static class InternalNode extends PPSNode { |
| private final List<PPSNode> mChildren; |
| public InternalNode(String nodeName, List<PPSNode> children) { |
| super(nodeName); |
| mChildren = children; |
| } |
| |
| @Override |
| public String getValue() { |
| return null; |
| } |
| |
| @Override |
| public List<PPSNode> getChildren() { |
| return mChildren; |
| } |
| |
| @Override |
| public boolean isLeaf() { |
| return false; |
| } |
| } |
| |
| /** |
| * @hide |
| */ |
| public PpsMoParser() {} |
| |
| /** |
| * Convert a XML string representation of a PPS MO (PerProviderSubscription |
| * Management Object) tree to a {@link PasspointConfiguration} object. |
| * |
| * @param xmlString XML string representation of a PPS MO tree |
| * @return {@link PasspointConfiguration} or null |
| */ |
| public static PasspointConfiguration parseMoText(String xmlString) { |
| // Convert the XML string to a XML tree. |
| XMLParser xmlParser = new XMLParser(); |
| XMLNode root = null; |
| try { |
| root = xmlParser.parse(xmlString); |
| } catch(IOException | SAXException e) { |
| return null; |
| } |
| if (root == null) { |
| return null; |
| } |
| |
| // Verify root node is a "MgmtTree" node. |
| if (root.getTag() != TAG_MANAGEMENT_TREE) { |
| Log.e(TAG, "Root is not a MgmtTree"); |
| return null; |
| } |
| |
| String verDtd = null; // Used for detecting duplicate VerDTD element. |
| PasspointConfiguration config = null; |
| for (XMLNode child : root.getChildren()) { |
| switch(child.getTag()) { |
| case TAG_VER_DTD: |
| if (verDtd != null) { |
| Log.e(TAG, "Duplicate VerDTD element"); |
| return null; |
| } |
| verDtd = child.getText(); |
| break; |
| case TAG_NODE: |
| if (config != null) { |
| Log.e(TAG, "Unexpected multiple Node element under MgmtTree"); |
| return null; |
| } |
| try { |
| config = parsePpsNode(child); |
| } catch (ParsingException e) { |
| Log.e(TAG, e.getMessage()); |
| return null; |
| } |
| break; |
| default: |
| Log.e(TAG, "Unknown node: " + child.getTag()); |
| return null; |
| } |
| } |
| return config; |
| } |
| |
| /** |
| * Parse a PerProviderSubscription node. Below is the format of the XML tree (with |
| * each XML element represent a node in the tree): |
| * |
| * <Node> |
| * <NodeName>PerProviderSubscription</NodeName> |
| * <RTProperties> |
| * ... |
| * </RTPProperties> |
| * <Node> |
| * <NodeName>UpdateIdentifier</NodeName> |
| * <Value>...</Value> |
| * </Node> |
| * <Node> |
| * ... |
| * </Node> |
| * </Node> |
| * |
| * @param node XMLNode that contains PerProviderSubscription node. |
| * @return PasspointConfiguration or null |
| * @throws ParsingException |
| */ |
| private static PasspointConfiguration parsePpsNode(XMLNode node) |
| throws ParsingException { |
| PasspointConfiguration config = null; |
| String nodeName = null; |
| int updateIdentifier = Integer.MIN_VALUE; |
| for (XMLNode child : node.getChildren()) { |
| switch (child.getTag()) { |
| case TAG_NODE_NAME: |
| if (nodeName != null) { |
| throw new ParsingException("Duplicate NodeName: " + child.getText()); |
| } |
| nodeName = child.getText(); |
| if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) { |
| throw new ParsingException("Unexpected NodeName: " + nodeName); |
| } |
| break; |
| case TAG_NODE: |
| // A node can be either an UpdateIdentifier node or a PerProviderSubscription |
| // instance node. Flatten out the XML tree first by converting it to a PPS |
| // tree to reduce the complexity of the parsing code. |
| PPSNode ppsNodeRoot = buildPpsNode(child); |
| if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) { |
| if (updateIdentifier != Integer.MIN_VALUE) { |
| throw new ParsingException("Multiple node for UpdateIdentifier"); |
| } |
| updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot)); |
| } else { |
| // Only one PerProviderSubscription instance is expected and allowed. |
| if (config != null) { |
| throw new ParsingException("Multiple PPS instance"); |
| } |
| config = parsePpsInstance(ppsNodeRoot); |
| } |
| break; |
| case TAG_RT_PROPERTIES: |
| // Parse and verify URN stored in the RT (Run Time) Properties. |
| String urn = parseUrn(child); |
| if (!TextUtils.equals(urn, PPS_MO_URN)) { |
| throw new ParsingException("Unknown URN: " + urn); |
| } |
| break; |
| default: |
| throw new ParsingException("Unknown tag under PPS node: " + child.getTag()); |
| } |
| } |
| if (config != null && updateIdentifier != Integer.MIN_VALUE) { |
| config.setUpdateIdentifier(updateIdentifier); |
| } |
| return config; |
| } |
| |
| /** |
| * Parse the URN stored in the RTProperties. Below is the format of the RTPProperties node: |
| * |
| * <RTProperties> |
| * <Type> |
| * <DDFName>urn:...</DDFName> |
| * </Type> |
| * </RTProperties> |
| * |
| * @param node XMLNode that contains RTProperties node. |
| * @return URN String of URN. |
| * @throws ParsingException |
| */ |
| private static String parseUrn(XMLNode node) throws ParsingException { |
| if (node.getChildren().size() != 1) |
| throw new ParsingException("Expect RTPProperties node to only have one child"); |
| |
| XMLNode typeNode = node.getChildren().get(0); |
| if (typeNode.getChildren().size() != 1) { |
| throw new ParsingException("Expect Type node to only have one child"); |
| } |
| if (!TextUtils.equals(typeNode.getTag(), TAG_TYPE)) { |
| throw new ParsingException("Unexpected tag for Type: " + typeNode.getTag()); |
| } |
| |
| XMLNode ddfNameNode = typeNode.getChildren().get(0); |
| if (!ddfNameNode.getChildren().isEmpty()) { |
| throw new ParsingException("Expect DDFName node to have no child"); |
| } |
| if (!TextUtils.equals(ddfNameNode.getTag(), TAG_DDF_NAME)) { |
| throw new ParsingException("Unexpected tag for DDFName: " + ddfNameNode.getTag()); |
| } |
| |
| return ddfNameNode.getText(); |
| } |
| |
| /** |
| * Convert a XML tree represented by XMLNode to a PPS (PerProviderSubscription) instance tree |
| * represented by PPSNode. This flattens out the XML tree to allow easier and cleaner parsing |
| * of the PPS configuration data. Only three types of XML tag are expected: "NodeName", |
| * "Node", and "Value". |
| * |
| * The original XML tree (each XML element represent a node): |
| * |
| * <Node> |
| * <NodeName>root</NodeName> |
| * <Node> |
| * <NodeName>child1</NodeName> |
| * <Value>value1</Value> |
| * </Node> |
| * <Node> |
| * <NodeName>child2</NodeName> |
| * <Node> |
| * <NodeName>grandchild1</NodeName> |
| * ... |
| * </Node> |
| * </Node> |
| * ... |
| * </Node> |
| * |
| * The converted PPS tree: |
| * |
| * [root] --- [child1, value1] |
| * | |
| * ---------[child2] --------[grandchild1] --- ... |
| * |
| * @param node XMLNode pointed to the root of a XML tree |
| * @return PPSNode pointing to the root of a PPS tree |
| * @throws ParsingException |
| */ |
| private static PPSNode buildPpsNode(XMLNode node) throws ParsingException { |
| String nodeName = null; |
| String nodeValue = null; |
| List<PPSNode> childNodes = new ArrayList<PPSNode>(); |
| // Names of parsed child nodes, use for detecting multiple child nodes with the same name. |
| Set<String> parsedNodes = new HashSet<String>(); |
| |
| for (XMLNode child : node.getChildren()) { |
| String tag = child.getTag(); |
| if (TextUtils.equals(tag, TAG_NODE_NAME)) { |
| if (nodeName != null) { |
| throw new ParsingException("Duplicate NodeName node"); |
| } |
| nodeName = child.getText(); |
| } else if (TextUtils.equals(tag, TAG_NODE)) { |
| PPSNode ppsNode = buildPpsNode(child); |
| if (parsedNodes.contains(ppsNode.getName())) { |
| throw new ParsingException("Duplicate node: " + ppsNode.getName()); |
| } |
| parsedNodes.add(ppsNode.getName()); |
| childNodes.add(ppsNode); |
| } else if (TextUtils.equals(tag, TAG_VALUE)) { |
| if (nodeValue != null) { |
| throw new ParsingException("Duplicate Value node"); |
| } |
| nodeValue = child.getText(); |
| } else { |
| throw new ParsingException("Unknown tag: " + tag); |
| } |
| } |
| |
| if (nodeName == null) { |
| throw new ParsingException("Invalid node: missing NodeName"); |
| } |
| if (nodeValue == null && childNodes.size() == 0) { |
| throw new ParsingException("Invalid node: " + nodeName + |
| " missing both value and children"); |
| } |
| if (nodeValue != null && childNodes.size() > 0) { |
| throw new ParsingException("Invalid node: " + nodeName + |
| " contained both value and children"); |
| } |
| |
| if (nodeValue != null) { |
| return new LeafNode(nodeName, nodeValue); |
| } |
| return new InternalNode(nodeName, childNodes); |
| } |
| |
| /** |
| * Return the value of a PPSNode. An exception will be thrown if the given node |
| * is not a leaf node. |
| * |
| * @param node PPSNode to retrieve the value from |
| * @return String representing the value of the node |
| * @throws ParsingException |
| */ |
| private static String getPpsNodeValue(PPSNode node) throws ParsingException { |
| if (!node.isLeaf()) { |
| throw new ParsingException("Cannot get value from a non-leaf node: " + node.getName()); |
| } |
| return node.getValue(); |
| } |
| |
| /** |
| * Parse a PPS (PerProviderSubscription) configurations from a PPS tree. |
| * |
| * @param root PPSNode representing the root of the PPS tree |
| * @return PasspointConfiguration |
| * @throws ParsingException |
| */ |
| private static PasspointConfiguration parsePpsInstance(PPSNode root) |
| throws ParsingException { |
| if (root.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for PPS instance"); |
| } |
| |
| PasspointConfiguration config = new PasspointConfiguration(); |
| for (PPSNode child : root.getChildren()) { |
| switch(child.getName()) { |
| case NODE_HOMESP: |
| config.setHomeSp(parseHomeSP(child)); |
| break; |
| case NODE_CREDENTIAL: |
| config.setCredential(parseCredential(child)); |
| break; |
| case NODE_POLICY: |
| config.setPolicy(parsePolicy(child)); |
| break; |
| case NODE_AAA_SERVER_TRUST_ROOT: |
| config.setTrustRootCertList(parseAAAServerTrustRootList(child)); |
| break; |
| case NODE_SUBSCRIPTION_UPDATE: |
| config.setSubscriptionUpdate(parseUpdateParameter(child)); |
| break; |
| case NODE_SUBSCRIPTION_PARAMETER: |
| parseSubscriptionParameter(child, config); |
| break; |
| case NODE_CREDENTIAL_PRIORITY: |
| config.setCredentialPriority(parseInteger(getPpsNodeValue(child))); |
| break; |
| case NODE_EXTENSION: |
| // All vendor specific information will be under this node. |
| Log.d(TAG, "Ignore Extension node for vendor specific information"); |
| break; |
| default: |
| throw new ParsingException("Unknown node: " + child.getName()); |
| } |
| } |
| return config; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP subtree. |
| * |
| * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP subtree |
| * @return HomeSP |
| * @throws ParsingException |
| */ |
| private static HomeSp parseHomeSP(PPSNode node) throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for HomeSP"); |
| } |
| |
| HomeSp homeSp = new HomeSp(); |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_FQDN: |
| homeSp.setFqdn(getPpsNodeValue(child)); |
| break; |
| case NODE_FRIENDLY_NAME: |
| homeSp.setFriendlyName(getPpsNodeValue(child)); |
| break; |
| case NODE_ROAMING_CONSORTIUM_OI: |
| homeSp.setRoamingConsortiumOis( |
| parseRoamingConsortiumOI(getPpsNodeValue(child))); |
| break; |
| case NODE_ICON_URL: |
| homeSp.setIconUrl(getPpsNodeValue(child)); |
| break; |
| case NODE_NETWORK_ID: |
| homeSp.setHomeNetworkIds(parseNetworkIds(child)); |
| break; |
| case NODE_HOME_OI_LIST: |
| Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child); |
| homeSp.setMatchAllOis(convertFromLongList(homeOIs.first)); |
| homeSp.setMatchAnyOis(convertFromLongList(homeOIs.second)); |
| break; |
| case NODE_OTHER_HOME_PARTNERS: |
| homeSp.setOtherHomePartners(parseOtherHomePartners(child)); |
| break; |
| default: |
| throw new ParsingException("Unknown node under HomeSP: " + child.getName()); |
| } |
| } |
| return homeSp; |
| } |
| |
| /** |
| * Parse the roaming consortium OI string, which contains a list of OIs separated by ",". |
| * |
| * @param oiStr string containing list of OIs (Organization Identifiers) separated by "," |
| * @return long[] |
| * @throws ParsingException |
| */ |
| private static long[] parseRoamingConsortiumOI(String oiStr) |
| throws ParsingException { |
| String[] oiStrArray = oiStr.split(","); |
| long[] oiArray = new long[oiStrArray.length]; |
| for (int i = 0; i < oiStrArray.length; i++) { |
| oiArray[i] = parseLong(oiStrArray[i], 16); |
| } |
| return oiArray; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree. |
| * |
| * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID |
| * subtree |
| * @return HashMap<String, Long> representing list of <SSID, HESSID> pair. |
| * @throws ParsingException |
| */ |
| static private Map<String, Long> parseNetworkIds(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for NetworkID"); |
| } |
| |
| Map<String, Long> networkIds = new HashMap<>(); |
| for (PPSNode child : node.getChildren()) { |
| Pair<String, Long> networkId = parseNetworkIdInstance(child); |
| networkIds.put(networkId.first, networkId.second); |
| } |
| return networkIds; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree. |
| * The instance name (<X+>) is irrelevant and must be unique for each instance, which |
| * is verified when the PPS tree is constructed {@link #buildPpsNode}. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/HomeSP/NetworkID/<X+> subtree |
| * @return Pair<String, Long> representing <SSID, HESSID> pair. |
| * @throws ParsingException |
| */ |
| static private Pair<String, Long> parseNetworkIdInstance(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for NetworkID instance"); |
| } |
| |
| String ssid = null; |
| Long hessid = null; |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_SSID: |
| ssid = getPpsNodeValue(child); |
| break; |
| case NODE_HESSID: |
| hessid = parseLong(getPpsNodeValue(child), 16); |
| break; |
| default: |
| throw new ParsingException("Unknown node under NetworkID instance: " + |
| child.getName()); |
| } |
| } |
| if (ssid == null) |
| throw new ParsingException("NetworkID instance missing SSID"); |
| |
| return new Pair<String, Long>(ssid, hessid); |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree. |
| * |
| * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList |
| * subtree |
| * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list. |
| * @throws ParsingException |
| */ |
| private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for HomeOIList"); |
| } |
| |
| List<Long> matchAllOIs = new ArrayList<Long>(); |
| List<Long> matchAnyOIs = new ArrayList<Long>(); |
| for (PPSNode child : node.getChildren()) { |
| Pair<Long, Boolean> homeOI = parseHomeOIInstance(child); |
| if (homeOI.second.booleanValue()) { |
| matchAllOIs.add(homeOI.first); |
| } else { |
| matchAnyOIs.add(homeOI.first); |
| } |
| } |
| return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs); |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree. |
| * The instance name (<X+>) is irrelevant and must be unique for each instance, which |
| * is verified when the PPS tree is constructed {@link #buildPpsNode}. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree |
| * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag |
| * @throws ParsingException |
| */ |
| private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for HomeOI instance"); |
| } |
| |
| Long oi = null; |
| Boolean required = null; |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_HOME_OI: |
| try { |
| oi = Long.valueOf(getPpsNodeValue(child), 16); |
| } catch (NumberFormatException e) { |
| throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child)); |
| } |
| break; |
| case NODE_HOME_OI_REQUIRED: |
| required = Boolean.valueOf(getPpsNodeValue(child)); |
| break; |
| default: |
| throw new ParsingException("Unknown node under NetworkID instance: " + |
| child.getName()); |
| } |
| } |
| if (oi == null) { |
| throw new ParsingException("HomeOI instance missing OI field"); |
| } |
| if (required == null) { |
| throw new ParsingException("HomeOI instance missing required field"); |
| } |
| return new Pair<Long, Boolean>(oi, required); |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree. |
| * This contains a list of FQDN (Fully Qualified Domain Name) that are considered |
| * home partners. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/HomeSP/OtherHomePartners subtree |
| * @return String[] list of partner's FQDN |
| * @throws ParsingException |
| */ |
| private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for OtherHomePartners"); |
| } |
| List<String> otherHomePartners = new ArrayList<String>(); |
| for (PPSNode child : node.getChildren()) { |
| String fqdn = parseOtherHomePartnerInstance(child); |
| otherHomePartners.add(fqdn); |
| } |
| return otherHomePartners.toArray(new String[otherHomePartners.size()]); |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree. |
| * The instance name (<X+>) is irrelevant and must be unique for each instance, which |
| * is verified when the PPS tree is constructed {@link #buildPpsNode}. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree |
| * @return String FQDN of the partner |
| * @throws ParsingException |
| */ |
| private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for OtherHomePartner instance"); |
| } |
| String fqdn = null; |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_FQDN: |
| fqdn = getPpsNodeValue(child); |
| break; |
| default: |
| throw new ParsingException( |
| "Unknown node under OtherHomePartner instance: " + child.getName()); |
| } |
| } |
| if (fqdn == null) { |
| throw new ParsingException("OtherHomePartner instance missing FQDN field"); |
| } |
| return fqdn; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/Credential subtree. |
| * |
| * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree |
| * @return Credential |
| * @throws ParsingException |
| */ |
| private static Credential parseCredential(PPSNode node) throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for HomeSP"); |
| } |
| |
| Credential credential = new Credential(); |
| for (PPSNode child: node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_CREATION_DATE: |
| credential.setCreationTimeInMillis(parseDate(getPpsNodeValue(child))); |
| break; |
| case NODE_EXPIRATION_DATE: |
| credential.setExpirationTimeInMillis(parseDate(getPpsNodeValue(child))); |
| break; |
| case NODE_USERNAME_PASSWORD: |
| credential.setUserCredential(parseUserCredential(child)); |
| break; |
| case NODE_DIGITAL_CERTIFICATE: |
| credential.setCertCredential(parseCertificateCredential(child)); |
| break; |
| case NODE_REALM: |
| credential.setRealm(getPpsNodeValue(child)); |
| break; |
| case NODE_CHECK_AAA_SERVER_CERT_STATUS: |
| credential.setCheckAaaServerCertStatus( |
| Boolean.parseBoolean(getPpsNodeValue(child))); |
| break; |
| case NODE_SIM: |
| credential.setSimCredential(parseSimCredential(child)); |
| break; |
| default: |
| throw new ParsingException("Unknown node under Credential: " + |
| child.getName()); |
| } |
| } |
| return credential; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/Credential/UsernamePassword subtree. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/Credential/UsernamePassword subtree |
| * @return Credential.UserCredential |
| * @throws ParsingException |
| */ |
| private static Credential.UserCredential parseUserCredential(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for UsernamePassword"); |
| } |
| |
| Credential.UserCredential userCred = new Credential.UserCredential(); |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_USERNAME: |
| userCred.setUsername(getPpsNodeValue(child)); |
| break; |
| case NODE_PASSWORD: |
| userCred.setPassword(getPpsNodeValue(child)); |
| break; |
| case NODE_MACHINE_MANAGED: |
| userCred.setMachineManaged(Boolean.parseBoolean(getPpsNodeValue(child))); |
| break; |
| case NODE_SOFT_TOKEN_APP: |
| userCred.setSoftTokenApp(getPpsNodeValue(child)); |
| break; |
| case NODE_ABLE_TO_SHARE: |
| userCred.setAbleToShare(Boolean.parseBoolean(getPpsNodeValue(child))); |
| break; |
| case NODE_EAP_METHOD: |
| parseEAPMethod(child, userCred); |
| break; |
| default: |
| throw new ParsingException("Unknown node under UsernamPassword: " + |
| child.getName()); |
| } |
| } |
| return userCred; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/Credential/UsernamePassword/EAPMethod |
| * subtree. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/Credential/UsernamePassword/EAPMethod subtree |
| * @param userCred UserCredential to be updated with EAP method values. |
| * @throws ParsingException |
| */ |
| private static void parseEAPMethod(PPSNode node, Credential.UserCredential userCred) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for EAPMethod"); |
| } |
| |
| for (PPSNode child : node.getChildren()) { |
| switch(child.getName()) { |
| case NODE_EAP_TYPE: |
| userCred.setEapType(parseInteger(getPpsNodeValue(child))); |
| break; |
| case NODE_INNER_METHOD: |
| userCred.setNonEapInnerMethod(getPpsNodeValue(child)); |
| break; |
| case NODE_VENDOR_ID: |
| case NODE_VENDOR_TYPE: |
| case NODE_INNER_EAP_TYPE: |
| case NODE_INNER_VENDOR_ID: |
| case NODE_INNER_VENDOR_TYPE: |
| // Only EAP-TTLS is currently supported for user credential, which doesn't |
| // use any of these parameters. |
| Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName()); |
| break; |
| default: |
| throw new ParsingException("Unknown node under EAPMethod: " + child.getName()); |
| } |
| } |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/Credential/DigitalCertificate subtree. |
| * |
| * @param node PPSNode representing the root of the |
| * PerProviderSubscription/Credential/DigitalCertificate subtree |
| * @return Credential.CertificateCredential |
| * @throws ParsingException |
| */ |
| private static Credential.CertificateCredential parseCertificateCredential(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for DigitalCertificate"); |
| } |
| |
| Credential.CertificateCredential certCred = new Credential.CertificateCredential(); |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_CERTIFICATE_TYPE: |
| certCred.setCertType(getPpsNodeValue(child)); |
| break; |
| case NODE_CERT_SHA256_FINGERPRINT: |
| certCred.setCertSha256Fingerprint(parseHexString(getPpsNodeValue(child))); |
| break; |
| default: |
| throw new ParsingException("Unknown node under DigitalCertificate: " + |
| child.getName()); |
| } |
| } |
| return certCred; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/Credential/SIM subtree. |
| * |
| * @param node PPSNode representing the root of the PerProviderSubscription/Credential/SIM |
| * subtree |
| * @return Credential.SimCredential |
| * @throws ParsingException |
| */ |
| private static Credential.SimCredential parseSimCredential(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for SIM"); |
| } |
| |
| Credential.SimCredential simCred = new Credential.SimCredential(); |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_SIM_IMSI: |
| simCred.setImsi(getPpsNodeValue(child)); |
| break; |
| case NODE_EAP_TYPE: |
| simCred.setEapType(parseInteger(getPpsNodeValue(child))); |
| break; |
| default: |
| throw new ParsingException("Unknown node under SIM: " + child.getName()); |
| } |
| } |
| 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.setPreferredRoamingPartnerList(parsePreferredRoamingPartnerList(child)); |
| break; |
| case NODE_MIN_BACKHAUL_THRESHOLD: |
| parseMinBackhaulThreshold(child, policy); |
| break; |
| case NODE_POLICY_UPDATE: |
| policy.setPolicyUpdate(parseUpdateParameter(child)); |
| break; |
| case NODE_SP_EXCLUSION_LIST: |
| policy.setExcludedSsidList(parseSpExclusionList(child)); |
| break; |
| case NODE_REQUIRED_PROTO_PORT_TUPLE: |
| policy.setRequiredProtoPortMap(parseRequiredProtoPortTuple(child)); |
| break; |
| case NODE_MAXIMUM_BSS_LOAD_VALUE: |
| policy.setMaximumBssLoadValue(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.setFqdn(fqdnMatchArray[0]); |
| if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) { |
| roamingPartner.setFqdnExactMatch(true); |
| } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) { |
| roamingPartner.setFqdnExactMatch(false); |
| } else { |
| throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch); |
| } |
| break; |
| case NODE_PRIORITY: |
| roamingPartner.setPriority(parseInteger(getPpsNodeValue(child))); |
| break; |
| case NODE_COUNTRY: |
| roamingPartner.setCountries(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: |
| downlinkBandwidth = parseLong(getPpsNodeValue(child), 10); |
| break; |
| case NODE_UPLINK_BANDWIDTH: |
| uplinkBandwidth = parseLong(getPpsNodeValue(child), 10); |
| 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.setMinHomeDownlinkBandwidth(downlinkBandwidth); |
| policy.setMinHomeUplinkBandwidth(uplinkBandwidth); |
| } else if (TextUtils.equals(networkType, "roaming")) { |
| policy.setMinRoamingDownlinkBandwidth(downlinkBandwidth); |
| policy.setMinRoamingUplinkBandwidth(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: |
| updateParam.setUpdateIntervalInMinutes(parseLong(getPpsNodeValue(child), 10)); |
| break; |
| case NODE_UPDATE_METHOD: |
| updateParam.setUpdateMethod(getPpsNodeValue(child)); |
| break; |
| case NODE_RESTRICTION: |
| updateParam.setRestriction(getPpsNodeValue(child)); |
| break; |
| case NODE_URI: |
| updateParam.setServerUri(getPpsNodeValue(child)); |
| break; |
| case NODE_USERNAME_PASSWORD: |
| Pair<String, String> usernamePassword = parseUpdateUserCredential(child); |
| updateParam.setUsername(usernamePassword.first); |
| updateParam.setBase64EncodedPassword(usernamePassword.second); |
| break; |
| case NODE_TRUST_ROOT: |
| Pair<String, byte[]> trustRoot = parseTrustRoot(child); |
| updateParam.setTrustRootCertUrl(trustRoot.first); |
| updateParam.setTrustRootCertSha256Fingerprint(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 update, subscription update, or AAA |
| * server trust root. |
| * |
| * This contained configurations under either |
| * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or |
| * PerProviderSubscription/SubscriptionUpdate/TrustRoot or |
| * PerProviderSubscription/AAAServerTrustRoot/<X+> 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[]> parseTrustRoot(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); |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree. |
| * |
| * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot |
| * subtree |
| * @return Map of certificate URL with the corresponding certificate fingerprint |
| * @throws ParsingException |
| */ |
| private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for AAAServerTrustRoot"); |
| } |
| Map<String, byte[]> certList = new HashMap<>(); |
| for (PPSNode child : node.getChildren()) { |
| Pair<String, byte[]> certTuple = parseTrustRoot(child); |
| certList.put(certTuple.first, certTuple.second); |
| } |
| return certList; |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree. |
| * |
| * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter |
| * subtree |
| * @param config Instance of {@link PasspointConfiguration} |
| * @throws ParsingException |
| */ |
| private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for SubscriptionParameter"); |
| } |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_CREATION_DATE: |
| config.setSubscriptionCreationTimeInMillis(parseDate(getPpsNodeValue(child))); |
| break; |
| case NODE_EXPIRATION_DATE: |
| config.setSubscriptionExpirationTimeInMillis(parseDate(getPpsNodeValue(child))); |
| break; |
| case NODE_TYPE_OF_SUBSCRIPTION: |
| config.setSubscriptionType(getPpsNodeValue(child)); |
| break; |
| case NODE_USAGE_LIMITS: |
| parseUsageLimits(child, config); |
| break; |
| default: |
| throw new ParsingException("Unknown node under SubscriptionParameter" |
| + child.getName()); |
| } |
| } |
| } |
| |
| /** |
| * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits |
| * subtree. |
| * |
| * @param node PPSNode representing the root of |
| * PerProviderSubscription/SubscriptionParameter/UsageLimits subtree |
| * @param config Instance of {@link PasspointConfiguration} |
| * @throws ParsingException |
| */ |
| private static void parseUsageLimits(PPSNode node, PasspointConfiguration config) |
| throws ParsingException { |
| if (node.isLeaf()) { |
| throw new ParsingException("Leaf node not expected for UsageLimits"); |
| } |
| for (PPSNode child : node.getChildren()) { |
| switch (child.getName()) { |
| case NODE_DATA_LIMIT: |
| config.setUsageLimitDataLimit(parseLong(getPpsNodeValue(child), 10)); |
| break; |
| case NODE_START_DATE: |
| config.setUsageLimitStartTimeInMillis(parseDate(getPpsNodeValue(child))); |
| break; |
| case NODE_TIME_LIMIT: |
| config.setUsageLimitTimeLimitInMinutes(parseLong(getPpsNodeValue(child), 10)); |
| break; |
| case NODE_USAGE_TIME_PERIOD: |
| config.setUsageLimitUsageTimePeriodInMinutes( |
| parseLong(getPpsNodeValue(child), 10)); |
| break; |
| default: |
| throw new ParsingException("Unknown node under UsageLimits" |
| + child.getName()); |
| } |
| } |
| } |
| |
| /** |
| * Convert a hex string to a byte array. |
| * |
| * @param str String containing hex values |
| * @return byte[] |
| * @throws ParsingException |
| */ |
| private static byte[] parseHexString(String str) throws ParsingException { |
| if ((str.length() & 1) == 1) { |
| throw new ParsingException("Odd length hex string: " + str.length()); |
| } |
| |
| byte[] result = new byte[str.length() / 2]; |
| for (int i = 0; i < result.length; i++) { |
| int index = i * 2; |
| try { |
| result[i] = (byte) Integer.parseInt(str.substring(index, index + 2), 16); |
| } catch (NumberFormatException e) { |
| throw new ParsingException("Invalid hex string: " + str); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT. |
| * |
| * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z' |
| * @return number of milliseconds |
| * @throws ParsingException |
| */ |
| private static long parseDate(String dateStr) throws ParsingException { |
| try { |
| DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); |
| return format.parse(dateStr).getTime(); |
| } catch (ParseException pe) { |
| throw new ParsingException("Badly formatted time: " + dateStr); |
| } |
| } |
| |
| /** |
| * Parse an integer string. |
| * |
| * @param value String of integer value |
| * @return int |
| * @throws ParsingException |
| */ |
| private static int parseInteger(String value) throws ParsingException { |
| try { |
| return Integer.parseInt(value); |
| } catch (NumberFormatException e) { |
| throw new ParsingException("Invalid integer value: " + value); |
| } |
| } |
| |
| /** |
| * Parse a string representing a long integer. |
| * |
| * @param value String of long integer value |
| * @return long |
| * @throws ParsingException |
| */ |
| private static long parseLong(String value, int radix) throws ParsingException { |
| try { |
| return Long.parseLong(value, radix); |
| } catch (NumberFormatException e) { |
| throw new ParsingException("Invalid long integer value: " + value); |
| } |
| } |
| |
| /** |
| * Convert a List<Long> to a primitive long array long[]. |
| * |
| * @param list List to be converted |
| * @return long[] |
| */ |
| private static long[] convertFromLongList(List<Long> list) { |
| Long[] objectArray = list.toArray(new Long[list.size()]); |
| long[] primitiveArray = new long[objectArray.length]; |
| for (int i = 0; i < objectArray.length; i++) { |
| primitiveArray[i] = objectArray[i].longValue(); |
| } |
| return primitiveArray; |
| } |
| } |