1 /* 2 * Copyright 2019 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 package com.android.cts.blob; 17 18 import static android.os.storage.StorageManager.UUID_DEFAULT; 19 20 import static com.android.utils.blob.Utils.TAG; 21 import static com.android.utils.blob.Utils.acquireLease; 22 import static com.android.utils.blob.Utils.assertLeasedBlobs; 23 import static com.android.utils.blob.Utils.assertNoLeasedBlobs; 24 import static com.android.utils.blob.Utils.releaseLease; 25 import static com.android.utils.blob.Utils.runShellCmd; 26 import static com.android.utils.blob.Utils.triggerIdleMaintenance; 27 28 import static com.google.common.truth.Truth.assertThat; 29 import static com.google.common.truth.Truth.assertWithMessage; 30 31 import static org.junit.Assert.assertFalse; 32 import static org.junit.Assert.assertTrue; 33 import static org.testng.Assert.assertThrows; 34 35 import android.app.blob.BlobHandle; 36 import android.app.blob.BlobStoreManager; 37 import android.app.usage.StorageStats; 38 import android.app.usage.StorageStatsManager; 39 import android.content.ComponentName; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.ServiceConnection; 43 import android.content.pm.PackageManager; 44 import android.os.Environment; 45 import android.os.IBinder; 46 import android.os.LimitExceededException; 47 import android.os.ParcelFileDescriptor; 48 import android.os.Process; 49 import android.os.SystemClock; 50 import android.provider.DeviceConfig; 51 import android.util.ArrayMap; 52 import android.util.Log; 53 import android.util.LongSparseArray; 54 import android.util.Pair; 55 56 import androidx.test.platform.app.InstrumentationRegistry; 57 58 import com.android.compatibility.common.util.AmUtils; 59 import com.android.compatibility.common.util.PollingCheck; 60 import com.android.compatibility.common.util.SystemUtil; 61 import com.android.compatibility.common.util.ThrowingRunnable; 62 import com.android.cts.blob.ICommandReceiver; 63 import com.android.utils.blob.FakeBlobData; 64 import com.android.utils.blob.Utils; 65 66 import com.google.common.io.BaseEncoding; 67 68 import org.junit.After; 69 import org.junit.Before; 70 import org.junit.Test; 71 import org.junit.runner.RunWith; 72 73 import java.io.IOException; 74 import java.io.OutputStream; 75 import java.nio.charset.StandardCharsets; 76 import java.util.ArrayList; 77 import java.util.Collections; 78 import java.util.Map; 79 import java.util.Objects; 80 import java.util.Random; 81 import java.util.concurrent.BlockingQueue; 82 import java.util.concurrent.CompletableFuture; 83 import java.util.concurrent.ExecutorService; 84 import java.util.concurrent.Executors; 85 import java.util.concurrent.LinkedBlockingQueue; 86 import java.util.concurrent.TimeUnit; 87 88 @RunWith(BlobStoreTestRunner.class) 89 public class BlobStoreManagerTest { 90 91 private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 100; 92 93 private static final long TIMEOUT_BIND_SERVICE_SEC = 2; 94 95 private static final long TIMEOUT_WAIT_FOR_IDLE_MS = 2_000; 96 97 // TODO: Make it a @TestApi or move the test using this to a different location. 98 // Copy of DeviceConfig.NAMESPACE_BLOBSTORE constant 99 private static final String NAMESPACE_BLOBSTORE = "blobstore"; 100 private static final String KEY_SESSION_EXPIRY_TIMEOUT_MS = "session_expiry_timeout_ms"; 101 private static final String KEY_LEASE_ACQUISITION_WAIT_DURATION_MS = 102 "lease_acquisition_wait_time_ms"; 103 private static final String KEY_DELETE_ON_LAST_LEASE_DELAY_MS = 104 "delete_on_last_lease_delay_ms"; 105 private static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR = 106 "total_bytes_per_app_limit_floor"; 107 private static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = 108 "total_bytes_per_app_limit_fraction"; 109 private static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions"; 110 private static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs"; 111 private static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs"; 112 private static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks"; 113 114 static final String HELPER_PKG = "com.android.cts.blob.helper"; 115 static final String HELPER_PKG2 = "com.android.cts.blob.helper2"; 116 static final String HELPER_PKG3 = "com.android.cts.blob.helper3"; 117 118 private static final String HELPER_SERVICE = HELPER_PKG + ".BlobStoreTestService"; 119 120 static final byte[] HELPER_PKG2_CERT_SHA256 = 121 BaseEncoding.base16() 122 .decode("187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3"); 123 static final byte[] HELPER_PKG3_CERT_SHA256 = 124 BaseEncoding.base16() 125 .decode("D760873D812FE1CFC02C15ED416AB774B2D4C2E936DF6D8B6707277479D4812F"); 126 127 private Context mContext; 128 int mUserId; 129 private BlobStoreManager mBlobStoreManager; 130 131 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 132 133 private static final long DELTA_BYTES = 100 * 1024L; 134 135 @Before setUp()136 public void setUp() { 137 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 138 mUserId = mContext.getUser().getIdentifier(); 139 mBlobStoreManager = (BlobStoreManager) mContext.getSystemService( 140 Context.BLOB_STORE_SERVICE); 141 // Wait for any previous package/uid related broadcasts to be handled before proceeding 142 // with the verifications. 143 AmUtils.waitForBroadcastBarrier(); 144 } 145 146 @After tearDown()147 public void tearDown() throws Exception { 148 runShellCmd("cmd blob_store clear-all-sessions -u " + mUserId); 149 runShellCmd("cmd blob_store clear-all-blobs -u " + mUserId); 150 mContext.getFilesDir().delete(); 151 for (String pkg : new String[] {HELPER_PKG, HELPER_PKG2, HELPER_PKG3}) { 152 runShellCmd("cmd package clear " + pkg + " -u " + mUserId); 153 } 154 for (String pkg : new String[] {HELPER_PKG, HELPER_PKG2, HELPER_PKG3}) { 155 waitUntilStorageCleared(pkg); 156 } 157 } 158 159 @Test testGetCreateSession()160 public void testGetCreateSession() throws Exception { 161 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 162 blobData.prepare(); 163 try { 164 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 165 assertThat(sessionId).isGreaterThan(0L); 166 assertThat(mBlobStoreManager.openSession(sessionId)).isNotNull(); 167 } finally { 168 blobData.delete(); 169 } 170 } 171 172 @Test testCreateBlobHandle_invalidArguments()173 public void testCreateBlobHandle_invalidArguments() throws Exception { 174 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 175 blobData.prepare(); 176 final BlobHandle handle = blobData.getBlobHandle(); 177 try { 178 assertThrows(IllegalArgumentException.class, () -> BlobHandle.createWithSha256( 179 handle.getSha256Digest(), null, handle.getExpiryTimeMillis(), handle.getTag())); 180 assertThrows(IllegalArgumentException.class, () -> BlobHandle.createWithSha256( 181 handle.getSha256Digest(), handle.getLabel(), handle.getExpiryTimeMillis(), 182 null)); 183 assertThrows(IllegalArgumentException.class, () -> BlobHandle.createWithSha256( 184 handle.getSha256Digest(), handle.getLabel(), -1, handle.getTag())); 185 assertThrows(IllegalArgumentException.class, () -> BlobHandle.createWithSha256( 186 EMPTY_BYTE_ARRAY, handle.getLabel(), handle.getExpiryTimeMillis(), 187 handle.getTag())); 188 } finally { 189 blobData.delete(); 190 } 191 } 192 193 @Test testGetCreateSession_invalidArguments()194 public void testGetCreateSession_invalidArguments() throws Exception { 195 assertThrows(NullPointerException.class, () -> mBlobStoreManager.createSession(null)); 196 } 197 198 @Test testOpenSession_invalidArguments()199 public void testOpenSession_invalidArguments() throws Exception { 200 assertThrows(IllegalArgumentException.class, () -> mBlobStoreManager.openSession(-1)); 201 } 202 203 @Test testAbandonSession_invalidArguments()204 public void testAbandonSession_invalidArguments() throws Exception { 205 assertThrows(IllegalArgumentException.class, () -> mBlobStoreManager.abandonSession(-1)); 206 } 207 208 @Test testAbandonSession()209 public void testAbandonSession() throws Exception { 210 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 211 blobData.prepare(); 212 try { 213 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 214 assertThat(sessionId).isGreaterThan(0L); 215 // Verify that session can be opened. 216 assertThat(mBlobStoreManager.openSession(sessionId)).isNotNull(); 217 218 mBlobStoreManager.abandonSession(sessionId); 219 // Verify that trying to open session after it is deleted will throw. 220 assertThrows(SecurityException.class, () -> mBlobStoreManager.openSession(sessionId)); 221 } finally { 222 blobData.delete(); 223 } 224 } 225 226 @Test testOpenReadWriteSession()227 public void testOpenReadWriteSession() throws Exception { 228 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 229 blobData.prepare(); 230 try { 231 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 232 assertThat(sessionId).isGreaterThan(0L); 233 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 234 blobData.writeToSession(session, 0, blobData.getFileSize()); 235 blobData.readFromSessionAndVerifyDigest(session); 236 blobData.readFromSessionAndVerifyBytes(session, 237 101 /* offset */, 1001 /* length */); 238 239 blobData.writeToSession(session, 202 /* offset */, 2002 /* length */, 240 blobData.getFileSize()); 241 blobData.readFromSessionAndVerifyBytes(session, 242 202 /* offset */, 2002 /* length */); 243 244 commitSession(sessionId, session, blobData.getBlobHandle()); 245 } 246 } finally { 247 blobData.delete(); 248 } 249 } 250 251 @Test testOpenSession_fromAnotherPkg()252 public void testOpenSession_fromAnotherPkg() throws Exception { 253 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 254 blobData.prepare(); 255 try { 256 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 257 assertThat(sessionId).isGreaterThan(0L); 258 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 259 assertThat(session).isNotNull(); 260 session.allowPublicAccess(); 261 } 262 assertThrows(SecurityException.class, () -> openSessionFromPkg(sessionId, HELPER_PKG)); 263 assertThrows(SecurityException.class, () -> openSessionFromPkg(sessionId, HELPER_PKG2)); 264 265 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 266 blobData.writeToSession(session, 0, blobData.getFileSize()); 267 blobData.readFromSessionAndVerifyDigest(session); 268 session.allowPublicAccess(); 269 } 270 assertThrows(SecurityException.class, () -> openSessionFromPkg(sessionId, HELPER_PKG)); 271 assertThrows(SecurityException.class, () -> openSessionFromPkg(sessionId, HELPER_PKG2)); 272 } finally { 273 blobData.delete(); 274 } 275 } 276 277 @Test testOpenSessionAndAbandon()278 public void testOpenSessionAndAbandon() throws Exception { 279 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 280 blobData.prepare(); 281 try { 282 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 283 assertThat(sessionId).isGreaterThan(0L); 284 285 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 286 // Verify session can be opened for read/write. 287 assertThat(session).isNotNull(); 288 assertThat(session.openWrite(0, 0)).isNotNull(); 289 assertThat(session.openRead()).isNotNull(); 290 291 // Verify that trying to read/write to the session after it is abandoned will throw. 292 session.abandon(); 293 assertThrows(IllegalStateException.class, () -> session.openWrite(0, 0)); 294 assertThrows(IllegalStateException.class, () -> session.openRead()); 295 } 296 297 // Verify that trying to open the session after it is abandoned will throw. 298 assertThrows(SecurityException.class, () -> mBlobStoreManager.openSession(sessionId)); 299 } finally { 300 blobData.delete(); 301 } 302 } 303 304 @Test testCloseSession()305 public void testCloseSession() throws Exception { 306 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 307 blobData.prepare(); 308 try { 309 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 310 assertThat(sessionId).isGreaterThan(0L); 311 312 // Verify session can be opened for read/write. 313 BlobStoreManager.Session session = null; 314 try { 315 session = mBlobStoreManager.openSession(sessionId); 316 assertThat(session).isNotNull(); 317 assertThat(session.openWrite(0, 0)).isNotNull(); 318 assertThat(session.openRead()).isNotNull(); 319 } finally { 320 session.close(); 321 } 322 323 // Verify trying to read/write to session after it is closed will throw. 324 // an exception. 325 final BlobStoreManager.Session closedSession = session; 326 assertThrows(IllegalStateException.class, () -> closedSession.openWrite(0, 0)); 327 assertThrows(IllegalStateException.class, () -> closedSession.openRead()); 328 329 // Verify that the session can be opened again for read/write. 330 try { 331 session = mBlobStoreManager.openSession(sessionId); 332 assertThat(session).isNotNull(); 333 assertThat(session.openWrite(0, 0)).isNotNull(); 334 assertThat(session.openRead()).isNotNull(); 335 } finally { 336 session.close(); 337 } 338 } finally { 339 blobData.delete(); 340 } 341 } 342 343 @Test testAllowPublicAccess()344 public void testAllowPublicAccess() throws Exception { 345 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 346 blobData.prepare(); 347 try { 348 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 349 assertThat(sessionId).isGreaterThan(0L); 350 351 commitBlob(blobData, session -> { 352 session.allowPublicAccess(); 353 assertThat(session.isPublicAccessAllowed()).isTrue(); 354 }); 355 356 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG); 357 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG2); 358 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG3); 359 } finally { 360 blobData.delete(); 361 } 362 } 363 364 @Test testAllowPublicAccess_abandonedSession()365 public void testAllowPublicAccess_abandonedSession() throws Exception { 366 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 367 blobData.prepare(); 368 try { 369 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 370 assertThat(sessionId).isGreaterThan(0L); 371 372 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 373 session.allowPublicAccess(); 374 assertThat(session.isPublicAccessAllowed()).isTrue(); 375 376 session.abandon(); 377 assertThrows(IllegalStateException.class, 378 () -> session.allowPublicAccess()); 379 assertThrows(IllegalStateException.class, 380 () -> session.isPublicAccessAllowed()); 381 } 382 } finally { 383 blobData.delete(); 384 } 385 } 386 387 @Test testAllowSameSignatureAccess()388 public void testAllowSameSignatureAccess() throws Exception { 389 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 390 blobData.prepare(); 391 try { 392 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 393 assertThat(sessionId).isGreaterThan(0L); 394 395 commitBlob(blobData, session -> { 396 session.allowSameSignatureAccess(); 397 assertThat(session.isSameSignatureAccessAllowed()).isTrue(); 398 }); 399 400 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG); 401 assertPkgCannotAccess(blobData, HELPER_PKG2); 402 assertPkgCannotAccess(blobData, HELPER_PKG3); 403 } finally { 404 blobData.delete(); 405 } 406 } 407 408 @Test testAllowSameSignatureAccess_abandonedSession()409 public void testAllowSameSignatureAccess_abandonedSession() throws Exception { 410 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 411 blobData.prepare(); 412 try { 413 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 414 assertThat(sessionId).isGreaterThan(0L); 415 416 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 417 session.allowSameSignatureAccess(); 418 assertThat(session.isSameSignatureAccessAllowed()).isTrue(); 419 420 session.abandon(); 421 assertThrows(IllegalStateException.class, 422 () -> session.allowSameSignatureAccess()); 423 assertThrows(IllegalStateException.class, 424 () -> session.isSameSignatureAccessAllowed()); 425 } 426 } finally { 427 blobData.delete(); 428 } 429 } 430 431 @Test testAllowPackageAccess()432 public void testAllowPackageAccess() throws Exception { 433 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 434 blobData.prepare(); 435 try { 436 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 437 assertThat(sessionId).isGreaterThan(0L); 438 439 commitBlob(blobData, session -> { 440 session.allowPackageAccess(HELPER_PKG2, HELPER_PKG2_CERT_SHA256); 441 assertThat(session.isPackageAccessAllowed(HELPER_PKG2, HELPER_PKG2_CERT_SHA256)) 442 .isTrue(); 443 }); 444 445 assertPkgCannotAccess(blobData, HELPER_PKG); 446 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG2); 447 assertPkgCannotAccess(blobData, HELPER_PKG3); 448 } finally { 449 blobData.delete(); 450 } 451 } 452 453 @Test testAllowPackageAccess_allowMultiple()454 public void testAllowPackageAccess_allowMultiple() throws Exception { 455 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 456 blobData.prepare(); 457 try { 458 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 459 assertThat(sessionId).isGreaterThan(0L); 460 461 commitBlob(blobData, session -> { 462 session.allowPackageAccess(HELPER_PKG2, HELPER_PKG2_CERT_SHA256); 463 session.allowPackageAccess(HELPER_PKG3, HELPER_PKG3_CERT_SHA256); 464 assertThat(session.isPackageAccessAllowed(HELPER_PKG2, HELPER_PKG2_CERT_SHA256)) 465 .isTrue(); 466 assertThat(session.isPackageAccessAllowed(HELPER_PKG3, HELPER_PKG3_CERT_SHA256)) 467 .isTrue(); 468 }); 469 470 assertPkgCannotAccess(blobData, HELPER_PKG); 471 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG2); 472 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG3); 473 } finally { 474 blobData.delete(); 475 } 476 } 477 478 @Test testAllowPackageAccess_abandonedSession()479 public void testAllowPackageAccess_abandonedSession() throws Exception { 480 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 481 blobData.prepare(); 482 try { 483 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 484 assertThat(sessionId).isGreaterThan(0L); 485 486 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 487 session.allowPackageAccess("com.example", "test_bytes".getBytes()); 488 assertThat(session.isPackageAccessAllowed("com.example", "test_bytes".getBytes())) 489 .isTrue(); 490 491 session.abandon(); 492 assertThrows(IllegalStateException.class, 493 () -> session.allowPackageAccess( 494 "com.example2", "test_bytes2".getBytes())); 495 assertThrows(IllegalStateException.class, 496 () -> session.isPackageAccessAllowed( 497 "com.example", "test_bytes".getBytes())); 498 } 499 } finally { 500 blobData.delete(); 501 } 502 } 503 504 @Test testPrivateAccess()505 public void testPrivateAccess() throws Exception { 506 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 507 blobData.prepare(); 508 final TestServiceConnection connection1 = bindToHelperService(HELPER_PKG); 509 final TestServiceConnection connection2 = bindToHelperService(HELPER_PKG2); 510 final TestServiceConnection connection3 = bindToHelperService(HELPER_PKG3); 511 try { 512 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 513 assertThat(sessionId).isGreaterThan(0L); 514 515 commitBlob(blobData); 516 517 assertPkgCannotAccess(blobData, connection1); 518 assertPkgCannotAccess(blobData, connection2); 519 assertPkgCannotAccess(blobData, connection3); 520 521 commitBlobFromPkg(blobData, connection1); 522 acquireLeaseAndAssertPkgCanAccess(blobData, connection1); 523 assertPkgCannotAccess(blobData, connection2); 524 assertPkgCannotAccess(blobData, connection3); 525 526 commitBlobFromPkg(blobData, connection2); 527 acquireLeaseAndAssertPkgCanAccess(blobData, connection1); 528 acquireLeaseAndAssertPkgCanAccess(blobData, connection2); 529 assertPkgCannotAccess(blobData, connection3); 530 } finally { 531 blobData.delete(); 532 connection1.unbind(); 533 connection2.unbind(); 534 connection3.unbind(); 535 } 536 } 537 538 @Test testMixedAccessType()539 public void testMixedAccessType() throws Exception { 540 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 541 blobData.prepare(); 542 try { 543 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 544 assertThat(sessionId).isGreaterThan(0L); 545 546 commitBlob(blobData, session -> { 547 session.allowSameSignatureAccess(); 548 session.allowPackageAccess(HELPER_PKG3, HELPER_PKG3_CERT_SHA256); 549 assertThat(session.isSameSignatureAccessAllowed()).isTrue(); 550 assertThat(session.isPackageAccessAllowed(HELPER_PKG3, HELPER_PKG3_CERT_SHA256)) 551 .isTrue(); 552 assertThat(session.isPublicAccessAllowed()).isFalse(); 553 }); 554 555 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG); 556 assertPkgCannotAccess(blobData, HELPER_PKG2); 557 acquireLeaseAndAssertPkgCanAccess(blobData, HELPER_PKG3); 558 } finally { 559 blobData.delete(); 560 } 561 } 562 563 @Test testMixedAccessType_fromMultiplePackages()564 public void testMixedAccessType_fromMultiplePackages() throws Exception { 565 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 566 blobData.prepare(); 567 final TestServiceConnection connection1 = bindToHelperService(HELPER_PKG); 568 final TestServiceConnection connection2 = bindToHelperService(HELPER_PKG2); 569 final TestServiceConnection connection3 = bindToHelperService(HELPER_PKG3); 570 try { 571 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 572 assertThat(sessionId).isGreaterThan(0L); 573 574 commitBlob(blobData, session -> { 575 session.allowSameSignatureAccess(); 576 session.allowPackageAccess(HELPER_PKG2, HELPER_PKG2_CERT_SHA256); 577 assertThat(session.isSameSignatureAccessAllowed()).isTrue(); 578 assertThat(session.isPackageAccessAllowed(HELPER_PKG2, HELPER_PKG2_CERT_SHA256)) 579 .isTrue(); 580 assertThat(session.isPublicAccessAllowed()).isFalse(); 581 }); 582 583 acquireLeaseAndAssertPkgCanAccess(blobData, connection1); 584 acquireLeaseAndAssertPkgCanAccess(blobData, connection2); 585 assertPkgCannotAccess(blobData, connection3); 586 587 commitBlobFromPkg(blobData, ICommandReceiver.FLAG_ACCESS_TYPE_PUBLIC, connection2); 588 589 acquireLeaseAndAssertPkgCanAccess(blobData, connection1); 590 acquireLeaseAndAssertPkgCanAccess(blobData, connection2); 591 acquireLeaseAndAssertPkgCanAccess(blobData, connection3); 592 } finally { 593 blobData.delete(); 594 connection1.unbind(); 595 connection2.unbind(); 596 connection3.unbind(); 597 } 598 } 599 600 @Test testSessionCommit()601 public void testSessionCommit() throws Exception { 602 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 603 blobData.prepare(); 604 try { 605 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 606 assertThat(sessionId).isGreaterThan(0L); 607 608 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 609 blobData.writeToSession(session); 610 611 final ParcelFileDescriptor pfd = session.openWrite( 612 0L /* offset */, 0L /* length */); 613 assertThat(pfd).isNotNull(); 614 blobData.writeToFd(pfd.getFileDescriptor(), 0 /* offset */, 100 /* length */); 615 616 commitSession(sessionId, session, blobData.getBlobHandle()); 617 618 // Verify that writing to the session after commit will throw. 619 assertThrows(IOException.class, () -> blobData.writeToFd( 620 pfd.getFileDescriptor() /* length */, 0 /* offset */, 100 /* length */)); 621 assertThrows(IllegalStateException.class, () -> session.openWrite(0L, 0L)); 622 } 623 } finally { 624 blobData.delete(); 625 } 626 } 627 628 @Test testSessionCommit_incompleteData()629 public void testSessionCommit_incompleteData() throws Exception { 630 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 631 blobData.prepare(); 632 try { 633 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 634 assertThat(sessionId).isGreaterThan(0L); 635 636 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 637 blobData.writeToSession(session, 0, blobData.getFileSize() - 2); 638 639 commitSession(sessionId, session, blobData.getBlobHandle(), 640 false /* expectSuccess */); 641 } 642 } finally { 643 blobData.delete(); 644 } 645 } 646 647 @Test testSessionCommit_incorrectData()648 public void testSessionCommit_incorrectData() throws Exception { 649 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 650 blobData.prepare(); 651 try { 652 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 653 assertThat(sessionId).isGreaterThan(0L); 654 655 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 656 blobData.writeToSession(session, 0, blobData.getFileSize()); 657 try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( 658 session.openWrite(0, blobData.getFileSize()))) { 659 out.write("wrong_data".getBytes(StandardCharsets.UTF_8)); 660 } 661 662 commitSession(sessionId, session, blobData.getBlobHandle(), 663 false /* expectSuccess */); 664 } 665 } finally { 666 blobData.delete(); 667 } 668 } 669 670 @Test testRecommitBlob()671 public void testRecommitBlob() throws Exception { 672 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 673 blobData.prepare(); 674 675 try { 676 commitBlob(blobData); 677 // Verify that blob can be accessed after committing. 678 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 679 assertThat(pfd).isNotNull(); 680 blobData.verifyBlob(pfd); 681 } 682 683 commitBlob(blobData); 684 // Verify that blob can be accessed after re-committing. 685 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 686 assertThat(pfd).isNotNull(); 687 blobData.verifyBlob(pfd); 688 } 689 } finally { 690 blobData.delete(); 691 } 692 } 693 694 @Test testRecommitBlob_fromMultiplePackages()695 public void testRecommitBlob_fromMultiplePackages() throws Exception { 696 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 697 blobData.prepare(); 698 final TestServiceConnection connection = bindToHelperService(HELPER_PKG); 699 try { 700 commitBlob(blobData); 701 // Verify that blob can be accessed after committing. 702 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 703 assertThat(pfd).isNotNull(); 704 blobData.verifyBlob(pfd); 705 } 706 707 commitBlobFromPkg(blobData, connection); 708 // Verify that blob can be accessed after re-committing. 709 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 710 assertThat(pfd).isNotNull(); 711 blobData.verifyBlob(pfd); 712 } 713 acquireLeaseAndAssertPkgCanAccess(blobData, connection); 714 } finally { 715 blobData.delete(); 716 connection.unbind(); 717 } 718 } 719 720 @Test testSessionCommit_largeBlob()721 public void testSessionCommit_largeBlob() throws Exception { 722 final long fileSizeBytes = Math.min(mBlobStoreManager.getRemainingLeaseQuotaBytes(), 723 150 * 1024L * 1024L); 724 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 725 .setFileSize(fileSizeBytes) 726 .build(); 727 blobData.prepare(); 728 final long commitTimeoutSec = TIMEOUT_COMMIT_CALLBACK_SEC * 2; 729 try { 730 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 731 assertThat(sessionId).isGreaterThan(0L); 732 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 733 blobData.writeToSession(session); 734 735 Log.d(TAG, "Committing session: " + sessionId 736 + "; blob: " + blobData.getBlobHandle()); 737 final CompletableFuture<Integer> callback = new CompletableFuture<>(); 738 session.commit(mContext.getMainExecutor(), callback::complete); 739 assertThat(callback.get(commitTimeoutSec, TimeUnit.SECONDS)) 740 .isEqualTo(0); 741 } 742 743 // Verify that blob can be accessed after committing. 744 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 745 assertThat(pfd).isNotNull(); 746 blobData.verifyBlob(pfd); 747 } 748 } finally { 749 blobData.delete(); 750 } 751 } 752 753 @Test testCommitSession_multipleWrites()754 public void testCommitSession_multipleWrites() throws Exception { 755 final int numThreads = 2; 756 final Random random = new Random(0); 757 final ExecutorService executorService = Executors.newFixedThreadPool(numThreads); 758 final CompletableFuture<Throwable>[] completableFutures = new CompletableFuture[numThreads]; 759 for (int i = 0; i < numThreads; ++i) { 760 completableFutures[i] = CompletableFuture.supplyAsync(() -> { 761 final int minSizeMb = 30; 762 final long fileSizeBytes = (minSizeMb + random.nextInt(minSizeMb)) * 1024L * 1024L; 763 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 764 .setFileSize(fileSizeBytes) 765 .build(); 766 try { 767 blobData.prepare(); 768 commitAndVerifyBlob(blobData); 769 } catch (Throwable t) { 770 return t; 771 } finally { 772 blobData.delete(); 773 } 774 return null; 775 }, executorService); 776 } 777 final ArrayList<Throwable> invalidResults = new ArrayList<>(); 778 for (int i = 0; i < numThreads; ++i) { 779 final Throwable result = completableFutures[i].get(); 780 if (result != null) { 781 invalidResults.add(result); 782 } 783 } 784 assertThat(invalidResults).isEmpty(); 785 } 786 787 @Test testCommitSession_multipleReadWrites()788 public void testCommitSession_multipleReadWrites() throws Exception { 789 final int numThreads = 2; 790 final Random random = new Random(0); 791 final ExecutorService executorService = Executors.newFixedThreadPool(numThreads); 792 final CompletableFuture<Throwable>[] completableFutures = new CompletableFuture[numThreads]; 793 for (int i = 0; i < numThreads; ++i) { 794 completableFutures[i] = CompletableFuture.supplyAsync(() -> { 795 final int minSizeMb = 30; 796 final long fileSizeBytes = (minSizeMb + random.nextInt(minSizeMb)) * 1024L * 1024L; 797 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 798 .setFileSize(fileSizeBytes) 799 .build(); 800 try { 801 blobData.prepare(); 802 final long sessionId = mBlobStoreManager.createSession( 803 blobData.getBlobHandle()); 804 assertThat(sessionId).isGreaterThan(0L); 805 try (BlobStoreManager.Session session = mBlobStoreManager.openSession( 806 sessionId)) { 807 final long partialFileSizeBytes = minSizeMb * 1024L * 1024L; 808 blobData.writeToSession(session, 0L, partialFileSizeBytes, 809 blobData.getFileSize()); 810 blobData.readFromSessionAndVerifyBytes(session, 0L, 811 (int) partialFileSizeBytes); 812 blobData.writeToSession(session, partialFileSizeBytes, 813 blobData.getFileSize() - partialFileSizeBytes, 814 blobData.getFileSize()); 815 blobData.readFromSessionAndVerifyBytes(session, partialFileSizeBytes, 816 (int) (blobData.getFileSize() - partialFileSizeBytes)); 817 818 commitSession(sessionId, session, blobData.getBlobHandle()); 819 } 820 821 // Verify that blob can be accessed after committing. 822 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob( 823 blobData.getBlobHandle())) { 824 assertThat(pfd).isNotNull(); 825 blobData.verifyBlob(pfd); 826 } 827 } catch (Throwable t) { 828 return t; 829 } finally { 830 blobData.delete(); 831 } 832 return null; 833 }, executorService); 834 } 835 final ArrayList<Throwable> invalidResults = new ArrayList<>(); 836 for (int i = 0; i < numThreads; ++i) { 837 final Throwable result = completableFutures[i].get(); 838 if (result != null) { 839 invalidResults.add(result); 840 } 841 } 842 assertThat(invalidResults).isEmpty(); 843 } 844 845 @Test testOpenBlob()846 public void testOpenBlob() throws Exception { 847 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 848 blobData.prepare(); 849 try { 850 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 851 assertThat(sessionId).isGreaterThan(0L); 852 853 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 854 blobData.writeToSession(session); 855 856 // Verify that trying to accessed the blob before commit throws 857 assertThrows(SecurityException.class, 858 () -> mBlobStoreManager.openBlob(blobData.getBlobHandle())); 859 860 commitSession(sessionId, session, blobData.getBlobHandle()); 861 } 862 863 // Verify that blob can be accessed after committing. 864 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 865 assertThat(pfd).isNotNull(); 866 867 blobData.verifyBlob(pfd); 868 } 869 } finally { 870 blobData.delete(); 871 } 872 } 873 874 @Test testOpenBlob_invalidArguments()875 public void testOpenBlob_invalidArguments() throws Exception { 876 assertThrows(NullPointerException.class, () -> mBlobStoreManager.openBlob(null)); 877 } 878 879 @Test testAcquireReleaseLease()880 public void testAcquireReleaseLease() throws Exception { 881 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 882 blobData.prepare(); 883 try { 884 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 885 assertThat(sessionId).isGreaterThan(0L); 886 887 commitBlob(blobData); 888 889 assertThrows(IllegalArgumentException.class, () -> 890 acquireLease(mContext, blobData.getBlobHandle(), 891 R.string.test_desc, blobData.getExpiryTimeMillis() + 1000)); 892 assertNoLeasedBlobs(mBlobStoreManager); 893 894 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 895 blobData.getExpiryTimeMillis() - 1000); 896 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 897 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc); 898 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 899 releaseLease(mContext, blobData.getBlobHandle()); 900 assertNoLeasedBlobs(mBlobStoreManager); 901 902 acquireLease(mContext, blobData.getBlobHandle(), "Test description", 903 blobData.getExpiryTimeMillis() - 20000); 904 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 905 acquireLease(mContext, blobData.getBlobHandle(), "Test description two"); 906 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 907 releaseLease(mContext, blobData.getBlobHandle()); 908 assertNoLeasedBlobs(mBlobStoreManager); 909 } finally { 910 blobData.delete(); 911 } 912 } 913 914 @Test testAcquireLease_multipleLeases()915 public void testAcquireLease_multipleLeases() throws Exception { 916 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 917 final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext) 918 .setRandomSeed(42) 919 .build(); 920 blobData.prepare(); 921 blobData2.prepare(); 922 try { 923 commitBlob(blobData); 924 925 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 926 blobData.getExpiryTimeMillis() - 1000); 927 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 928 929 commitBlob(blobData2); 930 931 acquireLease(mContext, blobData2.getBlobHandle(), "Test desc2", 932 blobData.getExpiryTimeMillis() - 2000); 933 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle(), 934 blobData2.getBlobHandle()); 935 936 releaseLease(mContext, blobData.getBlobHandle()); 937 assertLeasedBlobs(mBlobStoreManager, blobData2.getBlobHandle()); 938 939 releaseLease(mContext, blobData2.getBlobHandle()); 940 assertNoLeasedBlobs(mBlobStoreManager); 941 } finally { 942 blobData.delete(); 943 blobData2.delete(); 944 } 945 } 946 947 @Test testAcquireLease_multipleLeasees()948 public void testAcquireLease_multipleLeasees() throws Exception { 949 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 950 blobData.prepare(); 951 final TestServiceConnection serviceConnection = bindToHelperService(HELPER_PKG); 952 try { 953 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 954 assertThat(sessionId).isGreaterThan(0L); 955 956 commitBlob(blobData, session -> { 957 session.allowPublicAccess(); 958 assertThat(session.isPublicAccessAllowed()).isTrue(); 959 }); 960 961 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc); 962 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 963 acquireLeaseFrmPkg(blobData, serviceConnection); 964 assertPkgCanAccess(blobData, serviceConnection); 965 966 // Release the lease from this package 967 releaseLease(mContext, blobData.getBlobHandle()); 968 assertNoLeasedBlobs(mBlobStoreManager); 969 970 // Helper package should still be able to read the blob 971 assertPkgCanAccess(blobData, serviceConnection); 972 } finally { 973 blobData.delete(); 974 serviceConnection.unbind(); 975 } 976 } 977 978 @Test testAcquireLease_leaseExpired()979 public void testAcquireLease_leaseExpired() throws Exception { 980 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 981 blobData.prepare(); 982 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 983 try { 984 commitBlob(blobData); 985 986 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 987 System.currentTimeMillis() + waitDurationMs); 988 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 989 990 waitForLeaseExpiration(waitDurationMs, blobData.getBlobHandle()); 991 assertNoLeasedBlobs(mBlobStoreManager); 992 } finally { 993 blobData.delete(); 994 } 995 } 996 997 @Test testAcquireRelease_deleteAfterDelay()998 public void testAcquireRelease_deleteAfterDelay() throws Exception { 999 final long waitDurationMs = TimeUnit.SECONDS.toMillis(1); 1000 runWithKeyValues(() -> { 1001 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1002 blobData.prepare(); 1003 try { 1004 commitBlob(blobData); 1005 1006 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1007 blobData.getExpiryTimeMillis()); 1008 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 1009 1010 SystemClock.sleep(waitDurationMs); 1011 1012 releaseLease(mContext, blobData.getBlobHandle()); 1013 assertNoLeasedBlobs(mBlobStoreManager); 1014 1015 SystemClock.sleep(waitDurationMs); 1016 SystemUtil.runWithShellPermissionIdentity(() -> 1017 mBlobStoreManager.waitForIdle(TIMEOUT_WAIT_FOR_IDLE_MS)); 1018 1019 assertThrows(SecurityException.class, () -> mBlobStoreManager.acquireLease( 1020 blobData.getBlobHandle(), R.string.test_desc, 1021 blobData.getExpiryTimeMillis())); 1022 assertNoLeasedBlobs(mBlobStoreManager); 1023 } finally { 1024 blobData.delete(); 1025 } 1026 }, Pair.create(KEY_LEASE_ACQUISITION_WAIT_DURATION_MS, String.valueOf(waitDurationMs)), 1027 Pair.create(KEY_DELETE_ON_LAST_LEASE_DELAY_MS, String.valueOf(waitDurationMs))); 1028 } 1029 1030 @Test testAcquireReleaseLease_invalidArguments()1031 public void testAcquireReleaseLease_invalidArguments() throws Exception { 1032 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1033 blobData.prepare(); 1034 try { 1035 assertThrows(NullPointerException.class, () -> mBlobStoreManager.acquireLease( 1036 null, R.string.test_desc, blobData.getExpiryTimeMillis())); 1037 assertThrows(IllegalArgumentException.class, () -> mBlobStoreManager.acquireLease( 1038 blobData.getBlobHandle(), R.string.test_desc, -1)); 1039 assertThrows(IllegalArgumentException.class, () -> mBlobStoreManager.acquireLease( 1040 blobData.getBlobHandle(), -1)); 1041 assertThrows(IllegalArgumentException.class, () -> mBlobStoreManager.acquireLease( 1042 blobData.getBlobHandle(), null)); 1043 assertThrows(IllegalArgumentException.class, () -> mBlobStoreManager.acquireLease( 1044 blobData.getBlobHandle(), null, blobData.getExpiryTimeMillis())); 1045 } finally { 1046 blobData.delete(); 1047 } 1048 1049 assertThrows(NullPointerException.class, () -> mBlobStoreManager.releaseLease(null)); 1050 } 1051 1052 @Test testStorageAttributedToSelf()1053 public void testStorageAttributedToSelf() throws Exception { 1054 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1055 blobData.prepare(); 1056 final long partialFileSize = 3373L; 1057 1058 final StorageStatsManager storageStatsManager = mContext.getSystemService( 1059 StorageStatsManager.class); 1060 StorageStats beforeStatsForPkg = storageStatsManager 1061 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1062 StorageStats beforeStatsForUid = storageStatsManager 1063 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1064 1065 // Create a session and write some data. 1066 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1067 assertThat(sessionId).isGreaterThan(0L); 1068 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1069 blobData.writeToSession(session, 0, partialFileSize, partialFileSize); 1070 } 1071 1072 StorageStats afterStatsForPkg = storageStatsManager 1073 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1074 StorageStats afterStatsForUid = storageStatsManager 1075 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1076 1077 // 'partialFileSize' bytes were written, verify the size increase. 1078 assertSizeBytesMostlyEquals(partialFileSize, 1079 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1080 assertSizeBytesMostlyEquals(partialFileSize, 1081 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1082 1083 // Complete writing data. 1084 final long totalFileSize = blobData.getFileSize(); 1085 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1086 blobData.writeToSession(session, partialFileSize, totalFileSize - partialFileSize, 1087 totalFileSize); 1088 } 1089 1090 afterStatsForPkg = storageStatsManager 1091 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1092 afterStatsForUid = storageStatsManager 1093 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1094 1095 // 'totalFileSize' bytes were written so far, verify the size increase. 1096 assertSizeBytesMostlyEquals(totalFileSize, 1097 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1098 assertSizeBytesMostlyEquals(totalFileSize, 1099 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1100 1101 // Commit the session. 1102 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1103 blobData.writeToSession(session, partialFileSize, 1104 session.getSize() - partialFileSize, blobData.getFileSize()); 1105 commitSession(sessionId, session, blobData.getBlobHandle()); 1106 } 1107 1108 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc); 1109 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 1110 1111 afterStatsForPkg = storageStatsManager 1112 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1113 afterStatsForUid = storageStatsManager 1114 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1115 1116 // Session was committed but no one else is using it, verify the size increase stays 1117 // the same as earlier. 1118 assertSizeBytesMostlyEquals(totalFileSize, 1119 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1120 assertSizeBytesMostlyEquals(totalFileSize, 1121 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1122 1123 releaseLease(mContext, blobData.getBlobHandle()); 1124 assertNoLeasedBlobs(mBlobStoreManager); 1125 1126 afterStatsForPkg = storageStatsManager 1127 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1128 afterStatsForUid = storageStatsManager 1129 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1130 1131 // No leases on the blob, so it should not be attributed. 1132 assertSizeBytesMostlyEquals(0L, 1133 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1134 assertSizeBytesMostlyEquals(0L, 1135 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1136 } 1137 1138 @Test testStorageAttribution_acquireLease()1139 public void testStorageAttribution_acquireLease() throws Exception { 1140 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1141 blobData.prepare(); 1142 1143 final StorageStatsManager storageStatsManager = mContext.getSystemService( 1144 StorageStatsManager.class); 1145 StorageStats beforeStatsForPkg = storageStatsManager 1146 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1147 StorageStats beforeStatsForUid = storageStatsManager 1148 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1149 1150 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1151 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1152 blobData.writeToSession(session); 1153 session.allowPublicAccess(); 1154 1155 commitSession(sessionId, session, blobData.getBlobHandle()); 1156 } 1157 1158 StorageStats afterStatsForPkg = storageStatsManager 1159 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1160 StorageStats afterStatsForUid = storageStatsManager 1161 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1162 1163 // No leases on the blob, so it should not be attributed. 1164 assertSizeBytesMostlyEquals(0L, 1165 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1166 assertSizeBytesMostlyEquals(0L, 1167 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1168 1169 final TestServiceConnection serviceConnection = bindToHelperService(HELPER_PKG); 1170 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1171 try { 1172 StorageStats beforeStatsForHelperPkg = commandReceiver.queryStatsForPackage(); 1173 StorageStats beforeStatsForHelperUid = commandReceiver.queryStatsForUid(); 1174 1175 commandReceiver.acquireLease(blobData.getBlobHandle()); 1176 1177 StorageStats afterStatsForHelperPkg = commandReceiver.queryStatsForPackage(); 1178 StorageStats afterStatsForHelperUid = commandReceiver.queryStatsForUid(); 1179 1180 assertSizeBytesMostlyEquals(blobData.getFileSize(), 1181 afterStatsForHelperPkg.getDataBytes() - beforeStatsForHelperPkg.getDataBytes()); 1182 assertSizeBytesMostlyEquals(blobData.getFileSize(), 1183 afterStatsForHelperUid.getDataBytes() - beforeStatsForHelperUid.getDataBytes()); 1184 1185 afterStatsForPkg = storageStatsManager 1186 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), 1187 mContext.getUser()); 1188 afterStatsForUid = storageStatsManager 1189 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1190 1191 // There shouldn't be no change in stats for this package 1192 assertSizeBytesMostlyEquals(0L, 1193 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1194 assertSizeBytesMostlyEquals(0L, 1195 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1196 1197 commandReceiver.releaseLease(blobData.getBlobHandle()); 1198 1199 afterStatsForHelperPkg = commandReceiver.queryStatsForPackage(); 1200 afterStatsForHelperUid = commandReceiver.queryStatsForUid(); 1201 1202 // Lease is released, so it should not be attributed anymore. 1203 assertSizeBytesMostlyEquals(0L, 1204 afterStatsForHelperPkg.getDataBytes() - beforeStatsForHelperPkg.getDataBytes()); 1205 assertSizeBytesMostlyEquals(0L, 1206 afterStatsForHelperUid.getDataBytes() - beforeStatsForHelperUid.getDataBytes()); 1207 } finally { 1208 serviceConnection.unbind(); 1209 } 1210 } 1211 1212 @Test testStorageAttribution_withExpiredLease()1213 public void testStorageAttribution_withExpiredLease() throws Exception { 1214 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1215 blobData.prepare(); 1216 1217 final StorageStatsManager storageStatsManager = mContext.getSystemService( 1218 StorageStatsManager.class); 1219 StorageStats beforeStatsForPkg = storageStatsManager 1220 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1221 StorageStats beforeStatsForUid = storageStatsManager 1222 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1223 1224 commitBlob(blobData); 1225 1226 StorageStats afterStatsForPkg = storageStatsManager 1227 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1228 StorageStats afterStatsForUid = storageStatsManager 1229 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1230 1231 // No leases on the blob, so it should not be attributed. 1232 assertSizeBytesMostlyEquals(0L, 1233 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1234 assertSizeBytesMostlyEquals(0L, 1235 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1236 1237 final long leaseExpiryDurationMs = TimeUnit.SECONDS.toMillis(5); 1238 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1239 System.currentTimeMillis() + leaseExpiryDurationMs); 1240 1241 final long startTimeMs = System.currentTimeMillis(); 1242 afterStatsForPkg = storageStatsManager 1243 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1244 afterStatsForUid = storageStatsManager 1245 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1246 1247 assertSizeBytesMostlyEquals(blobData.getFileSize(), 1248 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1249 assertSizeBytesMostlyEquals(blobData.getFileSize(), 1250 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1251 1252 waitForLeaseExpiration( 1253 Math.abs(leaseExpiryDurationMs - (System.currentTimeMillis() - startTimeMs)), 1254 blobData.getBlobHandle()); 1255 1256 afterStatsForPkg = storageStatsManager 1257 .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser()); 1258 afterStatsForUid = storageStatsManager 1259 .queryStatsForUid(UUID_DEFAULT, Process.myUid()); 1260 1261 // Lease is expired, so it should not be attributed anymore. 1262 assertSizeBytesMostlyEquals(0L, 1263 afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes()); 1264 assertSizeBytesMostlyEquals(0L, 1265 afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes()); 1266 1267 blobData.delete(); 1268 } 1269 1270 @Test testLeaseQuotaExceeded()1271 public void testLeaseQuotaExceeded() throws Exception { 1272 final long totalBytes = Environment.getDataDirectory().getTotalSpace(); 1273 final long limitBytes = 100 * Utils.MB_IN_BYTES; 1274 runWithKeyValues(() -> { 1275 final LongSparseArray<BlobHandle> blobs = new LongSparseArray<>(); 1276 for (long blobSize : new long[] {20L, 30L, 40L}) { 1277 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1278 .setFileSize(blobSize * Utils.MB_IN_BYTES) 1279 .build(); 1280 blobData.prepare(); 1281 1282 commitBlob(blobData); 1283 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1284 blobData.getExpiryTimeMillis()); 1285 blobs.put(blobSize, blobData.getBlobHandle()); 1286 } 1287 final long blobSize = 40L; 1288 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1289 .setFileSize(blobSize * Utils.MB_IN_BYTES) 1290 .build(); 1291 blobData.prepare(); 1292 commitBlob(blobData); 1293 assertThrows(LimitExceededException.class, () -> mBlobStoreManager.acquireLease( 1294 blobData.getBlobHandle(), R.string.test_desc, blobData.getExpiryTimeMillis())); 1295 1296 releaseLease(mContext, blobs.get(20L)); 1297 assertThrows(LimitExceededException.class, () -> mBlobStoreManager.acquireLease( 1298 blobData.getBlobHandle(), R.string.test_desc, blobData.getExpiryTimeMillis())); 1299 1300 releaseLease(mContext, blobs.get(30L)); 1301 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1302 blobData.getExpiryTimeMillis()); 1303 }, Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR, String.valueOf(limitBytes)), 1304 Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION, 1305 String.valueOf((double) limitBytes / totalBytes))); 1306 } 1307 1308 @Test testLeaseQuotaExceeded_singleFileExceedsLimit()1309 public void testLeaseQuotaExceeded_singleFileExceedsLimit() throws Exception { 1310 final long totalBytes = Environment.getDataDirectory().getTotalSpace(); 1311 final long limitBytes = 50 * Utils.MB_IN_BYTES; 1312 runWithKeyValues(() -> { 1313 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1314 .setFileSize(limitBytes + (5 * Utils.MB_IN_BYTES)) 1315 .build(); 1316 blobData.prepare(); 1317 commitBlob(blobData); 1318 assertThrows(LimitExceededException.class, () -> mBlobStoreManager.acquireLease( 1319 blobData.getBlobHandle(), R.string.test_desc, blobData.getExpiryTimeMillis())); 1320 }, Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR, String.valueOf(limitBytes)), 1321 Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION, 1322 String.valueOf((double) limitBytes / totalBytes))); 1323 } 1324 1325 @Test testLeaseQuotaExceeded_withExpiredLease()1326 public void testLeaseQuotaExceeded_withExpiredLease() throws Exception { 1327 final long totalBytes = Environment.getDataDirectory().getTotalSpace(); 1328 final long limitBytes = 50 * Utils.MB_IN_BYTES; 1329 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 1330 runWithKeyValues(() -> { 1331 final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext) 1332 .setFileSize(40 * Utils.MB_IN_BYTES) 1333 .build(); 1334 blobData1.prepare(); 1335 final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext) 1336 .setFileSize(30 * Utils.MB_IN_BYTES) 1337 .build(); 1338 blobData2.prepare(); 1339 1340 commitBlob(blobData1); 1341 commitBlob(blobData2); 1342 acquireLease(mContext, blobData1.getBlobHandle(), R.string.test_desc, 1343 System.currentTimeMillis() + waitDurationMs); 1344 1345 assertThrows(LimitExceededException.class, () -> mBlobStoreManager.acquireLease( 1346 blobData2.getBlobHandle(), R.string.test_desc)); 1347 1348 waitForLeaseExpiration(waitDurationMs, blobData1.getBlobHandle()); 1349 acquireLease(mContext, blobData2.getBlobHandle(), R.string.test_desc); 1350 }, Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR, String.valueOf(limitBytes)), 1351 Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION, 1352 String.valueOf((double) limitBytes / totalBytes))); 1353 } 1354 1355 @Test testRemainingLeaseQuota()1356 public void testRemainingLeaseQuota() throws Exception { 1357 final long initialRemainingQuota = mBlobStoreManager.getRemainingLeaseQuotaBytes(); 1358 final long blobSize = 100 * Utils.MB_IN_BYTES; 1359 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1360 .setFileSize(blobSize) 1361 .build(); 1362 blobData.prepare(); 1363 1364 commitBlob(blobData); 1365 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1366 blobData.getExpiryTimeMillis()); 1367 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 1368 1369 assertThat(mBlobStoreManager.getRemainingLeaseQuotaBytes()) 1370 .isEqualTo(initialRemainingQuota - blobSize); 1371 1372 releaseLease(mContext, blobData.getBlobHandle()); 1373 assertNoLeasedBlobs(mBlobStoreManager); 1374 1375 assertThat(mBlobStoreManager.getRemainingLeaseQuotaBytes()) 1376 .isEqualTo(initialRemainingQuota); 1377 } 1378 1379 @Test testRemainingLeaseQuota_withExpiredLease()1380 public void testRemainingLeaseQuota_withExpiredLease() throws Exception { 1381 final long initialRemainingQuota = mBlobStoreManager.getRemainingLeaseQuotaBytes(); 1382 final long blobSize = 100 * Utils.MB_IN_BYTES; 1383 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1384 .setFileSize(blobSize) 1385 .build(); 1386 blobData.prepare(); 1387 1388 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 1389 commitBlob(blobData); 1390 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1391 System.currentTimeMillis() + waitDurationMs); 1392 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 1393 1394 assertThat(mBlobStoreManager.getRemainingLeaseQuotaBytes()) 1395 .isEqualTo(initialRemainingQuota - blobSize); 1396 1397 waitForLeaseExpiration(waitDurationMs, blobData.getBlobHandle()); 1398 assertNoLeasedBlobs(mBlobStoreManager); 1399 1400 assertThat(mBlobStoreManager.getRemainingLeaseQuotaBytes()) 1401 .isEqualTo(initialRemainingQuota); 1402 } 1403 1404 @Test testAccessExpiredBlob()1405 public void testAccessExpiredBlob() throws Exception { 1406 final long expiryDurationMs = TimeUnit.SECONDS.toMillis(6); 1407 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1408 .setExpiryDurationMs(expiryDurationMs) 1409 .build(); 1410 blobData.prepare(); 1411 1412 final long startTimeMs = System.currentTimeMillis(); 1413 final long blobId = commitBlob(blobData); 1414 assertTrue(blobExists(blobId, mUserId)); 1415 1416 // Wait for the blob to expire 1417 SystemClock.sleep(expiryDurationMs); 1418 1419 assertThrows(SecurityException.class, 1420 () -> mBlobStoreManager.openBlob(blobData.getBlobHandle())); 1421 assertThrows(SecurityException.class, 1422 () -> mBlobStoreManager.acquireLease(blobData.getBlobHandle(), 1423 R.string.test_desc)); 1424 1425 triggerIdleMaintenance(); 1426 assertFalse(blobExists(blobId, mUserId)); 1427 } 1428 1429 @Test testAccessExpiredBlob_withLeaseAcquired()1430 public void testAccessExpiredBlob_withLeaseAcquired() throws Exception { 1431 final long expiryDurationMs = TimeUnit.SECONDS.toMillis(6); 1432 final FakeBlobData blobData = new FakeBlobData.Builder(mContext) 1433 .setExpiryDurationMs(expiryDurationMs) 1434 .build(); 1435 blobData.prepare(); 1436 1437 final long startTimeMs = System.currentTimeMillis(); 1438 final long blobId = commitBlob(blobData); 1439 assertTrue(blobExists(blobId, mUserId)); 1440 1441 final long commitDurationMs = System.currentTimeMillis() - startTimeMs; 1442 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc); 1443 1444 SystemClock.sleep(Math.abs(expiryDurationMs - commitDurationMs)); 1445 1446 assertThrows(SecurityException.class, 1447 () -> mBlobStoreManager.openBlob(blobData.getBlobHandle())); 1448 assertThrows(SecurityException.class, 1449 () -> mBlobStoreManager.acquireLease(blobData.getBlobHandle(), 1450 R.string.test_desc)); 1451 1452 triggerIdleMaintenance(); 1453 assertFalse(blobExists(blobId, mUserId)); 1454 } 1455 1456 @Test testAccessBlob_withExpiredLease()1457 public void testAccessBlob_withExpiredLease() throws Exception { 1458 final long leaseExpiryDurationMs = TimeUnit.SECONDS.toMillis(2); 1459 final long leaseAcquisitionWaitDurationMs = TimeUnit.SECONDS.toMillis(1); 1460 runWithKeyValues(() -> { 1461 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1462 blobData.prepare(); 1463 try { 1464 final long blobId = commitBlob(blobData); 1465 assertTrue(blobExists(blobId, mUserId)); 1466 1467 acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc, 1468 System.currentTimeMillis() + leaseExpiryDurationMs); 1469 assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle()); 1470 1471 waitForLeaseExpiration(leaseExpiryDurationMs, blobData.getBlobHandle()); 1472 assertNoLeasedBlobs(mBlobStoreManager); 1473 1474 triggerIdleMaintenance(); 1475 assertFalse(blobExists(blobId, mUserId)); 1476 1477 assertThrows(SecurityException.class, () -> mBlobStoreManager.acquireLease( 1478 blobData.getBlobHandle(), R.string.test_desc, 1479 blobData.getExpiryTimeMillis())); 1480 assertNoLeasedBlobs(mBlobStoreManager); 1481 } finally { 1482 blobData.delete(); 1483 } 1484 }, Pair.create(KEY_LEASE_ACQUISITION_WAIT_DURATION_MS, 1485 String.valueOf(leaseAcquisitionWaitDurationMs))); 1486 } 1487 1488 @Test testCommitBlobAfterIdleMaintenance()1489 public void testCommitBlobAfterIdleMaintenance() throws Exception { 1490 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1491 blobData.prepare(); 1492 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 1493 final long partialFileSize = 100L; 1494 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1495 assertThat(sessionId).isGreaterThan(0L); 1496 1497 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1498 blobData.writeToSession(session, 0, partialFileSize, blobData.getFileSize()); 1499 } 1500 1501 SystemClock.sleep(waitDurationMs); 1502 1503 // Trigger idle maintenance which takes of deleting expired sessions. 1504 triggerIdleMaintenance(); 1505 1506 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1507 blobData.writeToSession(session, partialFileSize, 1508 blobData.getFileSize() - partialFileSize, blobData.getFileSize()); 1509 commitSession(sessionId, session, blobData.getBlobHandle()); 1510 } 1511 } 1512 1513 @Test testExpiredSessionsDeleted()1514 public void testExpiredSessionsDeleted() throws Exception { 1515 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 1516 runWithKeyValues(() -> { 1517 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1518 blobData.prepare(); 1519 1520 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1521 assertThat(sessionId).isGreaterThan(0L); 1522 1523 SystemClock.sleep(waitDurationMs); 1524 1525 // Trigger idle maintenance which takes of deleting expired sessions. 1526 triggerIdleMaintenance(); 1527 1528 assertThrows(SecurityException.class, () -> mBlobStoreManager.openSession(sessionId)); 1529 }, Pair.create(KEY_SESSION_EXPIRY_TIMEOUT_MS, String.valueOf(waitDurationMs))); 1530 } 1531 1532 @Test testExpiredSessionsDeleted_withPartialData()1533 public void testExpiredSessionsDeleted_withPartialData() throws Exception { 1534 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 1535 runWithKeyValues(() -> { 1536 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1537 blobData.prepare(); 1538 1539 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1540 assertThat(sessionId).isGreaterThan(0L); 1541 1542 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1543 blobData.writeToSession(session, 0, 100, blobData.getFileSize()); 1544 } 1545 1546 SystemClock.sleep(waitDurationMs); 1547 1548 // Trigger idle maintenance which takes of deleting expired sessions. 1549 triggerIdleMaintenance(); 1550 1551 assertThrows(SecurityException.class, () -> mBlobStoreManager.openSession(sessionId)); 1552 }, Pair.create(KEY_SESSION_EXPIRY_TIMEOUT_MS, String.valueOf(waitDurationMs))); 1553 } 1554 1555 @Test testCreateSession_countLimitExceeded()1556 public void testCreateSession_countLimitExceeded() throws Exception { 1557 runWithKeyValues(() -> { 1558 final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build(); 1559 blobData1.prepare(); 1560 final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build(); 1561 blobData2.prepare(); 1562 1563 mBlobStoreManager.createSession(blobData1.getBlobHandle()); 1564 assertThrows(LimitExceededException.class, 1565 () -> mBlobStoreManager.createSession(blobData2.getBlobHandle())); 1566 }, Pair.create(KEY_MAX_ACTIVE_SESSIONS, String.valueOf(1))); 1567 } 1568 1569 @Test testCommitSession_countLimitExceeded()1570 public void testCommitSession_countLimitExceeded() throws Exception { 1571 runWithKeyValues(() -> { 1572 final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build(); 1573 blobData1.prepare(); 1574 final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build(); 1575 blobData2.prepare(); 1576 1577 commitBlob(blobData1, null /* accessModifier */, true /* expectSuccess */); 1578 commitBlob(blobData2, null /* accessModifier */, false /* expectSuccess */); 1579 }, Pair.create(KEY_MAX_COMMITTED_BLOBS, String.valueOf(1))); 1580 } 1581 1582 @Test testLeaseBlob_countLimitExceeded()1583 public void testLeaseBlob_countLimitExceeded() throws Exception { 1584 runWithKeyValues(() -> { 1585 final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build(); 1586 blobData1.prepare(); 1587 final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build(); 1588 blobData2.prepare(); 1589 1590 commitBlob(blobData1); 1591 commitBlob(blobData2); 1592 1593 acquireLease(mContext, blobData1.getBlobHandle(), "test desc"); 1594 assertThrows(LimitExceededException.class, 1595 () -> acquireLease(mContext, blobData2.getBlobHandle(), "test desc")); 1596 }, Pair.create(KEY_MAX_LEASED_BLOBS, String.valueOf(1))); 1597 } 1598 1599 @Test testExpiredLease_countLimitExceeded()1600 public void testExpiredLease_countLimitExceeded() throws Exception { 1601 runWithKeyValues(() -> { 1602 final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build(); 1603 blobData1.prepare(); 1604 final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build(); 1605 blobData2.prepare(); 1606 final FakeBlobData blobData3 = new FakeBlobData.Builder(mContext).build(); 1607 blobData3.prepare(); 1608 final long waitDurationMs = TimeUnit.SECONDS.toMillis(2); 1609 1610 commitBlob(blobData1); 1611 commitBlob(blobData2); 1612 commitBlob(blobData3); 1613 1614 acquireLease(mContext, blobData1.getBlobHandle(), "test desc1", 1615 System.currentTimeMillis() + waitDurationMs); 1616 assertThrows(LimitExceededException.class, 1617 () -> acquireLease(mContext, blobData2.getBlobHandle(), "test desc2", 1618 System.currentTimeMillis() + TimeUnit.HOURS.toMillis(4))); 1619 1620 waitForLeaseExpiration(waitDurationMs, blobData1.getBlobHandle()); 1621 1622 acquireLease(mContext, blobData2.getBlobHandle(), "test desc2", 1623 System.currentTimeMillis() + TimeUnit.HOURS.toMillis(4)); 1624 assertThrows(LimitExceededException.class, 1625 () -> acquireLease(mContext, blobData3.getBlobHandle(), "test desc3")); 1626 }, Pair.create(KEY_MAX_LEASED_BLOBS, String.valueOf(1))); 1627 } 1628 1629 @Test testAllowPackageAccess_countLimitExceeded()1630 public void testAllowPackageAccess_countLimitExceeded() throws Exception { 1631 runWithKeyValues(() -> { 1632 final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build(); 1633 blobData.prepare(); 1634 1635 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1636 assertThat(sessionId).isGreaterThan(0L); 1637 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1638 blobData.writeToSession(session); 1639 1640 session.allowPackageAccess(HELPER_PKG2, HELPER_PKG2_CERT_SHA256); 1641 assertThrows(LimitExceededException.class, 1642 () -> session.allowPackageAccess(HELPER_PKG3, HELPER_PKG3_CERT_SHA256)); 1643 } 1644 }, Pair.create(KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES, String.valueOf(1))); 1645 } 1646 1647 @Test testBlobHandleEquality()1648 public void testBlobHandleEquality() throws Exception { 1649 // Check that BlobHandle objects are considered equal when digest, label, expiry time 1650 // and tag are equal. 1651 { 1652 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(), 1653 "Fake blob", 1111L, "tag"); 1654 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(), 1655 "Fake blob", 1111L, "tag"); 1656 assertThat(blobHandle1).isEqualTo(blobHandle2); 1657 } 1658 1659 // Check that BlobHandle objects are not equal if digests are not equal. 1660 { 1661 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), 1662 "Fake blob", 1111L, "tag"); 1663 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), 1664 "Fake blob", 1111L, "tag"); 1665 assertThat(blobHandle1).isNotEqualTo(blobHandle2); 1666 } 1667 1668 // Check that BlobHandle objects are not equal if expiry times are not equal. 1669 { 1670 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(), 1671 "Fake blob", 1111L, "tag"); 1672 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(), 1673 "Fake blob", 1112L, "tag"); 1674 assertThat(blobHandle1).isNotEqualTo(blobHandle2); 1675 } 1676 1677 // Check that BlobHandle objects are not equal if labels are not equal. 1678 { 1679 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(), 1680 "Fake blob1", 1111L, "tag"); 1681 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(), 1682 "Fake blob2", 1111L, "tag"); 1683 assertThat(blobHandle1).isNotEqualTo(blobHandle2); 1684 } 1685 1686 // Check that BlobHandle objects are not equal if tags are not equal. 1687 { 1688 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(), 1689 "Fake blob", 1111L, "tag1"); 1690 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(), 1691 "Fake blob", 1111L, "tag2"); 1692 assertThat(blobHandle1).isNotEqualTo(blobHandle2); 1693 } 1694 } 1695 1696 @Test testBlobHandleCreation()1697 public void testBlobHandleCreation() throws Exception { 1698 // Creating a BlobHandle with label > 100 chars will fail 1699 { 1700 final CharSequence label = String.join("", Collections.nCopies(101, "a")); 1701 assertThrows(IllegalArgumentException.class, 1702 () -> BlobHandle.createWithSha256("digest".getBytes(), label, 1111L, "tag")); 1703 } 1704 1705 // Creating a BlobHandle with tag > 128 chars will fail 1706 { 1707 final String tag = String.join("", Collections.nCopies(129, "a")); 1708 assertThrows(IllegalArgumentException.class, 1709 () -> BlobHandle.createWithSha256("digest".getBytes(), "label", 1111L, tag)); 1710 } 1711 } 1712 waitUntilStorageCleared(String packageName)1713 private void waitUntilStorageCleared(String packageName) { 1714 final StorageStatsManager storageStatsManager = mContext.getSystemService( 1715 StorageStatsManager.class); 1716 PollingCheck.waitFor(() -> { 1717 final long dataBytes = SystemUtil.runWithShellPermissionIdentity(() -> { 1718 try { 1719 return storageStatsManager.queryStatsForPackage(UUID_DEFAULT, packageName, 1720 mContext.getUser()).getDataBytes(); 1721 } catch (PackageManager.NameNotFoundException | IOException e) { 1722 Log.d(TAG, "Exception while querying the storage stats for pkg: " 1723 + packageName, e); 1724 return 0L; 1725 } 1726 }); 1727 Log.i(TAG, "Queried dataBytes for " + packageName + ": " + dataBytes); 1728 return dataBytes < DELTA_BYTES; 1729 }, "Timed out waiting for storage to be cleared for pkg: " + packageName); 1730 } 1731 runWithKeyValues(ThrowingRunnable runnable, Pair<String, String>... keyValues)1732 private static void runWithKeyValues(ThrowingRunnable runnable, 1733 Pair<String, String>... keyValues) throws Exception { 1734 final Map<String, String> previousValues = new ArrayMap(); 1735 SystemUtil.runWithShellPermissionIdentity(() -> { 1736 for (Pair<String, String> pair : keyValues) { 1737 final String key = pair.first; 1738 final String value = pair.second; 1739 final String previousValue = DeviceConfig.getProperty(NAMESPACE_BLOBSTORE, key); 1740 if (!Objects.equals(previousValue, value)) { 1741 previousValues.put(key, previousValue); 1742 Log.i(TAG, key + " previous value: " + previousValue); 1743 assertThat(DeviceConfig.setProperty(NAMESPACE_BLOBSTORE, key, value, 1744 false /* makeDefault */)).isTrue(); 1745 } 1746 Log.i(TAG, key + " value set: " + value); 1747 } 1748 }); 1749 try { 1750 runnable.run(); 1751 } finally { 1752 SystemUtil.runWithShellPermissionIdentity(() -> { 1753 previousValues.forEach((key, previousValue) -> { 1754 final String currentValue = DeviceConfig.getProperty( 1755 NAMESPACE_BLOBSTORE, key); 1756 if (!Objects.equals(previousValue, currentValue)) { 1757 assertThat(DeviceConfig.setProperty(NAMESPACE_BLOBSTORE, 1758 key, previousValue, false /* makeDefault */)).isTrue(); 1759 Log.i(TAG, key + " value restored: " + previousValue); 1760 } 1761 }); 1762 }); 1763 } 1764 } 1765 blobExists(long blobId, int userId)1766 private static boolean blobExists(long blobId, int userId) throws Exception { 1767 final String cmd = String.format( 1768 "cmd blob_store query-blob-existence -u %d -b %d", userId, blobId); 1769 return "1".equals(runShellCmd(cmd)); 1770 } 1771 commitAndVerifyBlob(FakeBlobData blobData)1772 private void commitAndVerifyBlob(FakeBlobData blobData) throws Exception { 1773 commitBlob(blobData); 1774 1775 // Verify that blob can be accessed after committing. 1776 try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) { 1777 assertThat(pfd).isNotNull(); 1778 blobData.verifyBlob(pfd); 1779 } 1780 } 1781 commitBlob(FakeBlobData blobData)1782 private long commitBlob(FakeBlobData blobData) throws Exception { 1783 return commitBlob(blobData, null); 1784 } 1785 commitBlob(FakeBlobData blobData, AccessModifier accessModifier)1786 private long commitBlob(FakeBlobData blobData, 1787 AccessModifier accessModifier) throws Exception { 1788 return commitBlob(blobData, accessModifier, true /* expectSuccess */); 1789 } 1790 commitBlob(FakeBlobData blobData, AccessModifier accessModifier, boolean expectSuccess)1791 private long commitBlob(FakeBlobData blobData, 1792 AccessModifier accessModifier, boolean expectSuccess) throws Exception { 1793 final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); 1794 assertThat(sessionId).isGreaterThan(0L); 1795 try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { 1796 blobData.writeToSession(session); 1797 1798 if (accessModifier != null) { 1799 accessModifier.modify(session); 1800 } 1801 commitSession(sessionId, session, blobData.getBlobHandle(), expectSuccess); 1802 } 1803 return sessionId; 1804 } 1805 commitSession(long sessionId, BlobStoreManager.Session session, BlobHandle blobHandle)1806 private void commitSession(long sessionId, BlobStoreManager.Session session, 1807 BlobHandle blobHandle) throws Exception { 1808 commitSession(sessionId, session, blobHandle, true /* expectSuccess */); 1809 } 1810 commitSession(long sessionId, BlobStoreManager.Session session, BlobHandle blobHandle, boolean expectSuccess)1811 private void commitSession(long sessionId, BlobStoreManager.Session session, 1812 BlobHandle blobHandle, boolean expectSuccess) throws Exception { 1813 Log.d(TAG, "Committing session: " + sessionId + "; blob: " + blobHandle); 1814 final CompletableFuture<Integer> callback = new CompletableFuture<>(); 1815 session.commit(mContext.getMainExecutor(), callback::complete); 1816 final int result = callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS); 1817 if (expectSuccess) { 1818 assertThat(result).isEqualTo(0); 1819 } else { 1820 assertThat(result).isNotEqualTo(0); 1821 } 1822 } 1823 1824 private interface AccessModifier { modify(BlobStoreManager.Session session)1825 void modify(BlobStoreManager.Session session) throws Exception; 1826 } 1827 commitBlobFromPkg(FakeBlobData blobData, TestServiceConnection serviceConnection)1828 private void commitBlobFromPkg(FakeBlobData blobData, TestServiceConnection serviceConnection) 1829 throws Exception { 1830 commitBlobFromPkg(blobData, ICommandReceiver.FLAG_ACCESS_TYPE_PRIVATE, serviceConnection); 1831 } 1832 commitBlobFromPkg(FakeBlobData blobData, int accessTypeFlags, TestServiceConnection serviceConnection)1833 private void commitBlobFromPkg(FakeBlobData blobData, int accessTypeFlags, 1834 TestServiceConnection serviceConnection) throws Exception { 1835 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1836 try (ParcelFileDescriptor pfd = blobData.openForRead()) { 1837 assertThat(commandReceiver.commit(blobData.getBlobHandle(), 1838 pfd, accessTypeFlags, TIMEOUT_COMMIT_CALLBACK_SEC, blobData.getFileSize())) 1839 .isEqualTo(0); 1840 } 1841 } 1842 acquireLeaseFrmPkg(FakeBlobData blobData, TestServiceConnection serviceConnection)1843 private void acquireLeaseFrmPkg(FakeBlobData blobData, TestServiceConnection serviceConnection) 1844 throws Exception { 1845 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1846 commandReceiver.acquireLease(blobData.getBlobHandle()); 1847 } 1848 openSessionFromPkg(long sessionId, String pkg)1849 private void openSessionFromPkg(long sessionId, String pkg) throws Exception { 1850 final TestServiceConnection serviceConnection = bindToHelperService(pkg); 1851 try { 1852 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1853 commandReceiver.openSession(sessionId); 1854 } finally { 1855 serviceConnection.unbind(); 1856 } 1857 } 1858 acquireLeaseAndAssertPkgCanAccess(FakeBlobData blobData, String pkg)1859 private void acquireLeaseAndAssertPkgCanAccess(FakeBlobData blobData, String pkg) 1860 throws Exception { 1861 final TestServiceConnection serviceConnection = bindToHelperService(pkg); 1862 try { 1863 acquireLeaseAndAssertPkgCanAccess(blobData, serviceConnection); 1864 } finally { 1865 serviceConnection.unbind(); 1866 } 1867 } 1868 acquireLeaseAndAssertPkgCanAccess(FakeBlobData blobData, TestServiceConnection serviceConnection)1869 private void acquireLeaseAndAssertPkgCanAccess(FakeBlobData blobData, 1870 TestServiceConnection serviceConnection) throws Exception { 1871 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1872 commandReceiver.acquireLease(blobData.getBlobHandle()); 1873 try (ParcelFileDescriptor pfd = commandReceiver.openBlob(blobData.getBlobHandle())) { 1874 assertThat(pfd).isNotNull(); 1875 blobData.verifyBlob(pfd); 1876 } 1877 } 1878 assertPkgCanAccess(FakeBlobData blobData, TestServiceConnection serviceConnection)1879 private void assertPkgCanAccess(FakeBlobData blobData, TestServiceConnection serviceConnection) 1880 throws Exception { 1881 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1882 try (ParcelFileDescriptor pfd = commandReceiver.openBlob(blobData.getBlobHandle())) { 1883 assertThat(pfd).isNotNull(); 1884 blobData.verifyBlob(pfd); 1885 } 1886 } 1887 assertPkgCannotAccess(FakeBlobData blobData, String pkg)1888 private void assertPkgCannotAccess(FakeBlobData blobData, String pkg) throws Exception { 1889 final TestServiceConnection serviceConnection = bindToHelperService(pkg); 1890 try { 1891 assertPkgCannotAccess(blobData, serviceConnection); 1892 } finally { 1893 serviceConnection.unbind(); 1894 } 1895 } 1896 assertPkgCannotAccess(FakeBlobData blobData, TestServiceConnection serviceConnection)1897 private void assertPkgCannotAccess(FakeBlobData blobData, 1898 TestServiceConnection serviceConnection) throws Exception { 1899 final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 1900 assertThrows(SecurityException.class, 1901 () -> commandReceiver.acquireLease(blobData.getBlobHandle())); 1902 assertThrows(SecurityException.class, 1903 () -> commandReceiver.openBlob(blobData.getBlobHandle())); 1904 } 1905 assertSizeBytesMostlyEquals(long expected, long actual)1906 private void assertSizeBytesMostlyEquals(long expected, long actual) { 1907 assertWithMessage("expected:" + expected + "; actual:" + actual) 1908 .that(Math.abs(expected - actual)) 1909 .isLessThan(DELTA_BYTES); 1910 } 1911 waitForLeaseExpiration(long waitDurationMs, BlobHandle leasedBlob)1912 private void waitForLeaseExpiration(long waitDurationMs, BlobHandle leasedBlob) 1913 throws Exception { 1914 SystemClock.sleep(waitDurationMs); 1915 assertThat(mBlobStoreManager.getLeaseInfo(leasedBlob)).isNull(); 1916 } 1917 bindToHelperService(String pkg)1918 private TestServiceConnection bindToHelperService(String pkg) throws Exception { 1919 final TestServiceConnection serviceConnection = new TestServiceConnection(mContext); 1920 final Intent intent = new Intent() 1921 .setComponent(new ComponentName(pkg, HELPER_SERVICE)); 1922 mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); 1923 return serviceConnection; 1924 } 1925 1926 private class TestServiceConnection implements ServiceConnection { 1927 private final Context mContext; 1928 private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>(); 1929 private ICommandReceiver mCommandReceiver; 1930 TestServiceConnection(Context context)1931 TestServiceConnection(Context context) { 1932 mContext = context; 1933 } 1934 onServiceConnected(ComponentName componentName, IBinder service)1935 public void onServiceConnected(ComponentName componentName, IBinder service) { 1936 Log.i(TAG, "Service got connected: " + componentName); 1937 mBlockingQueue.offer(service); 1938 } 1939 onServiceDisconnected(ComponentName componentName)1940 public void onServiceDisconnected(ComponentName componentName) { 1941 Log.e(TAG, "Service got disconnected: " + componentName); 1942 } 1943 getService()1944 private IBinder getService() throws Exception { 1945 final IBinder service = mBlockingQueue.poll(TIMEOUT_BIND_SERVICE_SEC, 1946 TimeUnit.SECONDS); 1947 return service; 1948 } 1949 getCommandReceiver()1950 public ICommandReceiver getCommandReceiver() throws Exception { 1951 if (mCommandReceiver == null) { 1952 mCommandReceiver = ICommandReceiver.Stub.asInterface(getService()); 1953 } 1954 return mCommandReceiver; 1955 } 1956 unbind()1957 public void unbind() { 1958 mCommandReceiver = null; 1959 mContext.unbindService(this); 1960 } 1961 } 1962 } 1963