package jxm.JWS.DF; import jxm.CHRF; import jxm.JWS.EX.IncompleteHandshakeException; import jxm.JWS.EX.InvalidDataException; import jxm.JWS.EX.InvalidHandshakeException; import jxm.JWS.EX.LimitExedeedException; import jxm.JWS.FR.CloseFrame; import jxm.JWS.FR.FrameBuilder; import jxm.JWS.FR.Framedata; import jxm.JWS.FR.FramedataImpl1; import jxm.JWS.WebSocket; import jxm.JWS.HS.*; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; /** * Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered. */ public abstract class Draft { public enum HandshakeState { /** * Handshake matched this Draft successfully */ MATCHED, /** * Handshake is does not match this Draft */ NOT_MATCHED } public enum CloseHandshakeType { NONE, ONEWAY, TWOWAY } public static int MAX_FAME_SIZE = 1000 * 1; public static int INITIAL_FAMESIZE = 64; public static final byte[] FLASH_POLICY_REQUEST = CHRF.utf8Bytes("<policy-file-request/>\0"); /** * In some cases the handshake will be parsed different depending on whether */ protected WebSocket.Role role = null; protected Framedata.Opcode continuousFrameType = null; public static ByteBuffer readLine(ByteBuffer buf) { ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining()); byte prev = '0'; byte cur = '0'; while (buf.hasRemaining()) { prev = cur; cur = buf.get(); sbuf.put(cur); if (prev == (byte) '\r' && cur == (byte) '\n') { sbuf.limit(sbuf.position() - 2); sbuf.position(0); return sbuf; } } // ensure that there wont be any bytes skipped buf.position(buf.position() - sbuf.position()); return null; } public static String readStringLine(ByteBuffer buf) { ByteBuffer b = readLine(buf); return b == null ? null : CHRF.stringAscii(b.array(), 0, b.limit()); } public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf, WebSocket.Role role) throws InvalidHandshakeException, IncompleteHandshakeException { HandshakeBuilder handshake; String line = readStringLine(buf); if (line == null) throw new IncompleteHandshakeException(buf.capacity() + 128); String[] firstLineTokens = line.split(" ", 3);// eg. HTTP/1.1 101 Switching the Protocols if (firstLineTokens.length != 3) { throw new InvalidHandshakeException(); } if (role == WebSocket.Role.CLIENT) { // translating/parsing the response from the SERVER handshake = new HandshakeImpl1Server(); ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1])); serverhandshake.setHttpStatusMessage(firstLineTokens[2]); } else { // translating/parsing the request from the CLIENT ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); clienthandshake.setResourceDescriptor(firstLineTokens[1]); handshake = clienthandshake; } line = readStringLine(buf); while (line != null && line.length() > 0) { String[] pair = line.split(":", 2); if (pair.length != 2) throw new InvalidHandshakeException("not an http header"); handshake.put(pair[0], pair[1].replaceFirst("^ +", "")); line = readStringLine(buf); } if (line == null) throw new IncompleteHandshakeException(); return handshake; } public abstract HandshakeState acceptHandshakeAsClient(ClientHandshake request, ServerHandshake response) throws InvalidHandshakeException; public abstract HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata) throws InvalidHandshakeException; protected boolean basicAccept(Handshakedata handshakedata) { return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase("websocket") && handshakedata.getFieldValue("Connection").toLowerCase(Locale.ENGLISH).contains("upgrade"); } public abstract ByteBuffer createBinaryFrame(Framedata framedata); // TODO Allow to send data on the base of an Iterator or InputStream public abstract List<Framedata> createFrames(ByteBuffer binary, boolean mask); public abstract List<Framedata> createFrames(String text, boolean mask); public List<Framedata> continuousFrame(Framedata.Opcode op, ByteBuffer buffer, boolean fin) { if (op != Framedata.Opcode.BINARY && op != Framedata.Opcode.TEXT && op != Framedata.Opcode.TEXT) { throw new IllegalArgumentException("Only Opcode.BINARY or Opcode.TEXT are allowed"); } if (continuousFrameType != null) { continuousFrameType = Framedata.Opcode.CONTINUOUS; } else { continuousFrameType = op; } FrameBuilder bui = new FramedataImpl1(continuousFrameType); try { bui.setPayload(buffer); } catch (InvalidDataException e) { throw new RuntimeException(e); // can only happen when one builds close frames(Opcode.Close) } bui.setFin(fin); if (fin) { continuousFrameType = null; } else { continuousFrameType = op; } return Collections.singletonList((Framedata) bui); } public abstract void reset(); public List<ByteBuffer> createHandshake(Handshakedata handshakedata, WebSocket.Role ownrole) { return createHandshake(handshakedata, ownrole, true); } public List<ByteBuffer> createHandshake(Handshakedata handshakedata, WebSocket.Role ownrole, boolean withcontent) { StringBuilder bui = new StringBuilder(100); if (handshakedata instanceof ClientHandshake) { bui.append("GET "); bui.append(((ClientHandshake) handshakedata).getResourceDescriptor()); bui.append(" HTTP/1.1"); } else if (handshakedata instanceof ServerHandshake) { bui.append("HTTP/1.1 101 " + ((ServerHandshake) handshakedata).getHttpStatusMessage()); } else { throw new RuntimeException("unknow role"); } bui.append("\r\n"); Iterator<String> it = handshakedata.iterateHttpFields(); while (it.hasNext()) { String fieldname = it.next(); String fieldvalue = handshakedata.getFieldValue(fieldname); bui.append(fieldname); bui.append(": "); bui.append(fieldvalue); bui.append("\r\n"); } bui.append("\r\n"); byte[] httpheader = CHRF.asciiBytes(bui.toString()); byte[] content = withcontent ? handshakedata.getContent() : null; ByteBuffer bytebuffer = ByteBuffer.allocate((content == null ? 0 : content.length) + httpheader.length); bytebuffer.put(httpheader); if (content != null) bytebuffer.put(content); bytebuffer.flip(); return Collections.singletonList(bytebuffer); } public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient(ClientHandshakeBuilder request) throws InvalidHandshakeException; public abstract HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException; public abstract List<Framedata> translateFrame(ByteBuffer buffer) throws InvalidDataException; public abstract CloseHandshakeType getCloseHandshakeType(); /** * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.<br> * The copy can be safely used in conjunction with a new websocket connection. */ public abstract Draft copyInstance(); public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException { return translateHandshakeHttp(buf, role); } public int checkAlloc(int bytecount) throws LimitExedeedException, InvalidDataException { if (bytecount < 0) throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count"); return bytecount; } public void setParseMode(WebSocket.Role role) { this.role = role; } public WebSocket.Role getRole() { return role; } }