1 /* 2 * Copyright (c) 2024-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.ohos.hapsigntoolcmd; 17 18 import com.ohos.entity.InForm; 19 import com.ohos.entity.Mode; 20 import com.ohos.entity.ProFileSigned; 21 import com.ohos.entity.SignAppParameters; 22 import com.ohos.entity.SignCode; 23 import com.ohos.entity.SignProfileParameters; 24 import com.ohos.entity.VerifyAppParameters; 25 import com.ohos.entity.VerifyProfileParameters; 26 import com.ohos.hapsigntool.HapSignTool; 27 import com.ohos.hapsigntool.error.ERROR; 28 import com.ohos.hapsigntool.utils.FileUtils; 29 30 import org.junit.jupiter.api.AfterAll; 31 import org.junit.jupiter.api.Assertions; 32 import org.junit.jupiter.api.BeforeAll; 33 import org.junit.jupiter.api.RepeatedTest; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Random; 41 import java.util.concurrent.ArrayBlockingQueue; 42 import java.util.concurrent.Callable; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.concurrent.ExecutionException; 45 import java.util.concurrent.Future; 46 import java.util.concurrent.ThreadPoolExecutor; 47 import java.util.concurrent.TimeUnit; 48 import java.util.zip.ZipEntry; 49 import java.util.zip.ZipOutputStream; 50 51 /** 52 * Concurrency Test 53 * 54 * @since 2024/03/18 55 */ 56 public class ConcurrencyTest { 57 private static final String TMP_UNSIGNED_FILE = "unsigned-"; 58 59 private static final String TMP_SIGNED_FILE = "signed-"; 60 61 private static final String TMP_HAP_SUFFIX = ".hap"; 62 63 private static final String TMP_PROFILE_SUFFIX = ".p7b"; 64 65 private static final int CONCURRENT_TASK_COUNT = 100; 66 67 private static final int LOOP_COUNT = 5; 68 69 private static final int KEEP_ALIVE_TIMES = 30; 70 71 private static final int MAX_ENTRY_SIZE = 10 * 1024 * 1024; 72 73 private static final int MIN_ENTRY_SIZE = 1024 * 1024; 74 75 private static final File TMP_DIR = new File("concurrentTest"); 76 77 private static final List<Cleanable> tmpSource = new ArrayList<>(); 78 79 /** 80 * before test 81 */ 82 @BeforeAll before()83 public static void before() { 84 TMP_DIR.mkdirs(); 85 } 86 87 /** 88 * after test 89 */ 90 @AfterAll after()91 public static void after() { 92 for (Cleanable c : tmpSource) { 93 c.clean(); 94 } 95 } 96 97 /** 98 * test Sign Hap 99 * 100 * @throws IOException IOException 101 * @throws ExecutionException ExecutionException 102 * @throws InterruptedException InterruptedException 103 */ 104 @RepeatedTest(2) testSignHapConcurrent()105 public void testSignHapConcurrent() throws IOException, ExecutionException, InterruptedException { 106 executeConcurrentTask(); 107 } 108 generateSignHapTask(CountDownLatch countDownLatch)109 private Callable<Boolean> generateSignHapTask(CountDownLatch countDownLatch) throws IOException { 110 File inputFile = File.createTempFile(TMP_UNSIGNED_FILE, TMP_HAP_SUFFIX, TMP_DIR); 111 File outputFile = File.createTempFile(TMP_SIGNED_FILE, TMP_HAP_SUFFIX, TMP_DIR); 112 File profile = File.createTempFile(TMP_SIGNED_FILE, TMP_PROFILE_SUFFIX, TMP_DIR); 113 tmpSource.add(new Cleanable(inputFile)); 114 tmpSource.add(new Cleanable(outputFile)); 115 tmpSource.add(new Cleanable(profile)); 116 generateHap(inputFile); 117 return new MultiSignHapTask(inputFile, outputFile, profile, countDownLatch); 118 } 119 generateHap(File file)120 private void generateHap(File file) throws IOException { 121 try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) { 122 ZipEntry zipEntry = new ZipEntry("test.so"); 123 zipEntry.setMethod(ZipEntry.DEFLATED); 124 byte[] bytes = generateChunkBytes(); 125 out.putNextEntry(zipEntry); 126 out.write(bytes); 127 out.closeEntry(); 128 } 129 } 130 generateChunkBytes()131 private byte[] generateChunkBytes() { 132 Random random = new Random(); 133 int size = Math.max(MIN_ENTRY_SIZE, random.nextInt(MAX_ENTRY_SIZE)); 134 byte[] bytes = new byte[size]; 135 random.nextBytes(bytes); 136 return bytes; 137 } 138 executeConcurrentTask()139 private void executeConcurrentTask() throws IOException, ExecutionException, InterruptedException { 140 CountDownLatch countDownLatch = new CountDownLatch(CONCURRENT_TASK_COUNT); 141 ThreadPoolExecutor executor = new ThreadPoolExecutor(CONCURRENT_TASK_COUNT, CONCURRENT_TASK_COUNT, 142 KEEP_ALIVE_TIMES, TimeUnit.SECONDS, new ArrayBlockingQueue<>(CONCURRENT_TASK_COUNT), 143 new ThreadPoolExecutor.AbortPolicy()); 144 List<Future<Boolean>> futures = new ArrayList<>(CONCURRENT_TASK_COUNT); 145 for (int i = 0; i < CONCURRENT_TASK_COUNT; i++) { 146 futures.add(executor.submit(generateSignHapTask(countDownLatch))); 147 } 148 executor.shutdown(); 149 boolean isFinished = countDownLatch.await(KEEP_ALIVE_TIMES, TimeUnit.SECONDS); 150 if (!isFinished) { 151 executor.shutdownNow(); 152 } 153 Assertions.assertTrue(isFinished, "task time out : " + KEEP_ALIVE_TIMES + " seconds"); 154 for (Future<Boolean> f : futures) { 155 Boolean isSuccess = f.get(); 156 Assertions.assertNotNull(isSuccess, "task not finished"); 157 Assertions.assertTrue(isSuccess, "task failed"); 158 } 159 } 160 161 private static class Cleanable { 162 private final File file; 163 Cleanable(File file)164 public Cleanable(File file) { 165 this.file = file; 166 } 167 clean()168 private void clean() { 169 FileUtils.deleteFile(file); 170 } 171 } 172 173 private static class MultiSignHapTask implements Callable<Boolean> { 174 private final File inputFile; 175 176 private final File outputFile; 177 178 private final File profile; 179 180 private final CountDownLatch countDownLatch; 181 MultiSignHapTask(File inputFile, File outputFile, File profile, CountDownLatch countDownLatch)182 private MultiSignHapTask(File inputFile, File outputFile, File profile, CountDownLatch countDownLatch) { 183 this.inputFile = inputFile; 184 this.outputFile = outputFile; 185 this.profile = profile; 186 this.countDownLatch = countDownLatch; 187 } 188 189 @Override call()190 public Boolean call() throws IOException { 191 try { 192 for (int i = 0; i < LOOP_COUNT; i++) { 193 if (!signProfile()) { 194 return false; 195 } 196 197 if (!verifyProfile()) { 198 return false; 199 } 200 201 if (!signApp()) { 202 return false; 203 } 204 if (!verifyApp()) { 205 return false; 206 } 207 } 208 return true; 209 } finally { 210 countDownLatch.countDown(); 211 } 212 } 213 signProfile()214 private boolean signProfile() throws IOException { 215 SignProfileParameters signProfileParameters = new SignProfileParameters(); 216 signProfileParameters.setMode(Mode.LOCAL_SIGN); 217 signProfileParameters.setKeyAlias("oh-app1-key-v1"); 218 signProfileParameters.setKeyPwd("123456".toCharArray()); 219 signProfileParameters.setProfileCertFile("../../tools/app1.pem"); 220 signProfileParameters.setInFile("../../tools/profile.json"); 221 signProfileParameters.setSignAlg("SHA256withECDSA"); 222 signProfileParameters.setKeyStoreFile("../../tools/ohtest_pass.jks"); 223 signProfileParameters.setKeystorePwd("123456".toCharArray()); 224 signProfileParameters.setOutFile(profile.getCanonicalPath()); 225 return HapSignTool.signProfile(signProfileParameters).getErrCode() == ERROR.SUCCESS_CODE; 226 } 227 verifyProfile()228 private boolean verifyProfile() throws IOException { 229 VerifyProfileParameters verifyProfileParameters = new VerifyProfileParameters(); 230 verifyProfileParameters.setInFile(profile.getCanonicalPath()); 231 verifyProfileParameters.setOutFile("out.json"); 232 return HapSignTool.verifyProfile(verifyProfileParameters).getErrCode() == ERROR.SUCCESS_CODE; 233 } 234 signApp()235 private boolean signApp() throws IOException { 236 SignAppParameters signAppParameters = new SignAppParameters(); 237 signAppParameters.setMode(Mode.LOCAL_SIGN); 238 signAppParameters.setKeyAlias("oh-app1-key-v1"); 239 signAppParameters.setKeyPwd("123456".toCharArray()); 240 signAppParameters.setAppCertFile("../../tools/app1.pem"); 241 signAppParameters.setProfileFile(profile.getCanonicalPath()); 242 signAppParameters.setInFile(inputFile.getCanonicalPath()); 243 signAppParameters.setSignAlg("SHA256withECDSA"); 244 signAppParameters.setKeyStoreFile("../../tools/ohtest_pass.jks"); 245 signAppParameters.setKeystorePwd("123456".toCharArray()); 246 signAppParameters.setOutFile(outputFile.getCanonicalPath()); 247 signAppParameters.setProfileSigned(ProFileSigned.SIGNED); 248 signAppParameters.setInForm(InForm.ZIP); 249 signAppParameters.setSignCode(SignCode.OPEN); 250 return HapSignTool.signApp(signAppParameters).getErrCode() == ERROR.SUCCESS_CODE; 251 } 252 verifyApp()253 private boolean verifyApp() throws IOException { 254 VerifyAppParameters verifyAppParameters = new VerifyAppParameters(); 255 verifyAppParameters.setInFile(outputFile.getCanonicalPath()); 256 verifyAppParameters.setOutCertChain("out.cer"); 257 verifyAppParameters.setOutProfile("out.p7b"); 258 return HapSignTool.verifyApp(verifyAppParameters).getErrCode() == ERROR.SUCCESS_CODE; 259 } 260 } 261 } 262