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.net.wifi.util.HexEncoding; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 27 import java.io.ByteArrayOutputStream; 28 import java.nio.ByteBuffer; 29 import java.nio.CharBuffer; 30 import java.nio.charset.Charset; 31 import java.nio.charset.CharsetDecoder; 32 import java.nio.charset.CoderResult; 33 import java.nio.charset.CodingErrorAction; 34 import java.nio.charset.StandardCharsets; 35 import java.util.Arrays; 36 37 /** 38 * Representation of a Wi-Fi Service Set Identifier (SSID). 39 */ 40 public final class WifiSsid implements Parcelable { 41 private final byte[] mBytes; 42 43 /** 44 * Creates a WifiSsid from the raw bytes. If the byte array is null, creates an empty WifiSsid 45 * object which will return an empty byte array and empty text. 46 * @param bytes the SSID 47 * @throws IllegalArgumentException if the raw byte array is longer than 32 bytes. 48 */ WifiSsid(@ullable byte[] bytes)49 private WifiSsid(@Nullable byte[] bytes) { 50 if (bytes == null) { 51 bytes = new byte[0]; 52 } 53 if (bytes.length > 32) { 54 throw new IllegalArgumentException( 55 "Max SSID length is 32 bytes, but received " + bytes.length + " bytes!"); 56 } 57 mBytes = bytes; 58 // Duplicate the bytes to #octets for legacy apps. 59 octets.write(bytes, 0, bytes.length); 60 } 61 62 /** 63 * Create a WifiSsid from the raw bytes. If the byte array is null, return an empty WifiSsid 64 * object which will return an empty byte array and empty text. 65 * @throws IllegalArgumentException if the raw byte array is longer than 32 bytes. 66 */ 67 @NonNull fromBytes(@ullable byte[] bytes)68 public static WifiSsid fromBytes(@Nullable byte[] bytes) { 69 return new WifiSsid(bytes); 70 } 71 72 /** 73 * Returns the raw byte array representing this SSID. 74 * @return the SSID 75 */ 76 @NonNull getBytes()77 public byte[] getBytes() { 78 return mBytes; 79 } 80 81 /** 82 * Create a UTF-8 WifiSsid from unquoted plaintext. If the text is null, return an 83 * empty WifiSsid object which will return an empty byte array and empty text. 84 * @throws IllegalArgumentException if the encoded UTF-8 byte array is longer than 32 bytes. 85 * @hide 86 */ 87 @NonNull fromUtf8Text(@ullable CharSequence utf8Text)88 public static WifiSsid fromUtf8Text(@Nullable CharSequence utf8Text) { 89 if (utf8Text == null) { 90 return new WifiSsid(null); 91 } 92 return new WifiSsid(utf8Text.toString().getBytes(StandardCharsets.UTF_8)); 93 } 94 95 /** 96 * If the SSID is encoded with UTF-8, this method returns the decoded SSID as plaintext. 97 * Otherwise, it returns {@code null}. 98 * @return the SSID 99 * @hide 100 */ 101 @Nullable getUtf8Text()102 public CharSequence getUtf8Text() { 103 return decodeSsid(mBytes, StandardCharsets.UTF_8); 104 } 105 106 /** 107 * Create a WifiSsid from a string matching the format of {@link WifiSsid#toString()}. 108 * If the string is null, return an empty WifiSsid object which will return an empty byte array 109 * and empty text. 110 * @throws IllegalArgumentException if the string is unquoted but not hexadecimal, 111 * if the hexadecimal string is odd-length, 112 * or if the encoded byte array is longer than 32 bytes. 113 * @hide 114 */ 115 @NonNull fromString(@ullable String string)116 public static WifiSsid fromString(@Nullable String string) { 117 if (string == null) { 118 return new WifiSsid(null); 119 } 120 final int length = string.length(); 121 if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { 122 return new WifiSsid(string.substring(1, length - 1).getBytes(StandardCharsets.UTF_8)); 123 } 124 return new WifiSsid(HexEncoding.decode(string)); 125 } 126 127 /** 128 * Returns the string representation of the WifiSsid. If the SSID can be decoded as UTF-8, it 129 * will be returned in plain text surrounded by double quotation marks. Otherwise, it is 130 * returned as an unquoted string of hex digits. This format is consistent with 131 * {@link WifiInfo#getSSID()} and {@link WifiConfiguration#SSID}. 132 * 133 * @return SSID as double-quoted plain text from UTF-8 or unquoted hex digits 134 */ 135 @Override 136 @NonNull toString()137 public String toString() { 138 String utf8String = decodeSsid(mBytes, StandardCharsets.UTF_8); 139 if (TextUtils.isEmpty(utf8String)) { 140 return HexEncoding.encodeToString(mBytes); 141 } 142 return "\"" + utf8String + "\""; 143 } 144 145 /** 146 * Returns the given SSID bytes as a String decoded using the given Charset. If the bytes cannot 147 * be decoded, then this returns {@code null}. 148 * @param ssidBytes SSID as bytes 149 * @param charset Charset to decode with 150 * @return SSID as string, or {@code null}. 151 */ 152 @Nullable decodeSsid(@onNull byte[] ssidBytes, @NonNull Charset charset)153 private static String decodeSsid(@NonNull byte[] ssidBytes, @NonNull Charset charset) { 154 CharsetDecoder decoder = charset.newDecoder() 155 .onMalformedInput(CodingErrorAction.REPORT) 156 .onUnmappableCharacter(CodingErrorAction.REPORT); 157 CharBuffer out = CharBuffer.allocate(32); 158 CoderResult result = decoder.decode(ByteBuffer.wrap(ssidBytes), out, true); 159 out.flip(); 160 if (result.isError()) { 161 return null; 162 } 163 return out.toString(); 164 } 165 166 @Override equals(Object thatObject)167 public boolean equals(Object thatObject) { 168 if (this == thatObject) { 169 return true; 170 } 171 if (!(thatObject instanceof WifiSsid)) { 172 return false; 173 } 174 WifiSsid that = (WifiSsid) thatObject; 175 return Arrays.equals(mBytes, that.mBytes); 176 } 177 178 @Override hashCode()179 public int hashCode() { 180 return Arrays.hashCode(mBytes); 181 } 182 183 /** Implement the Parcelable interface */ 184 @Override describeContents()185 public int describeContents() { 186 return 0; 187 } 188 189 /** Implement the Parcelable interface */ 190 @Override writeToParcel(@onNull Parcel dest, int flags)191 public void writeToParcel(@NonNull Parcel dest, int flags) { 192 dest.writeByteArray(mBytes); 193 } 194 195 /** Implement the Parcelable interface */ 196 public static final @NonNull Creator<WifiSsid> CREATOR = 197 new Creator<WifiSsid>() { 198 @Override 199 public WifiSsid createFromParcel(Parcel in) { 200 return new WifiSsid(in.createByteArray()); 201 } 202 203 @Override 204 public WifiSsid[] newArray(int size) { 205 return new WifiSsid[size]; 206 } 207 }; 208 209 /** 210 * Use {@link #getBytes()} instead. 211 * @hide 212 */ 213 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 214 @UnsupportedAppUsage(publicAlternatives = "{@link #getBytes()}") 215 public final ByteArrayOutputStream octets = new ByteArrayOutputStream(32); 216 217 /** 218 * Use {@link android.net.wifi.WifiManager#UNKNOWN_SSID} instead. 219 * @hide 220 */ 221 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 222 @UnsupportedAppUsage(publicAlternatives = "{@link android.net.wifi.WifiManager#UNKNOWN_SSID}") 223 public static final String NONE = WifiManager.UNKNOWN_SSID; 224 225 /** 226 * Use {@link #fromBytes(byte[])} instead. 227 * @hide 228 */ 229 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 230 @UnsupportedAppUsage(publicAlternatives = "{@link #fromBytes(byte[])}") createFromAsciiEncoded(String asciiEncoded)231 public static WifiSsid createFromAsciiEncoded(String asciiEncoded) { 232 return fromUtf8Text(asciiEncoded); 233 } 234 235 /** 236 * Use {@link #getBytes()} instead. 237 * @hide 238 */ 239 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 240 @UnsupportedAppUsage(publicAlternatives = "{@link #getBytes()}") getOctets()241 public byte[] getOctets() { 242 return getBytes(); 243 } 244 } 245