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    *   https://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: https://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.ByteOrder;
64  import java.util.List;
65  
66  /**
67   * <p>
68   * Encodes a web socket frame into wire protocol version 8 format. This code was forked from <a
69   * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
70   * </p>
71   */
72  public class WebSocket08FrameEncoder extends MessageToMessageEncoder<WebSocketFrame> implements WebSocketFrameEncoder {
73  
74      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);
75  
76      private static final byte OPCODE_CONT = 0x0;
77      private static final byte OPCODE_TEXT = 0x1;
78      private static final byte OPCODE_BINARY = 0x2;
79      private static final byte OPCODE_CLOSE = 0x8;
80      private static final byte OPCODE_PING = 0x9;
81      private static final byte OPCODE_PONG = 0xA;
82  
83      /**
84       * The size threshold for gathering writes. Non-Masked messages bigger than this size will be sent fragmented as
85       * a header and a content ByteBuf whereas messages smaller than the size will be merged into a single buffer and
86       * sent at once.<br>
87       * Masked messages will always be sent at once.
88       */
89      private static final int GATHERING_WRITE_THRESHOLD = 1024;
90  
91      private final WebSocketFrameMaskGenerator maskGenerator;
92  
93      /**
94       * Constructor
95       *
96       * @param maskPayload
97       *            Web socket clients must set this to true to mask payload. Server implementations must set this to
98       *            false.
99       */
100     public WebSocket08FrameEncoder(boolean maskPayload) {
101         this(maskPayload ? RandomWebSocketFrameMaskGenerator.INSTANCE : null);
102     }
103 
104     /**
105      * Constructor
106      *
107      * @param maskGenerator
108      *            Web socket clients must set this to {@code non null} to mask payload.
109      *            Server implementations must set this to {@code null}.
110      */
111     public WebSocket08FrameEncoder(WebSocketFrameMaskGenerator maskGenerator) {
112         super(WebSocketFrame.class);
113         this.maskGenerator = maskGenerator;
114     }
115 
116     @Override
117     protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
118         final ByteBuf data = msg.content();
119 
120         byte opcode = getOpCode(msg);
121 
122         int length = data.readableBytes();
123 
124         if (logger.isTraceEnabled()) {
125             logger.trace("Encoding WebSocket Frame opCode={} length={}", opcode, length);
126         }
127 
128         int b0 = 0;
129         if (msg.isFinalFragment()) {
130             b0 |= 1 << 7;
131         }
132         b0 |= (msg.rsv() & 0x07) << 4;
133         b0 |= opcode & 0x7F;
134 
135         if (opcode == OPCODE_PING && length > 125) {
136             throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length);
137         }
138 
139         boolean release = true;
140         ByteBuf buf = null;
141         try {
142             int maskLength = maskGenerator != null ? 4 : 0;
143             if (length <= 125) {
144                 int size = 2 + maskLength + length;
145                 buf = ctx.alloc().buffer(size);
146                 buf.writeByte(b0);
147                 byte b = (byte) (maskGenerator != null ? 0x80 | length : length);
148                 buf.writeByte(b);
149             } else if (length <= 0xFFFF) {
150                 int size = 4 + maskLength;
151                 if (maskGenerator != null || length <= GATHERING_WRITE_THRESHOLD) {
152                     size += length;
153                 }
154                 buf = ctx.alloc().buffer(size);
155                 buf.writeByte(b0);
156                 buf.writeByte(maskGenerator != null ? 0xFE : 126);
157                 buf.writeByte(length >>> 8 & 0xFF);
158                 buf.writeByte(length & 0xFF);
159             } else {
160                 int size = 10 + maskLength;
161                 if (maskGenerator != null) {
162                     size += length;
163                 }
164                 buf = ctx.alloc().buffer(size);
165                 buf.writeByte(b0);
166                 buf.writeByte(maskGenerator != null ? 0xFF : 127);
167                 buf.writeLong(length);
168             }
169 
170             // Write payload
171             if (maskGenerator != null) {
172                 int mask = maskGenerator.nextMask();
173                 buf.writeInt(mask);
174 
175                 // If the mask is 0 we can skip all the XOR operations.
176                 if (mask != 0) {
177                     if (length > 0) {
178                         ByteOrder srcOrder = data.order();
179                         ByteOrder dstOrder = buf.order();
180 
181                         int i = data.readerIndex();
182                         int end = data.writerIndex();
183 
184                         if (srcOrder == dstOrder) {
185                             // Use the optimized path only when byte orders match.
186                             // Avoid sign extension on widening primitive conversion
187                             long longMask = mask & 0xFFFFFFFFL;
188                             longMask |= longMask << 32;
189 
190                             // If the byte order of our buffers it little endian we have to bring our mask
191                             // into the same format, because getInt() and writeInt() will use a reversed byte order
192                             if (srcOrder == ByteOrder.LITTLE_ENDIAN) {
193                                 longMask = Long.reverseBytes(longMask);
194                             }
195 
196                             for (int lim = end - 7; i < lim; i += 8) {
197                                 buf.writeLong(data.getLong(i) ^ longMask);
198                             }
199 
200                             if (i < end - 3) {
201                                 buf.writeInt(data.getInt(i) ^ (int) longMask);
202                                 i += 4;
203                             }
204                         }
205                         int maskOffset = 0;
206                         for (; i < end; i++) {
207                             byte byteData = data.getByte(i);
208                             buf.writeByte(byteData ^ WebSocketUtil.byteAtIndex(mask, maskOffset++ & 3));
209                         }
210                     }
211                     out.add(buf);
212                 } else {
213                     addBuffers(buf, data, out);
214                 }
215             } else {
216                 addBuffers(buf, data, out);
217             }
218             release = false;
219         } finally {
220             if (release && buf != null) {
221                 buf.release();
222             }
223         }
224     }
225 
226     private static byte getOpCode(WebSocketFrame msg) {
227         if (msg instanceof TextWebSocketFrame) {
228             return OPCODE_TEXT;
229         }
230         if (msg instanceof BinaryWebSocketFrame) {
231             return OPCODE_BINARY;
232         }
233         if (msg instanceof PingWebSocketFrame) {
234             return OPCODE_PING;
235         }
236         if (msg instanceof PongWebSocketFrame) {
237             return OPCODE_PONG;
238         }
239         if (msg instanceof CloseWebSocketFrame) {
240             return OPCODE_CLOSE;
241         }
242         if (msg instanceof ContinuationWebSocketFrame) {
243             return OPCODE_CONT;
244         }
245         throw new UnsupportedOperationException("Cannot encode frame of type: " + msg.getClass().getName());
246     }
247 
248     private static void addBuffers(ByteBuf buf, ByteBuf data, List<Object> out) {
249         int readableBytes = data.readableBytes();
250         if (buf.writableBytes() >= readableBytes) {
251             // merge buffers as this is cheaper then a gathering write if the payload is small enough
252             buf.writeBytes(data);
253             out.add(buf);
254         } else {
255             out.add(buf);
256             if (readableBytes > 0) {
257                 out.add(data.retain());
258             }
259         }
260     }
261 }