File: Frame.java

package info (click to toggle)
openjdk-11 11.0.4%2B11-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 757,028 kB
  • sloc: java: 5,016,041; xml: 1,191,974; cpp: 934,731; ansic: 555,697; sh: 24,299; objc: 12,703; python: 3,602; asm: 3,415; makefile: 2,772; awk: 351; sed: 172; perl: 114; jsp: 24; csh: 3
file content (497 lines) | stat: -rw-r--r-- 17,918 bytes parent folder | download | duplicates (13)
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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
/*
 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.nio.ByteBuffer;

/* Copied from jdk.internal.net.http.websocket.Frame */
final class Frame {

    final Opcode opcode;
    final ByteBuffer data;
    final boolean last;

    public Frame(Opcode opcode, ByteBuffer data, boolean last) {
        this.opcode = opcode;
        /* copy */
        this.data = ByteBuffer.allocate(data.remaining()).put(data.slice()).flip();
        this.last = last;
    }

    static final int MAX_HEADER_SIZE_BYTES = 2 + 8 + 4;
    static final int MAX_CONTROL_FRAME_PAYLOAD_SIZE = 125;

    enum Opcode {

        CONTINUATION   (0x0),
        TEXT           (0x1),
        BINARY         (0x2),
        NON_CONTROL_0x3(0x3),
        NON_CONTROL_0x4(0x4),
        NON_CONTROL_0x5(0x5),
        NON_CONTROL_0x6(0x6),
        NON_CONTROL_0x7(0x7),
        CLOSE          (0x8),
        PING           (0x9),
        PONG           (0xA),
        CONTROL_0xB    (0xB),
        CONTROL_0xC    (0xC),
        CONTROL_0xD    (0xD),
        CONTROL_0xE    (0xE),
        CONTROL_0xF    (0xF);

        private static final Opcode[] opcodes;

        static {
            Opcode[] values = values();
            opcodes = new Opcode[values.length];
            for (Opcode c : values) {
                opcodes[c.code] = c;
            }
        }

        private final byte code;

        Opcode(int code) {
            this.code = (byte) code;
        }

        boolean isControl() {
            return (code & 0x8) != 0;
        }

        static Opcode ofCode(int code) {
            return opcodes[code & 0xF];
        }
    }

    /*
     * A utility for masking frame payload data.
     */
    static final class Masker {

        // Exploiting ByteBuffer's ability to read/write multi-byte integers
        private final ByteBuffer acc = ByteBuffer.allocate(8);
        private final int[] maskBytes = new int[4];
        private int offset;
        private long maskLong;

        /*
         * Reads all remaining bytes from the given input buffer, masks them
         * with the supplied mask and writes the resulting bytes to the given
         * output buffer.
         *
         * The source and the destination buffers may be the same instance.
         */
        static void transferMasking(ByteBuffer src, ByteBuffer dst, int mask) {
            if (src.remaining() > dst.remaining()) {
                throw new IllegalArgumentException();
            }
            new Masker().mask(mask).transferMasking(src, dst);
        }

        /*
         * Clears this instance's state and sets the mask.
         *
         * The behaviour is as if the mask was set on a newly created instance.
         */
        Masker mask(int value) {
            acc.clear().putInt(value).putInt(value).flip();
            for (int i = 0; i < maskBytes.length; i++) {
                maskBytes[i] = acc.get(i);
            }
            offset = 0;
            maskLong = acc.getLong(0);
            return this;
        }

        /*
         * Reads as many remaining bytes as possible from the given input
         * buffer, masks them with the previously set mask and writes the
         * resulting bytes to the given output buffer.
         *
         * The source and the destination buffers may be the same instance. If
         * the mask hasn't been previously set it is assumed to be 0.
         */
        Masker transferMasking(ByteBuffer src, ByteBuffer dst) {
            begin(src, dst);
            loop(src, dst);
            end(src, dst);
            return this;
        }

        /*
         * Applies up to 3 remaining from the previous pass bytes of the mask.
         */
        private void begin(ByteBuffer src, ByteBuffer dst) {
            if (offset == 0) { // No partially applied mask from the previous invocation
                return;
            }
            int i = src.position(), j = dst.position();
            final int srcLim = src.limit(), dstLim = dst.limit();
            for (; offset < 4 && i < srcLim && j < dstLim; i++, j++, offset++)
            {
                dst.put(j, (byte) (src.get(i) ^ maskBytes[offset]));
            }
            offset &= 3; // Will become 0 if the mask has been fully applied
            src.position(i);
            dst.position(j);
        }

        /*
         * Gallops one long (mask + mask) at a time.
         */
        private void loop(ByteBuffer src, ByteBuffer dst) {
            int i = src.position();
            int j = dst.position();
            final int srcLongLim = src.limit() - 7, dstLongLim = dst.limit() - 7;
            for (; i < srcLongLim && j < dstLongLim; i += 8, j += 8) {
                dst.putLong(j, src.getLong(i) ^ maskLong);
            }
            if (i > src.limit()) {
                src.position(i - 8);
            } else {
                src.position(i);
            }
            if (j > dst.limit()) {
                dst.position(j - 8);
            } else {
                dst.position(j);
            }
        }

        /*
         * Applies up to 7 remaining from the "galloping" phase bytes of the
         * mask.
         */
        private void end(ByteBuffer src, ByteBuffer dst) {
            assert Math.min(src.remaining(), dst.remaining()) < 8;
            final int srcLim = src.limit(), dstLim = dst.limit();
            int i = src.position(), j = dst.position();
            for (; i < srcLim && j < dstLim;
                 i++, j++, offset = (offset + 1) & 3) // offset cycles through 0..3
            {
                dst.put(j, (byte) (src.get(i) ^ maskBytes[offset]));
            }
            src.position(i);
            dst.position(j);
        }
    }

    /*
     * A builder-style writer of frame headers.
     *
     * The writer does not enforce any protocol-level rules, it simply writes a
     * header structure to the given buffer. The order of calls to intermediate
     * methods is NOT significant.
     */
    static final class HeaderWriter {

        private char firstChar;
        private long payloadLen;
        private int maskingKey;
        private boolean mask;

        HeaderWriter fin(boolean value) {
            if (value) {
                firstChar |=  0b10000000_00000000;
            } else {
                firstChar &= ~0b10000000_00000000;
            }
            return this;
        }

        HeaderWriter rsv1(boolean value) {
            if (value) {
                firstChar |=  0b01000000_00000000;
            } else {
                firstChar &= ~0b01000000_00000000;
            }
            return this;
        }

        HeaderWriter rsv2(boolean value) {
            if (value) {
                firstChar |=  0b00100000_00000000;
            } else {
                firstChar &= ~0b00100000_00000000;
            }
            return this;
        }

        HeaderWriter rsv3(boolean value) {
            if (value) {
                firstChar |=  0b00010000_00000000;
            } else {
                firstChar &= ~0b00010000_00000000;
            }
            return this;
        }

        HeaderWriter opcode(Opcode value) {
            firstChar = (char) ((firstChar & 0xF0FF) | (value.code << 8));
            return this;
        }

        HeaderWriter payloadLen(long value) {
            if (value < 0) {
                throw new IllegalArgumentException("Negative: " + value);
            }
            payloadLen = value;
            firstChar &= 0b11111111_10000000; // Clear previous payload length leftovers
            if (payloadLen < 126) {
                firstChar |= payloadLen;
            } else if (payloadLen < 65536) {
                firstChar |= 126;
            } else {
                firstChar |= 127;
            }
            return this;
        }

        HeaderWriter mask(int value) {
            firstChar |= 0b00000000_10000000;
            maskingKey = value;
            mask = true;
            return this;
        }

        HeaderWriter noMask() {
            firstChar &= ~0b00000000_10000000;
            mask = false;
            return this;
        }

        /*
         * Writes the header to the given buffer.
         *
         * The buffer must have at least MAX_HEADER_SIZE_BYTES remaining. The
         * buffer's position is incremented by the number of bytes written.
         */
        void write(ByteBuffer buffer) {
            buffer.putChar(firstChar);
            if (payloadLen >= 126) {
                if (payloadLen < 65536) {
                    buffer.putChar((char) payloadLen);
                } else {
                    buffer.putLong(payloadLen);
                }
            }
            if (mask) {
                buffer.putInt(maskingKey);
            }
        }
    }

    /*
     * A consumer of frame parts.
     *
     * Frame.Reader invokes the consumer's methods in the following order:
     *
     *     fin rsv1 rsv2 rsv3 opcode mask payloadLength maskingKey? payloadData+ endFrame
     */
    interface Consumer {

        void fin(boolean value);

        void rsv1(boolean value);

        void rsv2(boolean value);

        void rsv3(boolean value);

        void opcode(Opcode value);

        void mask(boolean value);

        void payloadLen(long value);

        void maskingKey(int value);

        /*
         * Called by the Frame.Reader when a part of the (or a complete) payload
         * is ready to be consumed.
         *
         * The sum of numbers of bytes consumed in each invocation of this
         * method corresponding to the given frame WILL be equal to
         * 'payloadLen', reported to `void payloadLen(long value)` before that.
         *
         * In particular, if `payloadLen` is 0, then there WILL be a single
         * invocation to this method.
         *
         * No unmasking is done.
         */
        void payloadData(ByteBuffer data);

        void endFrame();
    }

    /*
     * A Reader of frames.
     *
     * No protocol-level rules are checked.
     */
    static final class Reader {

        private static final int AWAITING_FIRST_BYTE  =  1;
        private static final int AWAITING_SECOND_BYTE =  2;
        private static final int READING_16_LENGTH    =  4;
        private static final int READING_64_LENGTH    =  8;
        private static final int READING_MASK         = 16;
        private static final int READING_PAYLOAD      = 32;

        // Exploiting ByteBuffer's ability to read multi-byte integers
        private final ByteBuffer accumulator = ByteBuffer.allocate(8);
        private int state = AWAITING_FIRST_BYTE;
        private boolean mask;
        private long remainingPayloadLength;

        /*
         * Reads at most one frame from the given buffer invoking the consumer's
         * methods corresponding to the frame parts found.
         *
         * As much of the frame's payload, if any, is read. The buffer's
         * position is updated to reflect the number of bytes read.
         *
         * Throws FailWebSocketException if detects the frame is malformed.
         */
        void readFrame(ByteBuffer input, Consumer consumer) {
            loop:
            while (true) {
                byte b;
                switch (state) {
                    case AWAITING_FIRST_BYTE:
                        if (!input.hasRemaining()) {
                            break loop;
                        }
                        b = input.get();
                        consumer.fin( (b & 0b10000000) != 0);
                        consumer.rsv1((b & 0b01000000) != 0);
                        consumer.rsv2((b & 0b00100000) != 0);
                        consumer.rsv3((b & 0b00010000) != 0);
                        consumer.opcode(Opcode.ofCode(b));
                        state = AWAITING_SECOND_BYTE;
                        continue loop;
                    case AWAITING_SECOND_BYTE:
                        if (!input.hasRemaining()) {
                            break loop;
                        }
                        b = input.get();
                        consumer.mask(mask = (b & 0b10000000) != 0);
                        byte p1 = (byte) (b & 0b01111111);
                        if (p1 < 126) {
                            assert p1 >= 0 : p1;
                            consumer.payloadLen(remainingPayloadLength = p1);
                            state = mask ? READING_MASK : READING_PAYLOAD;
                        } else if (p1 < 127) {
                            state = READING_16_LENGTH;
                        } else {
                            state = READING_64_LENGTH;
                        }
                        continue loop;
                    case READING_16_LENGTH:
                        if (!input.hasRemaining()) {
                            break loop;
                        }
                        b = input.get();
                        if (accumulator.put(b).position() < 2) {
                            continue loop;
                        }
                        remainingPayloadLength = accumulator.flip().getChar();
                        if (remainingPayloadLength < 126) {
                            throw notMinimalEncoding(remainingPayloadLength);
                        }
                        consumer.payloadLen(remainingPayloadLength);
                        accumulator.clear();
                        state = mask ? READING_MASK : READING_PAYLOAD;
                        continue loop;
                    case READING_64_LENGTH:
                        if (!input.hasRemaining()) {
                            break loop;
                        }
                        b = input.get();
                        if (accumulator.put(b).position() < 8) {
                            continue loop;
                        }
                        remainingPayloadLength = accumulator.flip().getLong();
                        if (remainingPayloadLength < 0) {
                            throw negativePayload(remainingPayloadLength);
                        } else if (remainingPayloadLength < 65536) {
                            throw notMinimalEncoding(remainingPayloadLength);
                        }
                        consumer.payloadLen(remainingPayloadLength);
                        accumulator.clear();
                        state = mask ? READING_MASK : READING_PAYLOAD;
                        continue loop;
                    case READING_MASK:
                        if (!input.hasRemaining()) {
                            break loop;
                        }
                        b = input.get();
                        if (accumulator.put(b).position() != 4) {
                            continue loop;
                        }
                        consumer.maskingKey(accumulator.flip().getInt());
                        accumulator.clear();
                        state = READING_PAYLOAD;
                        continue loop;
                    case READING_PAYLOAD:
                        // This state does not require any bytes to be available
                        // in the input buffer in order to proceed
                        int deliverable = (int) Math.min(remainingPayloadLength,
                                                         input.remaining());
                        int oldLimit = input.limit();
                        input.limit(input.position() + deliverable);
                        if (deliverable != 0 || remainingPayloadLength == 0) {
                            consumer.payloadData(input);
                        }
                        int consumed = deliverable - input.remaining();
                        if (consumed < 0) {
                            // Consumer cannot consume more than there was available
                            throw new InternalError();
                        }
                        input.limit(oldLimit);
                        remainingPayloadLength -= consumed;
                        if (remainingPayloadLength == 0) {
                            consumer.endFrame();
                            state = AWAITING_FIRST_BYTE;
                        }
                        break loop;
                    default:
                        throw new InternalError(String.valueOf(state));
                }
            }
        }

        private static IllegalArgumentException negativePayload(long payloadLength)
        {
            return new IllegalArgumentException("Negative payload length: "
                                                        + payloadLength);
        }

        private static IllegalArgumentException notMinimalEncoding(long payloadLength)
        {
            return new IllegalArgumentException("Not minimally-encoded payload length:"
                                                      + payloadLength);
        }
    }
}