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.content.Context;
20 import android.net.MacAddress;
21 import android.net.wifi.SoftApConfiguration;
22 import android.net.wifi.WifiConfiguration;
23 import android.net.wifi.WifiMigration;
24 import android.util.BackupUtils;
25 import android.util.Log;
26 import android.util.SparseIntArray;
27 
28 import com.android.modules.utils.build.SdkLevel;
29 import com.android.server.wifi.util.ApConfigUtil;
30 import com.android.server.wifi.util.SettingsMigrationDataHolder;
31 
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.DataInputStream;
35 import java.io.DataOutputStream;
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.Iterator;
39 import java.util.List;
40 
41 /**
42  * Class used to backup/restore data using the SettingsBackupAgent.
43  * There are 2 symmetric API's exposed here:
44  * 1. retrieveBackupDataFromSoftApConfiguration: Retrieve the configuration data to be backed up.
45  * 2. retrieveSoftApConfigurationFromBackupData: Restore the configuration using the provided data.
46  * The byte stream to be backed up is versioned to migrate the data easily across
47  * revisions.
48  */
49 public class SoftApBackupRestore {
50     private static final String TAG = "SoftApBackupRestore";
51 
52     /**
53      * Current backup data version.
54      */
55     private static final int CURRENT_SAP_BACKUP_DATA_VERSION = 8;
56     private static final int LAST_SAP_BACKUP_DATA_VERSION_IN_R = 7;
57 
58     private static final int ETHER_ADDR_LEN = 6; // Byte array size of MacAddress
59 
60     private final Context mContext;
61     private final SettingsMigrationDataHolder mSettingsMigrationDataHolder;
62 
SoftApBackupRestore(Context context, SettingsMigrationDataHolder settingsMigrationDataHolder)63     public SoftApBackupRestore(Context context,
64             SettingsMigrationDataHolder settingsMigrationDataHolder) {
65         mContext = context;
66         mSettingsMigrationDataHolder = settingsMigrationDataHolder;
67     }
68 
69     /**
70      * Retrieve a byte stream representing the data that needs to be backed up from the
71      * provided softap configuration.
72      *
73      * @param config saved soft ap config that needs to be backed up.
74      * @return Raw byte stream that needs to be backed up.
75      */
retrieveBackupDataFromSoftApConfiguration(SoftApConfiguration config)76     public byte[] retrieveBackupDataFromSoftApConfiguration(SoftApConfiguration config) {
77         if (config == null) {
78             Log.e(TAG, "Invalid configuration received");
79             return new byte[0];
80         }
81         ByteArrayOutputStream baos = new ByteArrayOutputStream();
82         try {
83             DataOutputStream out = new DataOutputStream(baos);
84             if (SdkLevel.isAtLeastS()) {
85                 out.writeInt(CURRENT_SAP_BACKUP_DATA_VERSION);
86             } else {
87                 out.writeInt(LAST_SAP_BACKUP_DATA_VERSION_IN_R);
88             }
89             BackupUtils.writeString(out, config.getSsid());
90             out.writeInt(config.getBand());
91             out.writeInt(config.getChannel());
92             BackupUtils.writeString(out, config.getPassphrase());
93             out.writeInt(config.getSecurityType());
94             out.writeBoolean(config.isHiddenSsid());
95             out.writeInt(config.getMaxNumberOfClients());
96             out.writeLong(config.getShutdownTimeoutMillis());
97             out.writeBoolean(config.isClientControlByUserEnabled());
98             writeMacAddressList(out, config.getBlockedClientList());
99             writeMacAddressList(out, config.getAllowedClientList());
100             out.writeBoolean(config.isAutoShutdownEnabled());
101             if (SdkLevel.isAtLeastS()) {
102                 out.writeBoolean(config.isBridgedModeOpportunisticShutdownEnabled());
103                 out.writeInt(config.getMacRandomizationSetting());
104                 SparseIntArray channels = config.getChannels();
105                 int numOfChannels = channels.size();
106                 out.writeInt(numOfChannels);
107                 for (int i = 0; i < numOfChannels; i++) {
108                     out.writeInt(channels.keyAt(i));
109                     out.writeInt(channels.valueAt(i));
110                 }
111                 out.writeBoolean(config.isIeee80211axEnabled());
112             }
113 
114         } catch (IOException io) {
115             Log.e(TAG, "Invalid configuration received, IOException " + io);
116             return new byte[0];
117         }
118         return baos.toByteArray();
119     }
120 
121     /**
122      * Parse out the configurations from the back up data.
123      *
124      * @param data raw byte stream representing the data.
125      * @return Soft ap config retrieved from the backed up data.
126      */
retrieveSoftApConfigurationFromBackupData(byte[] data)127     public SoftApConfiguration retrieveSoftApConfigurationFromBackupData(byte[] data) {
128         if (data == null || data.length == 0) {
129             Log.e(TAG, "Invalid backup data received");
130             return null;
131         }
132         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
133         try {
134             DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
135             int version = in.readInt();
136             if (version < 1 || version > CURRENT_SAP_BACKUP_DATA_VERSION) {
137                 throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
138             }
139 
140             if (version == 1) return null; // Version 1 is a bad dataset.
141 
142             configBuilder.setSsid(BackupUtils.readString(in));
143 
144             int band;
145             if (version < 4) {
146                 band = ApConfigUtil.convertWifiConfigBandToSoftApConfigBand(in.readInt());
147             } else {
148                 band = in.readInt();
149             }
150             int channel = in.readInt();
151 
152             if (channel == 0) {
153                 configBuilder.setBand(band);
154             } else {
155                 configBuilder.setChannel(channel, band);
156             }
157             String passphrase = BackupUtils.readString(in);
158             int securityType = in.readInt();
159             if (version < 4 && securityType == WifiConfiguration.KeyMgmt.WPA2_PSK) {
160                 configBuilder.setPassphrase(passphrase, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
161             } else if (version >= 4 && securityType != SoftApConfiguration.SECURITY_TYPE_OPEN) {
162                 configBuilder.setPassphrase(passphrase, securityType);
163             }
164             if (version >= 3) {
165                 configBuilder.setHiddenSsid(in.readBoolean());
166             }
167             if (version >= 5) {
168                 configBuilder.setMaxNumberOfClients(in.readInt());
169                 if (version >= 7) {
170                     configBuilder.setShutdownTimeoutMillis(in.readLong());
171                 } else {
172                     configBuilder.setShutdownTimeoutMillis(Long.valueOf(in.readInt()));
173                 }
174                 configBuilder.setClientControlByUserEnabled(in.readBoolean());
175                 int numberOfBlockedClient = in.readInt();
176                 List<MacAddress> blockedList = new ArrayList<>(
177                         macAddressListFromByteArray(in, numberOfBlockedClient));
178                 int numberOfAllowedClient = in.readInt();
179                 List<MacAddress> allowedList = new ArrayList<>(
180                         macAddressListFromByteArray(in, numberOfAllowedClient));
181                 configBuilder.setBlockedClientList(blockedList);
182                 configBuilder.setAllowedClientList(allowedList);
183             }
184             if (version >= 6) {
185                 configBuilder.setAutoShutdownEnabled(in.readBoolean());
186             } else {
187                 // Migrate data out of settings.
188                 WifiMigration.SettingsMigrationData migrationData =
189                         mSettingsMigrationDataHolder.retrieveData();
190                 if (migrationData == null) {
191                     Log.e(TAG, "No migration data present");
192                 } else {
193                     configBuilder.setAutoShutdownEnabled(migrationData.isSoftApTimeoutEnabled());
194                 }
195             }
196             if (version >= 8 && SdkLevel.isAtLeastS()) {
197                 configBuilder.setBridgedModeOpportunisticShutdownEnabled(in.readBoolean());
198                 configBuilder.setMacRandomizationSetting(in.readInt());
199                 int numOfChannels = in.readInt();
200                 SparseIntArray channels = new SparseIntArray(numOfChannels);
201                 for (int i = 0; i < numOfChannels; i++) {
202                     channels.put(in.readInt(), in.readInt());
203                 }
204                 configBuilder.setChannels(channels);
205                 configBuilder.setIeee80211axEnabled(in.readBoolean());
206             }
207         } catch (IOException io) {
208             Log.e(TAG, "Invalid backup data received, IOException: " + io);
209             return null;
210         } catch (BackupUtils.BadVersionException badVersion) {
211             Log.e(TAG, "Invalid backup data received, BadVersionException: " + badVersion);
212             return null;
213         } catch (IllegalArgumentException ie) {
214             Log.e(TAG, "Invalid backup data received, IllegalArgumentException " + ie);
215             return null;
216         }
217         return configBuilder.build();
218     }
219 
writeMacAddressList(DataOutputStream out, List<MacAddress> macList)220     private void writeMacAddressList(DataOutputStream out, List<MacAddress> macList)
221             throws IOException {
222         out.writeInt(macList.size());
223         Iterator<MacAddress> iterator = macList.iterator();
224         while (iterator.hasNext()) {
225             byte[] mac = iterator.next().toByteArray();
226             out.write(mac, 0, ETHER_ADDR_LEN);
227         }
228     }
229 
macAddressListFromByteArray(DataInputStream in, int numberOfClients)230     private List<MacAddress> macAddressListFromByteArray(DataInputStream in, int numberOfClients)
231             throws IOException {
232         List<MacAddress> macList = new ArrayList<>();
233         for (int i = 0; i < numberOfClients; i++) {
234             byte[] mac = new byte[ETHER_ADDR_LEN];
235             in.read(mac, 0, ETHER_ADDR_LEN);
236             macList.add(MacAddress.fromBytes(mac));
237         }
238         return macList;
239     }
240 }
241