Home | History | Annotate | Download | only in omadm
      1 /**
      2  * Copyright (c) 2016, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.net.wifi.hotspot2.omadm;
     18 
     19 import android.net.wifi.hotspot2.PasspointConfiguration;
     20 import android.net.wifi.hotspot2.pps.Credential;
     21 import android.net.wifi.hotspot2.pps.HomeSp;
     22 import android.net.wifi.hotspot2.pps.Policy;
     23 import android.net.wifi.hotspot2.pps.UpdateParameter;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 import android.util.Pair;
     27 
     28 import java.io.IOException;
     29 import java.text.DateFormat;
     30 import java.text.ParseException;
     31 import java.text.SimpleDateFormat;
     32 import java.util.ArrayList;
     33 import java.util.HashMap;
     34 import java.util.HashSet;
     35 import java.util.List;
     36 import java.util.Map;
     37 import java.util.Set;
     38 
     39 import org.xml.sax.SAXException;
     40 
     41 /**
     42  * Utility class for converting OMA-DM (Open Mobile Alliance's Device Management)
     43  * PPS-MO (PerProviderSubscription Management Object) XML tree to a
     44  * {@link PasspointConfiguration} object.
     45  *
     46  * Currently this only supports PerProviderSubscription/HomeSP and
     47  * PerProviderSubscription/Credential subtree for Hotspot 2.0 Release 1 support.
     48  *
     49  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
     50  * Release 2 Technical Specification.
     51  *
     52  * Below is a sample XML string for a Release 1 PPS MO tree:
     53  *
     54  * <MgmtTree xmlns="syncml:dmddf1.2">
     55  *   <VerDTD>1.2</VerDTD>
     56  *   <Node>
     57  *     <NodeName>PerProviderSubscription</NodeName>
     58  *     <RTProperties>
     59  *       <Type>
     60  *         <DDFName>urn:wfa:mo:hotspot2dot0perprovidersubscription:1.0</DDFName>
     61  *       </Type>
     62  *     </RTProperties>
     63  *     <Node>
     64  *       <NodeName>i001</NodeName>
     65  *       <Node>
     66  *         <NodeName>HomeSP</NodeName>
     67  *         <Node>
     68  *           <NodeName>FriendlyName</NodeName>
     69  *           <Value>Century House</Value>
     70  *         </Node>
     71  *         <Node>
     72  *           <NodeName>FQDN</NodeName>
     73  *           <Value>mi6.co.uk</Value>
     74  *         </Node>
     75  *         <Node>
     76  *           <NodeName>RoamingConsortiumOI</NodeName>
     77  *           <Value>112233,445566</Value>
     78  *         </Node>
     79  *       </Node>
     80  *       <Node>
     81  *         <NodeName>Credential</NodeName>
     82  *         <Node>
     83  *           <NodeName>Realm</NodeName>
     84  *           <Value>shaken.stirred.com</Value>
     85  *         </Node>
     86  *         <Node>
     87  *           <NodeName>UsernamePassword</NodeName>
     88  *           <Node>
     89  *             <NodeName>Username</NodeName>
     90  *             <Value>james</Value>
     91  *           </Node>
     92  *           <Node>
     93  *             <NodeName>Password</NodeName>
     94  *             <Value>Ym9uZDAwNw==</Value>
     95  *           </Node>
     96  *           <Node>
     97  *             <NodeName>EAPMethod</NodeName>
     98  *             <Node>
     99  *               <NodeName>EAPType</NodeName>
    100  *               <Value>21</Value>
    101  *             </Node>
    102  *             <Node>
    103  *               <NodeName>InnerMethod</NodeName>
    104  *               <Value>MS-CHAP-V2</Value>
    105  *             </Node>
    106  *           </Node>
    107  *         </Node>
    108  *       </Node>
    109  *     </Node>
    110  *   </Node>
    111  * </MgmtTree>
    112  */
    113 public final class PpsMoParser {
    114     private static final String TAG = "PpsMoParser";
    115 
    116     /**
    117      * XML tags expected in the PPS MO (PerProviderSubscription Management Object) XML tree.
    118      */
    119     private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
    120     private static final String TAG_VER_DTD = "VerDTD";
    121     private static final String TAG_NODE = "Node";
    122     private static final String TAG_NODE_NAME = "NodeName";
    123     private static final String TAG_RT_PROPERTIES = "RTProperties";
    124     private static final String TAG_TYPE = "Type";
    125     private static final String TAG_DDF_NAME = "DDFName";
    126     private static final String TAG_VALUE = "Value";
    127 
    128     /**
    129      * Name for PerProviderSubscription node.
    130      */
    131     private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
    132 
    133     /**
    134      * Fields under PerProviderSubscription.
    135      */
    136     private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
    137     private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
    138     private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
    139     private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
    140     private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
    141     private static final String NODE_USAGE_LIMITS = "UsageLimits";
    142     private static final String NODE_DATA_LIMIT = "DataLimit";
    143     private static final String NODE_START_DATE = "StartDate";
    144     private static final String NODE_TIME_LIMIT = "TimeLimit";
    145     private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
    146     private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
    147     private static final String NODE_EXTENSION = "Extension";
    148 
    149     /**
    150      * Fields under HomeSP subtree.
    151      */
    152     private static final String NODE_HOMESP = "HomeSP";
    153     private static final String NODE_FQDN = "FQDN";
    154     private static final String NODE_FRIENDLY_NAME = "FriendlyName";
    155     private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
    156     private static final String NODE_NETWORK_ID = "NetworkID";
    157     private static final String NODE_SSID = "SSID";
    158     private static final String NODE_HESSID = "HESSID";
    159     private static final String NODE_ICON_URL = "IconURL";
    160     private static final String NODE_HOME_OI_LIST = "HomeOIList";
    161     private static final String NODE_HOME_OI = "HomeOI";
    162     private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired";
    163     private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners";
    164 
    165     /**
    166      * Fields under Credential subtree.
    167      */
    168     private static final String NODE_CREDENTIAL = "Credential";
    169     private static final String NODE_CREATION_DATE = "CreationDate";
    170     private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
    171     private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
    172     private static final String NODE_USERNAME = "Username";
    173     private static final String NODE_PASSWORD = "Password";
    174     private static final String NODE_MACHINE_MANAGED = "MachineManaged";
    175     private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
    176     private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
    177     private static final String NODE_EAP_METHOD = "EAPMethod";
    178     private static final String NODE_EAP_TYPE = "EAPType";
    179     private static final String NODE_VENDOR_ID = "VendorId";
    180     private static final String NODE_VENDOR_TYPE = "VendorType";
    181     private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
    182     private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
    183     private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
    184     private static final String NODE_INNER_METHOD = "InnerMethod";
    185     private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
    186     private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
    187     private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
    188     private static final String NODE_REALM = "Realm";
    189     private static final String NODE_SIM = "SIM";
    190     private static final String NODE_SIM_IMSI = "IMSI";
    191     private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
    192 
    193     /**
    194      * Fields under Policy subtree.
    195      */
    196     private static final String NODE_POLICY = "Policy";
    197     private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
    198             "PreferredRoamingPartnerList";
    199     private static final String NODE_FQDN_MATCH = "FQDN_Match";
    200     private static final String NODE_PRIORITY = "Priority";
    201     private static final String NODE_COUNTRY = "Country";
    202     private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
    203     private static final String NODE_NETWORK_TYPE = "NetworkType";
    204     private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
    205     private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
    206     private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
    207     private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
    208     private static final String NODE_UPDATE_METHOD = "UpdateMethod";
    209     private static final String NODE_RESTRICTION = "Restriction";
    210     private static final String NODE_URI = "URI";
    211     private static final String NODE_TRUST_ROOT = "TrustRoot";
    212     private static final String NODE_CERT_URL = "CertURL";
    213     private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
    214     private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
    215     private static final String NODE_IP_PROTOCOL = "IPProtocol";
    216     private static final String NODE_PORT_NUMBER = "PortNumber";
    217     private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
    218     private static final String NODE_OTHER = "Other";
    219 
    220     /**
    221      * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
    222      */
    223     private static final String PPS_MO_URN =
    224             "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
    225 
    226     /**
    227      * Exception for generic parsing errors.
    228      */
    229     private static class ParsingException extends Exception {
    230         public ParsingException(String message) {
    231             super(message);
    232         }
    233     }
    234 
    235     /**
    236      * Class representing a node within the PerProviderSubscription tree.
    237      * This is used to flatten out and eliminate the extra layering in the XMLNode tree,
    238      * to make the data parsing easier and cleaner.
    239      *
    240      * A PPSNode can be an internal or a leaf node, but not both.
    241      *
    242      */
    243     private static abstract class PPSNode {
    244         private final String mName;
    245         public PPSNode(String name) {
    246             mName = name;
    247         }
    248 
    249         /**
    250          * @return the name of the node
    251          */
    252         public String getName() {
    253             return mName;
    254         }
    255 
    256         /**
    257          * Applies for internal node only.
    258          *
    259          * @return the list of children nodes.
    260          */
    261         public abstract List<PPSNode> getChildren();
    262 
    263         /**
    264          * Applies for leaf node only.
    265          *
    266          * @return the string value of the node
    267          */
    268         public abstract String getValue();
    269 
    270         /**
    271          * @return a flag indicating if this is a leaf or an internal node
    272          */
    273         public abstract boolean isLeaf();
    274     }
    275 
    276     /**
    277      * Class representing a leaf node in a PPS (PerProviderSubscription) tree.
    278      */
    279     private static class LeafNode extends PPSNode {
    280         private final String mValue;
    281         public LeafNode(String nodeName, String value) {
    282             super(nodeName);
    283             mValue = value;
    284         }
    285 
    286         @Override
    287         public String getValue() {
    288             return mValue;
    289         }
    290 
    291         @Override
    292         public List<PPSNode> getChildren() {
    293             return null;
    294         }
    295 
    296         @Override
    297         public boolean isLeaf() {
    298             return true;
    299         }
    300     }
    301 
    302     /**
    303      * Class representing an internal node in a PPS (PerProviderSubscription) tree.
    304      */
    305     private static class InternalNode extends PPSNode {
    306         private final List<PPSNode> mChildren;
    307         public InternalNode(String nodeName, List<PPSNode> children) {
    308             super(nodeName);
    309             mChildren = children;
    310         }
    311 
    312         @Override
    313         public String getValue() {
    314             return null;
    315         }
    316 
    317         @Override
    318         public List<PPSNode> getChildren() {
    319             return mChildren;
    320         }
    321 
    322         @Override
    323         public boolean isLeaf() {
    324             return false;
    325         }
    326     }
    327 
    328     /**
    329      * @hide
    330      */
    331     public PpsMoParser() {}
    332 
    333     /**
    334      * Convert a XML string representation of a PPS MO (PerProviderSubscription
    335      * Management Object) tree to a {@link PasspointConfiguration} object.
    336      *
    337      * @param xmlString XML string representation of a PPS MO tree
    338      * @return {@link PasspointConfiguration} or null
    339      */
    340     public static PasspointConfiguration parseMoText(String xmlString) {
    341         // Convert the XML string to a XML tree.
    342         XMLParser xmlParser = new XMLParser();
    343         XMLNode root = null;
    344         try {
    345             root = xmlParser.parse(xmlString);
    346         } catch(IOException | SAXException e) {
    347             return null;
    348         }
    349         if (root == null) {
    350             return null;
    351         }
    352 
    353         // Verify root node is a "MgmtTree" node.
    354         if (root.getTag() != TAG_MANAGEMENT_TREE) {
    355             Log.e(TAG, "Root is not a MgmtTree");
    356             return null;
    357         }
    358 
    359         String verDtd = null;    // Used for detecting duplicate VerDTD element.
    360         PasspointConfiguration config = null;
    361         for (XMLNode child : root.getChildren()) {
    362             switch(child.getTag()) {
    363                 case TAG_VER_DTD:
    364                     if (verDtd != null) {
    365                         Log.e(TAG, "Duplicate VerDTD element");
    366                         return null;
    367                     }
    368                     verDtd = child.getText();
    369                     break;
    370                 case TAG_NODE:
    371                     if (config != null) {
    372                         Log.e(TAG, "Unexpected multiple Node element under MgmtTree");
    373                         return null;
    374                     }
    375                     try {
    376                         config = parsePpsNode(child);
    377                     } catch (ParsingException e) {
    378                         Log.e(TAG, e.getMessage());
    379                         return null;
    380                     }
    381                     break;
    382                 default:
    383                     Log.e(TAG, "Unknown node: " + child.getTag());
    384                     return null;
    385             }
    386         }
    387         return config;
    388     }
    389 
    390     /**
    391      * Parse a PerProviderSubscription node. Below is the format of the XML tree (with
    392      * each XML element represent a node in the tree):
    393      *
    394      * <Node>
    395      *   <NodeName>PerProviderSubscription</NodeName>
    396      *   <RTProperties>
    397      *     ...
    398      *   </RTPProperties>
    399      *   <Node>
    400      *     <NodeName>UpdateIdentifier</NodeName>
    401      *     <Value>...</Value>
    402      *   </Node>
    403      *   <Node>
    404      *     ...
    405      *   </Node>
    406      * </Node>
    407      *
    408      * @param node XMLNode that contains PerProviderSubscription node.
    409      * @return PasspointConfiguration or null
    410      * @throws ParsingException
    411      */
    412     private static PasspointConfiguration parsePpsNode(XMLNode node)
    413             throws ParsingException {
    414         PasspointConfiguration config = null;
    415         String nodeName = null;
    416         int updateIdentifier = Integer.MIN_VALUE;
    417         for (XMLNode child : node.getChildren()) {
    418             switch (child.getTag()) {
    419                 case TAG_NODE_NAME:
    420                     if (nodeName != null) {
    421                         throw new ParsingException("Duplicate NodeName: " + child.getText());
    422                     }
    423                     nodeName = child.getText();
    424                     if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
    425                         throw new ParsingException("Unexpected NodeName: " + nodeName);
    426                     }
    427                     break;
    428                 case TAG_NODE:
    429                     // A node can be either an UpdateIdentifier node or a PerProviderSubscription
    430                     // instance node.  Flatten out the XML tree first by converting it to a PPS
    431                     // tree to reduce the complexity of the parsing code.
    432                     PPSNode ppsNodeRoot = buildPpsNode(child);
    433                     if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
    434                         if (updateIdentifier != Integer.MIN_VALUE) {
    435                             throw new ParsingException("Multiple node for UpdateIdentifier");
    436                         }
    437                         updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
    438                     } else {
    439                         // Only one PerProviderSubscription instance is expected and allowed.
    440                         if (config != null) {
    441                             throw new ParsingException("Multiple PPS instance");
    442                         }
    443                         config = parsePpsInstance(ppsNodeRoot);
    444                     }
    445                     break;
    446                 case TAG_RT_PROPERTIES:
    447                     // Parse and verify URN stored in the RT (Run Time) Properties.
    448                     String urn = parseUrn(child);
    449                     if (!TextUtils.equals(urn, PPS_MO_URN)) {
    450                         throw new ParsingException("Unknown URN: " + urn);
    451                     }
    452                     break;
    453                 default:
    454                     throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
    455             }
    456         }
    457         if (config != null && updateIdentifier != Integer.MIN_VALUE) {
    458             config.setUpdateIdentifier(updateIdentifier);
    459         }
    460         return config;
    461     }
    462 
    463     /**
    464      * Parse the URN stored in the RTProperties. Below is the format of the RTPProperties node:
    465      *
    466      * <RTProperties>
    467      *   <Type>
    468      *     <DDFName>urn:...</DDFName>
    469      *   </Type>
    470      * </RTProperties>
    471      *
    472      * @param node XMLNode that contains RTProperties node.
    473      * @return URN String of URN.
    474      * @throws ParsingException
    475      */
    476     private static String parseUrn(XMLNode node) throws ParsingException {
    477         if (node.getChildren().size() != 1)
    478             throw new ParsingException("Expect RTPProperties node to only have one child");
    479 
    480         XMLNode typeNode = node.getChildren().get(0);
    481         if (typeNode.getChildren().size() != 1) {
    482             throw new ParsingException("Expect Type node to only have one child");
    483         }
    484         if (!TextUtils.equals(typeNode.getTag(), TAG_TYPE)) {
    485             throw new ParsingException("Unexpected tag for Type: " + typeNode.getTag());
    486         }
    487 
    488         XMLNode ddfNameNode = typeNode.getChildren().get(0);
    489         if (!ddfNameNode.getChildren().isEmpty()) {
    490             throw new ParsingException("Expect DDFName node to have no child");
    491         }
    492         if (!TextUtils.equals(ddfNameNode.getTag(), TAG_DDF_NAME)) {
    493             throw new ParsingException("Unexpected tag for DDFName: " + ddfNameNode.getTag());
    494         }
    495 
    496         return ddfNameNode.getText();
    497     }
    498 
    499     /**
    500      * Convert a XML tree represented by XMLNode to a PPS (PerProviderSubscription) instance tree
    501      * represented by PPSNode.  This flattens out the XML tree to allow easier and cleaner parsing
    502      * of the PPS configuration data.  Only three types of XML tag are expected: "NodeName",
    503      * "Node", and "Value".
    504      *
    505      * The original XML tree (each XML element represent a node):
    506      *
    507      * <Node>
    508      *   <NodeName>root</NodeName>
    509      *   <Node>
    510      *     <NodeName>child1</NodeName>
    511      *     <Value>value1</Value>
    512      *   </Node>
    513      *   <Node>
    514      *     <NodeName>child2</NodeName>
    515      *     <Node>
    516      *       <NodeName>grandchild1</NodeName>
    517      *       ...
    518      *     </Node>
    519      *   </Node>
    520      *   ...
    521      * </Node>
    522      *
    523      * The converted PPS tree:
    524      *
    525      * [root] --- [child1, value1]
    526      *   |
    527      *   ---------[child2] --------[grandchild1] --- ...
    528      *
    529      * @param node XMLNode pointed to the root of a XML tree
    530      * @return PPSNode pointing to the root of a PPS tree
    531      * @throws ParsingException
    532      */
    533     private static PPSNode buildPpsNode(XMLNode node) throws ParsingException {
    534         String nodeName = null;
    535         String nodeValue = null;
    536         List<PPSNode> childNodes = new ArrayList<PPSNode>();
    537         // Names of parsed child nodes, use for detecting multiple child nodes with the same name.
    538         Set<String> parsedNodes = new HashSet<String>();
    539 
    540         for (XMLNode child : node.getChildren()) {
    541             String tag = child.getTag();
    542             if (TextUtils.equals(tag, TAG_NODE_NAME)) {
    543                 if (nodeName != null) {
    544                     throw new ParsingException("Duplicate NodeName node");
    545                 }
    546                 nodeName = child.getText();
    547             } else if (TextUtils.equals(tag, TAG_NODE)) {
    548                 PPSNode ppsNode = buildPpsNode(child);
    549                 if (parsedNodes.contains(ppsNode.getName())) {
    550                     throw new ParsingException("Duplicate node: " + ppsNode.getName());
    551                 }
    552                 parsedNodes.add(ppsNode.getName());
    553                 childNodes.add(ppsNode);
    554             } else if (TextUtils.equals(tag, TAG_VALUE)) {
    555                if (nodeValue != null) {
    556                    throw new ParsingException("Duplicate Value node");
    557                }
    558                nodeValue = child.getText();
    559             } else {
    560                 throw new ParsingException("Unknown tag: " + tag);
    561             }
    562         }
    563 
    564         if (nodeName == null) {
    565             throw new ParsingException("Invalid node: missing NodeName");
    566         }
    567         if (nodeValue == null && childNodes.size() == 0) {
    568             throw new ParsingException("Invalid node: " + nodeName +
    569                     " missing both value and children");
    570         }
    571         if (nodeValue != null && childNodes.size() > 0) {
    572             throw new ParsingException("Invalid node: " + nodeName +
    573                     " contained both value and children");
    574         }
    575 
    576         if (nodeValue != null) {
    577             return new LeafNode(nodeName, nodeValue);
    578         }
    579         return new InternalNode(nodeName, childNodes);
    580     }
    581 
    582     /**
    583      * Return the value of a PPSNode.  An exception will be thrown if the given node
    584      * is not a leaf node.
    585      *
    586      * @param node PPSNode to retrieve the value from
    587      * @return String representing the value of the node
    588      * @throws ParsingException
    589      */
    590     private static String getPpsNodeValue(PPSNode node) throws ParsingException {
    591         if (!node.isLeaf()) {
    592             throw new ParsingException("Cannot get value from a non-leaf node: " + node.getName());
    593         }
    594         return node.getValue();
    595     }
    596 
    597     /**
    598      * Parse a PPS (PerProviderSubscription) configurations from a PPS tree.
    599      *
    600      * @param root PPSNode representing the root of the PPS tree
    601      * @return PasspointConfiguration
    602      * @throws ParsingException
    603      */
    604     private static PasspointConfiguration parsePpsInstance(PPSNode root)
    605             throws ParsingException {
    606         if (root.isLeaf()) {
    607             throw new ParsingException("Leaf node not expected for PPS instance");
    608         }
    609 
    610         PasspointConfiguration config = new PasspointConfiguration();
    611         for (PPSNode child : root.getChildren()) {
    612             switch(child.getName()) {
    613                 case NODE_HOMESP:
    614                     config.setHomeSp(parseHomeSP(child));
    615                     break;
    616                 case NODE_CREDENTIAL:
    617                     config.setCredential(parseCredential(child));
    618                     break;
    619                 case NODE_POLICY:
    620                     config.setPolicy(parsePolicy(child));
    621                     break;
    622                 case NODE_AAA_SERVER_TRUST_ROOT:
    623                     config.setTrustRootCertList(parseAAAServerTrustRootList(child));
    624                     break;
    625                 case NODE_SUBSCRIPTION_UPDATE:
    626                     config.setSubscriptionUpdate(parseUpdateParameter(child));
    627                     break;
    628                 case NODE_SUBSCRIPTION_PARAMETER:
    629                     parseSubscriptionParameter(child, config);
    630                     break;
    631                 case NODE_CREDENTIAL_PRIORITY:
    632                     config.setCredentialPriority(parseInteger(getPpsNodeValue(child)));
    633                     break;
    634                 case NODE_EXTENSION:
    635                     // All vendor specific information will be under this node.
    636                     Log.d(TAG, "Ignore Extension node for vendor specific information");
    637                     break;
    638                 default:
    639                     throw new ParsingException("Unknown node: " + child.getName());
    640             }
    641         }
    642         return config;
    643     }
    644 
    645     /**
    646      * Parse configurations under PerProviderSubscription/HomeSP subtree.
    647      *
    648      * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP subtree
    649      * @return HomeSP
    650      * @throws ParsingException
    651      */
    652     private static HomeSp parseHomeSP(PPSNode node) throws ParsingException {
    653         if (node.isLeaf()) {
    654             throw new ParsingException("Leaf node not expected for HomeSP");
    655         }
    656 
    657         HomeSp homeSp = new HomeSp();
    658         for (PPSNode child : node.getChildren()) {
    659             switch (child.getName()) {
    660                 case NODE_FQDN:
    661                     homeSp.setFqdn(getPpsNodeValue(child));
    662                     break;
    663                 case NODE_FRIENDLY_NAME:
    664                     homeSp.setFriendlyName(getPpsNodeValue(child));
    665                     break;
    666                 case NODE_ROAMING_CONSORTIUM_OI:
    667                     homeSp.setRoamingConsortiumOis(
    668                             parseRoamingConsortiumOI(getPpsNodeValue(child)));
    669                     break;
    670                 case NODE_ICON_URL:
    671                     homeSp.setIconUrl(getPpsNodeValue(child));
    672                     break;
    673                 case NODE_NETWORK_ID:
    674                     homeSp.setHomeNetworkIds(parseNetworkIds(child));
    675                     break;
    676                 case NODE_HOME_OI_LIST:
    677                     Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child);
    678                     homeSp.setMatchAllOis(convertFromLongList(homeOIs.first));
    679                     homeSp.setMatchAnyOis(convertFromLongList(homeOIs.second));
    680                     break;
    681                 case NODE_OTHER_HOME_PARTNERS:
    682                     homeSp.setOtherHomePartners(parseOtherHomePartners(child));
    683                     break;
    684                 default:
    685                     throw new ParsingException("Unknown node under HomeSP: " + child.getName());
    686             }
    687         }
    688         return homeSp;
    689     }
    690 
    691     /**
    692      * Parse the roaming consortium OI string, which contains a list of OIs separated by ",".
    693      *
    694      * @param oiStr string containing list of OIs (Organization Identifiers) separated by ","
    695      * @return long[]
    696      * @throws ParsingException
    697      */
    698     private static long[] parseRoamingConsortiumOI(String oiStr)
    699             throws ParsingException {
    700         String[] oiStrArray = oiStr.split(",");
    701         long[] oiArray = new long[oiStrArray.length];
    702         for (int i = 0; i < oiStrArray.length; i++) {
    703             oiArray[i] = parseLong(oiStrArray[i], 16);
    704         }
    705         return oiArray;
    706     }
    707 
    708     /**
    709      * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree.
    710      *
    711      * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID
    712      *             subtree
    713      * @return HashMap<String, Long> representing list of <SSID, HESSID> pair.
    714      * @throws ParsingException
    715      */
    716     static private Map<String, Long> parseNetworkIds(PPSNode node)
    717             throws ParsingException {
    718         if (node.isLeaf()) {
    719             throw new ParsingException("Leaf node not expected for NetworkID");
    720         }
    721 
    722         Map<String, Long> networkIds = new HashMap<>();
    723         for (PPSNode child : node.getChildren()) {
    724             Pair<String, Long> networkId = parseNetworkIdInstance(child);
    725             networkIds.put(networkId.first, networkId.second);
    726         }
    727         return networkIds;
    728     }
    729 
    730     /**
    731      * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree.
    732      * The instance name (<X+>) is irrelevant and must be unique for each instance, which
    733      * is verified when the PPS tree is constructed {@link #buildPpsNode}.
    734      *
    735      * @param node PPSNode representing the root of the
    736      *             PerProviderSubscription/HomeSP/NetworkID/<X+> subtree
    737      * @return Pair<String, Long> representing <SSID, HESSID> pair.
    738      * @throws ParsingException
    739      */
    740     static private Pair<String, Long> parseNetworkIdInstance(PPSNode node)
    741             throws ParsingException {
    742         if (node.isLeaf()) {
    743             throw new ParsingException("Leaf node not expected for NetworkID instance");
    744         }
    745 
    746         String ssid = null;
    747         Long hessid = null;
    748         for (PPSNode child : node.getChildren()) {
    749             switch (child.getName()) {
    750                 case NODE_SSID:
    751                     ssid = getPpsNodeValue(child);
    752                     break;
    753                 case NODE_HESSID:
    754                     hessid = parseLong(getPpsNodeValue(child), 16);
    755                     break;
    756                 default:
    757                     throw new ParsingException("Unknown node under NetworkID instance: " +
    758                             child.getName());
    759             }
    760         }
    761         if (ssid == null)
    762             throw new ParsingException("NetworkID instance missing SSID");
    763 
    764         return new Pair<String, Long>(ssid, hessid);
    765     }
    766 
    767     /**
    768      * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree.
    769      *
    770      * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList
    771      *             subtree
    772      * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list.
    773      * @throws ParsingException
    774      */
    775     private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node)
    776             throws ParsingException {
    777         if (node.isLeaf()) {
    778             throw new ParsingException("Leaf node not expected for HomeOIList");
    779         }
    780 
    781         List<Long> matchAllOIs = new ArrayList<Long>();
    782         List<Long> matchAnyOIs = new ArrayList<Long>();
    783         for (PPSNode child : node.getChildren()) {
    784             Pair<Long, Boolean> homeOI = parseHomeOIInstance(child);
    785             if (homeOI.second.booleanValue()) {
    786                 matchAllOIs.add(homeOI.first);
    787             } else {
    788                 matchAnyOIs.add(homeOI.first);
    789             }
    790         }
    791         return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs);
    792     }
    793 
    794     /**
    795      * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree.
    796      * The instance name (<X+>) is irrelevant and must be unique for each instance, which
    797      * is verified when the PPS tree is constructed {@link #buildPpsNode}.
    798      *
    799      * @param node PPSNode representing the root of the
    800      *             PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree
    801      * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag
    802      * @throws ParsingException
    803      */
    804     private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException {
    805         if (node.isLeaf()) {
    806             throw new ParsingException("Leaf node not expected for HomeOI instance");
    807         }
    808 
    809         Long oi = null;
    810         Boolean required = null;
    811         for (PPSNode child : node.getChildren()) {
    812             switch (child.getName()) {
    813                 case NODE_HOME_OI:
    814                     try {
    815                         oi = Long.valueOf(getPpsNodeValue(child), 16);
    816                     } catch (NumberFormatException e) {
    817                         throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child));
    818                     }
    819                     break;
    820                 case NODE_HOME_OI_REQUIRED:
    821                     required = Boolean.valueOf(getPpsNodeValue(child));
    822                     break;
    823                 default:
    824                     throw new ParsingException("Unknown node under NetworkID instance: " +
    825                             child.getName());
    826             }
    827         }
    828         if (oi == null) {
    829             throw new ParsingException("HomeOI instance missing OI field");
    830         }
    831         if (required == null) {
    832             throw new ParsingException("HomeOI instance missing required field");
    833         }
    834         return new Pair<Long, Boolean>(oi, required);
    835     }
    836 
    837     /**
    838      * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree.
    839      * This contains a list of FQDN (Fully Qualified Domain Name) that are considered
    840      * home partners.
    841      *
    842      * @param node PPSNode representing the root of the
    843      *             PerProviderSubscription/HomeSP/OtherHomePartners subtree
    844      * @return String[] list of partner's FQDN
    845      * @throws ParsingException
    846      */
    847     private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException {
    848         if (node.isLeaf()) {
    849             throw new ParsingException("Leaf node not expected for OtherHomePartners");
    850         }
    851         List<String> otherHomePartners = new ArrayList<String>();
    852         for (PPSNode child : node.getChildren()) {
    853             String fqdn = parseOtherHomePartnerInstance(child);
    854             otherHomePartners.add(fqdn);
    855         }
    856         return otherHomePartners.toArray(new String[otherHomePartners.size()]);
    857     }
    858 
    859     /**
    860      * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree.
    861      * The instance name (<X+>) is irrelevant and must be unique for each instance, which
    862      * is verified when the PPS tree is constructed {@link #buildPpsNode}.
    863      *
    864      * @param node PPSNode representing the root of the
    865      *             PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree
    866      * @return String FQDN of the partner
    867      * @throws ParsingException
    868      */
    869     private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException {
    870         if (node.isLeaf()) {
    871             throw new ParsingException("Leaf node not expected for OtherHomePartner instance");
    872         }
    873         String fqdn = null;
    874         for (PPSNode child : node.getChildren()) {
    875             switch (child.getName()) {
    876                 case NODE_FQDN:
    877                     fqdn = getPpsNodeValue(child);
    878                     break;
    879                 default:
    880                     throw new ParsingException(
    881                             "Unknown node under OtherHomePartner instance: " + child.getName());
    882             }
    883         }
    884         if (fqdn == null) {
    885             throw new ParsingException("OtherHomePartner instance missing FQDN field");
    886         }
    887         return fqdn;
    888     }
    889 
    890     /**
    891      * Parse configurations under PerProviderSubscription/Credential subtree.
    892      *
    893      * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
    894      * @return Credential
    895      * @throws ParsingException
    896      */
    897     private static Credential parseCredential(PPSNode node) throws ParsingException {
    898         if (node.isLeaf()) {
    899             throw new ParsingException("Leaf node not expected for HomeSP");
    900         }
    901 
    902         Credential credential = new Credential();
    903         for (PPSNode child: node.getChildren()) {
    904             switch (child.getName()) {
    905                 case NODE_CREATION_DATE:
    906                     credential.setCreationTimeInMillis(parseDate(getPpsNodeValue(child)));
    907                     break;
    908                 case NODE_EXPIRATION_DATE:
    909                     credential.setExpirationTimeInMillis(parseDate(getPpsNodeValue(child)));
    910                     break;
    911                 case NODE_USERNAME_PASSWORD:
    912                     credential.setUserCredential(parseUserCredential(child));
    913                     break;
    914                 case NODE_DIGITAL_CERTIFICATE:
    915                     credential.setCertCredential(parseCertificateCredential(child));
    916                     break;
    917                 case NODE_REALM:
    918                     credential.setRealm(getPpsNodeValue(child));
    919                     break;
    920                 case NODE_CHECK_AAA_SERVER_CERT_STATUS:
    921                     credential.setCheckAaaServerCertStatus(
    922                             Boolean.parseBoolean(getPpsNodeValue(child)));
    923                     break;
    924                 case NODE_SIM:
    925                     credential.setSimCredential(parseSimCredential(child));
    926                     break;
    927                 default:
    928                     throw new ParsingException("Unknown node under Credential: " +
    929                             child.getName());
    930             }
    931         }
    932         return credential;
    933     }
    934 
    935     /**
    936      * Parse configurations under PerProviderSubscription/Credential/UsernamePassword subtree.
    937      *
    938      * @param node PPSNode representing the root of the
    939      *             PerProviderSubscription/Credential/UsernamePassword subtree
    940      * @return Credential.UserCredential
    941      * @throws ParsingException
    942      */
    943     private static Credential.UserCredential parseUserCredential(PPSNode node)
    944             throws ParsingException {
    945         if (node.isLeaf()) {
    946             throw new ParsingException("Leaf node not expected for UsernamePassword");
    947         }
    948 
    949         Credential.UserCredential userCred = new Credential.UserCredential();
    950         for (PPSNode child : node.getChildren()) {
    951             switch (child.getName()) {
    952                 case NODE_USERNAME:
    953                     userCred.setUsername(getPpsNodeValue(child));
    954                     break;
    955                 case NODE_PASSWORD:
    956                     userCred.setPassword(getPpsNodeValue(child));
    957                     break;
    958                 case NODE_MACHINE_MANAGED:
    959                     userCred.setMachineManaged(Boolean.parseBoolean(getPpsNodeValue(child)));
    960                     break;
    961                 case NODE_SOFT_TOKEN_APP:
    962                     userCred.setSoftTokenApp(getPpsNodeValue(child));
    963                     break;
    964                 case NODE_ABLE_TO_SHARE:
    965                     userCred.setAbleToShare(Boolean.parseBoolean(getPpsNodeValue(child)));
    966                     break;
    967                 case NODE_EAP_METHOD:
    968                     parseEAPMethod(child, userCred);
    969                     break;
    970                 default:
    971                     throw new ParsingException("Unknown node under UsernamPassword: " +
    972                             child.getName());
    973             }
    974         }
    975         return userCred;
    976     }
    977 
    978     /**
    979      * Parse configurations under PerProviderSubscription/Credential/UsernamePassword/EAPMethod
    980      * subtree.
    981      *
    982      * @param node PPSNode representing the root of the
    983      *             PerProviderSubscription/Credential/UsernamePassword/EAPMethod subtree
    984      * @param userCred UserCredential to be updated with EAP method values.
    985      * @throws ParsingException
    986      */
    987     private static void parseEAPMethod(PPSNode node, Credential.UserCredential userCred)
    988             throws ParsingException {
    989         if (node.isLeaf()) {
    990             throw new ParsingException("Leaf node not expected for EAPMethod");
    991         }
    992 
    993         for (PPSNode child : node.getChildren()) {
    994             switch(child.getName()) {
    995                 case NODE_EAP_TYPE:
    996                     userCred.setEapType(parseInteger(getPpsNodeValue(child)));
    997                     break;
    998                 case NODE_INNER_METHOD:
    999                     userCred.setNonEapInnerMethod(getPpsNodeValue(child));
   1000                     break;
   1001                 case NODE_VENDOR_ID:
   1002                 case NODE_VENDOR_TYPE:
   1003                 case NODE_INNER_EAP_TYPE:
   1004                 case NODE_INNER_VENDOR_ID:
   1005                 case NODE_INNER_VENDOR_TYPE:
   1006                     // Only EAP-TTLS is currently supported for user credential, which doesn't
   1007                     // use any of these parameters.
   1008                     Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
   1009                     break;
   1010                 default:
   1011                     throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
   1012             }
   1013         }
   1014     }
   1015 
   1016     /**
   1017      * Parse configurations under PerProviderSubscription/Credential/DigitalCertificate subtree.
   1018      *
   1019      * @param node PPSNode representing the root of the
   1020      *             PerProviderSubscription/Credential/DigitalCertificate subtree
   1021      * @return Credential.CertificateCredential
   1022      * @throws ParsingException
   1023      */
   1024     private static Credential.CertificateCredential parseCertificateCredential(PPSNode node)
   1025             throws ParsingException {
   1026         if (node.isLeaf()) {
   1027             throw new ParsingException("Leaf node not expected for DigitalCertificate");
   1028         }
   1029 
   1030         Credential.CertificateCredential certCred = new Credential.CertificateCredential();
   1031         for (PPSNode child : node.getChildren()) {
   1032             switch (child.getName()) {
   1033                 case NODE_CERTIFICATE_TYPE:
   1034                     certCred.setCertType(getPpsNodeValue(child));
   1035                     break;
   1036                 case NODE_CERT_SHA256_FINGERPRINT:
   1037                     certCred.setCertSha256Fingerprint(parseHexString(getPpsNodeValue(child)));
   1038                     break;
   1039                 default:
   1040                     throw new ParsingException("Unknown node under DigitalCertificate: " +
   1041                             child.getName());
   1042             }
   1043         }
   1044         return certCred;
   1045     }
   1046 
   1047     /**
   1048      * Parse configurations under PerProviderSubscription/Credential/SIM subtree.
   1049      *
   1050      * @param node PPSNode representing the root of the PerProviderSubscription/Credential/SIM
   1051      *             subtree
   1052      * @return Credential.SimCredential
   1053      * @throws ParsingException
   1054      */
   1055     private static Credential.SimCredential parseSimCredential(PPSNode node)
   1056             throws ParsingException {
   1057         if (node.isLeaf()) {
   1058             throw new ParsingException("Leaf node not expected for SIM");
   1059         }
   1060 
   1061         Credential.SimCredential simCred = new Credential.SimCredential();
   1062         for (PPSNode child : node.getChildren()) {
   1063             switch (child.getName()) {
   1064                 case NODE_SIM_IMSI:
   1065                     simCred.setImsi(getPpsNodeValue(child));
   1066                     break;
   1067                 case NODE_EAP_TYPE:
   1068                     simCred.setEapType(parseInteger(getPpsNodeValue(child)));
   1069                     break;
   1070                 default:
   1071                     throw new ParsingException("Unknown node under SIM: " + child.getName());
   1072             }
   1073         }
   1074         return simCred;
   1075     }
   1076 
   1077     /**
   1078      * Parse configurations under PerProviderSubscription/Policy subtree.
   1079      *
   1080      * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
   1081      * @return {@link Policy}
   1082      * @throws ParsingException
   1083      */
   1084     private static Policy parsePolicy(PPSNode node) throws ParsingException {
   1085         if (node.isLeaf()) {
   1086             throw new ParsingException("Leaf node not expected for Policy");
   1087         }
   1088 
   1089         Policy policy = new Policy();
   1090         for (PPSNode child : node.getChildren()) {
   1091             switch (child.getName()) {
   1092                 case NODE_PREFERRED_ROAMING_PARTNER_LIST:
   1093                     policy.setPreferredRoamingPartnerList(parsePreferredRoamingPartnerList(child));
   1094                     break;
   1095                 case NODE_MIN_BACKHAUL_THRESHOLD:
   1096                     parseMinBackhaulThreshold(child, policy);
   1097                     break;
   1098                 case NODE_POLICY_UPDATE:
   1099                     policy.setPolicyUpdate(parseUpdateParameter(child));
   1100                     break;
   1101                 case NODE_SP_EXCLUSION_LIST:
   1102                     policy.setExcludedSsidList(parseSpExclusionList(child));
   1103                     break;
   1104                 case NODE_REQUIRED_PROTO_PORT_TUPLE:
   1105                     policy.setRequiredProtoPortMap(parseRequiredProtoPortTuple(child));
   1106                     break;
   1107                 case NODE_MAXIMUM_BSS_LOAD_VALUE:
   1108                     policy.setMaximumBssLoadValue(parseInteger(getPpsNodeValue(child)));
   1109                     break;
   1110                 default:
   1111                     throw new ParsingException("Unknown node under Policy: " + child.getName());
   1112             }
   1113         }
   1114         return policy;
   1115     }
   1116 
   1117     /**
   1118      * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
   1119      * subtree.
   1120      *
   1121      * @param node PPSNode representing the root of the
   1122      *             PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
   1123      * @return List of {@link Policy#RoamingPartner}
   1124      * @throws ParsingException
   1125      */
   1126     private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
   1127             throws ParsingException {
   1128         if (node.isLeaf()) {
   1129             throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
   1130         }
   1131         List<Policy.RoamingPartner> partnerList = new ArrayList<>();
   1132         for (PPSNode child : node.getChildren()) {
   1133             partnerList.add(parsePreferredRoamingPartner(child));
   1134         }
   1135         return partnerList;
   1136     }
   1137 
   1138     /**
   1139      * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
   1140      * subtree.
   1141      *
   1142      * @param node PPSNode representing the root of the
   1143      *             PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
   1144      * @return {@link Policy#RoamingPartner}
   1145      * @throws ParsingException
   1146      */
   1147     private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
   1148             throws ParsingException {
   1149         if (node.isLeaf()) {
   1150             throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
   1151                     + "instance");
   1152         }
   1153 
   1154         Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
   1155         for (PPSNode child : node.getChildren()) {
   1156             switch (child.getName()) {
   1157                 case NODE_FQDN_MATCH:
   1158                     // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
   1159                     // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
   1160                     // matching all FQDNs with the same sub-domain.
   1161                     String fqdnMatch = getPpsNodeValue(child);
   1162                     String[] fqdnMatchArray = fqdnMatch.split(",");
   1163                     if (fqdnMatchArray.length != 2) {
   1164                         throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
   1165                     }
   1166                     roamingPartner.setFqdn(fqdnMatchArray[0]);
   1167                     if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
   1168                         roamingPartner.setFqdnExactMatch(true);
   1169                     } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
   1170                         roamingPartner.setFqdnExactMatch(false);
   1171                     } else {
   1172                         throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
   1173                     }
   1174                     break;
   1175                 case NODE_PRIORITY:
   1176                     roamingPartner.setPriority(parseInteger(getPpsNodeValue(child)));
   1177                     break;
   1178                 case NODE_COUNTRY:
   1179                     roamingPartner.setCountries(getPpsNodeValue(child));
   1180                     break;
   1181                 default:
   1182                     throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
   1183                             + "instance " + child.getName());
   1184             }
   1185         }
   1186         return roamingPartner;
   1187     }
   1188 
   1189     /**
   1190      * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
   1191      * into the given policy.
   1192      *
   1193      * @param node PPSNode representing the root of the
   1194      *             PerProviderSubscription/Policy/MinBackhaulThreshold subtree
   1195      * @param policy The policy to store the MinBackhualThreshold configuration
   1196      * @throws ParsingException
   1197      */
   1198     private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
   1199             throws ParsingException {
   1200         if (node.isLeaf()) {
   1201             throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
   1202         }
   1203         for (PPSNode child : node.getChildren()) {
   1204             parseMinBackhaulThresholdInstance(child, policy);
   1205         }
   1206     }
   1207 
   1208     /**
   1209      * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
   1210      * into the given policy.
   1211      *
   1212      * @param node PPSNode representing the root of the
   1213      *             PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
   1214      * @param policy The policy to store the MinBackhaulThreshold configuration
   1215      * @throws ParsingException
   1216      */
   1217     private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
   1218             throws ParsingException {
   1219         if (node.isLeaf()) {
   1220             throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
   1221         }
   1222         String networkType = null;
   1223         long downlinkBandwidth = Long.MIN_VALUE;
   1224         long uplinkBandwidth = Long.MIN_VALUE;
   1225         for (PPSNode child : node.getChildren()) {
   1226             switch (child.getName()) {
   1227                 case NODE_NETWORK_TYPE:
   1228                     networkType = getPpsNodeValue(child);
   1229                     break;
   1230                 case NODE_DOWNLINK_BANDWIDTH:
   1231                     downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
   1232                     break;
   1233                 case NODE_UPLINK_BANDWIDTH:
   1234                     uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
   1235                     break;
   1236                 default:
   1237                     throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
   1238                             + child.getName());
   1239             }
   1240         }
   1241         if (networkType == null) {
   1242             throw new ParsingException("Missing NetworkType field");
   1243         }
   1244 
   1245         if (TextUtils.equals(networkType, "home")) {
   1246             policy.setMinHomeDownlinkBandwidth(downlinkBandwidth);
   1247             policy.setMinHomeUplinkBandwidth(uplinkBandwidth);
   1248         } else if (TextUtils.equals(networkType, "roaming")) {
   1249             policy.setMinRoamingDownlinkBandwidth(downlinkBandwidth);
   1250             policy.setMinRoamingUplinkBandwidth(uplinkBandwidth);
   1251         } else {
   1252             throw new ParsingException("Invalid network type: " + networkType);
   1253         }
   1254     }
   1255 
   1256     /**
   1257      * Parse update parameters. This contained configurations from either
   1258      * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
   1259      * subtree.
   1260      *
   1261      * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
   1262      *             or PerProviderSubscription/SubscriptionUpdate subtree
   1263      * @return {@link UpdateParameter}
   1264      * @throws ParsingException
   1265      */
   1266     private static UpdateParameter parseUpdateParameter(PPSNode node)
   1267             throws ParsingException {
   1268         if (node.isLeaf()) {
   1269             throw new ParsingException("Leaf node not expected for Update Parameters");
   1270         }
   1271 
   1272         UpdateParameter updateParam = new UpdateParameter();
   1273         for (PPSNode child : node.getChildren()) {
   1274             switch(child.getName()) {
   1275                 case NODE_UPDATE_INTERVAL:
   1276                     updateParam.setUpdateIntervalInMinutes(parseLong(getPpsNodeValue(child), 10));
   1277                     break;
   1278                 case NODE_UPDATE_METHOD:
   1279                     updateParam.setUpdateMethod(getPpsNodeValue(child));
   1280                     break;
   1281                 case NODE_RESTRICTION:
   1282                     updateParam.setRestriction(getPpsNodeValue(child));
   1283                     break;
   1284                 case NODE_URI:
   1285                     updateParam.setServerUri(getPpsNodeValue(child));
   1286                     break;
   1287                 case NODE_USERNAME_PASSWORD:
   1288                     Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
   1289                     updateParam.setUsername(usernamePassword.first);
   1290                     updateParam.setBase64EncodedPassword(usernamePassword.second);
   1291                     break;
   1292                 case NODE_TRUST_ROOT:
   1293                     Pair<String, byte[]> trustRoot = parseTrustRoot(child);
   1294                     updateParam.setTrustRootCertUrl(trustRoot.first);
   1295                     updateParam.setTrustRootCertSha256Fingerprint(trustRoot.second);
   1296                     break;
   1297                 case NODE_OTHER:
   1298                     Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
   1299                     break;
   1300                 default:
   1301                     throw new ParsingException("Unknown node under Update Parameters: "
   1302                             + child.getName());
   1303             }
   1304         }
   1305         return updateParam;
   1306     }
   1307 
   1308     /**
   1309      * Parse username and password parameters associated with policy or subscription update.
   1310      * This contained configurations under either
   1311      * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
   1312      * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
   1313      *
   1314      * @param node PPSNode representing the root of the UsernamePassword subtree
   1315      * @return Pair of username and password
   1316      * @throws ParsingException
   1317      */
   1318     private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
   1319             throws ParsingException {
   1320         if (node.isLeaf()) {
   1321             throw new ParsingException("Leaf node not expected for UsernamePassword");
   1322         }
   1323 
   1324         String username = null;
   1325         String password = null;
   1326         for (PPSNode child : node.getChildren()) {
   1327             switch (child.getName()) {
   1328                 case NODE_USERNAME:
   1329                     username = getPpsNodeValue(child);
   1330                     break;
   1331                 case NODE_PASSWORD:
   1332                     password = getPpsNodeValue(child);
   1333                     break;
   1334                 default:
   1335                     throw new ParsingException("Unknown node under UsernamePassword: "
   1336                             + child.getName());
   1337             }
   1338         }
   1339         return Pair.create(username, password);
   1340     }
   1341 
   1342     /**
   1343      * Parse the trust root parameters associated with policy update, subscription update, or AAA
   1344      * server trust root.
   1345      *
   1346      * This contained configurations under either
   1347      * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
   1348      * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
   1349      * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
   1350      *
   1351      * @param node PPSNode representing the root of the TrustRoot subtree
   1352      * @return Pair of Certificate URL and fingerprint
   1353      * @throws ParsingException
   1354      */
   1355     private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
   1356             throws ParsingException {
   1357         if (node.isLeaf()) {
   1358             throw new ParsingException("Leaf node not expected for TrustRoot");
   1359         }
   1360 
   1361         String certUrl = null;
   1362         byte[] certFingerprint = null;
   1363         for (PPSNode child : node.getChildren()) {
   1364             switch (child.getName()) {
   1365                 case NODE_CERT_URL:
   1366                     certUrl = getPpsNodeValue(child);
   1367                     break;
   1368                 case NODE_CERT_SHA256_FINGERPRINT:
   1369                     certFingerprint = parseHexString(getPpsNodeValue(child));
   1370                     break;
   1371                 default:
   1372                     throw new ParsingException("Unknown node under TrustRoot: "
   1373                             + child.getName());
   1374             }
   1375         }
   1376         return Pair.create(certUrl, certFingerprint);
   1377     }
   1378 
   1379     /**
   1380      * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
   1381      *
   1382      * @param node PPSNode representing the root of the
   1383      *             PerProviderSubscription/Policy/SPExclusionList subtree
   1384      * @return Array of excluded SSIDs
   1385      * @throws ParsingException
   1386      */
   1387     private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
   1388         if (node.isLeaf()) {
   1389             throw new ParsingException("Leaf node not expected for SPExclusionList");
   1390         }
   1391         List<String> ssidList = new ArrayList<>();
   1392         for (PPSNode child : node.getChildren()) {
   1393             ssidList.add(parseSpExclusionInstance(child));
   1394         }
   1395         return ssidList.toArray(new String[ssidList.size()]);
   1396     }
   1397 
   1398     /**
   1399      * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
   1400      *
   1401      * @param node PPSNode representing the root of the
   1402      *             PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
   1403      * @return String
   1404      * @throws ParsingException
   1405      */
   1406     private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
   1407         if (node.isLeaf()) {
   1408             throw new ParsingException("Leaf node not expected for SPExclusion instance");
   1409         }
   1410         String ssid = null;
   1411         for (PPSNode child : node.getChildren()) {
   1412             switch (child.getName()) {
   1413                 case NODE_SSID:
   1414                     ssid = getPpsNodeValue(child);
   1415                     break;
   1416                 default:
   1417                     throw new ParsingException("Unknown node under SPExclusion instance");
   1418             }
   1419         }
   1420         return ssid;
   1421     }
   1422 
   1423     /**
   1424      * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
   1425      *
   1426      * @param node PPSNode representing the root of the
   1427      *             PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
   1428      * @return Map of IP Protocol to Port Number tuples
   1429      * @throws ParsingException
   1430      */
   1431     private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
   1432             throws ParsingException {
   1433         if (node.isLeaf()) {
   1434             throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
   1435         }
   1436         Map<Integer, String> protoPortTupleMap = new HashMap<>();
   1437         for (PPSNode child : node.getChildren()) {
   1438             Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
   1439             protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
   1440         }
   1441         return protoPortTupleMap;
   1442     }
   1443 
   1444     /**
   1445      * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
   1446      * subtree.
   1447      *
   1448      * @param node PPSNode representing the root of the
   1449      *             PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
   1450      * @return Pair of IP Protocol to Port Number tuple
   1451      * @throws ParsingException
   1452      */
   1453     private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
   1454             throws ParsingException {
   1455         if (node.isLeaf()) {
   1456             throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
   1457                     + "instance");
   1458         }
   1459         int proto = Integer.MIN_VALUE;
   1460         String ports = null;
   1461         for (PPSNode child : node.getChildren()) {
   1462             switch (child.getName()) {
   1463                 case NODE_IP_PROTOCOL:
   1464                     proto = parseInteger(getPpsNodeValue(child));
   1465                     break;
   1466                 case NODE_PORT_NUMBER:
   1467                     ports = getPpsNodeValue(child);
   1468                     break;
   1469                 default:
   1470                     throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
   1471                             + child.getName());
   1472             }
   1473         }
   1474         if (proto == Integer.MIN_VALUE) {
   1475             throw new ParsingException("Missing IPProtocol field");
   1476         }
   1477         if (ports == null) {
   1478             throw new ParsingException("Missing PortNumber field");
   1479         }
   1480         return Pair.create(proto, ports);
   1481     }
   1482 
   1483     /**
   1484      * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
   1485      *
   1486      * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
   1487      *             subtree
   1488      * @return Map of certificate URL with the corresponding certificate fingerprint
   1489      * @throws ParsingException
   1490      */
   1491     private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
   1492             throws ParsingException {
   1493         if (node.isLeaf()) {
   1494             throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
   1495         }
   1496         Map<String, byte[]> certList = new HashMap<>();
   1497         for (PPSNode child : node.getChildren()) {
   1498             Pair<String, byte[]> certTuple = parseTrustRoot(child);
   1499             certList.put(certTuple.first, certTuple.second);
   1500         }
   1501         return certList;
   1502     }
   1503 
   1504     /**
   1505      * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
   1506      *
   1507      * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
   1508      *             subtree
   1509      * @param config Instance of {@link PasspointConfiguration}
   1510      * @throws ParsingException
   1511      */
   1512     private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
   1513             throws ParsingException {
   1514         if (node.isLeaf()) {
   1515             throw new ParsingException("Leaf node not expected for SubscriptionParameter");
   1516         }
   1517         for (PPSNode child : node.getChildren()) {
   1518             switch (child.getName()) {
   1519                 case NODE_CREATION_DATE:
   1520                     config.setSubscriptionCreationTimeInMillis(parseDate(getPpsNodeValue(child)));
   1521                     break;
   1522                 case NODE_EXPIRATION_DATE:
   1523                     config.setSubscriptionExpirationTimeInMillis(parseDate(getPpsNodeValue(child)));
   1524                     break;
   1525                 case NODE_TYPE_OF_SUBSCRIPTION:
   1526                     config.setSubscriptionType(getPpsNodeValue(child));
   1527                     break;
   1528                 case NODE_USAGE_LIMITS:
   1529                     parseUsageLimits(child, config);
   1530                     break;
   1531                 default:
   1532                     throw new ParsingException("Unknown node under SubscriptionParameter"
   1533                             + child.getName());
   1534             }
   1535         }
   1536     }
   1537 
   1538     /**
   1539      * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
   1540      * subtree.
   1541      *
   1542      * @param node PPSNode representing the root of
   1543      *             PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
   1544      * @param config Instance of {@link PasspointConfiguration}
   1545      * @throws ParsingException
   1546      */
   1547     private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
   1548             throws ParsingException {
   1549         if (node.isLeaf()) {
   1550             throw new ParsingException("Leaf node not expected for UsageLimits");
   1551         }
   1552         for (PPSNode child : node.getChildren()) {
   1553             switch (child.getName()) {
   1554                 case NODE_DATA_LIMIT:
   1555                     config.setUsageLimitDataLimit(parseLong(getPpsNodeValue(child), 10));
   1556                     break;
   1557                 case NODE_START_DATE:
   1558                     config.setUsageLimitStartTimeInMillis(parseDate(getPpsNodeValue(child)));
   1559                     break;
   1560                 case NODE_TIME_LIMIT:
   1561                     config.setUsageLimitTimeLimitInMinutes(parseLong(getPpsNodeValue(child), 10));
   1562                     break;
   1563                 case NODE_USAGE_TIME_PERIOD:
   1564                     config.setUsageLimitUsageTimePeriodInMinutes(
   1565                             parseLong(getPpsNodeValue(child), 10));
   1566                     break;
   1567                 default:
   1568                     throw new ParsingException("Unknown node under UsageLimits"
   1569                             + child.getName());
   1570             }
   1571         }
   1572     }
   1573 
   1574     /**
   1575      * Convert a hex string to a byte array.
   1576      *
   1577      * @param str String containing hex values
   1578      * @return byte[]
   1579      * @throws ParsingException
   1580      */
   1581     private static byte[] parseHexString(String str) throws ParsingException {
   1582         if ((str.length() & 1) == 1) {
   1583             throw new ParsingException("Odd length hex string: " + str.length());
   1584         }
   1585 
   1586         byte[] result = new byte[str.length() / 2];
   1587         for (int i = 0; i < result.length; i++) {
   1588           int index = i * 2;
   1589           try {
   1590               result[i] = (byte) Integer.parseInt(str.substring(index, index + 2), 16);
   1591           } catch (NumberFormatException e) {
   1592               throw new ParsingException("Invalid hex string: " + str);
   1593           }
   1594         }
   1595         return result;
   1596     }
   1597 
   1598     /**
   1599      * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
   1600      *
   1601      * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
   1602      * @return number of milliseconds
   1603      * @throws ParsingException
   1604      */
   1605     private static long parseDate(String dateStr) throws ParsingException {
   1606         try {
   1607             DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
   1608             return format.parse(dateStr).getTime();
   1609         } catch (ParseException pe) {
   1610             throw new ParsingException("Badly formatted time: " + dateStr);
   1611         }
   1612     }
   1613 
   1614     /**
   1615      * Parse an integer string.
   1616      *
   1617      * @param value String of integer value
   1618      * @return int
   1619      * @throws ParsingException
   1620      */
   1621     private static int parseInteger(String value) throws ParsingException {
   1622         try {
   1623             return Integer.parseInt(value);
   1624         } catch (NumberFormatException e) {
   1625             throw new ParsingException("Invalid integer value: " + value);
   1626         }
   1627     }
   1628 
   1629     /**
   1630      * Parse a string representing a long integer.
   1631      *
   1632      * @param value String of long integer value
   1633      * @return long
   1634      * @throws ParsingException
   1635      */
   1636     private static long parseLong(String value, int radix) throws ParsingException {
   1637         try {
   1638             return Long.parseLong(value, radix);
   1639         } catch (NumberFormatException e) {
   1640             throw new ParsingException("Invalid long integer value: " + value);
   1641         }
   1642     }
   1643 
   1644     /**
   1645      * Convert a List<Long> to a primitive long array long[].
   1646      *
   1647      * @param list List to be converted
   1648      * @return long[]
   1649      */
   1650     private static long[] convertFromLongList(List<Long> list) {
   1651         Long[] objectArray = list.toArray(new Long[list.size()]);
   1652         long[] primitiveArray = new long[objectArray.length];
   1653         for (int i = 0; i < objectArray.length; i++) {
   1654             primitiveArray[i] = objectArray[i].longValue();
   1655         }
   1656         return primitiveArray;
   1657     }
   1658 }
   1659