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