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