1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.websocket;
20
21import java.io.IOException;
22import java.math.BigInteger;
23
24import org.eclipse.jetty.io.Buffer;
25import org.eclipse.jetty.io.EndPoint;
26import org.eclipse.jetty.io.EofException;
27
28
29/* ------------------------------------------------------------ */
30/** WebSocketGenerator.
31 * This class generates websocket packets.
32 * It is fully synchronized because it is likely that async
33 * threads will call the addMessage methods while other
34 * threads are flushing the generator.
35 */
36public class WebSocketGeneratorD00 implements WebSocketGenerator
37{
38    final private WebSocketBuffers _buffers;
39    final private EndPoint _endp;
40    private Buffer _buffer;
41
42    public WebSocketGeneratorD00(WebSocketBuffers buffers, EndPoint endp)
43    {
44        _buffers=buffers;
45        _endp=endp;
46    }
47
48    public synchronized void addFrame(byte flags, byte opcode,byte[] content, int offset, int length) throws IOException
49    {
50        long blockFor=_endp.getMaxIdleTime();
51
52        if (_buffer==null)
53            _buffer=_buffers.getDirectBuffer();
54
55        if (_buffer.space() == 0)
56            expelBuffer(blockFor);
57
58        bufferPut(opcode, blockFor);
59
60        if (isLengthFrame(opcode))
61        {
62            // Send a length delimited frame
63
64            // How many bytes we need for the length ?
65            // We have 7 bits available, so log2(length) / 7 + 1
66            // For example, 50000 bytes is 2 8-bytes: 11000011 01010000
67            // but we need to write it in 3 7-bytes 0000011 0000110 1010000
68            // 65536 == 1 00000000 00000000 => 100 0000000 0000000
69            int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
70            for (int i = lengthBytes - 1; i > 0; --i)
71            {
72                byte lengthByte = (byte)(0x80 | (0x7F & (length >> 7 * i)));
73                bufferPut(lengthByte, blockFor);
74            }
75            bufferPut((byte)(0x7F & length), blockFor);
76        }
77
78        int remaining = length;
79        while (remaining > 0)
80        {
81            int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
82            _buffer.put(content, offset + (length - remaining), chunk);
83            remaining -= chunk;
84            if (_buffer.space() > 0)
85            {
86                if (!isLengthFrame(opcode))
87                    _buffer.put((byte)0xFF);
88                // Gently flush the data, issuing a non-blocking write
89                flushBuffer();
90            }
91            else
92            {
93                // Forcibly flush the data, issuing a blocking write
94                expelBuffer(blockFor);
95                if (remaining == 0)
96                {
97                    if (!isLengthFrame(opcode))
98                        _buffer.put((byte)0xFF);
99                    // Gently flush the data, issuing a non-blocking write
100                    flushBuffer();
101                }
102            }
103        }
104    }
105
106    private synchronized boolean isLengthFrame(byte frame)
107    {
108        return (frame & WebSocketConnectionD00.LENGTH_FRAME) == WebSocketConnectionD00.LENGTH_FRAME;
109    }
110
111    private synchronized void bufferPut(byte datum, long blockFor) throws IOException
112    {
113        if (_buffer==null)
114            _buffer=_buffers.getDirectBuffer();
115        _buffer.put(datum);
116        if (_buffer.space() == 0)
117            expelBuffer(blockFor);
118    }
119
120    public synchronized int flush(int blockFor) throws IOException
121    {
122        return expelBuffer(blockFor);
123    }
124
125    public synchronized int flush() throws IOException
126    {
127        int flushed = flushBuffer();
128        if (_buffer!=null && _buffer.length()==0)
129        {
130            _buffers.returnBuffer(_buffer);
131            _buffer=null;
132        }
133        return flushed;
134    }
135
136    private synchronized int flushBuffer() throws IOException
137    {
138        if (!_endp.isOpen())
139            throw new EofException();
140
141        if (_buffer!=null && _buffer.hasContent())
142            return _endp.flush(_buffer);
143
144        return 0;
145    }
146
147    private synchronized int expelBuffer(long blockFor) throws IOException
148    {
149        if (_buffer==null)
150            return 0;
151        int result = flushBuffer();
152        _buffer.compact();
153        if (!_endp.isBlocking())
154        {
155            while (_buffer.space()==0)
156            {
157                boolean ready = _endp.blockWritable(blockFor);
158                if (!ready)
159                    throw new IOException("Write timeout");
160
161                result += flushBuffer();
162                _buffer.compact();
163            }
164        }
165        return result;
166    }
167
168    public synchronized boolean isBufferEmpty()
169    {
170        return _buffer==null || _buffer.length()==0;
171    }
172
173}
174