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.util.Arrays; public class GBT32960FullDecoder { private static final Logger log = LoggerFactory.getLogger(GBT32960FullDecoder.class); // 从SN码生成AES密钥 private static byte[] generateKeyFromSn(String sn) { StringBuilder keyStr = new StringBuilder(sn); while (keyStr.length() < 16) { keyStr.append("0"); } keyStr = new StringBuilder(keyStr.substring(0, 16)); return keyStr.toString().getBytes(java.nio.charset.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))) + "..."); // 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); 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; } 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); } }