1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec;
17
18 import io.netty5.buffer.api.Buffer;
19 import io.netty5.channel.ChannelHandlerContext;
20
21 import static io.netty5.util.internal.ObjectUtil.checkPositive;
22 import static java.util.Objects.requireNonNull;
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 public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
60
61 private final Buffer[] delimiters;
62 private final int maxFrameLength;
63 private final boolean stripDelimiter;
64 private final boolean failFast;
65 private boolean discardingTooLongFrame;
66 private int tooLongFrameLength;
67
68 private final LineBasedFrameDecoder lineBasedDecoder;
69
70
71
72
73
74
75
76
77
78 public DelimiterBasedFrameDecoder(int maxFrameLength, Buffer... delimiters) {
79 this(maxFrameLength, true, delimiters);
80 }
81
82
83
84
85
86
87
88
89
90
91
92 public DelimiterBasedFrameDecoder(
93 int maxFrameLength, boolean stripDelimiter, Buffer... delimiters) {
94 this(maxFrameLength, stripDelimiter, true, delimiters);
95 }
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 public DelimiterBasedFrameDecoder(
115 int maxFrameLength, boolean stripDelimiter, boolean failFast, Buffer... delimiters) {
116 validateMaxFrameLength(maxFrameLength);
117 requireNonNull(delimiters, "delimiters");
118 if (delimiters.length == 0) {
119 throw new IllegalArgumentException("empty delimiters");
120 }
121
122 if (isLineBased(delimiters) && !isSubclass()) {
123 lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
124 this.delimiters = null;
125 closeDelimiters(delimiters);
126 } else {
127 this.delimiters = new Buffer[delimiters.length];
128 RuntimeException re = null;
129 try {
130 for (int i = 0; i < delimiters.length; i++) {
131 Buffer d = delimiters[i];
132 validateDelimiter(d);
133 this.delimiters[i] = d.copy(true);
134 }
135 } catch (IllegalArgumentException e) {
136 re = e;
137 try {
138 closeDelimiters(this.delimiters);
139 } catch (RuntimeException e1) {
140 re.addSuppressed(e1);
141 }
142 } finally {
143 try {
144 closeDelimiters(delimiters);
145 } catch (RuntimeException e) {
146 if (re == null) {
147 re = e;
148 } else {
149 re.addSuppressed(e);
150 }
151 }
152 }
153 if (re != null) {
154 throw re;
155 }
156 lineBasedDecoder = null;
157 }
158 this.maxFrameLength = maxFrameLength;
159 this.stripDelimiter = stripDelimiter;
160 this.failFast = failFast;
161 }
162
163
164 private static boolean isLineBased(final Buffer[] delimiters) {
165 if (delimiters.length != 2) {
166 return false;
167 }
168 Buffer a = delimiters[0];
169 Buffer b = delimiters[1];
170 if (a.capacity() < b.capacity()) {
171 a = delimiters[1];
172 b = delimiters[0];
173 }
174 return a.capacity() == 2 && b.capacity() == 1
175 && a.getByte(0) == '\r' && a.getByte(1) == '\n'
176 && b.getByte(0) == '\n';
177 }
178
179
180
181
182 private boolean isSubclass() {
183 return getClass() != DelimiterBasedFrameDecoder.class;
184 }
185
186 @Override
187 protected final void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {
188 Object decoded = decode0(ctx, in);
189 if (decoded != null) {
190 ctx.fireChannelRead(decoded);
191 }
192 }
193
194
195
196
197
198
199
200
201
202 protected Object decode0(ChannelHandlerContext ctx, Buffer buffer) {
203 if (lineBasedDecoder != null) {
204 return lineBasedDecoder.decode0(ctx, buffer);
205 }
206
207 int minFrameLength = Integer.MAX_VALUE;
208 Buffer minDelim = null;
209 for (Buffer delim: delimiters) {
210 int frameLength = indexOf(buffer, delim);
211 if (frameLength >= 0 && frameLength < minFrameLength) {
212 minFrameLength = frameLength;
213 minDelim = delim;
214 }
215 }
216
217 if (minDelim != null) {
218 int minDelimLength = minDelim.capacity();
219 Buffer frame;
220
221 if (discardingTooLongFrame) {
222
223
224 discardingTooLongFrame = false;
225 buffer.skipReadableBytes(minFrameLength + minDelimLength);
226
227 int tooLongFrameLength = this.tooLongFrameLength;
228 this.tooLongFrameLength = 0;
229 if (!failFast) {
230 fail(tooLongFrameLength);
231 }
232 return null;
233 }
234
235 if (minFrameLength > maxFrameLength) {
236
237 buffer.skipReadableBytes(minFrameLength + minDelimLength);
238 fail(minFrameLength);
239 return null;
240 }
241
242 if (stripDelimiter) {
243 frame = buffer.readSplit(minFrameLength);
244 buffer.skipReadableBytes(minDelimLength);
245 } else {
246 frame = buffer.readSplit(minFrameLength + minDelimLength);
247 }
248
249 return frame;
250 } else {
251 if (!discardingTooLongFrame) {
252 if (buffer.readableBytes() > maxFrameLength) {
253
254 tooLongFrameLength = buffer.readableBytes();
255 buffer.skipReadableBytes(buffer.readableBytes());
256 discardingTooLongFrame = true;
257 if (failFast) {
258 fail(tooLongFrameLength);
259 }
260 }
261 } else {
262
263 tooLongFrameLength += buffer.readableBytes();
264 buffer.skipReadableBytes(buffer.readableBytes());
265 }
266 return null;
267 }
268 }
269
270 @Override
271 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
272 if (delimiters != null) {
273 closeDelimiters(delimiters);
274 }
275 super.handlerRemoved0(ctx);
276 }
277
278 private static void closeDelimiters(Buffer[] delimiters) {
279 RuntimeException re = null;
280 for (Buffer delimiter : delimiters) {
281 try {
282 if (delimiter != null) {
283 delimiter.close();
284 }
285 } catch (RuntimeException e) {
286 if (re == null) {
287 re = e;
288 } else {
289 re.addSuppressed(e);
290 }
291 }
292 }
293 if (re != null) {
294 throw re;
295 }
296 }
297
298 private void fail(long frameLength) {
299 if (frameLength > 0) {
300 throw new TooLongFrameException(
301 "frame length exceeds " + maxFrameLength +
302 ": " + frameLength + " - discarded");
303 } else {
304 throw new TooLongFrameException(
305 "frame length exceeds " + maxFrameLength +
306 " - discarding");
307 }
308 }
309
310
311
312
313
314
315 private static int indexOf(Buffer haystack, Buffer needle) {
316 for (int i = haystack.readerOffset(); i < haystack.writerOffset(); i ++) {
317 int haystackIndex = i;
318 int needleIndex;
319 for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
320 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
321 break;
322 } else {
323 haystackIndex ++;
324 if (haystackIndex == haystack.writerOffset() &&
325 needleIndex != needle.capacity() - 1) {
326 return -1;
327 }
328 }
329 }
330
331 if (needleIndex == needle.capacity()) {
332
333 return i - haystack.readerOffset();
334 }
335 }
336 return -1;
337 }
338
339 private static void validateDelimiter(Buffer delimiter) {
340 requireNonNull(delimiter, "delimiter");
341 if (delimiter.readableBytes() == 0) {
342 throw new IllegalArgumentException("empty delimiter");
343 }
344 }
345
346 private static void validateMaxFrameLength(int maxFrameLength) {
347 checkPositive(maxFrameLength, "maxFrameLength");
348 }
349 }