-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDemoCodecPlugin.cs
More file actions
352 lines (314 loc) · 13.5 KB
/
DemoCodecPlugin.cs
File metadata and controls
352 lines (314 loc) · 13.5 KB
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
using System;
using System.Text;
using Fatbeans.Plugin.Abstractions;
namespace DecoderPluginDemo
{
/// <summary>
/// 解码示例项目:
/// - 封包格式:[2字节包体长度][包体]
/// - Decode 支持拆包、并包、粘包,并对包体按 0x65 进行异或还原明文
/// - Encode 会先对明文按 0x65 进行异或,再自动补上2字节长度头
/// </summary>
public class DemoCodecPlugin : ICodecPlugin
{
/// <summary>
/// 包长字节数
/// </summary>
private static int PrefixLength = 2;
/// <summary>
/// 包长字节序,默认大端
/// </summary>
private static EndianType PrefixEndian = EndianType.BigEndian;
/// <summary>
/// 包长是否包含自身长度,默认为false,即包长只表示包体长度,不包含包长字段本身的长度。
/// </summary>
private static bool LengthIncludesPrefix = false;
/// <summary>
/// 会话状态,用于处理拆包/并包/粘包等情况,Key为会话标识(可以根据协议类型、源IP/端口、目的IP/端口等信息构建一个唯一标识),Value为会话状态对象(包含半包数据、已接收的包ID列表等信息)。
/// </summary>
private static readonly SessionStateStore<LengthPrefixedSessionState> SessionStates =
new SessionStateStore<LengthPrefixedSessionState>(
sessionKey => new LengthPrefixedSessionState(PrefixLength, PrefixEndian, LengthIncludesPrefix));
#region 插件元信息
/// <summary>
/// 解码器插件唯一标识,建议使用反向域名命名法,并包含版本号以便区分不同版本的插件,例如 "com.example.decoder.v1"。
/// </summary>
public string Id { get; private set; } = "decoder.demo.v2";
/// <summary>
/// 解码器插件名称,建议简洁明了,能够反映插件的功能和特点,例如 "Demo Codec Plugin"。
/// </summary>
public string Name { get; private set; } = "Demo Codec Plugin";
/// <summary>
/// 作者信息,建议包含作者姓名或组织名称
/// </summary>
public string Author { get; private set; } = "张三";
/// <summary>
/// 解码器插件版本,建议使用语义化版本号格式,例如 "1.0.0",以便用户了解插件的更新和兼容性情况。
/// </summary>
public string Version { get; private set; } = "1.0.0";
/// <summary>
/// 描述信息,建议简要介绍插件的功能、特点和使用场景,帮助用户快速了解插件的作用和价值
/// </summary>
public string Description { get; private set; } = "示例编解码插件:使用2字节长度头处理拆包/并包/粘包,包体按0x65异或进行编码与解码";
/// <summary>
/// 支持的肥豆主程序版本范围,建议使用语义化版本号格式,并明确指定最小和最大兼容版本,例如 "1.0.0" - "2.0.0",以便用户了解插件的兼容性情况。
/// (一般默认即可)
/// </summary>
public string HostMinVersion { get; private set; } = "1.0.0";
/// <summary>
/// 同上
/// </summary>
public string HostMaxVersion { get; private set; } = "9.9.9";
/// <summary>
/// 插件类型,必须指定为 PluginType.Decoder,表示这是一个解码器插件。
/// </summary>
public PluginType PluginType { get; private set; } = PluginType.Decoder;
/// <summary>
/// 为false,表示是一个专用解码器插件,只针对特定协议或数据格式进行解码。
/// 为true,则表示这是一个通用解码器插件,类似肥豆官方提供的ProtobufDecoderPlugin、MessageDecoderPlugin
/// </summary>
public bool IsUniversalDecoder { get; private set; } = true;
#endregion
#region 插件生命周期方法
/// <summary>
/// 插件初始化方法,在插件被加载时调用,可以根据需要在这里进行一些资源的初始化工作
/// </summary>
/// <param name="context"></param>
public void Initialize(PluginInitializationContext context)
{
}
/// <summary>
/// 是否能解码当前数据包,解码器主程序会根据这个方法的返回值来决定是否使用当前插件进行解码。
/// (根据DecodeContext对象提供的信息,比如协议类型、端口、包头等信息做一些基础判断,当前是否能解码)
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public bool CanDecode(DecodeContext context)
{
try
{
if (context == null || context.Packet == null)
return false;
var packet = context.Packet;
if (!string.IsNullOrEmpty(packet.Protocol) && packet.Protocol.Equals("TCP", StringComparison.OrdinalIgnoreCase))
return true;
if (packet.Data == null || packet.Data.Length < PrefixLength)
return HasPendingBuffer(context);
int bodyLength = LengthPrefixedFrameDecoder.ReadLength(packet.Data, PrefixLength, PrefixEndian);
if (bodyLength <= 0)
return false;
if (packet.Data.Length >= PrefixLength + bodyLength)
return true;
return HasPendingBuffer(context);
}
catch
{
return false;
}
}
/// <summary>
/// 解码方法,负责将输入的原始数据包解码或解密成结构化的业务数据。
/// 解码器主程序会调用这个方法来执行实际的解码操作。
/// </summary>
/// <param name="context">解码上下文对象,包含待解码的数据包及相关信息</param>
/// <returns>解码结果对象,包含解码后的数据和状态信息</returns>
public DecodeResult Decode(DecodeContext context)
{
var result = new DecodeResult();
result.PluginName = Name;
if (context == null || context.Packet == null || context.Packet.Data == null)
{
result.Success = false;
result.Summary = "No packet data";
return result;
}
try
{
string sessionKey = BuildSessionKey(context);
LengthPrefixedSessionState sessionState = SessionStates.GetOrCreate(sessionKey);
sessionState.AddPacketId(context.Packet.PacketId);
sessionState.FrameDecoder.Append(context.Packet.Data);
byte[] frame;
while (sessionState.FrameDecoder.TryReadFrame(out frame))
{
result.DecodeItems.Add(BuildDecodeItem(sessionState.GetJoinedPacketIds(), frame));
sessionState.ClearPacketIds();
}
result.Success = true;
if (result.DecodeItems.Count == 0)
{
result.Summary = string.Format("等待更多数据,当前已缓存 {0} 字节", sessionState.FrameDecoder.BufferedLength);
}
else if (sessionState.FrameDecoder.BufferedLength > 0)
{
result.Summary = string.Format("本次成功解出 {0} 个业务包,缓冲区剩余 {1} 字节", result.DecodeItems.Count, sessionState.FrameDecoder.BufferedLength);
}
else
{
result.Summary = string.Format("本次成功解出 {0} 个业务包", result.DecodeItems.Count);
}
}
catch (Exception ex)
{
result.Success = false;
result.Summary = "Decode failed: " + ex.Message;
}
return result;
}
/// <summary>
/// 是否能编码当前内容,编码器主程序会根据这个方法的返回值来决定是否使用当前插件进行编码。
/// </summary>
/// <param name="context">编码上下文对象,包含待编码的内容及相关信息</param>
/// <returns>如果可以编码返回true,否则返回false</returns>
public bool CanEncode(EncodeContext context)
{
if (context == null || context.Content == null)
return false;
return !string.IsNullOrWhiteSpace(context.Content);
}
/// <summary>
/// 编码方法,负责将输入的明文内容重新编码或加密成原始数据包格式,以便进行网络传输或存储。
/// </summary>
/// <param name="context">编码上下文对象,包含待编码的内容及相关信息</param>
/// <returns>编码结果对象,包含编码后的数据和状态信息</returns>
public EncodeResult Encode(EncodeContext context)
{
var res = new EncodeResult();
res.PluginName = Name;
res.Success = false;
if (context == null || context.Content == null)
return res;
try
{
byte[] body = Encoding.UTF8.GetBytes(context.Content);
byte[] encodedBody = XorBytes(body, 0x65);
res.EncodedData = BuildLengthPrefixedPacket(encodedBody);
res.Success = true;
}
catch
{
res.Success = false;
}
return res;
}
/// <summary>
/// 重置插件状态,清除所有会话状态数据,以确保插件能够在干净的状态下重新运行。
/// 比如会话中之前遗留的半包数据等都应该被清除掉,以避免对后续的解码过程造成干扰。
/// </summary>
public void Reset()
{
SessionStates.Clear();
}
/// <summary>
/// 加载外部配置,一般来说专用解码器不需要
/// </summary>
/// <param name="config"></param>
public void SetConfig(PluginConfig config)
{
//if (!string.IsNullOrEmpty(config.Id)) this.Id = config.Id;
//if (!string.IsNullOrEmpty(config.Name)) this.Name = config.Name;
//if (!string.IsNullOrEmpty(config.Author)) this.Author = config.Author;
//if (!string.IsNullOrEmpty(config.Description)) this.Description = config.Description;
//if (config.PrefixLength > 0)
//{
// PrefixLength = config.PrefixLength;
// PrefixEndian = config.PrefixEndian;
// LengthIncludesPrefix = config.LengthIncludesPrefix;
//}
}
/// <summary>
/// 插件销毁方法,在插件被卸载时调用,可以根据需要在这里进行一些资源的清理工作
/// </summary>
public void Dispose()
{
}
#endregion
#region 扩展的辅助方法(这块根据自己的需要决定)
/// <summary>
/// 是否存在未完成的半包数据
/// 或者之前的包已经解出但后续还有剩余数据未解出(可能是下一个包的部分数据)
/// 需要等待更多数据到来才能继续解码。
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static bool HasPendingBuffer(DecodeContext context)
{
string sessionKey = BuildSessionKey(context);
LengthPrefixedSessionState state = SessionStates.GetOrCreate(sessionKey);
return state.FrameDecoder.BufferedLength > 0;
}
/// <summary>
/// 创建会话标识
/// 通常可以根据协议类型、源IP/端口、目的IP/端口等信息构建一个唯一标识
/// 以便在SessionStates中存储和管理每个会话的状态。
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static string BuildSessionKey(DecodeContext context)
{
if (context == null || context.Packet == null)
return "default";
var packet = context.Packet;
return string.Format("{0}|{1}|{2}:{3}->{4}:{5}",
packet.Protocol ?? string.Empty,
packet.Direction ?? string.Empty,
packet.SrcIP ?? string.Empty,
packet.SrcPort,
packet.DstIP ?? string.Empty,
packet.DstPort);
}
/// <summary>
/// 创建解码项
/// 负责将解码后的原始数据包转换成结构化的业务数据对象
/// 以便在解码结果中返回
/// </summary>
/// <param name="sourcePacketId"></param>
/// <param name="frame"></param>
/// <returns></returns>
private static DecodeItem BuildDecodeItem(string sourcePacketId, byte[] frame)
{
int bodyLength = LengthPrefixedFrameDecoder.ReadLength(frame, PrefixLength, PrefixEndian);
byte[] encryptedBody = new byte[bodyLength];
Buffer.BlockCopy(frame, PrefixLength, encryptedBody, 0, bodyLength);
byte[] body = XorBytes(encryptedBody, 0x65);
string text = Encoding.UTF8.GetString(body, 0, body.Length);
return new DecodeItem()
{
SourcePacketId = sourcePacketId,
SourceData = frame,
Text = text,
OpCode = text.Substring(0, Math.Min(20, text.Length)) + (text.Length > 20 ? "..." : "")
};
}
/// <summary>
/// 组装最终数据包
/// </summary>
/// <param name="body"></param>
/// <returns></returns>
private static byte[] BuildLengthPrefixedPacket(byte[] body)
{
ushort bodyLength = (ushort)body.Length;
byte[] packet = new byte[body.Length + PrefixLength];
packet[0] = (byte)(bodyLength >> 8);
packet[1] = (byte)(bodyLength & 0xFF);
Buffer.BlockCopy(body, 0, packet, PrefixLength, body.Length);
return packet;
}
/// <summary>
/// 加解密算法示例:对数据进行异或处理,key为0x65
/// </summary>
/// <param name="data">要加解密的数据</param>
/// <param name="key">加解密的密钥</param>
/// <returns>加解密后的数据</returns>
private static byte[] XorBytes(byte[] data, byte key)
{
byte[] result = new byte[data.Length];
for (int i = 0; i < data.Length; i++)
{
result[i] = (byte)(data[i] ^ key);
}
return result;
}
#endregion
}
}