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