diff --git a/src/main/java/com/sunyard/SydApiException.java b/src/main/java/com/sunyard/SydApiException.java index 51cd224..1f95e2b 100644 --- a/src/main/java/com/sunyard/SydApiException.java +++ b/src/main/java/com/sunyard/SydApiException.java @@ -45,6 +45,9 @@ public class SydApiException extends RuntimeException{ case 4: msg = "DER 编码格式错误(公钥) "; break; + case 31: + msg = "缓冲区索引错误"; + break; case 0xd: msg = "公钥加密错误"; break; diff --git a/src/main/java/com/sunyard/util/BytesUtil.java b/src/main/java/com/sunyard/util/BytesUtil.java new file mode 100644 index 0000000..06233b7 --- /dev/null +++ b/src/main/java/com/sunyard/util/BytesUtil.java @@ -0,0 +1,174 @@ +package com.sunyard.util; + +import java.nio.ByteOrder; + +/** + * 字节数组转换工具类,提供常见的基本类型与字节数组之间的互转功能。 + * 默认采用大端字节序(高位在前),也支持通过参数指定字节序。 + * + * @author YourName + */ +public final class BytesUtil { + + private BytesUtil() { + // 私有构造方法,防止实例化 + } + + // ==================== 无符号 short 转字节数组 ==================== + + /** + * 将无符号 short 值(以 int 形式传入,范围 0 ~ 65535)转换为大端字节序的字节数组(2字节)。 + * + * @param value 无符号 short 值(0 到 65535) + * @return 长度为 2 的字节数组,高位在前 + * @throws IllegalArgumentException 如果 value 超出 0~65535 范围 + */ + public static byte[] unsignedShortToBytes(int value) { + if (value < 0 || value > 0xFFFF) { + throw new IllegalArgumentException("Value out of range for unsigned short: " + value); + } + return new byte[]{ + (byte) ((value >> 8) & 0xFF), + (byte) (value & 0xFF) + }; + } + + /** + * 将无符号 short 值(以 short 类型传入,自动按无符号处理)转换为大端字节序的字节数组(2字节)。 + * 例如,传入 (short) 0x8001 会得到 { (byte)0x80, (byte)0x01 }。 + * + * @param value short 类型,将按其二进制补码表示直接作为无符号值处理 + * @return 长度为 2 的字节数组,高位在前 + */ + public static byte[] unsignedShortToBytes(short value) { + // 将 short 转为无符号 int + int unsignedValue = value & 0xFFFF; + return unsignedShortToBytes(unsignedValue); + } + + /** + * 将无符号 short 值(int 类型)转换为指定字节序的字节数组(2字节)。 + * + * @param value 无符号 short 值(0 到 65535) + * @param byteOrder 字节序,ByteOrder.BIG_ENDIAN 或 ByteOrder.LITTLE_ENDIAN + * @return 长度为 2 的字节数组 + * @throws IllegalArgumentException 如果 value 超出范围或 byteOrder 为 null + */ + public static byte[] unsignedShortToBytes(int value, ByteOrder byteOrder) { + if (byteOrder == null) { + throw new IllegalArgumentException("ByteOrder must not be null"); + } + byte[] bytes = unsignedShortToBytes(value); + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + // 交换高低位 + byte tmp = bytes[0]; + bytes[0] = bytes[1]; + bytes[1] = tmp; + } + return bytes; + } + + /** + * 将无符号 short 值(short 类型)转换为指定字节序的字节数组(2字节)。 + * + * @param value short 类型,按无符号处理 + * @param byteOrder 字节序 + * @return 长度为 2 的字节数组 + */ + public static byte[] unsignedShortToBytes(short value, ByteOrder byteOrder) { + return unsignedShortToBytes(value & 0xFFFF, byteOrder); + } + + // ==================== 字节数组转无符号 short ==================== + + /** + * 从字节数组的起始位置读取 2 字节,按大端序转换为无符号 short 值(返回 int 类型)。 + * + * @param bytes 字节数组 + * @param offset 起始偏移量 + * @return 无符号 short 值(0 ~ 65535) + * @throws IllegalArgumentException 如果 bytes 为 null 或长度不足 + */ + public static int bytesToUnsignedShort(byte[] bytes, int offset) { + if (bytes == null || offset + 2 > bytes.length) { + throw new IllegalArgumentException("Invalid byte array or offset"); + } + return ((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF); + } + + /** + * 从字节数组的起始位置读取 2 字节,按指定字节序转换为无符号 short 值。 + * + * @param bytes 字节数组 + * @param offset 起始偏移量 + * @param byteOrder 字节序 + * @return 无符号 short 值 + */ + public static int bytesToUnsignedShort(byte[] bytes, int offset, ByteOrder byteOrder) { + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + return ((bytes[offset + 1] & 0xFF) << 8) | (bytes[offset] & 0xFF); + } else { + return bytesToUnsignedShort(bytes, offset); + } + } + + // ==================== 其他常见转换(扩展) ==================== + + /** + * 将 int 值转换为大端序的 4 字节数组。 + */ + public static byte[] intToBytes(int value) { + return new byte[]{ + (byte) ((value >> 24) & 0xFF), + (byte) ((value >> 16) & 0xFF), + (byte) ((value >> 8) & 0xFF), + (byte) (value & 0xFF) + }; + } + + /** + * 将大端序的 4 字节数组转换为 int 值。 + */ + public static int bytesToInt(byte[] bytes, int offset) { + if (bytes == null || offset + 4 > bytes.length) { + throw new IllegalArgumentException("Invalid byte array or offset"); + } + return ((bytes[offset] & 0xFF) << 24) + | ((bytes[offset + 1] & 0xFF) << 16) + | ((bytes[offset + 2] & 0xFF) << 8) + | (bytes[offset + 3] & 0xFF); + } + + /** + * 将 long 值转换为大端序的 8 字节数组。 + */ + public static byte[] longToBytes(long value) { + return new byte[]{ + (byte) ((value >> 56) & 0xFF), + (byte) ((value >> 48) & 0xFF), + (byte) ((value >> 40) & 0xFF), + (byte) ((value >> 32) & 0xFF), + (byte) ((value >> 24) & 0xFF), + (byte) ((value >> 16) & 0xFF), + (byte) ((value >> 8) & 0xFF), + (byte) (value & 0xFF) + }; + } + + /** + * 将大端序的 8 字节数组转换为 long 值。 + */ + public static long bytesToLong(byte[] bytes, int offset) { + if (bytes == null || offset + 8 > bytes.length) { + throw new IllegalArgumentException("Invalid byte array or offset"); + } + return ((long) (bytes[offset] & 0xFF) << 56) + | ((long) (bytes[offset + 1] & 0xFF) << 48) + | ((long) (bytes[offset + 2] & 0xFF) << 40) + | ((long) (bytes[offset + 3] & 0xFF) << 32) + | ((long) (bytes[offset + 4] & 0xFF) << 24) + | ((long) (bytes[offset + 5] & 0xFF) << 16) + | ((long) (bytes[offset + 6] & 0xFF) << 8) + | ((long) (bytes[offset + 7] & 0xFF)); + } +} diff --git a/src/main/java/racal/sunyard/main/SydApi4j.java b/src/main/java/racal/sunyard/main/SydApi4j.java index d2d9dad..6ba343f 100644 --- a/src/main/java/racal/sunyard/main/SydApi4j.java +++ b/src/main/java/racal/sunyard/main/SydApi4j.java @@ -46,10 +46,7 @@ import racal.sunyard.main.proto.*; import javax.security.auth.x500.X500Principal; import java.io.*; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketTimeoutException; +import java.net.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; @@ -6857,19 +6854,19 @@ public class SydApi4j implements SydApi { ParamChecker.checkNotNull(publicKey, "publicKey"); ParamChecker.checkNotNull(pOrgData, "pOrgData"); - final int CHUNK_SIZE = 63 * 1024; // 64 KB + final int CHUNK_SIZE = 62 * 1024; // 62 KB int dataLen = pOrgData.length; int offset = 0; while (offset < dataLen) { int len = Math.min(CHUNK_SIZE, dataLen - offset); // 调用底层支持偏移量的加密方法 - SYD_SM2Encrypt(1, publicKey, ByteBuffer.wrap(pOrgData, offset, len), offset); + SYD_SM2Encrypt(1, publicKey, ByteBuffer.wrap(pOrgData, offset, len), offset, len, dataLen); offset += len; } // 计算 - SYD_SM2Encrypt(2, publicKey, null, 0); + SYD_SM2Encrypt(2, publicKey, null, 0, 0, dataLen); // 取回 int idx = 0; @@ -6878,7 +6875,7 @@ public class SydApi4j implements SydApi { int isLast = 0; while (isLast != 1) { - MutiReturn7 ret = SYD_SM2Encrypt(3, null, null, idx); + MutiReturn7 ret = SYD_SM2Encrypt(3, null, null, idx, 0, 0); ByteBuffer bb = ret.getData(); isLast = ret.getIsLast(); @@ -6905,7 +6902,7 @@ public class SydApi4j implements SydApi { // @Override // 支持大包加解密 - private MutiReturn7 SYD_SM2Encrypt(int dataType, byte[] publicKey, ByteBuffer pOrgData, int idx) { + private MutiReturn7 SYD_SM2Encrypt(int dataType, byte[] publicKey, ByteBuffer pOrgData, int idx, int inLen, int totalLen) { // 参数检查 ParamChecker.checkInRange(dataType, "dataType", 1, 3); @@ -6928,13 +6925,13 @@ public class SydApi4j implements SydApi { bb.put(publicKey); } if ( 1 == dataType ) { // 数据 - bb.put(ByteUtil.shortToBytes((short) pOrgData.limit(), ByteOrder.BIG_ENDIAN)); + bb.put(com.sunyard.util.BytesUtil.unsignedShortToBytes(inLen, ByteOrder.BIG_ENDIAN)); bb.put(pOrgData); - bb.put(ByteUtil.intToBytes(idx)); + bb.put(ByteUtil.intToBytes(idx, ByteOrder.BIG_ENDIAN)); } if ( 3 == dataType) { // 仅数据长度 - bb.put( ByteUtil.shortToBytes((short) (63*1024), ByteOrder.BIG_ENDIAN) ); + bb.put( ByteUtil.shortToBytes((short) (62*1024), ByteOrder.BIG_ENDIAN) ); bb.put( ByteUtil.intToBytes(idx, ByteOrder.BIG_ENDIAN)); } @@ -6947,8 +6944,41 @@ public class SydApi4j implements SydApi { // 通信 synchronized (this) { - // 响应解析 - bb = syncRead(syncSend(bb)); + int originalTimeout = -1; + boolean timeoutModified = false; + + try { + // 仅当 dataType == 2 时才执行超时时间调整逻辑 + if (false && dataType == 2) { + // 获取当前 Socket 超时时间(毫秒),可能为 -1(无限等待)或 0(立即超时) + originalTimeout = this.socket.getSoTimeout(); + + // 计算 totalLen 是 1MB 的多少倍(整数倍,如 2M→2,3M→3,4M→4) + int rate = totalLen / (1024 * 1024); + + // 仅当倍数 >1 且原超时时间为正数时,才按比例放大超时时间 + if (rate > 1 && originalTimeout > 0) { + int newTimeout = originalTimeout * rate; + this.socket.setSoTimeout(newTimeout); + timeoutModified = true; // 标记已修改,以便 finally 中恢复 + } + } + + // 响应解析(无论是否调整超时,都正常执行) + bb = syncRead(syncSend(bb)); + + } catch (SocketException e) { + throw new SydApiException("socket 状态错误", -5, e); + } finally { + // 仅当超时时间被实际修改过时才恢复原值 + if (timeoutModified) { + try { + this.socket.setSoTimeout(originalTimeout); + } catch (SocketException e) { + // 恢复失败时可根据需要记录日志,此处忽略 + } + } + } } bb.flip(); @@ -6966,9 +6996,10 @@ public class SydApi4j implements SydApi { int isLast = bb.get(); byte[] dataLenArr = new byte[2]; bb.get(dataLenArr); - int dataLen = ByteUtil.bytesToShort(dataLenArr, ByteOrder.BIG_ENDIAN); + int dataLen = Short.toUnsignedInt(ByteUtil.bytesToShort(dataLenArr, ByteOrder.BIG_ENDIAN)); // byte[] data = new byte[dataLen]; // bb.get(data); + // 从返回数据中创建密文切片,此时不拷贝数据。 ByteBuffer slice = ByteBufferUtil.sliceAndConsume(bb, dataLen); return new MutiReturn7(retCode, isLast, slice); } else { @@ -6989,19 +7020,19 @@ public class SydApi4j implements SydApi { ParamChecker.checkNotNull(privateKey, "privateKey"); ParamChecker.checkNotNull(pCipherData, "pCipherData"); - final int CHUNK_SIZE = 63 * 1024; // 63 KB,与加密保持一致 + final int CHUNK_SIZE = 62 * 1024; // 62 KB,与加密保持一致 int dataLen = pCipherData.length; int offset = 0; // 1. 分块传入密文 while (offset < dataLen) { int len = Math.min(CHUNK_SIZE, dataLen - offset); - SYD_SM2Decrypt(1, privateKey, ByteBuffer.wrap(pCipherData, offset, len), offset); + SYD_SM2Decrypt(1, privateKey, ByteBuffer.wrap(pCipherData, offset, len), offset, len, 0); offset += len; } // 2. 传入私钥,触发解密处理 - SYD_SM2Decrypt(2, privateKey, null, 0); + SYD_SM2Decrypt(2, privateKey, null, 0, 0, dataLen); // 3. 分块取回明文 int idx = 0; @@ -7010,7 +7041,7 @@ public class SydApi4j implements SydApi { int isLast = 0; while (isLast != 1) { - MutiReturn7 ret = SYD_SM2Decrypt(3, null, null, idx); + MutiReturn7 ret = SYD_SM2Decrypt(3, null, null, idx, 0, 0); ByteBuffer bb = ret.getData(); isLast = ret.getIsLast(); @@ -7042,7 +7073,7 @@ public class SydApi4j implements SydApi { * @param idx 偏移量索引 * @return MutiReturn7 包含返回码、isLast标志及数据片 */ - private MutiReturn7 SYD_SM2Decrypt(int dataType, byte[] privateKey, ByteBuffer pCipherData, int idx) { + private MutiReturn7 SYD_SM2Decrypt(int dataType, byte[] privateKey, ByteBuffer pCipherData, int idx, int inLen, int totalLen) { ParamChecker.checkInRange(dataType, "dataType", 1, 3); // 估算缓存大小 @@ -7064,12 +7095,12 @@ public class SydApi4j implements SydApi { bb.put(privateKey); } if (dataType == 1) { // 密文块 - bb.put(ByteUtil.shortToBytes((short) pCipherData.limit(), ByteOrder.BIG_ENDIAN)); + bb.put( com.sunyard.util.BytesUtil.unsignedShortToBytes(inLen, ByteOrder.BIG_ENDIAN) ); bb.put(pCipherData); bb.put(ByteUtil.intToBytes(idx, ByteOrder.BIG_ENDIAN)); } if (dataType == 3) { // 请求明文块 - bb.put(ByteUtil.shortToBytes((short) (63 * 1024), ByteOrder.BIG_ENDIAN)); + bb.put(ByteUtil.shortToBytes((short) (62 * 1024), ByteOrder.BIG_ENDIAN)); bb.put(ByteUtil.intToBytes(idx, ByteOrder.BIG_ENDIAN)); } @@ -7080,7 +7111,41 @@ public class SydApi4j implements SydApi { // 通信 synchronized (this) { - bb = syncRead(syncSend(bb)); + int originalTimeout = -1; + boolean timeoutModified = false; + + try { + // 仅当 dataType == 2 时才执行超时时间调整逻辑 + if (false && dataType == 2) { + // 获取当前 Socket 超时时间(毫秒),可能为 -1(无限等待)或 0(立即超时) + originalTimeout = this.socket.getSoTimeout(); + + // 计算 totalLen 是 1MB 的多少倍(整数倍,如 2M→2,3M→3,4M→4) + int rate = totalLen / (1024 * 1024); + + // 仅当倍数 >1 且原超时时间为正数时,才按比例放大超时时间 + if (rate > 1 && originalTimeout > 0) { + int newTimeout = originalTimeout * rate; + this.socket.setSoTimeout(newTimeout); + timeoutModified = true; // 标记已修改,以便 finally 中恢复 + } + } + + // 响应解析(无论是否调整超时,都正常执行) + bb = syncRead(syncSend(bb)); + + } catch (SocketException e) { + throw new SydApiException("socket 状态错误", -5, e); + } finally { + // 仅当超时时间被实际修改过时才恢复原值 + if (timeoutModified) { + try { + this.socket.setSoTimeout(originalTimeout); + } catch (SocketException e) { + // 恢复失败时可根据需要记录日志,此处忽略 + } + } + } } bb.flip(); @@ -7097,7 +7162,7 @@ public class SydApi4j implements SydApi { int isLast = bb.get(); byte[] dataLenArr = new byte[2]; bb.get(dataLenArr); - int dataLen = ByteUtil.bytesToShort(dataLenArr, ByteOrder.BIG_ENDIAN); + int dataLen = Short.toUnsignedInt(ByteUtil.bytesToShort(dataLenArr, ByteOrder.BIG_ENDIAN)); ByteBuffer slice = ByteBufferUtil.sliceAndConsume(bb, dataLen); return new MutiReturn7(retCode, isLast, slice); } else { diff --git a/src/test/java/cmbpoc/FunctionTest.java b/src/test/java/cmbpoc/FunctionTest.java index 703be55..026024d 100644 --- a/src/test/java/cmbpoc/FunctionTest.java +++ b/src/test/java/cmbpoc/FunctionTest.java @@ -16,7 +16,10 @@ public class FunctionTest { private byte[] publicKey4Sign; private byte[] privateKey4Ende; private byte[] publicKey4Ende; - private byte[] orgData = new byte[15]; + private byte[] orgData = new byte[1024]; + private byte[] orgData2K = new byte[ 2 * 1024 ]; + private byte[] orgData2M = new byte[ 2 * 1024 * 1024 ]; + private byte[] orgData10M = new byte[ 4 * 1024 * 1024 - 1024]; private String sign; @Before @@ -26,7 +29,7 @@ public class FunctionTest { System.setProperty("com.sunyard.sydapi4j.debug", "true"); // 建立链接(单台) - this.api = (SydApi4j) new SydApi4j().connect("192.168.100.145", 8889, null, 5000); + this.api = (SydApi4j) new SydApi4j().connect("192.168.100.145", 8889, null, 30000); // RetWrap keypair = this.getPrivateKeyAndPublickKey(keypairIndex4Sign); // this.publicKey4Sign = Util.hexString2Bytes( keypair.get("pk").toString() ); @@ -103,6 +106,33 @@ public class FunctionTest { byte[] deData = this.api.SYD_SM2Decrypt(privateKey4Ende, enData); Assert.assertArrayEquals(orgData, deData); } + + + @Test + public void SYD_SM2_Encrypt_2K(){ + byte[] enData = this.api.SYD_SM2Encrypt(publicKey4Ende, orgData2K); + System.out.println("密文=" + Util.bytes2HexString(enData)); + byte[] deData = this.api.SYD_SM2Decrypt(privateKey4Ende, enData); + Assert.assertArrayEquals(orgData2K, deData); + } + + + @Test + public void SYD_SM2_Encrypt_2M(){ + byte[] enData = this.api.SYD_SM2Encrypt(publicKey4Ende, orgData2M); + System.out.println("密文=" + Util.bytes2HexString(enData)); + byte[] deData = this.api.SYD_SM2Decrypt(privateKey4Ende, enData); + Assert.assertArrayEquals(orgData2M, deData); + } + + + @Test + public void SYD_SM2_Encrypt_10M(){ + byte[] enData = this.api.SYD_SM2Encrypt(publicKey4Ende, orgData10M); + System.out.println("密文=" + Util.bytes2HexString(enData)); + byte[] deData = this.api.SYD_SM2Decrypt(privateKey4Ende, enData); + Assert.assertArrayEquals(orgData10M, deData); + } }