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