package com.zksy.common.netty; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; /** 每条连接独立累积报文;校验失败只丢弃坏帧,不关闭设备连接。 */ public final class DeviceFrameDecoder extends ByteToMessageDecoder { public enum Protocol { PINGSHENG, MANHOLE, GAS, FIRE_PRESSURE } private static final int MAX_FRAME = 8192; private final Protocol protocol; public DeviceFrameDecoder(Protocol protocol) { this.protocol = protocol; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { int minimum = protocol == Protocol.MANHOLE ? 72 : protocol == Protocol.GAS ? 74 : 5; while (in.readableBytes() >= minimum) { int start = in.readerIndex(); int length; if (protocol == Protocol.PINGSHENG) { if (in.getUnsignedMedium(start) != 0x123456) { in.skipBytes(1); continue; } length = in.getUnsignedShort(start + 3); if (length < 10 || length > MAX_FRAME) { in.skipBytes(1); continue; } if (in.readableBytes() < length) { // 长度字段损坏时,寻找后续完整且两层校验通过的上报帧。 int next = nextPingshengFrame(in, start + 3); if (next < 0) return; in.readerIndex(next); continue; } int xor = 0; for (int i = 0; i < length; i++) xor ^= in.getUnsignedByte(start + i); if (xor != 0) { in.skipBytes(1); continue; } } else if (protocol == Protocol.MANHOLE) { // 当前井盖解析器:35字节参数 + 20字节SIM + 15字节IMEI + CRC16。 length = 72; if (crc(in, start, 70) != in.getUnsignedShortLE(start + 70)) { in.skipBytes(1); continue; } } else if (protocol == Protocol.GAS) { // 当前已支持的7指标上报结构固定为74字节,内外两层校验与接收器一致。 length = 74; if (in.getUnsignedByte(start) != 0x7E || in.getUnsignedByte(start + 15) != 0xAA || !gasChecksum(in, start)) { in.skipBytes(1); continue; } } else { if (in.getUnsignedShort(start) != 0x7E7E) { in.skipBytes(1); continue; } // 现有消防设备长度字段不作为可信边界,使用结束符 + 完整CRC确认边界。 // 不能单靠03分隔:日期、TLV和压力数据中也可能出现03。 length = fireLength(in, start); if (length == 0) { // 坏帧后可能紧接新报文,不能让坏CRC永久阻塞下一条有效数据。 boolean recovered = false; for (int next = start + 2; next + 16 < in.writerIndex(); next++) { if (in.getUnsignedShort(next) == 0x7E7E && fireLength(in, next) > 0) { in.readerIndex(next); recovered = true; break; } } if (recovered) continue; if (in.readableBytes() >= MAX_FRAME) { in.skipBytes(1); continue; } return; } } out.add(in.readRetainedSlice(length)); } } private static boolean gasChecksum(ByteBuf in, int start) { int body = 0, full = 0; for (int i = 16; i < 72; i++) body += in.getUnsignedByte(start + i); for (int i = 3; i < 73; i++) full += in.getUnsignedByte(start + i); return (body & 255) == in.getUnsignedByte(start + 72) && ((255 - full) & 255) == in.getUnsignedByte(start + 73); } private static int nextPingshengFrame(ByteBuf in, int from) { for (int start = from; start + 10 <= in.writerIndex(); start++) { if (in.getUnsignedMedium(start) != 0x123456) continue; int length = in.getUnsignedShort(start + 3); if (length < 14 || length > MAX_FRAME || start + length > in.writerIndex() || in.getUnsignedByte(start + 6) != 0x31) continue; int xor = 0; for (int i = 0; i < length; i++) xor ^= in.getUnsignedByte(start + i); if (xor != 0) continue; int src = (in.getUnsignedByte(start + 7) + 1) / 2; int destIndex = start + 8 + src; if (src == 0 || src > 10 || destIndex >= start + length - 3) continue; int dest = (in.getUnsignedByte(destIndex) + 1) / 2; int body = destIndex + 1 + dest; if (dest > 10 || body + 2 >= start + length - 3) continue; if (crc(in, body, start + length - 3 - body) == in.getUnsignedShortLE(start + length - 3)) return start; } return -1; } private static int fireLength(ByteBuf in, int start) { if (in.writerIndex() - start < 14) return 0; if (in.getUnsignedByte(start + 10) != 0xEF) { // 上报数据固定头之后按TLV跨过值区,值中偶然的03+CRC不能作为帧尾。 int cursor = start + 37; int limit = Math.min(in.writerIndex(), start + MAX_FRAME); while (cursor + 3 <= limit) { int marker = in.getUnsignedByte(cursor); if (marker == 0x03 || marker == 0x17) { return crc(in, start, cursor - start + 1) == in.getUnsignedShort(cursor + 1) ? cursor - start + 3 : 0; } int tag = in.getUnsignedShort(cursor); int bytes = (in.getUnsignedByte(cursor + 2) >>> 3) & 31; if (bytes == 0 && tag == 0x581A) bytes = 2; if (bytes == 0 && tag == 0x4520) bytes = 4; cursor += 3 + bytes; } return 0; } int value = 0xFFFF; int end = Math.min(in.writerIndex() - 2, start + MAX_FRAME - 2); for (int i = start; i < end; i++) { value = crcByte(value, in.getUnsignedByte(i)); int marker = in.getUnsignedByte(i); // 消防现有crc16Tall以高字节在前追加CRC。 if (i >= start + 14 && (marker == 0x03 || marker == 0x17) && value == in.getUnsignedShort(i + 1)) return i - start + 3; } return 0; } private static int crc(ByteBuf in, int start, int length) { int value = 0xFFFF; for (int i = 0; i < length; i++) value = crcByte(value, in.getUnsignedByte(start + i)); return value; } private static int crcByte(int value, int b) { value ^= b; for (int i = 0; i < 8; i++) value = (value & 1) == 1 ? (value >>> 1) ^ 0xA001 : value >>> 1; return value; } }