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