| // |
| // ======================================================================== |
| // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.websocket.common; |
| |
| import java.nio.ByteBuffer; |
| import java.util.List; |
| |
| import org.eclipse.jetty.io.ByteBufferPool; |
| import org.eclipse.jetty.util.BufferUtil; |
| import org.eclipse.jetty.websocket.api.ProtocolException; |
| import org.eclipse.jetty.websocket.api.WebSocketBehavior; |
| import org.eclipse.jetty.websocket.api.WebSocketPolicy; |
| import org.eclipse.jetty.websocket.api.extensions.Extension; |
| import org.eclipse.jetty.websocket.api.extensions.Frame; |
| |
| /** |
| * Generating a frame in WebSocket land. |
| * |
| * <pre> |
| * 0 1 2 3 |
| * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| * +-+-+-+-+-------+-+-------------+-------------------------------+ |
| * |F|R|R|R| opcode|M| Payload len | Extended payload length | |
| * |I|S|S|S| (4) |A| (7) | (16/64) | |
| * |N|V|V|V| |S| | (if payload len==126/127) | |
| * | |1|2|3| |K| | | |
| * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + |
| * | Extended payload length continued, if payload len == 127 | |
| * + - - - - - - - - - - - - - - - +-------------------------------+ |
| * | |Masking-key, if MASK set to 1 | |
| * +-------------------------------+-------------------------------+ |
| * | Masking-key (continued) | Payload Data | |
| * +-------------------------------- - - - - - - - - - - - - - - - + |
| * : Payload Data continued ... : |
| * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
| * | Payload Data continued ... | |
| * +---------------------------------------------------------------+ |
| * </pre> |
| */ |
| public class Generator |
| { |
| /** |
| * The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key. |
| */ |
| public static final int MAX_HEADER_LENGTH = 28; |
| |
| private final WebSocketBehavior behavior; |
| private final ByteBufferPool bufferPool; |
| private final boolean validating; |
| private final boolean readOnly; |
| |
| /** |
| * Are any flags in use |
| * <p> |
| * |
| * <pre> |
| * 0100_0000 (0x40) = rsv1 |
| * 0010_0000 (0x20) = rsv2 |
| * 0001_0000 (0x10) = rsv3 |
| * </pre> |
| */ |
| private byte flagsInUse = 0x00; |
| |
| /** |
| * Construct Generator with provided policy and bufferPool |
| * |
| * @param policy |
| * the policy to use |
| * @param bufferPool |
| * the buffer pool to use |
| */ |
| public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool) |
| { |
| this(policy,bufferPool,true,false); |
| } |
| |
| /** |
| * Construct Generator with provided policy and bufferPool |
| * |
| * @param policy |
| * the policy to use |
| * @param bufferPool |
| * the buffer pool to use |
| * @param validating |
| * true to enable RFC frame validation |
| */ |
| public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating) |
| { |
| this(policy,bufferPool,validating,false); |
| } |
| |
| /** |
| * Construct Generator with provided policy and bufferPool |
| * |
| * @param policy |
| * the policy to use |
| * @param bufferPool |
| * the buffer pool to use |
| * @param validating |
| * true to enable RFC frame validation |
| * @param readOnly |
| * true if generator is to treat frames as read-only and not modify them. Useful for debugging purposes, but not generally for runtime use. |
| */ |
| public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating, boolean readOnly) |
| { |
| this.behavior = policy.getBehavior(); |
| this.bufferPool = bufferPool; |
| this.validating = validating; |
| this.readOnly = readOnly; |
| } |
| |
| public void assertFrameValid(Frame frame) |
| { |
| if (!validating) |
| { |
| return; |
| } |
| |
| /* |
| * RFC 6455 Section 5.2 |
| * |
| * MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated |
| * extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_. |
| */ |
| if (frame.isRsv1() && !isRsv1InUse()) |
| { |
| throw new ProtocolException("RSV1 not allowed to be set"); |
| } |
| |
| if (frame.isRsv2() && !isRsv2InUse()) |
| { |
| throw new ProtocolException("RSV2 not allowed to be set"); |
| } |
| |
| if (frame.isRsv3() && !isRsv3InUse()) |
| { |
| throw new ProtocolException("RSV3 not allowed to be set"); |
| } |
| |
| if (OpCode.isControlFrame(frame.getOpCode())) |
| { |
| /* |
| * RFC 6455 Section 5.5 |
| * |
| * All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented. |
| */ |
| if (frame.getPayloadLength() > 125) |
| { |
| throw new ProtocolException("Invalid control frame payload length"); |
| } |
| |
| if (!frame.isFin()) |
| { |
| throw new ProtocolException("Control Frames must be FIN=true"); |
| } |
| |
| /* |
| * RFC 6455 Section 5.5.1 |
| * |
| * close frame payload is specially formatted which is checked in CloseInfo |
| */ |
| if (frame.getOpCode() == OpCode.CLOSE) |
| { |
| |
| ByteBuffer payload = frame.getPayload(); |
| if (payload != null) |
| { |
| new CloseInfo(payload,true); |
| } |
| } |
| } |
| } |
| |
| public void configureFromExtensions(List<? extends Extension> exts) |
| { |
| // default |
| flagsInUse = 0x00; |
| |
| // configure from list of extensions in use |
| for (Extension ext : exts) |
| { |
| if (ext.isRsv1User()) |
| { |
| flagsInUse = (byte)(flagsInUse | 0x40); |
| } |
| if (ext.isRsv2User()) |
| { |
| flagsInUse = (byte)(flagsInUse | 0x20); |
| } |
| if (ext.isRsv3User()) |
| { |
| flagsInUse = (byte)(flagsInUse | 0x10); |
| } |
| } |
| } |
| |
| public ByteBuffer generateHeaderBytes(Frame frame) |
| { |
| ByteBuffer buffer = bufferPool.acquire(MAX_HEADER_LENGTH,true); |
| generateHeaderBytes(frame,buffer); |
| return buffer; |
| } |
| |
| public void generateHeaderBytes(Frame frame, ByteBuffer buffer) |
| { |
| int p = BufferUtil.flipToFill(buffer); |
| |
| // we need a framing header |
| assertFrameValid(frame); |
| |
| /* |
| * start the generation process |
| */ |
| byte b = 0x00; |
| |
| // Setup fin thru opcode |
| if (frame.isFin()) |
| { |
| b |= 0x80; // 1000_0000 |
| } |
| |
| // Set the flags |
| if (frame.isRsv1()) |
| { |
| b |= 0x40; // 0100_0000 |
| } |
| if (frame.isRsv2()) |
| { |
| b |= 0x20; // 0010_0000 |
| } |
| if (frame.isRsv3()) |
| { |
| b |= 0x10; // 0001_0000 |
| } |
| |
| // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons |
| byte opcode = frame.getOpCode(); |
| |
| if (frame.getOpCode() == OpCode.CONTINUATION) |
| { |
| // Continuations are not the same OPCODE |
| opcode = OpCode.CONTINUATION; |
| } |
| |
| b |= opcode & 0x0F; |
| |
| buffer.put(b); |
| |
| // is masked |
| b = (frame.isMasked()?(byte)0x80:(byte)0x00); |
| |
| // payload lengths |
| int payloadLength = frame.getPayloadLength(); |
| |
| /* |
| * if length is over 65535 then its a 7 + 64 bit length |
| */ |
| if (payloadLength > 0xFF_FF) |
| { |
| // we have a 64 bit length |
| b |= 0x7F; |
| buffer.put(b); // indicate 8 byte length |
| buffer.put((byte)0); // |
| buffer.put((byte)0); // anything over an |
| buffer.put((byte)0); // int is just |
| buffer.put((byte)0); // insane! |
| buffer.put((byte)((payloadLength >> 24) & 0xFF)); |
| buffer.put((byte)((payloadLength >> 16) & 0xFF)); |
| buffer.put((byte)((payloadLength >> 8) & 0xFF)); |
| buffer.put((byte)(payloadLength & 0xFF)); |
| } |
| /* |
| * if payload is greater that 126 we have a 7 + 16 bit length |
| */ |
| else if (payloadLength >= 0x7E) |
| { |
| b |= 0x7E; |
| buffer.put(b); // indicate 2 byte length |
| buffer.put((byte)(payloadLength >> 8)); |
| buffer.put((byte)(payloadLength & 0xFF)); |
| } |
| /* |
| * we have a 7 bit length |
| */ |
| else |
| { |
| b |= (payloadLength & 0x7F); |
| buffer.put(b); |
| } |
| |
| // masking key |
| if (frame.isMasked() && !readOnly) |
| { |
| byte[] mask = frame.getMask(); |
| buffer.put(mask); |
| int maskInt = 0; |
| for (byte maskByte : mask) |
| maskInt = (maskInt << 8) + (maskByte & 0xFF); |
| |
| // perform data masking here |
| ByteBuffer payload = frame.getPayload(); |
| if ((payload != null) && (payload.remaining() > 0)) |
| { |
| int maskOffset = 0; |
| int start = payload.position(); |
| int end = payload.limit(); |
| int remaining; |
| while ((remaining = end - start) > 0) |
| { |
| if (remaining >= 4) |
| { |
| payload.putInt(start,payload.getInt(start) ^ maskInt); |
| start += 4; |
| } |
| else |
| { |
| payload.put(start,(byte)(payload.get(start) ^ mask[maskOffset & 3])); |
| ++start; |
| ++maskOffset; |
| } |
| } |
| } |
| } |
| |
| BufferUtil.flipToFlush(buffer,p); |
| } |
| |
| /** |
| * Generate the whole frame (header + payload copy) into a single ByteBuffer. |
| * <p> |
| * Note: This is slow, moves lots of memory around. Only use this if you must (such as in unit testing). |
| * |
| * @param frame |
| * the frame to generate |
| * @param buf |
| * the buffer to output the generated frame to |
| */ |
| public void generateWholeFrame(Frame frame, ByteBuffer buf) |
| { |
| buf.put(generateHeaderBytes(frame)); |
| if (frame.hasPayload()) |
| { |
| if (readOnly) |
| { |
| buf.put(frame.getPayload().slice()); |
| } |
| else |
| { |
| buf.put(frame.getPayload()); |
| } |
| } |
| } |
| |
| public ByteBufferPool getBufferPool() |
| { |
| return bufferPool; |
| } |
| |
| public void setRsv1InUse(boolean rsv1InUse) |
| { |
| if (readOnly) |
| { |
| throw new RuntimeException("Not allowed to modify read-only frame"); |
| } |
| flagsInUse = (byte)((flagsInUse & 0xBF) | (rsv1InUse?0x40:0x00)); |
| } |
| |
| public void setRsv2InUse(boolean rsv2InUse) |
| { |
| if (readOnly) |
| { |
| throw new RuntimeException("Not allowed to modify read-only frame"); |
| } |
| flagsInUse = (byte)((flagsInUse & 0xDF) | (rsv2InUse?0x20:0x00)); |
| } |
| |
| public void setRsv3InUse(boolean rsv3InUse) |
| { |
| if (readOnly) |
| { |
| throw new RuntimeException("Not allowed to modify read-only frame"); |
| } |
| flagsInUse = (byte)((flagsInUse & 0xEF) | (rsv3InUse?0x10:0x00)); |
| } |
| |
| public boolean isRsv1InUse() |
| { |
| return (flagsInUse & 0x40) != 0; |
| } |
| |
| public boolean isRsv2InUse() |
| { |
| return (flagsInUse & 0x20) != 0; |
| } |
| |
| public boolean isRsv3InUse() |
| { |
| return (flagsInUse & 0x10) != 0; |
| } |
| |
| @Override |
| public String toString() |
| { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("Generator["); |
| builder.append(behavior); |
| if (validating) |
| { |
| builder.append(",validating"); |
| } |
| if (isRsv1InUse()) |
| { |
| builder.append(",+rsv1"); |
| } |
| if (isRsv2InUse()) |
| { |
| builder.append(",+rsv2"); |
| } |
| if (isRsv3InUse()) |
| { |
| builder.append(",+rsv3"); |
| } |
| builder.append("]"); |
| return builder.toString(); |
| } |
| } |