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