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