View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  // (BSD License: http://www.opensource.org/licenses/bsd-license)
17  //
18  // Copyright (c) 2011, Joe Walnes and contributors
19  // All rights reserved.
20  //
21  // Redistribution and use in source and binary forms, with or
22  // without modification, are permitted provided that the
23  // following conditions are met:
24  //
25  // * Redistributions of source code must retain the above
26  // copyright notice, this list of conditions and the
27  // following disclaimer.
28  //
29  // * Redistributions in binary form must reproduce the above
30  // copyright notice, this list of conditions and the
31  // following disclaimer in the documentation and/or other
32  // materials provided with the distribution.
33  //
34  // * Neither the name of the Webbit nor the names of
35  // its contributors may be used to endorse or promote products
36  // derived from this software without specific prior written
37  // permission.
38  //
39  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
40  // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
41  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
42  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
44  // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
45  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
46  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
47  // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
48  // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
49  // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
50  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51  // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
52  // POSSIBILITY OF SUCH DAMAGE.
53  
54  package io.netty.handler.codec.http.websocketx;
55  
56  import io.netty.buffer.ByteBuf;
57  import io.netty.channel.ChannelHandlerContext;
58  import io.netty.handler.codec.MessageToMessageEncoder;
59  import io.netty.handler.codec.TooLongFrameException;
60  import io.netty.util.internal.logging.InternalLogger;
61  import io.netty.util.internal.logging.InternalLoggerFactory;
62  
63  import java.nio.ByteBuffer;
64  import java.nio.ByteOrder;
65  import java.util.List;
66  
67  /**
68   * <p>
69   * Encodes a web socket frame into wire protocol version 8 format. This code was forked from <a
70   * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
71   * </p>
72   */
73  public class WebSocket08FrameEncoder extends MessageToMessageEncoder<WebSocketFrame> implements WebSocketFrameEncoder {
74  
75      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);
76  
77      private static final byte OPCODE_CONT = 0x0;
78      private static final byte OPCODE_TEXT = 0x1;
79      private static final byte OPCODE_BINARY = 0x2;
80      private static final byte OPCODE_CLOSE = 0x8;
81      private static final byte OPCODE_PING = 0x9;
82      private static final byte OPCODE_PONG = 0xA;
83  
84      /**
85       * The size threshold for gathering writes. Non-Masked messages bigger than this size will be be sent fragmented as
86       * a header and a content ByteBuf whereas messages smaller than the size will be merged into a single buffer and
87       * sent at once.<br>
88       * Masked messages will always be sent at once.
89       */
90      private static final int GATHERING_WRITE_TRESHOLD = 1024;
91  
92      private final boolean maskPayload;
93  
94      /**
95       * Constructor
96       *
97       * @param maskPayload
98       *            Web socket clients must set this to true to mask payload. Server implementations must set this to
99       *            false.
100      */
101     public WebSocket08FrameEncoder(boolean maskPayload) {
102         this.maskPayload = maskPayload;
103     }
104 
105     @Override
106     protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
107         final ByteBuf data = msg.content();
108         byte[] mask;
109 
110         byte opcode;
111         if (msg instanceof TextWebSocketFrame) {
112             opcode = OPCODE_TEXT;
113         } else if (msg instanceof PingWebSocketFrame) {
114             opcode = OPCODE_PING;
115         } else if (msg instanceof PongWebSocketFrame) {
116             opcode = OPCODE_PONG;
117         } else if (msg instanceof CloseWebSocketFrame) {
118             opcode = OPCODE_CLOSE;
119         } else if (msg instanceof BinaryWebSocketFrame) {
120             opcode = OPCODE_BINARY;
121         } else if (msg instanceof ContinuationWebSocketFrame) {
122             opcode = OPCODE_CONT;
123         } else {
124             throw new UnsupportedOperationException("Cannot encode frame of type: " + msg.getClass().getName());
125         }
126 
127         int length = data.readableBytes();
128 
129         if (logger.isDebugEnabled()) {
130             logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length);
131         }
132 
133         int b0 = 0;
134         if (msg.isFinalFragment()) {
135             b0 |= 1 << 7;
136         }
137         b0 |= msg.rsv() % 8 << 4;
138         b0 |= opcode % 128;
139 
140         if (opcode == OPCODE_PING && length > 125) {
141             throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
142                     + length);
143         }
144 
145         boolean release = true;
146         ByteBuf buf = null;
147         try {
148             int maskLength = maskPayload ? 4 : 0;
149             if (length <= 125) {
150                 int size = 2 + maskLength;
151                 if (maskPayload || length <= GATHERING_WRITE_TRESHOLD) {
152                     size += length;
153                 }
154                 buf = ctx.alloc().buffer(size);
155                 buf.writeByte(b0);
156                 byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length);
157                 buf.writeByte(b);
158             } else if (length <= 0xFFFF) {
159                 int size = 4 + maskLength;
160                 if (maskPayload || length <= GATHERING_WRITE_TRESHOLD) {
161                     size += length;
162                 }
163                 buf = ctx.alloc().buffer(size);
164                 buf.writeByte(b0);
165                 buf.writeByte(maskPayload ? 0xFE : 126);
166                 buf.writeByte(length >>> 8 & 0xFF);
167                 buf.writeByte(length & 0xFF);
168             } else {
169                 int size = 10 + maskLength;
170                 if (maskPayload || length <= GATHERING_WRITE_TRESHOLD) {
171                     size += length;
172                 }
173                 buf = ctx.alloc().buffer(size);
174                 buf.writeByte(b0);
175                 buf.writeByte(maskPayload ? 0xFF : 127);
176                 buf.writeLong(length);
177             }
178 
179             // Write payload
180             if (maskPayload) {
181                 int random = (int) (Math.random() * Integer.MAX_VALUE);
182                 mask = ByteBuffer.allocate(4).putInt(random).array();
183                 buf.writeBytes(mask);
184 
185                 ByteOrder srcOrder = data.order();
186                 ByteOrder dstOrder = buf.order();
187 
188                 int counter = 0;
189                 int i = data.readerIndex();
190                 int end = data.writerIndex();
191 
192                 if (srcOrder == dstOrder) {
193                     // Use the optimized path only when byte orders match
194                     // Remark: & 0xFF is necessary because Java will do signed expansion from
195                     // byte to int which we don't want.
196                     int intMask = ((mask[0] & 0xFF) << 24)
197                                 | ((mask[1] & 0xFF) << 16)
198                                 | ((mask[2] & 0xFF) << 8)
199                                 | (mask[3] & 0xFF);
200 
201                     // If the byte order of our buffers it little endian we have to bring our mask
202                     // into the same format, because getInt() and writeInt() will use a reversed byte order
203                     if (srcOrder == ByteOrder.LITTLE_ENDIAN) {
204                         intMask = Integer.reverseBytes(intMask);
205                     }
206 
207                     for (; i + 3 < end; i += 4) {
208                         int intData = data.getInt(i);
209                         buf.writeInt(intData ^ intMask);
210                     }
211                 }
212                 for (; i < end; i++) {
213                     byte byteData = data.getByte(i);
214                     buf.writeByte(byteData ^ mask[counter++ % 4]);
215                 }
216                 out.add(buf);
217             } else {
218                 if (buf.writableBytes() >= data.readableBytes()) {
219                     // merge buffers as this is cheaper then a gathering write if the payload is small enough
220                     buf.writeBytes(data);
221                     out.add(buf);
222                 } else {
223                     out.add(buf);
224                     out.add(data.retain());
225                 }
226             }
227             release = false;
228         } finally {
229             if (release && buf != null) {
230                 buf.release();
231             }
232         }
233     }
234 }