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

org.rapidoidx.websocket.impl.WebSocketProtocol Maven / Gradle / Ivy

The newest version!
package org.rapidoidx.websocket.impl;

/*
 * #%L
 * rapidoid-x-websocket
 * %%
 * Copyright (C) 2014 - 2015 Nikolche Mihajlovski and contributors
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * #L%
 */

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.log.Log;
import org.rapidoid.net.Protocol;
import org.rapidoid.net.abstracts.Channel;
import org.rapidoid.u.U;
import org.rapidoidx.websocket.WSExchange;
import org.rapidoidx.websocket.WSHandler;

/*

 http://tools.ietf.org/html/rfc6455

 5.2. Base Framing Protocol

 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 ...                |
 +---------------------------------------------------------------+
 */

@Authors("Nikolche Mihajlovski")
@Since("3.0.0")
public class WebSocketProtocol implements Protocol {

	private static final byte[] NO_DATA = new byte[0];

	// OPCODES (http://tools.ietf.org/html/rfc6455#section-11.8)
	private static final byte OP_CONTINUATION = 0;
	private static final byte OP_TEXT = 1;
	private static final byte OP_BINARY = 2;
	private static final byte OP_CLOSE = 8;
	private static final byte OP_PING = 9;
	private static final byte OP_PONG = 10;

	private final WSHandler handler;

	public WebSocketProtocol(WSHandler handler) {
		this.handler = handler;
	}

	public void process(Channel ctx) {
		if (ctx.isInitial()) {
			Log.debug("WebSocket connection established");
			return;
		}

		byte[] msg = readMsg(ctx);
		if (msg == null) {
			return;
		}

		WSExchange x = new WSExchangeImpl(ctx, msg);

		Object result;
		try {
			result = handler.handle(x);
		} catch (Exception e) {
			throw U.rte("WebSocket message processing error!", e);
		}

		if (result != null) {
			if (result instanceof byte[]) {
				x.send((byte[]) result);
			} else if (result instanceof String) {
				x.send((String) result);
			}
		}
	}

	protected static byte[] readMsg(Channel ctx) {

		byte[] head = ctx.input().readNbytes(2);
		byte head0 = head[0];
		byte head1 = head[1];

		byte opcode = (byte) (0xF & head0); // bits 4 - 7

		U.must(1 == (0xFF & head0) >> 7, "FIN bit wasn't set!");

		switch (opcode) {
		case OP_CONTINUATION:
			throw U.rte("The CONTINUATION frame type is currently not supported!");

		case OP_TEXT:
			return readTextFrame(ctx, head1);

		case OP_BINARY:
			throw U.rte("The BINARY frame type is currently not supported!");

		case OP_CLOSE:
			ctx.close();
			return null;

		case OP_PING:
			throw U.rte("The PING frame type is currently not supported!");

		case OP_PONG:
			throw U.rte("The PONG frame type is currently not supported!");

		default:
			throw U.notExpected();
		}
	}

	protected static byte[] readTextFrame(Channel ctx, byte head1) {
		U.must(1 == (0xFF & head1) >> 7, "Data must be masked!");

		int datalen = (127 & head1);
		U.must(datalen < 126);

		if (datalen > 0) {
			byte[] maskKey = ctx.input().readNbytes(4);
			byte[] data = ctx.input().readNbytes(datalen);

			maskUnmask(maskKey, data);

			return data;
		} else {
			return NO_DATA;
		}
	}

	protected static void maskUnmask(byte[] maskKey, byte[] data) {
		for (int i = 0; i < data.length; i++) {
			data[i] = (byte) (data[i] ^ maskKey[i % 4]);
		}
	}

	protected static void writeMsg(Channel ctx, byte[] msg) {

		U.must(msg.length < 126);

		// FIN bit set and text frame opcode
		ctx.output().append((byte) 0x81); // 10000001

		// set length, no mask
		ctx.output().append((byte) msg.length);

		// append data
		ctx.output().append(msg);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy