• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 static java.lang.Math.toIntExact;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.AlarmManager;
25 import android.content.Context;
26 import android.net.wifi.WifiMigration;
27 import android.os.Handler;
28 import android.os.UserHandle;
29 import android.util.AtomicFile;
30 import android.util.Log;
31 import android.util.SparseArray;
32 import android.util.Xml;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.util.FastXmlSerializer;
36 import com.android.internal.util.Preconditions;
37 import com.android.server.wifi.util.EncryptedData;
38 import com.android.server.wifi.util.Environment;
39 import com.android.server.wifi.util.FileUtils;
40 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
41 import com.android.server.wifi.util.XmlUtil;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 import org.xmlpull.v1.XmlSerializer;
46 
47 import java.io.ByteArrayInputStream;
48 import java.io.ByteArrayOutputStream;
49 import java.io.File;
50 import java.io.FileDescriptor;
51 import java.io.FileNotFoundException;
52 import java.io.FileOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.PrintWriter;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.nio.charset.StandardCharsets;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Objects;
65 import java.util.Set;
66 import java.util.stream.Collectors;
67 import java.util.stream.Stream;
68 
69 /**
70  * This class provides a mechanism to save data to persistent store files {@link StoreFile}.
71  * Modules can register a {@link StoreData} instance indicating the {@StoreFile} into which they
72  * want to save their data to.
73  *
74  * NOTE:
75  * <li>Modules can register their {@StoreData} using
76  * {@link WifiConfigStore#registerStoreData(StoreData)} directly, but should
77  * use {@link WifiConfigManager#saveToStore(boolean)} for any writes.</li>
78  * <li>{@link WifiConfigManager} controls {@link WifiConfigStore} and initiates read at bootup and
79  * store file changes on user switch.</li>
80  * <li>Not thread safe!</li>
81  */
82 public class WifiConfigStore {
83     /**
84      * Config store file for general shared store file.
85      */
86     public static final int STORE_FILE_SHARED_GENERAL = 0;
87     /**
88      * Config store file for softap shared store file.
89      */
90     public static final int STORE_FILE_SHARED_SOFTAP = 1;
91     /**
92      * Config store file for general user store file.
93      */
94     public static final int STORE_FILE_USER_GENERAL = 2;
95     /**
96      * Config store file for network suggestions user store file.
97      */
98     public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3;
99 
100     @IntDef(prefix = { "STORE_FILE_" }, value = {
101             STORE_FILE_SHARED_GENERAL,
102             STORE_FILE_SHARED_SOFTAP,
103             STORE_FILE_USER_GENERAL,
104             STORE_FILE_USER_NETWORK_SUGGESTIONS
105     })
106     @Retention(RetentionPolicy.SOURCE)
107     public @interface StoreFileId { }
108 
109     private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
110     private static final String XML_TAG_VERSION = "Version";
111     private static final String XML_TAG_HEADER_INTEGRITY = "Integrity";
112     /**
113      * Current config store data version. This will be incremented for any additions.
114      */
115     private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 3;
116     /** This list of older versions will be used to restore data from older config store. */
117     /**
118      * First version of the config store data format.
119      */
120     public static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1;
121     /**
122      * Second version of the config store data format, introduced:
123      *  - Integrity info.
124      */
125     public static final int INTEGRITY_CONFIG_STORE_DATA_VERSION = 2;
126     /**
127      * Third version of the config store data format,
128      * introduced:
129      *  - Encryption of credentials
130      * removed:
131      *  - Integrity info.
132      */
133     public static final int ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION = 3;
134 
135     @IntDef(suffix = { "_VERSION" }, value = {
136             INITIAL_CONFIG_STORE_DATA_VERSION,
137             INTEGRITY_CONFIG_STORE_DATA_VERSION,
138             ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION
139     })
140     @Retention(RetentionPolicy.SOURCE)
141     public @interface Version { }
142 
143     /**
144      * Alarm tag to use for starting alarms for buffering file writes.
145      */
146     @VisibleForTesting
147     public static final String BUFFERED_WRITE_ALARM_TAG = "WriteBufferAlarm";
148     /**
149      * Log tag.
150      */
151     private static final String TAG = "WifiConfigStore";
152     /**
153      * Time interval for buffering file writes for non-forced writes
154      */
155     private static final int BUFFERED_WRITE_ALARM_INTERVAL_MS = 10 * 1000;
156     /**
157      * Config store file name for general shared store file.
158      */
159     private static final String STORE_FILE_NAME_SHARED_GENERAL = "WifiConfigStore.xml";
160     /**
161      * Config store file name for SoftAp shared store file.
162      */
163     private static final String STORE_FILE_NAME_SHARED_SOFTAP = "WifiConfigStoreSoftAp.xml";
164     /**
165      * Config store file name for general user store file.
166      */
167     private static final String STORE_FILE_NAME_USER_GENERAL = "WifiConfigStore.xml";
168     /**
169      * Config store file name for network suggestions user store file.
170      */
171     private static final String STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS =
172             "WifiConfigStoreNetworkSuggestions.xml";
173     /**
174      * Mapping of Store file Id to Store file names.
175      */
176     private static final SparseArray<String> STORE_ID_TO_FILE_NAME =
177             new SparseArray<String>() {{
178                 put(STORE_FILE_SHARED_GENERAL, STORE_FILE_NAME_SHARED_GENERAL);
179                 put(STORE_FILE_SHARED_SOFTAP, STORE_FILE_NAME_SHARED_SOFTAP);
180                 put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL);
181                 put(STORE_FILE_USER_NETWORK_SUGGESTIONS, STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS);
182             }};
183     /**
184      * Handler instance to post alarm timeouts to
185      */
186     private final Handler mEventHandler;
187     /**
188      * Alarm manager instance to start buffer timeout alarms.
189      */
190     private final AlarmManager mAlarmManager;
191     /**
192      * Clock instance to retrieve timestamps for alarms.
193      */
194     private final Clock mClock;
195     private final WifiMetrics mWifiMetrics;
196     /**
197      * Shared config store file instance. There are 2 shared store files:
198      * {@link #STORE_FILE_NAME_SHARED_GENERAL} & {@link #STORE_FILE_NAME_SHARED_SOFTAP}.
199      */
200     private final List<StoreFile> mSharedStores;
201     /**
202      * User specific store file instances. There are 2 user store files:
203      * {@link #STORE_FILE_NAME_USER_GENERAL} & {@link #STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS}.
204      */
205     private List<StoreFile> mUserStores;
206     /**
207      * Verbose logging flag.
208      */
209     private boolean mVerboseLoggingEnabled = false;
210     /**
211      * Flag to indicate if there is a buffered write pending.
212      */
213     private boolean mBufferedWritePending = false;
214     /**
215      * Alarm listener for flushing out any buffered writes.
216      */
217     private final AlarmManager.OnAlarmListener mBufferedWriteListener =
218             new AlarmManager.OnAlarmListener() {
219                 public void onAlarm() {
220                     try {
221                         writeBufferedData();
222                     } catch (IOException e) {
223                         Log.wtf(TAG, "Buffered write failed", e);
224                     }
225                 }
226             };
227 
228     /**
229      * List of data containers.
230      */
231     private final List<StoreData> mStoreDataList;
232 
233     /**
234      * Create a new instance of WifiConfigStore.
235      * Note: The store file instances have been made inputs to this class to ease unit-testing.
236      *
237      * @param context     context to use for retrieving the alarm manager.
238      * @param handler     handler instance to post alarm timeouts to.
239      * @param clock       clock instance to retrieve timestamps for alarms.
240      * @param wifiMetrics Metrics instance.
241      * @param sharedStores List of {@link StoreFile} instances pointing to the shared store files.
242      *                     This should be retrieved using {@link #createSharedFiles(boolean)}
243      *                     method.
244      */
WifiConfigStore(Context context, Handler handler, Clock clock, WifiMetrics wifiMetrics, List<StoreFile> sharedStores)245     public WifiConfigStore(Context context, Handler handler, Clock clock, WifiMetrics wifiMetrics,
246             List<StoreFile> sharedStores) {
247 
248         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
249         mEventHandler = handler;
250         mClock = clock;
251         mWifiMetrics = wifiMetrics;
252         mStoreDataList = new ArrayList<>();
253 
254         // Initialize the store files.
255         mSharedStores = sharedStores;
256         // The user store is initialized to null, this will be set when the user unlocks and
257         // CE storage is accessible via |switchUserStoresAndRead|.
258         mUserStores = null;
259     }
260 
261     /**
262      * Set the user store files.
263      * (Useful for mocking in unit tests).
264      * @param userStores List of {@link StoreFile} created using
265      * {@link #createUserFiles(int, boolean)}.
266      */
setUserStores(@onNull List<StoreFile> userStores)267     public void setUserStores(@NonNull List<StoreFile> userStores) {
268         Preconditions.checkNotNull(userStores);
269         mUserStores = userStores;
270     }
271 
272     /**
273      * Register a {@link StoreData} to read/write data from/to a store. A {@link StoreData} is
274      * responsible for a block of data in the store file, and provides serialization/deserialization
275      * functions for those data.
276      *
277      * @param storeData The store data to be registered to the config store
278      * @return true if registered successfully, false if the store file name is not valid.
279      */
registerStoreData(@onNull StoreData storeData)280     public boolean registerStoreData(@NonNull StoreData storeData) {
281         if (storeData == null) {
282             Log.e(TAG, "Unable to register null store data");
283             return false;
284         }
285         int storeFileId = storeData.getStoreFileId();
286         if (STORE_ID_TO_FILE_NAME.get(storeFileId) == null) {
287             Log.e(TAG, "Invalid shared store file specified" + storeFileId);
288             return false;
289         }
290         mStoreDataList.add(storeData);
291         return true;
292     }
293 
294     /**
295      * Helper method to create a store file instance for either the shared store or user store.
296      * Note: The method creates the store directory if not already present. This may be needed for
297      * user store files.
298      *
299      * @param storeDir Base directory under which the store file is to be stored. The store file
300      *                 will be at <storeDir>/WifiConfigStore.xml.
301      * @param fileId Identifier for the file. See {@link StoreFileId}.
302      * @param userHandle User handle. Meaningful only for user specific store files.
303      * @param shouldEncryptCredentials Whether to encrypt credentials or not.
304      * @return new instance of the store file or null if the directory cannot be created.
305      */
createFile(@onNull File storeDir, @StoreFileId int fileId, UserHandle userHandle, boolean shouldEncryptCredentials)306     private static @Nullable StoreFile createFile(@NonNull File storeDir,
307             @StoreFileId int fileId, UserHandle userHandle, boolean shouldEncryptCredentials) {
308         if (!storeDir.exists()) {
309             if (!storeDir.mkdir()) {
310                 Log.w(TAG, "Could not create store directory " + storeDir);
311                 return null;
312             }
313         }
314         File file = new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId));
315         WifiConfigStoreEncryptionUtil encryptionUtil = null;
316         if (shouldEncryptCredentials) {
317             encryptionUtil = new WifiConfigStoreEncryptionUtil(file.getName());
318         }
319         return new StoreFile(file, fileId, userHandle, encryptionUtil);
320     }
321 
createFiles(File storeDir, List<Integer> storeFileIds, UserHandle userHandle, boolean shouldEncryptCredentials)322     private static @Nullable List<StoreFile> createFiles(File storeDir, List<Integer> storeFileIds,
323             UserHandle userHandle, boolean shouldEncryptCredentials) {
324         List<StoreFile> storeFiles = new ArrayList<>();
325         for (int fileId : storeFileIds) {
326             StoreFile storeFile =
327                     createFile(storeDir, fileId, userHandle, shouldEncryptCredentials);
328             if (storeFile == null) {
329                 return null;
330             }
331             storeFiles.add(storeFile);
332         }
333         return storeFiles;
334     }
335 
336     /**
337      * Create a new instance of the shared store file.
338      *
339      * @param shouldEncryptCredentials Whether to encrypt credentials or not.
340      * @return new instance of the store file or null if the directory cannot be created.
341      */
createSharedFiles(boolean shouldEncryptCredentials)342     public static @NonNull List<StoreFile> createSharedFiles(boolean shouldEncryptCredentials) {
343         return createFiles(
344                 Environment.getWifiSharedDirectory(),
345                 Arrays.asList(STORE_FILE_SHARED_GENERAL, STORE_FILE_SHARED_SOFTAP),
346                 UserHandle.ALL,
347                 shouldEncryptCredentials);
348     }
349 
350     /**
351      * Create new instances of the user specific store files.
352      * The user store file is inside the user's encrypted data directory.
353      *
354      * @param userId userId corresponding to the currently logged-in user.
355      * @param shouldEncryptCredentials Whether to encrypt credentials or not.
356      * @return List of new instances of the store files created or null if the directory cannot be
357      * created.
358      */
createUserFiles(int userId, boolean shouldEncryptCredentials)359     public static @Nullable List<StoreFile> createUserFiles(int userId,
360             boolean shouldEncryptCredentials) {
361         UserHandle userHandle = UserHandle.of(userId);
362         return createFiles(
363                 Environment.getWifiUserDirectory(userId),
364                 Arrays.asList(STORE_FILE_USER_GENERAL, STORE_FILE_USER_NETWORK_SUGGESTIONS),
365                 userHandle,
366                 shouldEncryptCredentials);
367     }
368 
369     /**
370      * Enable verbose logging.
371      */
enableVerboseLogging(boolean verbose)372     public void enableVerboseLogging(boolean verbose) {
373         mVerboseLoggingEnabled = verbose;
374     }
375 
376     /**
377      * Retrieve the list of {@link StoreData} instances registered for the provided
378      * {@link StoreFile}.
379      */
retrieveStoreDataListForStoreFile(@onNull StoreFile storeFile)380     private List<StoreData> retrieveStoreDataListForStoreFile(@NonNull StoreFile storeFile) {
381         return mStoreDataList
382                 .stream()
383                 .filter(s -> s.getStoreFileId() == storeFile.getFileId())
384                 .collect(Collectors.toList());
385     }
386 
387     /**
388      * Check if any of the provided list of {@link StoreData} instances registered
389      * for the provided {@link StoreFile }have indicated that they have new data to serialize.
390      */
hasNewDataToSerialize(@onNull StoreFile storeFile)391     private boolean hasNewDataToSerialize(@NonNull StoreFile storeFile) {
392         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
393         return storeDataList.stream().anyMatch(s -> s.hasNewDataToSerialize());
394     }
395 
396     /**
397      * API to write the data provided by registered store data to config stores.
398      * The method writes the user specific configurations to user specific config store and the
399      * shared configurations to shared config store.
400      *
401      * @param forceSync boolean to force write the config stores now. if false, the writes are
402      *                  buffered and written after the configured interval.
403      */
write(boolean forceSync)404     public void write(boolean forceSync)
405             throws XmlPullParserException, IOException {
406         boolean hasAnyNewData = false;
407         // Serialize the provided data and send it to the respective stores. The actual write will
408         // be performed later depending on the |forceSync| flag .
409         for (StoreFile sharedStoreFile : mSharedStores) {
410             if (hasNewDataToSerialize(sharedStoreFile)) {
411                 byte[] sharedDataBytes = serializeData(sharedStoreFile);
412                 sharedStoreFile.storeRawDataToWrite(sharedDataBytes);
413                 hasAnyNewData = true;
414             }
415         }
416         if (mUserStores != null) {
417             for (StoreFile userStoreFile : mUserStores) {
418                 if (hasNewDataToSerialize(userStoreFile)) {
419                     byte[] userDataBytes = serializeData(userStoreFile);
420                     userStoreFile.storeRawDataToWrite(userDataBytes);
421                     hasAnyNewData = true;
422                 }
423             }
424         }
425 
426         if (hasAnyNewData) {
427             // Every write provides a new snapshot to be persisted, so |forceSync| flag overrides
428             // any pending buffer writes.
429             if (forceSync) {
430                 writeBufferedData();
431             } else {
432                 startBufferedWriteAlarm();
433             }
434         } else if (forceSync && mBufferedWritePending) {
435             // no new data to write, but there is a pending buffered write. So, |forceSync| should
436             // flush that out.
437             writeBufferedData();
438         }
439     }
440 
441     /**
442      * Serialize all the data from all the {@link StoreData} clients registered for the provided
443      * {@link StoreFile}.
444      *
445      * This method also computes the integrity of the data being written and serializes the computed
446      * {@link EncryptedData} to the output.
447      *
448      * @param storeFile StoreFile that we want to write to.
449      * @return byte[] of serialized bytes
450      * @throws XmlPullParserException
451      * @throws IOException
452      */
serializeData(@onNull StoreFile storeFile)453     private byte[] serializeData(@NonNull StoreFile storeFile)
454             throws XmlPullParserException, IOException {
455         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
456 
457         final XmlSerializer out = new FastXmlSerializer();
458         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
459         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
460 
461         // First XML header.
462         XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
463         // Next version.
464         XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION);
465         for (StoreData storeData : storeDataList) {
466             String tag = storeData.getName();
467             XmlUtil.writeNextSectionStart(out, tag);
468             storeData.serializeData(out, storeFile.getEncryptionUtil());
469             XmlUtil.writeNextSectionEnd(out, tag);
470         }
471         XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
472         return outputStream.toByteArray();
473     }
474 
475     /**
476      * Helper method to start a buffered write alarm if one doesn't already exist.
477      */
startBufferedWriteAlarm()478     private void startBufferedWriteAlarm() {
479         if (!mBufferedWritePending) {
480             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
481                     mClock.getElapsedSinceBootMillis() + BUFFERED_WRITE_ALARM_INTERVAL_MS,
482                     BUFFERED_WRITE_ALARM_TAG, mBufferedWriteListener, mEventHandler);
483             mBufferedWritePending = true;
484         }
485     }
486 
487     /**
488      * Helper method to stop a buffered write alarm if one exists.
489      */
stopBufferedWriteAlarm()490     private void stopBufferedWriteAlarm() {
491         if (mBufferedWritePending) {
492             mAlarmManager.cancel(mBufferedWriteListener);
493             mBufferedWritePending = false;
494         }
495     }
496 
497     /**
498      * Helper method to actually perform the writes to the file. This flushes out any write data
499      * being buffered in the respective stores and cancels any pending buffer write alarms.
500      */
writeBufferedData()501     private void writeBufferedData() throws IOException {
502         stopBufferedWriteAlarm();
503 
504         long writeStartTime = mClock.getElapsedSinceBootMillis();
505         for (StoreFile sharedStoreFile : mSharedStores) {
506             sharedStoreFile.writeBufferedRawData();
507         }
508         if (mUserStores != null) {
509             for (StoreFile userStoreFile : mUserStores) {
510                 userStoreFile.writeBufferedRawData();
511             }
512         }
513         long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;
514         try {
515             mWifiMetrics.noteWifiConfigStoreWriteDuration(toIntExact(writeTime));
516         } catch (ArithmeticException e) {
517             // Silently ignore on any overflow errors.
518         }
519         Log.d(TAG, "Writing to stores completed in " + writeTime + " ms.");
520     }
521 
522     /**
523      * Note: This is a copy of {@link AtomicFile#readFully()} modified to use the passed in
524      * {@link InputStream} which was returned using {@link AtomicFile#openRead()}.
525      */
readAtomicFileFully(InputStream stream)526     private static byte[] readAtomicFileFully(InputStream stream) throws IOException {
527         try {
528             int pos = 0;
529             int avail = stream.available();
530             byte[] data = new byte[avail];
531             while (true) {
532                 int amt = stream.read(data, pos, data.length - pos);
533                 if (amt <= 0) {
534                     return data;
535                 }
536                 pos += amt;
537                 avail = stream.available();
538                 if (avail > data.length - pos) {
539                     byte[] newData = new byte[pos + avail];
540                     System.arraycopy(data, 0, newData, 0, pos);
541                     data = newData;
542                 }
543             }
544         } finally {
545             stream.close();
546         }
547     }
548 
549     /**
550      * Conversion for file id's to use in WifiMigration API surface.
551      */
getMigrationStoreFileId(@toreFileId int fileId)552     private static Integer getMigrationStoreFileId(@StoreFileId int fileId) {
553         switch (fileId) {
554             case STORE_FILE_SHARED_GENERAL:
555                 return WifiMigration.STORE_FILE_SHARED_GENERAL;
556             case STORE_FILE_SHARED_SOFTAP:
557                 return WifiMigration.STORE_FILE_SHARED_SOFTAP;
558             case STORE_FILE_USER_GENERAL:
559                 return WifiMigration.STORE_FILE_USER_GENERAL;
560             case STORE_FILE_USER_NETWORK_SUGGESTIONS:
561                 return WifiMigration.STORE_FILE_USER_NETWORK_SUGGESTIONS;
562             default:
563                 return null;
564         }
565     }
566 
readDataFromMigrationSharedStoreFile(@toreFileId int fileId)567     private static byte[] readDataFromMigrationSharedStoreFile(@StoreFileId int fileId)
568             throws IOException {
569         Integer migrationStoreFileId = getMigrationStoreFileId(fileId);
570         if (migrationStoreFileId == null) return null;
571         InputStream migrationIs =
572                 WifiMigration.convertAndRetrieveSharedConfigStoreFile(migrationStoreFileId);
573         if (migrationIs == null) return null;
574         return readAtomicFileFully(migrationIs);
575     }
576 
readDataFromMigrationUserStoreFile(@toreFileId int fileId, UserHandle userHandle)577     private static byte[] readDataFromMigrationUserStoreFile(@StoreFileId int fileId,
578             UserHandle userHandle) throws IOException {
579         Integer migrationStoreFileId = getMigrationStoreFileId(fileId);
580         if (migrationStoreFileId == null) return null;
581         InputStream migrationIs =
582                 WifiMigration.convertAndRetrieveUserConfigStoreFile(
583                         migrationStoreFileId, userHandle);
584         if (migrationIs == null) return null;
585         return readAtomicFileFully(migrationIs);
586     }
587 
588     /**
589      * Helper method to read from the shared store files.
590      * @throws XmlPullParserException
591      * @throws IOException
592      */
readFromSharedStoreFiles()593     private void readFromSharedStoreFiles() throws XmlPullParserException, IOException {
594         for (StoreFile sharedStoreFile : mSharedStores) {
595             byte[] sharedDataBytes =
596                     readDataFromMigrationSharedStoreFile(sharedStoreFile.getFileId());
597             if (sharedDataBytes == null) {
598                 // nothing to migrate, do normal read.
599                 sharedDataBytes = sharedStoreFile.readRawData();
600             } else {
601                 Log.i(TAG, "Read data out of shared migration store file: "
602                         + sharedStoreFile.getName());
603                 // Save the migrated file contents to the regular store file and delete the
604                 // migrated stored file.
605                 sharedStoreFile.storeRawDataToWrite(sharedDataBytes);
606                 sharedStoreFile.writeBufferedRawData();
607                 // Note: If the migrated store file is at the same location as the store file,
608                 // then the OEM implementation should ignore this remove.
609                 WifiMigration.removeSharedConfigStoreFile(
610                         getMigrationStoreFileId(sharedStoreFile.getFileId()));
611             }
612             deserializeData(sharedDataBytes, sharedStoreFile);
613         }
614     }
615 
616     /**
617      * Helper method to read from the user store files.
618      * @throws XmlPullParserException
619      * @throws IOException
620      */
readFromUserStoreFiles()621     private void readFromUserStoreFiles() throws XmlPullParserException, IOException {
622         for (StoreFile userStoreFile : mUserStores) {
623             byte[] userDataBytes = readDataFromMigrationUserStoreFile(
624                     userStoreFile.getFileId(), userStoreFile.mUserHandle);
625             if (userDataBytes == null) {
626                 // nothing to migrate, do normal read.
627                 userDataBytes = userStoreFile.readRawData();
628             } else {
629                 Log.i(TAG, "Read data out of user migration store file: "
630                         + userStoreFile.getName());
631                 // Save the migrated file contents to the regular store file and delete the
632                 // migrated stored file.
633                 userStoreFile.storeRawDataToWrite(userDataBytes);
634                 userStoreFile.writeBufferedRawData();
635                 // Note: If the migrated store file is at the same location as the store file,
636                 // then the OEM implementation should ignore this remove.
637                 WifiMigration.removeUserConfigStoreFile(
638                         getMigrationStoreFileId(userStoreFile.getFileId()),
639                         userStoreFile.mUserHandle);
640             }
641             deserializeData(userDataBytes, userStoreFile);
642         }
643     }
644 
645     /**
646      * API to read the store data from the config stores.
647      * The method reads the user specific configurations from user specific config store and the
648      * shared configurations from the shared config store.
649      */
read()650     public void read() throws XmlPullParserException, IOException {
651         // Reset both share and user store data.
652         for (StoreFile sharedStoreFile : mSharedStores) {
653             resetStoreData(sharedStoreFile);
654         }
655         if (mUserStores != null) {
656             for (StoreFile userStoreFile : mUserStores) {
657                 resetStoreData(userStoreFile);
658             }
659         }
660         long readStartTime = mClock.getElapsedSinceBootMillis();
661         readFromSharedStoreFiles();
662         if (mUserStores != null) {
663             readFromUserStoreFiles();
664         }
665         long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
666         try {
667             mWifiMetrics.noteWifiConfigStoreReadDuration(toIntExact(readTime));
668         } catch (ArithmeticException e) {
669             // Silently ignore on any overflow errors.
670         }
671         Log.d(TAG, "Reading from all stores completed in " + readTime + " ms.");
672     }
673 
674     /**
675      * Handles a user switch. This method changes the user specific store files and reads from the
676      * new user's store files.
677      *
678      * @param userStores List of {@link StoreFile} created using {@link #createUserFiles(int)}.
679      */
switchUserStoresAndRead(@onNull List<StoreFile> userStores)680     public void switchUserStoresAndRead(@NonNull List<StoreFile> userStores)
681             throws XmlPullParserException, IOException {
682         Preconditions.checkNotNull(userStores);
683         // Reset user store data.
684         if (mUserStores != null) {
685             for (StoreFile userStoreFile : mUserStores) {
686                 resetStoreData(userStoreFile);
687             }
688         }
689 
690         // Stop any pending buffered writes, if any.
691         stopBufferedWriteAlarm();
692         mUserStores = userStores;
693 
694         // Now read from the user store files.
695         long readStartTime = mClock.getElapsedSinceBootMillis();
696         readFromUserStoreFiles();
697         long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
698         mWifiMetrics.noteWifiConfigStoreReadDuration(toIntExact(readTime));
699         Log.d(TAG, "Reading from user stores completed in " + readTime + " ms.");
700     }
701 
702     /**
703      * Reset data for all {@link StoreData} instances registered for this {@link StoreFile}.
704      */
resetStoreData(@onNull StoreFile storeFile)705     private void resetStoreData(@NonNull StoreFile storeFile) {
706         for (StoreData storeData: retrieveStoreDataListForStoreFile(storeFile)) {
707             storeData.resetData();
708         }
709     }
710 
711     // Inform all the provided store data clients that there is nothing in the store for them.
indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet, @Version int version, @NonNull WifiConfigStoreEncryptionUtil encryptionUtil)712     private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet,
713             @Version int version, @NonNull WifiConfigStoreEncryptionUtil encryptionUtil)
714             throws XmlPullParserException, IOException {
715         for (StoreData storeData : storeDataSet) {
716             storeData.deserializeData(null, 0, version, encryptionUtil);
717         }
718     }
719 
720     /**
721      * Deserialize data from a {@link StoreFile} for all {@link StoreData} instances registered.
722      *
723      * This method also computes the integrity of the incoming |dataBytes| and compare with
724      * {@link EncryptedData} parsed from |dataBytes|. If the integrity check fails, the data
725      * is discarded.
726      *
727      * @param dataBytes The data to parse
728      * @param storeFile StoreFile that we read from. Will be used to retrieve the list of clients
729      *                  who have data to deserialize from this file.
730      *
731      * @throws XmlPullParserException
732      * @throws IOException
733      */
deserializeData(@onNull byte[] dataBytes, @NonNull StoreFile storeFile)734     private void deserializeData(@NonNull byte[] dataBytes, @NonNull StoreFile storeFile)
735             throws XmlPullParserException, IOException {
736         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
737         if (dataBytes == null) {
738             indicateNoDataForStoreDatas(storeDataList, -1 /* unknown */,
739                     storeFile.getEncryptionUtil());
740             return;
741         }
742         final XmlPullParser in = Xml.newPullParser();
743         final ByteArrayInputStream inputStream = new ByteArrayInputStream(dataBytes);
744         in.setInput(inputStream, StandardCharsets.UTF_8.name());
745 
746         // Start parsing the XML stream.
747         int rootTagDepth = in.getDepth() + 1;
748         XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
749 
750         @Version int version = parseVersionFromXml(in);
751         // Version 2 contains the now unused integrity data, parse & then discard the information.
752         if (version == INTEGRITY_CONFIG_STORE_DATA_VERSION) {
753             parseAndDiscardIntegrityDataFromXml(in, rootTagDepth);
754         }
755 
756         String[] headerName = new String[1];
757         Set<StoreData> storeDatasInvoked = new HashSet<>();
758         while (XmlUtil.gotoNextSectionOrEnd(in, headerName, rootTagDepth)) {
759             // There can only be 1 store data matching the tag, O indicates a previous StoreData
760             // module that no longer exists (ignore this XML section).
761             StoreData storeData = storeDataList.stream()
762                     .filter(s -> s.getSectionsToParse().contains(headerName[0]))
763                     .findAny()
764                     .orElse(null);
765             if (storeData == null) {
766                 Log.e(TAG, "Unknown store data: " + headerName[0] + ". List of store data: "
767                         + storeDataList);
768                 continue;
769             }
770             storeData.deserializeDataForSection(in, rootTagDepth + 1, version,
771                     storeFile.getEncryptionUtil(), headerName[0]);
772             storeDatasInvoked.add(storeData);
773         }
774         // Inform all the other registered store data clients that there is nothing in the store
775         // for them.
776         Set<StoreData> storeDatasNotInvoked = new HashSet<>(storeDataList);
777         storeDatasNotInvoked.removeAll(storeDatasInvoked);
778         indicateNoDataForStoreDatas(storeDatasNotInvoked, version, storeFile.getEncryptionUtil());
779     }
780 
781     /**
782      * Parse the version from the XML stream.
783      * This is used for both the shared and user config store data.
784      *
785      * @param in XmlPullParser instance pointing to the XML stream.
786      * @return version number retrieved from the Xml stream.
787      */
parseVersionFromXml(XmlPullParser in)788     private static @Version int parseVersionFromXml(XmlPullParser in)
789             throws XmlPullParserException, IOException {
790         int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
791         if (version < INITIAL_CONFIG_STORE_DATA_VERSION
792                 || version > CURRENT_CONFIG_STORE_DATA_VERSION) {
793             throw new XmlPullParserException("Invalid version of data: " + version);
794         }
795         return version;
796     }
797 
798     /**
799      * Parse the integrity data structure from the XML stream and discard it.
800      *
801      * @param in XmlPullParser instance pointing to the XML stream.
802      * @param outerTagDepth Outer tag depth.
803      */
parseAndDiscardIntegrityDataFromXml(XmlPullParser in, int outerTagDepth)804     private static void parseAndDiscardIntegrityDataFromXml(XmlPullParser in, int outerTagDepth)
805             throws XmlPullParserException, IOException {
806         XmlUtil.gotoNextSectionWithName(in, XML_TAG_HEADER_INTEGRITY, outerTagDepth);
807         XmlUtil.EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
808     }
809 
810     /**
811      * Dump the local log buffer and other internal state of WifiConfigManager.
812      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)813     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
814         pw.println("Dump of WifiConfigStore");
815         pw.println("WifiConfigStore - Store File Begin ----");
816         Stream.of(mSharedStores, mUserStores)
817                 .filter(Objects::nonNull)
818                 .flatMap(List::stream)
819                 .forEach((storeFile) -> {
820                     pw.print("Name: " + storeFile.mFileName);
821                     pw.print(", File Id: " + storeFile.mFileId);
822                     pw.println(", Credentials encrypted: "
823                             + (storeFile.getEncryptionUtil() != null));
824                 });
825         pw.println("WifiConfigStore - Store Data Begin ----");
826         for (StoreData storeData : mStoreDataList) {
827             pw.print("StoreData =>");
828             pw.print(" ");
829             pw.print("Name: " + storeData.getName());
830             pw.print(", ");
831             pw.print("File Id: " + storeData.getStoreFileId());
832             pw.print(", ");
833             pw.println("File Name: " + STORE_ID_TO_FILE_NAME.get(storeData.getStoreFileId()));
834         }
835         pw.println("WifiConfigStore - Store Data End ----");
836     }
837 
838     /**
839      * Class to encapsulate all file writes. This is a wrapper over {@link AtomicFile} to write/read
840      * raw data from the persistent file with integrity. This class provides helper methods to
841      * read/write the entire file into a byte array.
842      * This helps to separate out the processing, parsing, and integrity checking from the actual
843      * file writing.
844      */
845     public static class StoreFile {
846         /**
847          * File permissions to lock down the file.
848          */
849         private static final int FILE_MODE = 0600;
850         /**
851          * The store file to be written to.
852          */
853         private final AtomicFile mAtomicFile;
854         /**
855          * This is an intermediate buffer to store the data to be written.
856          */
857         private byte[] mWriteData;
858         /**
859          * Store the file name for setting the file permissions/logging purposes.
860          */
861         private final String mFileName;
862         /**
863          * {@link StoreFileId} Type of store file.
864          */
865         private final @StoreFileId int mFileId;
866         /**
867          * User handle. Meaningful only for user specific store files.
868          */
869         private final UserHandle mUserHandle;
870         /**
871          * Integrity checking for the store file.
872          */
873         private final WifiConfigStoreEncryptionUtil mEncryptionUtil;
874 
StoreFile(File file, @StoreFileId int fileId, @NonNull UserHandle userHandle, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)875         public StoreFile(File file, @StoreFileId int fileId,
876                 @NonNull UserHandle userHandle,
877                 @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) {
878             mAtomicFile = new AtomicFile(file);
879             mFileName = file.getAbsolutePath();
880             mFileId = fileId;
881             mUserHandle = userHandle;
882             mEncryptionUtil = encryptionUtil;
883         }
884 
getName()885         public String getName() {
886             return mAtomicFile.getBaseFile().getName();
887         }
888 
getFileId()889         public @StoreFileId int getFileId() {
890             return mFileId;
891         }
892 
893         /**
894          * @return Returns the encryption util used for this store file.
895          */
getEncryptionUtil()896         public @Nullable WifiConfigStoreEncryptionUtil getEncryptionUtil() {
897             return mEncryptionUtil;
898         }
899 
900         /**
901          * Read the entire raw data from the store file and return in a byte array.
902          *
903          * @return raw data read from the file or null if the file is not found or the data has
904          *  been altered.
905          * @throws IOException if an error occurs. The input stream is always closed by the method
906          * even when an exception is encountered.
907          */
readRawData()908         public byte[] readRawData() throws IOException {
909             byte[] bytes = null;
910             try {
911                 bytes = mAtomicFile.readFully();
912             } catch (FileNotFoundException e) {
913                 return null;
914             }
915             return bytes;
916         }
917 
918         /**
919          * Store the provided byte array to be written when {@link #writeBufferedRawData()} method
920          * is invoked.
921          * This intermediate step is needed to help in buffering file writes.
922          *
923          * @param data raw data to be written to the file.
924          */
storeRawDataToWrite(byte[] data)925         public void storeRawDataToWrite(byte[] data) {
926             mWriteData = data;
927         }
928 
929         /**
930          * Write the stored raw data to the store file.
931          * After the write to file, the mWriteData member is reset.
932          * @throws IOException if an error occurs. The output stream is always closed by the method
933          * even when an exception is encountered.
934          */
writeBufferedRawData()935         public void writeBufferedRawData() throws IOException {
936             if (mWriteData == null) return; // No data to write for this file.
937             // Write the data to the atomic file.
938             FileOutputStream out = null;
939             try {
940                 out = mAtomicFile.startWrite();
941                 FileUtils.chmod(mFileName, FILE_MODE);
942                 out.write(mWriteData);
943                 mAtomicFile.finishWrite(out);
944             } catch (IOException e) {
945                 if (out != null) {
946                     mAtomicFile.failWrite(out);
947                 }
948                 throw e;
949             }
950             // Reset the pending write data after write.
951             mWriteData = null;
952         }
953     }
954 
955     /**
956      * Interface to be implemented by a module that contained data in the config store file.
957      *
958      * The module will be responsible for serializing/deserializing their own data.
959      * Whenever {@link WifiConfigStore#read()} is invoked, all registered StoreData instances will
960      * be notified that a read was performed via {@link StoreData#deserializeData(
961      * XmlPullParser, int)} regardless of whether there is any data for them or not in the
962      * store file.
963      *
964      * Note: StoreData clients that need a config store read to kick-off operations should wait
965      * for the {@link StoreData#deserializeData(XmlPullParser, int)} invocation.
966      */
967     public interface StoreData {
968         /**
969          * Serialize a XML data block to the output stream.
970          *
971          * @param out The output stream to serialize the data to
972          * @param encryptionUtil Utility to help encrypt any credential data.
973          */
serializeData(XmlSerializer out, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)974         void serializeData(XmlSerializer out,
975                 @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
976                 throws XmlPullParserException, IOException;
977 
978         /**
979          * Deserialize a XML data block from the input stream.
980          *
981          * @param in The input stream to read the data from. This could be null if there is
982          *           nothing in the store.
983          * @param outerTagDepth The depth of the outer tag in the XML document
984          * @param version Version of config store file.
985          * @param encryptionUtil Utility to help decrypt any credential data.
986          *
987          * Note: This will be invoked every time a store file is read, even if there is nothing
988          *                      in the store for them.
989          */
deserializeData(@ullable XmlPullParser in, int outerTagDepth, @Version int version, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)990         void deserializeData(@Nullable XmlPullParser in, int outerTagDepth, @Version int version,
991                 @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
992                 throws XmlPullParserException, IOException;
993 
994         /**
995          * By default we will call the default deserializeData function. If some module needs to
996          * parse data with non-default structure(for migration purposes), then override this method.
997          */
deserializeDataForSection(@ullable XmlPullParser in, int outerTagDepth, @Version int version, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil, @NonNull String sectionName)998         default void deserializeDataForSection(@Nullable XmlPullParser in, int outerTagDepth,
999                 @Version int version, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil,
1000                 @NonNull String sectionName) throws XmlPullParserException, IOException {
1001             deserializeData(in, outerTagDepth, version, encryptionUtil);
1002         }
1003 
1004         /**
1005          * Reset configuration data.
1006          */
resetData()1007         void resetData();
1008 
1009         /**
1010          * Check if there is any new data to persist from the last write.
1011          *
1012          * @return true if the module has new data to persist, false otherwise.
1013          */
hasNewDataToSerialize()1014         boolean hasNewDataToSerialize();
1015 
1016         /**
1017          * Return the name of this store data.  The data will be enclosed under this tag in
1018          * the XML block.
1019          *
1020          * @return The name of the store data
1021          */
getName()1022         String getName();
1023 
1024         /**
1025          * By default, we parse the section the module writes. If some module needs to parse other
1026          * sections (for migration purposes), then override this method.
1027          * @return a set of section headers
1028          */
getSectionsToParse()1029         default HashSet<String> getSectionsToParse() {
1030             //
1031             return new HashSet<String>() {{ add(getName()); }};
1032         }
1033 
1034         /**
1035          * File Id where this data needs to be written to.
1036          * This should be one of {@link #STORE_FILE_SHARED_GENERAL},
1037          * {@link #STORE_FILE_USER_GENERAL} or
1038          * {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}.
1039          *
1040          * Note: For most uses, the shared or user general store is sufficient. Creating and
1041          * managing store files are expensive. Only use specific store files if you have a large
1042          * amount of data which may not need to be persisted frequently (or at least not as
1043          * frequently as the general store).
1044          * @return Id of the file where this data needs to be persisted.
1045          */
getStoreFileId()1046         @StoreFileId int getStoreFileId();
1047     }
1048 }
1049