summaryrefslogtreecommitdiffstats
path: root/wifi
diff options
context:
space:
mode:
authorVinit Deshpande <vinitd@google.com>2015-03-15 13:37:24 -0700
committerVinit Deshpande <vinitd@google.com>2015-03-15 13:37:24 -0700
commit53f87384484689dce0bcc14d9de258f299e5a075 (patch)
tree9a715aae9d09b34937ed42a888d21c07e179e422 /wifi
parent2c95c97c21a95a774e3fa68735629bf145468645 (diff)
parent2eba02a11234c9cfa6c597effb4f32349826cee4 (diff)
downloadframeworks_base-53f87384484689dce0bcc14d9de258f299e5a075.zip
frameworks_base-53f87384484689dce0bcc14d9de258f299e5a075.tar.gz
frameworks_base-53f87384484689dce0bcc14d9de258f299e5a075.tar.bz2
am "Initial Passpoint code."
merged from goog/mirror-m-wireless-internal-release 2eba02a Initial Passpoint code.
Diffstat (limited to 'wifi')
-rw-r--r--wifi/java/android/net/wifi/anqp/ANQPElement.java16
-rw-r--r--wifi/java/android/net/wifi/anqp/ANQPFactory.java213
-rw-r--r--wifi/java/android/net/wifi/anqp/CapabilityListElement.java44
-rw-r--r--wifi/java/android/net/wifi/anqp/CivicLocationElement.java201
-rw-r--r--wifi/java/android/net/wifi/anqp/Constants.java195
-rw-r--r--wifi/java/android/net/wifi/anqp/DomainNameElement.java37
-rw-r--r--wifi/java/android/net/wifi/anqp/EmergencyNumberElement.java36
-rw-r--r--wifi/java/android/net/wifi/anqp/GEOLocationElement.java314
-rw-r--r--wifi/java/android/net/wifi/anqp/GenericBlobElement.java25
-rw-r--r--wifi/java/android/net/wifi/anqp/GenericStringElement.java26
-rw-r--r--wifi/java/android/net/wifi/anqp/HSCapabilityListElement.java43
-rw-r--r--wifi/java/android/net/wifi/anqp/HSConnectionCapabilityElement.java82
-rw-r--r--wifi/java/android/net/wifi/anqp/HSFriendlyNameElement.java38
-rw-r--r--wifi/java/android/net/wifi/anqp/HSIconFileElement.java59
-rw-r--r--wifi/java/android/net/wifi/anqp/HSOsuProvidersElement.java51
-rw-r--r--wifi/java/android/net/wifi/anqp/HSWanMetricsElement.java92
-rw-r--r--wifi/java/android/net/wifi/anqp/I18Name.java45
-rw-r--r--wifi/java/android/net/wifi/anqp/IPAddressTypeAvailabilityElement.java52
-rw-r--r--wifi/java/android/net/wifi/anqp/IconInfo.java64
-rw-r--r--wifi/java/android/net/wifi/anqp/NAIRealmData.java68
-rw-r--r--wifi/java/android/net/wifi/anqp/NAIRealmElement.java49
-rw-r--r--wifi/java/android/net/wifi/anqp/NetworkAuthenticationTypeElement.java75
-rw-r--r--wifi/java/android/net/wifi/anqp/OSUProvider.java117
-rw-r--r--wifi/java/android/net/wifi/anqp/RoamingConsortiumElement.java44
-rw-r--r--wifi/java/android/net/wifi/anqp/ThreeGPPNetworkElement.java28
-rw-r--r--wifi/java/android/net/wifi/anqp/VenueNameElement.java194
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/AuthParam.java9
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/Credential.java74
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/EAP.java132
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/EAPMethod.java134
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/ExpandedEAPMethod.java66
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/InnerAuthEAP.java55
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/NonEAPInnerAuth.java59
-rw-r--r--wifi/java/android/net/wifi/anqp/eap/VendorSpecificAuth.java61
-rw-r--r--wifi/java/android/net/wifi/hotspot2/ANQPData.java26
-rw-r--r--wifi/java/android/net/wifi/hotspot2/NetworkInfo.java141
-rw-r--r--wifi/java/android/net/wifi/hotspot2/NetworkKey.java50
-rw-r--r--wifi/java/android/net/wifi/hotspot2/PasspointMatch.java12
-rw-r--r--wifi/java/android/net/wifi/hotspot2/SelectionManager.java122
-rw-r--r--wifi/java/android/net/wifi/hotspot2/pps/DomainMatcher.java70
-rw-r--r--wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java214
41 files changed, 3433 insertions, 0 deletions
diff --git a/wifi/java/android/net/wifi/anqp/ANQPElement.java b/wifi/java/android/net/wifi/anqp/ANQPElement.java
new file mode 100644
index 0000000..581f981
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/ANQPElement.java
@@ -0,0 +1,16 @@
+package android.net.wifi.anqp;
+
+/**
+ * Base class for an IEEE802.11u ANQP element.
+ */
+public abstract class ANQPElement {
+ private final Constants.ANQPElementType mID;
+
+ protected ANQPElement(Constants.ANQPElementType id) {
+ mID = id;
+ }
+
+ public Constants.ANQPElementType getID() {
+ return mID;
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/ANQPFactory.java b/wifi/java/android/net/wifi/anqp/ANQPFactory.java
new file mode 100644
index 0000000..7860926
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/ANQPFactory.java
@@ -0,0 +1,213 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+import static android.net.wifi.anqp.Constants.*;
+
+/**
+ * Factory to build a collection of 802.11u ANQP elements from a byte buffer.
+ */
+public class ANQPFactory {
+
+ public static ByteBuffer buildQueryRequest(Set<ANQPElementType> elements, ByteBuffer target) {
+ List<ANQPElementType> list = new ArrayList<ANQPElementType>(elements);
+ Collections.sort(list);
+
+ ListIterator<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();
+ }
+ }
+ target.putShort(lenPos, (short) (target.position() - lenPos - BYTES_IN_SHORT));
+
+ // Start a new vendor specific element for HS2.0 elements:
+ if (elementIterator.hasNext()) {
+ target.putShort((short) 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()) {
+ ANQPElementType elementType = elementIterator.next();
+ Integer id = Constants.getHS20ElementID(elementType);
+ if (id == null) {
+ throw new RuntimeException("Unmapped ANQPElementType: " + elementType);
+ } else {
+ target.put(id.byteValue());
+ }
+ }
+ target.putShort(vsLenPos, (short) (target.position() - vsLenPos - BYTES_IN_SHORT));
+ }
+
+ target.flip();
+ return target;
+ }
+
+ public static ByteBuffer buildHomeRealmRequest(List<String> realmNames, ByteBuffer target) {
+ target.order(ByteOrder.LITTLE_ENDIAN);
+ target.putShort((short) 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) UTF8_INDICATOR);
+ byte[] octets = realmName.getBytes(StandardCharsets.UTF_8);
+ target.put((byte) octets.length);
+ target.put(octets);
+ }
+ target.putShort(lenPos, (short) (target.position() - lenPos - BYTES_IN_SHORT));
+
+ target.flip();
+ return target;
+ }
+
+ public static ByteBuffer buildIconRequest(String fileName, ByteBuffer target) {
+ target.order(ByteOrder.LITTLE_ENDIAN);
+ target.putShort((short) 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 - 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() & SHORT_MASK;
+ ANQPElementType infoID = Constants.mapANQPElement(infoIDNumber);
+ if (infoID == null) {
+ throw new ProtocolException("Bad info ID: " + infoIDNumber);
+ }
+ int length = payload.getShort() & SHORT_MASK;
+
+ if (payload.remaining() < length) {
+ throw new ProtocolException("Truncated payload");
+ }
+
+ ByteBuffer elementPayload = payload.duplicate();
+ payload.position(payload.position() + length);
+ elementPayload.limit(elementPayload.position() + length);
+
+ switch (infoID) {
+ case ANQPCapabilityList:
+ return new CapabilityListElement(infoID, elementPayload);
+ case ANQPVenueName:
+ return new VenueNameElement(infoID, elementPayload);
+ case ANQPEmergencyNumber:
+ return new EmergencyNumberElement(infoID, elementPayload);
+ case ANQPNwkAuthType:
+ return new NetworkAuthenticationTypeElement(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 ANQPGeoLoc:
+ return new GEOLocationElement(infoID, elementPayload);
+ case ANQPCivicLoc:
+ return new CivicLocationElement(infoID, elementPayload);
+ case ANQPLocURI:
+ return new GenericStringElement(infoID, elementPayload);
+ case ANQPDomName:
+ return new DomainNameElement(infoID, elementPayload);
+ case ANQPEmergencyAlert:
+ return new GenericStringElement(infoID, elementPayload);
+ case ANQPTDLSCap:
+ return new GenericBlobElement(infoID, elementPayload);
+ case ANQPEmergencyNAI:
+ return new GenericStringElement(infoID, elementPayload);
+ case ANQPNeighborReport:
+ return new GenericBlobElement(infoID, elementPayload);
+ case ANQPVendorSpec:
+ if (elementPayload.remaining() > 5) {
+ int oi = elementPayload.getInt();
+ if (oi != Constants.HS20_PREFIX) {
+ return null;
+ }
+ int subType = elementPayload.get() & BYTE_MASK;
+ elementPayload.get();
+ return buildHS20Element(subType, elementPayload);
+ } else {
+ return new GenericBlobElement(infoID, elementPayload);
+ }
+ default:
+ throw new ProtocolException("Unknown element ID: " + infoID);
+ }
+ }
+
+ private static ANQPElement buildHS20Element(int subType, ByteBuffer payload)
+ throws ProtocolException {
+
+ ANQPElementType infoID = Constants.mapHS20Element(subType);
+
+ if (infoID == null) {
+ throw new ProtocolException("Bad HS20 info ID: " + subType);
+ }
+
+ switch (infoID) {
+ case HSCapabilityList:
+ return new HSCapabilityListElement(infoID, payload);
+ case HSFriendlyName:
+ return new HSFriendlyNameElement(infoID, payload);
+ case HSWANMetrics:
+ return new HSWanMetricsElement(infoID, payload);
+ case HSConnCapability:
+ return new HSConnectionCapabilityElement(infoID, payload);
+ case HSOperatingclass:
+ return new GenericBlobElement(infoID, payload);
+ case HSOSUProviders:
+ return new HSOsuProvidersElement(infoID, payload);
+ case HSIconFile:
+ return new HSIconFileElement(infoID, payload);
+ default:
+ throw new ProtocolException("Unknown HS20 sub type: " + subType);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/CapabilityListElement.java b/wifi/java/android/net/wifi/anqp/CapabilityListElement.java
new file mode 100644
index 0000000..00ee6d1
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/CapabilityListElement.java
@@ -0,0 +1,44 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static android.net.wifi.anqp.Constants.ANQPElementType;
+import static android.net.wifi.anqp.Constants.BYTES_IN_SHORT;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The ANQP Capability List element, 802.11-2012 section 8.4.4.3
+ */
+public class CapabilityListElement extends ANQPElement {
+ private final ANQPElementType[] mCapabilities;
+
+ public CapabilityListElement(ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+ if ((payload.remaining() & 1) == 1)
+ throw new ProtocolException("Odd length");
+ mCapabilities = new ANQPElementType[payload.remaining() / BYTES_IN_SHORT];
+
+ int index = 0;
+ while (payload.hasRemaining()) {
+ int capID = payload.getShort() & SHORT_MASK;
+ ANQPElementType capability = Constants.mapANQPElement(capID);
+ if (capability == null)
+ throw new ProtocolException("Unknown capability: " + capID);
+ mCapabilities[index++] = capability;
+ }
+ }
+
+ public ANQPElementType[] getCapabilities() {
+ return mCapabilities;
+ }
+
+ @Override
+ public String toString() {
+ return "CapabilityListElement{" +
+ "mCapabilities=" + Arrays.toString(mCapabilities) +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/CivicLocationElement.java b/wifi/java/android/net/wifi/anqp/CivicLocationElement.java
new file mode 100644
index 0000000..2cf3c75
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/CivicLocationElement.java
@@ -0,0 +1,201 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * The Civic Location ANQP Element, IEEE802.11-2012 section 8.4.4.13
+ */
+public class CivicLocationElement extends ANQPElement {
+ public enum LocationType {DHCPServer, NwkElement, Client}
+
+ private static final int GEOCONF_CIVIC4 = 99;
+ private static final int RFC4776 = 0; // Table 8-77, 1=vendor specific
+
+ private final LocationType mLocationType;
+ private final Locale mLocale;
+ private final Map<CAType, String> mValues;
+
+ public CivicLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ if (payload.remaining() < 6) {
+ throw new ProtocolException("Runt civic location:" + payload.remaining());
+ }
+
+ int locType = payload.get() & BYTE_MASK;
+ if (locType != RFC4776) {
+ throw new ProtocolException("Bad Civic location type: " + locType);
+ }
+
+ int locSubType = payload.get() & BYTE_MASK;
+ if (locSubType != GEOCONF_CIVIC4) {
+ throw new ProtocolException("Unexpected Civic location sub-type: " + locSubType +
+ " (cannot handle sub elements)");
+ }
+
+ int length = payload.get() & BYTE_MASK;
+ if (length > payload.remaining()) {
+ throw new ProtocolException("Invalid CA type length: " + length);
+ }
+
+ int what = payload.get() & BYTE_MASK;
+ mLocationType = what < LocationType.values().length ? LocationType.values()[what] : null;
+
+ mLocale = Locale.forLanguageTag(Constants.getString(payload, 2, StandardCharsets.US_ASCII));
+
+ mValues = new HashMap<CAType, String>();
+ while (payload.hasRemaining()) {
+ int caTypeNumber = payload.get() & BYTE_MASK;
+ CAType caType = s_caTypes.get(caTypeNumber);
+
+ int caValLen = payload.get() & BYTE_MASK;
+ if (caValLen > payload.remaining()) {
+ throw new ProtocolException("Bad CA value length: " + caValLen);
+ }
+ byte[] caValOctets = new byte[caValLen];
+ payload.get(caValOctets);
+
+ if (caType != null) {
+ mValues.put(caType, new String(caValOctets, StandardCharsets.UTF_8));
+ }
+ }
+ }
+
+ public LocationType getLocationType() {
+ return mLocationType;
+ }
+
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ public Map<CAType, String> getValues() {
+ return Collections.unmodifiableMap(mValues);
+ }
+
+ @Override
+ public String toString() {
+ return "CivicLocationElement{" +
+ "mLocationType=" + mLocationType +
+ ", mLocale=" + mLocale +
+ ", mValues=" + mValues +
+ '}';
+ }
+
+ private static final Map<Integer, CAType> s_caTypes = new HashMap<Integer, CAType>();
+
+ public static final int LANGUAGE = 0;
+ public static final int STATE_PROVINCE = 1;
+ public static final int COUNTY_DISTRICT = 2;
+ public static final int CITY = 3;
+ public static final int DIVISION_BOROUGH = 4;
+ public static final int BLOCK = 5;
+ public static final int STREET_GROUP = 6;
+ public static final int STREET_DIRECTION = 16;
+ public static final int LEADING_STREET_SUFFIX = 17;
+ public static final int STREET_SUFFIX = 18;
+ public static final int HOUSE_NUMBER = 19;
+ public static final int HOUSE_NUMBER_SUFFIX = 20;
+ public static final int LANDMARK = 21;
+ public static final int ADDITIONAL_LOCATION = 22;
+ public static final int NAME = 23;
+ public static final int POSTAL_ZIP = 24;
+ public static final int BUILDING = 25;
+ public static final int UNIT = 26;
+ public static final int FLOOR = 27;
+ public static final int ROOM = 28;
+ public static final int TYPE = 29;
+ public static final int POSTAL_COMMUNITY = 30;
+ public static final int PO_BOX = 31;
+ public static final int ADDITIONAL_CODE = 32;
+ public static final int SEAT_DESK = 33;
+ public static final int PRIMARY_ROAD = 34;
+ public static final int ROAD_SECTION = 35;
+ public static final int BRANCH_ROAD = 36;
+ public static final int SUB_BRANCH_ROAD = 37;
+ public static final int STREET_NAME_PRE_MOD = 38;
+ public static final int STREET_NAME_POST_MOD = 39;
+ public static final int SCRIPT = 128;
+ public static final int RESERVED = 255;
+
+ public enum CAType {
+ Language,
+ StateProvince,
+ CountyDistrict,
+ City,
+ DivisionBorough,
+ Block,
+ StreetGroup,
+ StreetDirection,
+ LeadingStreetSuffix,
+ StreetSuffix,
+ HouseNumber,
+ HouseNumberSuffix,
+ Landmark,
+ AdditionalLocation,
+ Name,
+ PostalZIP,
+ Building,
+ Unit,
+ Floor,
+ Room,
+ Type,
+ PostalCommunity,
+ POBox,
+ AdditionalCode,
+ SeatDesk,
+ PrimaryRoad,
+ RoadSection,
+ BranchRoad,
+ SubBranchRoad,
+ StreetNamePreMod,
+ StreetNamePostMod,
+ Script,
+ Reserved
+ }
+
+ static {
+ s_caTypes.put(LANGUAGE, CAType.Language);
+ s_caTypes.put(STATE_PROVINCE, CAType.StateProvince);
+ s_caTypes.put(COUNTY_DISTRICT, CAType.CountyDistrict);
+ s_caTypes.put(CITY, CAType.City);
+ s_caTypes.put(DIVISION_BOROUGH, CAType.DivisionBorough);
+ s_caTypes.put(BLOCK, CAType.Block);
+ s_caTypes.put(STREET_GROUP, CAType.StreetGroup);
+ s_caTypes.put(STREET_DIRECTION, CAType.StreetDirection);
+ s_caTypes.put(LEADING_STREET_SUFFIX, CAType.LeadingStreetSuffix);
+ s_caTypes.put(STREET_SUFFIX, CAType.StreetSuffix);
+ s_caTypes.put(HOUSE_NUMBER, CAType.HouseNumber);
+ s_caTypes.put(HOUSE_NUMBER_SUFFIX, CAType.HouseNumberSuffix);
+ s_caTypes.put(LANDMARK, CAType.Landmark);
+ s_caTypes.put(ADDITIONAL_LOCATION, CAType.AdditionalLocation);
+ s_caTypes.put(NAME, CAType.Name);
+ s_caTypes.put(POSTAL_ZIP, CAType.PostalZIP);
+ s_caTypes.put(BUILDING, CAType.Building);
+ s_caTypes.put(UNIT, CAType.Unit);
+ s_caTypes.put(FLOOR, CAType.Floor);
+ s_caTypes.put(ROOM, CAType.Room);
+ s_caTypes.put(TYPE, CAType.Type);
+ s_caTypes.put(POSTAL_COMMUNITY, CAType.PostalCommunity);
+ s_caTypes.put(PO_BOX, CAType.POBox);
+ s_caTypes.put(ADDITIONAL_CODE, CAType.AdditionalCode);
+ s_caTypes.put(SEAT_DESK, CAType.SeatDesk);
+ s_caTypes.put(PRIMARY_ROAD, CAType.PrimaryRoad);
+ s_caTypes.put(ROAD_SECTION, CAType.RoadSection);
+ s_caTypes.put(BRANCH_ROAD, CAType.BranchRoad);
+ s_caTypes.put(SUB_BRANCH_ROAD, CAType.SubBranchRoad);
+ s_caTypes.put(STREET_NAME_PRE_MOD, CAType.StreetNamePreMod);
+ s_caTypes.put(STREET_NAME_POST_MOD, CAType.StreetNamePostMod);
+ s_caTypes.put(SCRIPT, CAType.Script);
+ s_caTypes.put(RESERVED, CAType.Reserved);
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/Constants.java b/wifi/java/android/net/wifi/anqp/Constants.java
new file mode 100644
index 0000000..17af6de
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/Constants.java
@@ -0,0 +1,195 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ANQP related constants (802.11-2012)
+ */
+public class Constants {
+
+ public static final int BYTE_MASK = 0xff;
+ public static final int SHORT_MASK = 0xffff;
+ public static final long INT_MASK = 0xffffffffL;
+ public static final int BYTES_IN_SHORT = 2;
+ public static final int BYTES_IN_INT = 4;
+
+ public static final int HS20_PREFIX = 0x119a6f50; // Note that is represented as a LE int
+ public static final int UTF8_INDICATOR = 1;
+
+ public enum IconStatus {Success, FileNotFound, Unspecified}
+
+ public static final int ANQP_QUERY_LIST = 256;
+ public static final int ANQP_CAPABILITY_LIST = 257;
+ public static final int ANQP_VENUE_NAME = 258;
+ public static final int ANQP_EMERGENCY_NUMBER = 259;
+ public static final int ANQP_NWK_AUTH_TYPE = 260;
+ public static final int ANQP_ROAMING_CONSORTIUM = 261;
+ public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
+ public static final int ANQP_NAI_REALM = 263;
+ public static final int ANQP_3GPP_NETWORK = 264;
+ public static final int ANQP_GEO_LOC = 265;
+ public static final int ANQP_CIVIC_LOC = 266;
+ public static final int ANQP_LOC_URI = 267;
+ public static final int ANQP_DOM_NAME = 268;
+ public static final int ANQP_EMERGENCY_ALERT = 269;
+ public static final int ANQP_TDLS_CAP = 270;
+ public static final int ANQP_EMERGENCY_NAI = 271;
+ public static final int ANQP_NEIGHBOR_REPORT = 272;
+ public static final int ANQP_VENDOR_SPEC = 56797;
+
+ public static final int HS_QUERY_LIST = 1;
+ public static final int HS_CAPABILITY_LIST = 2;
+ public static final int HS_FRIENDLY_NAME = 3;
+ public static final int HS_WAN_METRICS = 4;
+ public static final int HS_CONN_CAPABILITY = 5;
+ public static final int HS_NAI_HOME_REALM_QUERY = 6;
+ public static final int HS_OPERATING_CLASS = 7;
+ public static final int HS_OSU_PROVIDERS = 8;
+ public static final int HS_ICON_REQUEST = 10;
+ public static final int HS_ICON_FILE = 11;
+
+ public enum ANQPElementType {
+ ANQPQueryList,
+ ANQPCapabilityList,
+ ANQPVenueName,
+ ANQPEmergencyNumber,
+ ANQPNwkAuthType,
+ ANQPRoamingConsortium,
+ ANQPIPAddrAvailability,
+ ANQPNAIRealm,
+ ANQP3GPPNetwork,
+ ANQPGeoLoc,
+ ANQPCivicLoc,
+ ANQPLocURI,
+ ANQPDomName,
+ ANQPEmergencyAlert,
+ ANQPTDLSCap,
+ ANQPEmergencyNAI,
+ ANQPNeighborReport,
+ ANQPVendorSpec,
+ HSQueryList,
+ HSCapabilityList,
+ HSFriendlyName,
+ HSWANMetrics,
+ HSConnCapability,
+ HSNAIHomeRealmQuery,
+ HSOperatingclass,
+ HSOSUProviders,
+ HSIconRequest,
+ HSIconFile
+ }
+
+ private static final Map<Integer, ANQPElementType> sAnqpMap = new HashMap<Integer, ANQPElementType>();
+ private static final Map<Integer, ANQPElementType> sHs20Map = new HashMap<Integer, ANQPElementType>();
+ private static final Map<ANQPElementType, Integer> sRevAnqpmap = new HashMap<ANQPElementType, Integer>();
+ private static final Map<ANQPElementType, Integer> sRevHs20map = new HashMap<ANQPElementType, Integer>();
+
+ static {
+ sAnqpMap.put(ANQP_QUERY_LIST, ANQPElementType.ANQPQueryList);
+ sAnqpMap.put(ANQP_CAPABILITY_LIST, ANQPElementType.ANQPCapabilityList);
+ sAnqpMap.put(ANQP_VENUE_NAME, ANQPElementType.ANQPVenueName);
+ sAnqpMap.put(ANQP_EMERGENCY_NUMBER, ANQPElementType.ANQPEmergencyNumber);
+ sAnqpMap.put(ANQP_NWK_AUTH_TYPE, ANQPElementType.ANQPNwkAuthType);
+ sAnqpMap.put(ANQP_ROAMING_CONSORTIUM, ANQPElementType.ANQPRoamingConsortium);
+ sAnqpMap.put(ANQP_IP_ADDR_AVAILABILITY, ANQPElementType.ANQPIPAddrAvailability);
+ sAnqpMap.put(ANQP_NAI_REALM, ANQPElementType.ANQPNAIRealm);
+ sAnqpMap.put(ANQP_3GPP_NETWORK, ANQPElementType.ANQP3GPPNetwork);
+ sAnqpMap.put(ANQP_GEO_LOC, ANQPElementType.ANQPGeoLoc);
+ sAnqpMap.put(ANQP_CIVIC_LOC, ANQPElementType.ANQPCivicLoc);
+ sAnqpMap.put(ANQP_LOC_URI, ANQPElementType.ANQPLocURI);
+ sAnqpMap.put(ANQP_DOM_NAME, ANQPElementType.ANQPDomName);
+ sAnqpMap.put(ANQP_EMERGENCY_ALERT, ANQPElementType.ANQPEmergencyAlert);
+ sAnqpMap.put(ANQP_TDLS_CAP, ANQPElementType.ANQPTDLSCap);
+ sAnqpMap.put(ANQP_EMERGENCY_NAI, ANQPElementType.ANQPEmergencyNAI);
+ sAnqpMap.put(ANQP_NEIGHBOR_REPORT, ANQPElementType.ANQPNeighborReport);
+ sAnqpMap.put(ANQP_VENDOR_SPEC, ANQPElementType.ANQPVendorSpec);
+
+ sHs20Map.put(HS_QUERY_LIST, ANQPElementType.HSQueryList);
+ sHs20Map.put(HS_CAPABILITY_LIST, ANQPElementType.HSCapabilityList);
+ sHs20Map.put(HS_FRIENDLY_NAME, ANQPElementType.HSFriendlyName);
+ sHs20Map.put(HS_WAN_METRICS, ANQPElementType.HSWANMetrics);
+ sHs20Map.put(HS_CONN_CAPABILITY, ANQPElementType.HSConnCapability);
+ sHs20Map.put(HS_NAI_HOME_REALM_QUERY, ANQPElementType.HSNAIHomeRealmQuery);
+ sHs20Map.put(HS_OPERATING_CLASS, ANQPElementType.HSOperatingclass);
+ sHs20Map.put(HS_OSU_PROVIDERS, ANQPElementType.HSOSUProviders);
+ sHs20Map.put(HS_ICON_REQUEST, ANQPElementType.HSIconRequest);
+ sHs20Map.put(HS_ICON_FILE, ANQPElementType.HSIconFile);
+
+ for (Map.Entry<Integer, ANQPElementType> entry : sAnqpMap.entrySet()) {
+ sRevAnqpmap.put(entry.getValue(), entry.getKey());
+ }
+ for (Map.Entry<Integer, ANQPElementType> entry : sHs20Map.entrySet()) {
+ sRevHs20map.put(entry.getValue(), entry.getKey());
+ }
+ }
+
+ public static ANQPElementType mapANQPElement(int id) {
+ return sAnqpMap.get(id);
+ }
+
+ public static ANQPElementType mapHS20Element(int id) {
+ return sHs20Map.get(id);
+ }
+
+ public static Integer getANQPElementID(ANQPElementType elementType) {
+ return sRevAnqpmap.get(elementType);
+ }
+
+ public static Integer getHS20ElementID(ANQPElementType elementType) {
+ return sRevHs20map.get(elementType);
+ }
+
+ public static long getInteger(ByteBuffer payload, int size) {
+ byte[] octets = new byte[size];
+ payload.get(octets);
+ long value = 0;
+ for (int n = octets.length - 1; n >= 0; n--) {
+ value = (value << Byte.SIZE) | (octets[n] & BYTE_MASK);
+ }
+ return value;
+ }
+
+ public static String getPrefixedString(ByteBuffer payload, int lengthLength, Charset charset)
+ throws ProtocolException {
+ if (payload.remaining() < lengthLength) {
+ throw new ProtocolException("Runt string: " + payload.remaining());
+ }
+ return getString(payload, (int) getInteger(payload, lengthLength), charset, false);
+ }
+
+ public static String getString(ByteBuffer payload, int length, Charset charset)
+ throws ProtocolException {
+ return getString(payload, length, charset, false);
+ }
+
+ public static String getString(ByteBuffer payload, int length, Charset charset, boolean useNull)
+ throws ProtocolException {
+ if (length > payload.remaining()) {
+ throw new ProtocolException("Bad string length: " + length);
+ }
+ if (useNull && length == 0) {
+ return null;
+ }
+ byte[] octets = new byte[length];
+ return new String(octets, charset);
+ }
+
+ public static String toHexString(byte[] data) {
+ StringBuilder sb = new StringBuilder(data.length * 3);
+
+ boolean first = true;
+ for (byte b : data) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(' ');
+ }
+ sb.append(String.format("%02x", b & BYTE_MASK));
+ }
+ return sb.toString();
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/DomainNameElement.java b/wifi/java/android/net/wifi/anqp/DomainNameElement.java
new file mode 100644
index 0000000..e683b64
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/DomainNameElement.java
@@ -0,0 +1,37 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Domain Name ANQP Element, IEEE802.11-2012 section 8.4.4.15
+ */
+public class DomainNameElement extends ANQPElement {
+ private final List<String> mDomains;
+
+ public DomainNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+ mDomains = new ArrayList<String>();
+
+ while (payload.hasRemaining()) {
+ // Use latin-1 to decode for now - safe for ASCII and retains encoding
+ mDomains.add(Constants.getPrefixedString(payload, 1, StandardCharsets.ISO_8859_1));
+ }
+ }
+
+ public List<String> getDomains() {
+ return Collections.unmodifiableList(mDomains);
+ }
+
+ @Override
+ public String toString() {
+ return "DomainNameElement{" +
+ "mDomains=" + mDomains +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/EmergencyNumberElement.java b/wifi/java/android/net/wifi/anqp/EmergencyNumberElement.java
new file mode 100644
index 0000000..c7d6b56
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/EmergencyNumberElement.java
@@ -0,0 +1,36 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The Emergency Number ANQP Element, IEEE802.11-2012 section 8.4.4.5
+ */
+public class EmergencyNumberElement extends ANQPElement {
+ private final List<String> mNumbers;
+
+ public EmergencyNumberElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ mNumbers = new ArrayList<String>();
+
+ while (payload.hasRemaining()) {
+ mNumbers.add(Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8));
+ }
+ }
+
+ public List<String> getNumbers() {
+ return mNumbers;
+ }
+
+ @Override
+ public String toString() {
+ return "EmergencyNumberElement{" +
+ "mNumbers=" + mNumbers +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/GEOLocationElement.java b/wifi/java/android/net/wifi/anqp/GEOLocationElement.java
new file mode 100644
index 0000000..691fdad
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/GEOLocationElement.java
@@ -0,0 +1,314 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * Holds an AP Geospatial Location ANQP Element, as specified in IEEE802.11-2012 section
+ * 8.4.4.12.
+ * <p/>
+ * <p>
+ * Section 8.4.2.24.10 of the IEEE802.11-2012 specification refers to RFC-3825 for the format of the
+ * Geospatial location information. RFC-3825 has subsequently been obsoleted by RFC-6225 which
+ * defines the same basic binary format for the DHCPv4 payload except that a few unused bits of the
+ * Datum field have been reserved for other uses.
+ * </p>
+ * <p/>
+ * <p>
+ * RFC-3825 defines a resolution field for each of latitude, longitude and altitude as "the number
+ * of significant bits" of precision in the respective values and implies through examples and
+ * otherwise that the non-significant bits should be simply disregarded and the range of values are
+ * calculated as the numeric interval obtained by varying the range of "insignificant bits" between
+ * its extremes. As a simple example, consider the value 33 as a simple 8-bit number with three
+ * significant bits: 33 is 00100001 binary and the leading 001 are the significant bits. With the
+ * above definition, the range of numbers are [32,63] with 33 asymmetrically located at the low end
+ * of the interval. In a more realistic setting an instrument, such as a GPS, would most likely
+ * deliver measurements with a gaussian distribution around the exact value, meaning it is more
+ * reasonable to assume the value as a "center" value with a symmetric uncertainty interval.
+ * RFC-6225 redefines the "resolution" from RFC-3825 with an "uncertainty" value with these
+ * properties, which is also the definition suggested here.
+ * </p>
+ * <p/>
+ * <p>
+ * The res fields provides the resolution as the exponent to a power of two,
+ * e.g. 8 means 2^8 = +/- 256, 0 means 2^0 = +/- 1 and -7 means 2^-7 +/- 0.00781250.
+ * Unknown resolution is indicated by not setting the respective resolution field in the RealValue.
+ * </p>
+ */
+public class GEOLocationElement extends ANQPElement {
+ public enum AltitudeType {Unknown, Meters, Floors}
+
+ public enum Datum {Unknown, WGS84, NAD83Land, NAD83Water}
+
+ private static final int ELEMENT_ID = 123; // ???
+ private static final int GEO_LOCATION_LENGTH = 16;
+
+ private static final int LL_FRACTION_SIZE = 25;
+ private static final int LL_WIDTH = 34;
+ private static final int ALT_FRACTION_SIZE = 8;
+ private static final int ALT_WIDTH = 30;
+ private static final int RES_WIDTH = 6;
+ private static final int ALT_TYPE_WIDTH = 4;
+ private static final int DATUM_WIDTH = 8;
+
+ private final RealValue mLatitude;
+ private final RealValue mLongitude;
+ private final RealValue mAltitude;
+ private final AltitudeType mAltitudeType;
+ private final Datum mDatum;
+
+ public static class RealValue {
+ private final double mValue;
+ private final boolean mResolutionSet;
+ private final int mResolution;
+
+ public RealValue(double value) {
+ mValue = value;
+ mResolution = Integer.MIN_VALUE;
+ mResolutionSet = false;
+ }
+
+ public RealValue(double value, int resolution) {
+ mValue = value;
+ mResolution = resolution;
+ mResolutionSet = true;
+ }
+
+ public double getValue() {
+ return mValue;
+ }
+
+ public boolean isResolutionSet() {
+ return mResolutionSet;
+ }
+
+ public int getResolution() {
+ return mResolution;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("%f", mValue));
+ if (mResolutionSet) {
+ sb.append("+/-2^").append(mResolution);
+ }
+ return sb.toString();
+ }
+ }
+
+ public GEOLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ payload.get();
+ int locLength = payload.get() & BYTE_MASK;
+
+ if (locLength != GEO_LOCATION_LENGTH) {
+ throw new ProtocolException("GeoLocation length field value " + locLength +
+ " incorrect, expected 16");
+ }
+ if (payload.remaining() != GEO_LOCATION_LENGTH) {
+ throw new ProtocolException("Bad buffer length " + payload.remaining() +
+ ", expected 16");
+ }
+
+ ReverseBitStream reverseBitStream = new ReverseBitStream(payload);
+
+ int rawLatRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
+ double latitude =
+ fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
+
+ mLatitude = rawLatRes != 0 ?
+ new RealValue(latitude, bitsToAbsResolution(rawLatRes, LL_WIDTH,
+ LL_FRACTION_SIZE)) :
+ new RealValue(latitude);
+
+ int rawLonRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
+ double longitude =
+ fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
+
+ mLongitude = rawLonRes != 0 ?
+ new RealValue(longitude, bitsToAbsResolution(rawLonRes, LL_WIDTH,
+ LL_FRACTION_SIZE)) :
+ new RealValue(longitude);
+
+ int altType = (int) reverseBitStream.sliceOff(ALT_TYPE_WIDTH);
+ mAltitudeType = altType < AltitudeType.values().length ?
+ AltitudeType.values()[altType] :
+ AltitudeType.Unknown;
+
+ int rawAltRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
+ double altitude = fixToFloat(reverseBitStream.sliceOff(ALT_WIDTH), ALT_FRACTION_SIZE,
+ ALT_WIDTH);
+
+ mAltitude = rawAltRes != 0 ?
+ new RealValue(altitude, bitsToAbsResolution(rawAltRes, ALT_WIDTH,
+ ALT_FRACTION_SIZE)) :
+ new RealValue(altitude);
+
+ int datumValue = (int) reverseBitStream.sliceOff(DATUM_WIDTH);
+ mDatum = datumValue < Datum.values().length ? Datum.values()[datumValue] : Datum.Unknown;
+ }
+
+ public RealValue getLatitude() {
+ return mLatitude;
+ }
+
+ public RealValue getLongitude() {
+ return mLongitude;
+ }
+
+ public RealValue getAltitude() {
+ return mAltitude;
+ }
+
+ public AltitudeType getAltitudeType() {
+ return mAltitudeType;
+ }
+
+ public Datum getDatum() {
+ return mDatum;
+ }
+
+ @Override
+ public String toString() {
+ return "GEOLocationElement{" +
+ "mLatitude=" + mLatitude +
+ ", mLongitude=" + mLongitude +
+ ", mAltitude=" + mAltitude +
+ ", mAltitudeType=" + mAltitudeType +
+ ", mDatum=" + mDatum +
+ '}';
+ }
+
+ private static class ReverseBitStream {
+
+ private final byte[] mOctets;
+ private int mBitoffset;
+
+ private ReverseBitStream(ByteBuffer octets) {
+ mOctets = new byte[octets.remaining()];
+ octets.get(mOctets);
+ }
+
+ private long sliceOff(int bits) {
+ final int bn = mBitoffset + bits;
+ int remaining = bits;
+ long value = 0;
+
+ while (mBitoffset < bn) {
+ int sbit = mBitoffset & 0x7; // Bit #0 is MSB, inclusive
+ int octet = mBitoffset >>> 3;
+
+ // Copy the minimum of what's to the right of sbit
+ // and how much more goes to the target
+ int width = Math.min(Byte.SIZE - sbit, remaining);
+
+ value = (value << width) | getBits(mOctets[octet], sbit, width);
+
+ mBitoffset += width;
+ remaining -= width;
+ }
+
+ System.out.printf(" - Sliced off %d bits: %x\n", bits, value);
+ return value;
+ }
+
+ private static int getBits(byte b, int b0, int width) {
+ int mask = (1 << width) - 1;
+ return (b >> (Byte.SIZE - b0 - width)) & mask;
+ }
+ }
+
+ private static class BitStream {
+
+ private final byte[] data;
+ private int bitOffset; // bit 0 is MSB of data[0]
+
+ private BitStream(int octets) {
+ data = new byte[octets];
+ }
+
+ private void append(long value, int width) {
+ System.out.printf("Appending %x:%d\n", value, width);
+ for (int sbit = width - 1; sbit >= 0; ) {
+ int b0 = bitOffset >>> 3;
+ int dbit = bitOffset & 0x7;
+
+ int shr = sbit - 7 + dbit;
+ int dmask = 0xff >>> dbit;
+
+ if (shr >= 0) {
+ data[b0] = (byte) ((data[b0] & ~dmask) | ((value >>> shr) & dmask));
+ bitOffset += Byte.SIZE - dbit;
+ sbit -= Byte.SIZE - dbit;
+ } else {
+ data[b0] = (byte) ((data[b0] & ~dmask) | ((value << -shr) & dmask));
+ bitOffset += sbit + 1;
+ sbit = -1;
+ }
+ }
+ }
+
+ private byte[] getOctets() {
+ return data;
+ }
+ }
+
+ static double fixToFloat(long value, int fractionSize, int width) {
+ long sign = 1L << (width - 1);
+ if ((value & sign) != 0) {
+ value = -value;
+ return -(double) (value & (sign - 1)) / (double) (1L << fractionSize);
+ } else {
+ return (double) (value & (sign - 1)) / (double) (1L << fractionSize);
+ }
+ }
+
+ private static long floatToFix(double value, int fractionSize, int width) {
+ return Math.round(value * (1L << fractionSize)) & ((1L << width) - 1);
+ }
+
+ private static final double LOG2_FACTOR = 1.0 / Math.log(2.0);
+
+ /**
+ * Convert an absolute variance value into absolute resolution representation,
+ * where the variance = 2^resolution.
+ *
+ * @param variance The absolute variance
+ * @return the absolute resolution.
+ */
+ private static int getResolution(double variance) {
+ return (int) Math.ceil(Math.log(variance) * LOG2_FACTOR);
+ }
+
+ /**
+ * Convert an absolute resolution, into the "number of significant bits" for the given fixed
+ * point notation as defined in RFC-3825 and refined in RFC-6225.
+ *
+ * @param resolution absolute resolution given as 2^resolution.
+ * @param fieldWidth Full width of the fixed point number used to represent the value.
+ * @param fractionBits Number of fraction bits in the fixed point number used to represent the
+ * value.
+ * @return The number of "significant bits".
+ */
+ private static int absResolutionToBits(int resolution, int fieldWidth, int fractionBits) {
+ return fieldWidth - fractionBits - 1 - resolution;
+ }
+
+ /**
+ * Convert the protocol definition of "number of significant bits" into an absolute resolution.
+ *
+ * @param bits The number of "significant bits" from the binary protocol.
+ * @param fieldWidth Full width of the fixed point number used to represent the value.
+ * @param fractionBits Number of fraction bits in the fixed point number used to represent the
+ * value.
+ * @return The absolute resolution given as 2^resolution.
+ */
+ private static int bitsToAbsResolution(long bits, int fieldWidth, int fractionBits) {
+ return fieldWidth - fractionBits - 1 - (int) bits;
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/GenericBlobElement.java b/wifi/java/android/net/wifi/anqp/GenericBlobElement.java
new file mode 100644
index 0000000..ecdf939
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/GenericBlobElement.java
@@ -0,0 +1,25 @@
+package android.net.wifi.anqp;
+
+import java.nio.ByteBuffer;
+
+/**
+ * ANQP Element to hold a raw, unparsed, octet blob
+ */
+public class GenericBlobElement extends ANQPElement {
+ private final byte[] mData;
+
+ public GenericBlobElement(Constants.ANQPElementType infoID, ByteBuffer payload) {
+ super(infoID);
+ mData = new byte[payload.remaining()];
+ payload.get(mData);
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ @Override
+ public String toString() {
+ return "Element ID " + getID() + ": " + Constants.toHexString(mData);
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/GenericStringElement.java b/wifi/java/android/net/wifi/anqp/GenericStringElement.java
new file mode 100644
index 0000000..d304444
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/GenericStringElement.java
@@ -0,0 +1,26 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * ANQP Element to hold a generic (UTF-8 decoded) character string
+ */
+public class GenericStringElement extends ANQPElement {
+ private final String mText;
+
+ public GenericStringElement(Constants.ANQPElementType infoID, ByteBuffer payload) throws ProtocolException {
+ super(infoID);
+ mText = Constants.getString(payload, payload.remaining(), StandardCharsets.UTF_8);
+ }
+
+ public String getM_text() {
+ return mText;
+ }
+
+ @Override
+ public String toString() {
+ return "Element ID " + getID() + ": '" + mText + "'";
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/HSCapabilityListElement.java b/wifi/java/android/net/wifi/anqp/HSCapabilityListElement.java
new file mode 100644
index 0000000..367dde5
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/HSCapabilityListElement.java
@@ -0,0 +1,43 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * The HS Capability list vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.2
+ */
+public class HSCapabilityListElement extends ANQPElement {
+ private final Constants.ANQPElementType[] mCapabilities;
+
+ public HSCapabilityListElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ mCapabilities = new Constants.ANQPElementType[payload.remaining()];
+
+ int index = 0;
+ while (payload.hasRemaining()) {
+ int capID = payload.get() & BYTE_MASK;
+ Constants.ANQPElementType capability = Constants.mapANQPElement(capID);
+ if (capability == null)
+ throw new ProtocolException("Unknown capability: " + capID);
+ mCapabilities[index++] = capability;
+ }
+ }
+
+ public Constants.ANQPElementType[] getCapabilities() {
+ return mCapabilities;
+ }
+
+ @Override
+ public String toString() {
+ return "HSCapabilityListElement{" +
+ "mCapabilities=" + Arrays.toString(mCapabilities) +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/HSConnectionCapabilityElement.java b/wifi/java/android/net/wifi/anqp/HSConnectionCapabilityElement.java
new file mode 100644
index 0000000..8937859
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/HSConnectionCapabilityElement.java
@@ -0,0 +1,82 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The Connection Capability vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.5
+ */
+public class HSConnectionCapabilityElement extends ANQPElement {
+
+ public enum ProtoStatus {Closed, Open, Unknown}
+
+ private final List<ProtocolTuple> mStatusList;
+
+ public static class ProtocolTuple {
+ private final int mProtocol;
+ private final int mPort;
+ private final ProtoStatus mStatus;
+
+ private ProtocolTuple(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 4) {
+ throw new ProtocolException("Runt protocol tuple: " + payload.remaining());
+ }
+ mProtocol = payload.get() & BYTE_MASK;
+ mPort = payload.getShort() & SHORT_MASK;
+ int statusNumber = payload.get() & BYTE_MASK;
+ mStatus = statusNumber < ProtoStatus.values().length ?
+ ProtoStatus.values()[statusNumber] :
+ null;
+ }
+
+ public int getProtocol() {
+ return mProtocol;
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+ public ProtoStatus getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "ProtocolTuple{" +
+ "mProtocol=" + mProtocol +
+ ", mPort=" + mPort +
+ ", mStatus=" + mStatus +
+ '}';
+ }
+ }
+
+ public HSConnectionCapabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ mStatusList = new ArrayList<ProtocolTuple>();
+ while (payload.hasRemaining()) {
+ mStatusList.add(new ProtocolTuple(payload));
+ }
+ }
+
+ public List<ProtocolTuple> getStatusList() {
+ return Collections.unmodifiableList(mStatusList);
+ }
+
+ @Override
+ public String toString() {
+ return "HSConnectionCapabilityElement{" +
+ "mStatusList=" + mStatusList +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/HSFriendlyNameElement.java b/wifi/java/android/net/wifi/anqp/HSFriendlyNameElement.java
new file mode 100644
index 0000000..0fe3692
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/HSFriendlyNameElement.java
@@ -0,0 +1,38 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+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
+ */
+public class HSFriendlyNameElement extends ANQPElement {
+ private final List<I18Name> mNames;
+
+ public HSFriendlyNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ mNames = new ArrayList<I18Name>();
+
+ while (payload.hasRemaining()) {
+ mNames.add(new I18Name(payload));
+ }
+ }
+
+ public List<I18Name> getNames() {
+ return Collections.unmodifiableList(mNames);
+ }
+
+ @Override
+ public String toString() {
+ return "HSFriendlyNameElement{" +
+ "mNames=" + mNames +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/HSIconFileElement.java b/wifi/java/android/net/wifi/anqp/HSIconFileElement.java
new file mode 100644
index 0000000..791ae93
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/HSIconFileElement.java
@@ -0,0 +1,59 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The Icon Binary File vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.11
+ */
+public class HSIconFileElement extends ANQPElement {
+
+ public enum StatusCode {Success, FileNotFound, Unspecified}
+
+ private final StatusCode mStatusCode;
+ private final String mType;
+ private final byte[] mIconData;
+
+ public HSIconFileElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ if (payload.remaining() < 4) {
+ throw new ProtocolException("Truncated icon file: " + payload.remaining());
+ }
+
+ int statusID = payload.get() & BYTE_MASK;
+ mStatusCode = statusID < StatusCode.values().length ? StatusCode.values()[statusID] : null;
+ mType = Constants.getString(payload, 1, StandardCharsets.US_ASCII);
+
+ int dataLength = payload.getShort() & SHORT_MASK;
+ mIconData = new byte[dataLength];
+ payload.get(mIconData);
+ }
+
+ public StatusCode getStatusCode() {
+ return mStatusCode;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ public byte[] getIconData() {
+ return mIconData;
+ }
+
+ @Override
+ public String toString() {
+ return "HSIconFileElement{" +
+ "mStatusCode=" + mStatusCode +
+ ", mType='" + mType + '\'' +
+ ", mIconData=" + mIconData.length + " bytes }";
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/HSOsuProvidersElement.java b/wifi/java/android/net/wifi/anqp/HSOsuProvidersElement.java
new file mode 100644
index 0000000..61e28c5
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/HSOsuProvidersElement.java
@@ -0,0 +1,51 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * The OSU Providers List vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8
+ */
+public class HSOsuProvidersElement extends ANQPElement {
+ private final String mSSID;
+ private final List<OSUProvider> mProviders;
+
+ public HSOsuProvidersElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ mSSID = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+ int providerCount = payload.get() & BYTE_MASK;
+
+ mProviders = new ArrayList<OSUProvider>(providerCount);
+
+ while (providerCount > 0) {
+ mProviders.add(new OSUProvider(payload));
+ providerCount--;
+ }
+ }
+
+ public String getSSID() {
+ return mSSID;
+ }
+
+ public List<OSUProvider> getProviders() {
+ return Collections.unmodifiableList(mProviders);
+ }
+
+ @Override
+ public String toString() {
+ return "HSOsuProvidersElement{" +
+ "mSSID='" + mSSID + '\'' +
+ ", mProviders=" + mProviders +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/HSWanMetricsElement.java b/wifi/java/android/net/wifi/anqp/HSWanMetricsElement.java
new file mode 100644
index 0000000..1f792a3
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/HSWanMetricsElement.java
@@ -0,0 +1,92 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+import static android.net.wifi.anqp.Constants.INT_MASK;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The WAN Metrics vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.4
+ */
+public class HSWanMetricsElement extends ANQPElement {
+
+ public enum LinkStatus {Reserved, Up, Down, Test}
+
+ private final LinkStatus mStatus;
+ private final boolean mSymmetric;
+ private final boolean mCapped;
+ private final long mDlSpeed;
+ private final long mUlSpeed;
+ private final int mDlLoad;
+ private final int mUlLoad;
+ private final int mLMD;
+
+ public HSWanMetricsElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ if (payload.remaining() != 13) {
+ throw new ProtocolException("Bad WAN metrics length: " + payload.remaining());
+ }
+
+ int status = payload.get() & BYTE_MASK;
+ mStatus = LinkStatus.values()[status & 0x03];
+ mSymmetric = (status & 0x04) != 0;
+ mCapped = (status & 0x08) != 0;
+ mDlSpeed = payload.getInt() & INT_MASK;
+ mUlSpeed = payload.getInt() & INT_MASK;
+ mDlLoad = payload.get() & BYTE_MASK;
+ mUlLoad = payload.get() & BYTE_MASK;
+ mLMD = payload.getShort() & SHORT_MASK;
+ }
+
+ public LinkStatus getStatus() {
+ return mStatus;
+ }
+
+ public boolean isSymmetric() {
+ return mSymmetric;
+ }
+
+ public boolean isCapped() {
+ return mCapped;
+ }
+
+ public long getDlSpeed() {
+ return mDlSpeed;
+ }
+
+ public long getUlSpeed() {
+ return mUlSpeed;
+ }
+
+ public int getDlLoad() {
+ return mDlLoad;
+ }
+
+ public int getUlLoad() {
+ return mUlLoad;
+ }
+
+ public int getLMD() {
+ return mLMD;
+ }
+
+ @Override
+ public String toString() {
+ return "HSWanMetricsElement{" +
+ "mStatus=" + mStatus +
+ ", mSymmetric=" + mSymmetric +
+ ", mCapped=" + mCapped +
+ ", mDlSpeed=" + mDlSpeed +
+ ", mUlSpeed=" + mUlSpeed +
+ ", mDlLoad=" + mDlLoad +
+ ", mUlLoad=" + mUlLoad +
+ ", mLMD=" + mLMD +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/I18Name.java b/wifi/java/android/net/wifi/anqp/I18Name.java
new file mode 100644
index 0000000..0ceeb18
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/I18Name.java
@@ -0,0 +1,45 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * A generic Internationalized name used in ANQP elements as specified in 802.11-2012 and
+ * "Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00"
+ */
+public class I18Name {
+ private static final int LANG_CODE_LENGTH = 3;
+
+ private final Locale mLocale;
+ private final String mText;
+
+ public I18Name(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 4) {
+ throw new ProtocolException("Truncated I18Name: " + payload.remaining());
+ }
+ int nameLength = payload.get() & BYTE_MASK;
+ if (nameLength < 3) {
+ throw new ProtocolException("Runt I18Name: " + nameLength);
+ }
+ String language = Constants.getString(payload, LANG_CODE_LENGTH, StandardCharsets.US_ASCII);
+ mLocale = Locale.forLanguageTag(language);
+ mText = Constants.getString(payload, nameLength - LANG_CODE_LENGTH, StandardCharsets.UTF_8);
+ }
+
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ @Override
+ public String toString() {
+ return mText + ':' + mLocale.getLanguage();
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/IPAddressTypeAvailabilityElement.java b/wifi/java/android/net/wifi/anqp/IPAddressTypeAvailabilityElement.java
new file mode 100644
index 0000000..8c8d388
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/IPAddressTypeAvailabilityElement.java
@@ -0,0 +1,52 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+/**
+ * The IP Address Type availability ANQP Element, IEEE802.11-2012 section 8.4.4.9
+ */
+public class IPAddressTypeAvailabilityElement extends ANQPElement {
+ public enum IPv4Availability {
+ NotAvailable, Public, PortRestricted, SingleNATA, DoubleNAT,
+ PortRestrictedAndSingleNAT, PortRestrictedAndDoubleNAT, Unknown
+ }
+
+ public enum IPv6Availability {NotAvailable, Available, Unknown, Reserved}
+
+ private final IPv4Availability mV4Availability;
+ private final IPv6Availability mV6Availability;
+
+ public IPAddressTypeAvailabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ if (payload.remaining() != 1)
+ throw new ProtocolException("Bad IP Address Type Availability length: " +
+ payload.remaining());
+
+ int ipField = payload.get();
+ mV6Availability = IPv6Availability.values()[ipField & 0x3];
+
+ ipField = (ipField >> 2) & 0x3f;
+ mV4Availability = ipField <= IPv4Availability.values().length ?
+ IPv4Availability.values()[ipField] :
+ IPv4Availability.Unknown;
+ }
+
+ public IPv4Availability getV4Availability() {
+ return mV4Availability;
+ }
+
+ public IPv6Availability getV6Availability() {
+ return mV6Availability;
+ }
+
+ @Override
+ public String toString() {
+ return "IPAddressTypeAvailabilityElement{" +
+ "mV4Availability=" + mV4Availability +
+ ", mV6Availability=" + mV6Availability +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/IconInfo.java b/wifi/java/android/net/wifi/anqp/IconInfo.java
new file mode 100644
index 0000000..33d4c77
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/IconInfo.java
@@ -0,0 +1,64 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The Icons available OSU Providers sub field, as specified in
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8.1.4
+ */
+public class IconInfo {
+ private final int mWidth;
+ private final int mHeight;
+ private final Locale mLocale;
+ private final String mIconType;
+ private final String mFileName;
+
+ public IconInfo(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 9) {
+ throw new ProtocolException("Truncated icon meta data");
+ }
+
+ mWidth = payload.getShort() & SHORT_MASK;
+ mHeight = payload.getShort() & SHORT_MASK;
+ mLocale = Locale.forLanguageTag(Constants.getString(payload, 3, StandardCharsets.US_ASCII));
+ mIconType = Constants.getPrefixedString(payload, 1, StandardCharsets.US_ASCII);
+ mFileName = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ public String getIconType() {
+ return mIconType;
+ }
+
+ public String getFileName() {
+ return mFileName;
+ }
+
+ @Override
+ public String toString() {
+ return "IconInfo{" +
+ "mWidth=" + mWidth +
+ ", mHeight=" + mHeight +
+ ", mLocale=" + mLocale +
+ ", mIconType='" + mIconType + '\'' +
+ ", mFileName='" + mFileName + '\'' +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/NAIRealmData.java b/wifi/java/android/net/wifi/anqp/NAIRealmData.java
new file mode 100644
index 0000000..14d5aaf
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/NAIRealmData.java
@@ -0,0 +1,68 @@
+package android.net.wifi.anqp;
+
+import android.net.wifi.anqp.eap.EAPMethod;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+import static android.net.wifi.anqp.Constants.UTF8_INDICATOR;
+
+/**
+ * The NAI Realm Data ANQP sub-element, IEEE802.11-2012 section 8.4.4.10 figure 8-418
+ */
+public class NAIRealmData {
+ private final List<String> mRealms;
+ private final List<EAPMethod> mEAPMethods;
+
+ public NAIRealmData(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 5) {
+ throw new ProtocolException("Runt payload: " + payload.remaining());
+ }
+
+ int length = payload.getShort() & SHORT_MASK;
+ if (length > payload.remaining()) {
+ throw new ProtocolException("Invalid data length: " + length);
+ }
+ boolean utf8 = (payload.get() & 1) == UTF8_INDICATOR;
+
+ String realm = Constants.getPrefixedString(payload, 1, utf8 ?
+ StandardCharsets.UTF_8 :
+ StandardCharsets.US_ASCII);
+ String[] realms = realm.split(";");
+ mRealms = new ArrayList<String>();
+ for (String realmElement : realms) {
+ if (realmElement.length() > 0) {
+ mRealms.add(realmElement);
+ }
+ }
+
+ int methodCount = payload.get() & BYTE_MASK;
+ mEAPMethods = new ArrayList<EAPMethod>(methodCount);
+ while (methodCount > 0) {
+ mEAPMethods.add(new EAPMethod(payload));
+ methodCount--;
+ }
+ }
+
+ public List<String> getRealms() {
+ return Collections.unmodifiableList(mRealms);
+ }
+
+ public List<EAPMethod> getEAPMethods() {
+ return Collections.unmodifiableList(mEAPMethods);
+ }
+
+ @Override
+ public String toString() {
+ return "NAIRealmData{" +
+ "mRealms='" + mRealms + '\'' +
+ ", mEAPMethods=" + mEAPMethods +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/NAIRealmElement.java b/wifi/java/android/net/wifi/anqp/NAIRealmElement.java
new file mode 100644
index 0000000..9083518
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/NAIRealmElement.java
@@ -0,0 +1,49 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTES_IN_SHORT;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The NAI Realm ANQP Element, IEEE802.11-2012 section 8.4.4.10
+ */
+public class NAIRealmElement extends ANQPElement {
+ private final List<NAIRealmData> mRealmData;
+
+ public NAIRealmElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ if (!payload.hasRemaining()) {
+ mRealmData = Collections.emptyList();
+ return;
+ }
+
+ if (payload.remaining() < BYTES_IN_SHORT) {
+ throw new ProtocolException("Runt NAI Realm: " + payload.remaining());
+ }
+
+ int count = payload.getShort() & SHORT_MASK;
+ mRealmData = new ArrayList<NAIRealmData>(count);
+ while (count > 0) {
+ mRealmData.add(new NAIRealmData(payload));
+ count--;
+ }
+ }
+
+ public List<NAIRealmData> getRealmData() {
+ return Collections.unmodifiableList(mRealmData);
+ }
+
+ @Override
+ public String toString() {
+ return "NAIRealmElement{" +
+ "mRealmData=" + mRealmData +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/NetworkAuthenticationTypeElement.java b/wifi/java/android/net/wifi/anqp/NetworkAuthenticationTypeElement.java
new file mode 100644
index 0000000..d26c19d
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/NetworkAuthenticationTypeElement.java
@@ -0,0 +1,75 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * The Network Authentication Type ANQP Element, IEEE802.11-2012 section 8.4.4.6
+ */
+public class NetworkAuthenticationTypeElement extends ANQPElement {
+
+ private final List<NetworkAuthentication> m_authenticationTypes;
+
+ public enum NwkAuthTypeEnum {
+ TermsAndConditions,
+ OnLineEnrollment,
+ HTTPRedirection,
+ DNSRedirection,
+ Reserved
+ }
+
+ public static class NetworkAuthentication {
+ private final NwkAuthTypeEnum m_type;
+ private final String m_url;
+
+ private NetworkAuthentication(NwkAuthTypeEnum type, String url) {
+ m_type = type;
+ m_url = url;
+ }
+
+ public NwkAuthTypeEnum getType() {
+ return m_type;
+ }
+
+ public String getURL() {
+ return m_url;
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkAuthentication{" +
+ "m_type=" + m_type +
+ ", m_url='" + m_url + '\'' +
+ '}';
+ }
+ }
+
+ public NetworkAuthenticationTypeElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+
+ super(infoID);
+
+ m_authenticationTypes = new ArrayList<NetworkAuthentication>();
+
+ while (payload.hasRemaining()) {
+ int typeNumber = payload.get() & BYTE_MASK;
+ NwkAuthTypeEnum type;
+ type = typeNumber >= NwkAuthTypeEnum.values().length ?
+ NwkAuthTypeEnum.Reserved :
+ NwkAuthTypeEnum.values()[typeNumber];
+
+ m_authenticationTypes.add(new NetworkAuthentication(type,
+ Constants.getPrefixedString(payload, 2, StandardCharsets.UTF_8)));
+ }
+ }
+
+ public List<NetworkAuthentication> getAuthenticationTypes() {
+ return Collections.unmodifiableList(m_authenticationTypes);
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/OSUProvider.java b/wifi/java/android/net/wifi/anqp/OSUProvider.java
new file mode 100644
index 0000000..aead563
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/OSUProvider.java
@@ -0,0 +1,117 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+import static android.net.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * An OSU Provider, as specified in
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8.1
+ */
+public class OSUProvider {
+
+ public enum OSUMethod {OmaDm, SoapXml}
+
+ private final List<I18Name> mNames;
+ private final String mOSUServer;
+ private final List<OSUMethod> mOSUMethods;
+ private final List<IconInfo> mIcons;
+ private final String mOsuNai;
+ private final List<I18Name> mServiceDescriptions;
+
+ public OSUProvider(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 11) {
+ throw new ProtocolException("Truncated OSU provider: " + payload.remaining());
+ }
+
+ int length = payload.getShort() & SHORT_MASK;
+ int namesLength = payload.getShort() & SHORT_MASK;
+
+ ByteBuffer namesBuffer = payload.duplicate();
+ namesBuffer.limit(namesBuffer.position() + namesLength);
+ payload.position(payload.position() + namesLength);
+
+ mNames = new ArrayList<I18Name>();
+
+ while (namesBuffer.hasRemaining()) {
+ mNames.add(new I18Name(namesBuffer));
+ }
+
+ mOSUServer = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+ int methodLength = payload.get() & BYTE_MASK;
+ mOSUMethods = new ArrayList<OSUMethod>(methodLength);
+ while (methodLength > 0) {
+ int methodID = payload.get() & BYTE_MASK;
+ mOSUMethods.add(methodID < OSUMethod.values().length ?
+ OSUMethod.values()[methodID] :
+ null);
+ methodLength--;
+ }
+
+ int iconsLength = payload.getShort() & SHORT_MASK;
+ ByteBuffer iconsBuffer = payload.duplicate();
+ iconsBuffer.limit(iconsBuffer.position() + iconsLength);
+ payload.position(payload.position() + iconsLength);
+
+ mIcons = new ArrayList<IconInfo>();
+
+ while (iconsBuffer.hasRemaining()) {
+ mIcons.add(new IconInfo(iconsBuffer));
+ }
+
+ mOsuNai = Constants.getString(payload, 1, StandardCharsets.UTF_8, true);
+
+ int descriptionsLength = payload.getShort() & SHORT_MASK;
+ ByteBuffer descriptionsBuffer = payload.duplicate();
+ descriptionsBuffer.limit(descriptionsBuffer.position() + descriptionsLength);
+ payload.position(payload.position() + descriptionsLength);
+
+ mServiceDescriptions = new ArrayList<I18Name>();
+
+ while (descriptionsBuffer.hasRemaining()) {
+ mServiceDescriptions.add(new I18Name(descriptionsBuffer));
+ }
+ }
+
+ public List<I18Name> getNames() {
+ return mNames;
+ }
+
+ public String getOSUServer() {
+ return mOSUServer;
+ }
+
+ public List<OSUMethod> getOSUMethods() {
+ return mOSUMethods;
+ }
+
+ public List<IconInfo> getIcons() {
+ return mIcons;
+ }
+
+ public String getOsuNai() {
+ return mOsuNai;
+ }
+
+ public List<I18Name> getServiceDescriptions() {
+ return mServiceDescriptions;
+ }
+
+ @Override
+ public String toString() {
+ return "OSUProvider{" +
+ "mNames=" + mNames +
+ ", mOSUServer='" + mOSUServer + '\'' +
+ ", mOSUMethods=" + mOSUMethods +
+ ", mIcons=" + mIcons +
+ ", mOsuNai='" + mOsuNai + '\'' +
+ ", mServiceDescriptions=" + mServiceDescriptions +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/RoamingConsortiumElement.java b/wifi/java/android/net/wifi/anqp/RoamingConsortiumElement.java
new file mode 100644
index 0000000..ff8727e
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/RoamingConsortiumElement.java
@@ -0,0 +1,44 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+import static android.net.wifi.anqp.Constants.getInteger;
+
+/**
+ * The Roaming Consortium ANQP Element, IEEE802.11-2012 section 8.4.4.7
+ */
+public class RoamingConsortiumElement extends ANQPElement {
+
+ private final List<Long> mOis;
+
+ public RoamingConsortiumElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ mOis = new ArrayList<Long>();
+
+ while (payload.hasRemaining()) {
+ int length = payload.get() & BYTE_MASK;
+ if (length > payload.remaining()) {
+ throw new ProtocolException("Bad OI length: " + length);
+ }
+ mOis.add(getInteger(payload, length));
+ }
+ }
+
+ public List<Long> getOIs() {
+ return Collections.unmodifiableList(mOis);
+ }
+
+ @Override
+ public String toString() {
+ return "RoamingConsortiumElement{" +
+ "mOis=" + mOis +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/ThreeGPPNetworkElement.java b/wifi/java/android/net/wifi/anqp/ThreeGPPNetworkElement.java
new file mode 100644
index 0000000..57dbe77
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/ThreeGPPNetworkElement.java
@@ -0,0 +1,28 @@
+package android.net.wifi.anqp;
+
+import java.nio.ByteBuffer;
+
+/**
+ * The 3GPP Cellular Network ANQP Element, IEEE802.11-2012 section 8.4.4.11
+ */
+public class ThreeGPPNetworkElement extends ANQPElement {
+
+ private final byte[] mData;
+
+ public ThreeGPPNetworkElement(Constants.ANQPElementType infoID, ByteBuffer payload) {
+ super(infoID);
+ mData = new byte[payload.remaining()];
+ payload.get(mData);
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ @Override
+ public String toString() {
+ return "ThreeGPPNetworkElement{" +
+ "mData=" + Constants.toHexString(mData) +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/VenueNameElement.java b/wifi/java/android/net/wifi/anqp/VenueNameElement.java
new file mode 100644
index 0000000..bb71524
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/VenueNameElement.java
@@ -0,0 +1,194 @@
+package android.net.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * The Venue Name ANQP Element, IEEE802.11-2012 section 8.4.4.4
+ */
+public class VenueNameElement extends ANQPElement {
+ private final VenueGroup mGroup;
+ private final VenueType mType;
+ private final List<I18Name> mNames;
+
+ private static final Map<VenueGroup, Integer> s_groupBases =
+ new EnumMap<VenueGroup, Integer>(VenueGroup.class);
+
+ public VenueNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+ throws ProtocolException {
+ super(infoID);
+
+ if (payload.remaining() < 2)
+ throw new ProtocolException("Runt Venue Name");
+
+ int group = payload.get() & BYTE_MASK;
+ int type = payload.get() & BYTE_MASK;
+
+ if (group >= VenueGroup.values().length) {
+ mGroup = VenueGroup.Reserved;
+ mType = VenueType.Reserved;
+ } else {
+ mGroup = VenueGroup.values()[group];
+ type += s_groupBases.get(mGroup);
+ if (type >= VenueType.values().length) {
+ mType = VenueType.Reserved;
+ } else {
+ mType = VenueType.values()[type];
+ }
+ }
+
+ mNames = new ArrayList<I18Name>();
+ while (payload.hasRemaining()) {
+ mNames.add(new I18Name(payload));
+ }
+ }
+
+ public VenueGroup getGroup() {
+ return mGroup;
+ }
+
+ public VenueType getType() {
+ return mType;
+ }
+
+ public List<I18Name> getNames() {
+ return Collections.unmodifiableList(mNames);
+ }
+
+ @Override
+ public String toString() {
+ return "VenueNameElement{" +
+ "m_group=" + mGroup +
+ ", m_type=" + mType +
+ ", m_names=" + mNames +
+ '}';
+ }
+
+ public enum VenueGroup {
+ Unspecified,
+ Assembly,
+ Business,
+ Educational,
+ FactoryIndustrial,
+ Institutional,
+ Mercantile,
+ Residential,
+ Storage,
+ UtilityMiscellaneous,
+ Vehicular,
+ Outdoor,
+ Reserved
+ }
+
+ public enum VenueType {
+ Unspecified,
+
+ UnspecifiedAssembly,
+ Arena,
+ Stadium,
+ PassengerTerminal,
+ Amphitheater,
+ AmusementPark,
+ PlaceOfWorship,
+ ConventionCenter,
+ Library,
+ Museum,
+ Restaurant,
+ Theater,
+ Bar,
+ CoffeeShop,
+ ZooOrAquarium,
+ EmergencyCoordinationCenter,
+
+ UnspecifiedBusiness,
+ DoctorDentistoffice,
+ Bank,
+ FireStation,
+ PoliceStation,
+ PostOffice,
+ ProfessionalOffice,
+ ResearchDevelopmentFacility,
+ AttorneyOffice,
+
+ UnspecifiedEducational,
+ SchoolPrimary,
+ SchoolSecondary,
+ UniversityCollege,
+
+ UnspecifiedFactoryIndustrial,
+ Factory,
+
+ UnspecifiedInstitutional,
+ Hospital,
+ LongTermCareFacility,
+ AlcoholAndDrugRehabilitationCenter,
+ GroupHome,
+ PrisonJail,
+
+ UnspecifiedMercantile,
+ RetailStore,
+ GroceryMarket,
+ AutomotiveServiceStation,
+ ShoppingMall,
+ GasStation,
+
+ UnspecifiedResidential,
+ PrivateResidence,
+ HotelMotel,
+ Dormitory,
+ BoardingHouse,
+
+ UnspecifiedStorage,
+
+ UnspecifiedUtilityMiscellaneous,
+
+ AutomobileOrTruck,
+ Airplane,
+ Bus,
+ Ferry,
+ ShipOrBoat,
+ Train,
+ MotorBike,
+
+ UnspecifiedOutdoor,
+ MuniMeshNetwork,
+ CityPark,
+ RestArea,
+ TrafficControl,
+ BusStop,
+ Kiosk,
+
+ Reserved
+ }
+
+ private static final VenueType[] PerGroup =
+ {
+ VenueType.Unspecified,
+ VenueType.UnspecifiedAssembly,
+ VenueType.UnspecifiedBusiness,
+ VenueType.UnspecifiedEducational,
+ VenueType.UnspecifiedFactoryIndustrial,
+ VenueType.UnspecifiedInstitutional,
+ VenueType.UnspecifiedMercantile,
+ VenueType.UnspecifiedResidential,
+ VenueType.UnspecifiedStorage,
+ VenueType.UnspecifiedUtilityMiscellaneous,
+ VenueType.AutomobileOrTruck,
+ VenueType.UnspecifiedOutdoor,
+ null
+ };
+
+ static {
+ int index = 0;
+ for (VenueType venue : PerGroup) {
+ s_groupBases.put(VenueGroup.values()[index++], venue.ordinal());
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/AuthParam.java b/wifi/java/android/net/wifi/anqp/eap/AuthParam.java
new file mode 100644
index 0000000..9c40300
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/AuthParam.java
@@ -0,0 +1,9 @@
+package android.net.wifi.anqp.eap;
+
+/**
+ * An Authentication parameter, part of the NAI Realm ANQP element, specified in
+ * IEEE802.11-2012 section 8.4.4.10, table 8-188
+ */
+public interface AuthParam {
+ public EAP.AuthInfoID getAuthInfoID();
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/Credential.java b/wifi/java/android/net/wifi/anqp/eap/Credential.java
new file mode 100644
index 0000000..df77b89
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/Credential.java
@@ -0,0 +1,74 @@
+package android.net.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class Credential implements AuthParam {
+
+ public enum CredType {
+ Reserved,
+ SIM,
+ USIM,
+ NFC,
+ HWToken,
+ Softoken,
+ Certificate,
+ Username,
+ None,
+ Anonymous,
+ VendorSpecific}
+
+ private final EAP.AuthInfoID mAuthInfoID;
+ private final CredType mCredType;
+
+ public Credential(EAP.AuthInfoID infoID, ByteBuffer payload) throws ProtocolException {
+ mAuthInfoID = infoID;
+
+ if (payload.remaining() != 1) {
+ throw new ProtocolException("Bad length: " + payload.remaining());
+ }
+
+ int typeID = payload.get() & BYTE_MASK;
+ mCredType = typeID < CredType.values().length ?
+ CredType.values()[typeID] :
+ CredType.Reserved;
+ }
+
+ @Override
+ public EAP.AuthInfoID getAuthInfoID() {
+ return mAuthInfoID;
+ }
+
+ @Override
+ public int hashCode() {
+ return mAuthInfoID.hashCode() * 31 + mCredType.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (thatObject == this) {
+ return true;
+ } else if (thatObject == null || thatObject.getClass() != AuthParam.class) {
+ return false;
+ } else {
+ return ((Credential) thatObject).getCredType() == getCredType();
+ }
+ }
+
+ public CredType getCredType() {
+ return mCredType;
+ }
+
+ @Override
+ public String toString() {
+ return "Credential{" +
+ "mAuthInfoID=" + mAuthInfoID +
+ ", mCredType=" + mCredType +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/EAP.java b/wifi/java/android/net/wifi/anqp/eap/EAP.java
new file mode 100644
index 0000000..35d28d9
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/EAP.java
@@ -0,0 +1,132 @@
+package android.net.wifi.anqp.eap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * EAP Related constants for the ANQP NAIRealm element, IEEE802.11-2012 section 8.4.4.10
+ */
+public abstract class EAP {
+
+ private static final Map<Integer, EAPMethodID> s_eapIds = new HashMap<Integer, EAPMethodID>();
+
+ public static final int EAP_MD5 = 4;
+ public static final int EAP_OTP = 5;
+ public static final int EAP_RSA = 9;
+ public static final int EAP_KEA = 11;
+ public static final int EAP_KEA_VALIDATE = 12;
+ public static final int EAP_TLS = 13;
+ public static final int EAP_LEAP = 17;
+ public static final int EAP_SIM = 18;
+ public static final int EAP_TTLS = 21;
+ public static final int EAP_AKA = 23;
+ public static final int EAP_3Com = 24;
+ public static final int EAP_MSCHAPv2 = 26;
+ public static final int EAP_PEAP = 29;
+ public static final int EAP_POTP = 32;
+ public static final int EAP_ActiontecWireless = 35;
+ public static final int EAP_HTTPDigest = 38;
+ public static final int EAP_SPEKE = 41;
+ public static final int EAP_MOBAC = 42;
+ public static final int EAP_FAST = 43;
+ public static final int EAP_ZLXEAP = 44;
+ public static final int EAP_Link = 45;
+ public static final int EAP_PAX = 46;
+ public static final int EAP_PSK = 47;
+ public static final int EAP_SAKE = 48;
+ public static final int EAP_IKEv2 = 49;
+ public static final int EAP_AKAPrim = 50;
+ public static final int EAP_GPSK = 51;
+ public static final int EAP_PWD = 52;
+ public static final int EAP_EKE = 53;
+ public static final int EAP_TEAP = 55;
+
+ public enum EAPMethodID {
+ EAP_MD5,
+ EAP_OTP,
+ EAP_RSA,
+ EAP_KEA,
+ EAP_KEA_VALIDATE,
+ EAP_TLS,
+ EAP_LEAP,
+ EAP_SIM,
+ EAP_TTLS,
+ EAP_AKA,
+ EAP_3Com,
+ EAP_MSCHAPv2,
+ EAP_PEAP,
+ EAP_POTP,
+ EAP_ActiontecWireless,
+ EAP_HTTPDigest,
+ EAP_SPEKE,
+ EAP_MOBAC,
+ EAP_FAST,
+ EAP_ZLXEAP,
+ EAP_Link,
+ EAP_PAX,
+ EAP_PSK,
+ EAP_SAKE,
+ EAP_IKEv2,
+ EAP_AKAPrim,
+ EAP_GPSK,
+ EAP_PWD,
+ EAP_EKE,
+ EAP_TEAP
+ }
+
+ public static final int ExpandedEAPMethod = 1;
+ public static final int NonEAPInnerAuthType = 2;
+ public static final int InnerAuthEAPMethodType = 3;
+ public static final int ExpandedInnerEAPMethod = 4;
+ public static final int CredentialType = 5;
+ public static final int TunneledEAPMethodCredType = 6;
+ public static final int VendorSpecific = 221;
+
+ public enum AuthInfoID {
+ Undefined,
+ ExpandedEAPMethod,
+ NonEAPInnerAuthType,
+ InnerAuthEAPMethodType,
+ ExpandedInnerEAPMethod,
+ CredentialType,
+ TunneledEAPMethodCredType,
+ VendorSpecific
+ }
+
+ static {
+ s_eapIds.put(EAP_MD5, EAPMethodID.EAP_MD5);
+ s_eapIds.put(EAP_OTP, EAPMethodID.EAP_OTP);
+ s_eapIds.put(EAP_RSA, EAPMethodID.EAP_RSA);
+ s_eapIds.put(EAP_KEA, EAPMethodID.EAP_KEA);
+ s_eapIds.put(EAP_KEA_VALIDATE, EAPMethodID.EAP_KEA_VALIDATE);
+ s_eapIds.put(EAP_TLS, EAPMethodID.EAP_TLS);
+ s_eapIds.put(EAP_LEAP, EAPMethodID.EAP_LEAP);
+ s_eapIds.put(EAP_SIM, EAPMethodID.EAP_SIM);
+ s_eapIds.put(EAP_TTLS, EAPMethodID.EAP_TTLS);
+ s_eapIds.put(EAP_AKA, EAPMethodID.EAP_AKA);
+ s_eapIds.put(EAP_3Com, EAPMethodID.EAP_3Com);
+ s_eapIds.put(EAP_MSCHAPv2, EAPMethodID.EAP_MSCHAPv2);
+ s_eapIds.put(EAP_PEAP, EAPMethodID.EAP_PEAP);
+ s_eapIds.put(EAP_POTP, EAPMethodID.EAP_POTP);
+ s_eapIds.put(EAP_ActiontecWireless, EAPMethodID.EAP_ActiontecWireless);
+ s_eapIds.put(EAP_HTTPDigest, EAPMethodID.EAP_HTTPDigest);
+ s_eapIds.put(EAP_SPEKE, EAPMethodID.EAP_SPEKE);
+ s_eapIds.put(EAP_MOBAC, EAPMethodID.EAP_MOBAC);
+ s_eapIds.put(EAP_FAST, EAPMethodID.EAP_FAST);
+ s_eapIds.put(EAP_ZLXEAP, EAPMethodID.EAP_ZLXEAP);
+ s_eapIds.put(EAP_Link, EAPMethodID.EAP_Link);
+ s_eapIds.put(EAP_PAX, EAPMethodID.EAP_PAX);
+ s_eapIds.put(EAP_PSK, EAPMethodID.EAP_PSK);
+ s_eapIds.put(EAP_SAKE, EAPMethodID.EAP_SAKE);
+ s_eapIds.put(EAP_IKEv2, EAPMethodID.EAP_IKEv2);
+ s_eapIds.put(EAP_AKAPrim, EAPMethodID.EAP_AKAPrim);
+ s_eapIds.put(EAP_GPSK, EAPMethodID.EAP_GPSK);
+ s_eapIds.put(EAP_PWD, EAPMethodID.EAP_PWD);
+ s_eapIds.put(EAP_EKE, EAPMethodID.EAP_EKE);
+ s_eapIds.put(EAP_TEAP, EAPMethodID.EAP_TEAP);
+ }
+
+ public static EAPMethodID mapEAPMethod(int methodID) {
+ return s_eapIds.get(methodID);
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/EAPMethod.java b/wifi/java/android/net/wifi/anqp/eap/EAPMethod.java
new file mode 100644
index 0000000..968da36
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/EAPMethod.java
@@ -0,0 +1,134 @@
+package android.net.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP Method, part of the NAI Realm ANQP element, specified in
+ * IEEE802.11-2012 section 8.4.4.10, figure 8-420
+ */
+public class EAPMethod {
+ private final EAP.EAPMethodID mEAPMethodID;
+ private final Map<EAP.AuthInfoID, Set<AuthParam>> mAuthParams;
+
+ public EAPMethod(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 3) {
+ throw new ProtocolException("Runt EAP Method: " + payload.remaining());
+ }
+
+ int length = payload.get() & BYTE_MASK;
+ int methodID = payload.get() & BYTE_MASK;
+ int count = payload.get() & BYTE_MASK;
+
+ mEAPMethodID = EAP.mapEAPMethod(methodID);
+ mAuthParams = new EnumMap<EAP.AuthInfoID, Set<AuthParam>>(EAP.AuthInfoID.class);
+
+ int realCount = 0;
+
+ ByteBuffer paramPayload = payload.duplicate();
+ paramPayload.limit(paramPayload.position() + length);
+ payload.position(payload.position() + length);
+ while (paramPayload.hasRemaining()) {
+ int id = paramPayload.get() & BYTE_MASK;
+
+ EAP.AuthInfoID authInfoID;
+
+ if (id == EAP.VendorSpecific) {
+ authInfoID = EAP.AuthInfoID.VendorSpecific;
+ } else if (id <= EAP.AuthInfoID.TunneledEAPMethodCredType.ordinal() && id > 0) {
+ authInfoID = EAP.AuthInfoID.values()[id];
+ } else {
+ throw new ProtocolException("Unknown auth parameter ID: " + id);
+ }
+
+ switch (authInfoID) {
+ case ExpandedEAPMethod:
+ addAuthParam(new ExpandedEAPMethod(authInfoID, paramPayload));
+ break;
+ case NonEAPInnerAuthType:
+ addAuthParam(new NonEAPInnerAuth(paramPayload));
+ break;
+ case InnerAuthEAPMethodType:
+ addAuthParam(new InnerAuthEAP(paramPayload));
+ break;
+ case ExpandedInnerEAPMethod:
+ addAuthParam(new ExpandedEAPMethod(authInfoID, paramPayload));
+ break;
+ case CredentialType:
+ addAuthParam(new Credential(authInfoID, paramPayload));
+ break;
+ case TunneledEAPMethodCredType:
+ addAuthParam(new Credential(authInfoID, paramPayload));
+ break;
+ case VendorSpecific:
+ addAuthParam(new VendorSpecificAuth(paramPayload));
+ break;
+ }
+
+ realCount++;
+ }
+ if (realCount != count)
+ throw new ProtocolException("Invalid parameter count: " + realCount +
+ ", expected " + count);
+ }
+
+ private void addAuthParam(AuthParam param) {
+ Set<AuthParam> authParams = mAuthParams.get(param.getAuthInfoID());
+ if (authParams == null) {
+ authParams = new HashSet<AuthParam>();
+ mAuthParams.put(param.getAuthInfoID(), authParams);
+ }
+ authParams.add(param);
+ }
+
+ public Map<EAP.AuthInfoID, Set<AuthParam>> getAuthParams() {
+ return Collections.unmodifiableMap(mAuthParams);
+ }
+
+ public EAP.EAPMethodID getEAPMethodID() {
+ return mEAPMethodID;
+ }
+
+ public boolean matchesAuthParams(EAPMethod other) {
+ for (Map.Entry<EAP.AuthInfoID, Set<AuthParam>> entry : other.getAuthParams().entrySet()) {
+
+ Set<AuthParam> myParams = mAuthParams.get(entry.getKey());
+ if (myParams == null)
+ continue;
+
+ Set<AuthParam> otherParams = entry.getValue();
+
+ Set<AuthParam> iterationSet;
+ Set<AuthParam> seekSet;
+ if (myParams.size() >= otherParams.size()) {
+ seekSet = myParams;
+ iterationSet = otherParams;
+ } else {
+ seekSet = otherParams;
+ iterationSet = myParams;
+ }
+
+ for (AuthParam param : iterationSet) {
+ if (seekSet.contains(param)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "EAPMethod{" +
+ "mEAPMethodID=" + mEAPMethodID +
+ ", mAuthParams=" + mAuthParams +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/ExpandedEAPMethod.java b/wifi/java/android/net/wifi/anqp/eap/ExpandedEAPMethod.java
new file mode 100644
index 0000000..635d3b1
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/ExpandedEAPMethod.java
@@ -0,0 +1,66 @@
+package android.net.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static android.net.wifi.anqp.Constants.INT_MASK;
+import static android.net.wifi.anqp.Constants.getInteger;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class ExpandedEAPMethod implements AuthParam {
+
+ private final EAP.AuthInfoID m_authInfoID;
+ private final int m_vendorID;
+ private final long m_vendorType;
+
+ public ExpandedEAPMethod(EAP.AuthInfoID authInfoID, ByteBuffer payload) throws ProtocolException {
+ m_authInfoID = authInfoID;
+ if (payload.remaining() != 7) {
+ throw new ProtocolException("Bad length: " + payload.remaining());
+ }
+
+ m_vendorID = (int) getInteger(payload, 3);
+ m_vendorType = payload.getInt() & INT_MASK;
+ }
+
+ @Override
+ public EAP.AuthInfoID getAuthInfoID() {
+ return m_authInfoID;
+ }
+
+ @Override
+ public int hashCode() {
+ return (m_authInfoID.hashCode() * 31 + m_vendorID) * 31 + (int) m_vendorType;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (thatObject == this) {
+ return true;
+ } else if (thatObject == null || thatObject.getClass() != ExpandedEAPMethod.class) {
+ return false;
+ } else {
+ ExpandedEAPMethod that = (ExpandedEAPMethod) thatObject;
+ return that.getVendorID() == getVendorID() && that.getVendorType() == getVendorType();
+ }
+ }
+
+ public int getVendorID() {
+ return m_vendorID;
+ }
+
+ public long getVendorType() {
+ return m_vendorType;
+ }
+
+ @Override
+ public String toString() {
+ return "ExpandedEAPMethod{" +
+ "m_authInfoID=" + m_authInfoID +
+ ", m_vendorID=" + m_vendorID +
+ ", m_vendorType=" + m_vendorType +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/InnerAuthEAP.java b/wifi/java/android/net/wifi/anqp/eap/InnerAuthEAP.java
new file mode 100644
index 0000000..06ce0f3
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/InnerAuthEAP.java
@@ -0,0 +1,55 @@
+package android.net.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class InnerAuthEAP implements AuthParam {
+
+ private final EAP.EAPMethodID mEapMethodID;
+
+ public InnerAuthEAP(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() != 1) {
+ throw new ProtocolException("Bad length: " + payload.remaining());
+ }
+
+ int typeID = payload.get() & BYTE_MASK;
+ mEapMethodID = EAP.mapEAPMethod(typeID);
+ }
+
+ @Override
+ public EAP.AuthInfoID getAuthInfoID() {
+ return EAP.AuthInfoID.InnerAuthEAPMethodType;
+ }
+
+ public EAP.EAPMethodID getEAPMethodID() {
+ return mEapMethodID;
+ }
+
+ @Override
+ public int hashCode() {
+ return mEapMethodID.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (thatObject == this) {
+ return true;
+ } else if (thatObject == null || thatObject.getClass() != InnerAuthEAP.class) {
+ return false;
+ } else {
+ return ((InnerAuthEAP) thatObject).getEAPMethodID() == getEAPMethodID();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "InnerAuthEAP{" +
+ "EapMethodID=" + mEapMethodID +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/NonEAPInnerAuth.java b/wifi/java/android/net/wifi/anqp/eap/NonEAPInnerAuth.java
new file mode 100644
index 0000000..188472c
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/NonEAPInnerAuth.java
@@ -0,0 +1,59 @@
+package android.net.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class NonEAPInnerAuth implements AuthParam {
+
+ public enum NonEAPType {Reserved, PAP, CHAP, MSCHAP, MSCHAPv2}
+
+ private final NonEAPType mType;
+
+ public NonEAPInnerAuth(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() != 1) {
+ throw new ProtocolException("Bad length: " + payload.remaining());
+ }
+
+ int typeID = payload.get() & BYTE_MASK;
+ mType = typeID < NonEAPType.values().length ?
+ NonEAPType.values()[typeID] :
+ NonEAPType.Reserved;
+ }
+
+ @Override
+ public EAP.AuthInfoID getAuthInfoID() {
+ return EAP.AuthInfoID.NonEAPInnerAuthType;
+ }
+
+ public NonEAPType getType() {
+ return mType;
+ }
+
+ @Override
+ public int hashCode() {
+ return mType.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (thatObject == this) {
+ return true;
+ } else if (thatObject == null || thatObject.getClass() != NonEAPInnerAuth.class) {
+ return false;
+ } else {
+ return ((NonEAPInnerAuth) thatObject).getType() == getType();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "NonEAPInnerAuth{" +
+ "mType=" + mType +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/anqp/eap/VendorSpecificAuth.java b/wifi/java/android/net/wifi/anqp/eap/VendorSpecificAuth.java
new file mode 100644
index 0000000..eaefd91
--- /dev/null
+++ b/wifi/java/android/net/wifi/anqp/eap/VendorSpecificAuth.java
@@ -0,0 +1,61 @@
+package android.net.wifi.anqp.eap;
+
+import android.net.wifi.anqp.Constants;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static android.net.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class VendorSpecificAuth implements AuthParam {
+
+ private final byte[] mData;
+
+ public VendorSpecificAuth(ByteBuffer payload) throws ProtocolException {
+ if (payload.remaining() < 1 || payload.remaining() > 256) {
+ throw new ProtocolException("Bad length: " + payload.remaining());
+ }
+
+ int length = payload.get() & BYTE_MASK;
+ if (length > payload.remaining()) {
+ throw new ProtocolException("Excessive length: " + length);
+ }
+ mData = new byte[length];
+ payload.get(mData);
+ }
+
+ @Override
+ public EAP.AuthInfoID getAuthInfoID() {
+ return EAP.AuthInfoID.VendorSpecific;
+ }
+
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (thatObject == this) {
+ return true;
+ } else if (thatObject == null || thatObject.getClass() != VendorSpecificAuth.class) {
+ return false;
+ } else {
+ return Arrays.equals(((VendorSpecificAuth) thatObject).getData(), getData());
+ }
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ @Override
+ public String toString() {
+ return "VendorSpecificAuth{" +
+ "mData=" + Constants.toHexString(mData) +
+ '}';
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/ANQPData.java b/wifi/java/android/net/wifi/hotspot2/ANQPData.java
new file mode 100644
index 0000000..626fdb1
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/ANQPData.java
@@ -0,0 +1,26 @@
+package android.net.wifi.hotspot2;
+
+import android.net.wifi.anqp.ANQPElement;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by jannq on 1/21/15.
+ */
+public class ANQPData {
+ private final List<ANQPElement> mANQPElements;
+ private final long mCtime;
+ private volatile long mAtime;
+
+ public ANQPData( List<ANQPElement> ANQPElements ) {
+ mANQPElements = Collections.unmodifiableList( ANQPElements );
+ mCtime = System.currentTimeMillis();
+ mAtime = mCtime;
+ }
+
+ public List<ANQPElement> getANQPElements() {
+ mAtime = System.currentTimeMillis();
+ return mANQPElements;
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/NetworkInfo.java b/wifi/java/android/net/wifi/hotspot2/NetworkInfo.java
new file mode 100644
index 0000000..add5fde
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/NetworkInfo.java
@@ -0,0 +1,141 @@
+package android.net.wifi.hotspot2;
+
+import android.net.wifi.anqp.VenueNameElement;
+
+/**
+ * Created by jannq on 1/20/15.
+ */
+public class NetworkInfo {
+
+ public enum Ant {
+ Private,
+ PrivateWithGuest,
+ ChargeablePublic,
+ FreePublic,
+ Personal,
+ EmergencyOnly,
+ TestOrExperimental,
+ Wildcard
+ }
+
+ public enum HSRelease {
+ R1,
+ R2,
+ Unknown
+ }
+
+ // General identifiers:
+ private final String mSSID;
+ private final String mHESSID;
+ private final long mBSSID;
+
+ // BSS Load element:
+ private final int mStationCount;
+ private final int mChannelUtilization;
+ private final int mCapacity;
+
+ /*
+ * From Interworking element:
+ * mAnt non null indicates the presence of Interworking, i.e. 802.11u
+ * mVenueGroup and mVenueType may be null if not present in the Interworking element.
+ */
+ private final Ant mAnt;
+ private final boolean mInternet;
+ private final VenueNameElement.VenueGroup mVenueGroup;
+ private final VenueNameElement.VenueType mVenueType;
+
+ /*
+ * From HS20 Indication element:
+ * mHSRelease is null only if the HS20 Indication element was not present.
+ * mAnqpDomainID is set to -1 if not present in the element.
+ */
+ private final HSRelease mHSRelease;
+ private final int mAnqpDomainID;
+
+ /*
+ * From beacon:
+ * mRoamingConsortiums is either null, if the element was not present, or is an array of
+ * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
+ */
+ private final long[] mRoamingConsortiums;
+
+ public NetworkInfo(String SSID,
+ String HESSID,
+ long BSSID,
+ int stationCount,
+ int channelUtilization,
+ int capacity,
+ Ant ant,
+ boolean internet,
+ VenueNameElement.VenueGroup venueGroup,
+ VenueNameElement.VenueType venueType,
+ HSRelease HSRelease,
+ int anqpDomainID,
+ long[] roamingConsortiums) {
+ mSSID = SSID;
+ mHESSID = HESSID;
+ mBSSID = BSSID;
+ mStationCount = stationCount;
+ mChannelUtilization = channelUtilization;
+ mCapacity = capacity;
+ mAnt = ant;
+ mInternet = internet;
+ mVenueGroup = venueGroup;
+ mVenueType = venueType;
+ mHSRelease = HSRelease;
+ mAnqpDomainID = anqpDomainID;
+ mRoamingConsortiums = roamingConsortiums;
+ }
+
+ public String getSSID() {
+ return mSSID;
+ }
+
+ public String getHESSID() {
+ return mHESSID;
+ }
+
+ public long getBSSID() {
+ return mBSSID;
+ }
+
+ public int getStationCount() {
+ return mStationCount;
+ }
+
+ public int getChannelUtilization() {
+ return mChannelUtilization;
+ }
+
+ public int getCapacity() {
+ return mCapacity;
+ }
+
+ public Ant getAnt() {
+ return mAnt;
+ }
+
+ public boolean isInternet() {
+ return mInternet;
+ }
+
+ public VenueNameElement.VenueGroup getVenueGroup() {
+ return mVenueGroup;
+ }
+
+ public VenueNameElement.VenueType getVenueType() {
+ return mVenueType;
+ }
+
+ public HSRelease getHSRelease() {
+ return mHSRelease;
+ }
+
+ public int getAnqpDomainID() {
+ return mAnqpDomainID;
+ }
+
+ public long[] getRoamingConsortiums() {
+ return mRoamingConsortiums;
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/NetworkKey.java b/wifi/java/android/net/wifi/hotspot2/NetworkKey.java
new file mode 100644
index 0000000..0631a35
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/NetworkKey.java
@@ -0,0 +1,50 @@
+package android.net.wifi.hotspot2;
+
+/**
+ * Created by jannq on 1/20/15.
+ */
+public class NetworkKey {
+ private final String mSSID;
+ private final long mBSSID;
+ private final int mANQPDomainID;
+
+ public NetworkKey(String SSID, long BSSID, int ANQPDomainID) {
+ mSSID = SSID;
+ mBSSID = BSSID;
+ mANQPDomainID = ANQPDomainID;
+ }
+
+ public String getSSID() {
+ return mSSID;
+ }
+
+ public long getBSSID() {
+ return mBSSID;
+ }
+
+ public int getANQPDomainID() {
+ return mANQPDomainID;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NetworkKey that = (NetworkKey) o;
+
+ if (mANQPDomainID != that.mANQPDomainID) return false;
+ if (mBSSID != that.mBSSID) return false;
+ if (!mSSID.equals(that.mSSID)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mSSID.hashCode();
+ result = 31 * result + (int) (mBSSID ^ (mBSSID >>> 32));
+ result = 31 * result + mANQPDomainID;
+ return result;
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointMatch.java b/wifi/java/android/net/wifi/hotspot2/PasspointMatch.java
new file mode 100644
index 0000000..0f430e5
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointMatch.java
@@ -0,0 +1,12 @@
+package android.net.wifi.hotspot2;
+
+/**
+ * Created by jannq on 1/21/15.
+ */
+public enum PasspointMatch {
+ HomeProvider,
+ RoamingProvider,
+ Incomplete,
+ None,
+ Declined
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/SelectionManager.java b/wifi/java/android/net/wifi/hotspot2/SelectionManager.java
new file mode 100644
index 0000000..8f2de36
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/SelectionManager.java
@@ -0,0 +1,122 @@
+package android.net.wifi.hotspot2;
+
+import android.net.wifi.anqp.ANQPElement;
+import android.net.wifi.hotspot2.pps.HomeSP;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by jannq on 1/20/15.
+ */
+public class SelectionManager {
+ private final List<HomeSP> mHomeSPs;
+ private final Map<NetworkKey,ANQPData> mANQPCache;
+ private final Map<NetworkKey,NetworkInfo> mPendingANQP;
+ private final List<ScoredNetwork> mScoredNetworks;
+
+ private static class ScoredNetwork implements Comparable<ScoredNetwork> {
+ private final PasspointMatch mMatch;
+ private final NetworkInfo mNetworkInfo;
+
+ private ScoredNetwork(PasspointMatch match, NetworkInfo networkInfo) {
+ mMatch = match;
+ mNetworkInfo = networkInfo;
+ // !!! Further score on BSS Load, ANT, "Internet" and HSRelease
+ }
+
+ public PasspointMatch getMatch() {
+ return mMatch;
+ }
+
+ public NetworkInfo getNetworkInfo() {
+ return mNetworkInfo;
+ }
+
+ @Override
+ public int compareTo( ScoredNetwork other ) {
+ if ( getMatch() == other.getMatch() ) {
+ return 0;
+ }
+ else {
+ return getMatch().ordinal() > other.getMatch().ordinal() ? 1 : -1;
+ }
+ }
+ }
+
+ public SelectionManager( List<HomeSP> homeSPs ) {
+ mHomeSPs = homeSPs;
+ mANQPCache = new HashMap<NetworkKey,ANQPData>();
+ mPendingANQP = new HashMap<NetworkKey, NetworkInfo>();
+ mScoredNetworks = new ArrayList<ScoredNetwork>();
+ }
+
+ public NetworkInfo findNetwork( NetworkInfo networkInfo ) {
+
+ NetworkKey networkKey = new NetworkKey( networkInfo.getSSID(), networkInfo.getBSSID(), networkInfo.getAnqpDomainID() );
+ ANQPData anqpData = mANQPCache.get( networkKey );
+ List<ANQPElement> anqpElements = anqpData != null ? anqpData.getANQPElements() : null;
+ for ( HomeSP homeSP : mHomeSPs ) {
+ PasspointMatch match = homeSP.match( networkInfo, anqpElements );
+ if ( match == PasspointMatch.HomeProvider || match == PasspointMatch.RoamingProvider ) {
+ mScoredNetworks.add( new ScoredNetwork( match, networkInfo ) );
+ }
+ else if ( match == PasspointMatch.Incomplete && networkInfo.getAnt() != null ) {
+ mPendingANQP.put(networkKey, networkInfo);
+ }
+ }
+
+ // !!! Should really return a score-sorted list.
+ Collections.sort( mScoredNetworks );
+ if ( ! mScoredNetworks.isEmpty() &&
+ mScoredNetworks.get( 0 ).getMatch() == PasspointMatch.HomeProvider ) {
+ return mScoredNetworks.get( 0 ).getNetworkInfo();
+ }
+ else {
+ return null;
+ }
+ }
+
+ public void notifyANQPResponse( NetworkInfo networkInfo, List<ANQPElement> anqpElements ) {
+ NetworkKey networkKey = new NetworkKey( networkInfo.getSSID(), networkInfo.getBSSID(), networkInfo.getAnqpDomainID() );
+ mPendingANQP.remove( networkKey );
+ mANQPCache.put( networkKey, new ANQPData( anqpElements ) );
+
+ for ( HomeSP homeSP : mHomeSPs ) {
+ PasspointMatch match = homeSP.match( networkInfo, anqpElements );
+ if ( match == PasspointMatch.HomeProvider || match == PasspointMatch.RoamingProvider ) {
+ mScoredNetworks.add( new ScoredNetwork( match, networkInfo ) );
+ }
+ else if ( match == PasspointMatch.Declined ) {
+ Iterator<ScoredNetwork> scoredNetworkIterator = mScoredNetworks.iterator();
+ while ( scoredNetworkIterator.hasNext() ) {
+ ScoredNetwork scoredNetwork = scoredNetworkIterator.next();
+ if ( scoredNetwork.getNetworkInfo().getBSSID() == networkInfo.getBSSID() &&
+ scoredNetwork.getNetworkInfo().getSSID().equals( networkInfo.getSSID() ) ) {
+ scoredNetworkIterator.remove();
+ break;
+ }
+ }
+ }
+ }
+ Collections.sort( mScoredNetworks );
+ if ( ! mScoredNetworks.isEmpty() &&
+ mScoredNetworks.get( 0 ).getMatch() == PasspointMatch.HomeProvider ) {
+ // Kill mPendingANQP?
+ // Connect to mScoredNetworks.get( 0 ).getNetworkInfo()?
+ }
+ }
+
+ private void sendANQPQuery( NetworkInfo network ) {
+ if ( network.getHSRelease() != null ) {
+ // Query for 802.11u + potential HS2.0 elements
+ }
+ else if ( network.getAnt() != null ) {
+ // !!! Query for 802.11u
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/DomainMatcher.java b/wifi/java/android/net/wifi/hotspot2/pps/DomainMatcher.java
new file mode 100644
index 0000000..7e906d7
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/DomainMatcher.java
@@ -0,0 +1,70 @@
+package android.net.wifi.hotspot2.pps;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by jannq on 1/21/15.
+ */
+public class DomainMatcher {
+
+ public enum Match { None, Primary, Secondary }
+
+ private final Label mRoot;
+
+ private static class Label {
+ private final Map<String,Label> mSubDomains;
+ private final Match mMatch;
+
+ private Label( Match match ) {
+ mMatch = match;
+ mSubDomains = match == Match.None ? null : new HashMap<String,Label>();
+ }
+
+ private void addDomain( Iterator<String> labels, Match match ) {
+ String labelName = labels.next();
+ if ( labels.hasNext() ) {
+ Label subLabel = new Label( Match.None );
+ mSubDomains.put( labelName, subLabel );
+ subLabel.addDomain( labels, match );
+ }
+ else {
+ mSubDomains.put( labelName, new Label( match ) );
+ }
+ }
+
+ private Label getSubLabel( String labelString ) {
+ return mSubDomains.get( labelString );
+ }
+
+ public Match getMatch() {
+ return mMatch;
+ }
+ }
+
+ public DomainMatcher( List<String> primary, List<List<String>> secondary ) {
+ mRoot = new Label( Match.None );
+ for ( List<String> secondaryLabel : secondary ) {
+ mRoot.addDomain( secondaryLabel.iterator(), Match.Secondary );
+ }
+ // Primary overwrites secondary.
+ mRoot.addDomain( primary.iterator(), Match.Primary );
+ }
+
+ public Match isSubDomain( List<String> domain ) {
+
+ Label label = mRoot;
+ for ( String labelString : domain ) {
+ label = label.getSubLabel( labelString );
+ if ( label == null ) {
+ return Match.None;
+ }
+ else if ( label.getMatch() != Match.None ) {
+ return label.getMatch();
+ }
+ }
+ return Match.None; // Domain is a super domain
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
new file mode 100644
index 0000000..de9423c
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -0,0 +1,214 @@
+package android.net.wifi.hotspot2.pps;
+
+import android.net.wifi.anqp.ANQPElement;
+import android.net.wifi.anqp.DomainNameElement;
+import android.net.wifi.anqp.HSConnectionCapabilityElement;
+import android.net.wifi.anqp.HSWanMetricsElement;
+import android.net.wifi.anqp.IPAddressTypeAvailabilityElement;
+import android.net.wifi.anqp.NAIRealmData;
+import android.net.wifi.anqp.NAIRealmElement;
+import android.net.wifi.anqp.RoamingConsortiumElement;
+import android.net.wifi.anqp.ThreeGPPNetworkElement;
+import android.net.wifi.anqp.eap.EAP;
+import android.net.wifi.anqp.eap.EAPMethod;
+import android.net.wifi.hotspot2.NetworkInfo;
+import android.net.wifi.hotspot2.PasspointMatch;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static android.net.wifi.anqp.Constants.ANQPElementType;
+
+/**
+ * Created by jannq on 1/20/15.
+ */
+public class HomeSP {
+ private final Map<String, String> mSSIDs; // SSID, HESSID, [0,N]
+ private final DomainMatcher mDomainMatcher;
+ private final Set<Long> mRoamingConsortiums; // [0,N]
+ private final Set<Long> mMatchAnyOIs; // [0,N]
+ private final List<Long> mMatchAllOIs; // [0,N]
+
+ private final Map<EAP.EAPMethodID, EAPMethod> mCredentials;
+
+ // Informational:
+ private final String mFriendlyName; // [1]
+ private final String mIconURL; // [0,1]
+
+ public HomeSP(Map<String, String> ssidMap,
+ /*@NotNull*/ String fqdn,
+ /*@NotNull*/ Set<Long> roamingConsortiums,
+ /*@NotNull*/ Set<String> otherHomePartners,
+ /*@NotNull*/ Set<Long> matchAnyOIs,
+ /*@NotNull*/ List<Long> matchAllOIs,
+ String friendlyName,
+ String iconURL,
+ Map<EAP.EAPMethodID, EAPMethod> credentials) {
+
+ mSSIDs = ssidMap;
+ List<List<String>> otherPartners = new ArrayList<List<String>>(otherHomePartners.size());
+ for (String otherPartner : otherHomePartners) {
+ otherPartners.add(splitDomain(otherPartner));
+ }
+ mDomainMatcher = new DomainMatcher(splitDomain(fqdn), otherPartners);
+ mRoamingConsortiums = roamingConsortiums;
+ mMatchAnyOIs = matchAnyOIs;
+ mMatchAllOIs = matchAllOIs;
+ mFriendlyName = friendlyName;
+ mIconURL = iconURL;
+ mCredentials = credentials;
+ }
+
+ public PasspointMatch match(NetworkInfo networkInfo, List<ANQPElement> anqpElements) {
+
+ if (mSSIDs.containsKey(networkInfo.getSSID())) {
+ String hessid = mSSIDs.get(networkInfo.getSSID());
+ if (hessid == null || networkInfo.getHESSID().equals(hessid)) {
+ return PasspointMatch.HomeProvider;
+ }
+ }
+
+ List<Long> allOIs = null;
+
+ if (networkInfo.getRoamingConsortiums() != null) {
+ allOIs = new ArrayList<Long>();
+ for (long oi : networkInfo.getRoamingConsortiums()) {
+ allOIs.add(oi);
+ }
+ }
+
+ Map<ANQPElementType, ANQPElement> anqpElementMap = null;
+
+ if (anqpElements != null) {
+ anqpElementMap = new EnumMap<ANQPElementType, ANQPElement>(ANQPElementType.class);
+ for (ANQPElement element : anqpElements) {
+ anqpElementMap.put(element.getID(), element);
+ if (element.getID() == ANQPElementType.ANQPRoamingConsortium) {
+ RoamingConsortiumElement rcElement = (RoamingConsortiumElement) element;
+ if (!rcElement.getOIs().isEmpty()) {
+ if (allOIs == null) {
+ allOIs = new ArrayList<Long>(rcElement.getOIs());
+ } else {
+ allOIs.addAll(rcElement.getOIs());
+ }
+ }
+ }
+ }
+ }
+
+ if (allOIs != null) {
+ if (!mRoamingConsortiums.isEmpty()) {
+ for (long oi : allOIs) {
+ if (mRoamingConsortiums.contains(oi)) {
+ return PasspointMatch.HomeProvider;
+ }
+ }
+ }
+ if (!mMatchAnyOIs.isEmpty() || !mMatchAllOIs.isEmpty()) {
+ for (long anOI : allOIs) {
+
+ boolean oneMatchesAll = true;
+
+ for (long spOI : mMatchAllOIs) {
+ if (spOI != anOI) {
+ oneMatchesAll = false;
+ break;
+ }
+ }
+
+ if (oneMatchesAll) {
+ return PasspointMatch.HomeProvider;
+ }
+
+ if (mMatchAnyOIs.contains(anOI)) {
+ return PasspointMatch.HomeProvider;
+ }
+ }
+ }
+ }
+
+ if (anqpElementMap == null) {
+ return PasspointMatch.Incomplete;
+ }
+
+ DomainNameElement domainNameElement =
+ (DomainNameElement) anqpElementMap.get(ANQPElementType.ANQPDomName);
+ NAIRealmElement naiRealmElement =
+ (NAIRealmElement) anqpElementMap.get(ANQPElementType.ANQPNAIRealm);
+ ThreeGPPNetworkElement threeGPPNetworkElement =
+ (ThreeGPPNetworkElement) anqpElementMap.get(ANQPElementType.ANQP3GPPNetwork);
+
+ // For future policy decisions:
+ IPAddressTypeAvailabilityElement ipAddressAvailabilityElement =
+ (IPAddressTypeAvailabilityElement) anqpElementMap.get(
+ ANQPElementType.ANQPIPAddrAvailability);
+ HSConnectionCapabilityElement hsConnCapElement =
+ (HSConnectionCapabilityElement) anqpElementMap.get(
+ ANQPElementType.HSConnCapability);
+ HSWanMetricsElement hsWanMetricsElement =
+ (HSWanMetricsElement) anqpElementMap.get(ANQPElementType.HSWANMetrics);
+
+ if (domainNameElement != null) {
+ for (String domain : domainNameElement.getDomains()) {
+ DomainMatcher.Match match = mDomainMatcher.isSubDomain(splitDomain(domain));
+ if (match != DomainMatcher.Match.None) {
+ return PasspointMatch.HomeProvider;
+ }
+ }
+ }
+
+ /*
+ if ( threeGPPNetworkElement != null ) {
+ !!! Insert matching based on 3GPP credentials here
+ }
+ */
+
+ if (naiRealmElement != null) {
+
+ for (NAIRealmData naiRealmData : naiRealmElement.getRealmData()) {
+
+ DomainMatcher.Match match = DomainMatcher.Match.None;
+ for (String anRealm : naiRealmData.getRealms()) {
+ match = mDomainMatcher.isSubDomain(splitDomain(anRealm));
+ if (match != DomainMatcher.Match.None) {
+ break;
+ }
+ }
+ if (match != DomainMatcher.Match.None) {
+ if (mCredentials == null) {
+ return PasspointMatch.RoamingProvider;
+ } else {
+ for (EAPMethod anMethod : naiRealmData.getEAPMethods()) {
+ EAPMethod spMethod = mCredentials.get(anMethod.getEAPMethodID());
+ if (spMethod.matchesAuthParams(anMethod)) {
+ return PasspointMatch.RoamingProvider;
+ }
+ }
+ }
+ }
+ }
+ }
+ return PasspointMatch.None;
+ }
+
+ private static List<String> splitDomain(String domain) {
+
+ if (domain.endsWith("."))
+ domain = domain.substring(0, domain.length() - 1);
+ int at = domain.indexOf('@');
+ if (at >= 0)
+ domain = domain.substring(at + 1);
+
+ String[] labels = domain.split("\\.");
+ LinkedList<String> labelList = new LinkedList<String>();
+ for (String label : labels) {
+ labelList.addFirst(label);
+ }
+
+ return labelList;
+ }
+}