// // ======================================================================== // Copyright (c) 1995-2017 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(); } }