| /* |
| * Copyright (c) 2015, 2016, 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * 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. |
| */ |
| |
| package jdk.incubator.http.internal.websocket; |
| |
| import java.nio.ByteBuffer; |
| |
| import static jdk.incubator.http.internal.common.Utils.dump; |
| import static jdk.incubator.http.internal.websocket.Frame.Opcode.ofCode; |
| |
| /* |
| * A collection of utilities for reading, writing, and masking frames. |
| */ |
| final class Frame { |
| |
| private Frame() { } |
| |
| static final int MAX_HEADER_SIZE_BYTES = 2 + 8 + 4; |
| |
| 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(dump(src, dst)); |
| } |
| 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(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 FailWebSocketException negativePayload(long payloadLength) |
| { |
| return new FailWebSocketException( |
| "Negative payload length: " + payloadLength); |
| } |
| |
| private static FailWebSocketException notMinimalEncoding(long payloadLength) |
| { |
| return new FailWebSocketException( |
| "Not minimally-encoded payload length:" + payloadLength); |
| } |
| } |
| } |