Home | History | Annotate | Download | only in omadm
      1 package com.android.hotspot2.omadm;
      2 
      3 import android.util.Base64;
      4 import android.util.Log;
      5 
      6 import com.android.anqp.eap.EAP;
      7 import com.android.anqp.eap.EAPMethod;
      8 import com.android.anqp.eap.ExpandedEAPMethod;
      9 import com.android.anqp.eap.InnerAuthEAP;
     10 import com.android.anqp.eap.NonEAPInnerAuth;
     11 import com.android.hotspot2.IMSIParameter;
     12 import com.android.hotspot2.Utils;
     13 import com.android.hotspot2.osu.OSUManager;
     14 import com.android.hotspot2.osu.commands.MOData;
     15 import com.android.hotspot2.pps.Credential;
     16 import com.android.hotspot2.pps.HomeSP;
     17 import com.android.hotspot2.pps.Policy;
     18 import com.android.hotspot2.pps.SubscriptionParameters;
     19 import com.android.hotspot2.pps.UpdateInfo;
     20 
     21 import org.xml.sax.SAXException;
     22 
     23 import java.io.BufferedInputStream;
     24 import java.io.BufferedOutputStream;
     25 import java.io.File;
     26 import java.io.FileInputStream;
     27 import java.io.FileNotFoundException;
     28 import java.io.FileOutputStream;
     29 import java.io.IOException;
     30 import java.nio.charset.StandardCharsets;
     31 import java.text.DateFormat;
     32 import java.text.ParseException;
     33 import java.text.SimpleDateFormat;
     34 import java.util.ArrayList;
     35 import java.util.Arrays;
     36 import java.util.Collection;
     37 import java.util.Collections;
     38 import java.util.Date;
     39 import java.util.HashMap;
     40 import java.util.HashSet;
     41 import java.util.LinkedList;
     42 import java.util.List;
     43 import java.util.Map;
     44 import java.util.Set;
     45 import java.util.TimeZone;
     46 
     47 /**
     48  * Handles provisioning of PerProviderSubscription data.
     49  */
     50 public class MOManager {
     51 
     52     public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
     53     public static final String TAG_AbleToShare = "AbleToShare";
     54     public static final String TAG_CertificateType = "CertificateType";
     55     public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
     56     public static final String TAG_CertURL = "CertURL";
     57     public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
     58     public static final String TAG_Country = "Country";
     59     public static final String TAG_CreationDate = "CreationDate";
     60     public static final String TAG_Credential = "Credential";
     61     public static final String TAG_CredentialPriority = "CredentialPriority";
     62     public static final String TAG_DataLimit = "DataLimit";
     63     public static final String TAG_DigitalCertificate = "DigitalCertificate";
     64     public static final String TAG_DLBandwidth = "DLBandwidth";
     65     public static final String TAG_EAPMethod = "EAPMethod";
     66     public static final String TAG_EAPType = "EAPType";
     67     public static final String TAG_ExpirationDate = "ExpirationDate";
     68     public static final String TAG_Extension = "Extension";
     69     public static final String TAG_FQDN = "FQDN";
     70     public static final String TAG_FQDN_Match = "FQDN_Match";
     71     public static final String TAG_FriendlyName = "FriendlyName";
     72     public static final String TAG_HESSID = "HESSID";
     73     public static final String TAG_HomeOI = "HomeOI";
     74     public static final String TAG_HomeOIList = "HomeOIList";
     75     public static final String TAG_HomeOIRequired = "HomeOIRequired";
     76     public static final String TAG_HomeSP = "HomeSP";
     77     public static final String TAG_IconURL = "IconURL";
     78     public static final String TAG_IMSI = "IMSI";
     79     public static final String TAG_InnerEAPType = "InnerEAPType";
     80     public static final String TAG_InnerMethod = "InnerMethod";
     81     public static final String TAG_InnerVendorID = "InnerVendorID";
     82     public static final String TAG_InnerVendorType = "InnerVendorType";
     83     public static final String TAG_IPProtocol = "IPProtocol";
     84     public static final String TAG_MachineManaged = "MachineManaged";
     85     public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
     86     public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
     87     public static final String TAG_NetworkID = "NetworkID";
     88     public static final String TAG_NetworkType = "NetworkType";
     89     public static final String TAG_Other = "Other";
     90     public static final String TAG_OtherHomePartners = "OtherHomePartners";
     91     public static final String TAG_Password = "Password";
     92     public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
     93     public static final String TAG_Policy = "Policy";
     94     public static final String TAG_PolicyUpdate = "PolicyUpdate";
     95     public static final String TAG_PortNumber = "PortNumber";
     96     public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
     97     public static final String TAG_Priority = "Priority";
     98     public static final String TAG_Realm = "Realm";
     99     public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
    100     public static final String TAG_Restriction = "Restriction";
    101     public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
    102     public static final String TAG_SIM = "SIM";
    103     public static final String TAG_SoftTokenApp = "SoftTokenApp";
    104     public static final String TAG_SPExclusionList = "SPExclusionList";
    105     public static final String TAG_SSID = "SSID";
    106     public static final String TAG_StartDate = "StartDate";
    107     public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
    108     public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
    109     public static final String TAG_TimeLimit = "TimeLimit";
    110     public static final String TAG_TrustRoot = "TrustRoot";
    111     public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
    112     public static final String TAG_ULBandwidth = "ULBandwidth";
    113     public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
    114     public static final String TAG_UpdateInterval = "UpdateInterval";
    115     public static final String TAG_UpdateMethod = "UpdateMethod";
    116     public static final String TAG_URI = "URI";
    117     public static final String TAG_UsageLimits = "UsageLimits";
    118     public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
    119     public static final String TAG_Username = "Username";
    120     public static final String TAG_UsernamePassword = "UsernamePassword";
    121     public static final String TAG_VendorId = "VendorId";
    122     public static final String TAG_VendorType = "VendorType";
    123 
    124     public static final long IntervalFactor = 60000L;  // All MO intervals are in minutes
    125 
    126     private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    127 
    128     private static final Map<String, Map<String, Object>> sSelectionMap;
    129 
    130     static {
    131         DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    132 
    133         sSelectionMap = new HashMap<>();
    134 
    135         setSelections(TAG_FQDN_Match,
    136                 "exactmatch", Boolean.FALSE,
    137                 "includesubdomains", Boolean.TRUE);
    138         setSelections(TAG_UpdateMethod,
    139                 "oma-dm-clientinitiated", Boolean.FALSE,
    140                 "spp-clientinitiated", Boolean.TRUE);
    141         setSelections(TAG_Restriction,
    142                 "homesp", UpdateInfo.UpdateRestriction.HomeSP,
    143                 "roamingpartner", UpdateInfo.UpdateRestriction.RoamingPartner,
    144                 "unrestricted", UpdateInfo.UpdateRestriction.Unrestricted);
    145     }
    146 
    147     private static void setSelections(String key, Object... pairs) {
    148         Map<String, Object> kvp = new HashMap<>();
    149         sSelectionMap.put(key, kvp);
    150         for (int n = 0; n < pairs.length; n += 2) {
    151             kvp.put(pairs[n].toString(), pairs[n + 1]);
    152         }
    153     }
    154 
    155     private final File mPpsFile;
    156     private final boolean mEnabled;
    157     private final Map<String, HomeSP> mSPs;
    158 
    159     public MOManager(File ppsFile, boolean hs2enabled) {
    160         mPpsFile = ppsFile;
    161         mEnabled = hs2enabled;
    162         mSPs = new HashMap<>();
    163     }
    164 
    165     public File getPpsFile() {
    166         return mPpsFile;
    167     }
    168 
    169     public boolean isEnabled() {
    170         return mEnabled;
    171     }
    172 
    173     public boolean isConfigured() {
    174         return mEnabled && !mSPs.isEmpty();
    175     }
    176 
    177     public Map<String, HomeSP> getLoadedSPs() {
    178         return Collections.unmodifiableMap(mSPs);
    179     }
    180 
    181     public List<HomeSP> loadAllSPs() throws IOException {
    182 
    183         if (!mEnabled || !mPpsFile.exists()) {
    184             return Collections.emptyList();
    185         }
    186 
    187         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
    188             MOTree moTree = MOTree.unmarshal(in);
    189             mSPs.clear();
    190             if (moTree == null) {
    191                 return Collections.emptyList();     // Empty file
    192             }
    193 
    194             List<HomeSP> sps = buildSPs(moTree);
    195             if (sps != null) {
    196                 for (HomeSP sp : sps) {
    197                     if (mSPs.put(sp.getFQDN(), sp) != null) {
    198                         throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
    199                     } else {
    200                         Log.d(OSUManager.TAG, "retrieved " + sp.getFQDN() + " from PPS");
    201                     }
    202                 }
    203                 return sps;
    204 
    205             } else {
    206                 throw new OMAException("Failed to build HomeSP");
    207             }
    208         }
    209     }
    210 
    211     public static HomeSP buildSP(String xml) throws IOException, SAXException {
    212         OMAParser omaParser = new OMAParser();
    213         MOTree tree = omaParser.parse(xml, OMAConstants.PPS_URN);
    214         List<HomeSP> spList = buildSPs(tree);
    215         if (spList.size() != 1) {
    216             throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
    217         }
    218         return spList.iterator().next();
    219     }
    220 
    221     public HomeSP addSP(String xml, OSUManager osuManager) throws IOException, SAXException {
    222         OMAParser omaParser = new OMAParser();
    223         return addSP(omaParser.parse(xml, OMAConstants.PPS_URN));
    224     }
    225 
    226     private static final List<String> FQDNPath = Arrays.asList(TAG_HomeSP, TAG_FQDN);
    227 
    228     /**
    229      * R1 *only* addSP method.
    230      *
    231      * @param homeSP
    232      * @throws IOException
    233      */
    234     public void addSP(HomeSP homeSP) throws IOException {
    235         if (!mEnabled) {
    236             throw new IOException("HS2.0 not enabled on this device");
    237         }
    238         if (mSPs.containsKey(homeSP.getFQDN())) {
    239             Log.d(OSUManager.TAG, "HS20 profile for " +
    240                     homeSP.getFQDN() + " already exists");
    241             return;
    242         }
    243         Log.d(OSUManager.TAG, "Adding new HS20 profile for " + homeSP.getFQDN());
    244 
    245         OMAConstructed dummyRoot = new OMAConstructed(null, TAG_PerProviderSubscription, null);
    246         buildHomeSPTree(homeSP, dummyRoot, mSPs.size() + 1);
    247         try {
    248             addSP(dummyRoot);
    249         } catch (FileNotFoundException fnfe) {
    250             MOTree tree =
    251                     MOTree.buildMgmtTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, dummyRoot);
    252             // No file to load a pre-build MO tree from, create a new one and save it.
    253             //MOTree tree = new MOTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, dummyRoot);
    254             writeMO(tree, mPpsFile);
    255         }
    256         mSPs.put(homeSP.getFQDN(), homeSP);
    257     }
    258 
    259     public HomeSP addSP(MOTree instanceTree) throws IOException {
    260         List<HomeSP> spList = buildSPs(instanceTree);
    261         if (spList.size() != 1) {
    262             throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
    263         }
    264 
    265         HomeSP sp = spList.iterator().next();
    266         String fqdn = sp.getFQDN();
    267         if (mSPs.put(fqdn, sp) != null) {
    268             throw new OMAException("SP " + fqdn + " already exists");
    269         }
    270 
    271         OMAConstructed pps = (OMAConstructed) instanceTree.getRoot().
    272                 getChild(TAG_PerProviderSubscription);
    273 
    274         try {
    275             addSP(pps);
    276         } catch (FileNotFoundException fnfe) {
    277             MOTree tree = new MOTree(instanceTree.getUrn(), instanceTree.getDtdRev(),
    278                     instanceTree.getRoot());
    279             writeMO(tree, mPpsFile);
    280         }
    281 
    282         return sp;
    283     }
    284 
    285     /**
    286      * Add an SP sub-tree. mo must be PPS with an immediate instance child (e.g. Cred01) and an
    287      * optional UpdateIdentifier,
    288      *
    289      * @param mo The new MO
    290      * @throws IOException
    291      */
    292     private void addSP(OMANode mo) throws IOException {
    293         MOTree moTree;
    294         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
    295             moTree = MOTree.unmarshal(in);
    296             moTree.getRoot().addChild(mo);
    297 
    298                 /*
    299             OMAConstructed ppsRoot = (OMAConstructed)
    300                     moTree.getRoot().addChild(TAG_PerProviderSubscription, "", null, null);
    301             for (OMANode child : mo.getChildren()) {
    302                 ppsRoot.addChild(child);
    303                 if (!child.isLeaf()) {
    304                     moTree.getRoot().addChild(child);
    305                 }
    306                 else if (child.getName().equals(TAG_UpdateIdentifier)) {
    307                     OMANode currentUD = moTree.getRoot().getChild(TAG_UpdateIdentifier);
    308                     if (currentUD != null) {
    309                         moTree.getRoot().replaceNode(currentUD, child);
    310                     }
    311                     else {
    312                         moTree.getRoot().addChild(child);
    313                     }
    314                 }
    315             }
    316                 */
    317         }
    318         writeMO(moTree, mPpsFile);
    319     }
    320 
    321     private static OMAConstructed findTargetTree(MOTree moTree, String fqdn) throws OMAException {
    322         OMANode pps = moTree.getRoot();
    323         for (OMANode node : pps.getChildren()) {
    324             OMANode instance = null;
    325             if (node.getName().equals(TAG_PerProviderSubscription)) {
    326                 instance = getInstanceNode((OMAConstructed) node);
    327             } else if (!node.isLeaf()) {
    328                 instance = node;
    329             }
    330             if (instance != null) {
    331                 String nodeFqdn = getString(instance.getListValue(FQDNPath.iterator()));
    332                 if (fqdn.equalsIgnoreCase(nodeFqdn)) {
    333                     return (OMAConstructed) node;
    334                     // targetTree is rooted at the PPS
    335                 }
    336             }
    337         }
    338         return null;
    339     }
    340 
    341     private static OMAConstructed getInstanceNode(OMAConstructed root) throws OMAException {
    342         for (OMANode child : root.getChildren()) {
    343             if (!child.isLeaf()) {
    344                 return (OMAConstructed) child;
    345             }
    346         }
    347         throw new OMAException("Cannot find instance node");
    348     }
    349 
    350     public static HomeSP modifySP(HomeSP homeSP, MOTree moTree, Collection<MOData> mods)
    351             throws OMAException {
    352 
    353         OMAConstructed ppsTree =
    354                 (OMAConstructed) moTree.getRoot().getChildren().iterator().next();
    355         OMAConstructed instance = getInstanceNode(ppsTree);
    356 
    357         int ppsMods = 0;
    358         int updateIdentifier = homeSP.getUpdateIdentifier();
    359         for (MOData mod : mods) {
    360             LinkedList<String> tailPath =
    361                     getTailPath(mod.getBaseURI(), TAG_PerProviderSubscription);
    362             OMAConstructed modRoot = mod.getMOTree().getRoot();
    363             // modRoot is the MgmtTree with the actual object as a direct child
    364             // (e.g. Credential)
    365 
    366             if (tailPath.getFirst().equals(TAG_UpdateIdentifier)) {
    367                 updateIdentifier = getInteger(modRoot.getChildren().iterator().next());
    368                 OMANode oldUdi = ppsTree.getChild(TAG_UpdateIdentifier);
    369                 if (getInteger(oldUdi) != updateIdentifier) {
    370                     ppsMods++;
    371                 }
    372                 if (oldUdi != null) {
    373                     ppsTree.replaceNode(oldUdi, modRoot.getChild(TAG_UpdateIdentifier));
    374                 } else {
    375                     ppsTree.addChild(modRoot.getChild(TAG_UpdateIdentifier));
    376                 }
    377             } else {
    378                 tailPath.removeFirst();     // Drop the instance
    379                 OMANode current = instance.getListValue(tailPath.iterator());
    380                 if (current == null) {
    381                     throw new OMAException("No previous node for " + tailPath + " in "
    382                             + homeSP.getFQDN());
    383                 }
    384                 for (OMANode newNode : modRoot.getChildren()) {
    385                     // newNode is something like Credential
    386                     // current is the same existing node
    387                     OMANode old = current.getParent().replaceNode(current, newNode);
    388                     ppsMods++;
    389                 }
    390             }
    391         }
    392 
    393         return ppsMods > 0 ? buildHomeSP(instance, updateIdentifier) : null;
    394     }
    395 
    396     public HomeSP modifySP(HomeSP homeSP, Collection<MOData> mods)
    397             throws IOException {
    398 
    399         Log.d(OSUManager.TAG, "modifying SP: " + mods);
    400         MOTree moTree;
    401         int ppsMods = 0;
    402         int updateIdentifier = 0;
    403         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
    404             moTree = MOTree.unmarshal(in);
    405             // moTree is PPS/?/provider-data
    406 
    407             OMAConstructed targetTree = findTargetTree(moTree, homeSP.getFQDN());
    408             if (targetTree == null) {
    409                 throw new IOException("Failed to find PPS tree for " + homeSP.getFQDN());
    410             }
    411             OMAConstructed instance = getInstanceNode(targetTree);
    412 
    413             for (MOData mod : mods) {
    414                 LinkedList<String> tailPath =
    415                         getTailPath(mod.getBaseURI(), TAG_PerProviderSubscription);
    416                 OMAConstructed modRoot = mod.getMOTree().getRoot();
    417                 // modRoot is the MgmtTree with the actual object as a direct child
    418                 // (e.g. Credential)
    419 
    420                 if (tailPath.getFirst().equals(TAG_UpdateIdentifier)) {
    421                     updateIdentifier = getInteger(modRoot.getChildren().iterator().next());
    422                     OMANode oldUdi = targetTree.getChild(TAG_UpdateIdentifier);
    423                     if (getInteger(oldUdi) != updateIdentifier) {
    424                         ppsMods++;
    425                     }
    426                     if (oldUdi != null) {
    427                         targetTree.replaceNode(oldUdi, modRoot.getChild(TAG_UpdateIdentifier));
    428                     } else {
    429                         targetTree.addChild(modRoot.getChild(TAG_UpdateIdentifier));
    430                     }
    431                 } else {
    432                     tailPath.removeFirst();     // Drop the instance
    433                     OMANode current = instance.getListValue(tailPath.iterator());
    434                     if (current == null) {
    435                         throw new IOException("No previous node for " + tailPath + " in " +
    436                                 homeSP.getFQDN());
    437                     }
    438                     for (OMANode newNode : modRoot.getChildren()) {
    439                         // newNode is something like Credential
    440                         // current is the same existing node
    441                         OMANode old = current.getParent().replaceNode(current, newNode);
    442                         ppsMods++;
    443                     }
    444                 }
    445             }
    446         }
    447         writeMO(moTree, mPpsFile);
    448 
    449         if (ppsMods == 0) {
    450             return null;    // HomeSP not modified.
    451         }
    452 
    453         // Return a new rebuilt HomeSP
    454         List<HomeSP> sps = buildSPs(moTree);
    455         if (sps != null) {
    456             for (HomeSP sp : sps) {
    457                 if (sp.getFQDN().equals(homeSP.getFQDN())) {
    458                     return sp;
    459                 }
    460             }
    461         } else {
    462             throw new OMAException("Failed to build HomeSP");
    463         }
    464         return null;
    465     }
    466 
    467     private static LinkedList<String> getTailPath(String pathString, String rootName)
    468             throws OMAException {
    469         String[] path = pathString.split("/");
    470         int pathIndex;
    471         for (pathIndex = 0; pathIndex < path.length; pathIndex++) {
    472             if (path[pathIndex].equalsIgnoreCase(rootName)) {
    473                 pathIndex++;
    474                 break;
    475             }
    476         }
    477         if (pathIndex >= path.length) {
    478             throw new OMAException("Bad node-path: " + pathString);
    479         }
    480         LinkedList<String> tailPath = new LinkedList<>();
    481         while (pathIndex < path.length) {
    482             tailPath.add(path[pathIndex]);
    483             pathIndex++;
    484         }
    485         return tailPath;
    486     }
    487 
    488     public HomeSP getHomeSP(String fqdn) {
    489         return mSPs.get(fqdn);
    490     }
    491 
    492     public void removeSP(String fqdn) throws IOException {
    493         if (mSPs.remove(fqdn) == null) {
    494             Log.d(OSUManager.TAG, "No HS20 profile to delete for " + fqdn);
    495             return;
    496         }
    497 
    498         Log.d(OSUManager.TAG, "Deleting HS20 profile for " + fqdn);
    499 
    500         MOTree moTree;
    501         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
    502             moTree = MOTree.unmarshal(in);
    503             OMAConstructed tbd = findTargetTree(moTree, fqdn);
    504             if (tbd == null) {
    505                 throw new IOException("Node " + fqdn + " doesn't exist in MO tree");
    506             }
    507             OMAConstructed pps = moTree.getRoot();
    508             OMANode removed = pps.removeNode("?", tbd);
    509             if (removed == null) {
    510                 throw new IOException("Failed to remove " + fqdn + " out of MO tree");
    511             }
    512         }
    513         writeMO(moTree, mPpsFile);
    514     }
    515 
    516     public MOTree getMOTree(HomeSP homeSP) throws IOException {
    517         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
    518             MOTree moTree = MOTree.unmarshal(in);
    519             OMAConstructed target = findTargetTree(moTree, homeSP.getFQDN());
    520             if (target == null) {
    521                 throw new IOException("Can't find " + homeSP.getFQDN() + " in MO tree");
    522             }
    523             return MOTree.buildMgmtTree(OMAConstants.PPS_URN, OMAConstants.OMAVersion, target);
    524         }
    525     }
    526 
    527     private static void writeMO(MOTree moTree, File f) throws IOException {
    528         try (BufferedOutputStream out =
    529                      new BufferedOutputStream(new FileOutputStream(f, false))) {
    530             moTree.marshal(out);
    531             out.flush();
    532         }
    533     }
    534 
    535     private static String fqdnList(Collection<HomeSP> sps) {
    536         StringBuilder sb = new StringBuilder();
    537         boolean first = true;
    538         for (HomeSP sp : sps) {
    539             if (first) {
    540                 first = false;
    541             } else {
    542                 sb.append(", ");
    543             }
    544             sb.append(sp.getFQDN());
    545         }
    546         return sb.toString();
    547     }
    548 
    549     private static OMANode buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int instanceID)
    550             throws IOException {
    551         OMANode providerSubNode = root.addChild(getInstanceString(instanceID),
    552                 null, null, null);
    553 
    554         // The HomeSP:
    555         OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
    556         if (!homeSP.getSSIDs().isEmpty()) {
    557             OMAConstructed nwkIDNode =
    558                     (OMAConstructed) homeSpNode.addChild(TAG_NetworkID, null, null, null);
    559             int instance = 0;
    560             for (Map.Entry<String, Long> entry : homeSP.getSSIDs().entrySet()) {
    561                 OMAConstructed inode =
    562                         (OMAConstructed) nwkIDNode
    563                                 .addChild(getInstanceString(instance++), null, null, null);
    564                 inode.addChild(TAG_SSID, null, entry.getKey(), null);
    565                 if (entry.getValue() != null) {
    566                     inode.addChild(TAG_HESSID, null,
    567                             String.format("%012x", entry.getValue()), null);
    568                 }
    569             }
    570         }
    571 
    572         homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
    573 
    574         if (homeSP.getIconURL() != null) {
    575             homeSpNode.addChild(TAG_IconURL, null, homeSP.getIconURL(), null);
    576         }
    577 
    578         homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
    579 
    580         if (!homeSP.getMatchAllOIs().isEmpty() || !homeSP.getMatchAnyOIs().isEmpty()) {
    581             OMAConstructed homeOIList =
    582                     (OMAConstructed) homeSpNode.addChild(TAG_HomeOIList, null, null, null);
    583 
    584             int instance = 0;
    585             for (Long oi : homeSP.getMatchAllOIs()) {
    586                 OMAConstructed inode =
    587                         (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
    588                                 null, null, null);
    589                 inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
    590                 inode.addChild(TAG_HomeOIRequired, null, "TRUE", null);
    591             }
    592             for (Long oi : homeSP.getMatchAnyOIs()) {
    593                 OMAConstructed inode =
    594                         (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
    595                                 null, null, null);
    596                 inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
    597                 inode.addChild(TAG_HomeOIRequired, null, "FALSE", null);
    598             }
    599         }
    600 
    601         if (!homeSP.getOtherHomePartners().isEmpty()) {
    602             OMAConstructed otherPartners =
    603                     (OMAConstructed) homeSpNode.addChild(TAG_OtherHomePartners, null, null, null);
    604             int instance = 0;
    605             for (String fqdn : homeSP.getOtherHomePartners()) {
    606                 OMAConstructed inode =
    607                         (OMAConstructed) otherPartners.addChild(getInstanceString(instance++),
    608                                 null, null, null);
    609                 inode.addChild(TAG_FQDN, null, fqdn, null);
    610             }
    611         }
    612 
    613         if (!homeSP.getRoamingConsortiums().isEmpty()) {
    614             homeSpNode.addChild(TAG_RoamingConsortiumOI, null,
    615                     getRCList(homeSP.getRoamingConsortiums()), null);
    616         }
    617 
    618         // The Credential:
    619         OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
    620         Credential cred = homeSP.getCredential();
    621         EAPMethod method = cred.getEAPMethod();
    622 
    623         if (cred.getCtime() > 0) {
    624             credentialNode.addChild(TAG_CreationDate,
    625                     null, DTFormat.format(new Date(cred.getCtime())), null);
    626         }
    627         if (cred.getExpTime() > 0) {
    628             credentialNode.addChild(TAG_ExpirationDate,
    629                     null, DTFormat.format(new Date(cred.getExpTime())), null);
    630         }
    631 
    632         if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
    633                 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
    634                 || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
    635 
    636             OMANode simNode = credentialNode.addChild(TAG_SIM, null, null, null);
    637             simNode.addChild(TAG_IMSI, null, cred.getImsi().toString(), null);
    638             simNode.addChild(TAG_EAPType, null,
    639                     Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
    640 
    641         } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
    642 
    643             OMANode unpNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
    644             unpNode.addChild(TAG_Username, null, cred.getUserName(), null);
    645             unpNode.addChild(TAG_Password, null,
    646                     Base64.encodeToString(cred.getPassword().getBytes(StandardCharsets.UTF_8),
    647                             Base64.DEFAULT), null);
    648             OMANode eapNode = unpNode.addChild(TAG_EAPMethod, null, null, null);
    649             eapNode.addChild(TAG_EAPType, null,
    650                     Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
    651             eapNode.addChild(TAG_InnerMethod, null,
    652                     ((NonEAPInnerAuth) method.getAuthParam()).getOMAtype(), null);
    653 
    654         } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
    655 
    656             OMANode certNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
    657             certNode.addChild(TAG_CertificateType, null, Credential.CertTypeX509, null);
    658             certNode.addChild(TAG_CertSHA256Fingerprint, null,
    659                     Utils.toHex(cred.getFingerPrint()), null);
    660 
    661         } else {
    662             throw new OMAException("Invalid credential on " + homeSP.getFQDN());
    663         }
    664 
    665         credentialNode.addChild(TAG_Realm, null, cred.getRealm(), null);
    666 
    667         // !!! Note: This node defines CRL checking through OSCP, I suspect we won't be able
    668         // to do that so it is commented out:
    669         //credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "TRUE", null);
    670         return providerSubNode;
    671     }
    672 
    673     private static String getInstanceString(int instance) {
    674         return "r1i" + instance;
    675     }
    676 
    677     private static String getRCList(Collection<Long> rcs) {
    678         StringBuilder builder = new StringBuilder();
    679         boolean first = true;
    680         for (Long roamingConsortium : rcs) {
    681             if (first) {
    682                 first = false;
    683             } else {
    684                 builder.append(',');
    685             }
    686             builder.append(String.format("%x", roamingConsortium));
    687         }
    688         return builder.toString();
    689     }
    690 
    691     public static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
    692         OMAConstructed spList;
    693         List<HomeSP> homeSPs = new ArrayList<>();
    694         if (moTree.getRoot().getName().equals(TAG_PerProviderSubscription)) {
    695             // The old PPS file was rooted at PPS instead of MgmtTree to conserve space
    696             spList = moTree.getRoot();
    697 
    698             if (spList == null) {
    699                 return homeSPs;
    700             }
    701 
    702             for (OMANode node : spList.getChildren()) {
    703                 if (!node.isLeaf()) {
    704                     homeSPs.add(buildHomeSP(node, 0));
    705                 }
    706             }
    707         } else {
    708             for (OMANode ppsRoot : moTree.getRoot().getChildren()) {
    709                 if (ppsRoot.getName().equals(TAG_PerProviderSubscription)) {
    710                     Integer updateIdentifier = null;
    711                     OMANode instance = null;
    712                     for (OMANode child : ppsRoot.getChildren()) {
    713                         if (child.getName().equals(TAG_UpdateIdentifier)) {
    714                             updateIdentifier = getInteger(child);
    715                         } else if (!child.isLeaf()) {
    716                             instance = child;
    717                         }
    718                     }
    719                     if (instance == null) {
    720                         throw new OMAException("PPS node missing instance node");
    721                     }
    722                     homeSPs.add(buildHomeSP(instance,
    723                             updateIdentifier != null ? updateIdentifier : 0));
    724                 }
    725             }
    726         }
    727 
    728         return homeSPs;
    729     }
    730 
    731     private static HomeSP buildHomeSP(OMANode ppsRoot, int updateIdentifier) throws OMAException {
    732         OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
    733 
    734         String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
    735         String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
    736         String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
    737 
    738         HashSet<Long> roamingConsortiums = new HashSet<>();
    739         String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
    740         if (oiString != null) {
    741             for (String oi : oiString.split(",")) {
    742                 roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
    743             }
    744         }
    745 
    746         Map<String, Long> ssids = new HashMap<>();
    747 
    748         OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
    749         if (ssidListNode != null) {
    750             for (OMANode ssidRoot : ssidListNode.getChildren()) {
    751                 OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
    752                 ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
    753             }
    754         }
    755 
    756         Set<Long> matchAnyOIs = new HashSet<>();
    757         List<Long> matchAllOIs = new ArrayList<>();
    758         OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
    759         if (homeOIListNode != null) {
    760             for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
    761                 String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
    762                 if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
    763                     matchAllOIs.add(Long.parseLong(homeOI, 16));
    764                 } else {
    765                     matchAnyOIs.add(Long.parseLong(homeOI, 16));
    766                 }
    767             }
    768         }
    769 
    770         Set<String> otherHomePartners = new HashSet<>();
    771         OMANode otherListNode =
    772                 spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
    773         if (otherListNode != null) {
    774             for (OMANode fqdnNode : otherListNode.getChildren()) {
    775                 otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
    776             }
    777         }
    778 
    779         Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
    780 
    781         OMANode policyNode = ppsRoot.getChild(TAG_Policy);
    782         Policy policy = policyNode != null ? new Policy(policyNode) : null;
    783 
    784         Map<String, String> aaaTrustRoots;
    785         OMANode aaaRootNode = ppsRoot.getChild(TAG_AAAServerTrustRoot);
    786         if (aaaRootNode == null) {
    787             aaaTrustRoots = null;
    788         } else {
    789             aaaTrustRoots = new HashMap<>(aaaRootNode.getChildren().size());
    790             for (OMANode child : aaaRootNode.getChildren()) {
    791                 aaaTrustRoots.put(getString(child, TAG_CertURL),
    792                         getString(child, TAG_CertSHA256Fingerprint));
    793             }
    794         }
    795 
    796         OMANode updateNode = ppsRoot.getChild(TAG_SubscriptionUpdate);
    797         UpdateInfo subscriptionUpdate = updateNode != null ? new UpdateInfo(updateNode) : null;
    798         OMANode subNode = ppsRoot.getChild(TAG_SubscriptionParameters);
    799         SubscriptionParameters subscriptionParameters = subNode != null ?
    800                 new SubscriptionParameters(subNode) : null;
    801 
    802         return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
    803                 matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential,
    804                 policy, getInteger(ppsRoot.getChild(TAG_CredentialPriority), 0),
    805                 aaaTrustRoots, subscriptionUpdate, subscriptionParameters, updateIdentifier);
    806     }
    807 
    808     private static Credential buildCredential(OMANode credNode) throws OMAException {
    809         long ctime = getTime(credNode.getChild(TAG_CreationDate));
    810         long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
    811         String realm = getString(credNode.getChild(TAG_Realm));
    812         boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
    813 
    814         OMANode unNode = credNode.getChild(TAG_UsernamePassword);
    815         OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
    816         OMANode simNode = credNode.getChild(TAG_SIM);
    817 
    818         int alternatives = 0;
    819         alternatives += unNode != null ? 1 : 0;
    820         alternatives += certNode != null ? 1 : 0;
    821         alternatives += simNode != null ? 1 : 0;
    822         if (alternatives != 1) {
    823             throw new OMAException("Expected exactly one credential type, got " + alternatives);
    824         }
    825 
    826         if (unNode != null) {
    827             String userName = getString(unNode.getChild(TAG_Username));
    828             String password = getString(unNode.getChild(TAG_Password));
    829             boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
    830             String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
    831             boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
    832 
    833             OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
    834             int eapID = getInteger(eapMethodNode.getChild(TAG_EAPType));
    835 
    836             EAP.EAPMethodID eapMethodID = EAP.mapEAPMethod(eapID);
    837             if (eapMethodID == null) {
    838                 throw new OMAException("Unknown EAP method: " + eapID);
    839             }
    840 
    841             Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
    842             Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
    843             Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
    844             EAP.EAPMethodID innerEAPMethod = null;
    845             if (innerEAPType != null) {
    846                 innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
    847                 if (innerEAPMethod == null) {
    848                     throw new OMAException("Bad inner EAP method: " + innerEAPType);
    849                 }
    850             }
    851 
    852             Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
    853             Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
    854             String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
    855 
    856             EAPMethod eapMethod;
    857             if (innerEAPMethod != null) {
    858                 eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
    859             } else if (vid != null) {
    860                 eapMethod = new EAPMethod(eapMethodID,
    861                         new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
    862                                 vid.intValue(), vtype));
    863             } else if (innerVid != null) {
    864                 eapMethod =
    865                         new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
    866                                 .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
    867             } else if (innerNonEAPMethod != null) {
    868                 eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
    869             } else {
    870                 throw new OMAException("Incomplete set of EAP parameters");
    871             }
    872 
    873             return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
    874                     password, machineManaged, softTokenApp, ableToShare);
    875         }
    876         if (certNode != null) {
    877             try {
    878                 String certTypeString = getString(certNode.getChild(TAG_CertificateType));
    879                 byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
    880 
    881                 EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
    882 
    883                 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
    884                         Credential.mapCertType(certTypeString), fingerPrint);
    885             } catch (NumberFormatException nfe) {
    886                 throw new OMAException("Bad hex string: " + nfe.toString());
    887             }
    888         }
    889         if (simNode != null) {
    890             try {
    891                 IMSIParameter imsi = new IMSIParameter(getString(simNode.getChild(TAG_IMSI)));
    892 
    893                 EAPMethod eapMethod =
    894                         new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
    895                                 null);
    896 
    897                 return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
    898             } catch (IOException ioe) {
    899                 throw new OMAException("Failed to parse IMSI: " + ioe);
    900             }
    901         }
    902         throw new OMAException("Missing credential parameters");
    903     }
    904 
    905     public static OMANode getChild(OMANode node, String key) throws OMAException {
    906         OMANode child = node.getChild(key);
    907         if (child == null) {
    908             throw new OMAException("No such node: " + key);
    909         }
    910         return child;
    911     }
    912 
    913     public static String getString(OMANode node, String key) throws OMAException {
    914         OMANode child = node.getChild(key);
    915         if (child == null) {
    916             throw new OMAException("Missing value for " + key);
    917         } else if (!child.isLeaf()) {
    918             throw new OMAException(key + " is not a leaf node");
    919         }
    920         return child.getValue();
    921     }
    922 
    923     public static long getLong(OMANode node, String key, Long dflt) throws OMAException {
    924         OMANode child = node.getChild(key);
    925         if (child == null) {
    926             if (dflt != null) {
    927                 return dflt;
    928             } else {
    929                 throw new OMAException("Missing value for " + key);
    930             }
    931         } else {
    932             if (!child.isLeaf()) {
    933                 throw new OMAException(key + " is not a leaf node");
    934             }
    935             String value = child.getValue();
    936             try {
    937                 long result = Long.parseLong(value);
    938                 if (result < 0) {
    939                     throw new OMAException("Negative value for " + key);
    940                 }
    941                 return result;
    942             } catch (NumberFormatException nfe) {
    943                 throw new OMAException("Value for " + key + " is non-numeric: " + value);
    944             }
    945         }
    946     }
    947 
    948     public static <T> T getSelection(OMANode node, String key) throws OMAException {
    949         OMANode child = node.getChild(key);
    950         if (child == null) {
    951             throw new OMAException("Missing value for " + key);
    952         } else if (!child.isLeaf()) {
    953             throw new OMAException(key + " is not a leaf node");
    954         }
    955         return getSelection(key, child.getValue());
    956     }
    957 
    958     public static <T> T getSelection(String key, String value) throws OMAException {
    959         if (value == null) {
    960             throw new OMAException("No value for " + key);
    961         }
    962         Map<String, Object> kvp = sSelectionMap.get(key);
    963         T result = (T) kvp.get(value.toLowerCase());
    964         if (result == null) {
    965             throw new OMAException("Invalid value '" + value + "' for " + key);
    966         }
    967         return result;
    968     }
    969 
    970     private static boolean getBoolean(OMANode boolNode) {
    971         return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
    972     }
    973 
    974     public static String getString(OMANode stringNode) {
    975         return stringNode != null ? stringNode.getValue() : null;
    976     }
    977 
    978     private static int getInteger(OMANode intNode, int dflt) throws OMAException {
    979         if (intNode == null) {
    980             return dflt;
    981         }
    982         return getInteger(intNode);
    983     }
    984 
    985     private static int getInteger(OMANode intNode) throws OMAException {
    986         if (intNode == null) {
    987             throw new OMAException("Missing integer value");
    988         }
    989         try {
    990             return Integer.parseInt(intNode.getValue());
    991         } catch (NumberFormatException nfe) {
    992             throw new OMAException("Invalid integer: " + intNode.getValue());
    993         }
    994     }
    995 
    996     private static Long getMac(OMANode macNode) throws OMAException {
    997         if (macNode == null) {
    998             return null;
    999         }
   1000         try {
   1001             return Long.parseLong(macNode.getValue(), 16);
   1002         } catch (NumberFormatException nfe) {
   1003             throw new OMAException("Invalid MAC: " + macNode.getValue());
   1004         }
   1005     }
   1006 
   1007     private static Long getOptionalInteger(OMANode intNode) throws OMAException {
   1008         if (intNode == null) {
   1009             return null;
   1010         }
   1011         try {
   1012             return Long.parseLong(intNode.getValue());
   1013         } catch (NumberFormatException nfe) {
   1014             throw new OMAException("Invalid integer: " + intNode.getValue());
   1015         }
   1016     }
   1017 
   1018     public static long getTime(OMANode timeNode) throws OMAException {
   1019         if (timeNode == null) {
   1020             return Utils.UNSET_TIME;
   1021         }
   1022         String timeText = timeNode.getValue();
   1023         try {
   1024             Date date = DTFormat.parse(timeText);
   1025             return date.getTime();
   1026         } catch (ParseException pe) {
   1027             throw new OMAException("Badly formatted time: " + timeText);
   1028         }
   1029     }
   1030 
   1031     private static byte[] getOctets(OMANode octetNode) throws OMAException {
   1032         if (octetNode == null) {
   1033             throw new OMAException("Missing byte value");
   1034         }
   1035         return Utils.hexToBytes(octetNode.getValue());
   1036     }
   1037 }
   1038