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