All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javaxt.websocket.Frame Maven / Gradle / Ivy

package javaxt.websocket;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Random;

//******************************************************************************
//**  Frame
//******************************************************************************
/**
 * Used to represent a message sent or received via a websocket connection.
 * Websocket messages are encapsulated in frames. There are 2 types of frames:
 * Control Frames (e.g. CloseFrame) and Data Frames (e.g. TextFrame). This class
 * provides abstract methods to create frames and includes subclasses that
 * implement all standard frame types defined in RFC 6455.
 *
 ******************************************************************************/

public abstract class Frame {

	/**
	 * Enum which contains the different valid opcodes
	 */
	public enum Opcode {
		CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
	}

	/**
	 * Indicates that this is the final fragment in a message.
	 */
	private boolean fin;
	/**
	 * Defines the interpretation of the "Payload data".
	 */
	private Opcode optcode;

	/**
	 * The unmasked "Payload data" which was sent in this frame
	 */
	private ByteBuffer unmaskedpayload;

	/**
	 * Defines whether the "Payload data" is masked.
	 */
	private boolean transferemasked;

	/**
	 * Indicates that the rsv1 bit is set or not
	 */
	private boolean rsv1;

	/**
	 * Indicates that the rsv2 bit is set or not
	 */
	private boolean rsv2;

	/**
	 * Indicates that the rsv3 bit is set or not
	 */
	private boolean rsv3;

	/**
	 * Check if the frame is valid due to specification
	 *
	 * @throws FrameException
	 *             thrown if the frame is not a valid frame
	 */
	public abstract void isValid() throws FrameException;

	/**
	 * Constructor for a FramedataImpl without any attributes set apart from the
	 * opcode
	 *
	 * @param op
	 *            the opcode to use
	 */
	public Frame(Opcode op) {
		optcode = op;
		unmaskedpayload = ByteBuffer.allocate(0);
		fin = true;
		transferemasked = false;
		rsv1 = false;
		rsv2 = false;
		rsv3 = false;
	}

	public boolean isRSV1() {
		return rsv1;
	}

	public boolean isRSV2() {
		return rsv2;
	}

	public boolean isRSV3() {
		return rsv3;
	}

	public boolean isFin() {
		return fin;
	}

	public Opcode getOpcode() {
		return optcode;
	}

	public boolean getTransfereMasked() {
		return transferemasked;
	}

	public ByteBuffer getPayloadData() {
		return unmaskedpayload;
	}

	public void append(Frame nextframe) {
		ByteBuffer b = nextframe.getPayloadData();
		if (unmaskedpayload == null) {
			unmaskedpayload = ByteBuffer.allocate(b.remaining());
			b.mark();
			unmaskedpayload.put(b);
			b.reset();
		} else {
			b.mark();
			unmaskedpayload.position(unmaskedpayload.limit());
			unmaskedpayload.limit(unmaskedpayload.capacity());

			if (b.remaining() > unmaskedpayload.remaining()) {
				ByteBuffer tmp = ByteBuffer.allocate(b.remaining() + unmaskedpayload.capacity());
				unmaskedpayload.flip();
				tmp.put(unmaskedpayload);
				tmp.put(b);
				unmaskedpayload = tmp;

			} else {
				unmaskedpayload.put(b);
			}
			unmaskedpayload.rewind();
			b.reset();
		}
		fin = nextframe.isFin();

	}

	@Override
	public String toString() {
		return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", rsv1:" + isRSV1() + ", rsv2:" + isRSV2()
		        + ", rsv3:" + isRSV3() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:"
		        + unmaskedpayload.remaining() + "], payload:"
		        + (unmaskedpayload.remaining() > 1000 ? "(too big to display)" : new String(unmaskedpayload.array()))
		        + "}";
	}

	/**
	 * Set the payload of this frame to the provided payload
	 *
	 * @param payload
	 *            the payload which is to set
	 */
	public void setPayload(ByteBuffer payload) {
		this.unmaskedpayload = payload;
	}

	/**
	 * Set the fin of this frame to the provided boolean
	 *
	 * @param fin
	 *            true if fin has to be set
	 */
	public void setFin(boolean fin) {
		this.fin = fin;
	}

	/**
	 * Set the rsv1 of this frame to the provided boolean
	 *
	 * @param rsv1
	 *            true if fin has to be set
	 */
	public void setRSV1(boolean rsv1) {
		this.rsv1 = rsv1;
	}

	/**
	 * Set the rsv2 of this frame to the provided boolean
	 *
	 * @param rsv2
	 *            true if fin has to be set
	 */
	public void setRSV2(boolean rsv2) {
		this.rsv2 = rsv2;
	}

	/**
	 * Set the rsv3 of this frame to the provided boolean
	 *
	 * @param rsv3
	 *            true if fin has to be set
	 */
	public void setRSV3(boolean rsv3) {
		this.rsv3 = rsv3;
	}

	/**
	 * Set the tranferemask of this frame to the provided boolean
	 *
	 * @param transferemasked
	 *            true if transferemasked has to be set
	 */
	public void setTransferemasked(boolean transferemasked) {
		this.transferemasked = transferemasked;
	}

	/**
	 * Get a frame with a specific opcode
	 *
	 * @param opcode
	 *            the opcode representing the frame
	 * @return the frame with a specific opcode
	 */
	public static Frame get(Opcode opcode) {
		if (opcode == null) {
			throw new IllegalArgumentException("Supplied opcode cannot be null");
		}
		switch (opcode) {
		case PING:
			return new PingFrame();
		case PONG:
			return new PongFrame();
		case TEXT:
			return new TextFrame();
		case BINARY:
			return new BinaryFrame();
		case CLOSING:
			return new CloseFrame();
		case CONTINUOUS:
			return new ContinuousFrame();
		default:
			throw new IllegalArgumentException("Supplied opcode is invalid");
		}
	}

	// **************************************************************************
	// ** DataFrame
	// **************************************************************************
	/**
	 * Class to represent a data frame.
	 */
	public static class DataFrame extends Frame {
		public DataFrame(Opcode opcode) {
			super(opcode);
		}

		@Override
		public void isValid() throws FrameException {
			// Nothing specific to check
		}
	}

	// **************************************************************************
	// ** ControlFrame
	// **************************************************************************
	/**
	 * Class to represent a control frame.
	 */
	public static abstract class ControlFrame extends Frame {
		public ControlFrame(Opcode opcode) {
			super(opcode);
		}

		@Override
		public void isValid() throws FrameException {
			if (!isFin()) {
				throw new FrameException("Control frame cant have fin==false set");
			}
			if (isRSV1()) {
				throw new FrameException("Control frame cant have rsv1==true set");
			}
			if (isRSV2()) {
				throw new FrameException("Control frame cant have rsv2==true set");
			}
			if (isRSV3()) {
				throw new FrameException("Control frame cant have rsv3==true set");
			}
		}
	}

	// **************************************************************************
	// ** PingFrame
	// **************************************************************************
	public static class PingFrame extends ControlFrame {
		public PingFrame() {
			super(Opcode.PING);
		}
	}

	// **************************************************************************
	// ** PongFrame
	// **************************************************************************
	public static class PongFrame extends ControlFrame {
		public PongFrame() {
			super(Opcode.PONG);
		}

		public PongFrame(PingFrame pingFrame) {
			super(Opcode.PONG);
			setPayload(pingFrame.getPayloadData());
		}
	}

	// **************************************************************************
	// ** TextFrame
	// **************************************************************************
	public static class TextFrame extends DataFrame {
		public TextFrame() {
			super(Opcode.TEXT);
		}

		@Override
		public void isValid() throws FrameException {
			super.isValid();
			if (!UTF8.isValid(getPayloadData())) {
				throw new FrameException(CloseFrame.NO_UTF8);
			}
		}
	}

	// **************************************************************************
	// ** BinaryFrame
	// **************************************************************************
	public static class BinaryFrame extends DataFrame {
		public BinaryFrame() {
			super(Opcode.BINARY);
		}
	}

	// **************************************************************************
	// ** ContinuousFrame
	// **************************************************************************
	public static class ContinuousFrame extends DataFrame {
		public ContinuousFrame() {
			super(Opcode.CONTINUOUS);
		}
	}

	// **************************************************************************
	// ** CloseFrame
	// **************************************************************************
	public static class CloseFrame extends ControlFrame {

		/**
		 * indicates a normal closure, meaning whatever purpose the connection
		 * was established for has been fulfilled.
		 */
		public static final int NORMAL = 1000;
		/**
		 * 1001 indicates that an endpoint is "going away", such as a server
		 * going down, or a browser having navigated away from a page.
		 */
		public static final int GOING_AWAY = 1001;
		/**
		 * 1002 indicates that an endpoint is terminating the connection due to
		 * a protocol error.
		 */
		public static final int PROTOCOL_ERROR = 1002;
		/**
		 * 1003 indicates that an endpoint is terminating the connection because
		 * it has received a type of data it cannot accept (e.g. an endpoint
		 * that understands only text data MAY send this if it receives a binary
		 * message).
		 */
		public static final int REFUSE = 1003;
		/*
		 * 1004: Reserved. The specific meaning might be defined in the future.
		 */
		/**
		 * 1005 is a reserved value and MUST NOT be set as a status code in a
		 * Close control frame by an endpoint. It is designated for use in
		 * applications expecting a status code to indicate that no status code
		 * was actually present.
		 */
		public static final int NOCODE = 1005;
		/**
		 * 1006 is a reserved value and MUST NOT be set as a status code in a
		 * Close control frame by an endpoint. It is designated for use in
		 * applications expecting a status code to indicate that the connection
		 * was closed abnormally, e.g. without sending or receiving a Close
		 * control frame.
		 */
		public static final int ABNORMAL_CLOSE = 1006;
		/**
		 * 1007 indicates that an endpoint is terminating the connection because
		 * it has received data within a message that was not consistent with
		 * the type of the message (e.g., non-UTF-8 [RFC3629] data within a text
		 * message).
		 */
		public static final int NO_UTF8 = 1007;
		/**
		 * 1008 indicates that an endpoint is terminating the connection because
		 * it has received a message that violates its policy. This is a generic
		 * status code that can be returned when there is no other more suitable
		 * status code (e.g. 1003 or 1009), or if there is a need to hide
		 * specific details about the policy.
		 */
		public static final int POLICY_VALIDATION = 1008;
		/**
		 * 1009 indicates that an endpoint is terminating the connection because
		 * it has received a message which is too big for it to process.
		 */
		public static final int TOOBIG = 1009;
		/**
		 * 1010 indicates that an endpoint (client) is terminating the
		 * connection because it has expected the server to negotiate one or
		 * more extension, but the server didn't return them in the response
		 * message of the WebSocket handshake. The list of extensions which are
		 * needed SHOULD appear in the /reason/ part of the Close frame. Note
		 * that this status code is not used by the server, because it can fail
		 * the WebSocket handshake instead.
		 */
		public static final int EXTENSION = 1010;
		/**
		 * 1011 indicates that a server is terminating the connection because it
		 * encountered an unexpected condition that prevented it from fulfilling
		 * the request.
		 **/
		public static final int UNEXPECTED_CONDITION = 1011;
		/**
		 * 1015 is a reserved value and MUST NOT be set as a status code in a
		 * Close control frame by an endpoint. It is designated for use in
		 * applications expecting a status code to indicate that the connection
		 * was closed due to a failure to perform a TLS handshake (e.g., the
		 * server certificate can't be verified).
		 **/
		public static final int TLS_ERROR = 1015;

		/**
		 * The connection had never been established
		 */
		public static final int NEVER_CONNECTED = -1;

		/**
		 * The connection had a buggy close (this should not happen)
		 */
		public static final int BUGGYCLOSE = -2;

		/**
		 * The connection was flushed and closed
		 */
		public static final int FLASHPOLICY = -3;

		/**
		 * The close code used in this close frame
		 */
		private int code;

		/**
		 * The close message used in this close frame
		 */
		private String reason;

		/**
		 * Constructor for a close frame
		 * 

* Using opcode closing and fin = true */ public CloseFrame() { super(Opcode.CLOSING); setReason(""); setCode(CloseFrame.NORMAL); } public CloseFrame(int code, String reason) { this(); setCode(code); setReason(reason); } /** * Set the close code for this close frame * * @param code * the close code */ public void setCode(int code) { this.code = code; // CloseFrame.TLS_ERROR is not allowed to be transfered over the // wire if (code == CloseFrame.TLS_ERROR) { this.code = CloseFrame.NOCODE; this.reason = ""; } updatePayload(); } /** * Set the close reason for this close frame * * @param reason * the reason code */ public void setReason(String reason) { if (reason == null) { reason = ""; } this.reason = reason; updatePayload(); } /** * Get the used close code * * @return the used close code */ public int getCloseCode() { return code; } /** * Get the message that closeframe is containing * * @return the message in this frame */ public String getMessage() { return reason; } @Override public String toString() { return super.toString() + "code: " + code; } @Override public void isValid() throws FrameException { super.isValid(); if (code == CloseFrame.NO_UTF8 && reason == null) { throw new FrameException(CloseFrame.NO_UTF8); } if (code == CloseFrame.NOCODE && 0 < reason.length()) { throw new FrameException(PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason"); } // Intentional check for code != CloseFrame.TLS_ERROR just to make // sure even if the code earlier changes if ((code > CloseFrame.UNEXPECTED_CONDITION && code < 3000 && code != CloseFrame.TLS_ERROR)) { throw new FrameException(PROTOCOL_ERROR, "Trying to send an illegal close code!"); } if (code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004) { throw new FrameException("closecode must not be sent over the wire: " + code); } } @Override public void setPayload(ByteBuffer payload) { code = CloseFrame.NOCODE; reason = ""; payload.mark(); if (payload.remaining() == 0) { code = CloseFrame.NORMAL; } else if (payload.remaining() == 1) { code = CloseFrame.PROTOCOL_ERROR; } else { if (payload.remaining() >= 2) { ByteBuffer bb = ByteBuffer.allocate(4); bb.position(2); bb.putShort(payload.getShort()); bb.position(0); code = bb.getInt(); } payload.reset(); try { int mark = payload.position();// because stringUtf8 also // creates a mark try { payload.position(payload.position() + 2); reason = UTF8.decode(payload); } catch (IllegalArgumentException e) { throw new FrameException(CloseFrame.NO_UTF8); } finally { payload.position(mark); } } catch (FrameException e) { code = CloseFrame.NO_UTF8; reason = null; } } } /** * Update the payload to represent the close code and the reason */ private void updatePayload() { byte[] by = UTF8.encode(reason); ByteBuffer buf = ByteBuffer.allocate(4); buf.putInt(code); buf.position(2); ByteBuffer pay = ByteBuffer.allocate(2 + by.length); pay.put(buf); pay.put(by); pay.rewind(); super.setPayload(pay); } @Override public ByteBuffer getPayloadData() { if (code == NOCODE) return ByteBuffer.allocate(0); return super.getPayloadData(); } } // End CloseFrame Class private final Random reuseableRandom = new Random(); public static Frame createFrame(String text, boolean mask) { TextFrame curframe = new TextFrame(); curframe.setPayload(ByteBuffer.wrap(UTF8.encode(text))); curframe.setTransferemasked(mask); try { curframe.isValid(); } catch (FrameException e) { // throw new NotSendableException( e ); } return curframe; } // ************************************************************************** // ** translateSingleFrame // ************************************************************************** public static Frame translateSingleFrame(ByteBuffer buffer) throws IncompleteFrameException, FrameException { int maxpacketsize = buffer.remaining(); int realpacketsize = 2; if (maxpacketsize < realpacketsize) { throw new IncompleteFrameException(realpacketsize); } byte b1 = buffer.get( /* 0 */ ); boolean FIN = b1 >> 8 != 0; boolean rsv1 = false; boolean rsv2 = false; boolean rsv3 = false; if ((b1 & 0x40) != 0) { rsv1 = true; } if ((b1 & 0x20) != 0) { rsv2 = true; } if ((b1 & 0x10) != 0) { rsv3 = true; } byte b2 = buffer.get( /* 1 */ ); boolean MASK = (b2 & -128) != 0; int payloadlength = (byte) (b2 & ~(byte) 128); Opcode optcode = getOpCode((byte) (b1 & 15)); if (!(payloadlength >= 0 && payloadlength <= 125)) { if (optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING) { throw new FrameException("more than 125 octets"); } if (payloadlength == 126) { realpacketsize += 2; // additional length bytes if (maxpacketsize < realpacketsize) throw new IncompleteFrameException(realpacketsize); byte[] sizebytes = new byte[3]; sizebytes[1] = buffer.get( /* 1 + 1 */ ); sizebytes[2] = buffer.get( /* 1 + 2 */ ); payloadlength = new BigInteger(sizebytes).intValue(); } else { realpacketsize += 8; // additional length bytes if (maxpacketsize < realpacketsize) throw new IncompleteFrameException(realpacketsize); byte[] bytes = new byte[8]; for (int i = 0; i < 8; i++) { bytes[i] = buffer.get( /* 1 + i */ ); } long length = new BigInteger(bytes).longValue(); if (length > Integer.MAX_VALUE) { throw new FrameException("Payloadsize is to big..."); } else { payloadlength = (int) length; } } } // int maskskeystart = foff + realpacketsize; realpacketsize += (MASK ? 4 : 0); // int payloadstart = foff + realpacketsize; realpacketsize += payloadlength; if (maxpacketsize < realpacketsize) throw new IncompleteFrameException(realpacketsize); ByteBuffer payload = ByteBuffer.allocate(checkAlloc(payloadlength)); if (MASK) { byte[] maskskey = new byte[4]; buffer.get(maskskey); for (int i = 0; i < payloadlength; i++) { payload.put((byte) (buffer.get( /* payloadstart + i */ ) ^ maskskey[i % 4])); } } else { try { payload.put(buffer.array(), buffer.position(), payload.limit()); buffer.position(buffer.position() + payload.limit()); } catch (UnsupportedOperationException e) { return null; } } Frame frame = Frame.get(optcode); frame.setFin(FIN); frame.setRSV1(rsv1); frame.setRSV2(rsv2); frame.setRSV3(rsv3); payload.flip(); frame.setPayload(payload); isFrameValid(frame); // getExtension().decodeFrame(frame); frame.isValid(); return frame; } // ************************************************************************** // ** checkAlloc // ************************************************************************** private static int checkAlloc(int bytecount) throws FrameException { if (bytecount < 0) throw new FrameException(CloseFrame.PROTOCOL_ERROR, "Negative count"); return bytecount; } // ************************************************************************** // ** getByteBuffer // ************************************************************************** public ByteBuffer getByteBuffer(boolean mask) throws java.io.IOException { Frame framedata = this; ByteBuffer mes = framedata.getPayloadData(); int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8; ByteBuffer buf = ByteBuffer .allocate(1 + (sizebytes > 1 ? sizebytes + 1 : sizebytes) + (mask ? 4 : 0) + mes.remaining()); byte optcode = getByte(framedata.getOpcode()); byte one = (byte) (framedata.isFin() ? -128 : 0); one |= optcode; buf.put(one); byte[] payloadlengthbytes = toByteArray(mes.remaining(), sizebytes); assert (payloadlengthbytes.length == sizebytes); if (sizebytes == 1) { buf.put((byte) (payloadlengthbytes[0] | (mask ? (byte) -128 : 0))); } else if (sizebytes == 2) { buf.put((byte) ((byte) 126 | (mask ? (byte) -128 : 0))); buf.put(payloadlengthbytes); } else if (sizebytes == 8) { buf.put((byte) ((byte) 127 | (mask ? (byte) -128 : 0))); buf.put(payloadlengthbytes); } else throw new RuntimeException("Size representation not supported/specified"); if (mask) { ByteBuffer maskkey = ByteBuffer.allocate(4); maskkey.putInt(reuseableRandom.nextInt()); buf.put(maskkey.array()); for (int i = 0; mes.hasRemaining(); i++) { buf.put((byte) (mes.get() ^ maskkey.get(i % 4))); } } else { buf.put(mes); // Reset the position of the bytebuffer e.g. for additional use mes.flip(); } assert (buf.remaining() == 0) : buf.remaining(); return buf; } private byte[] toByteArray(long val, int bytecount) { byte[] buffer = new byte[bytecount]; int highest = 8 * bytecount - 8; for (int i = 0; i < bytecount; i++) { buffer[i] = (byte) (val >>> (highest - 8 * i)); } return buffer; } // ************************************************************************** // ** getByte // ************************************************************************** /** * Returns a single byte representing a Frame's opcode. */ private byte getByte(Frame.Opcode opcode) { if (opcode == Frame.Opcode.CONTINUOUS) return 0; else if (opcode == Frame.Opcode.TEXT) return 1; else if (opcode == Frame.Opcode.BINARY) return 2; else if (opcode == Frame.Opcode.CLOSING) return 8; else if (opcode == Frame.Opcode.PING) return 9; else if (opcode == Frame.Opcode.PONG) return 10; throw new IllegalArgumentException("Unknown Opcode: " + opcode.toString()); } private static void isFrameValid(Frame inputFrame) throws FrameException { if (inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3()) { throw new FrameException("bad rsv" + " RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + inputFrame.isRSV3()); } } // ************************************************************************** // ** getOpCode // ************************************************************************** /** * Returns a Frame opcode associated with a given byte */ private static Opcode getOpCode(byte opcode) throws FrameException { switch (opcode) { case 0: return Opcode.CONTINUOUS; case 1: return Opcode.TEXT; case 2: return Opcode.BINARY; // 3-7 are not yet defined case 8: return Opcode.CLOSING; case 9: return Opcode.PING; case 10: return Opcode.PONG; // 11-15 are not yet defined default: throw new FrameException("Unknown opcode " + (short) opcode); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy