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
17package android.net.wifi;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21
22import java.io.ByteArrayOutputStream;
23import java.nio.ByteBuffer;
24import java.nio.CharBuffer;
25import java.nio.charset.Charset;
26import java.nio.charset.CharsetDecoder;
27import java.nio.charset.CoderResult;
28import java.nio.charset.CodingErrorAction;
29import java.util.Arrays;
30import 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 */
42public 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
50    private WifiSsid() {
51    }
52
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
61    public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
62        WifiSsid a = new WifiSsid();
63        a.convertToBytes(asciiEncoded);
64        return a;
65    }
66
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 */
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
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
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
206    public int hashCode() {
207        return Arrays.hashCode(octets.toByteArray());
208    }
209
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 */
218    public boolean isHidden() {
219        return isArrayAllZeroes(octets.toByteArray());
220    }
221
222    /** @hide */
223    public byte[] getOctets() {
224        return octets.toByteArray();
225    }
226
227    /** @hide */
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} */
238    public int describeContents() {
239        return 0;
240    }
241
242    /** Implement the Parcelable interface {@hide} */
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