summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorPeter Qiu <zqiu@google.com>2016-12-05 16:11:37 -0800
committerPeter Qiu <zqiu@google.com>2016-12-15 15:37:19 -0800
commit74339de52d7066f22771d914e698da503232c107 (patch)
treef38b4466b773e40a6ddfb6eec9c2bdc2084542e9 /service
parentfa04a81daf829e6e5c099c9a249b8dd8dd112102 (diff)
hotspot2: ANQP elements cleanup Part 1
Cleanup and add unit tests for the following ANQP elements (and the underlying classes used by those elements): - HSFriendNameElement - IPAddressTypeAvailabilityElement - RoamingConsortiumElement - VenueNameElement The cleanup included using a static #parse function for parsing raw bytes into an element object, the new ByteBufferReader APIs for reading integer and string from ByteBuffer, and documented possible runtime exceptions. Additional changes include: - remove the unnecessary setting of byte order for the ByteBuffer, since we're not using the ByteBuffer's APIs for reading integer values (all reads are either byte or byte array). - remove the unused functions in ANQPFactory More ANQP elements cleanup will be done in the upcoming CLs. Bug: 33000864 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I6da918c83722d5c0ca7a2374ff5fa5f630cdea6d
Diffstat (limited to 'service')
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java26
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java34
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java253
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java70
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java60
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java135
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java70
-rw-r--r--service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java75
8 files changed, 428 insertions, 295 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
index 1699625e3..3c6237fb2 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
@@ -19,17 +19,17 @@ package com.android.server.wifi.hotspot2;
import android.util.Base64;
import android.util.Log;
+import com.android.server.wifi.WifiNative;
import com.android.server.wifi.hotspot2.anqp.ANQPElement;
import com.android.server.wifi.hotspot2.anqp.ANQPFactory;
import com.android.server.wifi.hotspot2.anqp.Constants;
-import com.android.server.wifi.WifiNative;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -122,10 +122,9 @@ public class PasspointEventHandler {
if (element != null) {
elements.put(element.getID(), element);
}
- }
- catch (ProtocolException pe) {
+ } catch (ProtocolException | BufferUnderflowException e) {
Log.e(Utils.hs2LogTag(PasspointEventHandler.class),
- "Failed to parse ANQP: " + pe);
+ "Failed to parse ANQP: " + e);
}
}
return elements;
@@ -184,14 +183,9 @@ public class PasspointEventHandler {
Log.d(Utils.hs2LogTag(getClass()),
String.format("Successful ANQP response for %012x: %s",
bssid, elements));
- }
- catch (IOException ioe) {
+ } catch (IOException | BufferUnderflowException e) {
Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
- ioe.toString() + ": " + bssData);
- }
- catch (RuntimeException rte) {
- Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
- rte.toString() + ": " + bssData, rte);
+ e.toString() + ": " + bssData);
}
}
mCallbacks.onANQPResponse(bssid, elements);
@@ -312,10 +306,12 @@ public class PasspointEventHandler {
"Failed to parse hex string");
return null;
}
+ // Wrap the payload inside a ByteBuffer.
+ ByteBuffer buffer = ByteBuffer.wrap(payload);
+
return Constants.getANQPElementID(elementType) != null ?
- ANQPFactory.buildElement(ByteBuffer.wrap(payload), elementType, payload.length) :
- ANQPFactory.buildHS20Element(elementType,
- ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN));
+ ANQPFactory.buildElement(elementType, buffer) :
+ ANQPFactory.buildHS20Element(elementType, buffer);
}
private byte[] retrieveIcon(IconEvent iconEvent) throws IOException {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java b/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
index a759b0619..9f26675ad 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
@@ -6,10 +6,7 @@ import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
import com.android.server.wifi.hotspot2.anqp.HSConnectionCapabilityElement;
import com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement;
import com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement;
-import com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement.IPv4Availability;
-import com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement.IPv6Availability;
-import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
@@ -21,10 +18,8 @@ public class PasspointMatchInfo implements Comparable<PasspointMatchInfo> {
private final ScanDetail mScanDetail;
private final int mScore;
- private static final Map<IPv4Availability, Integer> sIP4Scores =
- new EnumMap<>(IPv4Availability.class);
- private static final Map<IPv6Availability, Integer> sIP6Scores =
- new EnumMap<>(IPv6Availability.class);
+ private static final Map<Integer, Integer> sIP4Scores = new HashMap<>();
+ private static final Map<Integer, Integer> sIP6Scores = new HashMap<>();
private static final Map<Integer, Map<Integer, Integer>> sPortScores = new HashMap<>();
@@ -46,19 +41,18 @@ public class PasspointMatchInfo implements Comparable<PasspointMatchInfo> {
sAntScores.put(NetworkDetail.Ant.Wildcard, 1);
sAntScores.put(NetworkDetail.Ant.TestOrExperimental, 0);
- sIP4Scores.put(IPv4Availability.NotAvailable, 0);
- sIP4Scores.put(IPv4Availability.PortRestricted, 1);
- sIP4Scores.put(IPv4Availability.PortRestrictedAndSingleNAT, 1);
- sIP4Scores.put(IPv4Availability.PortRestrictedAndDoubleNAT, 1);
- sIP4Scores.put(IPv4Availability.Unknown, 1);
- sIP4Scores.put(IPv4Availability.Public, 2);
- sIP4Scores.put(IPv4Availability.SingleNAT, 2);
- sIP4Scores.put(IPv4Availability.DoubleNAT, 2);
-
- sIP6Scores.put(IPv6Availability.NotAvailable, 0);
- sIP6Scores.put(IPv6Availability.Reserved, 1);
- sIP6Scores.put(IPv6Availability.Unknown, 1);
- sIP6Scores.put(IPv6Availability.Available, 2);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_NOT_AVAILABLE, 0);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED, 1);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED_AND_SINGLE_NAT, 1);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT, 1);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_UNKNOWN, 1);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_PUBLIC, 2);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_SINGLE_NAT, 2);
+ sIP4Scores.put(IPAddressTypeAvailabilityElement.IPV4_DOUBLE_NAT, 2);
+
+ sIP6Scores.put(IPAddressTypeAvailabilityElement.IPV6_NOT_AVAILABLE, 0);
+ sIP6Scores.put(IPAddressTypeAvailabilityElement.IPV6_UNKNOWN, 1);
+ sIP6Scores.put(IPAddressTypeAvailabilityElement.IPV6_AVAILABLE, 2);
Map<Integer, Integer> tcpMap = new HashMap<>();
tcpMap.put(20, 1);
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java
index 2f4430f67..6944a6e00 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java
@@ -3,15 +3,11 @@ package com.android.server.wifi.hotspot2.anqp;
import com.android.server.wifi.hotspot2.NetworkDetail;
import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
/**
* Factory to build a collection of 802.11u ANQP elements from a byte buffer.
@@ -73,192 +69,79 @@ public class ANQPFactory {
return querySet;
}
- public static ByteBuffer buildQueryRequest(Set<Constants.ANQPElementType> elements,
- ByteBuffer target) {
- List<Constants.ANQPElementType> list = new ArrayList<Constants.ANQPElementType>(elements);
- Collections.sort(list);
-
- ListIterator<Constants.ANQPElementType> elementIterator = list.listIterator();
-
- target.order(ByteOrder.LITTLE_ENDIAN);
- target.putShort((short) Constants.ANQP_QUERY_LIST);
- int lenPos = target.position();
- target.putShort((short) 0);
-
- while (elementIterator.hasNext()) {
- Integer id = Constants.getANQPElementID(elementIterator.next());
- if (id != null) {
- target.putShort(id.shortValue());
- } else {
- elementIterator.previous();
- break;
- }
- }
- target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
-
- // Start a new vendor specific element for HS2.0 elements:
- if (elementIterator.hasNext()) {
- target.putShort((short) Constants.ANQP_VENDOR_SPEC);
- int vsLenPos = target.position();
- target.putShort((short) 0);
-
- target.putInt(Constants.HS20_PREFIX);
- target.put((byte) Constants.HS_QUERY_LIST);
- target.put((byte) 0);
-
- while (elementIterator.hasNext()) {
- Constants.ANQPElementType elementType = elementIterator.next();
- Integer id = Constants.getHS20ElementID(elementType);
- if (id == null) {
- throw new RuntimeException("Unmapped ANQPElementType: " + elementType);
+ /**
+ * Build an ANQP element from the pass-in byte buffer.
+ *
+ * Note: Each Hotspot 2.0 Release 2 element will be wrapped inside a Vendor Specific element
+ * in the ANQP response from the AP. However, the lower layer (e.g. wpa_supplicant) should
+ * already take care of parsing those elements out of Vendor Specific elements. To be safe,
+ * we will parse the Vendor Specific elements for non-Hotspot 2.0 Release elements or in
+ * the case they're not parsed by the lower layer.
+ *
+ * @param infoID The ANQP element type
+ * @param payload The buffer to read from
+ * @return {@link com.android.server.wifi.hotspot2.anqp.ANQPElement}
+ * @throws BufferUnderflowException
+ * @throws ProtocolException
+ */
+ public static ANQPElement buildElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ switch (infoID) {
+ case ANQPVenueName:
+ return VenueNameElement.parse(payload);
+ case ANQPRoamingConsortium:
+ return RoamingConsortiumElement.parse(payload);
+ case ANQPIPAddrAvailability:
+ return IPAddressTypeAvailabilityElement.parse(payload);
+ case ANQPNAIRealm:
+ return new NAIRealmElement(infoID, payload);
+ case ANQP3GPPNetwork:
+ return new ThreeGPPNetworkElement(infoID, payload);
+ case ANQPDomName:
+ return new DomainNameElement(infoID, payload);
+ case ANQPVendorSpec:
+ if (payload.remaining() > 5) {
+ int oi = payload.getInt();
+ if (oi != Constants.HS20_PREFIX) {
+ return null;
+ }
+ int subType = payload.get() & Constants.BYTE_MASK;
+ Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
+ if (hs20ID == null) {
+ throw new ProtocolException("Bad HS20 info ID: " + subType);
+ }
+ payload.get(); // Skip the reserved octet
+ return buildHS20Element(hs20ID, payload);
} else {
- target.put(id.byteValue());
+ return new GenericBlobElement(infoID, payload);
}
- }
- target.putShort(vsLenPos,
- (short) (target.position() - vsLenPos - Constants.BYTES_IN_SHORT));
- }
-
- target.flip();
- return target;
- }
-
- public static ByteBuffer buildHomeRealmRequest(List<String> realmNames, ByteBuffer target) {
- target.order(ByteOrder.LITTLE_ENDIAN);
- target.putShort((short) Constants.ANQP_VENDOR_SPEC);
- int lenPos = target.position();
- target.putShort((short) 0);
-
- target.putInt(Constants.HS20_PREFIX);
- target.put((byte) Constants.HS_NAI_HOME_REALM_QUERY);
- target.put((byte) 0);
-
- target.put((byte) realmNames.size());
- for (String realmName : realmNames) {
- target.put((byte) Constants.UTF8_INDICATOR);
- byte[] octets = realmName.getBytes(StandardCharsets.UTF_8);
- target.put((byte) octets.length);
- target.put(octets);
- }
- target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
-
- target.flip();
- return target;
- }
-
- public static ByteBuffer buildIconRequest(String fileName, ByteBuffer target) {
- target.order(ByteOrder.LITTLE_ENDIAN);
- target.putShort((short) Constants.ANQP_VENDOR_SPEC);
- int lenPos = target.position();
- target.putShort((short) 0);
-
- target.putInt(Constants.HS20_PREFIX);
- target.put((byte) Constants.HS_ICON_REQUEST);
- target.put((byte) 0);
-
- target.put(fileName.getBytes(StandardCharsets.UTF_8));
- target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
-
- target.flip();
- return target;
- }
-
- public static List<ANQPElement> parsePayload(ByteBuffer payload) throws ProtocolException {
- payload.order(ByteOrder.LITTLE_ENDIAN);
- List<ANQPElement> elements = new ArrayList<ANQPElement>();
- while (payload.hasRemaining()) {
- elements.add(buildElement(payload));
- }
- return elements;
- }
-
- private static ANQPElement buildElement(ByteBuffer payload) throws ProtocolException {
- if (payload.remaining() < 4)
- throw new ProtocolException("Runt payload: " + payload.remaining());
-
- int infoIDNumber = payload.getShort() & Constants.SHORT_MASK;
- Constants.ANQPElementType infoID = Constants.mapANQPElement(infoIDNumber);
- if (infoID == null) {
- throw new ProtocolException("Bad info ID: " + infoIDNumber);
- }
- int length = payload.getShort() & Constants.SHORT_MASK;
-
- if (payload.remaining() < length) {
- throw new ProtocolException("Truncated payload: " +
- payload.remaining() + " vs " + length);
- }
- return buildElement(payload, infoID, length);
- }
-
- public static ANQPElement buildElement(ByteBuffer payload, Constants.ANQPElementType infoID,
- int length) throws ProtocolException {
- try {
- ByteBuffer elementPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
- payload.position(payload.position() + length);
- elementPayload.limit(elementPayload.position() + length);
-
- switch (infoID) {
- case ANQPVenueName:
- return new VenueNameElement(infoID, elementPayload);
- case ANQPRoamingConsortium:
- return new RoamingConsortiumElement(infoID, elementPayload);
- case ANQPIPAddrAvailability:
- return new IPAddressTypeAvailabilityElement(infoID, elementPayload);
- case ANQPNAIRealm:
- return new NAIRealmElement(infoID, elementPayload);
- case ANQP3GPPNetwork:
- return new ThreeGPPNetworkElement(infoID, elementPayload);
- case ANQPDomName:
- return new DomainNameElement(infoID, elementPayload);
- case ANQPVendorSpec:
- if (elementPayload.remaining() > 5) {
- int oi = elementPayload.getInt();
- if (oi != Constants.HS20_PREFIX) {
- return null;
- }
- int subType = elementPayload.get() & Constants.BYTE_MASK;
- Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
- if (hs20ID == null) {
- throw new ProtocolException("Bad HS20 info ID: " + subType);
- }
- elementPayload.get(); // Skip the reserved octet
- return buildHS20Element(hs20ID, elementPayload);
- } else {
- return new GenericBlobElement(infoID, elementPayload);
- }
- default:
- throw new ProtocolException("Unknown element ID: " + infoID);
- }
- } catch (ProtocolException e) {
- throw e;
- } catch (Exception e) {
- // TODO: remove this catch-all for exceptions, once the element parsing code
- // has been thoroughly unit tested. b/30562650
- throw new ProtocolException("Unknown parsing error", e);
+ default:
+ throw new ProtocolException("Unknown element ID: " + infoID);
}
}
+ /**
+ * Build a Hotspot 2.0 Release 2 ANQP element from the pass-in byte buffer.
+ *
+ * @param infoID The ANQP element ID
+ * @param payload The buffer to read from
+ * @return {@link com.android.server.wifi.hotspot2.anqp.ANQPElement}
+ * @throws BufferUnderflowException
+ * @throws ProtocolException
+ */
public static ANQPElement buildHS20Element(Constants.ANQPElementType infoID,
- ByteBuffer payload) throws ProtocolException {
- try {
- switch (infoID) {
- case HSFriendlyName:
- return new HSFriendlyNameElement(infoID, payload);
- case HSWANMetrics:
- return new HSWanMetricsElement(infoID, payload);
- case HSConnCapability:
- return new HSConnectionCapabilityElement(infoID, payload);
- case HSOSUProviders:
- return new RawByteElement(infoID, payload);
- default:
- return null;
- }
- } catch (ProtocolException e) {
- throw e;
- } catch (Exception e) {
- // TODO: remove this catch-all for exceptions, once the element parsing code
- // has been thoroughly unit tested. b/30562650
- throw new ProtocolException("Unknown parsing error", e);
+ ByteBuffer payload) throws ProtocolException {
+ switch (infoID) {
+ case HSFriendlyName:
+ return HSFriendlyNameElement.parse(payload);
+ case HSWANMetrics:
+ return new HSWanMetricsElement(infoID, payload);
+ case HSConnCapability:
+ return new HSConnectionCapabilityElement(infoID, payload);
+ case HSOSUProviders:
+ return new RawByteElement(infoID, payload);
+ default:
+ throw new ProtocolException("Unknown element ID: " + infoID);
}
}
}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java
index 3ba170e42..c6794c86e 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java
@@ -1,7 +1,11 @@
package com.android.server.wifi.hotspot2.anqp;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -9,20 +13,55 @@ import java.util.List;
/**
* The Operator Friendly Name vendor specific ANQP Element,
* Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.3
+ * section 4.3.
+ *
+ * Format:
+ *
+ * | Operator Name Duple #1 (optional) | ...
+ * variable
+ *
+ * | Operator Name Duple #N (optional) |
+ * variable
*/
public class HSFriendlyNameElement extends ANQPElement {
- private final List<I18Name> mNames;
+ /**
+ * Maximum length for an Operator Name. Refer to Hotspot 2.0 (Release 2) Technical
+ * Specification section 4.3 for more info.
+ */
+ @VisibleForTesting
+ public static final int MAXIMUM_OPERATOR_NAME_LENGTH = 252;
- public HSFriendlyNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
- throws ProtocolException {
- super(infoID);
+ private final List<I18Name> mNames;
- mNames = new ArrayList<I18Name>();
+ @VisibleForTesting
+ public HSFriendlyNameElement(List<I18Name> names) {
+ super(Constants.ANQPElementType.HSFriendlyName);
+ mNames = names;
+ }
+ /**
+ * Parse a HSFriendlyNameElement from the given buffer.
+ *
+ * @param payload The buffer to read from
+ * @return {@link HSFriendlyNameElement}
+ * @throws BufferUnderflowException
+ * @throws ProtocolException
+ */
+ public static HSFriendlyNameElement parse(ByteBuffer payload)
+ throws ProtocolException {
+ List<I18Name> names = new ArrayList<I18Name>();
while (payload.hasRemaining()) {
- mNames.add(new I18Name(payload));
+ I18Name name = I18Name.parse(payload);
+ // Verify that the number of bytes for the operator name doesn't exceed the max
+ // allowed.
+ int textBytes = name.getText().getBytes(StandardCharsets.UTF_8).length;
+ if (textBytes > MAXIMUM_OPERATOR_NAME_LENGTH) {
+ throw new ProtocolException("Operator Name exceeds the maximum allowed "
+ + textBytes);
+ }
+ names.add(name);
}
+ return new HSFriendlyNameElement(names);
}
public List<I18Name> getNames() {
@@ -30,6 +69,23 @@ public class HSFriendlyNameElement extends ANQPElement {
}
@Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof HSFriendlyNameElement)) {
+ return false;
+ }
+ HSFriendlyNameElement that = (HSFriendlyNameElement) thatObject;
+ return mNames.equals(that.mNames);
+ }
+
+ @Override
+ public int hashCode() {
+ return mNames.hashCode();
+ }
+
+ @Override
public String toString() {
return "HSFriendlyName{" +
"mNames=" + mNames +
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java b/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java
index 81ebdfa2a..3d44b0beb 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java
@@ -16,19 +16,20 @@
package com.android.server.wifi.hotspot2.anqp;
-import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wifi.ByteBufferReader;
import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
* A generic Internationalized name field used in the Operator Friendly Name ANQP element
- * (see HS2.0 R2 Spec 4.2), and the Venue Name ANQP element (see 802.11-2012 8.4.4.4).
+ * (see HS2.0 R2 Spec 4.2) and the Venue Name ANQP element (see 802.11-2012 8.4.4.4).
*
* Format:
*
@@ -36,26 +37,16 @@ import java.util.Locale;
* 1 3 variable
*/
public class I18Name {
+ @VisibleForTesting
+ public static final int LANGUAGE_CODE_LENGTH = 3;
+
+ @VisibleForTesting
+ public static final int MINIMUM_LENGTH = LANGUAGE_CODE_LENGTH;
+
private final String mLanguage;
private final Locale mLocale;
private final String mText;
- public I18Name(ByteBuffer payload) throws ProtocolException {
- if (payload.remaining() < Constants.LANG_CODE_LENGTH + 1) {
- throw new ProtocolException("Truncated I18Name payload of length "
- + payload.remaining());
- }
- int length = payload.get() & BYTE_MASK;
- if (length < Constants.LANG_CODE_LENGTH || length > payload.remaining()) {
- throw new ProtocolException("Invalid I18Name length field value " + length);
- }
- mLanguage = ByteBufferReader.readString(
- payload, Constants.LANG_CODE_LENGTH, StandardCharsets.US_ASCII).trim();
- mLocale = Locale.forLanguageTag(mLanguage);
- mText = ByteBufferReader.readString(payload, length - Constants.LANG_CODE_LENGTH,
- StandardCharsets.UTF_8);
- }
-
@VisibleForTesting
public I18Name(String language, Locale locale, String text) {
mLanguage = language;
@@ -63,6 +54,34 @@ public class I18Name {
mText = text;
}
+ /**
+ * Parse a I18Name from the given buffer.
+ *
+ * @param payload The byte buffer to read from
+ * @return {@link I18Name}
+ * @throws BufferUnderflowException
+ * @throws ProtocolException
+ */
+ public static I18Name parse(ByteBuffer payload) throws ProtocolException {
+ // Retrieve the length field.
+ int length = payload.get() & 0xFF;
+
+ // Check for the minimum required length.
+ if (length < MINIMUM_LENGTH) {
+ throw new ProtocolException("Invalid length: " + length);
+ }
+
+ // Read the language string.
+ String language = ByteBufferReader.readString(
+ payload, LANGUAGE_CODE_LENGTH, StandardCharsets.US_ASCII).trim();
+ Locale locale = Locale.forLanguageTag(language);
+
+ // Read the text string.
+ String text = ByteBufferReader.readString(payload, length - LANGUAGE_CODE_LENGTH,
+ StandardCharsets.UTF_8);
+ return new I18Name(language, locale, text);
+ }
+
public String getLanguage() {
return mLanguage;
}
@@ -80,12 +99,13 @@ public class I18Name {
if (this == thatObject) {
return true;
}
- if (thatObject == null || getClass() != thatObject.getClass()) {
+ if (!(thatObject instanceof I18Name)) {
return false;
}
I18Name that = (I18Name) thatObject;
- return mLanguage.equals(that.mLanguage) && mText.equals(that.mText);
+ return TextUtils.equals(mLanguage, that.mLanguage)
+ && TextUtils.equals(mText, that.mText);
}
@Override
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java
index 3f9737ded..ed8f8c19f 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java
@@ -1,48 +1,145 @@
package com.android.server.wifi.hotspot2.anqp;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.ProtocolException;
import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
/**
* The IP Address Type availability ANQP Element, IEEE802.11-2012 section 8.4.4.9
+ *
+ * Format:
+ *
+ * | IP Address |
+ * 1
+ * b0 b7
+ * | IPv6 Address | IPv4 Address |
+ * 2 bits 6 bits
+ *
+ * IPv4 Address field values:
+ * 0 - Address type not available
+ * 1 - Public IPv4 address available
+ * 2 - Port-restricted IPv4 address available
+ * 3 - Single NATed private IPv4 address available
+ * 4 - Single NATed private IPv4 address available
+ * 5 - Port-restricted IPv4 address and single NATed IPv4 address available
+ * 6 - Port-restricted IPv4 address and double NATed IPv4 address available
+ * 7 - Availability of the address type is not known
+ *
+ * IPv6 Address field values:
+ * 0 - Address type not available
+ * 1 - Address type not available
+ * 2 - Availability of the address type not known
+ *
*/
public class IPAddressTypeAvailabilityElement extends ANQPElement {
- public enum IPv4Availability {
- NotAvailable, Public, PortRestricted, SingleNAT, DoubleNAT,
- PortRestrictedAndSingleNAT, PortRestrictedAndDoubleNAT, Unknown
+ @VisibleForTesting
+ public static final int EXPECTED_BUFFER_LENGTH = 1;
+
+ /**
+ * Constants for IPv4 availability.
+ */
+ public static final int IPV4_NOT_AVAILABLE = 0;
+ public static final int IPV4_PUBLIC = 1;
+ public static final int IPV4_PORT_RESTRICTED = 2;
+ public static final int IPV4_SINGLE_NAT = 3;
+ public static final int IPV4_DOUBLE_NAT = 4;
+ public static final int IPV4_PORT_RESTRICTED_AND_SINGLE_NAT = 5;
+ public static final int IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT = 6;
+ public static final int IPV4_UNKNOWN = 7;
+ private static final Set<Integer> IPV4_AVAILABILITY = new HashSet<Integer>();
+ static {
+ IPV4_AVAILABILITY.add(IPV4_NOT_AVAILABLE);
+ IPV4_AVAILABILITY.add(IPV4_PUBLIC);
+ IPV4_AVAILABILITY.add(IPV4_PORT_RESTRICTED);
+ IPV4_AVAILABILITY.add(IPV4_SINGLE_NAT);
+ IPV4_AVAILABILITY.add(IPV4_DOUBLE_NAT);
+ IPV4_AVAILABILITY.add(IPV4_PORT_RESTRICTED_AND_SINGLE_NAT);
+ IPV4_AVAILABILITY.add(IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT);
+ }
+
+ /**
+ * Constants for IPv6 availability.
+ */
+ public static final int IPV6_NOT_AVAILABLE = 0;
+ public static final int IPV6_AVAILABLE = 1;
+ public static final int IPV6_UNKNOWN = 2;
+ private static final Set<Integer> IPV6_AVAILABILITY = new HashSet<Integer>();
+ static {
+ IPV6_AVAILABILITY.add(IPV6_NOT_AVAILABLE);
+ IPV6_AVAILABILITY.add(IPV6_AVAILABLE);
+ IPV6_AVAILABILITY.add(IPV6_UNKNOWN);
}
- public enum IPv6Availability {NotAvailable, Available, Unknown, Reserved}
+ private static final int IPV4_AVAILABILITY_MASK = 0x3F;
+ private static final int IPV6_AVAILABILITY_MASK = 0x3;
- private final IPv4Availability mV4Availability;
- private final IPv6Availability mV6Availability;
+ private final int mV4Availability;
+ private final int mV6Availability;
- public IPAddressTypeAvailabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ @VisibleForTesting
+ public IPAddressTypeAvailabilityElement(int v4Availability, int v6Availability) {
+ super(Constants.ANQPElementType.ANQPIPAddrAvailability);
+ mV4Availability = v4Availability;
+ mV6Availability = v6Availability;
+ }
+
+ /**
+ * Parse an IPAddressTypeAvailabilityElement from the given buffer.
+ *
+ * @param payload The byte buffer to read from
+ * @return {@link IPAddressTypeAvailabilityElement}
+ * @throws ProtocolException
+ */
+ public static IPAddressTypeAvailabilityElement parse(ByteBuffer payload)
throws ProtocolException {
- super(infoID);
+ if (payload.remaining() != EXPECTED_BUFFER_LENGTH) {
+ throw new ProtocolException("Unexpected buffer length: " + payload.remaining());
+ }
+
+ int ipField = payload.get() & 0xFF;
- if (payload.remaining() != 1)
- throw new ProtocolException("Bad IP Address Type Availability length: " +
- payload.remaining());
+ int v6Availability = ipField & IPV6_AVAILABILITY_MASK;
+ if (!IPV6_AVAILABILITY.contains(v6Availability)) {
+ v6Availability = IPV6_UNKNOWN;
+ }
- int ipField = payload.get();
- mV6Availability = IPv6Availability.values()[ipField & 0x3];
+ int v4Availability = (ipField >> 2) & IPV4_AVAILABILITY_MASK;
+ if (!IPV4_AVAILABILITY.contains(v4Availability)) {
+ v4Availability = IPV4_UNKNOWN;
+ }
- ipField = (ipField >> 2) & 0x3f;
- mV4Availability = ipField < IPv4Availability.values().length ?
- IPv4Availability.values()[ipField] :
- IPv4Availability.Unknown;
+ return new IPAddressTypeAvailabilityElement(v4Availability, v6Availability);
}
- public IPv4Availability getV4Availability() {
+ public int getV4Availability() {
return mV4Availability;
}
- public IPv6Availability getV6Availability() {
+ public int getV6Availability() {
return mV6Availability;
}
@Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof IPAddressTypeAvailabilityElement)) {
+ return false;
+ }
+ IPAddressTypeAvailabilityElement that = (IPAddressTypeAvailabilityElement) thatObject;
+ return mV4Availability == that.mV4Availability && mV6Availability == that.mV6Availability;
+ }
+
+ @Override
+ public int hashCode() {
+ return mV4Availability << 2 + mV6Availability;
+ }
+
+ @Override
public String toString() {
return "IPAddressTypeAvailability{" +
"mV4Availability=" + mV4Availability +
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java
index 430126070..a40e9d63b 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java
@@ -1,46 +1,88 @@
package com.android.server.wifi.hotspot2.anqp;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
import com.android.server.wifi.hotspot2.Utils;
import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK;
-
-import com.android.server.wifi.ByteBufferReader;
-
/**
* The Roaming Consortium ANQP Element, IEEE802.11-2012 section 8.4.4.7
+ *
+ ** Format:
+ *
+ * | OI Duple #1 (optional) | ...
+ * variable
+ *
+ * | OI Length | OI |
+ * 1 variable
+ *
*/
public class RoamingConsortiumElement extends ANQPElement {
+ @VisibleForTesting
+ public static final int MINIMUM_OI_LENGTH = Byte.BYTES;
- private final List<Long> mOis;
+ @VisibleForTesting
+ public static final int MAXIMUM_OI_LENGTH = Long.BYTES;
- public RoamingConsortiumElement(Constants.ANQPElementType infoID, ByteBuffer payload)
- throws ProtocolException {
- super(infoID);
+ private final List<Long> mOIs;
- mOis = new ArrayList<Long>();
+ @VisibleForTesting
+ public RoamingConsortiumElement(List<Long> ois) {
+ super(Constants.ANQPElementType.ANQPRoamingConsortium);
+ mOIs = ois;
+ }
+ /**
+ * Parse a VenueNameElement from the given payload.
+ *
+ * @param payload The byte buffer to read from
+ * @return {@link RoamingConsortiumElement}
+ * @throws BufferUnderflowException
+ * @throws ProtocolException
+ */
+ public static RoamingConsortiumElement parse(ByteBuffer payload)
+ throws ProtocolException {
+ List<Long> OIs = new ArrayList<Long>();
while (payload.hasRemaining()) {
- int length = payload.get() & BYTE_MASK;
- if (length > payload.remaining()) {
+ int length = payload.get() & 0xFF;
+ if (length < MINIMUM_OI_LENGTH || length > MAXIMUM_OI_LENGTH) {
throw new ProtocolException("Bad OI length: " + length);
}
- mOis.add(ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, length));
+ OIs.add(ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, length));
}
+ return new RoamingConsortiumElement(OIs);
}
public List<Long> getOIs() {
- return Collections.unmodifiableList(mOis);
+ return Collections.unmodifiableList(mOIs);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof RoamingConsortiumElement)) {
+ return false;
+ }
+ RoamingConsortiumElement that = (RoamingConsortiumElement) thatObject;
+ return mOIs.equals(that.mOIs);
+ }
+
+ @Override
+ public int hashCode() {
+ return mOIs.hashCode();
}
@Override
public String toString() {
- return "RoamingConsortium{mOis=[" + Utils.roamingConsortiumsToString(mOis) + "]}";
+ return "RoamingConsortium{mOis=[" + Utils.roamingConsortiumsToString(mOIs) + "]}";
}
}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java
index 573519080..9a4e64b39 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java
@@ -16,8 +16,12 @@
package com.android.server.wifi.hotspot2.anqp;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -27,36 +31,59 @@ import java.util.List;
*
* Format:
*
- * | Info ID | Length | Venue Info | Venue Name Duple #1 (optional) | ...
- * 2 2 2 variable
+ * | Venue Info | Venue Name Duple #1 (optional) | ...
+ * 2 variable
+ *
* | Venue Name Duple #N (optional) |
* variable
*
- * Refer to {@link com.android.server.wifi.anqp.I18Name} for the format of the Venue Name Duple
+ * Refer to {@link I18Name} for the format of the Venue Name Duple
* fields.
- *
- * Note: The payload parsed by this class already has 'Info ID' and 'Length' stripped off.
*/
public class VenueNameElement extends ANQPElement {
- private final List<I18Name> mNames;
+ @VisibleForTesting
+ public static final int VENUE_INFO_LENGTH = 2;
- public VenueNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
- throws ProtocolException {
- super(infoID);
+ /**
+ * Maximum length for a Venue Name. Refer to IEEE802.11-2012 section 8.4.4.4 for more info.
+ */
+ @VisibleForTesting
+ public static final int MAXIMUM_VENUE_NAME_LENGTH = 252;
- if (payload.remaining() < 2) {
- throw new ProtocolException("Venue Name Element cannot contain less than 2 bytes");
- }
+ private final List<I18Name> mNames;
+ @VisibleForTesting
+ public VenueNameElement(List<I18Name> names) {
+ super(Constants.ANQPElementType.ANQPVenueName);
+ mNames = names;
+ }
+
+ /**
+ * Parse a VenueNameElement from the given buffer.
+ *
+ * @param payload The byte buffer to read from
+ * @return {@link VenueNameElement}
+ * @throws BufferUnderflowException
+ * @throws ProtocolException
+ */
+ public static VenueNameElement parse(ByteBuffer payload)
+ throws ProtocolException {
// Skip the Venue Info field, which we don't use.
- for (int i = 0; i < Constants.VENUE_INFO_LENGTH; ++i) {
+ for (int i = 0; i < VENUE_INFO_LENGTH; ++i) {
payload.get();
}
- mNames = new ArrayList<I18Name>();
+ List<I18Name> names = new ArrayList<I18Name>();
while (payload.hasRemaining()) {
- mNames.add(new I18Name(payload));
+ I18Name name = I18Name.parse(payload);
+ // Verify that the number of octets for the venue name doesn't exceed the max allowed.
+ int textBytes = name.getText().getBytes(StandardCharsets.UTF_8).length;
+ if (textBytes > MAXIMUM_VENUE_NAME_LENGTH) {
+ throw new ProtocolException("Venue Name exceeds the maximum allowed " + textBytes);
+ }
+ names.add(name);
}
+ return new VenueNameElement(names);
}
public List<I18Name> getNames() {
@@ -64,7 +91,25 @@ public class VenueNameElement extends ANQPElement {
}
@Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof VenueNameElement)) {
+ return false;
+ }
+ VenueNameElement that = (VenueNameElement) thatObject;
+ return mNames.equals(that.mNames);
+ }
+
+ @Override
+ public int hashCode() {
+ return mNames.hashCode();
+ }
+
+ @Override
public String toString() {
return "VenueName{ mNames=" + mNames + "}";
}
+
}