/** * The MIT License * Copyright (c) 2010 Tad Glines * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.glines.socketio.server; import java.util.ArrayList; import java.util.List; public class SocketIOFrame { public static final char SEPERATOR_CHAR = '~'; public enum FrameType { UNKNOWN(-1), CLOSE(0), SESSION_ID(1), HEARTBEAT_INTERVAL(2), PING(3), PONG(4), DATA(0xE), FRAGMENT(0xF); private int value; FrameType(int value) { this.value = value; } public int value() { return value; } public static FrameType fromInt(int val) { switch (val) { case 0: return CLOSE; case 1: return SESSION_ID; case 2: return HEARTBEAT_INTERVAL; case 3: return PING; case 4: return PONG; case 0xE: return DATA; case 0xF: return FRAGMENT; default: return UNKNOWN; } } } public static final int TEXT_MESSAGE_TYPE = 0; public static final int JSON_MESSAGE_TYPE = 1; private static boolean isHexDigit(String str, int start, int end) { for (int i = start; i < end; i++) { char c = str.charAt(i); if (!Character.isDigit(c) && c < 'A' && c > 'F' && c < 'a' && c > 'f') { return false; } } return true; } public static List<SocketIOFrame> parse(String data) { List<SocketIOFrame> messages = new ArrayList<SocketIOFrame>(); int idx = 0; // Parse the data and silently ignore any part that fails to parse properly. while (data.length() > idx && data.charAt(idx) == SEPERATOR_CHAR) { int start = idx + 1; int end = data.indexOf(SEPERATOR_CHAR, start); if (-1 == end || start == end || !isHexDigit(data, start, end)) { break; } int mtype = 0; int ftype = Integer.parseInt(data.substring(start, start + 1), 16); FrameType frameType = FrameType.fromInt(ftype); if (frameType == FrameType.UNKNOWN) { break; } if (end - start > 1) { mtype = Integer.parseInt(data.substring(start + 1, end), 16); } start = end + 1; end = data.indexOf(SEPERATOR_CHAR, start); if (-1 == end || start == end || !isHexDigit(data, start, end)) { break; } int size = Integer.parseInt(data.substring(start, end), 16); start = end + 1; end = start + size; if (data.length() < end) { break; } messages.add(new SocketIOFrame(frameType, mtype, data.substring(start, end))); idx = end; } return messages; } public static String encode(FrameType type, int messageType, String data) { StringBuilder str = new StringBuilder(data.length() + 16); str.append(SEPERATOR_CHAR); str.append(Integer.toHexString(type.value())); if (messageType != TEXT_MESSAGE_TYPE) { str.append(Integer.toHexString(messageType)); } str.append(SEPERATOR_CHAR); str.append(Integer.toHexString(data.length())); str.append(SEPERATOR_CHAR); str.append(data); return str.toString(); } private final FrameType frameType; private final int messageType; private final String data; public SocketIOFrame(FrameType frameType, int messageType, String data) { this.frameType = frameType; this.messageType = messageType; this.data = data; } public FrameType getFrameType() { return frameType; } public int getMessageType() { return messageType; } public String getData() { return data; } public String encode() { return encode(frameType, messageType, data); } }