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.os.Environment;
27 import android.os.FileUtils;
28 import android.os.Handler;
29 import android.os.Looper;
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.os.AtomicFile;
36 import com.android.internal.util.FastXmlSerializer;
37 import com.android.internal.util.Preconditions;
38 import com.android.server.wifi.util.DataIntegrityChecker;
39 import com.android.server.wifi.util.XmlUtil;
40 
41 import org.xmlpull.v1.XmlPullParser;
42 import org.xmlpull.v1.XmlPullParserException;
43 import org.xmlpull.v1.XmlSerializer;
44 
45 import java.io.ByteArrayInputStream;
46 import java.io.ByteArrayOutputStream;
47 import java.io.File;
48 import java.io.FileDescriptor;
49 import java.io.FileNotFoundException;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
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.security.DigestException;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Set;
63 import java.util.stream.Collectors;
64 
65 /**
66  * This class provides a mechanism to save data to persistent store files {@link StoreFile}.
67  * Modules can register a {@link StoreData} instance indicating the {@StoreFile} into which they
68  * want to save their data to.
69  *
70  * NOTE:
71  * <li>Modules can register their {@StoreData} using
72  * {@link WifiConfigStore#registerStoreData(StoreData)} directly, but should
73  * use {@link WifiConfigManager#saveToStore(boolean)} for any writes.</li>
74  * <li>{@link WifiConfigManager} controls {@link WifiConfigStore} and initiates read at bootup and
75  * store file changes on user switch.</li>
76  * <li>Not thread safe!</li>
77  */
78 public class WifiConfigStore {
79     /**
80      * Config store file for general shared store file.
81      */
82     public static final int STORE_FILE_SHARED_GENERAL = 0;
83     /**
84      * Config store file for general user store file.
85      */
86     public static final int STORE_FILE_USER_GENERAL = 1;
87     /**
88      * Config store file for network suggestions user store file.
89      */
90     public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 2;
91 
92     @IntDef(prefix = { "STORE_FILE_" }, value = {
93             STORE_FILE_SHARED_GENERAL,
94             STORE_FILE_USER_GENERAL,
95             STORE_FILE_USER_NETWORK_SUGGESTIONS
96     })
97     @Retention(RetentionPolicy.SOURCE)
98     public @interface StoreFileId { }
99 
100     private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
101     private static final String XML_TAG_VERSION = "Version";
102     /**
103      * Current config store data version. This will be incremented for any additions.
104      */
105     private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 1;
106     /** This list of older versions will be used to restore data from older config store. */
107     /**
108      * First version of the config store data format.
109      */
110     private static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1;
111 
112     /**
113      * Alarm tag to use for starting alarms for buffering file writes.
114      */
115     @VisibleForTesting
116     public static final String BUFFERED_WRITE_ALARM_TAG = "WriteBufferAlarm";
117     /**
118      * Log tag.
119      */
120     private static final String TAG = "WifiConfigStore";
121     /**
122      * Directory to store the config store files in.
123      */
124     private static final String STORE_DIRECTORY_NAME = "wifi";
125     /**
126      * Time interval for buffering file writes for non-forced writes
127      */
128     private static final int BUFFERED_WRITE_ALARM_INTERVAL_MS = 10 * 1000;
129     /**
130      * Config store file name for general shared store file.
131      */
132     private static final String STORE_FILE_NAME_SHARED_GENERAL = "WifiConfigStore.xml";
133     /**
134      * Config store file name for general user store file.
135      */
136     private static final String STORE_FILE_NAME_USER_GENERAL = "WifiConfigStore.xml";
137     /**
138      * Config store file name for network suggestions user store file.
139      */
140     private static final String STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS =
141             "WifiConfigStoreNetworkSuggestions.xml";
142     /**
143      * Mapping of Store file Id to Store file names.
144      */
145     private static final SparseArray<String> STORE_ID_TO_FILE_NAME =
146             new SparseArray<String>() {{
147                 put(STORE_FILE_SHARED_GENERAL, STORE_FILE_NAME_SHARED_GENERAL);
148                 put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL);
149                 put(STORE_FILE_USER_NETWORK_SUGGESTIONS, STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS);
150             }};
151 
152     /**
153      * Handler instance to post alarm timeouts to
154      */
155     private final Handler mEventHandler;
156     /**
157      * Alarm manager instance to start buffer timeout alarms.
158      */
159     private final AlarmManager mAlarmManager;
160     /**
161      * Clock instance to retrieve timestamps for alarms.
162      */
163     private final Clock mClock;
164     private final WifiMetrics mWifiMetrics;
165     /**
166      * Shared config store file instance. There is 1 shared store file:
167      * {@link #STORE_FILE_NAME_SHARED_GENERAL}.
168      */
169     private StoreFile mSharedStore;
170     /**
171      * User specific store file instances. There are 2 user store files:
172      * {@link #STORE_FILE_NAME_USER_GENERAL} & {@link #STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS}.
173      */
174     private List<StoreFile> mUserStores;
175     /**
176      * Verbose logging flag.
177      */
178     private boolean mVerboseLoggingEnabled = false;
179     /**
180      * Flag to indicate if there is a buffered write pending.
181      */
182     private boolean mBufferedWritePending = false;
183     /**
184      * Alarm listener for flushing out any buffered writes.
185      */
186     private final AlarmManager.OnAlarmListener mBufferedWriteListener =
187             new AlarmManager.OnAlarmListener() {
188                 public void onAlarm() {
189                     try {
190                         writeBufferedData();
191                     } catch (IOException e) {
192                         Log.wtf(TAG, "Buffered write failed", e);
193                     }
194                 }
195             };
196 
197     /**
198      * List of data containers.
199      */
200     private final List<StoreData> mStoreDataList;
201 
202     /**
203      * Create a new instance of WifiConfigStore.
204      * Note: The store file instances have been made inputs to this class to ease unit-testing.
205      *
206      * @param context     context to use for retrieving the alarm manager.
207      * @param looper      looper instance to post alarm timeouts to.
208      * @param clock       clock instance to retrieve timestamps for alarms.
209      * @param wifiMetrics Metrics instance.
210      * @param sharedStore StoreFile instance pointing to the shared store file. This should
211      *                    be retrieved using {@link #createSharedFile()} method.
212      */
WifiConfigStore(Context context, Looper looper, Clock clock, WifiMetrics wifiMetrics, StoreFile sharedStore)213     public WifiConfigStore(Context context, Looper looper, Clock clock, WifiMetrics wifiMetrics,
214             StoreFile sharedStore) {
215 
216         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
217         mEventHandler = new Handler(looper);
218         mClock = clock;
219         mWifiMetrics = wifiMetrics;
220         mStoreDataList = new ArrayList<>();
221 
222         // Initialize the store files.
223         mSharedStore = sharedStore;
224         // The user store is initialized to null, this will be set when the user unlocks and
225         // CE storage is accessible via |switchUserStoresAndRead|.
226         mUserStores = null;
227     }
228 
229     /**
230      * Set the user store files.
231      * (Useful for mocking in unit tests).
232      * @param userStores List of {@link StoreFile} created using {@link #createUserFiles(int)}.
233      */
setUserStores(@onNull List<StoreFile> userStores)234     public void setUserStores(@NonNull List<StoreFile> userStores) {
235         Preconditions.checkNotNull(userStores);
236         mUserStores = userStores;
237     }
238 
239     /**
240      * Register a {@link StoreData} to read/write data from/to a store. A {@link StoreData} is
241      * responsible for a block of data in the store file, and provides serialization/deserialization
242      * functions for those data.
243      *
244      * @param storeData The store data to be registered to the config store
245      * @return true if registered successfully, false if the store file name is not valid.
246      */
registerStoreData(@onNull StoreData storeData)247     public boolean registerStoreData(@NonNull StoreData storeData) {
248         if (storeData == null) {
249             Log.e(TAG, "Unable to register null store data");
250             return false;
251         }
252         int storeFileId = storeData.getStoreFileId();
253         if (STORE_ID_TO_FILE_NAME.get(storeFileId) == null) {
254             Log.e(TAG, "Invalid shared store file specified" + storeFileId);
255             return false;
256         }
257         mStoreDataList.add(storeData);
258         return true;
259     }
260 
261     /**
262      * Helper method to create a store file instance for either the shared store or user store.
263      * Note: The method creates the store directory if not already present. This may be needed for
264      * user store files.
265      *
266      * @param storeBaseDir Base directory under which the store file is to be stored. The store file
267      *                     will be at <storeBaseDir>/wifi/WifiConfigStore.xml.
268      * @param fileId Identifier for the file. See {@link StoreFileId}.
269      * @return new instance of the store file or null if the directory cannot be created.
270      */
createFile(File storeBaseDir, @StoreFileId int fileId)271     private static @Nullable StoreFile createFile(File storeBaseDir, @StoreFileId int fileId) {
272         File storeDir = new File(storeBaseDir, STORE_DIRECTORY_NAME);
273         if (!storeDir.exists()) {
274             if (!storeDir.mkdir()) {
275                 Log.w(TAG, "Could not create store directory " + storeDir);
276                 return null;
277             }
278         }
279         return new StoreFile(new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId)), fileId);
280     }
281 
282     /**
283      * Create a new instance of the shared store file.
284      *
285      * @return new instance of the store file or null if the directory cannot be created.
286      */
createSharedFile()287     public static @Nullable StoreFile createSharedFile() {
288         return createFile(Environment.getDataMiscDirectory(), STORE_FILE_SHARED_GENERAL);
289     }
290 
291     /**
292      * Create new instances of the user specific store files.
293      * The user store file is inside the user's encrypted data directory.
294      *
295      * @param userId userId corresponding to the currently logged-in user.
296      * @return List of new instances of the store files created or null if the directory cannot be
297      * created.
298      */
createUserFiles(int userId)299     public static @Nullable List<StoreFile> createUserFiles(int userId) {
300         List<StoreFile> storeFiles = new ArrayList<>();
301         for (int fileId : Arrays.asList(
302                 STORE_FILE_USER_GENERAL, STORE_FILE_USER_NETWORK_SUGGESTIONS)) {
303             StoreFile storeFile = createFile(Environment.getDataMiscCeDirectory(userId), fileId);
304             if (storeFile == null) {
305                 return null;
306             }
307             storeFiles.add(storeFile);
308         }
309         return storeFiles;
310     }
311 
312     /**
313      * Enable verbose logging.
314      */
enableVerboseLogging(boolean verbose)315     public void enableVerboseLogging(boolean verbose) {
316         mVerboseLoggingEnabled = verbose;
317     }
318 
319     /**
320      * API to check if any of the store files are present on the device. This can be used
321      * to detect if the device needs to perform data migration from legacy stores.
322      *
323      * @return true if any of the store file is present, false otherwise.
324      */
areStoresPresent()325     public boolean areStoresPresent() {
326         // Checking for the shared store file existence is sufficient since this is guaranteed
327         // to be present on migrated devices.
328         return mSharedStore.exists();
329     }
330 
331     /**
332      * Retrieve the list of {@link StoreData} instances registered for the provided
333      * {@link StoreFile}.
334      */
retrieveStoreDataListForStoreFile(@onNull StoreFile storeFile)335     private List<StoreData> retrieveStoreDataListForStoreFile(@NonNull StoreFile storeFile) {
336         return mStoreDataList
337                 .stream()
338                 .filter(s -> s.getStoreFileId() == storeFile.mFileId)
339                 .collect(Collectors.toList());
340     }
341 
342     /**
343      * Check if any of the provided list of {@link StoreData} instances registered
344      * for the provided {@link StoreFile }have indicated that they have new data to serialize.
345      */
hasNewDataToSerialize(@onNull StoreFile storeFile)346     private boolean hasNewDataToSerialize(@NonNull StoreFile storeFile) {
347         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
348         return storeDataList.stream().anyMatch(s -> s.hasNewDataToSerialize());
349     }
350 
351     /**
352      * API to write the data provided by registered store data to config stores.
353      * The method writes the user specific configurations to user specific config store and the
354      * shared configurations to shared config store.
355      *
356      * @param forceSync boolean to force write the config stores now. if false, the writes are
357      *                  buffered and written after the configured interval.
358      */
write(boolean forceSync)359     public void write(boolean forceSync)
360             throws XmlPullParserException, IOException {
361         boolean hasAnyNewData = false;
362         // Serialize the provided data and send it to the respective stores. The actual write will
363         // be performed later depending on the |forceSync| flag .
364         if (hasNewDataToSerialize(mSharedStore)) {
365             byte[] sharedDataBytes = serializeData(mSharedStore);
366             mSharedStore.storeRawDataToWrite(sharedDataBytes);
367             hasAnyNewData = true;
368         }
369         if (mUserStores != null) {
370             for (StoreFile userStoreFile : mUserStores) {
371                 if (hasNewDataToSerialize(userStoreFile)) {
372                     byte[] userDataBytes = serializeData(userStoreFile);
373                     userStoreFile.storeRawDataToWrite(userDataBytes);
374                     hasAnyNewData = true;
375                 }
376             }
377         }
378 
379         if (hasAnyNewData) {
380             // Every write provides a new snapshot to be persisted, so |forceSync| flag overrides
381             // any pending buffer writes.
382             if (forceSync) {
383                 writeBufferedData();
384             } else {
385                 startBufferedWriteAlarm();
386             }
387         } else if (forceSync && mBufferedWritePending) {
388             // no new data to write, but there is a pending buffered write. So, |forceSync| should
389             // flush that out.
390             writeBufferedData();
391         }
392     }
393 
394     /**
395      * Serialize all the data from all the {@link StoreData} clients registered for the provided
396      * {@link StoreFile}.
397      *
398      * @param storeFile StoreFile that we want to write to.
399      * @return byte[] of serialized bytes
400      * @throws XmlPullParserException
401      * @throws IOException
402      */
serializeData(@onNull StoreFile storeFile)403     private byte[] serializeData(@NonNull StoreFile storeFile)
404             throws XmlPullParserException, IOException {
405         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
406 
407         final XmlSerializer out = new FastXmlSerializer();
408         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
409         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
410 
411         XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
412         XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION);
413         for (StoreData storeData : storeDataList) {
414             String tag = storeData.getName();
415             XmlUtil.writeNextSectionStart(out, tag);
416             storeData.serializeData(out);
417             XmlUtil.writeNextSectionEnd(out, tag);
418         }
419         XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
420 
421         return outputStream.toByteArray();
422     }
423 
424     /**
425      * Helper method to start a buffered write alarm if one doesn't already exist.
426      */
startBufferedWriteAlarm()427     private void startBufferedWriteAlarm() {
428         if (!mBufferedWritePending) {
429             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
430                     mClock.getElapsedSinceBootMillis() + BUFFERED_WRITE_ALARM_INTERVAL_MS,
431                     BUFFERED_WRITE_ALARM_TAG, mBufferedWriteListener, mEventHandler);
432             mBufferedWritePending = true;
433         }
434     }
435 
436     /**
437      * Helper method to stop a buffered write alarm if one exists.
438      */
stopBufferedWriteAlarm()439     private void stopBufferedWriteAlarm() {
440         if (mBufferedWritePending) {
441             mAlarmManager.cancel(mBufferedWriteListener);
442             mBufferedWritePending = false;
443         }
444     }
445 
446     /**
447      * Helper method to actually perform the writes to the file. This flushes out any write data
448      * being buffered in the respective stores and cancels any pending buffer write alarms.
449      */
writeBufferedData()450     private void writeBufferedData() throws IOException {
451         stopBufferedWriteAlarm();
452 
453         long writeStartTime = mClock.getElapsedSinceBootMillis();
454         mSharedStore.writeBufferedRawData();
455         if (mUserStores != null) {
456             for (StoreFile userStoreFile : mUserStores) {
457                 userStoreFile.writeBufferedRawData();
458             }
459         }
460         long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;
461         try {
462             mWifiMetrics.noteWifiConfigStoreWriteDuration(toIntExact(writeTime));
463         } catch (ArithmeticException e) {
464             // Silently ignore on any overflow errors.
465         }
466         Log.d(TAG, "Writing to stores completed in " + writeTime + " ms.");
467     }
468 
469     /**
470      * API to read the store data from the config stores.
471      * The method reads the user specific configurations from user specific config store and the
472      * shared configurations from the shared config store.
473      */
read()474     public void read() throws XmlPullParserException, IOException {
475         // Reset both share and user store data.
476         resetStoreData(mSharedStore);
477         if (mUserStores != null) {
478             for (StoreFile userStoreFile : mUserStores) {
479                 resetStoreData(userStoreFile);
480             }
481         }
482 
483         long readStartTime = mClock.getElapsedSinceBootMillis();
484         byte[] sharedDataBytes = mSharedStore.readRawData();
485         deserializeData(sharedDataBytes, mSharedStore);
486         if (mUserStores != null) {
487             for (StoreFile userStoreFile : mUserStores) {
488                 byte[] userDataBytes = userStoreFile.readRawData();
489                 deserializeData(userDataBytes, userStoreFile);
490             }
491         }
492         long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
493         try {
494             mWifiMetrics.noteWifiConfigStoreReadDuration(toIntExact(readTime));
495         } catch (ArithmeticException e) {
496             // Silently ignore on any overflow errors.
497         }
498         Log.d(TAG, "Reading from all stores completed in " + readTime + " ms.");
499     }
500 
501     /**
502      * Handles a user switch. This method changes the user specific store files and reads from the
503      * new user's store files.
504      *
505      * @param userStores List of {@link StoreFile} created using {@link #createUserFiles(int)}.
506      */
switchUserStoresAndRead(@onNull List<StoreFile> userStores)507     public void switchUserStoresAndRead(@NonNull List<StoreFile> userStores)
508             throws XmlPullParserException, IOException {
509         Preconditions.checkNotNull(userStores);
510         // Reset user store data.
511         if (mUserStores != null) {
512             for (StoreFile userStoreFile : mUserStores) {
513                 resetStoreData(userStoreFile);
514             }
515         }
516 
517         // Stop any pending buffered writes, if any.
518         stopBufferedWriteAlarm();
519         mUserStores = userStores;
520 
521         // Now read from the user store file.
522         long readStartTime = mClock.getElapsedSinceBootMillis();
523         for (StoreFile userStoreFile : mUserStores) {
524             byte[] userDataBytes = userStoreFile.readRawData();
525             deserializeData(userDataBytes, userStoreFile);
526         }
527         long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
528         mWifiMetrics.noteWifiConfigStoreReadDuration(toIntExact(readTime));
529         Log.d(TAG, "Reading from user stores completed in " + readTime + " ms.");
530     }
531 
532     /**
533      * Reset data for all {@link StoreData} instances registered for this {@link StoreFile}.
534      */
resetStoreData(@onNull StoreFile storeFile)535     private void resetStoreData(@NonNull StoreFile storeFile) {
536         for (StoreData storeData: retrieveStoreDataListForStoreFile(storeFile)) {
537             storeData.resetData();
538         }
539     }
540 
541     // Inform all the provided store data clients that there is nothing in the store for them.
indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet)542     private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet)
543             throws XmlPullParserException, IOException {
544         for (StoreData storeData : storeDataSet) {
545             storeData.deserializeData(null, 0);
546         }
547     }
548 
549     /**
550      * Deserialize data from a {@link StoreFile} for all {@link StoreData} instances registered.
551      *
552      * @param dataBytes The data to parse
553      * @param storeFile StoreFile that we read from. Will be used to retrieve the list of clients
554      *                  who have data to deserialize from this file.
555      *
556      * @throws XmlPullParserException
557      * @throws IOException
558      */
deserializeData(@onNull byte[] dataBytes, @NonNull StoreFile storeFile)559     private void deserializeData(@NonNull byte[] dataBytes, @NonNull StoreFile storeFile)
560             throws XmlPullParserException, IOException {
561         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
562         if (dataBytes == null) {
563             indicateNoDataForStoreDatas(storeDataList);
564             return;
565         }
566         final XmlPullParser in = Xml.newPullParser();
567         final ByteArrayInputStream inputStream = new ByteArrayInputStream(dataBytes);
568         in.setInput(inputStream, StandardCharsets.UTF_8.name());
569 
570         // Start parsing the XML stream.
571         int rootTagDepth = in.getDepth() + 1;
572         parseDocumentStartAndVersionFromXml(in);
573 
574         String[] headerName = new String[1];
575         Set<StoreData> storeDatasInvoked = new HashSet<>();
576         while (XmlUtil.gotoNextSectionOrEnd(in, headerName, rootTagDepth)) {
577             // There can only be 1 store data matching the tag (O indicates a fatal
578             // error).
579             StoreData storeData = storeDataList.stream()
580                     .filter(s -> s.getName().equals(headerName[0]))
581                     .findAny()
582                     .orElse(null);
583             if (storeData == null) {
584                 throw new XmlPullParserException("Unknown store data: " + headerName[0]
585                         + ". List of store data: " + storeDataList);
586             }
587             storeData.deserializeData(in, rootTagDepth + 1);
588             storeDatasInvoked.add(storeData);
589         }
590         // Inform all the other registered store data clients that there is nothing in the store
591         // for them.
592         Set<StoreData> storeDatasNotInvoked = new HashSet<>(storeDataList);
593         storeDatasNotInvoked.removeAll(storeDatasInvoked);
594         indicateNoDataForStoreDatas(storeDatasNotInvoked);
595     }
596 
597     /**
598      * Parse the document start and version from the XML stream.
599      * This is used for both the shared and user config store data.
600      *
601      * @param in XmlPullParser instance pointing to the XML stream.
602      * @return version number retrieved from the Xml stream.
603      */
parseDocumentStartAndVersionFromXml(XmlPullParser in)604     private static int parseDocumentStartAndVersionFromXml(XmlPullParser in)
605             throws XmlPullParserException, IOException {
606         XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
607         int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
608         if (version < INITIAL_CONFIG_STORE_DATA_VERSION
609                 || version > CURRENT_CONFIG_STORE_DATA_VERSION) {
610             throw new XmlPullParserException("Invalid version of data: " + version);
611         }
612         return version;
613     }
614 
615     /**
616      * Dump the local log buffer and other internal state of WifiConfigManager.
617      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)618     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
619         pw.println("Dump of WifiConfigStore");
620         pw.println("WifiConfigStore - Store Data Begin ----");
621         for (StoreData storeData : mStoreDataList) {
622             pw.print("StoreData =>");
623             pw.print(" ");
624             pw.print("Name: " + storeData.getName());
625             pw.print(", ");
626             pw.print("File Id: " + storeData.getStoreFileId());
627             pw.print(", ");
628             pw.println("File Name: " + STORE_ID_TO_FILE_NAME.get(storeData.getStoreFileId()));
629         }
630         pw.println("WifiConfigStore - Store Data End ----");
631     }
632 
633     /**
634      * Class to encapsulate all file writes. This is a wrapper over {@link AtomicFile} to write/read
635      * raw data from the persistent file with integrity. This class provides helper methods to
636      * read/write the entire file into a byte array.
637      * This helps to separate out the processing, parsing, and integrity checking from the actual
638      * file writing.
639      */
640     public static class StoreFile {
641         /**
642          * File permissions to lock down the file.
643          */
644         private static final int FILE_MODE = 0600;
645         /**
646          * The store file to be written to.
647          */
648         private final AtomicFile mAtomicFile;
649         /**
650          * This is an intermediate buffer to store the data to be written.
651          */
652         private byte[] mWriteData;
653         /**
654          * Store the file name for setting the file permissions/logging purposes.
655          */
656         private String mFileName;
657         /**
658          * The integrity file storing integrity checking data for the store file.
659          */
660         private DataIntegrityChecker mDataIntegrityChecker;
661         /**
662          * {@link StoreFileId} Type of store file.
663          */
664         private @StoreFileId int mFileId;
665 
StoreFile(File file, @StoreFileId int fileId)666         public StoreFile(File file, @StoreFileId int fileId) {
667             mAtomicFile = new AtomicFile(file);
668             mFileName = mAtomicFile.getBaseFile().getAbsolutePath();
669             mDataIntegrityChecker = new DataIntegrityChecker(mFileName);
670             mFileId = fileId;
671         }
672 
673         /**
674          * Returns whether the store file already exists on disk or not.
675          *
676          * @return true if it exists, false otherwise.
677          */
exists()678         public boolean exists() {
679             return mAtomicFile.exists();
680         }
681 
682         /**
683          * Read the entire raw data from the store file and return in a byte array.
684          *
685          * @return raw data read from the file or null if the file is not found or the data has
686          *  been altered.
687          * @throws IOException if an error occurs. The input stream is always closed by the method
688          * even when an exception is encountered.
689          */
readRawData()690         public byte[] readRawData() throws IOException {
691             byte[] bytes = null;
692             try {
693                 bytes = mAtomicFile.readFully();
694                 // Check that the file has not been altered since last writeBufferedRawData()
695                 if (!mDataIntegrityChecker.isOk(bytes)) {
696                     Log.wtf(TAG, "Data integrity problem with file: " + mFileName);
697                     return null;
698                 }
699             } catch (FileNotFoundException e) {
700                 return null;
701             } catch (DigestException e) {
702                 // When integrity checking is introduced. The existing data will have no related
703                 // integrity file for validation. Thus, we will assume the existing data is correct
704                 // and immediately create the integrity file.
705                 Log.i(TAG, "isOK() had no integrity data to check; thus vacuously "
706                         + "true. Running update now.");
707                 mDataIntegrityChecker.update(bytes);
708             }
709             return bytes;
710         }
711 
712         /**
713          * Store the provided byte array to be written when {@link #writeBufferedRawData()} method
714          * is invoked.
715          * This intermediate step is needed to help in buffering file writes.
716          *
717          * @param data raw data to be written to the file.
718          */
storeRawDataToWrite(byte[] data)719         public void storeRawDataToWrite(byte[] data) {
720             mWriteData = data;
721         }
722 
723         /**
724          * Write the stored raw data to the store file.
725          * After the write to file, the mWriteData member is reset.
726          * @throws IOException if an error occurs. The output stream is always closed by the method
727          * even when an exception is encountered.
728          */
writeBufferedRawData()729         public void writeBufferedRawData() throws IOException {
730             if (mWriteData == null) return; // No data to write for this file.
731             // Write the data to the atomic file.
732             FileOutputStream out = null;
733             try {
734                 out = mAtomicFile.startWrite();
735                 FileUtils.setPermissions(mFileName, FILE_MODE, -1, -1);
736                 out.write(mWriteData);
737                 mAtomicFile.finishWrite(out);
738             } catch (IOException e) {
739                 if (out != null) {
740                     mAtomicFile.failWrite(out);
741                 }
742                 throw e;
743             }
744             // There was a legitimate change and update the integrity checker.
745             mDataIntegrityChecker.update(mWriteData);
746             // Reset the pending write data after write.
747             mWriteData = null;
748         }
749     }
750 
751     /**
752      * Interface to be implemented by a module that contained data in the config store file.
753      *
754      * The module will be responsible for serializing/deserializing their own data.
755      * Whenever {@link WifiConfigStore#read()} is invoked, all registered StoreData instances will
756      * be notified that a read was performed via {@link StoreData#deserializeData(
757      * XmlPullParser, int)} regardless of whether there is any data for them or not in the
758      * store file.
759      *
760      * Note: StoreData clients that need a config store read to kick-off operations should wait
761      * for the {@link StoreData#deserializeData(XmlPullParser, int)} invocation.
762      */
763     public interface StoreData {
764         /**
765          * Serialize a XML data block to the output stream.
766          *
767          * @param out The output stream to serialize the data to
768          */
serializeData(XmlSerializer out)769         void serializeData(XmlSerializer out)
770                 throws XmlPullParserException, IOException;
771 
772         /**
773          * Deserialize a XML data block from the input stream.
774          *
775          * @param in The input stream to read the data from. This could be null if there is
776          *           nothing in the store.
777          * @param outerTagDepth The depth of the outer tag in the XML document
778          * Note: This will be invoked every time a store file is read, even if there is nothing
779          *                      in the store for them.
780          */
deserializeData(@ullable XmlPullParser in, int outerTagDepth)781         void deserializeData(@Nullable XmlPullParser in, int outerTagDepth)
782                 throws XmlPullParserException, IOException;
783 
784         /**
785          * Reset configuration data.
786          */
resetData()787         void resetData();
788 
789         /**
790          * Check if there is any new data to persist from the last write.
791          *
792          * @return true if the module has new data to persist, false otherwise.
793          */
hasNewDataToSerialize()794         boolean hasNewDataToSerialize();
795 
796         /**
797          * Return the name of this store data.  The data will be enclosed under this tag in
798          * the XML block.
799          *
800          * @return The name of the store data
801          */
getName()802         String getName();
803 
804         /**
805          * File Id where this data needs to be written to.
806          * This should be one of {@link #STORE_FILE_SHARED_GENERAL},
807          * {@link #STORE_FILE_USER_GENERAL} or
808          * {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}.
809          *
810          * Note: For most uses, the shared or user general store is sufficient. Creating and
811          * managing store files are expensive. Only use specific store files if you have a large
812          * amount of data which may not need to be persisted frequently (or at least not as
813          * frequently as the general store).
814          * @return Id of the file where this data needs to be persisted.
815          */
getStoreFileId()816         @StoreFileId int getStoreFileId();
817     }
818 }
819