package com.evobms.project.bms.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Arrays; public class GBT32960FullDecoder { private static final Logger log = LoggerFactory.getLogger(GBT32960FullDecoder.class); // 从SN码生成AES密钥 private static byte[] generateKeyFromSn(String sn) { String s = sn == null ? "" : sn; String key; if ("EVO0001".equals(s)) { StringBuilder sb = new StringBuilder(16); sb.append("EVO0001"); while (sb.length() < 16) sb.append('0'); key = sb.toString(); } else if (s.length() >= 16) { key = s.substring(s.length() - 16); } else { StringBuilder sb = new StringBuilder(16); sb.append(s); while (sb.length() < 16) sb.append('0'); key = sb.toString(); } return key.getBytes(StandardCharsets.UTF_8); } // AES解密 private static byte[] decryptData(byte[] encryptedData, byte[] key) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(encryptedData); } // BCC校验 private static byte calculateBCC(byte[] data, int start, int end) { byte bcc = 0; for (int i = start; i < end; i++) { bcc ^= data[i]; } return bcc; } // 16进制字符串转字节数组 private static byte[] hexStringToByteArray(String hex) { hex = hex.replaceAll("\\s", ""); // 移除空格 int len = hex.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); } return data; } // 字节数组转16进制字符串 private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X", b)); } return sb.toString(); } // 详细的协议解析方法 public static String parseGBT32960Data(byte[] receivedData, String deviceSn) { String bytesData = null; try { log.info("=== GBT32960协议数据解析 ==="); log.info("实际数据长度: " + receivedData.length + " 字节"); // 1. 检查起始位 if (receivedData.length < 2 || receivedData[0] != 0x23 || receivedData[1] != 0x23) { log.error("无效的起始位或数据过短"); return null; } log.info("起始位: 0x2323 ✓"); // 2. 打印完整数据用于调试 log.info("完整数据: " + bytesToHex(receivedData)); // 3. 分析协议结构 int offset = 2; // 跳过起始位 // 命令标识 byte commandId = receivedData[offset++]; log.info("命令标识: 0x" + String.format("%02X", commandId)); // 应答标志 byte responseFlag = receivedData[offset++]; log.info("应答标志: 0x" + String.format("%02X", responseFlag)); // VIN码 (17字节) byte[] vin = Arrays.copyOfRange(receivedData, offset, offset + 17); offset += 17; log.info("VIN码: " + bytesToHex(vin) + " (" + new String(vin).trim() + ")"); // 加密方式 byte encryptionMethod = receivedData[offset++]; log.info("加密方式: 0x" + String.format("%02X", encryptionMethod)); if (encryptionMethod != 0x03) { log.error("不支持的解密方式,期望AES128(0x03)"); return null; } // 数据单元长度 (WORD, 大端序) int dataUnitLength = ((receivedData[offset] & 0xFF) << 8) | (receivedData[offset + 1] & 0xFF); offset += 2; log.info("声明的数据单元长度: " + dataUnitLength + " 字节"); // 计算当前头部长度 int headerLength = offset; log.info("协议头部长度: " + headerLength + " 字节"); // 剩余数据长度 int remainingDataLength = receivedData.length - headerLength; log.info("剩余数据长度: " + remainingDataLength + " 字节"); // 4. 重新计算期望长度 // 方案1: 如果BCC是1字节 int expectedLength1 = headerLength + dataUnitLength + 1; // 方案2: 如果BCC是2字节(根据您最初描述) int expectedLength2 = headerLength + dataUnitLength + 2; log.info("期望长度 (BCC=1字节): " + expectedLength1); log.info("期望长度 (BCC=2字节): " + expectedLength2); log.info("实际长度: " + receivedData.length); // 5. 确定正确的数据范围 int encryptedDataStart = offset; int encryptedDataEnd; int bccStart; if (receivedData.length == expectedLength1) { // BCC为1字节 encryptedDataEnd = encryptedDataStart + dataUnitLength; bccStart = encryptedDataEnd; log.info("采用方案: BCC为1字节"); } else if (receivedData.length == expectedLength2) { // BCC为2字节 encryptedDataEnd = encryptedDataStart + dataUnitLength; bccStart = encryptedDataEnd; log.info("采用方案: BCC为2字节"); } else { // 自动适应:使用剩余的所有数据作为加密数据 encryptedDataEnd = receivedData.length - 1; // 假设BCC为1字节 int actualDataUnitLength = encryptedDataEnd - encryptedDataStart; log.info("长度不匹配,使用实际数据单元长度: " + actualDataUnitLength + " 字节"); dataUnitLength = actualDataUnitLength; bccStart = encryptedDataEnd; } // 6. 提取加密数据 byte[] encryptedData = Arrays.copyOfRange(receivedData, encryptedDataStart, encryptedDataStart + dataUnitLength); log.info("加密数据长度: " + encryptedData.length + " 字节"); log.info("加密数据 (前64字节): " + bytesToHex(Arrays.copyOf(encryptedData, Math.min(64, encryptedData.length))) + "..."); if (encryptedData.length % 16 != 0) { log.error("AES密文长度非法,不是16倍数,length=" + encryptedData.length); return null; } // 7. 提取BCC校验码 int bccLength = receivedData.length - (encryptedDataStart + dataUnitLength); byte[] receivedBCC = Arrays.copyOfRange(receivedData, encryptedDataStart + dataUnitLength, receivedData.length); //log.info("BCC长度: " + bccLength + " 字节"); //log.info("接收到的BCC: " + bytesToHex(receivedBCC)); // 8. 计算BCC校验范围:从命令单元开始到加密数据结束 int bccCalcStart = 2; // 从命令标识开始 (跳过0x2323) int bccCalcEnd = encryptedDataStart + dataUnitLength; byte calculatedBCC = calculateBCC(receivedData, bccCalcStart, bccCalcEnd); log.info("BCC计算范围: 字节[" + bccCalcStart + "~" + bccCalcEnd + ")"); log.info("计算出的BCC: 0x" + String.format("%02X", calculatedBCC)); // 验证BCC (只比较第一个字节) if (receivedBCC.length > 0 && calculatedBCC == receivedBCC[0]) { log.info("BCC校验通过 ✓"); } else { log.error("BCC校验失败!"); } //使用的AES密钥 byte[] aesKey = generateKeyFromSn(deviceSn); System.out.println(Arrays.toString(aesKey)); System.out.println(aesKey.length); log.info("AES密钥: " + bytesToHex(aesKey)); byte[] decryptedData = decryptData(encryptedData, aesKey); bytesData = bytesToHex(decryptedData); log.info("解密成功!"); log.info("解密后数据长度: " + decryptedData.length + " 字节"); log.info("解密后数据 (完整): " + bytesData); // 10. 解析解密后的数据单元 // parseDecryptedDataUnit(decryptedData); } catch (Exception e) { log.error("解析过程中发生错误: " + e.getMessage()); e.printStackTrace(); } return bytesData; } /* public static String parseGBT32960Data(byte[] frame, String deviceSn) throws Exception { if (frame == null || frame.length < 25) { throw new IllegalArgumentException("报文长度非法"); } int offset = 0; // 1. 起始符校验 0x23 0x23 if (frame[offset++] != 0x23 || frame[offset++] != 0x23) { throw new IllegalArgumentException("非法起始位,非0x2323"); } // 2. 命令标识 byte commandId = frame[offset++]; // 3. 应答标志 byte responseFlag = frame[offset++]; // 4. VIN (17字节) byte[] vinBytes = Arrays.copyOfRange(frame, offset, offset + 17); String vin = new String(vinBytes).trim(); offset += 17; // 5. 加密方式 byte encryptType = frame[offset++]; // 6. 数据单元长度 (大端) if (offset + 2 > frame.length) { throw new IllegalArgumentException("数据单元长度字段越界"); } int dataUnitLength = ((frame[offset] & 0xFF) << 8) | (frame[offset + 1] & 0xFF); offset += 2; int headerLength = offset; // 7. 校验整体长度 // 标准格式: // 头部 + 数据单元 + 1字节BCC int expectedLength = headerLength + dataUnitLength + 1; if (frame.length != expectedLength) { throw new IllegalArgumentException("报文长度不匹配,声明长度=" + dataUnitLength + ",实际长度=" + frame.length + ",期望总长度=" + expectedLength); } // 8. 提取数据单元 byte[] dataUnit = Arrays.copyOfRange(frame, headerLength, headerLength + dataUnitLength ); // 9. BCC 校验 // BCC范围:从命令标识开始 byte receivedBcc = frame[frame.length - 1]; byte calculatedBcc = calculateBCC(frame, 2, headerLength + dataUnitLength ); if (receivedBcc != calculatedBcc) { throw new IllegalStateException( String.format("BCC校验失败,收到=0x%02X 计算=0x%02X", receivedBcc, calculatedBcc) ); } // 10. 加密处理 if (encryptType == 0x01) { // 不加密 return bytesToHex(dataUnit); } if (encryptType != 0x03) { throw new UnsupportedOperationException( "不支持的加密方式: " + encryptType); } // AES 必须 16倍数 if (dataUnitLength % 16 != 0) { throw new IllegalStateException( "AES密文长度非法,不是16倍数,length=" + dataUnitLength); } // 11. AES解密 byte[] aesKey = generateKeyFromSn(deviceSn); byte[] decrypted = decryptData(dataUnit, aesKey); return bytesToHex(decrypted); } */ private static void parseDecryptedDataUnit(byte[] decryptedData) { log.info("\n=== 数据单元解析 ==="); if (decryptedData == null || decryptedData.length == 0) { log.info("解密数据为空"); return; } log.info("数据单元总长度: " + decryptedData.length + " 字节"); log.info("数据单元内容: " + bytesToHex(decryptedData)); // 尝试解析采集时间(前6字节BCD) if (decryptedData.length >= 6) { String ts = parseTimestampFromBCD(Arrays.copyOfRange(decryptedData, 0, 6)); log.info("采集时间: " + ts + " (BCD)"); } // 扫描数据块: [类型ID(1)] [长度(1)] int offset = 6; // 时间之后 int itemIndex = 0; while (offset + 2 <= decryptedData.length) { int typeId = decryptedData[offset] & 0xFF; int length = decryptedData[offset + 1] & 0xFF; int payloadStart = offset + 2; int payloadEnd = payloadStart + length; // 长度异常时,尝试步进1字节继续 if (payloadEnd > decryptedData.length) { // 可能当前字节并非数据块起点(例如信息标志位),小步扫描跳过 offset += 1; continue; } byte[] payload = Arrays.copyOfRange(decryptedData, payloadStart, payloadEnd); log.info(String.format("数据块[%d] 类型: %s, 长度: %d, 起止: %d-%d", itemIndex++, typeName(typeId), length, payloadStart, payloadEnd - 1)); log.info("数据块HEX: " + bytesToHex(Arrays.copyOf(payload, Math.min(64, payload.length))) + (payload.length > 64 ? "..." : "")); // 极值数据尝试详细解析 if (typeId == 0x06) { try { GBT32960ExtremeDataParser.ExtremeData extreme = GBT32960ExtremeDataParser.parseExtremeData(Arrays.copyOfRange(decryptedData, offset, payloadEnd), 0); if (extreme != null) { log.info("极值数据解析结果: " + extreme.toString()); } } catch (Exception ex) { log.warn("极值数据解析异常: " + ex.getMessage()); } } offset = payloadEnd; } // 兼容旧的极值扫描逻辑 GBT32960ExtremeDataExtractor.ExtremeData andParseExtremeData = GBT32960ExtremeDataExtractor.findAndParseExtremeData(decryptedData); if (andParseExtremeData != null) { log.info(andParseExtremeData.toString()); } else { log.info("未找到极值数据或解析失败"); } } /** * 跳过数据项 */ private static int skipDataItem(byte[] data, int offset) { if (offset >= data.length) return offset; offset++; // 跳过数据类型 if (offset < data.length) { byte length = data[offset]; offset += 1 + (length & 0xFF); // 跳过长度字节和数据内容 } return offset; } private static String typeName(int typeId) { switch (typeId & 0xFF) { case 0x01: return "整车数据"; case 0x02: return "驱动电机数据"; case 0x03: return "燃料电池数据"; case 0x04: return "发动机数据"; case 0x05: return "车辆位置数据"; case 0x06: return "极值数据"; case 0x07: return "报警数据"; case 0x08: return "储能装置电压数据"; case 0x09: return "储能装置温度数据"; default: return String.format("未知类型(0x%02X)", typeId); } } // BCD字节转十进制整数 private static int bcdToInt(byte b) { return ((b >> 4) & 0x0F) * 10 + (b & 0x0F); } // 解析采集时间(BCD YY MM DD hh mm ss) private static String parseTimestampFromBCD(byte[] data) { if (data == null || data.length < 6) return "未知"; int year = 2000 + bcdToInt(data[0]); int month = bcdToInt(data[1]); int day = bcdToInt(data[2]); int hour = bcdToInt(data[3]); int minute = bcdToInt(data[4]); int second = bcdToInt(data[5]); return String.format("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second); } }