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