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 }