• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.server.wifi;
18 
19 import android.compat.Compatibility;
20 import android.content.Context;
21 import android.net.MacAddress;
22 import android.net.wifi.SoftApConfiguration;
23 import android.net.wifi.WifiConfiguration;
24 import android.net.wifi.WifiMigration;
25 import android.util.BackupUtils;
26 import android.util.Log;
27 import android.util.SparseIntArray;
28 import android.util.Xml;
29 
30 import com.android.internal.util.FastXmlSerializer;
31 import com.android.modules.utils.build.SdkLevel;
32 import com.android.server.wifi.util.ApConfigUtil;
33 import com.android.server.wifi.util.SettingsMigrationDataHolder;
34 import com.android.server.wifi.util.XmlUtil;
35 
36 import org.xmlpull.v1.XmlPullParser;
37 import org.xmlpull.v1.XmlPullParserException;
38 import org.xmlpull.v1.XmlSerializer;
39 
40 import java.io.ByteArrayInputStream;
41 import java.io.ByteArrayOutputStream;
42 import java.io.DataInputStream;
43 import java.io.DataOutputStream;
44 import java.io.IOException;
45 import java.nio.charset.StandardCharsets;
46 import java.util.ArrayList;
47 import java.util.Iterator;
48 import java.util.List;
49 
50 /**
51  * Class used to backup/restore data using the SettingsBackupAgent.
52  * There are 2 symmetric API's exposed here:
53  * 1. retrieveBackupDataFromSoftApConfiguration: Retrieve the configuration data to be backed up.
54  * 2. retrieveSoftApConfigurationFromBackupData: Restore the configuration using the provided data.
55  * The byte stream to be backed up is versioned to migrate the data easily across
56  * revisions.
57  */
58 public class SoftApBackupRestore {
59     private static final String TAG = "SoftApBackupRestore";
60 
61     /**
62      * Current backup data version.
63      */
64     // Starting from SoftAp data backup version 9, framework support to back up configuration
65     // in XML format. This allows to restore the SoftAp configuration when the user downgrades
66     // the Android version. (From Any version >= 9 to version#9)
67     private static final int SUPPORTED_SAP_BACKUP_XML_DATA_VERSION = 9;
68     private static final int LAST_SAP_BACKUP_DATA_VERSION_IN_S = 8;
69     private static final int LAST_SAP_BACKUP_DATA_VERSION_IN_R = 7;
70     private static final String XML_TAG_DOCUMENT_HEADER = "SoftApBackupData";
71 
72 
73     private static final int ETHER_ADDR_LEN = 6; // Byte array size of MacAddress
74 
75     private final Context mContext;
76     private final SettingsMigrationDataHolder mSettingsMigrationDataHolder;
77 
SoftApBackupRestore(Context context, SettingsMigrationDataHolder settingsMigrationDataHolder)78     public SoftApBackupRestore(Context context,
79             SettingsMigrationDataHolder settingsMigrationDataHolder) {
80         mContext = context;
81         mSettingsMigrationDataHolder = settingsMigrationDataHolder;
82     }
83 
84     /**
85      * Retrieve a byte stream representing the data that needs to be backed up from the
86      * provided softap configuration.
87      *
88      * @param config saved soft ap config that needs to be backed up.
89      * @return Raw byte stream that needs to be backed up.
90      */
retrieveBackupDataFromSoftApConfiguration(SoftApConfiguration config)91     public byte[] retrieveBackupDataFromSoftApConfiguration(SoftApConfiguration config) {
92         if (config == null) {
93             Log.e(TAG, "Invalid configuration received");
94             return new byte[0];
95         }
96         try {
97             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
98             final DataOutputStream out = new DataOutputStream(baos);
99             if (SdkLevel.isAtLeastT()) {
100                 out.writeInt(SUPPORTED_SAP_BACKUP_XML_DATA_VERSION);
101                 final XmlSerializer xmlOut = new FastXmlSerializer();
102                 xmlOut.setOutput(baos, StandardCharsets.UTF_8.name());
103                 XmlUtil.writeDocumentStart(xmlOut, XML_TAG_DOCUMENT_HEADER);
104 
105                 // Start writing the XML stream.
106                 XmlUtil.SoftApConfigurationXmlUtil.writeSoftApConfigurationToXml(xmlOut, config);
107 
108                 XmlUtil.writeDocumentEnd(xmlOut, XML_TAG_DOCUMENT_HEADER);
109 
110             } else {
111                 if (SdkLevel.isAtLeastS()) {
112                     out.writeInt(LAST_SAP_BACKUP_DATA_VERSION_IN_S);
113                 } else {
114                     out.writeInt(LAST_SAP_BACKUP_DATA_VERSION_IN_R);
115                 }
116                 BackupUtils.writeString(out, config.getSsid());
117                 out.writeInt(config.getBand());
118                 out.writeInt(config.getChannel());
119                 BackupUtils.writeString(out, config.getPassphrase());
120                 out.writeInt(config.getSecurityType());
121                 out.writeBoolean(config.isHiddenSsid());
122                 out.writeInt(config.getMaxNumberOfClients());
123                 out.writeLong(config.getShutdownTimeoutMillis());
124                 out.writeBoolean(config.isClientControlByUserEnabled());
125                 writeMacAddressList(out, config.getBlockedClientList());
126                 writeMacAddressList(out, config.getAllowedClientList());
127                 out.writeBoolean(config.isAutoShutdownEnabled());
128                 if (SdkLevel.isAtLeastS()) {
129                     out.writeBoolean(config.isBridgedModeOpportunisticShutdownEnabled());
130                     out.writeInt(config.getMacRandomizationSetting());
131                     SparseIntArray channels = config.getChannels();
132                     int numOfChannels = channels.size();
133                     out.writeInt(numOfChannels);
134                     for (int i = 0; i < numOfChannels; i++) {
135                         out.writeInt(channels.keyAt(i));
136                         out.writeInt(channels.valueAt(i));
137                     }
138                     out.writeBoolean(config.isIeee80211axEnabled());
139                 }
140             }
141             return baos.toByteArray();
142         } catch (IOException | XmlPullParserException e) {
143             Log.e(TAG, "Error retrieving the backup data from SoftApConfiguration: " +  config
144                     + ", exception " + e);
145         }
146         return new byte[0];
147     }
148 
149     /**
150      * Parse out the configurations from the back up data.
151      *
152      * @param data raw byte stream representing the data.
153      * @return Soft ap config retrieved from the backed up data.
154      */
retrieveSoftApConfigurationFromBackupData(byte[] data)155     public SoftApConfiguration retrieveSoftApConfigurationFromBackupData(byte[] data) {
156         if (data == null || data.length == 0) {
157             Log.e(TAG, "Invalid backup data received");
158             return null;
159         }
160         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
161         try {
162             DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
163             int version = in.readInt();
164             // Starting from T, frameworks support to downgrade restore configuration.
165             if ((!SdkLevel.isAtLeastT() && version > LAST_SAP_BACKUP_DATA_VERSION_IN_S)
166                     || version < 1) {
167                 throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
168             }
169 
170             if (version == 1) return null; // Version 1 is a bad dataset.
171             Log.i(TAG, "The backed-up version is " + version);
172 
173             if (version >= SUPPORTED_SAP_BACKUP_XML_DATA_VERSION) {
174                 final XmlPullParser xmlIn = Xml.newPullParser();
175                 ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
176                 // The first 4 bytes are designed to store version
177                 inputStream.skip(Integer.BYTES);
178                 xmlIn.setInput(inputStream, StandardCharsets.UTF_8.name());
179                 XmlUtil.gotoDocumentStart(xmlIn, XML_TAG_DOCUMENT_HEADER);
180                 int rootTagDepth = xmlIn.getDepth();
181                 return XmlUtil.SoftApConfigurationXmlUtil
182                         .parseFromXml(xmlIn, rootTagDepth, mSettingsMigrationDataHolder);
183             }
184             configBuilder.setSsid(BackupUtils.readString(in));
185 
186             int band;
187             if (version < 4) {
188                 band = ApConfigUtil.convertWifiConfigBandToSoftApConfigBand(in.readInt());
189             } else {
190                 band = in.readInt();
191             }
192             int channel = in.readInt();
193             if (channel == 0) {
194                 configBuilder.setBand(band);
195             } else {
196                 configBuilder.setChannel(channel, band);
197             }
198             String passphrase = BackupUtils.readString(in);
199             int securityType = in.readInt();
200             if (version < 4 && securityType == WifiConfiguration.KeyMgmt.WPA2_PSK) {
201                 configBuilder.setPassphrase(passphrase, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
202             } else if (version >= 4 && securityType != SoftApConfiguration.SECURITY_TYPE_OPEN) {
203                 configBuilder.setPassphrase(passphrase, securityType);
204             }
205             if (version >= 3) {
206                 configBuilder.setHiddenSsid(in.readBoolean());
207             }
208             if (version >= 5) {
209                 configBuilder.setMaxNumberOfClients(in.readInt());
210                 long shutDownMillis;
211                 if (version >= 7) {
212                     shutDownMillis = in.readLong();
213                 } else {
214                     shutDownMillis = Long.valueOf(in.readInt());
215                 }
216                 if (shutDownMillis == 0 && Compatibility.isChangeEnabled(
217                         SoftApConfiguration.REMOVE_ZERO_FOR_TIMEOUT_SETTING)) {
218                     shutDownMillis = SoftApConfiguration.DEFAULT_TIMEOUT;
219                 }
220                 configBuilder.setShutdownTimeoutMillis(shutDownMillis);
221                 configBuilder.setClientControlByUserEnabled(in.readBoolean());
222                 int numberOfBlockedClient = in.readInt();
223                 List<MacAddress> blockedList = new ArrayList<>(
224                         macAddressListFromByteArray(in, numberOfBlockedClient));
225                 int numberOfAllowedClient = in.readInt();
226                 List<MacAddress> allowedList = new ArrayList<>(
227                         macAddressListFromByteArray(in, numberOfAllowedClient));
228                 configBuilder.setBlockedClientList(blockedList);
229                 configBuilder.setAllowedClientList(allowedList);
230             }
231             if (version >= 6) {
232                 configBuilder.setAutoShutdownEnabled(in.readBoolean());
233             } else {
234                 // Migrate data out of settings.
235                 WifiMigration.SettingsMigrationData migrationData =
236                         mSettingsMigrationDataHolder.retrieveData();
237                 if (migrationData == null) {
238                     Log.e(TAG, "No migration data present");
239                 } else {
240                     configBuilder.setAutoShutdownEnabled(migrationData.isSoftApTimeoutEnabled());
241                 }
242             }
243             if (version >= 8 && SdkLevel.isAtLeastS()) {
244                 configBuilder.setBridgedModeOpportunisticShutdownEnabled(in.readBoolean());
245                 configBuilder.setMacRandomizationSetting(in.readInt());
246                 int numOfChannels = in.readInt();
247                 SparseIntArray channels = new SparseIntArray(numOfChannels);
248                 for (int i = 0; i < numOfChannels; i++) {
249                     channels.put(in.readInt(), in.readInt());
250                 }
251                 configBuilder.setChannels(channels);
252                 configBuilder.setIeee80211axEnabled(in.readBoolean());
253             }
254             return configBuilder.build();
255         } catch (IOException | BackupUtils.BadVersionException
256                 | IllegalArgumentException | XmlPullParserException e) {
257             Log.e(TAG, "Invalid backup data received, Exception: " + e);
258         }
259         return null;
260     }
261 
writeMacAddressList(DataOutputStream out, List<MacAddress> macList)262     private void writeMacAddressList(DataOutputStream out, List<MacAddress> macList)
263             throws IOException {
264         out.writeInt(macList.size());
265         Iterator<MacAddress> iterator = macList.iterator();
266         while (iterator.hasNext()) {
267             byte[] mac = iterator.next().toByteArray();
268             out.write(mac, 0, ETHER_ADDR_LEN);
269         }
270     }
271 
macAddressListFromByteArray(DataInputStream in, int numberOfClients)272     private List<MacAddress> macAddressListFromByteArray(DataInputStream in, int numberOfClients)
273             throws IOException {
274         List<MacAddress> macList = new ArrayList<>();
275         for (int i = 0; i < numberOfClients; i++) {
276             byte[] mac = new byte[ETHER_ADDR_LEN];
277             in.read(mac, 0, ETHER_ADDR_LEN);
278             macList.add(MacAddress.fromBytes(mac));
279         }
280         return macList;
281     }
282 }
283