/******************************************************************************* * Copyright (c) 2011 Intalio, Inc. * ====================================================================== * 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. *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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; import java.io.IOException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; /* ------------------------------------------------------------ */ /** WebSocketGenerator. * This class generates websocket packets. * It is fully synchronized because it is likely that async * threads will call the addMessage methods while other * threads are flushing the generator. */ public class WebSocketGeneratorD06 implements WebSocketGenerator { final private WebSocketBuffers _buffers; final private EndPoint _endp; private Buffer _buffer; private final byte[] _mask=new byte[4]; private int _m; private boolean _opsent; private final MaskGen _maskGen; public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp) { _buffers=buffers; _endp=endp; _maskGen=null; } public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) { _buffers=buffers; _endp=endp; _maskGen=maskGen; } public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException { // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length); long blockFor=_endp.getMaxIdleTime(); if (_buffer==null) _buffer=(_maskGen!=null)?_buffers.getBuffer():_buffers.getDirectBuffer(); boolean last=WebSocketConnectionD06.isLastFrame(flags); opcode=(byte)(((0xf&flags)<<4)+0xf&opcode); int space=(_maskGen!=null)?14:10; do { opcode = _opsent?WebSocketConnectionD06.OP_CONTINUATION:opcode; _opsent=true; int payload=length; if (payload+space>_buffer.capacity()) { // We must fragement, so clear FIN bit opcode&=(byte)0x7F; // Clear the FIN bit payload=_buffer.capacity()-space; } else if (last) opcode|=(byte)0x80; // Set the FIN bit // ensure there is space for header if (_buffer.space() <= space) expelBuffer(blockFor); // write mask if ((_maskGen!=null)) { _maskGen.genMask(_mask); _m=0; _buffer.put(_mask); } // write the opcode and length if (payload>0xffff) { bufferPut(new byte[]{ opcode, (byte)0x7f, (byte)0, (byte)0, (byte)0, (byte)0, (byte)((payload>>24)&0xff), (byte)((payload>>16)&0xff), (byte)((payload>>8)&0xff), (byte)(payload&0xff)}); } else if (payload >=0x7e) { bufferPut(new byte[]{ opcode, (byte)0x7e, (byte)(payload>>8), (byte)(payload&0xff)}); } else { bufferPut(opcode); bufferPut((byte)payload); } // write payload int remaining = payload; while (remaining > 0) { _buffer.compact(); int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); if ((_maskGen!=null)) { for (int i=0;i<chunk;i++) bufferPut(content[offset+ (payload-remaining)+i]); } else _buffer.put(content, offset + (payload - remaining), chunk); remaining -= chunk; if (_buffer.space() > 0) { // Gently flush the data, issuing a non-blocking write flushBuffer(); } else { // Forcibly flush the data, issuing a blocking write expelBuffer(blockFor); if (remaining == 0) { // Gently flush the data, issuing a non-blocking write flushBuffer(); } } } offset+=payload; length-=payload; } while (length>0); _opsent=!last; } private synchronized void bufferPut(byte[] data) throws IOException { if (_maskGen!=null) for (int i=0;i<data.length;i++) data[i]^=_mask[+_m++%4]; _buffer.put(data); } private synchronized void bufferPut(byte data) throws IOException { _buffer.put((byte)(data^_mask[+_m++%4])); } public synchronized int flush(int blockFor) throws IOException { return expelBuffer(blockFor); } public synchronized int flush() throws IOException { int flushed = flushBuffer(); if (_buffer!=null && _buffer.length()==0) { _buffers.returnBuffer(_buffer); _buffer=null; } return flushed; } private synchronized int flushBuffer() throws IOException { if (!_endp.isOpen()) throw new EofException(); if (_buffer!=null) return _endp.flush(_buffer); return 0; } private synchronized int expelBuffer(long blockFor) throws IOException { if (_buffer==null) return 0; int result = flushBuffer(); _buffer.compact(); if (!_endp.isBlocking()) { while (_buffer.space()==0) { boolean ready = _endp.blockWritable(blockFor); if (!ready) throw new IOException("Write timeout"); result += flushBuffer(); _buffer.compact(); } } return result; } public synchronized boolean isBufferEmpty() { return _buffer==null || _buffer.length()==0; } }