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