• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:hotspot2dot0­perprovidersubscription: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 {
ParsingException(String message)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;
PPSNode(String name)245         public PPSNode(String name) {
246             mName = name;
247         }
248 
249         /**
250          * @return the name of the node
251          */
getName()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          */
getChildren()261         public abstract List<PPSNode> getChildren();
262 
263         /**
264          * Applies for leaf node only.
265          *
266          * @return the string value of the node
267          */
getValue()268         public abstract String getValue();
269 
270         /**
271          * @return a flag indicating if this is a leaf or an internal node
272          */
isLeaf()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;
LeafNode(String nodeName, String value)281         public LeafNode(String nodeName, String value) {
282             super(nodeName);
283             mValue = value;
284         }
285 
286         @Override
getValue()287         public String getValue() {
288             return mValue;
289         }
290 
291         @Override
getChildren()292         public List<PPSNode> getChildren() {
293             return null;
294         }
295 
296         @Override
isLeaf()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;
InternalNode(String nodeName, List<PPSNode> children)307         public InternalNode(String nodeName, List<PPSNode> children) {
308             super(nodeName);
309             mChildren = children;
310         }
311 
312         @Override
getValue()313         public String getValue() {
314             return null;
315         }
316 
317         @Override
getChildren()318         public List<PPSNode> getChildren() {
319             return mChildren;
320         }
321 
322         @Override
isLeaf()323         public boolean isLeaf() {
324             return false;
325         }
326     }
327 
328     /**
329      * @hide
330      */
PpsMoParser()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      */
parseMoText(String xmlString)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      */
parsePpsNode(XMLNode node)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      */
parseUrn(XMLNode node)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      */
buildPpsNode(XMLNode node)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      */
getPpsNodeValue(PPSNode node)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      */
parsePpsInstance(PPSNode root)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      */
parseHomeSP(PPSNode node)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      */
parseRoamingConsortiumOI(String oiStr)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      */
parseNetworkIds(PPSNode node)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      */
parseNetworkIdInstance(PPSNode node)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      */
parseHomeOIList(PPSNode node)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      */
parseHomeOIInstance(PPSNode node)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      */
parseOtherHomePartners(PPSNode node)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      */
parseOtherHomePartnerInstance(PPSNode node)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      */
parseCredential(PPSNode node)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      */
parseUserCredential(PPSNode node)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      */
parseEAPMethod(PPSNode node, Credential.UserCredential userCred)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      */
parseCertificateCredential(PPSNode node)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      */
parseSimCredential(PPSNode node)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      */
parsePolicy(PPSNode node)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      */
parsePreferredRoamingPartnerList(PPSNode node)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      */
parsePreferredRoamingPartner(PPSNode node)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      */
parseMinBackhaulThreshold(PPSNode node, Policy policy)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      */
parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)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      */
parseUpdateParameter(PPSNode node)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      */
parseUpdateUserCredential(PPSNode node)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      */
parseTrustRoot(PPSNode node)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      */
parseSpExclusionList(PPSNode node)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      */
parseSpExclusionInstance(PPSNode node)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      */
parseRequiredProtoPortTuple(PPSNode node)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      */
parseProtoPortTuple(PPSNode node)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      */
parseAAAServerTrustRootList(PPSNode node)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      */
parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)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      */
parseUsageLimits(PPSNode node, PasspointConfiguration config)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      */
parseHexString(String str)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      */
parseDate(String dateStr)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      */
parseInteger(String value)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      */
parseLong(String value, int radix)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      */
convertFromLongList(List<Long> list)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