1 /*
2  * Copyright (C) 2020 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 static android.os.Environment.getDataMiscDirectory;
20 
21 import android.annotation.Nullable;
22 import android.net.MacAddress;
23 import android.util.Log;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.FastXmlSerializer;
27 import com.android.internal.util.XmlUtils;
28 
29 import org.xmlpull.v1.XmlPullParserException;
30 import org.xmlpull.v1.XmlSerializer;
31 
32 import java.io.BufferedInputStream;
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.DataInputStream;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileNotFoundException;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.nio.charset.StandardCharsets;
42 
43 /**
44  * Utility class to convert the legacy softap.conf file format to the new XML format.
45  * Note:
46  * <li>This should be modified by the OEM if they want to migrate configuration for existing
47  * devices for new softap features supported by AOSP in Android 11.
48  * For ex: client allowlist/blocklist feature was already supported by some OEM's before Android 10
49  * while AOSP only supported it in Android 11. </li>
50  * <li>Most of this class was copied over from WifiApConfigStore class in Android 10 and
51  * SoftApStoreData class in Android 11</li>
52  * @hide
53  */
54 public final class SoftApConfToXmlMigrationUtil {
55     private static final String TAG = "SoftApConfToXmlMigrationUtil";
56 
57     /**
58      * Directory to read the wifi config store files from under.
59      */
60     private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi";
61     /**
62      * The legacy Softap config file which contained key/value pairs.
63      */
64     private static final String LEGACY_AP_CONFIG_FILE = "softap.conf";
65 
66     /**
67      * Pre-apex wifi shared folder.
68      */
getLegacyWifiSharedDirectory()69     private static File getLegacyWifiSharedDirectory() {
70         return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME);
71     }
72 
73     /* @hide constants copied from WifiConfiguration */
74     /**
75      * 2GHz band.
76      */
77     private static final int WIFICONFIG_AP_BAND_2GHZ = 0;
78     /**
79      * 5GHz band.
80      */
81     private static final int WIFICONFIG_AP_BAND_5GHZ = 1;
82     /**
83      * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
84      * operating country code and current radio conditions.
85      */
86     private static final int WIFICONFIG_AP_BAND_ANY = -1;
87     /**
88      * Convert band from WifiConfiguration into SoftApConfiguration
89      *
90      * @param wifiConfigBand band encoded as WIFICONFIG_AP_BAND_xxxx
91      * @return band as encoded as SoftApConfiguration.BAND_xxx
92      */
93     @VisibleForTesting
convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand)94     public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) {
95         switch (wifiConfigBand) {
96             case WIFICONFIG_AP_BAND_2GHZ:
97                 return SoftApConfiguration.BAND_2GHZ;
98             case WIFICONFIG_AP_BAND_5GHZ:
99                 return SoftApConfiguration.BAND_5GHZ;
100             case WIFICONFIG_AP_BAND_ANY:
101                 return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
102             default:
103                 return SoftApConfiguration.BAND_2GHZ;
104         }
105     }
106 
107     /**
108      * Load AP configuration from legacy persistent storage.
109      * Note: This is deprecated and only used for migrating data once on reboot.
110      */
loadFromLegacyFile(InputStream fis)111     private static SoftApConfiguration loadFromLegacyFile(InputStream fis) {
112         SoftApConfiguration config = null;
113         DataInputStream in = null;
114         try {
115             SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
116             in = new DataInputStream(new BufferedInputStream(fis));
117 
118             int version = in.readInt();
119             if (version < 1 || version > 3) {
120                 Log.e(TAG, "Bad version on hotspot configuration file");
121                 return null;
122             }
123             configBuilder.setSsid(in.readUTF());
124 
125             if (version >= 2) {
126                 int band = in.readInt();
127                 int channel = in.readInt();
128                 if (channel == 0) {
129                     configBuilder.setBand(
130                             convertWifiConfigBandToSoftApConfigBand(band));
131                 } else {
132                     configBuilder.setChannel(channel,
133                             convertWifiConfigBandToSoftApConfigBand(band));
134                 }
135             }
136             if (version >= 3) {
137                 configBuilder.setHiddenSsid(in.readBoolean());
138             }
139             int authType = in.readInt();
140             if (authType == WifiConfiguration.KeyMgmt.WPA2_PSK) {
141                 configBuilder.setPassphrase(in.readUTF(),
142                         SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
143             }
144             config = configBuilder.build();
145         } catch (IOException e) {
146             Log.e(TAG, "Error reading hotspot configuration ",  e);
147             config = null;
148         } catch (IllegalArgumentException ie) {
149             Log.e(TAG, "Invalid hotspot configuration ", ie);
150             config = null;
151         } finally {
152             if (in != null) {
153                 try {
154                     in.close();
155                 } catch (IOException e) {
156                     Log.e(TAG, "Error closing hotspot configuration during read", e);
157                 }
158             }
159         }
160         // NOTE: OEM's should add their customized parsing code here.
161         return config;
162     }
163 
164     // This is the version that Android 11 released with.
165     private static final int CONFIG_STORE_DATA_VERSION = 3;
166 
167     private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
168     private static final String XML_TAG_VERSION = "Version";
169     private static final String XML_TAG_SECTION_HEADER_SOFTAP = "SoftAp";
170     private static final String XML_TAG_SSID = "SSID";
171     private static final String XML_TAG_BSSID = "Bssid";
172     private static final String XML_TAG_CHANNEL = "Channel";
173     private static final String XML_TAG_HIDDEN_SSID = "HiddenSSID";
174     private static final String XML_TAG_SECURITY_TYPE = "SecurityType";
175     private static final String XML_TAG_AP_BAND = "ApBand";
176     private static final String XML_TAG_PASSPHRASE = "Passphrase";
177     private static final String XML_TAG_MAX_NUMBER_OF_CLIENTS = "MaxNumberOfClients";
178     private static final String XML_TAG_AUTO_SHUTDOWN_ENABLED = "AutoShutdownEnabled";
179     private static final String XML_TAG_SHUTDOWN_TIMEOUT_MILLIS = "ShutdownTimeoutMillis";
180     private static final String XML_TAG_CLIENT_CONTROL_BY_USER = "ClientControlByUser";
181     private static final String XML_TAG_BLOCKED_CLIENT_LIST = "BlockedClientList";
182     private static final String XML_TAG_ALLOWED_CLIENT_LIST = "AllowedClientList";
183     public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress";
184 
convertConfToXml(SoftApConfiguration softApConf)185     private static byte[] convertConfToXml(SoftApConfiguration softApConf) {
186         try {
187             final XmlSerializer out = new FastXmlSerializer();
188             final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
189             out.setOutput(outputStream, StandardCharsets.UTF_8.name());
190 
191             // Header for the XML file.
192             out.startDocument(null, true);
193             out.startTag(null, XML_TAG_DOCUMENT_HEADER);
194             XmlUtils.writeValueXml(CONFIG_STORE_DATA_VERSION, XML_TAG_VERSION, out);
195             out.startTag(null, XML_TAG_SECTION_HEADER_SOFTAP);
196 
197             // SoftAp conf
198             XmlUtils.writeValueXml(softApConf.getSsid(), XML_TAG_SSID, out);
199             if (softApConf.getBssid() != null) {
200                 XmlUtils.writeValueXml(softApConf.getBssid().toString(), XML_TAG_BSSID, out);
201             }
202             XmlUtils.writeValueXml(softApConf.getBand(), XML_TAG_AP_BAND, out);
203             XmlUtils.writeValueXml(softApConf.getChannel(), XML_TAG_CHANNEL, out);
204             XmlUtils.writeValueXml(softApConf.isHiddenSsid(), XML_TAG_HIDDEN_SSID, out);
205             XmlUtils.writeValueXml(softApConf.getSecurityType(), XML_TAG_SECURITY_TYPE, out);
206             if (softApConf.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) {
207                 XmlUtils.writeValueXml(softApConf.getPassphrase(), XML_TAG_PASSPHRASE, out);
208             }
209             XmlUtils.writeValueXml(softApConf.getMaxNumberOfClients(),
210                     XML_TAG_MAX_NUMBER_OF_CLIENTS, out);
211             XmlUtils.writeValueXml(softApConf.isClientControlByUserEnabled(),
212                     XML_TAG_CLIENT_CONTROL_BY_USER, out);
213             XmlUtils.writeValueXml(softApConf.isAutoShutdownEnabled(),
214                     XML_TAG_AUTO_SHUTDOWN_ENABLED, out);
215             XmlUtils.writeValueXml(softApConf.getShutdownTimeoutMillis(),
216                     XML_TAG_SHUTDOWN_TIMEOUT_MILLIS, out);
217             out.startTag(null, XML_TAG_BLOCKED_CLIENT_LIST);
218             for (MacAddress mac: softApConf.getBlockedClientList()) {
219                 XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out);
220             }
221             out.endTag(null, XML_TAG_BLOCKED_CLIENT_LIST);
222             out.startTag(null, XML_TAG_ALLOWED_CLIENT_LIST);
223             for (MacAddress mac: softApConf.getAllowedClientList()) {
224                 XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out);
225             }
226             out.endTag(null, XML_TAG_ALLOWED_CLIENT_LIST);
227 
228             // Footer for the XML file.
229             out.endTag(null, XML_TAG_SECTION_HEADER_SOFTAP);
230             out.endTag(null, XML_TAG_DOCUMENT_HEADER);
231             out.endDocument();
232 
233             return outputStream.toByteArray();
234         } catch (IOException | XmlPullParserException e) {
235             Log.e(TAG, "Failed to convert softap conf to XML", e);
236             return null;
237         }
238     }
239 
SoftApConfToXmlMigrationUtil()240     private SoftApConfToXmlMigrationUtil() { }
241 
242     /**
243      * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML
244      * format understood by WifiConfigStore.
245      * Note: Used for unit testing.
246      */
247     @VisibleForTesting
248     @Nullable
convert(InputStream fis)249     public static InputStream convert(InputStream fis) {
250         SoftApConfiguration softApConf = loadFromLegacyFile(fis);
251         if (softApConf == null) return null;
252 
253         byte[] xmlBytes = convertConfToXml(softApConf);
254         if (xmlBytes == null) return null;
255 
256         return new ByteArrayInputStream(xmlBytes);
257     }
258 
259     /**
260      * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML
261      * format understood by WifiConfigStore.
262      */
263     @Nullable
convert()264     public static InputStream convert() {
265         File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE);
266         FileInputStream fis = null;
267         try {
268             fis = new FileInputStream(file);
269         } catch (FileNotFoundException e) {
270             return null;
271         }
272         if (fis == null) return null;
273         return convert(fis);
274     }
275 
276     /**
277      * Remove the legacy /data/misc/wifi/softap.conf file.
278      */
279     @Nullable
remove()280     public static void remove() {
281         File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE);
282         file.delete();
283     }
284 }
285