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