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