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