2026-06-26 20b96473f2520590a0dca6b775b81e3ea06a77a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.length;
 
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.parsetools.RecordParser;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.lang.Assert;
 
import java.util.concurrent.atomic.AtomicReference;
 
/**
 * IoT TCP 长度字段帧编解码器
 * <p>
 * 基于长度字段的拆包策略,消息格式:[长度字段][消息体]
 * <p>
 * 参数说明:
 * <ul>
 *   <li>lengthFieldOffset: 长度字段在消息中的偏移量</li>
 *   <li>lengthFieldLength: 长度字段的字节数(1/2/4)</li>
 *   <li>lengthAdjustment: 长度调整值,用于调整长度字段的实际含义</li>
 *   <li>initialBytesToStrip: 解码后跳过的字节数</li>
 * </ul>
 *
 * @author 芋道源码
 */
@Slf4j
public class IotTcpLengthFieldFrameCodec implements IotTcpFrameCodec {
 
    /**
     * 最大帧长度(64KB),防止 DoS 攻击
     */
    private static final int MAX_FRAME_LENGTH = 65536;
 
    private final int lengthFieldOffset;
    private final int lengthFieldLength;
    private final int lengthAdjustment;
    private final int initialBytesToStrip;
 
    /**
     * 头部长度 = 长度字段偏移量 + 长度字段长度
     */
    private final int headerLength;
 
    public IotTcpLengthFieldFrameCodec(IotTcpConfig.CodecConfig config) {
        Assert.notNull(config.getLengthFieldOffset(), "lengthFieldOffset 不能为空");
        Assert.notNull(config.getLengthFieldLength(), "lengthFieldLength 不能为空");
        Assert.notNull(config.getLengthAdjustment(), "lengthAdjustment 不能为空");
        Assert.notNull(config.getInitialBytesToStrip(), "initialBytesToStrip 不能为空");
        this.lengthFieldOffset = config.getLengthFieldOffset();
        this.lengthFieldLength = config.getLengthFieldLength();
        this.lengthAdjustment = config.getLengthAdjustment();
        this.initialBytesToStrip = config.getInitialBytesToStrip();
        this.headerLength = lengthFieldOffset + lengthFieldLength;
    }
 
    @Override
    public IotTcpCodecTypeEnum getType() {
        return IotTcpCodecTypeEnum.LENGTH_FIELD;
    }
 
    @Override
    public RecordParser createDecodeParser(Handler<Buffer> handler) {
        // 创建状态机:先读取头部,再读取消息体
        RecordParser parser = RecordParser.newFixed(headerLength);
        parser.maxRecordSize(MAX_FRAME_LENGTH); // 设置最大记录大小,防止 DoS 攻击
        final AtomicReference<Integer> bodyLength = new AtomicReference<>(null); // 消息体长度,null 表示读取头部阶段
        final AtomicReference<Buffer> headerBuffer = new AtomicReference<>(null); // 头部消息
 
        // 处理读取到的数据
        parser.handler(buffer -> {
            if (bodyLength.get() == null) {
                // 阶段 1: 读取头部,解析长度字段
                headerBuffer.set(buffer.copy());
                int length = readLength(buffer, lengthFieldOffset, lengthFieldLength);
                int frameBodyLength = length + lengthAdjustment;
                // 检查帧长度是否合法
                if (frameBodyLength < 0) {
                    throw new IllegalStateException(String.format(
                            "[createDecodeParser][帧长度异常,length: %d, frameBodyLength: %d]",
                            length, frameBodyLength));
                }
                // 消息体为空,抛出异常
                if (frameBodyLength == 0) {
                    throw new IllegalStateException("[createDecodeParser][消息体不能为空]");
                }
 
                // 【重要】切换到读取消息体模式
                bodyLength.set(frameBodyLength);
                parser.fixedSizeMode(frameBodyLength);
            } else {
                // 阶段 2: 读取消息体,组装完整帧
                Buffer frame = processFrame(headerBuffer.get(), buffer);
                // 重置状态,准备读取下一帧
                bodyLength.set(null);
                headerBuffer.set(null);
                parser.fixedSizeMode(headerLength);
 
                // 【重要】处理完整消息
                handler.handle(frame);
            }
        });
        parser.exceptionHandler(ex -> {
            throw new RuntimeException("[createDecodeParser][解析异常]", ex);
        });
        return parser;
    }
 
    @Override
    public Buffer encode(byte[] data) {
        Buffer buffer = Buffer.buffer();
        // 计算要写入的长度值
        int lengthValue = data.length - lengthAdjustment;
        // 写入偏移量前的填充字节(如果有)
        for (int i = 0; i < lengthFieldOffset; i++) {
            buffer.appendByte((byte) 0);
        }
        // 写入长度字段
        writeLength(buffer, lengthValue, lengthFieldLength);
        // 写入消息体
        buffer.appendBytes(data);
        return buffer;
    }
 
    /**
     * 从 Buffer 中读取长度字段
     */
    @SuppressWarnings("EnhancedSwitchMigration")
    private int readLength(Buffer buffer, int offset, int length) {
        switch (length) {
            case 1:
                return buffer.getUnsignedByte(offset);
            case 2:
                return buffer.getUnsignedShort(offset);
            case 4:
                return buffer.getInt(offset);
            default:
                throw new IllegalArgumentException("不支持的长度字段长度: " + length);
        }
    }
 
    /**
     * 向 Buffer 中写入长度字段
     */
    private void writeLength(Buffer buffer, int length, int fieldLength) {
        switch (fieldLength) {
            case 1:
                buffer.appendByte((byte) length);
                break;
            case 2:
                buffer.appendShort((short) length);
                break;
            case 4:
                buffer.appendInt(length);
                break;
            default:
                throw new IllegalArgumentException("不支持的长度字段长度: " + fieldLength);
        }
    }
 
    /**
     * 处理帧数据(根据 initialBytesToStrip 跳过指定字节)
     */
    private Buffer processFrame(Buffer header, Buffer body) {
        Buffer fullFrame = Buffer.buffer();
        if (header != null) {
            fullFrame.appendBuffer(header);
        }
        if (body != null) {
            fullFrame.appendBuffer(body);
        }
        // 根据 initialBytesToStrip 跳过指定字节
        if (initialBytesToStrip > 0 && initialBytesToStrip < fullFrame.length()) {
            return fullFrame.slice(initialBytesToStrip, fullFrame.length());
        }
        return fullFrame;
    }
 
}