summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Peter Qiu <zqiu@google.com> 2017-01-18 16:00:45 -0800
committer Peter Qiu <zqiu@google.com> 2017-01-25 10:20:22 -0800
commitddf6fa06c62e60c103a8f184688ee70a40737d62 (patch)
tree900753882ac82cf58c9a33ec68b83d4a5022af58
parent2d7af45e939a49f3b75e275a12824c9be3d2a064 (diff)
hotspot2: added remaining parameters to PasspointConfiguration
Added remaining parameters to PasspointConfiguration to support complete PerProviderSubscription tree for Release 2 support. The new parameters include: - Update identifier - Credential priority - AAA server trust root - Subscription update - Subscription parameters Bug: 34198926 Test: frameworks/base/wifi/test/runtests.sh Change-Id: If50253612f7777b3b693a344378a2e4810b3ff47
-rw-r--r--wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java275
-rw-r--r--wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java208
-rw-r--r--wifi/tests/assets/pps/PerProviderSubscription.xml97
-rw-r--r--wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java114
-rw-r--r--wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java35
5 files changed, 672 insertions, 57 deletions
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index ad7477ca230d..ca4d12141871 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -19,9 +19,18 @@ package android.net.wifi.hotspot2;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
import android.os.Parcel;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Class representing Passpoint configuration. This contains configurations specified in
* PerProviderSubscription (PPS) Management Object (MO) tree.
@@ -32,11 +41,107 @@ import android.os.Parcel;
* @hide
*/
public final class PasspointConfiguration implements Parcelable {
+ private static final String TAG = "PasspointConfiguration";
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * Maximum bytes for URL string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
public HomeSP homeSp = null;
public Credential credential = null;
public Policy policy = null;
/**
+ * Meta data for performing subscription update.
+ */
+ public UpdateParameter subscriptionUpdate = null;
+
+ /**
+ * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+ * fingerprint of the certificate. The certificates are used for verifying AAA server's
+ * identity during EAP authentication.
+ */
+ public Map<String, byte[]> trustRootCertList = null;
+
+ /**
+ * Set by the subscription server, updated every time the configuration is updated by
+ * the subscription server.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ public int updateIdentifier = Integer.MIN_VALUE;
+
+ /**
+ * The priority of the credential.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ public int credentialPriority = Integer.MIN_VALUE;
+
+ /**
+ * The time this subscription is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long subscriptionCreationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The time this subscription will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long subscriptionExpirationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The type of the subscription. This is defined by the provider and the value is provider
+ * specific.
+ */
+ public String subscriptionType = null;
+
+ /**
+ * The time period for usage statistics accumulation. A value of zero means that usage
+ * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+ * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+ */
+ public long usageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+
+ /**
+ * The time at which usage statistic accumulation begins. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long usageLimitStartTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited data usage.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long usageLimitDataLimit = Long.MIN_VALUE;
+
+ /**
+ * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited time usage.
+ */
+ public long usageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+
+
+ /**
* Constructor for creating PasspointConfiguration with default values.
*/
public PasspointConfiguration() {}
@@ -47,17 +152,34 @@ public final class PasspointConfiguration implements Parcelable {
* @param source The source to copy from
*/
public PasspointConfiguration(PasspointConfiguration source) {
- if (source != null) {
- if (source.homeSp != null) {
- homeSp = new HomeSP(source.homeSp);
- }
- if (source.credential != null) {
- credential = new Credential(source.credential);
- }
- if (source.policy != null) {
- policy = new Policy(source.policy);
- }
+ if (source == null) {
+ return;
}
+
+ if (source.homeSp != null) {
+ homeSp = new HomeSP(source.homeSp);
+ }
+ if (source.credential != null) {
+ credential = new Credential(source.credential);
+ }
+ if (source.policy != null) {
+ policy = new Policy(source.policy);
+ }
+ if (source.trustRootCertList != null) {
+ trustRootCertList = Collections.unmodifiableMap(source.trustRootCertList);
+ }
+ if (source.subscriptionUpdate != null) {
+ subscriptionUpdate = new UpdateParameter(source.subscriptionUpdate);
+ }
+ updateIdentifier = source.updateIdentifier;
+ credentialPriority = source.credentialPriority;
+ subscriptionCreationTimeInMs = source.subscriptionCreationTimeInMs;
+ subscriptionExpirationTimeInMs = source.subscriptionExpirationTimeInMs;
+ subscriptionType = source.subscriptionType;
+ usageLimitDataLimit = source.usageLimitDataLimit;
+ usageLimitStartTimeInMs = source.usageLimitStartTimeInMs;
+ usageLimitTimeLimitInMinutes = source.usageLimitTimeLimitInMinutes;
+ usageLimitUsageTimePeriodInMinutes = source.usageLimitUsageTimePeriodInMinutes;
}
@Override
@@ -70,6 +192,17 @@ public final class PasspointConfiguration implements Parcelable {
dest.writeParcelable(homeSp, flags);
dest.writeParcelable(credential, flags);
dest.writeParcelable(policy, flags);
+ dest.writeParcelable(subscriptionUpdate, flags);
+ writeTrustRootCerts(dest, trustRootCertList);
+ dest.writeInt(updateIdentifier);
+ dest.writeInt(credentialPriority);
+ dest.writeLong(subscriptionCreationTimeInMs);
+ dest.writeLong(subscriptionExpirationTimeInMs);
+ dest.writeString(subscriptionType);
+ dest.writeLong(usageLimitUsageTimePeriodInMinutes);
+ dest.writeLong(usageLimitStartTimeInMs);
+ dest.writeLong(usageLimitDataLimit);
+ dest.writeLong(usageLimitTimeLimitInMinutes);
}
@Override
@@ -82,9 +215,21 @@ public final class PasspointConfiguration implements Parcelable {
}
PasspointConfiguration that = (PasspointConfiguration) thatObject;
return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
- && (credential == null ? that.credential == null :
- credential.equals(that.credential))
- && (policy == null) ? that.policy == null : policy.equals(that.policy);
+ && (credential == null ? that.credential == null
+ : credential.equals(that.credential))
+ && (policy == null) ? that.policy == null : policy.equals(that.policy)
+ && (subscriptionUpdate == null) ? that.subscriptionUpdate == null
+ : subscriptionUpdate.equals(that.subscriptionUpdate)
+ && isTrustRootCertListEquals(trustRootCertList, that.trustRootCertList)
+ && updateIdentifier == that.updateIdentifier
+ && credentialPriority == that.credentialPriority
+ && subscriptionCreationTimeInMs == that.subscriptionCreationTimeInMs
+ && subscriptionExpirationTimeInMs == that.subscriptionExpirationTimeInMs
+ && TextUtils.equals(subscriptionType, that.subscriptionType)
+ && usageLimitUsageTimePeriodInMinutes == that.usageLimitUsageTimePeriodInMinutes
+ && usageLimitStartTimeInMs == that.usageLimitStartTimeInMs
+ && usageLimitDataLimit == that.usageLimitDataLimit
+ && usageLimitTimeLimitInMinutes == that .usageLimitTimeLimitInMinutes;
}
/**
@@ -102,6 +247,34 @@ public final class PasspointConfiguration implements Parcelable {
if (policy != null && !policy.validate()) {
return false;
}
+ if (subscriptionUpdate != null && !subscriptionUpdate.validate()) {
+ return false;
+ }
+ if (trustRootCertList != null) {
+ for (Map.Entry<String, byte[]> entry : trustRootCertList.entrySet()) {
+ String url = entry.getKey();
+ byte[] certFingerprint = entry.getValue();
+ if (TextUtils.isEmpty(url)) {
+ Log.d(TAG, "Empty URL");
+ return false;
+ }
+ if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "URL bytes exceeded the max: "
+ + url.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (certFingerprint == null) {
+ Log.d(TAG, "Fingerprint not specified");
+ return false;
+ }
+ if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + certFingerprint.length);
+ return false;
+ }
+ }
+ }
return true;
}
@@ -113,11 +286,87 @@ public final class PasspointConfiguration implements Parcelable {
config.homeSp = in.readParcelable(null);
config.credential = in.readParcelable(null);
config.policy = in.readParcelable(null);
+ config.subscriptionUpdate = in.readParcelable(null);
+ config.trustRootCertList = readTrustRootCerts(in);
+ config.updateIdentifier = in.readInt();
+ config.credentialPriority = in.readInt();
+ config.subscriptionCreationTimeInMs = in.readLong();
+ config.subscriptionExpirationTimeInMs = in.readLong();
+ config.subscriptionType = in.readString();
+ config.usageLimitUsageTimePeriodInMinutes = in.readLong();
+ config.usageLimitStartTimeInMs = in.readLong();
+ config.usageLimitDataLimit = in.readLong();
+ config.usageLimitTimeLimitInMinutes = in.readLong();
return config;
}
+
@Override
public PasspointConfiguration[] newArray(int size) {
return new PasspointConfiguration[size];
}
+
+ /**
+ * Helper function for reading trust root certificate info list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return The list of trust root certificate URL with the corresponding certificate
+ * fingerprint
+ */
+ private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ byte[] value = in.createByteArray();
+ trustRootCerts.put(key, value);
+ }
+ return trustRootCerts;
+ }
};
+
+ /**
+ * Helper function for writing trust root certificate information list.
+ *
+ * @param dest The Parcel to write to
+ * @param trustRootCerts The list of trust root certificate URL with the corresponding
+ * certificate fingerprint
+ */
+ private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+ if (trustRootCerts == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(trustRootCerts.size());
+ for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeByteArray(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for comparing two trust root certificate list. Cannot use Map#equals
+ * method since the value type (byte[]) doesn't override equals method.
+ *
+ * @param list1 The first trust root certificate list
+ * @param list2 The second trust root certificate list
+ * @return true if the two list are equal
+ */
+ private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+ Map<String, byte[]> list2) {
+ if (list1 == null || list2 == null) {
+ return list1 == list2;
+ }
+ if (list1.size() != list2.size()) {
+ return false;
+ }
+ for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+ if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index e557c64e17d6..22b0f977d3e8 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -133,6 +133,20 @@ public final class PPSMOParser {
private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
/**
+ * Fields under PerProviderSubscription.
+ */
+ private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+ private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+ private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+ private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
+ private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+ private static final String NODE_USAGE_LIMITS = "UsageLimits";
+ private static final String NODE_DATA_LIMIT = "DataLimit";
+ private static final String NODE_START_DATE = "StartDate";
+ private static final String NODE_TIME_LIMIT = "TimeLimit";
+ private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+ private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+ /**
* Fields under HomeSP subtree.
*/
private static final String NODE_HOMESP = "HomeSP";
@@ -378,6 +392,10 @@ public final class PPSMOParser {
* ...
* </RTPProperties>
* <Node>
+ * <NodeName>UpdateIdentifier</NodeName>
+ * <Value>...</Value>
+ * </Node>
+ * <Node>
* ...
* </Node>
* </Node>
@@ -390,11 +408,12 @@ public final class PPSMOParser {
throws ParsingException {
PasspointConfiguration config = null;
String nodeName = null;
+ int updateIdentifier = Integer.MIN_VALUE;
for (XMLNode child : node.getChildren()) {
switch (child.getTag()) {
case TAG_NODE_NAME:
if (nodeName != null) {
- throw new ParsingException("Duplicant NodeName: " + child.getText());
+ throw new ParsingException("Duplicate NodeName: " + child.getText());
}
nodeName = child.getText();
if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
@@ -402,13 +421,22 @@ public final class PPSMOParser {
}
break;
case TAG_NODE:
- // Only one PerProviderSubscription instance is expected and allowed.
- if (config != null) {
- throw new ParsingException("Multiple PPS instance");
+ // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+ // instance node. Flatten out the XML tree first by converting it to a PPS
+ // tree to reduce the complexity of the parsing code.
+ PPSNode ppsNodeRoot = buildPpsNode(child);
+ if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+ if (updateIdentifier != Integer.MIN_VALUE) {
+ throw new ParsingException("Multiple node for UpdateIdentifier");
+ }
+ updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+ } else {
+ // Only one PerProviderSubscription instance is expected and allowed.
+ if (config != null) {
+ throw new ParsingException("Multiple PPS instance");
+ }
+ config = parsePpsInstance(ppsNodeRoot);
}
- // Convert the XML tree to a PPS tree.
- PPSNode ppsInstanceRoot = buildPpsNode(child);
- config = parsePpsInstance(ppsInstanceRoot);
break;
case TAG_RT_PROPERTIES:
// Parse and verify URN stored in the RT (Run Time) Properties.
@@ -421,6 +449,9 @@ public final class PPSMOParser {
throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
}
}
+ if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+ config.updateIdentifier = updateIdentifier;
+ }
return config;
}
@@ -583,6 +614,18 @@ public final class PPSMOParser {
case NODE_POLICY:
config.policy = parsePolicy(child);
break;
+ case NODE_AAA_SERVER_TRUST_ROOT:
+ config.trustRootCertList = parseAAAServerTrustRootList(child);
+ break;
+ case NODE_SUBSCRIPTION_UPDATE:
+ config.subscriptionUpdate = parseUpdateParameter(child);
+ break;
+ case NODE_SUBSCRIPTION_PARAMETER:
+ parseSubscriptionParameter(child, config);
+ break;
+ case NODE_CREDENTIAL_PRIORITY:
+ config.credentialPriority = parseInteger(getPpsNodeValue(child));
+ break;
default:
throw new ParsingException("Unknown node: " + child.getName());
}
@@ -648,11 +691,7 @@ public final class PPSMOParser {
String[] oiStrArray = oiStr.split(",");
long[] oiArray = new long[oiStrArray.length];
for (int i = 0; i < oiStrArray.length; i++) {
- try {
- oiArray[i] = Long.parseLong(oiStrArray[i], 16);
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid OI: " + oiStrArray[i]);
- }
+ oiArray[i] = parseLong(oiStrArray[i], 16);
}
return oiArray;
}
@@ -703,11 +742,7 @@ public final class PPSMOParser {
ssid = getPpsNodeValue(child);
break;
case NODE_HESSID:
- try {
- hessid = Long.parseLong(getPpsNodeValue(child), 16);
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
- }
+ hessid = parseLong(getPpsNodeValue(child), 16);
break;
default:
throw new ParsingException("Unknown node under NetworkID instance: " +
@@ -1184,20 +1219,10 @@ public final class PPSMOParser {
networkType = getPpsNodeValue(child);
break;
case NODE_DOWNLINK_BANDWIDTH:
- try {
- downlinkBandwidth = Long.parseLong(getPpsNodeValue(child));
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid value for downlink bandwidth: "
- + getPpsNodeValue(child));
- }
+ downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
break;
case NODE_UPLINK_BANDWIDTH:
- try {
- uplinkBandwidth = Long.parseLong(getPpsNodeValue(child));
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid value for downlink bandwidth: "
- + getPpsNodeValue(child));
- }
+ uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
break;
default:
throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
@@ -1239,13 +1264,7 @@ public final class PPSMOParser {
for (PPSNode child : node.getChildren()) {
switch(child.getName()) {
case NODE_UPDATE_INTERVAL:
- try {
- updateParam.updateIntervalInMinutes =
- Long.parseLong(getPpsNodeValue(child));
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid value for update interval: "
- + getPpsNodeValue(child));
- }
+ updateParam.updateIntervalInMinutes = parseLong(getPpsNodeValue(child), 10);
break;
case NODE_UPDATE_METHOD:
updateParam.updateMethod = getPpsNodeValue(child);
@@ -1262,7 +1281,7 @@ public final class PPSMOParser {
updateParam.base64EncodedPassword = usernamePassword.second;
break;
case NODE_TRUST_ROOT:
- Pair<String, byte[]> trustRoot = parseUpdateTrustRoot(child);
+ Pair<String, byte[]> trustRoot = parseTrustRoot(child);
updateParam.trustRootCertUrl = trustRoot.first;
updateParam.trustRootCertSha256Fingerprint = trustRoot.second;
break;
@@ -1312,16 +1331,19 @@ public final class PPSMOParser {
}
/**
- * Parse the trust root parameters associated with policy or subscription update.
+ * Parse the trust root parameters associated with policy update, subscription update, or AAA
+ * server trust root.
+ *
* This contained configurations under either
* PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
- * PerProviderSubscription/SubscriptionUpdate/TrustRoot subtree.
+ * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+ * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
*
* @param node PPSNode representing the root of the TrustRoot subtree
* @return Pair of Certificate URL and fingerprint
* @throws ParsingException
*/
- private static Pair<String, byte[]> parseUpdateTrustRoot(PPSNode node)
+ private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
throws ParsingException {
if (node.isLeaf()) {
throw new ParsingException("Leaf node not expected for TrustRoot");
@@ -1450,6 +1472,97 @@ public final class PPSMOParser {
}
/**
+ * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+ * subtree
+ * @return Map of certificate URL with the corresponding certificate fingerprint
+ * @throws ParsingException
+ */
+ private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+ }
+ Map<String, byte[]> certList = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, byte[]> certTuple = parseTrustRoot(child);
+ certList.put(certTuple.first, certTuple.second);
+ }
+ return certList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+ * subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ config.subscriptionCreationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_EXPIRATION_DATE:
+ config.subscriptionExpirationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_TYPE_OF_SUBSCRIPTION:
+ config.subscriptionType = getPpsNodeValue(child);
+ break;
+ case NODE_USAGE_LIMITS:
+ parseUsageLimits(child, config);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SubscriptionParameter"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+ * subtree.
+ *
+ * @param node PPSNode representing the root of
+ * PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsageLimits");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_DATA_LIMIT:
+ config.usageLimitDataLimit = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_START_DATE:
+ config.usageLimitStartTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_TIME_LIMIT:
+ config.usageLimitTimeLimitInMinutes = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_USAGE_TIME_PERIOD:
+ config.usageLimitUsageTimePeriodInMinutes =
+ parseLong(getPpsNodeValue(child), 10);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsageLimits"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
* Convert a hex string to a byte array.
*
* @param str String containing hex values
@@ -1505,6 +1618,21 @@ public final class PPSMOParser {
}
/**
+ * Parse a string representing a long integer.
+ *
+ * @param value String of long integer value
+ * @return long
+ * @throws ParsingException
+ */
+ private static long parseLong(String value, int radix) throws ParsingException {
+ try {
+ return Long.parseLong(value, radix);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid long integer value: " + value);
+ }
+ }
+
+ /**
* Convert a List<Long> to a primitive long array long[].
*
* @param list List to be converted
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 7f36cdbf5cd6..7f2d95de95e9 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -8,6 +8,10 @@
</Type>
</RTProperties>
<Node>
+ <NodeName>UpdateIdentifier</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
<NodeName>i001</NodeName>
<Node>
<NodeName>HomeSP</NodeName>
@@ -297,6 +301,99 @@
<Value>23</Value>
</Node>
</Node>
+ <Node>
+ <NodeName>CredentialPriority</NodeName>
+ <Value>99</Value>
+ </Node>
+ <Node>
+ <NodeName>AAAServerTrustRoot</NodeName>
+ <Node>
+ <NodeName>a001</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>server1.trust.root.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>SSP-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>RoamingPartner</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>subscription.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>subscriptionUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>subscriptionPass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>subscription.update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionParameter</NodeName>
+ <Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-03-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TypeOfSubscription</NodeName>
+ <Value>Gold</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageLimits</NodeName>
+ <Node>
+ <NodeName>DataLimit</NodeName>
+ <Value>921890</Value>
+ </Node>
+ <Node>
+ <NodeName>StartDate</NodeName>
+ <Value>2016-12-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TimeLimit</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageTimePeriod</NodeName>
+ <Value>99910</Value>
+ </Node>
+ </Node>
+ </Node>
</Node>
</Node>
</MgmtTree>
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index a386d2a51abb..1eb08e0d3b58 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -30,7 +30,9 @@ import android.util.Base64;
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -38,6 +40,8 @@ import java.util.HashMap;
*/
@SmallTest
public class PasspointConfigurationTest {
+ private static final int MAX_URL_BYTES = 1023;
+ private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
/**
* Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -111,11 +115,25 @@ public class PasspointConfigurationTest {
policy.policyUpdate.base64EncodedPassword =
Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
- policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+ policy.policyUpdate.trustRootCertSha256Fingerprint =
+ new byte[CERTIFICATE_FINGERPRINT_BYTES];
return policy;
}
+ private static UpdateParameter createSubscriptionUpdate() {
+ UpdateParameter subUpdate = new UpdateParameter();
+ subUpdate.updateIntervalInMinutes = 9021;
+ subUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+ subUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+ subUpdate.serverUri = "subscription.update.com";
+ subUpdate.username = "subUsername";
+ subUpdate.base64EncodedPassword =
+ Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT);
+ subUpdate.trustRootCertUrl = "subscription.trust.cert.com";
+ subUpdate.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_FINGERPRINT_BYTES];
+ return subUpdate;
+ }
/**
* Helper function for creating a {@link PasspointConfiguration} for testing.
*
@@ -126,6 +144,21 @@ public class PasspointConfigurationTest {
config.homeSp = createHomeSp();
config.credential = createCredential();
config.policy = createPolicy();
+ config.subscriptionUpdate = createSubscriptionUpdate();
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("trustRoot.cert1.com",
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ config.trustRootCertList.put("trustRoot.cert2.com",
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ config.updateIdentifier = 1;
+ config.credentialPriority = 120;
+ config.subscriptionCreationTimeInMs = 231200;
+ config.subscriptionExpirationTimeInMs = 2134232;
+ config.subscriptionType = "Gold";
+ config.usageLimitUsageTimePeriodInMinutes = 3600;
+ config.usageLimitStartTimeInMs = 124214213;
+ config.usageLimitDataLimit = 14121;
+ config.usageLimitTimeLimitInMinutes = 78912;
return config;
}
@@ -202,6 +235,31 @@ public class PasspointConfigurationTest {
}
/**
+ * Verify parcel read/write for a configuration that doesn't contain subscription update.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutSubscriptionUpdate() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.subscriptionUpdate = null;
+ verifyParcel(config);
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain trust root certificate
+ * list.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutTrustRootCertList() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.trustRootCertList = null;
+ verifyParcel(config);
+ }
+
+ /**
* Verify that a default/empty configuration is invalid.
*
* @throws Exception
@@ -261,6 +319,60 @@ public class PasspointConfigurationTest {
}
/**
+ * Verify that a configuration without subscription update is valid, since subscription
+ * update configurations are optional (applied for Hotspot 2.0 Release only).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithoutSubscriptionUpdate() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.subscriptionUpdate = null;
+ assertTrue(config.validate());
+ }
+
+ /**
+ * Verify that a configuration with a trust root certificate URL exceeding the max size
+ * is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithInvalidTrustRootCertUrl() throws Exception {
+ PasspointConfiguration config = createConfig();
+ byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+ Arrays.fill(rawUrlBytes, (byte) 'a');
+ config.trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ assertFalse(config.validate());
+ }
+
+ /**
+ * Verify that a configuration with an invalid trust root certificate fingerprint is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithInvalidTrustRootCertFingerprint() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", null);
+ assertFalse(config.validate());
+ }
+
+ /**
* Verify that copy constructor works when pass in a null source.
*
* @throws Exception
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
index e9b41a95e6d7..055204ce5b03 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -85,7 +85,38 @@ public class PPSMOParserTest {
* @return {@link PasspointConfiguration}
*/
private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
PasspointConfiguration config = new PasspointConfiguration();
+ config.updateIdentifier = 12;
+ config.credentialPriority = 99;
+
+ // AAA Server trust root.
+ config.trustRootCertList = new HashMap<>();
+ byte[] certFingerprint = new byte[32];
+ Arrays.fill(certFingerprint, (byte) 0x1f);
+ config.trustRootCertList.put("server1.trust.root.com", certFingerprint);
+
+ // Subscription update.
+ config.subscriptionUpdate = new UpdateParameter();
+ config.subscriptionUpdate.updateIntervalInMinutes = 120;
+ config.subscriptionUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+ config.subscriptionUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+ config.subscriptionUpdate.serverUri = "subscription.update.com";
+ config.subscriptionUpdate.username = "subscriptionUser";
+ config.subscriptionUpdate.base64EncodedPassword = "subscriptionPass";
+ config.subscriptionUpdate.trustRootCertUrl = "subscription.update.cert.com";
+ config.subscriptionUpdate.trustRootCertSha256Fingerprint = new byte[32];
+ Arrays.fill(config.subscriptionUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
+ // Subscription parameters.
+ config.subscriptionCreationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
+ config.subscriptionExpirationTimeInMs = format.parse("2016-03-01T10:00:00Z").getTime();
+ config.subscriptionType = "Gold";
+ config.usageLimitDataLimit = 921890;
+ config.usageLimitStartTimeInMs = format.parse("2016-12-01T10:00:00Z").getTime();
+ config.usageLimitTimeLimitInMinutes = 120;
+ config.usageLimitUsageTimePeriodInMinutes = 99910;
// HomeSP configuration.
config.homeSp = new HomeSP();
@@ -101,7 +132,6 @@ public class PPSMOParserTest {
config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"};
// Credential configuration.
- DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
config.credential = new Credential();
config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
@@ -161,8 +191,7 @@ public class PPSMOParserTest {
}
/**
- * Parse and verify all supported fields under PPS MO tree (currently only fields under
- * HomeSP and Credential subtree).
+ * Parse and verify all supported fields under PPS MO tree.
*
* @throws Exception
*/