org.eclipse.jetty.websocket.common.extensions.mux.MuxParser Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2013 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.extensions.mux;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel;
import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxFlowControl;
import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxNewChannelSlot;
public class MuxParser
{
public static interface Listener
{
public void onMuxAddChannelRequest(MuxAddChannelRequest request);
public void onMuxAddChannelResponse(MuxAddChannelResponse response);
public void onMuxDropChannel(MuxDropChannel drop);
public void onMuxedFrame(MuxedFrame frame);
public void onMuxException(MuxException e);
public void onMuxFlowControl(MuxFlowControl flow);
public void onMuxNewChannelSlot(MuxNewChannelSlot slot);
}
private final static Logger LOG = Log.getLogger(MuxParser.class);
private MuxedFrame muxframe = new MuxedFrame();
private MuxParser.Listener events;
private long channelId;
public MuxParser.Listener getEvents()
{
return events;
}
/**
* Parse the raw {@link WebSocketFrame} payload data for various Mux frames.
*
* @param frame
* the WebSocketFrame to parse for mux payload
*/
public synchronized void parse(Frame frame)
{
if (events == null)
{
throw new RuntimeException("No " + MuxParser.Listener.class + " specified");
}
if (!frame.hasPayload())
{
LOG.debug("No payload data, skipping");
return; // nothing to parse
}
if (frame.getType().getOpCode() != OpCode.BINARY)
{
LOG.debug("Not a binary opcode (base frame), skipping");
return; // not a binary opcode
}
LOG.debug("Parsing Mux Payload of {}",frame);
try
{
ByteBuffer buffer = frame.getPayload().slice();
if (buffer.remaining() <= 0)
{
return;
}
if (frame.isContinuation())
{
muxframe.reset();
muxframe.setFin(frame.isFin());
muxframe.setFin(frame.isRsv1());
muxframe.setFin(frame.isRsv2());
muxframe.setFin(frame.isRsv3());
muxframe.setContinuation(true);
parseDataFramePayload(buffer);
}
else
{
// new frame
channelId = readChannelId(buffer);
if (channelId == 0)
{
parseControlBlocks(buffer);
}
else
{
parseDataFrame(buffer);
}
}
}
catch (MuxException e)
{
events.onMuxException(e);
}
catch (Throwable t)
{
events.onMuxException(new MuxException(t));
}
}
private void parseControlBlocks(ByteBuffer buffer)
{
// process the remaining buffer here.
while (buffer.remaining() > 0)
{
byte b = buffer.get();
byte opc = (byte)((byte)(b >> 5) & 0xFF);
b = (byte)(b & 0x1F);
try {
switch (opc)
{
case MuxOp.ADD_CHANNEL_REQUEST:
{
MuxAddChannelRequest op = new MuxAddChannelRequest();
op.setRsv((byte)((b & 0x1C) >> 2));
op.setEncoding((byte)(b & 0x03));
op.setChannelId(readChannelId(buffer));
long handshakeSize = read139EncodedSize(buffer);
op.setHandshake(readBlock(buffer,handshakeSize));
events.onMuxAddChannelRequest(op);
break;
}
case MuxOp.ADD_CHANNEL_RESPONSE:
{
MuxAddChannelResponse op = new MuxAddChannelResponse();
op.setFailed((b & 0x10) != 0);
op.setRsv((byte)((byte)(b & 0x0C) >> 2));
op.setEncoding((byte)(b & 0x03));
op.setChannelId(readChannelId(buffer));
long handshakeSize = read139EncodedSize(buffer);
op.setHandshake(readBlock(buffer,handshakeSize));
events.onMuxAddChannelResponse(op);
break;
}
case MuxOp.DROP_CHANNEL:
{
int rsv = (b & 0x1F);
long channelId = readChannelId(buffer);
long reasonSize = read139EncodedSize(buffer);
ByteBuffer reasonBuf = readBlock(buffer,reasonSize);
MuxDropChannel op = MuxDropChannel.parse(channelId,reasonBuf);
op.setRsv(rsv);
events.onMuxDropChannel(op);
break;
}
case MuxOp.FLOW_CONTROL:
{
MuxFlowControl op = new MuxFlowControl();
op.setRsv((byte)(b & 0x1F));
op.setChannelId(readChannelId(buffer));
op.setSendQuotaSize(read139EncodedSize(buffer));
events.onMuxFlowControl(op);
break;
}
case MuxOp.NEW_CHANNEL_SLOT:
{
MuxNewChannelSlot op = new MuxNewChannelSlot();
op.setRsv((byte)((b & 0x1E) >> 1));
op.setFallback((b & 0x01) != 0);
op.setNumberOfSlots(read139EncodedSize(buffer));
op.setInitialSendQuota(read139EncodedSize(buffer));
events.onMuxNewChannelSlot(op);
break;
}
default:
{
String err = String.format("Unknown Mux Control Code OPC [0x%X]",opc);
throw new MuxException(err);
}
}
}
catch (Throwable t)
{
LOG.warn(t);
throw new MuxException(t);
}
}
}
private void parseDataFrame(ByteBuffer buffer)
{
byte b = buffer.get();
boolean fin = ((b & 0x80) != 0);
boolean rsv1 = ((b & 0x40) != 0);
boolean rsv2 = ((b & 0x20) != 0);
boolean rsv3 = ((b & 0x10) != 0);
byte opcode = (byte)(b & 0x0F);
if (opcode == OpCode.CONTINUATION)
{
muxframe.setContinuation(true);
}
else
{
muxframe.reset();
muxframe.setOpCode(opcode);
}
muxframe.setChannelId(channelId);
muxframe.setFin(fin);
muxframe.setRsv1(rsv1);
muxframe.setRsv2(rsv2);
muxframe.setRsv3(rsv3);
parseDataFramePayload(buffer);
}
private void parseDataFramePayload(ByteBuffer buffer)
{
int capacity = buffer.remaining();
ByteBuffer payload = ByteBuffer.allocateDirect(capacity);
payload.put(buffer);
BufferUtil.flipToFlush(payload,0);
muxframe.setPayload(payload);
try
{
LOG.debug("notifyFrame() - {}",muxframe);
events.onMuxedFrame(muxframe);
}
catch (Throwable t)
{
LOG.warn(t);
}
}
/**
* Per section 9.1. Number Encoding in Multiplex Control
* Blocks, read the 1/3/9 byte length using Section 5.2 of RFC 6455.
*
* @param buffer
* the buffer to read from
* @return the decoded size
* @throws MuxException
* when the encoding does not make sense per the spec, or it is a value above {@link Long#MAX_VALUE}
*/
public long read139EncodedSize(ByteBuffer buffer)
{
long ret = -1;
long minValue = 0x00; // used to validate minimum # of bytes (per spec)
int cursor = 0;
byte b = buffer.get();
ret = (b & 0x7F);
if (ret == 0x7F)
{
// 9 byte length
ret = 0;
minValue = 0xFF_FF;
cursor = 8;
}
else if (ret == 0x7E)
{
// 3 byte length
ret = 0;
minValue = 0x7F;
cursor = 2;
}
else
{
// 1 byte length
// no validation of minimum bytes needed here
return ret;
}
// parse multi-byte length
while (cursor > 0)
{
ret = ret << 8;
b = buffer.get();
ret |= (b & 0xFF);
--cursor;
}
// validate minimum value per spec.
if (ret <= minValue)
{
String err = String.format("Invalid 1/3/9 length 0x%X (minimum value for chosen encoding is 0x%X)",ret,minValue);
throw new MuxException(err);
}
return ret;
}
private ByteBuffer readBlock(ByteBuffer buffer, long size)
{
if (size == 0)
{
return null;
}
if (size > buffer.remaining())
{
String err = String.format("Truncated data, expected %,d byte(s), but only %,d byte(s) remain",size,buffer.remaining());
throw new MuxException(err);
}
if (size > Integer.MAX_VALUE)
{
String err = String.format("[Int-Sane!] Buffer size %,d is too large to be supported (max allowed is %,d)",size,Integer.MAX_VALUE);
throw new MuxException(err);
}
ByteBuffer ret = ByteBuffer.allocateDirect((int)size);
BufferUtil.put(buffer,ret);
BufferUtil.flipToFlush(ret,0);
return ret;
}
/**
* Read Channel ID using Section 7. Framing techniques
*
* @param buffer
* the buffer to parse from.
* @return the channel Id
* @throws MuxException
* when the encoding does not make sense per the spec.
*/
public long readChannelId(ByteBuffer buffer)
{
long id = -1;
long minValue = 0x00; // used to validate minimum # of bytes (per spec)
byte b = buffer.get();
int cursor = -1;
if ((b & 0x80) == 0)
{
// 7 bit channel id
// no validation of minimum bytes needed here
return (b & 0x7F);
}
else if ((b & 0x40) == 0)
{
// 14 bit channel id
id = (b & 0x3F);
minValue = 0x7F;
cursor = 1;
}
else if ((b & 0x20) == 0)
{
// 21 bit channel id
id = (b & 0x1F);
minValue = 0x3F_FF;
cursor = 2;
}
else
{
// 29 bit channel id
id = (b & 0x1F);
minValue = 0x1F_FF_FF;
cursor = 3;
}
while (cursor > 0)
{
id = id << 8;
b = buffer.get();
id |= (b & 0xFF);
--cursor;
}
// validate minimum value per spec.
if (id <= minValue)
{
String err = String.format("Invalid Channel ID 0x%X (minimum value for chosen encoding is 0x%X)",id,minValue);
throw new MuxException(err);
}
return id;
}
public void setEvents(MuxParser.Listener events)
{
this.events = events;
}
}