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 
getBaseANQPSet(boolean includeRC)49     public static List<Constants.ANQPElementType> getBaseANQPSet(boolean includeRC) {
50         return includeRC ? BaseANQPSet1 : BaseANQPSet2;
51     }
52 
getHS20ANQPSet(boolean includeOSU)53     public static List<Constants.ANQPElementType> getHS20ANQPSet(boolean includeOSU) {
54         return includeOSU ? HS20ANQPSetwOSU : HS20ANQPSet;
55     }
56 
buildQueryList(NetworkDetail networkDetail, boolean matchSet, boolean osu)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 
buildQueryRequest(Set<Constants.ANQPElementType> elements, ByteBuffer target)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 
buildHomeRealmRequest(List<String> realmNames, ByteBuffer target)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 
buildIconRequest(String fileName, ByteBuffer target)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 
parsePayload(ByteBuffer payload)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 
buildElement(ByteBuffer payload)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 
buildElement(ByteBuffer payload, Constants.ANQPElementType infoID, int length)195     public static ANQPElement buildElement(ByteBuffer payload, Constants.ANQPElementType infoID,
196                                             int length) throws ProtocolException {
197         ByteBuffer elementPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
198         payload.position(payload.position() + length);
199         elementPayload.limit(elementPayload.position() + length);
200 
201         switch (infoID) {
202             case ANQPCapabilityList:
203                 return new CapabilityListElement(infoID, elementPayload);
204             case ANQPVenueName:
205                 return new VenueNameElement(infoID, elementPayload);
206             case ANQPEmergencyNumber:
207                 return new EmergencyNumberElement(infoID, elementPayload);
208             case ANQPNwkAuthType:
209                 return new NetworkAuthenticationTypeElement(infoID, elementPayload);
210             case ANQPRoamingConsortium:
211                 return new RoamingConsortiumElement(infoID, elementPayload);
212             case ANQPIPAddrAvailability:
213                 return new IPAddressTypeAvailabilityElement(infoID, elementPayload);
214             case ANQPNAIRealm:
215                 return new NAIRealmElement(infoID, elementPayload);
216             case ANQP3GPPNetwork:
217                 return new ThreeGPPNetworkElement(infoID, elementPayload);
218             case ANQPGeoLoc:
219                 return new GEOLocationElement(infoID, elementPayload);
220             case ANQPCivicLoc:
221                 return new CivicLocationElement(infoID, elementPayload);
222             case ANQPLocURI:
223                 return new GenericStringElement(infoID, elementPayload);
224             case ANQPDomName:
225                 return new DomainNameElement(infoID, elementPayload);
226             case ANQPEmergencyAlert:
227                 return new GenericStringElement(infoID, elementPayload);
228             case ANQPTDLSCap:
229                 return new GenericBlobElement(infoID, elementPayload);
230             case ANQPEmergencyNAI:
231                 return new GenericStringElement(infoID, elementPayload);
232             case ANQPNeighborReport:
233                 return new GenericBlobElement(infoID, elementPayload);
234             case ANQPVendorSpec:
235                 if (elementPayload.remaining() > 5) {
236                     int oi = elementPayload.getInt();
237                     if (oi != Constants.HS20_PREFIX) {
238                         return null;
239                     }
240                     int subType = elementPayload.get() & Constants.BYTE_MASK;
241                     Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
242                     if (hs20ID == null) {
243                         throw new ProtocolException("Bad HS20 info ID: " + subType);
244                     }
245                     elementPayload.get();   // Skip the reserved octet
246                     return buildHS20Element(hs20ID, elementPayload);
247                 } else {
248                     return new GenericBlobElement(infoID, elementPayload);
249                 }
250             default:
251                 throw new ProtocolException("Unknown element ID: " + infoID);
252         }
253     }
254 
buildHS20Element(Constants.ANQPElementType infoID, ByteBuffer payload)255     public static ANQPElement buildHS20Element(Constants.ANQPElementType infoID,
256                                                 ByteBuffer payload) throws ProtocolException {
257         switch (infoID) {
258             case HSCapabilityList:
259                 return new HSCapabilityListElement(infoID, payload);
260             case HSFriendlyName:
261                 return new HSFriendlyNameElement(infoID, payload);
262             case HSWANMetrics:
263                 return new HSWanMetricsElement(infoID, payload);
264             case HSConnCapability:
265                 return new HSConnectionCapabilityElement(infoID, payload);
266             case HSOperatingclass:
267                 return new GenericBlobElement(infoID, payload);
268             case HSOSUProviders:
269                 return new RawByteElement(infoID, payload);
270             case HSIconFile:
271                 return new HSIconFileElement(infoID, payload);
272             default:
273                 return null;
274         }
275     }
276 }
277