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.netty5.handler.codec.http.websocketx;
55  
56  import io.netty5.buffer.api.Buffer;
57  import io.netty5.channel.ChannelHandlerContext;
58  import io.netty5.handler.codec.MessageToMessageEncoder;
59  import io.netty5.handler.codec.TooLongFrameException;
60  import io.netty5.util.internal.logging.InternalLogger;
61  import io.netty5.util.internal.logging.InternalLoggerFactory;
62  
63  import java.util.List;
64  import java.util.concurrent.ThreadLocalRandom;
65  
66  /**
67   * <p>
68   * Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same as V8.
69   * </p>
70   */
71  public class WebSocket13FrameEncoder extends MessageToMessageEncoder<WebSocketFrame> implements WebSocketFrameEncoder {
72  
73      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket13FrameEncoder.class);
74      private static final byte OPCODE_CONT = 0x0;
75      private static final byte OPCODE_TEXT = 0x1;
76      private static final byte OPCODE_BINARY = 0x2;
77      private static final byte OPCODE_CLOSE = 0x8;
78      private static final byte OPCODE_PING = 0x9;
79      private static final byte OPCODE_PONG = 0xA;
80      /**
81       * The size threshold for gathering writes. Non-Masked messages bigger than this size will be be sent fragmented as
82       * a header and a content ByteBuf whereas messages smaller than the size will be merged into a single buffer and
83       * sent at once.<br>
84       * Masked messages will always be sent at once.
85       */
86      private static final int GATHERING_WRITE_THRESHOLD = 1024;
87      private final boolean maskPayload;
88  
89      /**
90       * Constructor
91       *
92       * @param maskPayload
93       *            Web socket clients must set this to true to mask payload. Server implementations must set this to
94       *            false.
95       */
96      public WebSocket13FrameEncoder(boolean maskPayload) {
97          this.maskPayload = maskPayload;
98      }
99  
100     @Override
101     protected void encodeAndClose(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
102         final Buffer data = msg.binaryData();
103         byte[] mask;
104 
105         byte opcode;
106         if (msg instanceof TextWebSocketFrame) {
107             opcode = OPCODE_TEXT;
108         } else if (msg instanceof PingWebSocketFrame) {
109             opcode = OPCODE_PING;
110         } else if (msg instanceof PongWebSocketFrame) {
111             opcode = OPCODE_PONG;
112         } else if (msg instanceof CloseWebSocketFrame) {
113             opcode = OPCODE_CLOSE;
114         } else if (msg instanceof BinaryWebSocketFrame) {
115             opcode = OPCODE_BINARY;
116         } else if (msg instanceof ContinuationWebSocketFrame) {
117             opcode = OPCODE_CONT;
118         } else {
119             throw new UnsupportedOperationException("Cannot encode frame of type: " + msg.getClass().getName());
120         }
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() % 8 << 4;
133         b0 |= opcode % 128;
134 
135         if (opcode == OPCODE_PING && length > 125) {
136             throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
137                     + length);
138         }
139 
140         Buffer buf = null;
141         try {
142             int maskLength = maskPayload ? 4 : 0;
143             if (length <= 125) {
144                 int size = 2 + maskLength;
145                 if (maskPayload || length <= GATHERING_WRITE_THRESHOLD) {
146                     size += length;
147                 }
148                 buf = ctx.bufferAllocator().allocate(size);
149                 buf.writeByte((byte) b0);
150                 byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length);
151                 buf.writeByte(b);
152             } else if (length <= 0xFFFF) {
153                 int size = 4 + maskLength;
154                 if (maskPayload || length <= GATHERING_WRITE_THRESHOLD) {
155                     size += length;
156                 }
157                 buf = ctx.bufferAllocator().allocate(size);
158                 buf.writeByte((byte) b0);
159                 buf.writeByte((byte) (maskPayload ? 0xFE : 126));
160                 buf.writeByte((byte) (length >>> 8 & 0xFF));
161                 buf.writeByte((byte) (length & 0xFF));
162             } else {
163                 int size = 10 + maskLength;
164                 if (maskPayload || length <= GATHERING_WRITE_THRESHOLD) {
165                     size += length;
166                 }
167                 buf = ctx.bufferAllocator().allocate(size);
168                 buf.writeByte((byte) b0);
169                 buf.writeByte((byte) (maskPayload ? 0xFF : 127));
170                 buf.writeLong(length);
171             }
172 
173             // Write payload
174             if (maskPayload) {
175                 mask = new byte[4];
176                 ThreadLocalRandom.current().nextBytes(mask);
177                 buf.writeBytes(mask);
178 
179                 int counter = 0;
180                 int i = data.readerOffset();
181                 int end = data.writerOffset();
182 
183                 // Remark: & 0xFF is necessary because Java will do signed expansion from
184                 // byte to int which we don't want.
185                 int intMask = (mask[0] & 0xFF) << 24
186                               | (mask[1] & 0xFF) << 16
187                               | (mask[2] & 0xFF) << 8
188                               | mask[3] & 0xFF;
189 
190                 for (; i + 3 < end; i += 4) {
191                     int intData = data.getInt(i);
192                     buf.writeInt(intData ^ intMask);
193                 }
194                 for (; i < end; i++) {
195                     byte byteData = data.getByte(i);
196                     buf.writeByte((byte) (byteData ^ mask[counter++ % 4]));
197                 }
198                 out.add(buf);
199             } else {
200                 if (buf.writableBytes() >= data.readableBytes()) {
201                     // merge buffers as this is cheaper then a gathering write if the payload is small enough
202                     buf.writeBytes(data);
203                     data.close();
204                     out.add(buf);
205                 } else {
206                     out.add(buf);
207                     out.add(data);
208                 }
209             }
210         } catch (Throwable t) {
211             if (buf != null) {
212                 buf.close();
213             }
214             throw t;
215         }
216     }
217 }