1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.rkpdapp.stress; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 import static com.google.common.truth.TruthJUnit.assume; 22 23 import android.content.Context; 24 import android.hardware.security.keymint.IRemotelyProvisionedComponent; 25 import android.os.IBinder; 26 import android.os.Process; 27 import android.os.ServiceManager; 28 import android.os.SystemProperties; 29 30 import androidx.test.core.app.ApplicationProvider; 31 32 import com.android.rkpdapp.IGetKeyCallback; 33 import com.android.rkpdapp.RemotelyProvisionedKey; 34 import com.android.rkpdapp.database.ProvisionedKey; 35 import com.android.rkpdapp.database.ProvisionedKeyDao; 36 import com.android.rkpdapp.database.RkpdDatabase; 37 import com.android.rkpdapp.interfaces.ServerInterface; 38 import com.android.rkpdapp.interfaces.ServiceManagerInterface; 39 import com.android.rkpdapp.interfaces.SystemInterface; 40 import com.android.rkpdapp.provisioner.Provisioner; 41 import com.android.rkpdapp.service.RegistrationBinder; 42 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.rules.TestName; 47 48 import java.time.Duration; 49 import java.time.Instant; 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.concurrent.CompletableFuture; 53 import java.util.concurrent.ExecutorService; 54 import java.util.concurrent.Executors; 55 56 public class RegistrationBinderStressTest { 57 private static final int NUM_THREADS = Math.min(16, Runtime.getRuntime().availableProcessors()); 58 private static final Duration STRESS_THREAD_TIME_LIMIT = Duration.ofSeconds(60); 59 private static final String SERVICE = IRemotelyProvisionedComponent.DESCRIPTOR + "/default"; 60 private final ExecutorService mExecutor = Executors.newCachedThreadPool(); 61 private Context mContext; 62 private SystemInterface mIrpcHal; 63 private ProvisionedKeyDao mKeyDao; 64 65 @Rule 66 public final TestName mName = new TestName(); 67 68 @Before setUp()69 public void setUp() throws Exception { 70 assume() 71 .withMessage("The RKP server hostname is not configured -- assume RKP disabled.") 72 .that(SystemProperties.get("remote_provisioning.hostname")) 73 .isNotEmpty(); 74 assume() 75 .withMessage("Remotely Provisioned Component is not found -- RKP disabled.") 76 .that(ServiceManager.isDeclared(SERVICE)) 77 .isTrue(); 78 mContext = ApplicationProvider.getApplicationContext(); 79 mIrpcHal = ServiceManagerInterface.getInstance(SERVICE); 80 mKeyDao = RkpdDatabase.getDatabase(mContext).provisionedKeyDao(); 81 mKeyDao.deleteAllKeys(); 82 } 83 createRegistrationBinder()84 private RegistrationBinder createRegistrationBinder() { 85 boolean isAsync = false; 86 return new RegistrationBinder(mContext, Process.myUid(), mIrpcHal, mKeyDao, 87 new ServerInterface(mContext, isAsync), new Provisioner(mContext, mKeyDao, isAsync), 88 mExecutor); 89 } 90 getKeyHelper(int keyId)91 private void getKeyHelper(int keyId) { 92 RegistrationBinder binder = createRegistrationBinder(); 93 CompletableFuture<String> result = new CompletableFuture<>(); 94 binder.getKey(keyId, new IGetKeyCallback.Stub() { 95 @Override 96 public void onSuccess(RemotelyProvisionedKey key) { 97 result.complete(""); 98 } 99 100 @Override 101 public void onProvisioningNeeded() { /* noop */ } 102 103 @Override 104 public void onCancel() { 105 result.complete("Received unexpected cancel"); 106 } 107 108 @Override 109 public void onError(byte error, String description) { 110 result.complete(description); 111 } 112 113 @Override 114 public IBinder asBinder() { 115 return this; 116 } 117 }); 118 try { 119 assertThat(result.get()).isEmpty(); 120 } catch (Exception e) { 121 assertWithMessage("Unexpected exception: " + e).fail(); 122 } 123 } 124 125 @Test testGetSameKeyInParallel()126 public void testGetSameKeyInParallel() throws Exception { 127 // Run through various operations on binder objects in parallel, ensuring operations 128 // work concurrently. 129 int keyId = 0; 130 List<Thread> stressThreads = new ArrayList<>(); 131 Instant endOfTest = Instant.now().plus(STRESS_THREAD_TIME_LIMIT); 132 for (int i = 0; i < NUM_THREADS; ++i) { 133 System.out.println("Spinning up test thread " + i); 134 Thread t = new Thread(() -> { 135 int counter = 0; 136 while (Instant.now().isBefore(endOfTest)) { 137 ++counter; 138 if (counter % 1000 == 0) { 139 System.out.println("Thread " + Thread.currentThread().getId() 140 + " on iteration " + counter); 141 } 142 getKeyHelper(keyId); 143 144 // Clear the key assignment so that it can be re-used without hitting the RKP 145 // server again. It's possible another thread already unassigned the key, so 146 // we cannot assume we get a key here. 147 ProvisionedKey key = mKeyDao.getKeyForClientAndIrpc(mIrpcHal.getServiceName(), 148 Process.myUid(), keyId); 149 if (key != null) { 150 key.keyId = null; 151 key.clientUid = null; 152 mKeyDao.updateKey(key); 153 } 154 } 155 }); 156 t.start(); 157 stressThreads.add(t); 158 } 159 160 for (Thread t : stressThreads) { 161 t.join(); 162 } 163 } 164 } 165