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