1 package com.android.server.wifi.anqp; 2 3 import java.net.ProtocolException; 4 import java.nio.ByteBuffer; 5 import java.nio.ByteOrder; 6 import java.nio.charset.StandardCharsets; 7 import java.util.ArrayList; 8 import java.util.Collections; 9 import java.util.List; 10 import java.util.ListIterator; 11 import java.util.Set; 12 13 /** 14 * Factory to build a collection of 802.11u ANQP elements from a byte buffer. 15 */ 16 public class ANQPFactory { 17 18 private static final Constants.ANQPElementType[] BaseANQPSet = new Constants.ANQPElementType[]{ 19 Constants.ANQPElementType.ANQPVenueName, 20 Constants.ANQPElementType.ANQPNwkAuthType, 21 Constants.ANQPElementType.ANQPRoamingConsortium, 22 Constants.ANQPElementType.ANQPIPAddrAvailability, 23 Constants.ANQPElementType.ANQPNAIRealm, 24 Constants.ANQPElementType.ANQP3GPPNetwork, 25 Constants.ANQPElementType.ANQPDomName 26 }; 27 28 private static final Constants.ANQPElementType[] HS20ANQPSet = new Constants.ANQPElementType[]{ 29 Constants.ANQPElementType.HSFriendlyName, 30 Constants.ANQPElementType.HSWANMetrics, 31 Constants.ANQPElementType.HSConnCapability 32 }; 33 34 public static Constants.ANQPElementType[] getBaseANQPSet() { 35 return BaseANQPSet; 36 } 37 38 public static Constants.ANQPElementType[] getHS20ANQPSet() { 39 return HS20ANQPSet; 40 } 41 42 public static ByteBuffer buildQueryRequest(Set<Constants.ANQPElementType> elements, 43 ByteBuffer target) { 44 List<Constants.ANQPElementType> list = new ArrayList<Constants.ANQPElementType>(elements); 45 Collections.sort(list); 46 47 ListIterator<Constants.ANQPElementType> elementIterator = list.listIterator(); 48 49 target.order(ByteOrder.LITTLE_ENDIAN); 50 target.putShort((short) Constants.ANQP_QUERY_LIST); 51 int lenPos = target.position(); 52 target.putShort((short) 0); 53 54 while (elementIterator.hasNext()) { 55 Integer id = Constants.getANQPElementID(elementIterator.next()); 56 if (id != null) { 57 target.putShort(id.shortValue()); 58 } else { 59 elementIterator.previous(); 60 break; 61 } 62 } 63 target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT)); 64 65 // Start a new vendor specific element for HS2.0 elements: 66 if (elementIterator.hasNext()) { 67 target.putShort((short) Constants.ANQP_VENDOR_SPEC); 68 int vsLenPos = target.position(); 69 target.putShort((short) 0); 70 71 target.putInt(Constants.HS20_PREFIX); 72 target.put((byte) Constants.HS_QUERY_LIST); 73 target.put((byte) 0); 74 75 while (elementIterator.hasNext()) { 76 Constants.ANQPElementType elementType = elementIterator.next(); 77 Integer id = Constants.getHS20ElementID(elementType); 78 if (id == null) { 79 throw new RuntimeException("Unmapped ANQPElementType: " + elementType); 80 } else { 81 target.put(id.byteValue()); 82 } 83 } 84 target.putShort(vsLenPos, 85 (short) (target.position() - vsLenPos - Constants.BYTES_IN_SHORT)); 86 } 87 88 target.flip(); 89 return target; 90 } 91 92 public static ByteBuffer buildHomeRealmRequest(List<String> realmNames, ByteBuffer target) { 93 target.order(ByteOrder.LITTLE_ENDIAN); 94 target.putShort((short) Constants.ANQP_VENDOR_SPEC); 95 int lenPos = target.position(); 96 target.putShort((short) 0); 97 98 target.putInt(Constants.HS20_PREFIX); 99 target.put((byte) Constants.HS_NAI_HOME_REALM_QUERY); 100 target.put((byte) 0); 101 102 target.put((byte) realmNames.size()); 103 for (String realmName : realmNames) { 104 target.put((byte) Constants.UTF8_INDICATOR); 105 byte[] octets = realmName.getBytes(StandardCharsets.UTF_8); 106 target.put((byte) octets.length); 107 target.put(octets); 108 } 109 target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT)); 110 111 target.flip(); 112 return target; 113 } 114 115 public static ByteBuffer buildIconRequest(String fileName, ByteBuffer target) { 116 target.order(ByteOrder.LITTLE_ENDIAN); 117 target.putShort((short) Constants.ANQP_VENDOR_SPEC); 118 int lenPos = target.position(); 119 target.putShort((short) 0); 120 121 target.putInt(Constants.HS20_PREFIX); 122 target.put((byte) Constants.HS_ICON_REQUEST); 123 target.put((byte) 0); 124 125 target.put(fileName.getBytes(StandardCharsets.UTF_8)); 126 target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT)); 127 128 target.flip(); 129 return target; 130 } 131 132 public static List<ANQPElement> parsePayload(ByteBuffer payload) throws ProtocolException { 133 payload.order(ByteOrder.LITTLE_ENDIAN); 134 List<ANQPElement> elements = new ArrayList<ANQPElement>(); 135 while (payload.hasRemaining()) { 136 elements.add(buildElement(payload)); 137 } 138 return elements; 139 } 140 141 private static ANQPElement buildElement(ByteBuffer payload) throws ProtocolException { 142 if (payload.remaining() < 4) 143 throw new ProtocolException("Runt payload: " + payload.remaining()); 144 145 int infoIDNumber = payload.getShort() & Constants.SHORT_MASK; 146 Constants.ANQPElementType infoID = Constants.mapANQPElement(infoIDNumber); 147 if (infoID == null) { 148 throw new ProtocolException("Bad info ID: " + infoIDNumber); 149 } 150 int length = payload.getShort() & Constants.SHORT_MASK; 151 152 if (payload.remaining() < length) { 153 throw new ProtocolException("Truncated payload: " + 154 payload.remaining() + " vs " + length); 155 } 156 return buildElement(payload, infoID, length); 157 } 158 159 public static ANQPElement buildElement(ByteBuffer payload, Constants.ANQPElementType infoID, 160 int length) throws ProtocolException { 161 ByteBuffer elementPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN); 162 payload.position(payload.position() + length); 163 elementPayload.limit(elementPayload.position() + length); 164 165 switch (infoID) { 166 case ANQPCapabilityList: 167 return new CapabilityListElement(infoID, elementPayload); 168 case ANQPVenueName: 169 return new VenueNameElement(infoID, elementPayload); 170 case ANQPEmergencyNumber: 171 return new EmergencyNumberElement(infoID, elementPayload); 172 case ANQPNwkAuthType: 173 return new NetworkAuthenticationTypeElement(infoID, elementPayload); 174 case ANQPRoamingConsortium: 175 return new RoamingConsortiumElement(infoID, elementPayload); 176 case ANQPIPAddrAvailability: 177 return new IPAddressTypeAvailabilityElement(infoID, elementPayload); 178 case ANQPNAIRealm: 179 return new NAIRealmElement(infoID, elementPayload); 180 case ANQP3GPPNetwork: 181 return new ThreeGPPNetworkElement(infoID, elementPayload); 182 case ANQPGeoLoc: 183 return new GEOLocationElement(infoID, elementPayload); 184 case ANQPCivicLoc: 185 return new CivicLocationElement(infoID, elementPayload); 186 case ANQPLocURI: 187 return new GenericStringElement(infoID, elementPayload); 188 case ANQPDomName: 189 return new DomainNameElement(infoID, elementPayload); 190 case ANQPEmergencyAlert: 191 return new GenericStringElement(infoID, elementPayload); 192 case ANQPTDLSCap: 193 return new GenericBlobElement(infoID, elementPayload); 194 case ANQPEmergencyNAI: 195 return new GenericStringElement(infoID, elementPayload); 196 case ANQPNeighborReport: 197 return new GenericBlobElement(infoID, elementPayload); 198 case ANQPVendorSpec: 199 if (elementPayload.remaining() > 5) { 200 int oi = elementPayload.getInt(); 201 if (oi != Constants.HS20_PREFIX) { 202 return null; 203 } 204 int subType = elementPayload.get() & Constants.BYTE_MASK; 205 Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType); 206 if (hs20ID == null) { 207 throw new ProtocolException("Bad HS20 info ID: " + subType); 208 } 209 elementPayload.get(); // Skip the reserved octet 210 return buildHS20Element(hs20ID, elementPayload); 211 } else { 212 return new GenericBlobElement(infoID, elementPayload); 213 } 214 default: 215 throw new ProtocolException("Unknown element ID: " + infoID); 216 } 217 } 218 219 public static ANQPElement buildHS20Element(Constants.ANQPElementType infoID, 220 ByteBuffer payload) throws ProtocolException { 221 switch (infoID) { 222 case HSCapabilityList: 223 return new HSCapabilityListElement(infoID, payload); 224 case HSFriendlyName: 225 return new HSFriendlyNameElement(infoID, payload); 226 case HSWANMetrics: 227 return new HSWanMetricsElement(infoID, payload); 228 case HSConnCapability: 229 return new HSConnectionCapabilityElement(infoID, payload); 230 case HSOperatingclass: 231 return new GenericBlobElement(infoID, payload); 232 case HSOSUProviders: 233 return new HSOsuProvidersElement(infoID, payload); 234 case HSIconFile: 235 return new HSIconFileElement(infoID, payload); 236 default: 237 return null; 238 } 239 } 240 } 241