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