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