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