blob: 89074c925d35aded6b12058e7a7511740ec6904c [file] [log] [blame]
//
// ========================================================================
// 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();
}
}