summaryrefslogtreecommitdiffstats
path: root/service/java/com/android/server/wifi/hotspot2/omadm
diff options
context:
space:
mode:
authorJan Nordqvist <jannq@google.com>2015-01-28 14:44:53 -0800
committerJan Nordqvist <jannq@google.com>2015-01-30 15:46:45 -0800
commit71a988c8e9859244b83cd55bb6b6ee913fcaf95c (patch)
treecdb37981105d853e003efcfe9d2fd5ec26ea8165 /service/java/com/android/server/wifi/hotspot2/omadm
parent7b2caa25fb57f2d95e0d0421704c49d3af4b8e6f (diff)
downloadandroid_frameworks_opt_net_wifi-71a988c8e9859244b83cd55bb6b6ee913fcaf95c.tar.gz
android_frameworks_opt_net_wifi-71a988c8e9859244b83cd55bb6b6ee913fcaf95c.tar.bz2
android_frameworks_opt_net_wifi-71a988c8e9859244b83cd55bb6b6ee913fcaf95c.zip
Credential/profile storage added, completed network matching and added HS20 Simulator.
Change-Id: I1b27dadf3d65ed0a858dee209df975180a3b90e6
Diffstat (limited to 'service/java/com/android/server/wifi/hotspot2/omadm')
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java432
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java218
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java30
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java96
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java114
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java9
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java118
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java83
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java68
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java60
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java149
-rw-r--r--service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java128
12 files changed, 1505 insertions, 0 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java b/service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java
new file mode 100644
index 000000000..15dbb33bc
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java
@@ -0,0 +1,432 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import com.android.server.wifi.anqp.eap.EAP;
+import com.android.server.wifi.anqp.eap.EAPMethod;
+import com.android.server.wifi.anqp.eap.ExpandedEAPMethod;
+import com.android.server.wifi.anqp.eap.InnerAuthEAP;
+import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
+import com.android.server.wifi.hotspot2.pps.Credential;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import org.xml.sax.SAXException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * Handles provisioning of PerProviderSubscription data.
+ */
+public class MOManager {
+ private final File mPpsFile;
+ private final Map<String, HomeSP> mSPs;
+
+ public MOManager(File ppsFile) throws IOException {
+ mPpsFile = ppsFile;
+ mSPs = new HashMap<String, HomeSP>();
+ }
+
+ public File getPpsFile() {
+ return mPpsFile;
+ }
+
+ public List<HomeSP> loadAllSPs() throws IOException {
+ List<MOTree> trees = new ArrayList<MOTree>();
+ List<HomeSP> sps = new ArrayList<HomeSP>();
+
+ if (!mPpsFile.exists()) {
+ return sps;
+ }
+
+ BufferedInputStream in = null;
+ try {
+ in = new BufferedInputStream(new FileInputStream(mPpsFile));
+ while (in.available() > 0) {
+ MOTree tree = MOTree.unmarshal(in);
+ if (tree != null) {
+ trees.add(tree);
+ } else {
+ break;
+ }
+ }
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ /**/
+ }
+ }
+ }
+
+ for (MOTree moTree : trees) {
+ List<HomeSP> sp = buildSPs(moTree);
+ if (sp != null) {
+ sps.addAll(sp);
+ }
+ }
+
+ for (HomeSP sp : sps) {
+ if (mSPs.put(sp.getFQDN(), sp) != null) {
+ throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
+ }
+ }
+ return sps;
+ }
+
+ public HomeSP addSP(InputStream xmlIn) throws IOException, SAXException {
+ OMAParser omaParser = new OMAParser();
+ MOTree tree = omaParser.parse(xmlIn, OMAConstants.LOC_PPS + ":1.0");
+ List<HomeSP> spList = buildSPs(tree);
+ if (spList.size() != 1) {
+ throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
+ }
+ HomeSP sp = spList.iterator().next();
+ String fqdn = sp.getFQDN();
+ if (mSPs.put(fqdn, sp) != null) {
+ throw new OMAException("SP " + fqdn + " already exists");
+ }
+
+ BufferedOutputStream out = null;
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true));
+ tree.marshal(out);
+ out.flush();
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ioe) {
+ /**/
+ }
+ }
+ }
+
+ return sp;
+ }
+
+ private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
+ static {
+ DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
+ public static final String TAG_AbleToShare = "AbleToShare";
+ public static final String TAG_CertificateType = "CertificateType";
+ public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
+ public static final String TAG_CertURL = "CertURL";
+ public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
+ public static final String TAG_Country = "Country";
+ public static final String TAG_CreationDate = "CreationDate";
+ public static final String TAG_Credential = "Credential";
+ public static final String TAG_CredentialPriority = "CredentialPriority";
+ public static final String TAG_DataLimit = "DataLimit";
+ public static final String TAG_DigitalCertificate = "DigitalCertificate";
+ public static final String TAG_DLBandwidth = "DLBandwidth";
+ public static final String TAG_EAPMethod = "EAPMethod";
+ public static final String TAG_EAPType = "EAPType";
+ public static final String TAG_ExpirationDate = "ExpirationDate";
+ public static final String TAG_Extension = "Extension";
+ public static final String TAG_FQDN = "FQDN";
+ public static final String TAG_FQDN_Match = "FQDN_Match";
+ public static final String TAG_FriendlyName = "FriendlyName";
+ public static final String TAG_HESSID = "HESSID";
+ public static final String TAG_HomeOI = "HomeOI";
+ public static final String TAG_HomeOIList = "HomeOIList";
+ public static final String TAG_HomeOIRequired = "HomeOIRequired";
+ public static final String TAG_HomeSP = "HomeSP";
+ public static final String TAG_IconURL = "IconURL";
+ public static final String TAG_IMSI = "IMSI";
+ public static final String TAG_InnerEAPType = "InnerEAPType";
+ public static final String TAG_InnerMethod = "InnerMethod";
+ public static final String TAG_InnerVendorID = "InnerVendorID";
+ public static final String TAG_InnerVendorType = "InnerVendorType";
+ public static final String TAG_IPProtocol = "IPProtocol";
+ public static final String TAG_MachineManaged = "MachineManaged";
+ public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
+ public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
+ public static final String TAG_NetworkID = "NetworkID";
+ public static final String TAG_NetworkType = "NetworkType";
+ public static final String TAG_Other = "Other";
+ public static final String TAG_OtherHomePartners = "OtherHomePartners";
+ public static final String TAG_Password = "Password";
+ public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
+ public static final String TAG_Policy = "Policy";
+ public static final String TAG_PolicyUpdate = "PolicyUpdate";
+ public static final String TAG_PortNumber = "PortNumber";
+ public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
+ public static final String TAG_Priority = "Priority";
+ public static final String TAG_Realm = "Realm";
+ public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
+ public static final String TAG_Restriction = "Restriction";
+ public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
+ public static final String TAG_SIM = "SIM";
+ public static final String TAG_SoftTokenApp = "SoftTokenApp";
+ public static final String TAG_SPExclusionList = "SPExclusionList";
+ public static final String TAG_SSID = "SSID";
+ public static final String TAG_StartDate = "StartDate";
+ public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
+ public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
+ public static final String TAG_TimeLimit = "TimeLimit";
+ public static final String TAG_TrustRoot = "TrustRoot";
+ public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
+ public static final String TAG_ULBandwidth = "ULBandwidth";
+ public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
+ public static final String TAG_UpdateInterval = "UpdateInterval";
+ public static final String TAG_UpdateMethod = "UpdateMethod";
+ public static final String TAG_URI = "URI";
+ public static final String TAG_UsageLimits = "UsageLimits";
+ public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
+ public static final String TAG_Username = "Username";
+ public static final String TAG_UsernamePassword = "UsernamePassword";
+ public static final String TAG_VendorId = "VendorId";
+ public static final String TAG_VendorType = "VendorType";
+
+ private static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
+ List<String> spPath = Arrays.asList(TAG_PerProviderSubscription);
+ OMAConstructed spList = moTree.getRoot().getListValue(spPath.iterator());
+
+ List<HomeSP> homeSPs = new ArrayList<HomeSP>();
+
+ for (OMANode spRoot : spList.getChildren()) {
+ homeSPs.add(buildHomeSP(spRoot));
+ }
+
+ return homeSPs;
+ }
+
+ private static HomeSP buildHomeSP(OMANode ppsRoot) throws OMAException {
+ OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
+
+ String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
+ String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
+ System.out.println("FQDN: " + fqdn + ", friendly: " + friendlyName);
+ String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
+
+ Set<Long> roamingConsortiums = new HashSet<Long>();
+ String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
+ if (oiString != null) {
+ for (String oi : oiString.split(",")) {
+ roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
+ }
+ }
+
+ Map<String, String> ssids = new HashMap<String, String>();
+
+ OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
+ if (ssidListNode != null) {
+ for (OMANode ssidRoot : ssidListNode.getChildren()) {
+ OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
+ ssids.put(ssidRoot.getChild(TAG_SSID).getValue(),
+ hessidNode != null ? hessidNode.getValue() : null);
+ }
+ }
+
+ Set<Long> matchAnyOIs = new HashSet<Long>();
+ List<Long> matchAllOIs = new ArrayList<Long>();
+ OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
+ if (homeOIListNode != null) {
+ for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
+ String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
+ if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
+ matchAllOIs.add(Long.parseLong(homeOI, 16));
+ } else {
+ matchAnyOIs.add(Long.parseLong(homeOI, 16));
+ }
+ }
+ }
+
+ Set<String> otherHomePartners = new HashSet<String>();
+ OMANode otherListNode =
+ spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
+ if (otherListNode != null) {
+ for (OMANode fqdnNode : otherListNode.getChildren()) {
+ otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
+ }
+ }
+
+ Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
+
+ return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
+ matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential);
+ }
+
+ private static Credential buildCredential(OMANode credNode) throws OMAException {
+ long ctime = getTime(credNode.getChild(TAG_CreationDate));
+ long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
+ String realm = getString(credNode.getChild(TAG_Realm));
+ boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
+
+ OMANode unNode = credNode.getChild(TAG_UsernamePassword);
+ OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
+ OMANode simNode = credNode.getChild(TAG_SIM);
+
+ int alternatives = 0;
+ alternatives += unNode != null ? 1 : 0;
+ alternatives += certNode != null ? 1 : 0;
+ alternatives += simNode != null ? 1 : 0;
+ if (alternatives != 1) {
+ throw new OMAException("Expected exactly one credential type, got " + alternatives);
+ }
+
+ if (unNode != null) {
+ String userName = unNode.getChild(TAG_Username).getValue();
+ String password = unNode.getChild(TAG_Password).getValue();
+ boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
+ String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
+ boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
+
+ OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
+ EAP.EAPMethodID eapMethodID =
+ EAP.mapEAPMethod(getInteger(eapMethodNode.getChild(TAG_EAPType)));
+ if (eapMethodID == null) {
+ throw new OMAException("Unknown EAP method");
+ }
+
+ Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
+ Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
+ Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
+ EAP.EAPMethodID innerEAPMethod = null;
+ if (innerEAPType != null) {
+ innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
+ if (innerEAPMethod == null) {
+ throw new OMAException("Bad inner EAP method: " + innerEAPType);
+ }
+ }
+
+ Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
+ Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
+ String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
+
+ EAPMethod eapMethod;
+ if (innerEAPMethod != null) {
+ eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
+ } else if (vid != null) {
+ eapMethod = new EAPMethod(eapMethodID,
+ new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
+ vid.intValue(), vtype));
+ } else if (innerVid != null) {
+ eapMethod =
+ new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
+ .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
+ } else if (innerNonEAPMethod != null) {
+ eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
+ } else {
+ throw new OMAException("Incomplete set of EAP parameters");
+ }
+
+ return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
+ password, machineManaged, softTokenApp, ableToShare);
+ }
+ if (certNode != null) {
+ String certTypeString = getString(certNode.getChild(TAG_CertificateType));
+ byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
+
+ EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
+
+ return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
+ Credential.mapCertType(certTypeString), fingerPrint);
+ }
+ if (simNode != null) {
+
+ String imsi = getString(simNode.getChild(TAG_IMSI));
+ EAPMethod eapMethod =
+ new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
+ null);
+
+ return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
+ }
+ throw new OMAException("Missing credential parameters");
+ }
+
+ private static boolean getBoolean(OMANode boolNode) {
+ return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
+ }
+
+ private static String getString(OMANode stringNode) {
+ return stringNode != null ? stringNode.getValue() : null;
+ }
+
+ private static int getInteger(OMANode intNode) throws OMAException {
+ if (intNode == null) {
+ throw new OMAException("Missing integer value");
+ }
+ try {
+ return Integer.parseInt(intNode.getValue());
+ } catch (NumberFormatException nfe) {
+ throw new OMAException("Invalid integer: " + intNode.getValue());
+ }
+ }
+
+ private static Long getOptionalInteger(OMANode intNode) throws OMAException {
+ if (intNode == null) {
+ return null;
+ }
+ try {
+ return Long.parseLong(intNode.getValue());
+ } catch (NumberFormatException nfe) {
+ throw new OMAException("Invalid integer: " + intNode.getValue());
+ }
+ }
+
+ private static long getTime(OMANode timeNode) throws OMAException {
+ if (timeNode == null) {
+ return -1;
+ }
+ String timeText = timeNode.getValue();
+ try {
+ Date date = DTFormat.parse(timeText);
+ return date.getTime();
+ } catch (ParseException pe) {
+ throw new OMAException("Badly formatted time: " + timeText);
+ }
+ }
+
+ private static byte[] getOctets(OMANode octetNode) throws OMAException {
+ if (octetNode == null) {
+ throw new OMAException("Missing byte value");
+ }
+ String text = octetNode.getValue();
+ if ((text.length() & 1) == 1) {
+ throw new OMAException("Odd length octet value: " + text);
+ }
+ byte[] octets = new byte[text.length() / 2];
+ for (int n = 0; n < octets.length; n++) {
+ octets[n] = (byte) (fromHex(text.charAt(n * 2)) << Byte.SIZE |
+ fromHex(text.charAt(n * 2 + 1)));
+ }
+ return octets;
+ }
+
+ private static int fromHex(char ch) throws OMAException {
+ if (ch <= '9' && ch >= '0') {
+ return ch - '0';
+ } else if (ch >= 'a' && ch <= 'f') {
+ return ch + 10 - 'a';
+ } else if (ch <= 'F' && ch >= 'A') {
+ return ch + 10 - 'A';
+ } else {
+ throw new OMAException("Bad hex-character: " + ch);
+ }
+ }
+
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java b/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java
new file mode 100644
index 000000000..bb6b16d50
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java
@@ -0,0 +1,218 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class MOTree {
+ private static final String NodeTag = "Node";
+ private static final String NodeNameTag = "NodeName";
+ private static final String PathTag = "Path";
+ private static final String ValueTag = "Value";
+ private static final String RTPropTag = "RTProperties";
+ private static final String TypeTag = "Type";
+ private static final String DDFNameTag = "DDFName";
+
+ private final String mUrn;
+ private final String mDtdRev;
+ private final OMAConstructed mRoot;
+
+ public MOTree(XMLNode node, String urn) throws IOException, SAXException {
+ Iterator<XMLNode> children = node.getChildren().iterator();
+
+ String dtdRev = null;
+
+ while (children.hasNext()) {
+ XMLNode child = children.next();
+ if (child.getTag().equals(OMAConstants.SyncMLVersionTag)) {
+ dtdRev = child.getText();
+ children.remove();
+ break;
+ }
+ }
+
+ mUrn = urn;
+ mDtdRev = dtdRev;
+
+ mRoot = new OMAConstructed(null, ".", null);
+
+ for (XMLNode child : node.getChildren()) {
+ buildNode(mRoot, child);
+ }
+ }
+
+ private MOTree(String urn, String rev, OMAConstructed root) {
+ mUrn = urn;
+ mDtdRev = rev;
+ mRoot = root;
+ }
+
+ private static class NodeData {
+ private final String mName;
+ private String mPath;
+ private String mValue;
+
+ private NodeData(String name) {
+ mName = name;
+ }
+
+ private void setPath(String path) {
+ mPath = path;
+ }
+
+ private void setValue(String value) {
+ mValue = value;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+ }
+
+ private static void buildNode(OMANode parent, XMLNode node) throws IOException {
+ if (!node.getTag().equals(NodeTag))
+ throw new IOException("Node is a '" + node.getTag() + "' instead of a 'Node'");
+
+ Map<String, XMLNode> checkMap = new HashMap<String, XMLNode>(3);
+ String context = null;
+ List<NodeData> values = new ArrayList<NodeData>();
+ List<XMLNode> children = new ArrayList<XMLNode>();
+
+ NodeData curValue = null;
+
+ for (XMLNode child : node.getChildren()) {
+ XMLNode old = checkMap.put(child.getTag(), child);
+
+ if (child.getTag().equals(NodeNameTag)) {
+ if (curValue != null)
+ throw new IOException(NodeNameTag + " not expected");
+ curValue = new NodeData(child.getText());
+
+ } else if (child.getTag().equals(PathTag)) {
+ if (curValue == null || curValue.getPath() != null)
+ throw new IOException(PathTag + " not expected");
+ curValue.setPath(child.getText());
+
+ } else if (child.getTag().equals(ValueTag)) {
+ if (!children.isEmpty())
+ throw new IOException(ValueTag + " in constructed node");
+ if (curValue == null || curValue.getValue() != null)
+ throw new IOException(ValueTag + " not expected");
+ curValue.setValue(child.getText());
+ values.add(curValue);
+ curValue = null;
+
+ } else if (child.getTag().equals(RTPropTag)) {
+ if (old != null)
+ throw new IOException("Duplicate " + RTPropTag);
+ XMLNode typeNode = getNextNode(child, TypeTag);
+ XMLNode ddfName = getNextNode(typeNode, DDFNameTag);
+ context = ddfName.getText();
+ if (context == null)
+ throw new IOException("No text in " + DDFNameTag);
+
+ } else if (child.getTag().equals(NodeTag)) {
+ if (!values.isEmpty())
+ throw new IOException("Scalar node " + node.getText() + " has Node child");
+ children.add(child);
+
+ }
+ }
+
+ if (values.isEmpty()) {
+ if (curValue == null)
+ throw new IOException("Missing name");
+
+ OMANode subNode = parent.addChild(curValue.getName(),
+ context, null, curValue.getPath());
+
+ for (XMLNode child : children) {
+ buildNode(subNode, child);
+ }
+ } else {
+ if (!children.isEmpty())
+ throw new IOException("Got both sub nodes and value(s)");
+
+ for (NodeData nodeData : values) {
+ parent.addChild(nodeData.getName(), context,
+ nodeData.getValue(), nodeData.getPath());
+ }
+ }
+ }
+
+ private static XMLNode getNextNode(XMLNode node, String tag) throws IOException {
+ if (node == null)
+ throw new IOException("No node for " + tag);
+ if (node.getChildren().size() != 1)
+ throw new IOException("Expected " + node.getTag() + " to have exactly one child");
+ XMLNode child = node.getChildren().iterator().next();
+ if (!child.getTag().equals(tag))
+ throw new IOException("Expected " + node.getTag() + " to have child '" + tag +
+ "' instead of '" + child.getTag() + "'");
+ return child;
+ }
+
+ public String getUrn() {
+ return mUrn;
+ }
+
+ public String getDtdRev() {
+ return mDtdRev;
+ }
+
+ public OMAConstructed getRoot() {
+ return mRoot;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MO Tree v").append(mDtdRev).append(", urn ").append(mUrn).append(")\n");
+ sb.append(mRoot);
+
+ return sb.toString();
+ }
+
+ public void marshal(OutputStream out) throws IOException {
+ out.write("tree ".getBytes(StandardCharsets.UTF_8));
+ OMAConstants.serializeString(mDtdRev, out);
+ out.write(String.format("(%s)\n", mUrn).getBytes(StandardCharsets.UTF_8));
+ mRoot.marshal(out, 0);
+ }
+
+ public static MOTree unmarshal(InputStream in) throws IOException {
+ boolean strip = true;
+ StringBuilder tree = new StringBuilder();
+ for (; ; ) {
+ int octet = in.read();
+ if (octet < 0) {
+ return null;
+ } else if (octet > ' ') {
+ tree.append((char) octet);
+ strip = false;
+ } else if (!strip) {
+ break;
+ }
+ }
+ if (!tree.toString().equals("tree"))
+ throw new IOException("Not a tree: " + tree);
+
+ String version = OMAConstants.deserializeString(in);
+ String urn = OMAConstants.readURN(in);
+ OMAConstructed root = OMANode.unmarshal(in);
+
+ return new MOTree(urn, version, root);
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java b/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java
new file mode 100644
index 000000000..1fc1adf7a
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java
@@ -0,0 +1,30 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+public class NodeAttribute {
+ private final String mName;
+ private final String mType;
+ private final String mValue;
+
+ public NodeAttribute(String name, String type, String value) {
+ mName = name;
+ mType = type;
+ mValue = value;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s (%s) = '%s'", mName, mType, mValue);
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java
new file mode 100644
index 000000000..1a42341ad
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java
@@ -0,0 +1,96 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class OMAConstants {
+ private OMAConstants() {
+ }
+
+ public static final String TAG_PostDevData = "spp:sppPostDevData";
+ public static final String TAG_SupportedVersions = "spp:supportedSPPVersions";
+ public static final String TAG_SupportedMOs = "spp:supportedMOList";
+
+ public static final String TAG_MO_Add = "spp:addMO";
+ public static final String TAG_MO_Container = "spp:moContainer";
+
+ public static final String ATTR_URN = "spp:moURN";
+
+ // Following strings excludes the trailing version number (e.g. :1.0)
+ public static final String LOC_PPS = "urn:wfa:mo:hotspot2dot0-perprovidersubscription";
+ public static final String LOC_DEVINFO =
+ "urn:oma:mo:oma-dm-devinfo:1.0 urn:oma:mo:oma-dm-devdetail";
+ public static final String LOC_DEVDETAIL = "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext";
+
+ public static final String SyncMLVersionTag = "VerDTD";
+ public static final String RequiredSyncMLVersion = "1.2";
+
+ private static final Set<String> sMOContainers = new HashSet<String>();
+
+ static {
+ sMOContainers.add(TAG_MO_Add);
+ sMOContainers.add(TAG_MO_Container);
+ }
+
+ public static boolean isMOContainer(String tag) {
+ return sMOContainers.contains(tag);
+ }
+
+ private static final byte[] INDENT = new byte[1024];
+
+ static {
+ Arrays.fill(INDENT, (byte) ' ');
+ }
+
+ public static void serializeString(String s, OutputStream out) throws IOException {
+ byte[] octets = s.getBytes(StandardCharsets.UTF_8);
+ byte[] prefix = String.format("%x:", octets.length).getBytes(StandardCharsets.UTF_8);
+ out.write(prefix);
+ out.write(octets);
+ }
+
+ public static void indent(int level, OutputStream out) throws IOException {
+ out.write(INDENT, 0, level);
+ }
+
+ public static String deserializeString(InputStream in) throws IOException {
+ StringBuilder prefix = new StringBuilder();
+ for (; ; ) {
+ byte b = (byte) in.read();
+ if (b == '.')
+ return null;
+ else if (b == ':')
+ break;
+ else if (b > ' ')
+ prefix.append((char) b);
+ }
+ int length = Integer.parseInt(prefix.toString(), 16);
+ byte[] octets = new byte[length];
+ int offset = 0;
+ while (offset < octets.length) {
+ int amount = in.read(octets, offset, octets.length - offset);
+ if (amount <= 0)
+ throw new EOFException();
+ offset += amount;
+ }
+ return new String(octets, StandardCharsets.UTF_8);
+ }
+
+ public static String readURN(InputStream in) throws IOException {
+ StringBuilder urn = new StringBuilder();
+
+ for (; ; ) {
+ byte b = (byte) in.read();
+ if (b == ')')
+ break;
+ urn.append((char) b);
+ }
+ return urn.toString();
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java
new file mode 100644
index 000000000..6646fcfe8
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java
@@ -0,0 +1,114 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * Created by jannq on 1/12/15.
+ */
+public class OMAConstructed extends OMANode {
+ private final Map<String, OMANode> m_children;
+
+ public OMAConstructed(OMANode parent, String name, String context) {
+ super(parent, name, context);
+ m_children = new HashMap<String, OMANode>();
+ }
+
+ @Override
+ public OMANode addChild(String name, String context, String value, String pathString) throws IOException {
+ if (pathString == null) {
+ OMANode child = value != null ?
+ new OMAScalar(this, name, context, value) :
+ new OMAConstructed(this, name, context);
+ m_children.put(name, child);
+ return child;
+ } else {
+ OMANode target = this;
+ while (target.getParent() != null)
+ target = target.getParent();
+
+ for (String element : pathString.split("/")) {
+ target = target.getChild(element);
+ if (target == null)
+ throw new IOException("No child node '" + element + "' in " + getPathString());
+ else if (target.isLeaf())
+ throw new IOException("Cannot add child to leaf node: " + getPathString());
+ }
+ return target.addChild(name, context, value, null);
+ }
+ }
+
+ public String getScalarValue(Iterator<String> path) throws OMAException {
+ if (!path.hasNext()) {
+ throw new OMAException("Path too short for " + getPathString());
+ }
+ String tag = path.next();
+ OMANode child = m_children.get(tag);
+ if (child != null) {
+ return child.getScalarValue(path);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public OMAConstructed getListValue(Iterator<String> path) throws OMAException {
+ if (!path.hasNext()) {
+ return this;
+ }
+ String tag = path.next();
+ OMANode child = m_children.get(tag);
+ if (child != null) {
+ return child.getListValue(path);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return false;
+ }
+
+ @Override
+ public Collection<OMANode> getChildren() {
+ return Collections.unmodifiableCollection(m_children.values());
+ }
+
+ public OMANode getChild(String name) {
+ return m_children.get(name);
+ }
+
+ @Override
+ public String getValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void toString(StringBuilder sb, int level) {
+ if (getContext() != null) {
+ sb.append(getPathString()).append(" (").append(getContext()).append(')').append('\n');
+ }
+
+ for (OMANode node : m_children.values())
+ node.toString(sb, level + 1);
+ }
+
+ @Override
+ public void marshal(OutputStream out, int level) throws IOException {
+ OMAConstants.indent(level, out);
+ OMAConstants.serializeString(getName(), out);
+ if (getContext() != null) {
+ out.write(String.format("(%s)", getContext()).getBytes(StandardCharsets.UTF_8));
+ }
+ out.write(new byte[] { '+', '\n' });
+
+ for (OMANode child : m_children.values()) {
+ child.marshal(out, level + 1);
+ }
+ OMAConstants.indent(level, out);
+ out.write(".\n".getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java
new file mode 100644
index 000000000..5f8cd112c
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java
@@ -0,0 +1,9 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+
+public class OMAException extends IOException {
+ public OMAException(String message) {
+ super(message);
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java
new file mode 100644
index 000000000..e07f92365
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java
@@ -0,0 +1,118 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class OMANode {
+ private final OMANode mParent;
+ private final String mName;
+ private final String mContext;
+
+ protected OMANode(OMANode parent, String name, String context) {
+ mParent = parent;
+ mName = name;
+ mContext = context;
+ }
+
+ public OMANode getParent() {
+ return mParent;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getContext() {
+ return mContext;
+ }
+
+ public List<String> getPath() {
+ LinkedList<String> path = new LinkedList<String>();
+ for (OMANode node = this; node.getParent() != null; node = node.getParent())
+ path.addFirst(node.getName());
+ return path;
+ }
+
+ public String getPathString() {
+ StringBuilder sb = new StringBuilder();
+ for (String element : getPath()) {
+ sb.append('/').append(element);
+ }
+ return sb.toString();
+ }
+
+ public abstract String getScalarValue(Iterator<String> path) throws OMAException;
+
+ public abstract OMAConstructed getListValue(Iterator<String> path) throws OMAException;
+
+ public abstract boolean isLeaf();
+
+ public abstract Collection<OMANode> getChildren();
+
+ public abstract OMANode getChild(String name);
+
+ public abstract String getValue();
+
+ public abstract OMANode addChild(String name, String context, String value, String path)
+ throws IOException;
+
+ public abstract void marshal(OutputStream out, int level) throws IOException;
+
+ public abstract void toString(StringBuilder sb, int level);
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(sb, 0);
+ return sb.toString();
+ }
+
+ public static OMAConstructed unmarshal(InputStream in) throws IOException {
+ OMANode node = buildNode(in, null);
+ if (node == null || node.isLeaf())
+ throw new IOException("Bad OMA tree");
+ unmarshal(in, (OMAConstructed) node);
+ return (OMAConstructed) node;
+ }
+
+ private static void unmarshal(InputStream in, OMAConstructed parent) throws IOException {
+ for (; ; ) {
+ OMANode node = buildNode(in, parent);
+ if (node == null)
+ return;
+ else if (!node.isLeaf())
+ unmarshal(in, (OMAConstructed) node);
+ }
+ }
+
+ private static OMANode buildNode(InputStream in, OMAConstructed parent) throws IOException {
+ String name = OMAConstants.deserializeString(in);
+ if (name == null)
+ return null;
+
+ String urn = null;
+ int next = in.read();
+ if (next == '(') {
+ urn = OMAConstants.readURN(in);
+ next = in.read();
+ }
+
+ if (next == '=') {
+ String value = OMAConstants.deserializeString(in);
+ return parent.addChild(name, urn, value, null);
+ } else if (next == '+') {
+ if (parent != null)
+ return parent.addChild(name, urn, null, null);
+ else
+ return new OMAConstructed(null, name, urn);
+ }
+ else {
+ throw new IOException("Parse error: expected = or + after node name");
+ }
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java
new file mode 100644
index 000000000..2f352f337
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java
@@ -0,0 +1,83 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.*;
+
+/**
+ * Parses an OMA-DM XML tree.
+ */
+public class OMAParser extends DefaultHandler
+{
+ private XMLNode mRoot;
+ private XMLNode mCurrent;
+
+ public MOTree parse( String text, String urn ) throws IOException, SAXException
+ {
+ try
+ {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ parser.parse( new InputSource( new StringReader( text ) ), this );
+ return new MOTree(mRoot, urn );
+ }
+ catch ( ParserConfigurationException pce )
+ {
+ throw new SAXException( pce );
+ }
+ }
+
+ public MOTree parse( InputStream in, String urn ) throws IOException, SAXException
+ {
+ try
+ {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ parser.parse( new InputSource( in ), this );
+ return new MOTree(mRoot, urn );
+ }
+ catch ( ParserConfigurationException pce )
+ {
+ throw new SAXException( pce );
+ }
+ }
+
+ @Override
+ public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException
+ {
+ XMLNode parent = mCurrent;
+
+ mCurrent = new XMLNode(mCurrent, qName, attributes );
+
+ if ( mRoot == null )
+ mRoot = mCurrent;
+ else
+ parent.addChild(mCurrent);
+ }
+
+ @Override
+ public void endElement( String uri, String localName, String qName ) throws SAXException
+ {
+ if ( ! qName.equals(mCurrent.getTag()) )
+ throw new SAXException( "End tag '" + qName + "' doesn't match current node: " + mCurrent);
+
+ try {
+ mCurrent.close();
+ }
+ catch ( IOException ioe ) {
+ throw new SAXException("Failed to close element", ioe);
+ }
+
+ mCurrent = mCurrent.getParent();
+ }
+
+ @Override
+ public void characters( char[] ch, int start, int length ) throws SAXException
+ {
+ mCurrent.addText(ch, start, length);
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java
new file mode 100644
index 000000000..94a121e45
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java
@@ -0,0 +1,68 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+
+public class OMAScalar extends OMANode {
+ private final String mValue;
+
+ public OMAScalar(OMANode parent, String name, String context, String value) {
+ super(parent, name, context);
+ mValue = value;
+ }
+
+ public String getScalarValue(Iterator<String> path) throws OMAException {
+ return mValue;
+ }
+
+ @Override
+ public OMAConstructed getListValue(Iterator<String> path) throws OMAException {
+ throw new OMAException("Scalar encountered in list path: " + getPathString());
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return true;
+ }
+
+ @Override
+ public Collection<OMANode> getChildren() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getValue() {
+ return mValue;
+ }
+
+ @Override
+ public OMANode getChild(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public OMANode addChild(String name, String context, String value, String path)
+ throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void toString(StringBuilder sb, int level) {
+ sb.append(getPathString()).append('=').append(mValue);
+ if (getContext() != null) {
+ sb.append(" (").append(getContext()).append(')');
+ }
+ sb.append('\n');
+ }
+
+ @Override
+ public void marshal(OutputStream out, int level) throws IOException {
+ OMAConstants.indent(level, out);
+ OMAConstants.serializeString(getName(), out);
+ out.write((byte) '=');
+ OMAConstants.serializeString(getValue(), out);
+ out.write((byte) '\n');
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java b/service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java
new file mode 100644
index 000000000..11d5c5b5a
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java
@@ -0,0 +1,60 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+public class RequestDetail {
+ private final String mSppversion;
+ private final String mRedirectURI;
+ private final String mRequestReason;
+ private final String mSessionID;
+ private final String[] mSupportedVersions;
+ private final String[] mSupportedMOs;
+ private final Collection<MOTree> m_MOs;
+
+ public enum RequestFields {
+ SPPVersion,
+ RedirectURI,
+ RequestReason,
+ SessionID,
+ SupportedVersions,
+ SupportedMOs
+ }
+
+ public RequestDetail(Map<RequestFields, String> values, Collection<MOTree> mos) {
+ mSppversion = values.get(RequestFields.SPPVersion);
+ mRedirectURI = values.get(RequestFields.RedirectURI);
+ mRequestReason = values.get(RequestFields.RequestReason);
+ mSessionID = values.get(RequestFields.SessionID);
+ mSupportedVersions = split(values.get(RequestFields.SupportedVersions));
+ mSupportedMOs = split(values.get(RequestFields.SupportedMOs));
+ m_MOs = mos;
+ }
+
+ public Collection<MOTree> getMOs() {
+ return m_MOs;
+ }
+
+ private static String[] split(String list) {
+ return list != null ? list.split("[ \n\r]+") : null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SPPVersion").append(" = '").append(mSppversion).append("'\n");
+ sb.append("RedirectURI").append(" = '").append(mRedirectURI).append("'\n");
+ sb.append("RequestReason").append(" = '").append(mRequestReason).append("'\n");
+ sb.append("SessionID").append(" = '").append(mSessionID).append("'\n");
+ sb.append("SupportedVersions").append(" = ").append(Arrays.toString(mSupportedVersions))
+ .append('\n');
+ sb.append("SupportedMOs").append(" = ").append(Arrays.toString(mSupportedMOs)).append('\n');
+ sb.append("MOs:\n");
+ for (MOTree mo : m_MOs)
+ sb.append(mo);
+
+ return sb.toString();
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java b/service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java
new file mode 100644
index 000000000..634ba4b4e
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java
@@ -0,0 +1,149 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import java.io.*;
+import java.util.*;
+
+import static com.android.server.wifi.hotspot2.omadm.RequestDetail.RequestFields.*;
+
+public class SOAPParser extends DefaultHandler {
+ private XMLNode mRoot;
+ private XMLNode mCurrent;
+
+ private static String[] TagOnly = new String[0];
+ private static final Map<RequestDetail.RequestFields, String> sSoapMappings =
+ new EnumMap<RequestDetail.RequestFields, String>(RequestDetail.RequestFields.class);
+ private static final Map<String, RequestDetail.RequestFields> sRevMappings =
+ new HashMap<String, RequestDetail.RequestFields>();
+ private static final Map<String, String[]> sSoapAttributes =
+ new HashMap<String, String[]>();
+
+ static {
+ sSoapMappings.put(SPPVersion, "spp:sppVersion");
+ sSoapMappings.put(RedirectURI, "redirectURI");
+ sSoapMappings.put(RequestReason, "requestReason");
+ sSoapMappings.put(SessionID, "spp:sessionID");
+ sSoapMappings.put(SupportedVersions, "spp:supportedSPPVersions");
+ sSoapMappings.put(SupportedMOs, "spp:supportedMOList");
+
+ for (Map.Entry<RequestDetail.RequestFields, String> entry : sSoapMappings.entrySet()) {
+ sRevMappings.put(entry.getValue(), entry.getKey());
+ }
+
+ // Really: The first element inside the body
+ sSoapAttributes.put("spp:sppPostDevDataResponse", new String[]{
+ sSoapMappings.get(SPPVersion),
+ sSoapMappings.get(RedirectURI),
+ sSoapMappings.get(RequestReason),
+ sSoapMappings.get(SessionID)});
+
+ sSoapAttributes.put(sSoapMappings.get(SupportedVersions), TagOnly);
+ sSoapAttributes.put(sSoapMappings.get(SupportedMOs), TagOnly);
+ }
+
+ public XMLNode parse(File file) throws IOException, ParserConfigurationException, SAXException {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+
+ BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
+ try {
+ parser.parse(in, this);
+ } finally {
+ in.close();
+ }
+ return mRoot;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ XMLNode parent = mCurrent;
+
+ mCurrent = new XMLNode(mCurrent, qName, attributes);
+ System.out.println("Added " + mCurrent.getTag() + ", atts " + mCurrent.getAttributes());
+
+ if (mRoot == null)
+ mRoot = mCurrent;
+ else
+ parent.addChild(mCurrent);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (!qName.equals(mCurrent.getTag()))
+ throw new SAXException("End tag '" + qName + "' doesn't match current node: " +
+ mCurrent);
+
+ try {
+ mCurrent.close();
+ } catch (IOException ioe) {
+ throw new SAXException("Failed to close element", ioe);
+ }
+
+ mCurrent = mCurrent.getParent();
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ mCurrent.addText(ch, start, length);
+ }
+
+ public RequestDetail getRequestDetail() {
+ Map<RequestDetail.RequestFields, String> values =
+ new EnumMap<RequestDetail.RequestFields, String>(RequestDetail.RequestFields.class);
+ List<MOTree> mos = new ArrayList<MOTree>();
+ extractFields(mRoot, values, mos);
+ return new RequestDetail(values, mos);
+ }
+
+ private static void extractFields(XMLNode node, Map<RequestDetail.RequestFields,
+ String> values, Collection<MOTree> mos) {
+ String[] attributes = sSoapAttributes.get(node.getTag());
+
+ if (attributes != null) {
+ if (attributes.length == 0) {
+ RequestDetail.RequestFields field = sRevMappings.get(node.getTag());
+ values.put(field, node.getText());
+ } else {
+ for (String attribute : attributes) {
+ RequestDetail.RequestFields field = sRevMappings.get(attribute);
+ if (field != null) {
+ String value = node.getAttributeValue(attribute);
+
+ if (value != null)
+ values.put(field, value);
+ }
+ }
+ }
+ }
+
+ if (node.getMOTree() != null)
+ mos.add(node.getMOTree());
+
+ for (XMLNode child : node.getChildren()) {
+ extractFields(child, values, mos);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ SOAPParser soapParser = new SOAPParser();
+ XMLNode root = soapParser.parse(new File(args[0]));
+ //System.out.println( root );
+ System.out.println(soapParser.getRequestDetail());
+ System.out.println("Marshalled: ");
+ for (MOTree mo : soapParser.getRequestDetail().getMOs()) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mo.marshal(out);
+ System.out.println(out.toString());
+ MOTree back = MOTree.unmarshal(new ByteArrayInputStream(out.toByteArray()));
+ System.out.println(back);
+ }
+ System.out.println("---");
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java b/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java
new file mode 100644
index 000000000..d400e2fc5
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java
@@ -0,0 +1,128 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class XMLNode {
+ private final String mTag;
+ private final Map<String, NodeAttribute> mAttributes;
+ private final List<XMLNode> mChildren;
+ private final XMLNode mParent;
+ private MOTree mMO;
+ private StringBuilder mTextBuilder;
+ private String mText;
+
+ public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
+ mTag = tag;
+
+ mAttributes = new HashMap<String, NodeAttribute>();
+
+ if (attributes.getLength() > 0) {
+ for (int n = 0; n < attributes.getLength(); n++)
+ mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
+ attributes.getType(n), attributes.getValue(n)));
+ }
+
+ mParent = parent;
+ mChildren = new ArrayList<XMLNode>();
+
+ mTextBuilder = new StringBuilder();
+ }
+
+ public void addText(char[] chs, int start, int length) {
+ String s = new String(chs, start, length);
+ String trimmed = s.trim();
+ if (trimmed.isEmpty())
+ return;
+
+ if (s.charAt(0) != trimmed.charAt(0))
+ mTextBuilder.append(' ');
+ mTextBuilder.append(trimmed);
+ if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
+ mTextBuilder.append(' ');
+ }
+
+ public void addChild(XMLNode child) {
+ mChildren.add(child);
+ }
+
+ public void close() throws IOException, SAXException {
+ String text = mTextBuilder.toString().trim();
+ StringBuilder filtered = new StringBuilder(text.length());
+ for (int n = 0; n < text.length(); n++) {
+ char ch = text.charAt(n);
+ if (ch >= ' ')
+ filtered.append(ch);
+ }
+
+ mText = filtered.toString();
+ mTextBuilder = null;
+
+ if (OMAConstants.isMOContainer(mTag)) {
+ NodeAttribute urn = mAttributes.get(OMAConstants.ATTR_URN);
+ OMAParser omaParser = new OMAParser();
+ mMO = omaParser.parse(mText, urn.getValue());
+ }
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public XMLNode getParent() {
+ return mParent;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public Map<String, NodeAttribute> getAttributes() {
+ return Collections.unmodifiableMap(mAttributes);
+ }
+
+ public String getAttributeValue(String name) {
+ NodeAttribute nodeAttribute = mAttributes.get(name);
+ return nodeAttribute != null ? nodeAttribute.getValue() : null;
+ }
+
+ public List<XMLNode> getChildren() {
+ return mChildren;
+ }
+
+ public MOTree getMOTree() {
+ return mMO;
+ }
+
+ private void toString(char[] indent, StringBuilder sb) {
+ Arrays.fill(indent, ' ');
+
+ sb.append(indent).append('<').append(mTag).append("> ").append(mAttributes.values());
+
+ if (mMO != null)
+ sb.append('\n').append(mMO);
+ else if (!mText.isEmpty())
+ sb.append(", text: ").append(mText);
+
+ sb.append('\n');
+
+ char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
+ for (XMLNode child : mChildren)
+ child.toString(subIndent, sb);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(new char[0], sb);
+ return sb.toString();
+ }
+}