• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.hotspot2.anqp;
18 
19 import android.net.Uri;
20 import android.text.TextUtils;
21 
22 import com.android.internal.annotations.VisibleForTesting;
23 import com.android.server.wifi.ByteBufferReader;
24 
25 import java.net.ProtocolException;
26 import java.nio.BufferUnderflowException;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.nio.charset.StandardCharsets;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Objects;
35 
36 /**
37  * The OSU Provider subfield in the OSU Providers List ANQP Element,
38  * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
39  * section 4.8.1
40  *
41  * Format:
42  *
43  * | Length | Friendly Name Length | Friendly Name #1 | ... | Friendly Name #n |
44  *     2               2                variable                  variable
45  * | Server URI length | Server URI | Method List Length | Method List |
46  *          1             variable             1             variable
47  * | Icon Available Length | Icon Available | NAI Length | NAI | Description Length |
48  *            2                variable            1     variable      2
49  * | Description #1 | ... | Description #n |
50  *      variable               variable
51  *
52  * | Operator Name Duple #N (optional) |
53  *             variable
54  */
55 public class OsuProviderInfo {
56     /**
57      * The raw payload should minimum include the following fields:
58      * - Friendly Name Length (2)
59      * - Server URI Length (1)
60      * - Method List Length (1)
61      * - Icon Available Length (2)
62      * - NAI Length (1)
63      * - Description Length (2)
64      */
65     @VisibleForTesting
66     public static final int MINIMUM_LENGTH = 9;
67 
68     /**
69      * Maximum octets for a I18N string.
70      */
71     private static final int MAXIMUM_I18N_STRING_LENGTH = 252;
72 
73     private final List<I18Name> mFriendlyNames;
74     private final Uri mServerUri;
75     private final List<Integer> mMethodList;
76     private final List<IconInfo> mIconInfoList;
77     private final String mNetworkAccessIdentifier;
78     private final List<I18Name> mServiceDescriptions;
79 
80     @VisibleForTesting
OsuProviderInfo(List<I18Name> friendlyNames, Uri serverUri, List<Integer> methodList, List<IconInfo> iconInfoList, String nai, List<I18Name> serviceDescriptions)81     public OsuProviderInfo(List<I18Name> friendlyNames, Uri serverUri, List<Integer> methodList,
82             List<IconInfo> iconInfoList, String nai, List<I18Name> serviceDescriptions) {
83         mFriendlyNames = friendlyNames;
84         mServerUri = serverUri;
85         mMethodList = methodList;
86         mIconInfoList = iconInfoList;
87         mNetworkAccessIdentifier = nai;
88         mServiceDescriptions = serviceDescriptions;
89     }
90 
91     /**
92      * Parse a OsuProviderInfo from the given buffer.
93      *
94      * @param payload The buffer to read from
95      * @return {@link OsuProviderInfo}
96      * @throws BufferUnderflowException
97      * @throws ProtocolException
98      */
parse(ByteBuffer payload)99     public static OsuProviderInfo parse(ByteBuffer payload)
100             throws ProtocolException {
101         // Parse length field.
102         int length = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
103                 & 0xFFFF;
104         if (length < MINIMUM_LENGTH) {
105             throw new ProtocolException("Invalid length value: " + length);
106         }
107 
108         // Parse friendly names.
109         int friendlyNameLength =
110                 (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
111         ByteBuffer friendlyNameBuffer = getSubBuffer(payload, friendlyNameLength);
112         List<I18Name> friendlyNameList = parseI18Names(friendlyNameBuffer);
113 
114         // Parse server URI.
115         Uri serverUri = Uri.parse(
116                 ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8));
117 
118         // Parse method list.
119         int methodListLength = payload.get() & 0xFF;
120         List<Integer> methodList = new ArrayList<>();
121         while (methodListLength > 0) {
122             methodList.add(payload.get() & 0xFF);
123             methodListLength--;
124         }
125 
126         // Parse list of icon info.
127         int availableIconLength =
128                 (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
129         ByteBuffer iconBuffer = getSubBuffer(payload, availableIconLength);
130         List<IconInfo> iconInfoList = new ArrayList<>();
131         while (iconBuffer.hasRemaining()) {
132             iconInfoList.add(IconInfo.parse(iconBuffer));
133         }
134 
135         // Parse Network Access Identifier.
136         String nai = ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8);
137 
138         // Parse service descriptions.
139         int serviceDescriptionLength =
140                 (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
141         ByteBuffer descriptionsBuffer = getSubBuffer(payload, serviceDescriptionLength);
142         List<I18Name> serviceDescriptionList = parseI18Names(descriptionsBuffer);
143 
144         return new OsuProviderInfo(friendlyNameList, serverUri, methodList, iconInfoList, nai,
145                 serviceDescriptionList);
146     }
147 
getFriendlyNames()148     public List<I18Name> getFriendlyNames() {
149         return Collections.unmodifiableList(mFriendlyNames);
150     }
151 
getServerUri()152     public Uri getServerUri() {
153         return mServerUri;
154     }
155 
getMethodList()156     public List<Integer> getMethodList() {
157         return Collections.unmodifiableList(mMethodList);
158     }
159 
getIconInfoList()160     public List<IconInfo> getIconInfoList() {
161         return Collections.unmodifiableList(mIconInfoList);
162     }
163 
getNetworkAccessIdentifier()164     public String getNetworkAccessIdentifier() {
165         return mNetworkAccessIdentifier;
166     }
167 
getServiceDescriptions()168     public List<I18Name> getServiceDescriptions() {
169         return Collections.unmodifiableList(mServiceDescriptions);
170     }
171 
172     /**
173      * Return the friendly name string from the friendly name list.  The string matching
174      * the default locale will be returned if it is found, otherwise the first name in the list
175      * will be returned.  A null will be returned if the list is empty.
176      *
177      * @return friendly name string
178      */
getFriendlyName()179     public String getFriendlyName() {
180         return getI18String(mFriendlyNames);
181     }
182 
183     /**
184      * Return the service description string from the service description list.  The string
185      * matching the default locale will be returned if it is found, otherwise the first element in
186      * the list will be returned.  A null will be returned if the list is empty.
187      *
188      * @return service description string
189      */
getServiceDescription()190     public String getServiceDescription() {
191         return getI18String(mServiceDescriptions);
192     }
193 
194     @Override
equals(Object thatObject)195     public boolean equals(Object thatObject) {
196         if (this == thatObject) {
197             return true;
198         }
199         if (!(thatObject instanceof OsuProviderInfo)) {
200             return false;
201         }
202         OsuProviderInfo that = (OsuProviderInfo) thatObject;
203         return (mFriendlyNames == null ? that.mFriendlyNames == null
204                         : mFriendlyNames.equals(that.mFriendlyNames))
205                 && (mServerUri == null ? that.mServerUri == null
206                         : mServerUri.equals(that.mServerUri))
207                 && (mMethodList == null ? that.mMethodList == null
208                         : mMethodList.equals(that.mMethodList))
209                 && (mIconInfoList == null ? that.mIconInfoList == null
210                         : mIconInfoList.equals(that.mIconInfoList))
211                 && TextUtils.equals(mNetworkAccessIdentifier, that.mNetworkAccessIdentifier)
212                 && (mServiceDescriptions == null ? that.mServiceDescriptions == null
213                         : mServiceDescriptions.equals(that.mServiceDescriptions));
214     }
215 
216     @Override
hashCode()217     public int hashCode() {
218         return Objects.hash(mFriendlyNames, mServerUri, mMethodList, mIconInfoList,
219                 mNetworkAccessIdentifier, mServiceDescriptions);
220     }
221 
222     @Override
toString()223     public String toString() {
224         return "OsuProviderInfo{"
225                 + "mFriendlyNames=" + mFriendlyNames
226                 + ", mServerUri=" + mServerUri
227                 + ", mMethodList=" + mMethodList
228                 + ", mIconInfoList=" + mIconInfoList
229                 + ", mNetworkAccessIdentifier=" + mNetworkAccessIdentifier
230                 + ", mServiceDescriptions=" + mServiceDescriptions
231                 + "}";
232     }
233 
234     /**
235      * Parse list of I18N string from the given payload.
236      *
237      * @param payload The payload to parse from
238      * @return List of {@link I18Name}
239      * @throws ProtocolException
240      */
parseI18Names(ByteBuffer payload)241     private static List<I18Name> parseI18Names(ByteBuffer payload) throws ProtocolException {
242         List<I18Name> results = new ArrayList<>();
243         while (payload.hasRemaining()) {
244             I18Name name = I18Name.parse(payload);
245             // Verify that the number of bytes for the operator name doesn't exceed the max
246             // allowed.
247             int textBytes = name.getText().getBytes(StandardCharsets.UTF_8).length;
248             if (textBytes > MAXIMUM_I18N_STRING_LENGTH) {
249                 throw new ProtocolException("I18Name string exceeds the maximum allowed "
250                         + textBytes);
251             }
252             results.add(name);
253         }
254         return results;
255     }
256 
257     /**
258      * Creates a new byte buffer whose content is a shared subsequence of
259      * the given buffer's content.
260      *
261      * The sub buffer will starts from |payload|'s current position
262      * and ends at |payload|'s current position plus |length|.  The |payload|'s current
263      * position will advance pass |length| bytes.
264      *
265      * @param payload The original buffer
266      * @param length The length of the new buffer
267      * @return {@link ByteBuffer}
268      * @throws BufferUnderflowException
269      */
getSubBuffer(ByteBuffer payload, int length)270     private static ByteBuffer getSubBuffer(ByteBuffer payload, int length) {
271         if (payload.remaining() < length) {
272             throw new BufferUnderflowException();
273         }
274         // Set the subBuffer's starting and ending position.
275         ByteBuffer subBuffer = payload.slice();
276         subBuffer.limit(length);
277         // Advance the original buffer's current position.
278         payload.position(payload.position() + length);
279         return subBuffer;
280     }
281 
282     /**
283      * Return the appropriate I18 string value from the list of I18 string values.
284      * The string matching the default locale will be returned if it is found, otherwise the
285      * first string in the list will be returned.  A null will be returned if the list is empty.
286      *
287      * @param i18Strings List of I18 string values
288      * @return String matching the default locale, null otherwise
289      */
getI18String(List<I18Name> i18Strings)290     private static String getI18String(List<I18Name> i18Strings) {
291         for (I18Name name : i18Strings) {
292             if (name.getLanguage().equals(Locale.getDefault().getLanguage())) {
293                 return name.getText();
294             }
295         }
296         if (i18Strings.size() > 0) {
297             return i18Strings.get(0).getText();
298         }
299         return null;
300     }
301 }
302