From 654e7617cba31cf4ced560ab8ac10b2721cbfebb Mon Sep 17 00:00:00 2001 From: "jif2.zhang" Date: Mon, 21 Jul 2025 17:41:59 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9F=AD=E6=95=B0=E6=8D=AE=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E7=94=9F=E6=88=90mac=EF=BC=8C=E7=9F=AD=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A0=A1=E9=AA=8Cmac=E5=B9=B6=E8=A7=A3=E5=AF=86=EF=BC=9A?= =?UTF-8?q?=E7=9F=AD=E9=93=BE=E6=8E=A5=E7=9A=84=E6=80=A7=E8=83=BD=E6=8F=90?= =?UTF-8?q?=E5=8D=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.properties | 10 +- .../sge/database/SydApiBaseFunction.java | 209 ++++- src/main/resources/linux-x86-64/libsunyard.so | Bin 155448 -> 161656 bytes src/test/java/test/FuncTest.java | 16 + src/test/java/test/TestAll.java | 23 +- src/test/java/test/TestMAC.java | 829 ++++++++++++++++++ 6 files changed, 1056 insertions(+), 31 deletions(-) create mode 100644 src/test/java/test/TestMAC.java diff --git a/config.properties b/config.properties index 2b2c397..d699413 100644 --- a/config.properties +++ b/config.properties @@ -1,6 +1,12 @@ # 应用配置 # 调试模式 (true/false) -debug=true +debug=false + +# 最大线程数 +maxThread=20 + +# 总数 +total=100000 # 地址列表 (格式: addX=ip:port) -addr1=192.168.110.240:8889 +addr1=172.1.41.129:8889 \ No newline at end of file diff --git a/src/main/java/com/sunyard/sge/database/SydApiBaseFunction.java b/src/main/java/com/sunyard/sge/database/SydApiBaseFunction.java index f273e63..7937edb 100644 --- a/src/main/java/com/sunyard/sge/database/SydApiBaseFunction.java +++ b/src/main/java/com/sunyard/sge/database/SydApiBaseFunction.java @@ -1,17 +1,22 @@ package com.sunyard.sge.database; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.PointerByReference; import com.sunyard.SydApiException; import com.sunyard.log.ILogFactory; import com.sunyard.log.ILogger; import com.sunyard.proto.Util; +import com.sunyard.sge.bytes.BytesUtil; import com.sunyard.sge.database.pool.HsmLinkInfo; import com.sunyard.sge.log.LogbackFactory; +import com.sunyard.sge.plus.SydLibrary; import com.sunyard.sge.pool.ObjectInPool; import com.sunyard.util.SYMUtil; import racal.sunyard.main.SydApi4j; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -22,6 +27,10 @@ public class SydApiBaseFunction implements SydApi { private HsmLinkInfo[] linkInfos = null; private ObjectInPool oip = null; private boolean isShortLinkMode = false; + + // c 加速句柄,短链接。 + private PointerByReference phHandle = null; + private ILogFactory iLogFactory = new LogbackFactory(); private ILogger log = iLogFactory.getLogger(SydApiBaseFunction.class); @@ -85,10 +94,33 @@ public class SydApiBaseFunction implements SydApi { hsms = apis; isShortLinkMode = true; + + // c 加速链接 + if ( Env.isLinux() ) { + PointerByReference phHandle = new PointerByReference(); + int connectResult = SydLibrary.INSTANCE.SYD_Connect_Ex( + pcIpList, + iPortList, + pcIpList.length, + iConnectTimeOut, + iDealTimeOut, + 0, + phHandle); + + if ( 0 != connectResult ){ + throw new SydApiException("连接错误", -1); + } + this.phHandle = phHandle; + } return this; } public void SYD_Disconnect() { + if ( null != phHandle && Env.isLinux()) { + SydLibrary.INSTANCE.SYD_DisConnect_Ex(phHandle.getValue()); + phHandle = null; + } + if (null == hsms) { return; } @@ -494,9 +526,6 @@ public class SydApiBaseFunction implements SydApi { } } catch (SydApiException e) { - if (null == e) { - - } le = e; if (e.getRetCode() < 0) { log.error("HA 重试"); @@ -869,9 +898,48 @@ public class SydApiBaseFunction implements SydApi { return Arrays.equals(pcMac, mac); } + DataAndMac SYD_SM4_EncryptAndMac_ShortData_c(int iEncKeyIndex, int iMacKeyIndex, byte[] pcData) { + log.info("SYD_SM4_EncryptAndMac_ShortData_c {} {}", iEncKeyIndex, iMacKeyIndex); + try { + int enLen = BytesUtil.calculatePaddedLength(pcData.length); + byte[] encData = new byte[enLen]; + byte[] mac = new byte[16]; + IntByReference encDataLen = new IntByReference(encData.length); + + int i = SydLibrary.INSTANCE.SYD_SM4_Encrypt_Mac_ShortData( + phHandle.getValue(), + iEncKeyIndex, + iMacKeyIndex, + pcData, + pcData.length, + encData, + encDataLen, + mac + ); + + if (0 == i) { + byte[] data = new byte[encDataLen.getValue()]; + if (data.length == encData.length) { + data = encData; + } else { + System.arraycopy(encData, 0, data, 0, data.length); + } + + return new DataAndMac(data, mac); + } else { + throw new SydApiException(i); + } + } catch (SydApiException e) { + throw e; + } catch (Exception e) { + if (e.getCause() instanceof SydApiException) { + throw (SydApiException) e.getCause(); + } + throw new SydApiException("并行任务执行失败", 0xC101, e); + } + } /** - * 非依赖 C 实现,仅长连接依赖 C * * @param iEncKeyIndex:输入。加密所需要的密钥索引值 * @param iMacKeyIndex:输入。计算mac所需要的密钥索引值 @@ -884,11 +952,58 @@ public class SydApiBaseFunction implements SydApi { if (log.isDebugEnabled()) { log.debug("SYD_SM4_EncryptAndMac_ShortData:k1={} k2={}", iEncKeyIndex, iMacKeyIndex); } - // 参数检查在函数内部 - byte[] enData = SYD_SM4_ShortData(iEncKeyIndex, pcData, Consts.ECB_ENC); - // mac 长度放宽到 1040,因为密文填充 - byte[] mac = SYD_SM4Mac_ShortData(iMacKeyIndex, enData, 1040); - return new DataAndMac(enData, mac); + + if ( null != phHandle ) { + + return SYD_SM4_EncryptAndMac_ShortData_c(iEncKeyIndex, iMacKeyIndex, pcData); + + } else { + // 参数检查在函数内部 + byte[] enData = SYD_SM4_ShortData(iEncKeyIndex, pcData, Consts.ECB_ENC); + // mac 长度放宽到 1040,因为密文填充 + byte[] mac = SYD_SM4Mac_ShortData(iMacKeyIndex, enData, 1040); + return new DataAndMac(enData, mac); + } + } + + public DataAndMacCheck SYD_SM4_CheckMacAndDecrypt_ShortData_c(int iEncKeyIndex, int iMacKeyIndex, byte[] pcData, byte[] macs) { + log.info("SYD_SM4_CheckMacAndDecrypt_ShortData_c {} {}", iEncKeyIndex, iMacKeyIndex); + try { + + byte[] decryptedData = new byte[pcData.length]; + IntByReference decryptedDataLen = new IntByReference(decryptedData.length); + + + int i = SydLibrary.INSTANCE.SYD_SM4_Decrypt_Mac_ShortData( + phHandle.getValue(), + iEncKeyIndex, + iMacKeyIndex, + pcData, + pcData.length, + macs, + decryptedData, + decryptedDataLen + ); + + byte[] data = new byte[decryptedDataLen.getValue()]; + System.arraycopy(decryptedData, 0, data, 0, data.length); + + if (0 == i) { + return new DataAndMacCheck(data, true); + } + if (0x01000010 == i) { + return new DataAndMacCheck(data, false); + } else { + throw new SydApiException(i); + } + } catch (SydApiException e) { + throw e; + } catch (Exception e) { + if (e.getCause() instanceof SydApiException) { + throw (SydApiException) e.getCause(); + } + throw new SydApiException("并行任务执行失败", 0xC101, e); + } } @@ -899,14 +1014,21 @@ public class SydApiBaseFunction implements SydApi { log.debug("SYD_SM4_CheckMacAndDecrypt_ShortData:k1={} k2={}", iEncKeyIndex, iMacKeyIndex); } - boolean ret = SYD_SM4Mac_ShortData(iMacKeyIndex, pcData, 1040, mac); - byte[] data = null; - try { - data = SYD_SM4_ShortData(iEncKeyIndex, pcData, Consts.ECB_DEC); - } catch (Exception e) { - return new DataAndMacCheck(null, ret); + if( null != phHandle ) { + return SYD_SM4_CheckMacAndDecrypt_ShortData_c(iEncKeyIndex, iMacKeyIndex, pcData, mac); + } else { + boolean ret = SYD_SM4Mac_ShortData(iMacKeyIndex, pcData, 1040, mac); + byte[] data = null; + if (!ret){ + return new DataAndMacCheck(data, ret); + } + try { + data = SYD_SM4_ShortData(iEncKeyIndex, pcData, Consts.ECB_DEC); + } catch (Exception e) { + return new DataAndMacCheck(null, ret); + } + return new DataAndMacCheck(data, ret); } - return new DataAndMacCheck(data, ret); } @@ -968,13 +1090,56 @@ public class SydApiBaseFunction implements SydApi { datas.add(pcDatum.getData()); } - boolean[] macCheck = SYD_SM4Mac_BatchData(iMacKeyIndex, datas, macs); - List enData = SYD_SM4_BatchData(iEncKeyIndex, datas, Consts.ECB_DEC); +// boolean[] macCheck = SYD_SM4Mac_BatchData(iMacKeyIndex, datas, macs); +// List enData = SYD_SM4_BatchData(iEncKeyIndex, datas, Consts.ECB_DEC); +// +// List ret = new ArrayList<>(enData.size()); +// for (int i = 0; i < enData.size(); i++) { +// DataAndMacCheck damc = new DataAndMacCheck(enData.get(i), macCheck[i]); +// ret.add(damc); +// } - List ret = new ArrayList<>(enData.size()); - for (int i = 0; i < enData.size(); i++) { - DataAndMacCheck damc = new DataAndMacCheck(enData.get(i), macCheck[i]); - ret.add(damc); + // 批量执行MAC校验 + boolean[] macCheck = SYD_SM4Mac_BatchData(iMacKeyIndex, datas, macs); + + // 分离通过和未通过MAC校验的数据 + List validIndices = new ArrayList<>(); + List validData = new ArrayList<>(); + List validKeyIndices = new ArrayList<>(); + + for (int i = 0; i < macCheck.length; i++) { + if (macCheck[i]) { + validIndices.add(i); + validData.add(datas.get(i)); + validKeyIndices.add(iEncKeyIndex[i]); + } + } + + // 仅对通过MAC校验的数据进行批量解密 + List decryptedData; + if (validData.isEmpty()) { + decryptedData = Collections.emptyList(); + } else { + // 转换为数组(根据SYD_SM4_BatchData的接口要求) + int[] validKeysArray = validKeyIndices.stream() + .mapToInt(Integer::intValue) + .toArray(); + // 使用过滤后的密钥索引和数据 + decryptedData = SYD_SM4_BatchData(validKeysArray, validData, Consts.ECB_DEC); + } + + // 构建结果集 + List ret = new ArrayList<>(pcData.size()); + int decryptedIndex = 0; + + for (int i = 0; i < pcData.size(); i++) { + if (macCheck[i]) { + // 对于通过校验的条目,使用解密数据 + ret.add(new DataAndMacCheck(decryptedData.get(decryptedIndex++), true)); + } else { + // 对于未通过校验的条目,返回null + ret.add(new DataAndMacCheck(null, false)); + } } return ret; diff --git a/src/main/resources/linux-x86-64/libsunyard.so b/src/main/resources/linux-x86-64/libsunyard.so index 86ae16829ba936dcd770d3bd298cd68a56e24a13..b85cbf7613eac76ab9f67729bc1c6a1f3687472f 100644 GIT binary patch delta 39580 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 delta 35474 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 de_data1 = api.SYD_SM4_CheckMacAndDecrypt_BatchData(keyEnc, keyMac, en_data); + int index = 0; + for (DataAndMacCheck item : de_data1) { + boolean macCheck = item.getMacCheck(); + + System.out.println("索引 " + (index++) + ":"+"MAC校验结果为:"+macCheck); + } + System.out.println("-------- 批量数据加密计算MAC,校验MAC解密功能测试结束 --------\n"); } diff --git a/src/test/java/test/TestAll.java b/src/test/java/test/TestAll.java index 0af5a21..f6d41f6 100644 --- a/src/test/java/test/TestAll.java +++ b/src/test/java/test/TestAll.java @@ -456,17 +456,26 @@ public class TestAll { int keyEnc = 0; int keyMac = 0; - SydApiBuilder builder = new SydApiBuilder( - config.getIps(), - config.getPorts(), - 1000, - 2000 - ); +// SydApiBuilder builder = new SydApiBuilder( +// config.getIps(), +// config.getPorts(), +// 1000, +// 2000 +// ); +// +// ThreadLocal Api = new ThreadLocal() { +// @Override +// protected SydApi initialValue() { +// return builder.build(); +// } +// }; ThreadLocal Api = new ThreadLocal() { @Override protected SydApi initialValue() { - return builder.build(); + SydApi api = new SydApi4Database(); + api.SYD_Short_Connect_Ex(config.getIps(),config.getPorts(),1000,2000); + return api; } }; diff --git a/src/test/java/test/TestMAC.java b/src/test/java/test/TestMAC.java new file mode 100644 index 0000000..36c2ce4 --- /dev/null +++ b/src/test/java/test/TestMAC.java @@ -0,0 +1,829 @@ +package test; + +import com.sunyard.sge.database.*; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class TestMAC { + private static final String[] ipListElec = new String[]{"127.0.0.1", "172.1.41.129"}; + private static final String[] ipListLight = new String[]{"192.168.110.240", "192.168.100.45"}; + + private static final String[] ipList = ipListLight; + private static final String[] ipListErr = new String[]{"172.2.41.216", "172.2.41.129"}; + + public static final int ECB_DEC = 0; + public static final int ECB_ENC = 1; + + + public static void setLog() { + System.setProperty("com.sunyard.sydapi4j.debug", "false"); + } + + + private static void startPerformanceFile() { + try (FileWriter fw = new FileWriter(PERF_RESULT_FILE, true); + BufferedWriter bw = new BufferedWriter(fw)) { + Date now = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String timeStr = sdf.format(now); + bw.write("==================== Performance Test Start: " + timeStr + " ===================="); + bw.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void stopPerformanceFile() { + try (FileWriter fw = new FileWriter(PERF_RESULT_FILE, true); + BufferedWriter bw = new BufferedWriter(fw)) { + Date now = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String timeStr = sdf.format(now); + bw.write("==================== Performance Test End: " + timeStr + " ===================="); + bw.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeToPerformanceFile(String interfaceName, int dataLen, int machineCount, int threadNum, int totalRequests, double tps, double avgLatency) { + try (FileWriter fw = new FileWriter(PERF_RESULT_FILE, true); + BufferedWriter bw = new BufferedWriter(fw)) { + String line = String.format("%s\t%d\t%d\t%d\t%d\t%.2f\t%.2f", + interfaceName, dataLen, machineCount, threadNum, totalRequests, tps, avgLatency); + bw.write(line); + bw.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeToPerformanceFile(String interfaceName, int dataLen, int machineCount, int threadNum, int totalRequests, String tps, double avgLatency) { + try (FileWriter fw = new FileWriter(PERF_RESULT_FILE, true); + BufferedWriter bw = new BufferedWriter(fw)) { + String line = String.format("%s\t%d\t%d\t%d\t%d\t%s\t%.2f", + interfaceName, dataLen, machineCount, threadNum, totalRequests, tps, avgLatency); + bw.write(line); + bw.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeToPerformanceFile2(String interfaceName, String dataLen, int machineCount, int threadNum, double totalRequests) { + try (FileWriter fw = new FileWriter(PERF_RESULT_FILE, true); + BufferedWriter bw = new BufferedWriter(fw)) { + String line = String.format("%s\t%s\t%d\t%d\t%.2f", + interfaceName, dataLen, machineCount, threadNum, totalRequests); + bw.write(line); + bw.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public static void menu1(Config config, Choise choise, TestMAC test) throws Exception { + + FuncTest funcTest = new FuncTest(config); + + + while (true) { + System.out.println("请选择测试项:"); +// System.out.println("1. 功能单项测试"); +// System.out.println("2. 功能全量测试"); + System.out.println("1. 性能单项测试"); + System.out.println("2. 性能全量测试"); + + System.out.println("-1. 退出"); + + switch (choise.getAsInt()) { +// case 1: +// SydApi.SYD_SetLogConfig("./logs", 3); +// funcTest.funcMenu(config, choise, funcTest); +// break; +// case 2: +// funcTest.funcAll(); +// break; + case 1: + SydApi.SYD_SetLogConfig("./logs", 0); + perfMenu(config, choise, test); + break; + case 2: + SydApi.SYD_SetLogConfig("./logs", 0); + startPerformanceFile(); + test.perfAll(); + stopPerformanceFile(); + break; + default: + System.exit(0); + } + } + } + + + public void sm4ShortEndes() throws InterruptedException { + System.out.println("-------- SM4短数据加解密性能测试开始 --------"); + sm4ShortEnde(1, config.getTotal(), 1024); + sm4ShortEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM4短数据加解密性能测试结束 --------\n"); + } + + private static final String PERF_RESULT_FILE = "performance_results.txt"; + + + public void sm4ShortEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + byte[] data = new byte[plainLen]; + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4_ShortData(0, data, ECB_ENC); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("加密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatencyEnc = numberOfThreads * duration * 1.0 / totalRequest; + System.out.println("平均延时(加密): " + String.format("%.2f", avgLatencyEnc) + " ms"); + + writeToPerformanceFile("SM4短数据加密", 1024, 1, numberOfThreads, totalRequest, tps, avgLatencyEnc); + + SydApi apiTest = Api.get(); + byte[] res = apiTest.SYD_SM4_ShortData(0, data, ECB_ENC); + + ExecutorService poolDec = Executors.newFixedThreadPool(numberOfThreads); + start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolDec.submit(() -> { + SydApi api = Api.get(); + byte[] plain = api.SYD_SM4_ShortData(0, res, ECB_DEC); + }); + } + poolDec.shutdown(); + poolDec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + end = System.currentTimeMillis(); + duration = end - start; + tps = totalRequest * 1000.0 / duration; + throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("解密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatencyDec = numberOfThreads * duration * 1.0 / totalRequest; + System.out.println("平均延时(解密): " + String.format("%.2f", avgLatencyDec) + " ms"); + + writeToPerformanceFile("SM4短数据解密", 1024, 1, numberOfThreads, totalRequest, tps, avgLatencyDec); + } + + public void sm4BatchEndes() throws InterruptedException { + System.out.println("-------- SM4批量数据加解密性能测试开始 --------"); + sm4BatchEnde(1, config.getTotal(), 1024); + sm4BatchEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM4批量数据加解密性能测试结束 --------\n"); + } + + public void sm4BatchEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + int[] keyIdx = new int[]{0, 0, 0, 0}; + List data = new ArrayList<>(); + data.add(new byte[1024]); + data.add(new byte[1024]); + data.add(new byte[1024]); + data.add(new byte[1024]); + + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4_BatchData(keyIdx, data, ECB_ENC); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("加密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4批量数据加密", 1024, 1, numberOfThreads, totalRequest, String.format("%.2f", tps) + " x 4", avgLatency); + + SydApi apiTest = Api.get(); + List res = apiTest.SYD_SM4_BatchData(keyIdx, data, ECB_ENC); + + ExecutorService poolDec = Executors.newFixedThreadPool(numberOfThreads); + start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolDec.submit(() -> { + SydApi api = Api.get(); + List plain = api.SYD_SM4_BatchData(keyIdx, res, ECB_DEC); + }); + } + poolDec.shutdown(); + poolDec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + end = System.currentTimeMillis(); + duration = end - start; + tps = totalRequest * 1000.0 / duration; + throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("解密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4批量数据解密", 1024, 1, numberOfThreads, totalRequest, tps, avgLatency); + } + + + public void sm4macShortEndes() throws InterruptedException { + System.out.println("-------- SM4MAC短数据性能测试开始 --------"); + sm4macShortEnde(1, config.getTotal(), 1024); + sm4macShortEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM4MAC短数据性能测试结束 --------\n"); + } + + + public void sm4macShortEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + byte[] data = new byte[plainLen]; + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4Mac_ShortData(0, data); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4MAC 短数据", 1024, 1, numberOfThreads, totalRequest, tps, avgLatency); + } + + public void sm4macBatchEndes() throws InterruptedException { + System.out.println("-------- SM4MAC批量数据性能测试开始 --------"); + sm4macBatchEnde(1, config.getTotal(), 1024); + sm4macBatchEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM4MAC 批量数据性能测试结束 --------\n"); + } + + public void sm4macBatchEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + int[] keyIdx = new int[]{0, 0, 0, 0}; + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + List data = new ArrayList<>(); + data.add(new byte[1024]); + data.add(new byte[1024]); + data.add(new byte[1024]); + data.add(new byte[1024]); + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + List s = api.SYD_SM4Mac_BatchData(keyIdx, data); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4MAC 批量数据", 1024, 1, numberOfThreads, totalRequest, String.format("%.2f", tps) + " x 4", avgLatency); + } + + public void sm3ShortEndes() throws InterruptedException { + System.out.println("-------- SM3短数据性能测试开始 --------"); + sm3ShortEnde(1, config.getTotal(), 1024); + sm3ShortEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM3短数据性能测试结束 --------\n"); + } + + public void sm3ShortEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + byte[] data = new byte[plainLen]; + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM3_Hash_ShortData(data); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM3 短数据", 1024, 1, numberOfThreads, totalRequest, tps, avgLatency); + } + + public void sm4ShortMacEndes() throws InterruptedException { + System.out.println("-------- SM4短数据加密生成MAC,验证MAC解密性能测试开始 --------"); + System.out.println("单线程"); + sm4ShortMacEnde(1, config.getTotal(), 1024); + + System.out.println("40 线程"); + sm4ShortMacEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM4短数据加密生成MAC,验证MAC解密性能测试结束 --------\n"); + } + + public void sm4ShortMacEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + byte[] data = new byte[plainLen]; + int keyEnc = 0; + int keyMac = 0; + + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + + SydApi apiTest = Api.get(); + DataAndMac ret = apiTest.SYD_SM4_EncryptAndMac_ShortData(keyEnc, keyMac, data); + + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4_EncryptAndMac_ShortData(keyEnc, keyMac, data); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("加密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4短数据加密并生成MAC", 1024, 1, numberOfThreads, totalRequest, tps, avgLatency); + + + ExecutorService poolDec = Executors.newFixedThreadPool(numberOfThreads); + start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolDec.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4_CheckMacAndDecrypt_ShortData(keyEnc, keyMac, ret.getData(), ret.getMac()); + }); + } + poolDec.shutdown(); + poolDec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + end = System.currentTimeMillis(); + duration = end - start; + tps = totalRequest * 1000.0 / duration; + throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("解密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4短数据解密并校验MAC", 1024, 1, numberOfThreads, totalRequest, tps, avgLatency); + } + + public void sm4BatchMacEndes() throws InterruptedException { + System.out.println("-------- SM4批量数据加密生成MAC,验证MAC解密性能测试开始 --------"); + System.out.println("单线程"); + sm4BatchMacEnde(1, config.getTotal(), 1024); + System.out.println("40 线程"); + sm4BatchMacEnde(40, config.getTotal(), 1024); + System.out.println("-------- SM4批量数据加密生成MAC,验证MAC解密性能测试结束 --------\n"); + } + + public void sm4BatchMacEnde(int numberOfThreads, int totalRequest, int plainLen) throws InterruptedException { + int[] keyEnc = new int[]{0, 0, 0, 0}; + int[] keyMac = new int[]{0, 0, 0, 0}; + List data = new ArrayList<>(); + data.add(new byte[plainLen]); + data.add(new byte[plainLen]); + data.add(new byte[plainLen]); + data.add(new byte[plainLen]); + + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + + ThreadLocal Api = new ThreadLocal() { + @Override + protected SydApi initialValue() { + return builder.build(); + } + }; + SydApi apiTest = Api.get(); + List ret = apiTest.SYD_SM4_EncryptAndMac_BatchData(keyEnc, keyMac, data); + + + ExecutorService poolEnc = Executors.newFixedThreadPool(numberOfThreads); + long start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolEnc.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4_EncryptAndMac_BatchData(keyEnc, keyMac, data); + }); + } + poolEnc.shutdown(); + poolEnc.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + long end = System.currentTimeMillis(); + long duration = end - start; + double tps = totalRequest * 1000.0 / duration; + double throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("加密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + double avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4批量数据加密并生成MAC", 1024, 1, numberOfThreads, totalRequest, String.format("%.2f", tps) + " x 4", avgLatency); + + ExecutorService poolDec = Executors.newFixedThreadPool(numberOfThreads); + start = System.currentTimeMillis(); + for (int i = 0; i < totalRequest; i++) { + poolDec.submit(() -> { + SydApi api = Api.get(); + api.SYD_SM4_CheckMacAndDecrypt_BatchData(keyEnc, keyMac, ret); + }); + } + poolDec.shutdown(); + poolDec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + end = System.currentTimeMillis(); + duration = end - start; + tps = totalRequest * 1000.0 / duration; + throughput = totalRequest * 1000.0 / duration * plainLen / 1024 / 1024; + + System.out.println("并发数: " + numberOfThreads); + System.out.println("解密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("TPS: " + String.format("%.2f", tps)); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + avgLatency = numberOfThreads * duration * 1.0 / totalRequest; + writeToPerformanceFile("SM4批量数据解密并校验MAC", 1024, 1, numberOfThreads, totalRequest, String.format("%.2f", tps) + " x 4", avgLatency); + } + + + public void sm4LongEnde() { + System.out.println("-------- SM4长数据加解密性能测试开始 --------"); + + byte[] data = new byte[2048]; + int key = 0; +// ByteBuffer enDataBuff = ByteBuffer.allocate(100 * 1024 * 1024 + 16); + + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + long start = System.currentTimeMillis(); + SydApi api = builder.build(); + + ISM4 sm4 = api.initSM4(key, ECB_ENC); + for (int i = 0; i < 100 * 1024 * 1024 / data.length; i++) { + sm4.update(data); + } + byte[] res = sm4.finish(); + + long end = System.currentTimeMillis(); + long duration = end - start; + double throughput = 100.0 * 1000 / duration; + + System.out.println("加密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + writeToPerformanceFile2("SM4长数据加密", "100M", 1, 1, throughput); + + start = System.currentTimeMillis(); + + sm4 = api.initSM4(key, ECB_DEC); + for (int i = 0; i < 100 * 1024 * 1024 / data.length; i++) { + sm4.update(data); + } + // sm4.finish(); + + end = System.currentTimeMillis(); + duration = end - start; + throughput = 100.0 * 1000 / duration; + + System.out.println("解密共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + writeToPerformanceFile2("SM4长数据解密", "100M", 1, 1, throughput); + + System.out.println("-------- SM4长数据加解密性能测试结束 --------\\n"); + + } + + public void sm4MacLongEnde() { + System.out.println("-------- SM4MAC长数据性能测试开始 --------"); + + byte[] data = new byte[2048]; + + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + long start = System.currentTimeMillis(); + SydApi api = builder.build(); + + ISM4Mac sm4mac = api.initSM4Mac(0); + for (int i = 0; i < 100 * 1024 * 1024 / data.length; i++) { + sm4mac.update(data); + } + sm4mac.digest(); + + long end = System.currentTimeMillis(); + long duration = end - start; + double throughput = 100.0 * 1000 / duration; + + System.out.println("共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + writeToPerformanceFile2("SM4长数据MAC", "100M", 1, 1, throughput); + + System.out.println("-------- SM4MAC长数据性能测试结束 --------"); + } + + public void sm3LongEnde() { + System.out.println("-------- SM3长数据性能测试开始 --------"); + + byte[] data = new byte[2048]; + + SydApiBuilder builder = new SydApiBuilder( + config.getIps(), + config.getPorts(), + 1000, + 2000 + ); + + long start = System.currentTimeMillis(); + SydApi api = builder.build(); + + ISM3 sm3 = api.initSM3(); + for (int i = 0; i < 100 * 1024 * 1024 / data.length; i++) { + sm3.update(data); + } + sm3.digest(); + + long end = System.currentTimeMillis(); + long duration = end - start; + double throughput = 100.0 * 1000 / duration; + + System.out.println("共花费 " + String.format("%.2f", duration / 1000.0) + " 秒"); + System.out.println("吞吐量: " + String.format("%.2f", throughput) + " MB/S"); + writeToPerformanceFile2("SM3长数据", "100MB", 1, 1, throughput); + + System.out.println("-------- SM3长数据性能测试结束 --------"); + } + + + public void perfAll() throws InterruptedException { + this.sm4ShortEndes(); + this.sm4BatchEndes(); +// this.sm4macShortEndes(); +// this.sm4macBatchEndes(); +// this.sm3ShortEndes(); + this.sm4ShortMacEndes(); + this.sm4BatchMacEndes(); + +// this.sm4LongEnde(); +// this.sm4MacLongEnde(); +// this.sm3LongEnde(); + } + + + public static void showmenu() { + System.out.println("请选择测试项:"); + System.out.println("1.SM4 短数据加解密"); + System.out.println("2.SM4 批量数据加解密"); +// System.out.println("3.SM4MAC 短数据"); +// System.out.println("4.SM4MAC 批量数据"); +// System.out.println("5.SM3 短数据"); + System.out.println("6.SM4 短数据加密后生成MAC,解密验证MAC"); + System.out.println("7.SM4 批量数据加密后生成MAC,解密验证MAC"); +// System.out.println("8.SM4 长数据加解密"); +// System.out.println("9.SM4 长数据MAC"); +// System.out.println("10.SM3 长数据"); +// System.out.println("11. 短数据 SM4+MAC性能测试"); +// System.out.println("12. 批量数据 SM4+MAC性能测试"); + System.out.println("-1.退出"); + } + + public static void perfMenu(Config config, Choise choise, TestMAC test) throws Exception { + + boolean flag = true; + + while (flag) { + showmenu(); + switch (choise.getAsInt()) { + case 1: + test.sm4ShortEndes(); + break; + case 2: + test.sm4BatchEndes(); + break; +// case 3: +// test.sm4macShortEndes(); +// break; +// case 4: +// test.sm4macBatchEndes(); +// break; +// case 5: +// test.sm3ShortEndes(); +// break; + case 6: + test.sm4ShortMacEndes(); + break; + case 7: + test.sm4BatchMacEndes(); + break; +// case 8: +// test.sm4LongEnde(); +// break; +// case 9: +// test.sm4MacLongEnde(); +// break; +// case 10: +// test.sm3LongEnde(); +// break; +// case 11: { +// test.sm4ShortMacEndes(); +// break; +// } +// case 12: { +// test.sm4BatchMacEndes(); +// break; +// } + case -1: + flag = false; + break; + } + } + } + + + private static Config config = null; + + public static void main(String[] args) throws Exception { + + // 载入配置文件 + config = Config.loadConfig(); + if (config == null || config.isDebug()) { + System.setProperty("com.sunyard.sydapi4j.debug", "true"); + } + + System.out.println("ips=" + Arrays.toString(config.getIps())); + System.out.println("ports=" + Arrays.toString(config.getPorts())); + System.out.println("debug=" + config.isDebug()); + + + TestMAC test = new TestMAC(); + Choise choise = new Choise(args); + menu1(config, choise, test); + + } + +}