package com.android.server.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;
/**
* Factory to build a collection of 802.11u ANQP elements from a byte buffer.
*/
public class ANQPFactory {
private static final Constants.ANQPElementType[] BaseANQPSet = new Constants.ANQPElementType[]{
Constants.ANQPElementType.ANQPVenueName,
Constants.ANQPElementType.ANQPNwkAuthType,
Constants.ANQPElementType.ANQPRoamingConsortium,
Constants.ANQPElementType.ANQPIPAddrAvailability,
Constants.ANQPElementType.ANQPNAIRealm,
Constants.ANQPElementType.ANQP3GPPNetwork,
Constants.ANQPElementType.ANQPDomName
};
private static final Constants.ANQPElementType[] HS20ANQPSet = new Constants.ANQPElementType[]{
Constants.ANQPElementType.HSFriendlyName,
Constants.ANQPElementType.HSWANMetrics,
Constants.ANQPElementType.HSConnCapability
};
public static Constants.ANQPElementType[] getBaseANQPSet() {
return BaseANQPSet;
}
public static Constants.ANQPElementType[] getHS20ANQPSet() {
return HS20ANQPSet;
}
public static ByteBuffer buildQueryRequest(Set<Constants.ANQPElementType> elements,
ByteBuffer target) {
List<Constants.ANQPElementType> list = new ArrayList<Constants.ANQPElementType>(elements);
Collections.sort(list);
ListIterator<Constants.ANQPElementType> elementIterator = list.listIterator();
target.order(ByteOrder.LITTLE_ENDIAN);
target.putShort((short) Constants.ANQP_QUERY_LIST);
int lenPos = target.position();
target.putShort((short) 0);
while (elementIterator.hasNext()) {
Integer id = Constants.getANQPElementID(elementIterator.next());
if (id != null) {
target.putShort(id.shortValue());
} else {
elementIterator.previous();
break;
}
}
target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
// Start a new vendor specific element for HS2.0 elements:
if (elementIterator.hasNext()) {
target.putShort((short) Constants.ANQP_VENDOR_SPEC);
int vsLenPos = target.position();
target.putShort((short) 0);
target.putInt(Constants.HS20_PREFIX);
target.put((byte) Constants.HS_QUERY_LIST);
target.put((byte) 0);
while (elementIterator.hasNext()) {
Constants.ANQPElementType elementType = elementIterator.next();
Integer id = Constants.getHS20ElementID(elementType);
if (id == null) {
throw new RuntimeException("Unmapped ANQPElementType: " + elementType);
} else {
target.put(id.byteValue());
}
}
target.putShort(vsLenPos,
(short) (target.position() - vsLenPos - Constants.BYTES_IN_SHORT));
}
target.flip();
return target;
}
public static ByteBuffer buildHomeRealmRequest(List<String> realmNames, ByteBuffer target) {
target.order(ByteOrder.LITTLE_ENDIAN);
target.putShort((short) Constants.ANQP_VENDOR_SPEC);
int lenPos = target.position();
target.putShort((short) 0);
target.putInt(Constants.HS20_PREFIX);
target.put((byte) Constants.HS_NAI_HOME_REALM_QUERY);
target.put((byte) 0);
target.put((byte) realmNames.size());
for (String realmName : realmNames) {
target.put((byte) Constants.UTF8_INDICATOR);
byte[] octets = realmName.getBytes(StandardCharsets.UTF_8);
target.put((byte) octets.length);
target.put(octets);
}
target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
target.flip();
return target;
}
public static ByteBuffer buildIconRequest(String fileName, ByteBuffer target) {
target.order(ByteOrder.LITTLE_ENDIAN);
target.putShort((short) Constants.ANQP_VENDOR_SPEC);
int lenPos = target.position();
target.putShort((short) 0);
target.putInt(Constants.HS20_PREFIX);
target.put((byte) Constants.HS_ICON_REQUEST);
target.put((byte) 0);
target.put(fileName.getBytes(StandardCharsets.UTF_8));
target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
target.flip();
return target;
}
public static List<ANQPElement> parsePayload(ByteBuffer payload) throws ProtocolException {
payload.order(ByteOrder.LITTLE_ENDIAN);
List<ANQPElement> elements = new ArrayList<ANQPElement>();
while (payload.hasRemaining()) {
elements.add(buildElement(payload));
}
return elements;
}
private static ANQPElement buildElement(ByteBuffer payload) throws ProtocolException {
if (payload.remaining() < 4)
throw new ProtocolException("Runt payload: " + payload.remaining());
int infoIDNumber = payload.getShort() & Constants.SHORT_MASK;
Constants.ANQPElementType infoID = Constants.mapANQPElement(infoIDNumber);
if (infoID == null) {
throw new ProtocolException("Bad info ID: " + infoIDNumber);
}
int length = payload.getShort() & Constants.SHORT_MASK;
if (payload.remaining() < length) {
throw new ProtocolException("Truncated payload: " +
payload.remaining() + " vs " + length);
}
return buildElement(payload, infoID, length);
}
public static ANQPElement buildElement(ByteBuffer payload, Constants.ANQPElementType infoID,
int length) throws ProtocolException {
ByteBuffer elementPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
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() & Constants.BYTE_MASK;
Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
if (hs20ID == null) {
throw new ProtocolException("Bad HS20 info ID: " + subType);
}
elementPayload.get(); // Skip the reserved octet
return buildHS20Element(hs20ID, elementPayload);
} else {
return new GenericBlobElement(infoID, elementPayload);
}
default:
throw new ProtocolException("Unknown element ID: " + infoID);
}
}
public static ANQPElement buildHS20Element(Constants.ANQPElementType infoID,
ByteBuffer payload) throws ProtocolException {
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:
return null;
}
}
}