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