• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 android.net.wifi;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 
25 import java.io.ByteArrayOutputStream;
26 import java.nio.ByteBuffer;
27 import java.nio.CharBuffer;
28 import java.nio.charset.Charset;
29 import java.nio.charset.CharsetDecoder;
30 import java.nio.charset.CoderResult;
31 import java.nio.charset.CodingErrorAction;
32 import java.util.Arrays;
33 import java.util.Locale;
34 
35 /**
36  * Stores SSID octets and handles conversion.
37  *
38  * For Ascii encoded string, any octet < 32 or > 127 is encoded as
39  * a "\x" followed by the hex representation of the octet.
40  * Exception chars are ", \, \e, \n, \r, \t which are escaped by a \
41  * See src/utils/common.c for the implementation in the supplicant.
42  *
43  * @hide
44  */
45 public final class WifiSsid implements Parcelable {
46     private static final String TAG = "WifiSsid";
47 
48     @UnsupportedAppUsage
49     public final ByteArrayOutputStream octets = new ByteArrayOutputStream(32);
50 
51     private static final int HEX_RADIX = 16;
52 
53     @UnsupportedAppUsage
54     public static final String NONE = WifiManager.UNKNOWN_SSID;
55 
WifiSsid()56     private WifiSsid() {
57     }
58 
59     /**
60      * Create a WifiSsid from a raw byte array. If the byte array is null, return an empty WifiSsid
61      * object.
62      */
63     @NonNull
createFromByteArray(@ullable byte[] ssid)64     public static WifiSsid createFromByteArray(@Nullable byte[] ssid) {
65         WifiSsid wifiSsid = new WifiSsid();
66         if (ssid != null) {
67             wifiSsid.octets.write(ssid, 0 /* the start offset */, ssid.length);
68         }
69         return wifiSsid;
70     }
71 
72     @UnsupportedAppUsage
createFromAsciiEncoded(String asciiEncoded)73     public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
74         WifiSsid a = new WifiSsid();
75         a.convertToBytes(asciiEncoded);
76         return a;
77     }
78 
createFromHex(String hexStr)79     public static WifiSsid createFromHex(String hexStr) {
80         WifiSsid a = new WifiSsid();
81         if (hexStr == null) return a;
82 
83         if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
84             hexStr = hexStr.substring(2);
85         }
86 
87         for (int i = 0; i < hexStr.length()-1; i += 2) {
88             int val;
89             try {
90                 val = Integer.parseInt(hexStr.substring(i, i + 2), HEX_RADIX);
91             } catch(NumberFormatException e) {
92                 val = 0;
93             }
94             a.octets.write(val);
95         }
96         return a;
97     }
98 
99     /* This function is equivalent to printf_decode() at src/utils/common.c in
100      * the supplicant */
convertToBytes(String asciiEncoded)101     private void convertToBytes(String asciiEncoded) {
102         int i = 0;
103         int val = 0;
104         while (i< asciiEncoded.length()) {
105             char c = asciiEncoded.charAt(i);
106             switch (c) {
107                 case '\\':
108                     i++;
109                     switch(asciiEncoded.charAt(i)) {
110                         case '\\':
111                             octets.write('\\');
112                             i++;
113                             break;
114                         case '"':
115                             octets.write('"');
116                             i++;
117                             break;
118                         case 'n':
119                             octets.write('\n');
120                             i++;
121                             break;
122                         case 'r':
123                             octets.write('\r');
124                             i++;
125                             break;
126                         case 't':
127                             octets.write('\t');
128                             i++;
129                             break;
130                         case 'e':
131                             octets.write(27); //escape char
132                             i++;
133                             break;
134                         case 'x':
135                             i++;
136                             try {
137                                 val = Integer.parseInt(asciiEncoded.substring(i, i + 2), HEX_RADIX);
138                             } catch (NumberFormatException e) {
139                                 val = -1;
140                             } catch (StringIndexOutOfBoundsException e) {
141                                 val = -1;
142                             }
143                             if (val < 0) {
144                                 val = Character.digit(asciiEncoded.charAt(i), HEX_RADIX);
145                                 if (val < 0) break;
146                                 octets.write(val);
147                                 i++;
148                             } else {
149                                 octets.write(val);
150                                 i += 2;
151                             }
152                             break;
153                         case '0':
154                         case '1':
155                         case '2':
156                         case '3':
157                         case '4':
158                         case '5':
159                         case '6':
160                         case '7':
161                             val = asciiEncoded.charAt(i) - '0';
162                             i++;
163                             if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
164                                 val = val * 8 + asciiEncoded.charAt(i) - '0';
165                                 i++;
166                             }
167                             if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
168                                 val = val * 8 + asciiEncoded.charAt(i) - '0';
169                                 i++;
170                             }
171                             octets.write(val);
172                             break;
173                         default:
174                             break;
175                     }
176                     break;
177                 default:
178                     octets.write(c);
179                     i++;
180                     break;
181             }
182         }
183     }
184 
185     /**
186      * Converts this SSID to an unquoted UTF-8 String representation.
187      * @return the SSID string, or {@link WifiManager#UNKNOWN_SSID} if there was an error.
188      */
189     @Override
toString()190     public String toString() {
191         byte[] ssidBytes = octets.toByteArray();
192         // Supplicant returns \x00\x00\x00\x00\x00\x00\x00\x00 hex string
193         // for a hidden access point. Make sure we maintain the previous
194         // behavior of returning empty string for this case.
195         if (octets.size() <= 0 || isArrayAllZeroes(ssidBytes)) return "";
196         // TODO: Handle conversion to other charsets upon failure
197         Charset charset = Charset.forName("UTF-8");
198         CharsetDecoder decoder = charset.newDecoder()
199                 .onMalformedInput(CodingErrorAction.REPLACE)
200                 .onUnmappableCharacter(CodingErrorAction.REPLACE);
201         CharBuffer out = CharBuffer.allocate(32);
202 
203         CoderResult result = decoder.decode(ByteBuffer.wrap(ssidBytes), out, true);
204         out.flip();
205         if (result.isError()) {
206             return WifiManager.UNKNOWN_SSID;
207         }
208         return out.toString();
209     }
210 
211     @Override
equals(Object thatObject)212     public boolean equals(Object thatObject) {
213         if (this == thatObject) {
214             return true;
215         }
216         if (!(thatObject instanceof WifiSsid)) {
217             return false;
218         }
219         WifiSsid that = (WifiSsid) thatObject;
220         return Arrays.equals(octets.toByteArray(), that.octets.toByteArray());
221     }
222 
223     @Override
hashCode()224     public int hashCode() {
225         return Arrays.hashCode(octets.toByteArray());
226     }
227 
isArrayAllZeroes(byte[] ssidBytes)228     private boolean isArrayAllZeroes(byte[] ssidBytes) {
229         for (int i = 0; i< ssidBytes.length; i++) {
230             if (ssidBytes[i] != 0) return false;
231         }
232         return true;
233     }
234 
235     /** @hide */
isHidden()236     public boolean isHidden() {
237         return isArrayAllZeroes(octets.toByteArray());
238     }
239 
240     /** @hide */
241     @UnsupportedAppUsage
getOctets()242     public byte[] getOctets() {
243         return octets.toByteArray();
244     }
245 
246     /** @hide */
getHexString()247     public String getHexString() {
248         String out = "0x";
249         byte[] ssidbytes = getOctets();
250         for (int i = 0; i < octets.size(); i++) {
251             out += String.format(Locale.US, "%02x", ssidbytes[i]);
252         }
253         return (octets.size() > 0) ? out : null;
254     }
255 
256     /** Implement the Parcelable interface */
257     @Override
describeContents()258     public int describeContents() {
259         return 0;
260     }
261 
262     /** Implement the Parcelable interface */
263     @Override
writeToParcel(@onNull Parcel dest, int flags)264     public void writeToParcel(@NonNull Parcel dest, int flags) {
265         dest.writeInt(octets.size());
266         dest.writeByteArray(octets.toByteArray());
267     }
268 
269     /** Implement the Parcelable interface */
270     @UnsupportedAppUsage
271     public static final @NonNull Creator<WifiSsid> CREATOR =
272             new Creator<WifiSsid>() {
273                 @Override
274                 public WifiSsid createFromParcel(Parcel in) {
275                     WifiSsid ssid = new WifiSsid();
276                     int length = in.readInt();
277                     byte[] b = new byte[length];
278                     in.readByteArray(b);
279                     ssid.octets.write(b, 0, length);
280                     return ssid;
281                 }
282 
283                 @Override
284                 public WifiSsid[] newArray(int size) {
285                     return new WifiSsid[size];
286                 }
287             };
288 }
289